Skip to content
Snippets Groups Projects
Commit 7bbb8fc7 authored by Alexander Rose's avatar Alexander Rose
Browse files

wip, transient, per-instance transformations

parent 558a082f
Branches
Tags
No related merge requests found
Showing
with 142 additions and 50 deletions
...@@ -57,7 +57,9 @@ export class BoundingSphereHelper { ...@@ -57,7 +57,9 @@ export class BoundingSphereHelper {
const instanceData = this.instancesData.get(ro) const instanceData = this.instancesData.get(ro)
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, { const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
aTransform: ro.values.aTransform, aTransform: ro.values.aTransform,
matrix: ro.values.matrix,
transform: ro.values.transform, transform: ro.values.transform,
extraTransform: ro.values.extraTransform,
uInstanceCount: ro.values.uInstanceCount, uInstanceCount: ro.values.uInstanceCount,
instanceCount: ro.values.instanceCount, instanceCount: ro.values.instanceCount,
aInstance: ro.values.aInstance, aInstance: ro.values.aInstance,
......
/** /**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* *
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
...@@ -9,8 +9,18 @@ import { Mat4 } from 'mol-math/linear-algebra'; ...@@ -9,8 +9,18 @@ import { Mat4 } from 'mol-math/linear-algebra';
import { fillSerial } from 'mol-util/array'; import { fillSerial } from 'mol-util/array';
export type TransformData = { export type TransformData = {
/**
* final per-instance transform calculated for instance `i` as
* `aTransform[i] = matrix * transform[i] * extraTransform[i]`
*/
aTransform: ValueCell<Float32Array>, aTransform: ValueCell<Float32Array>,
/** global transform, see aTransform */
matrix: ValueCell<Mat4>,
/** base per-instance transform, see aTransform */
transform: ValueCell<Float32Array>, transform: ValueCell<Float32Array>,
/** additional per-instance transform, see aTransform */
extraTransform: ValueCell<Float32Array>,
uInstanceCount: ValueCell<number>, uInstanceCount: ValueCell<number>,
instanceCount: ValueCell<number>, instanceCount: ValueCell<number>,
aInstance: ValueCell<Float32Array>, aInstance: ValueCell<Float32Array>,
...@@ -18,17 +28,30 @@ export type TransformData = { ...@@ -18,17 +28,30 @@ export type TransformData = {
export function createTransform(transformArray: Float32Array, instanceCount: number, transformData?: TransformData): TransformData { export function createTransform(transformArray: Float32Array, instanceCount: number, transformData?: TransformData): TransformData {
if (transformData) { if (transformData) {
ValueCell.update(transformData.aTransform, transformArray) ValueCell.update(transformData.matrix, transformData.matrix.ref.value)
ValueCell.update(transformData.transform, new Float32Array(transformArray)) ValueCell.update(transformData.transform, transformArray)
ValueCell.update(transformData.uInstanceCount, instanceCount) ValueCell.update(transformData.uInstanceCount, instanceCount)
ValueCell.update(transformData.instanceCount, instanceCount) ValueCell.update(transformData.instanceCount, instanceCount)
const aTransform = transformData.aTransform.ref.value.length >= instanceCount * 16 ? transformData.aTransform.ref.value : new Float32Array(instanceCount * 16)
aTransform.set(transformArray)
ValueCell.update(transformData.aTransform, aTransform)
// Note that this sets `extraTransform` to identity transforms
const extraTransform = transformData.extraTransform.ref.value.length >= instanceCount * 16 ? transformData.extraTransform.ref.value : new Float32Array(instanceCount * 16)
ValueCell.update(transformData.extraTransform, fillIdentityTransform(extraTransform, instanceCount))
const aInstance = transformData.aInstance.ref.value.length >= instanceCount ? transformData.aInstance.ref.value : new Float32Array(instanceCount) const aInstance = transformData.aInstance.ref.value.length >= instanceCount ? transformData.aInstance.ref.value : new Float32Array(instanceCount)
ValueCell.update(transformData.aInstance, fillSerial(aInstance, instanceCount)) ValueCell.update(transformData.aInstance, fillSerial(aInstance, instanceCount))
updateTransformData(transformData)
return transformData return transformData
} else { } else {
return { return {
aTransform: ValueCell.create(transformArray), aTransform: ValueCell.create(new Float32Array(transformArray)),
transform: ValueCell.create(new Float32Array(transformArray)), matrix: ValueCell.create(Mat4.identity()),
transform: ValueCell.create(transformArray),
extraTransform: ValueCell.create(fillIdentityTransform(new Float32Array(instanceCount * 16), instanceCount)),
uInstanceCount: ValueCell.create(instanceCount), uInstanceCount: ValueCell.create(instanceCount),
instanceCount: ValueCell.create(instanceCount), instanceCount: ValueCell.create(instanceCount),
aInstance: ValueCell.create(fillSerial(new Float32Array(instanceCount))) aInstance: ValueCell.create(fillSerial(new Float32Array(instanceCount)))
...@@ -38,16 +61,32 @@ export function createTransform(transformArray: Float32Array, instanceCount: num ...@@ -38,16 +61,32 @@ export function createTransform(transformArray: Float32Array, instanceCount: num
const identityTransform = new Float32Array(16) const identityTransform = new Float32Array(16)
Mat4.toArray(Mat4.identity(), identityTransform, 0) Mat4.toArray(Mat4.identity(), identityTransform, 0)
export function createIdentityTransform(transformData?: TransformData): TransformData { export function createIdentityTransform(transformData?: TransformData): TransformData {
return createTransform(new Float32Array(identityTransform), 1, transformData) return createTransform(new Float32Array(identityTransform), 1, transformData)
} }
export function setTransformData(matrix: Mat4, transformData: TransformData) { export function fillIdentityTransform(transform: Float32Array, count: number) {
for (let i = 0; i < count; i++) {
transform.set(identityTransform, i * 16)
}
return transform
}
/**
* updates per-instance transform calculated for instance `i` as
* `aTransform[i] = matrix * transform[i] * extraTransform[i]`
*/
export function updateTransformData(transformData: TransformData) {
const aTransform = transformData.aTransform.ref.value
const instanceCount = transformData.instanceCount.ref.value const instanceCount = transformData.instanceCount.ref.value
const matrix = transformData.matrix.ref.value
const transform = transformData.transform.ref.value const transform = transformData.transform.ref.value
const aTransform = transformData.aTransform.ref.value const extraTransform = transformData.extraTransform.ref.value
for (let i = 0; i < instanceCount; i++) { for (let i = 0; i < instanceCount; i++) {
Mat4.mulOffset(aTransform, transform, matrix, i * 16, i * 16, 0) const i16 = i * 16
Mat4.mulOffset(aTransform, transform, extraTransform, i16, i16, i16)
Mat4.mulOffset(aTransform, aTransform, matrix, i16, i16, 0)
} }
ValueCell.update(transformData.aTransform, aTransform) ValueCell.update(transformData.aTransform, aTransform)
} }
\ No newline at end of file
...@@ -57,6 +57,7 @@ function createPoints() { ...@@ -57,6 +57,7 @@ function createPoints() {
const m4 = Mat4.identity() const m4 = Mat4.identity()
Mat4.toArray(m4, aTransform.ref.value, 0) Mat4.toArray(m4, aTransform.ref.value, 0)
const transform = ValueCell.create(new Float32Array(aTransform.ref.value)) const transform = ValueCell.create(new Float32Array(aTransform.ref.value))
const extraTransform = ValueCell.create(new Float32Array(aTransform.ref.value))
const boundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2)) const boundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2))
const invariantBoundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2)) const invariantBoundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2))
...@@ -78,7 +79,9 @@ function createPoints() { ...@@ -78,7 +79,9 @@ function createPoints() {
drawCount: ValueCell.create(3), drawCount: ValueCell.create(3),
instanceCount: ValueCell.create(1), instanceCount: ValueCell.create(1),
matrix: ValueCell.create(m4),
transform, transform,
extraTransform,
boundingSphere, boundingSphere,
invariantBoundingSphere, invariantBoundingSphere,
......
...@@ -28,7 +28,11 @@ export const DirectVolumeSchema = { ...@@ -28,7 +28,11 @@ export const DirectVolumeSchema = {
drawCount: ValueSpec('number'), drawCount: ValueSpec('number'),
instanceCount: ValueSpec('number'), instanceCount: ValueSpec('number'),
transform: AttributeSpec('float32', 16, 1),
matrix: ValueSpec('m4'),
transform: ValueSpec('float32'),
extraTransform: ValueSpec('float32'),
boundingSphere: ValueSpec('sphere'), boundingSphere: ValueSpec('sphere'),
invariantBoundingSphere: ValueSpec('sphere'), invariantBoundingSphere: ValueSpec('sphere'),
......
...@@ -19,8 +19,8 @@ export type ValueKindType = { ...@@ -19,8 +19,8 @@ export type ValueKindType = {
'boolean': string 'boolean': string
'any': any 'any': any
'm4': Mat4,
'float32': Float32Array 'float32': Float32Array
'sphere': Sphere3D 'sphere': Sphere3D
} }
export type ValueKind = keyof ValueKindType export type ValueKind = keyof ValueKindType
...@@ -191,6 +191,10 @@ export const BaseSchema = { ...@@ -191,6 +191,10 @@ export const BaseSchema = {
aInstance: AttributeSpec('float32', 1, 1), aInstance: AttributeSpec('float32', 1, 1),
aGroup: AttributeSpec('float32', 1, 0), aGroup: AttributeSpec('float32', 1, 0),
/**
* final per-instance transform calculated for instance `i` as
* `aTransform[i] = matrix * transform[i] * extraTransform[i]`
*/
aTransform: AttributeSpec('float32', 16, 1), aTransform: AttributeSpec('float32', 16, 1),
uAlpha: UniformSpec('f'), uAlpha: UniformSpec('f'),
...@@ -204,8 +208,17 @@ export const BaseSchema = { ...@@ -204,8 +208,17 @@ export const BaseSchema = {
drawCount: ValueSpec('number'), drawCount: ValueSpec('number'),
instanceCount: ValueSpec('number'), instanceCount: ValueSpec('number'),
/** global transform, see aTransform */
matrix: ValueSpec('m4'),
/** base per-instance transform, see aTransform */
transform: ValueSpec('float32'), transform: ValueSpec('float32'),
/** additional per-instance transform, see aTransform */
extraTransform: ValueSpec('float32'),
/** bounding sphere taking aTransform into account */
boundingSphere: ValueSpec('sphere'), boundingSphere: ValueSpec('sphere'),
/** bounding sphere NOT taking aTransform into account */
invariantBoundingSphere: ValueSpec('sphere'), invariantBoundingSphere: ValueSpec('sphere'),
dUseFog: DefineSpec('boolean'), dUseFog: DefineSpec('boolean'),
......
...@@ -134,7 +134,7 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> ...@@ -134,7 +134,7 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps>
register(): void { } register(): void { }
update(p: StructureAnimationProps) { update(p: StructureAnimationProps) {
let updated = PD.areEqual(StructureAnimationParams, this.params, p) let updated = !PD.areEqual(StructureAnimationParams, this.params, p)
if (this.params.rotate !== p.rotate) { if (this.params.rotate !== p.rotate) {
this.params.rotate = p.rotate this.params.rotate = p.rotate
this.animateRotate(this.params.rotate) this.animateRotate(this.params.rotate)
......
...@@ -18,6 +18,7 @@ import { Theme, ThemeRegistryContext, createEmptyTheme } from 'mol-theme/theme'; ...@@ -18,6 +18,7 @@ import { Theme, ThemeRegistryContext, createEmptyTheme } from 'mol-theme/theme';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { Mat4 } from 'mol-math/linear-algebra'; import { Mat4 } from 'mol-math/linear-algebra';
import { BaseGeometry } from 'mol-geo/geometry/base'; import { BaseGeometry } from 'mol-geo/geometry/base';
import { Visual } from './visual';
// export interface RepresentationProps { // export interface RepresentationProps {
// visuals?: string[] // visuals?: string[]
...@@ -115,15 +116,27 @@ namespace Representation { ...@@ -115,15 +116,27 @@ namespace Representation {
syncManually: boolean syncManually: boolean
/** A transformation applied to the representation's renderobjects */ /** A transformation applied to the representation's renderobjects */
transform: Mat4 transform: Mat4
/**
* A set of transformations applied to the instances within the representation's renderobjects,
* laid out as Mat4's in a Float32Array
*/
instanceTransforms: Float32Array
} }
export function createState() { export function createState(): State {
return { visible: false, pickable: false, syncManually: false, transform: Mat4.identity() } return { visible: false, pickable: false, syncManually: false, transform: Mat4.identity(), instanceTransforms: new Float32Array(Mat4.identity()) }
} }
export function updateState(state: State, update: Partial<State>) { export function updateState(state: State, update: Partial<State>) {
if (update.visible !== undefined) state.visible = update.visible if (update.visible !== undefined) state.visible = update.visible
if (update.pickable !== undefined) state.pickable = update.pickable if (update.pickable !== undefined) state.pickable = update.pickable
if (update.syncManually !== undefined) state.syncManually = update.syncManually if (update.syncManually !== undefined) state.syncManually = update.syncManually
if (update.transform !== undefined) Mat4.copy(state.transform, update.transform) if (update.transform !== undefined) Mat4.copy(state.transform, update.transform)
if (update.instanceTransforms !== undefined) {
if (update.instanceTransforms.length !== state.instanceTransforms.length) {
state.instanceTransforms = new Float32Array(update.instanceTransforms)
} else {
state.instanceTransforms.set(update.instanceTransforms)
}
}
} }
export type Any = Representation<any, any> export type Any = Representation<any, any>
...@@ -278,9 +291,11 @@ namespace Representation { ...@@ -278,9 +291,11 @@ namespace Representation {
return false return false
}, },
setState: (state: Partial<State>) => { setState: (state: Partial<State>) => {
if (state.visible !== undefined) renderObject.state.visible = state.visible if (state.visible !== undefined) Visual.setVisibility(renderObject, state.visible)
if (state.pickable !== undefined) renderObject.state.pickable = state.pickable if (state.pickable !== undefined) Visual.setPickable(renderObject, state.pickable)
// TODO transform if (state.transform !== undefined || state.instanceTransforms !== undefined) {
Visual.setTransform(renderObject, state.transform, state.instanceTransforms)
}
Representation.updateState(currentState, state) Representation.updateState(currentState, state)
}, },
......
...@@ -196,7 +196,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa ...@@ -196,7 +196,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
if (_renderObject) { if (_renderObject) {
if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible) if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible)
if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable) if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable)
if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform) if (state.transform !== undefined || state.instanceTransforms !== undefined) {
Visual.setTransform(_renderObject, state.transform, state.instanceTransforms)
}
} }
Representation.updateState(_state, state) Representation.updateState(_state, state)
......
...@@ -194,14 +194,14 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom ...@@ -194,14 +194,14 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom
} }
return changed return changed
}, },
setVisibility(value: boolean) { setVisibility(visible: boolean) {
Visual.setVisibility(renderObject, value) Visual.setVisibility(renderObject, visible)
}, },
setPickable(value: boolean) { setPickable(pickable: boolean) {
Visual.setPickable(renderObject, value) Visual.setPickable(renderObject, pickable)
}, },
setTransform(value: Mat4) { setTransform(matrix?: Mat4, instanceMatrices?: Float32Array) {
Visual.setTransform(renderObject, value) Visual.setTransform(renderObject, matrix, instanceMatrices)
}, },
destroy() { destroy() {
// TODO // TODO
......
...@@ -14,8 +14,11 @@ import { Points } from 'mol-geo/geometry/points/points'; ...@@ -14,8 +14,11 @@ import { Points } from 'mol-geo/geometry/points/points';
import { Lines } from 'mol-geo/geometry/lines/lines'; import { Lines } from 'mol-geo/geometry/lines/lines';
import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume'; import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
import { Spheres } from 'mol-geo/geometry/spheres/spheres'; import { Spheres } from 'mol-geo/geometry/spheres/spheres';
// import { Mat4 } from 'mol-math/linear-algebra';
export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { } export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> {
// setUnitsTransform(unitTransforms: { [id: number]: Mat4 }): void
}
export type StructureRepresentationProvider<P extends PD.Params> = RepresentationProvider<Structure, P> export type StructureRepresentationProvider<P extends PD.Params> = RepresentationProvider<Structure, P>
......
...@@ -163,9 +163,12 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R ...@@ -163,9 +163,12 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
} }
function setState(state: Partial<Representation.State>) { function setState(state: Partial<Representation.State>) {
if (state.visible !== undefined) visuals.forEach(({ visual }) => visual.setVisibility(state.visible!)) const { visible, pickable, transform, instanceTransforms } = state
if (state.pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(state.pickable!)) if (visible !== undefined) visuals.forEach(({ visual }) => visual.setVisibility(visible))
if (state.transform !== undefined) visuals.forEach(({ visual }) => visual.setTransform(state.transform!)) if (pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(pickable))
if (transform !== undefined || instanceTransforms !== undefined) {
visuals.forEach(({ visual }) => visual.setTransform(transform, instanceTransforms))
}
Representation.updateState(_state, state) Representation.updateState(_state, state)
} }
......
...@@ -240,14 +240,14 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry ...@@ -240,14 +240,14 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry
} }
return changed return changed
}, },
setVisibility(value: boolean) { setVisibility(visible: boolean) {
Visual.setVisibility(renderObject, value) Visual.setVisibility(renderObject, visible)
}, },
setPickable(value: boolean) { setPickable(pickable: boolean) {
Visual.setPickable(renderObject, value) Visual.setPickable(renderObject, pickable)
}, },
setTransform(value: Mat4) { setTransform(matrix?: Mat4, instanceMatrices?: Float32Array) {
Visual.setTransform(renderObject, value) Visual.setTransform(renderObject, matrix, instanceMatrices)
}, },
destroy() { destroy() {
// TODO // TODO
......
...@@ -13,7 +13,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; ...@@ -13,7 +13,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
import { WebGLContext } from 'mol-gl/webgl/context'; import { WebGLContext } from 'mol-gl/webgl/context';
import { Theme } from 'mol-theme/theme'; import { Theme } from 'mol-theme/theme';
import { Mat4 } from 'mol-math/linear-algebra'; import { Mat4 } from 'mol-math/linear-algebra';
import { setTransformData } from 'mol-geo/geometry/transform-data'; import { updateTransformData } from 'mol-geo/geometry/transform-data';
import { calculateTransformBoundingSphere } from 'mol-gl/renderable/util'; import { calculateTransformBoundingSphere } from 'mol-gl/renderable/util';
import { ValueCell } from 'mol-util'; import { ValueCell } from 'mol-util';
...@@ -31,24 +31,32 @@ interface Visual<D, P extends PD.Params> { ...@@ -31,24 +31,32 @@ interface Visual<D, P extends PD.Params> {
createOrUpdate: (ctx: VisualContext, theme: Theme, props?: Partial<PD.Values<P>>, data?: D) => Promise<void> | void createOrUpdate: (ctx: VisualContext, theme: Theme, props?: Partial<PD.Values<P>>, data?: D) => Promise<void> | void
getLoci: (pickingId: PickingId) => Loci getLoci: (pickingId: PickingId) => Loci
mark: (loci: Loci, action: MarkerAction) => boolean mark: (loci: Loci, action: MarkerAction) => boolean
setVisibility: (value: boolean) => void setVisibility: (visible: boolean) => void
setPickable: (value: boolean) => void setPickable: (pickable: boolean) => void
setTransform: (value: Mat4) => void setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array) => void
destroy: () => void destroy: () => void
} }
namespace Visual { namespace Visual {
export function setVisibility(renderObject: GraphicsRenderObject | undefined, value: boolean) { export function setVisibility(renderObject: GraphicsRenderObject | undefined, visible: boolean) {
if (renderObject) renderObject.state.visible = value if (renderObject) renderObject.state.visible = visible
} }
export function setPickable(renderObject: GraphicsRenderObject | undefined, value: boolean) { export function setPickable(renderObject: GraphicsRenderObject | undefined, pickable: boolean) {
if (renderObject) renderObject.state.pickable = value if (renderObject) renderObject.state.pickable = pickable
} }
export function setTransform(renderObject: GraphicsRenderObject | undefined, value: Mat4) { export function setTransform(renderObject: GraphicsRenderObject | undefined, transform?: Mat4, instanceTransforms?: Float32Array) {
if (renderObject) { if (renderObject && (transform || instanceTransforms)) {
const { values } = renderObject const { values } = renderObject
setTransformData(value, values) if (transform) {
Mat4.copy(values.matrix.ref.value, transform)
ValueCell.update(values.matrix, values.matrix.ref.value)
}
if (instanceTransforms) {
values.extraTransform.ref.value.set(instanceTransforms)
ValueCell.update(values.extraTransform, values.extraTransform.ref.value)
}
updateTransformData(values)
const boundingSphere = calculateTransformBoundingSphere(values.invariantBoundingSphere.ref.value, values.aTransform.ref.value, values.instanceCount.ref.value) const boundingSphere = calculateTransformBoundingSphere(values.invariantBoundingSphere.ref.value, values.aTransform.ref.value, values.instanceCount.ref.value)
ValueCell.update(values.boundingSphere, boundingSphere) ValueCell.update(values.boundingSphere, boundingSphere)
} }
......
...@@ -123,14 +123,14 @@ export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeomet ...@@ -123,14 +123,14 @@ export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeomet
} }
return changed return changed
}, },
setVisibility(value: boolean) { setVisibility(visible: boolean) {
Visual.setVisibility(renderObject, value) Visual.setVisibility(renderObject, visible)
}, },
setPickable(value: boolean) { setPickable(pickable: boolean) {
Visual.setPickable(renderObject, value) Visual.setPickable(renderObject, pickable)
}, },
setTransform(value: Mat4) { setTransform(matrix?: Mat4, instanceMatrices?: Float32Array) {
Visual.setTransform(renderObject, value) Visual.setTransform(renderObject, matrix, instanceMatrices)
}, },
destroy() { destroy() {
// TODO // TODO
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment