|
@@ -129,32 +129,6 @@ describe('nodeOps', () => {
|
|
|
it('throws an error if tag does not exist in catalogue', () => {
|
|
|
expect(() => { nodeOps.createElement('THIS_TAG_DOES_NOT_EXIST', undefined, undefined, {}) }).toThrow()
|
|
|
})
|
|
|
-
|
|
|
- it('adds material with "attach" property if instance is a material', () => {
|
|
|
- // Setup
|
|
|
- const tag = 'TresMeshStandardMaterial'
|
|
|
- const props = { args: [] }
|
|
|
-
|
|
|
- // Test
|
|
|
- const instance = nodeOps.createElement(tag, undefined, undefined, props)
|
|
|
-
|
|
|
- // Assert
|
|
|
- expect(instance?.isMaterial).toBeTruthy()
|
|
|
- expect(instance?.attach).toBe('material')
|
|
|
- })
|
|
|
-
|
|
|
- it('adds attach geometry property if instance is a geometry', () => {
|
|
|
- // Setup
|
|
|
- const tag = 'TresTorusGeometry'
|
|
|
- const props = { args: [] }
|
|
|
-
|
|
|
- // Test
|
|
|
- const instance = nodeOps.createElement(tag, undefined, undefined, props)
|
|
|
-
|
|
|
- // Assert
|
|
|
- expect(instance?.isBufferGeometry).toBeTruthy()
|
|
|
- expect(instance?.attach).toBe('geometry')
|
|
|
- })
|
|
|
})
|
|
|
|
|
|
describe('insert', () => {
|
|
@@ -185,46 +159,47 @@ describe('nodeOps', () => {
|
|
|
expect(parent.children.includes(child)).toBeTruthy()
|
|
|
})
|
|
|
|
|
|
- describe('primitive :object', () => {
|
|
|
+ describe.skip('primitive :object', () => {
|
|
|
describe('into mesh', () => {
|
|
|
- it.skip('inserts a mesh :object', () => {
|
|
|
+ it('inserts a mesh :object', () => {
|
|
|
const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
const object = new THREE.Mesh()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object })
|
|
|
|
|
|
- expect(parent.material.uuid).not.toBe(object.uuid)
|
|
|
+ expect(parent.children.length).toBe(0)
|
|
|
nodeOps.insert(primitive, parent)
|
|
|
- expect(parent.material.uuid).toBe(object.uuid)
|
|
|
+ expect(parent.children.length).toBe(1)
|
|
|
+ expect(parent.children[0]).toBe(object)
|
|
|
})
|
|
|
|
|
|
- it.skip('inserts a material :object', () => {
|
|
|
+ it('inserts a material :object', () => {
|
|
|
const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
const object = new THREE.MeshNormalMaterial()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object })
|
|
|
|
|
|
- expect(parent.material.uuid).not.toBe(object.uuid)
|
|
|
+ expect(parent.material.uuid).not.toBe(object)
|
|
|
nodeOps.insert(primitive, parent)
|
|
|
- expect(parent.material.uuid).toBe(object.uuid)
|
|
|
+ expect(parent.material).toBe(object)
|
|
|
})
|
|
|
|
|
|
- it.skip('inserts a geometry :object', () => {
|
|
|
+ it('inserts a geometry :object', () => {
|
|
|
const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
const object = new THREE.BoxGeometry()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object })
|
|
|
|
|
|
- expect(parent.material.uuid).not.toBe(object.uuid)
|
|
|
+ expect(parent.geometry).not.toBe(object)
|
|
|
nodeOps.insert(primitive, parent)
|
|
|
- expect(parent.material.uuid).toBe(object.uuid)
|
|
|
+ expect(parent.geometry).toBe(object)
|
|
|
})
|
|
|
|
|
|
- it.skip('inserts a group :object', () => {
|
|
|
+ it('inserts a group :object', () => {
|
|
|
const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
const object = new THREE.Group()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object })
|
|
|
|
|
|
- expect(parent.material.uuid).not.toBe(object.uuid)
|
|
|
+ expect(parent.children.length).toBe(0)
|
|
|
nodeOps.insert(primitive, parent)
|
|
|
- expect(parent.material.uuid).toBe(object.uuid)
|
|
|
+ expect(parent.children[0]).toBe(object)
|
|
|
})
|
|
|
})
|
|
|
})
|
|
@@ -238,22 +213,179 @@ describe('nodeOps', () => {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- it('inserts Fog as a property', () => {
|
|
|
- const parent = nodeOps.createElement('Object3D', undefined, undefined, {})
|
|
|
- const fog = nodeOps.createElement('Fog', undefined, undefined, {})
|
|
|
- nodeOps.insert(fog, parent)
|
|
|
- expect(parent.fog).toBe(fog)
|
|
|
- })
|
|
|
+ describe('attach/detach', () => {
|
|
|
+ // NOTE: Special implementation case: `attach`/`detach`
|
|
|
+ //
|
|
|
+ // Objects that aren't added to the Scene using
|
|
|
+ // `THREE.Object3D`'s `add` will generally be inserted
|
|
|
+ // using `attach` and removed using `detach`.
|
|
|
+ //
|
|
|
+ // This way of inserting/removing has special challenges:
|
|
|
+ // - The user can specify how the object is `attach`/`detach`ed
|
|
|
+ // by setting the `attach` prop.
|
|
|
+ // - Before a new value is `attach`ed, the system must record
|
|
|
+ // the current value and restore it when the new value is
|
|
|
+ // `detach`ed.
|
|
|
+ it('if "attach" prop is provided, sets `parent[attach], even if the field does not exist on the parent`', () => {
|
|
|
+ const parent = nodeOps.createElement('Object3D', undefined, undefined, {})
|
|
|
+ for (const attach of ['material', 'foo', 'bar', 'baz']) {
|
|
|
+ const child = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach })
|
|
|
+ nodeOps.insert(child, parent)
|
|
|
+ expect(parent[attach]).toBe(child)
|
|
|
+ expect(parent.children.length).toBe(0)
|
|
|
+ }
|
|
|
+ })
|
|
|
|
|
|
- it('if "attach" prop is provided, sets `parent[attach]`', () => {
|
|
|
- const parent = nodeOps.createElement('Object3D', undefined, undefined, {})
|
|
|
- for (const attach of ['material', 'foo', 'bar', 'baz']) {
|
|
|
- const child = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, {})
|
|
|
- child.attach = attach
|
|
|
- nodeOps.insert(child, parent)
|
|
|
- expect(parent[attach]).toBe(child)
|
|
|
- expect(parent.children.length).toBe(0)
|
|
|
- }
|
|
|
+ it('can attach and detach a BufferGeometry', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const previousAttach = parent.geometry
|
|
|
+ const geometry0 = nodeOps.createElement('BoxGeometry', undefined, undefined, {})
|
|
|
+ const geometry1 = nodeOps.createElement('BoxGeometry', undefined, undefined, {})
|
|
|
+
|
|
|
+ nodeOps.insert(geometry0, parent)
|
|
|
+ expect(parent.geometry).not.toBe(previousAttach)
|
|
|
+ expect(parent.geometry).toBe(geometry0)
|
|
|
+
|
|
|
+ nodeOps.remove(geometry0)
|
|
|
+ nodeOps.insert(geometry1, parent)
|
|
|
+ expect(parent.geometry).toBe(geometry1)
|
|
|
+
|
|
|
+ nodeOps.remove(geometry1)
|
|
|
+ expect(parent.geometry).toBe(previousAttach)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('can attach and detach a material', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const previousAttach = parent.material
|
|
|
+ const material0 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, {})
|
|
|
+ const material1 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, {})
|
|
|
+ nodeOps.insert(material0, parent)
|
|
|
+ expect(parent.material).toBe(material0)
|
|
|
+
|
|
|
+ nodeOps.remove(material0)
|
|
|
+ nodeOps.insert(material1, parent)
|
|
|
+ expect(parent.material).toBe(material1)
|
|
|
+
|
|
|
+ nodeOps.remove(material1)
|
|
|
+ expect(parent.material).toBe(previousAttach)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('can attach and detach a material array', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const previousMaterial = parent.material
|
|
|
+ const material0 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach: 'material-0' })
|
|
|
+ const material1 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach: 'material-1' })
|
|
|
+ const material2 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach: 'material-2' })
|
|
|
+ nodeOps.insert(material0, parent)
|
|
|
+ expect(parent.material[0]).toStrictEqual(material0)
|
|
|
+ nodeOps.insert(material1, parent)
|
|
|
+ expect(parent.material[1]).toStrictEqual(material1)
|
|
|
+ nodeOps.insert(material2, parent)
|
|
|
+ expect(parent.material[2]).toStrictEqual(material2)
|
|
|
+
|
|
|
+ nodeOps.remove(material0)
|
|
|
+ expect(parent.material[0]).toBeUndefined()
|
|
|
+ expect(parent.material[1]).toBe(material1)
|
|
|
+ expect(parent.material[2]).toBe(material2)
|
|
|
+ nodeOps.remove(material2)
|
|
|
+ expect(parent.material[0]).toBeUndefined()
|
|
|
+ expect(parent.material[1]).toBe(material1)
|
|
|
+ expect(parent.material[2]).toBeUndefined()
|
|
|
+
|
|
|
+ nodeOps.patchProp(material1, 'attach', undefined, 'material-2')
|
|
|
+ expect(parent.material[0]).toBeUndefined()
|
|
|
+ expect(parent.material[1]).toBeUndefined()
|
|
|
+ expect(parent.material[2]).toBe(material1)
|
|
|
+
|
|
|
+ nodeOps.remove(material1)
|
|
|
+ expect(parent.material).toBe(previousMaterial)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('can attach and detach fog', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const fog = nodeOps.createElement('Fog', undefined, undefined, {})
|
|
|
+ nodeOps.insert(fog, parent)
|
|
|
+ expect(parent.fog).toBe(fog)
|
|
|
+ nodeOps.remove(fog)
|
|
|
+ expect('fog' in parent).toBe(false)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('can attach and detach a "pierced" string', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const material = nodeOps.createElement('MeshBasicMaterial', undefined, undefined, { color: 'red' })
|
|
|
+ const previousColor = material.color
|
|
|
+ const color = nodeOps.createElement('Color', undefined, undefined, { attach: 'material-color' })
|
|
|
+ nodeOps.insert(material, parent)
|
|
|
+ nodeOps.insert(color, parent)
|
|
|
+ expect(parent.material.color).toBe(color)
|
|
|
+ nodeOps.remove(color)
|
|
|
+ expect(parent.material.color).toBe(previousColor)
|
|
|
+
|
|
|
+ material.alphaMap = new THREE.Texture()
|
|
|
+ const previousAlphaMap = material.alphaMap
|
|
|
+ const alphaMap = nodeOps.createElement('Texture', undefined, undefined, { attach: 'material-alpha-map' })
|
|
|
+ nodeOps.insert(alphaMap, parent)
|
|
|
+ expect(parent.material.alphaMap).toBe(alphaMap)
|
|
|
+ nodeOps.remove(alphaMap)
|
|
|
+ expect(parent.material.alphaMap).toBe(previousAlphaMap)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('attach can be patched', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const previousMaterial = parent.material
|
|
|
+ const material = nodeOps.createElement('MeshBasicMaterial', undefined, undefined, { color: 'red', attach: 'material' })
|
|
|
+ nodeOps.insert(material, parent)
|
|
|
+ expect(parent.material).toBe(material)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material, 'attach', undefined, 'foo')
|
|
|
+ expect(parent.foo).toBe(material)
|
|
|
+ expect(parent.material).toBe(previousMaterial)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material, 'attach', undefined, 'material')
|
|
|
+ expect(parent.foo).toBeUndefined()
|
|
|
+ expect(parent.material).toBe(material)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material, 'attach', undefined, 'bar')
|
|
|
+ expect(parent.bar).toBe(material)
|
|
|
+ expect(parent.material).toBe(previousMaterial)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('can attach and detach a material array by patching `attach`', () => {
|
|
|
+ const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const previousMaterial = parent.material
|
|
|
+ const material0 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach: 'material-0' })
|
|
|
+ const material1 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach: 'material-1' })
|
|
|
+ const material2 = nodeOps.createElement('MeshNormalMaterial', undefined, undefined, { attach: 'material-2' })
|
|
|
+ nodeOps.insert(material0, parent)
|
|
|
+ nodeOps.insert(material1, parent)
|
|
|
+ nodeOps.insert(material2, parent)
|
|
|
+ expect(parent.material[0]).toBe(material0)
|
|
|
+ expect(parent.material[1]).toBe(material1)
|
|
|
+ expect(parent.material[2]).toBe(material2)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material1, 'attach', undefined, 'material-0')
|
|
|
+ expect(parent.material[0]).toBe(material1)
|
|
|
+ expect(parent.material[1]).toBeUndefined()
|
|
|
+ expect(parent.material[2]).toBe(material2)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material1, 'attach', undefined, 'material-2')
|
|
|
+ expect(parent.material[0]).toBe(material0)
|
|
|
+ expect(parent.material[1]).toBeUndefined()
|
|
|
+ expect(parent.material[2]).toBe(material1)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material0, 'attach', undefined, 'foo')
|
|
|
+ expect(parent.material[0]).toBeUndefined()
|
|
|
+ expect(parent.material[1]).toBeUndefined()
|
|
|
+ expect(parent.material[2]).toBe(material1)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material1, 'attach', undefined, 'foo')
|
|
|
+ expect(parent.material[0]).toBeUndefined()
|
|
|
+ expect(parent.material[1]).toBeUndefined()
|
|
|
+ expect(parent.material[2]).toBe(material2)
|
|
|
+
|
|
|
+ nodeOps.patchProp(material2, 'attach', undefined, 'foo')
|
|
|
+ expect(parent.material).toBe(previousMaterial)
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
it('adds a material to parent.__tres.objects', () => {
|
|
@@ -299,10 +431,86 @@ describe('nodeOps', () => {
|
|
|
nodeOps.insert(geometry, parent)
|
|
|
nodeOps.insert(fog, parent)
|
|
|
expect(parent.__tres.objects.length).toBe(3)
|
|
|
- const objectSet = new Set(parent.__tres.objects)
|
|
|
- expect(objectSet.has(material)).toBe(true)
|
|
|
- expect(objectSet.has(geometry)).toBe(true)
|
|
|
- expect(objectSet.has(fog)).toBe(true)
|
|
|
+ const childrenSet = new Set(parent.__tres.objects)
|
|
|
+ expect(childrenSet.has(material)).toBe(true)
|
|
|
+ expect(childrenSet.has(geometry)).toBe(true)
|
|
|
+ expect(childrenSet.has(fog)).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it.skip('can insert the same `primitive :object` in multiple places in the scene graph', () => {
|
|
|
+ const material = new THREE.MeshNormalMaterial()
|
|
|
+ const geometry = new THREE.BoxGeometry()
|
|
|
+ const otherMaterial = new THREE.MeshBasicMaterial()
|
|
|
+ const otherGeometry = new THREE.SphereGeometry()
|
|
|
+
|
|
|
+ const grandparent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const parent0 = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const parent1 = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+ const parent2 = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
+
|
|
|
+ const materialPrimitive0 = nodeOps.createElement('primitive', undefined, undefined, { object: material })
|
|
|
+ const materialPrimitive1 = nodeOps.createElement('primitive', undefined, undefined, { object: material })
|
|
|
+ const materialPrimitive2 = nodeOps.createElement('primitive', undefined, undefined, { object: material })
|
|
|
+ const materialPrimitiveOther = nodeOps.createElement('primitive', undefined, undefined, { object: otherMaterial })
|
|
|
+
|
|
|
+ const geometryPrimitive0 = nodeOps.createElement('primitive', undefined, undefined, { object: geometry })
|
|
|
+ const geometryPrimitive1 = nodeOps.createElement('primitive', undefined, undefined, { object: geometry })
|
|
|
+ const geometryPrimitive2 = nodeOps.createElement('primitive', undefined, undefined, { object: geometry })
|
|
|
+ const geometryPrimitiveOther = nodeOps.createElement('primitive', undefined, undefined, { object: otherGeometry })
|
|
|
+
|
|
|
+ nodeOps.insert(parent0, grandparent)
|
|
|
+ nodeOps.insert(parent1, grandparent)
|
|
|
+ nodeOps.insert(parent2, grandparent)
|
|
|
+ nodeOps.insert(materialPrimitive0, parent0)
|
|
|
+ nodeOps.insert(materialPrimitive1, parent1)
|
|
|
+ nodeOps.insert(materialPrimitive2, parent2)
|
|
|
+ nodeOps.insert(geometryPrimitive0, parent0)
|
|
|
+ nodeOps.insert(geometryPrimitive1, parent1)
|
|
|
+ nodeOps.insert(geometryPrimitive2, parent2)
|
|
|
+
|
|
|
+ expect(parent0.material).toBe(material)
|
|
|
+ expect(parent1.material).toBe(material)
|
|
|
+ expect(parent2.material).toBe(material)
|
|
|
+ expect(parent0.geometry).toBe(geometry)
|
|
|
+ expect(parent1.geometry).toBe(geometry)
|
|
|
+ expect(parent2.geometry).toBe(geometry)
|
|
|
+
|
|
|
+ nodeOps.insert(materialPrimitiveOther, parent0)
|
|
|
+ nodeOps.insert(geometryPrimitiveOther, parent1)
|
|
|
+
|
|
|
+ expect(parent0.material).not.toBe(material)
|
|
|
+ expect(parent1.material).toBe(material)
|
|
|
+ expect(parent2.material).toBe(material)
|
|
|
+ expect(parent0.geometry).toBe(geometry)
|
|
|
+ expect(parent1.geometry).not.toBe(geometry)
|
|
|
+ expect(parent2.geometry).toBe(geometry)
|
|
|
+
|
|
|
+ nodeOps.insert(materialPrimitiveOther, parent1)
|
|
|
+ nodeOps.insert(geometryPrimitiveOther, parent0)
|
|
|
+
|
|
|
+ expect(parent0.material).not.toBe(material)
|
|
|
+ expect(parent1.material).not.toBe(material)
|
|
|
+ expect(parent2.material).toBe(material)
|
|
|
+ expect(parent0.geometry).not.toBe(geometry)
|
|
|
+ expect(parent1.geometry).not.toBe(geometry)
|
|
|
+ expect(parent2.geometry).toBe(geometry)
|
|
|
+
|
|
|
+ nodeOps.insert(materialPrimitiveOther, parent2)
|
|
|
+ nodeOps.insert(geometryPrimitiveOther, parent2)
|
|
|
+
|
|
|
+ expect(parent0.material).not.toBe(material)
|
|
|
+ expect(parent1.material).not.toBe(material)
|
|
|
+ expect(parent2.material).not.toBe(material)
|
|
|
+ expect(parent0.geometry).not.toBe(geometry)
|
|
|
+ expect(parent1.geometry).not.toBe(geometry)
|
|
|
+ expect(parent2.geometry).not.toBe(geometry)
|
|
|
+
|
|
|
+ expect(parent0.material).toBe(otherMaterial)
|
|
|
+ expect(parent1.material).toBe(otherMaterial)
|
|
|
+ expect(parent2.material).toBe(otherMaterial)
|
|
|
+ expect(parent0.geometry).toBe(otherGeometry)
|
|
|
+ expect(parent1.geometry).toBe(otherGeometry)
|
|
|
+ expect(parent2.geometry).toBe(otherGeometry)
|
|
|
})
|
|
|
})
|
|
|
|
|
@@ -312,7 +520,7 @@ describe('nodeOps', () => {
|
|
|
const child = mockTresObjectRootInObject(new Mesh() as unknown as TresObject)
|
|
|
nodeOps.insert(child, parent)
|
|
|
nodeOps.remove(child)
|
|
|
- expect(!parent.children.includes(child)).toBeTruthy()
|
|
|
+ expect(parent.children.includes(child)).toBeFalsy()
|
|
|
})
|
|
|
|
|
|
it('silently does not remove a falsy child', () => {
|
|
@@ -330,7 +538,9 @@ describe('nodeOps', () => {
|
|
|
expect(spy).toHaveBeenCalledOnce()
|
|
|
})
|
|
|
|
|
|
- it('calls dispose on a material array', () => {
|
|
|
+ it.skip('calls dispose on a material array', () => {
|
|
|
+ // TODO: Make this test pass.
|
|
|
+ // No way to add a material array via nodeOps currently.
|
|
|
const parent = mockTresObjectRootInObject(nodeOps.createElement('Mesh', undefined, undefined, {}))
|
|
|
const material0 = new THREE.MeshNormalMaterial()
|
|
|
const material1 = new THREE.MeshNormalMaterial()
|
|
@@ -351,6 +561,19 @@ describe('nodeOps', () => {
|
|
|
expect(spy).toHaveBeenCalledOnce()
|
|
|
})
|
|
|
|
|
|
+ it('calls dispose on material/geometry in a TresMesh child of a TresMesh', () => {
|
|
|
+ const { mesh: grandparent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child } = createElementMesh(nodeOps)
|
|
|
+ nodeOps.insert(parent, grandparent)
|
|
|
+ nodeOps.insert(child, parent)
|
|
|
+ const childMaterialDisposalSpy = vi.spyOn(child.material, 'dispose')
|
|
|
+ const childGeometryDisposalSpy = vi.spyOn(child.geometry, 'dispose')
|
|
|
+ nodeOps.remove(parent)
|
|
|
+ expect(childGeometryDisposalSpy).toHaveBeenCalledOnce()
|
|
|
+ expect(childMaterialDisposalSpy).toHaveBeenCalledOnce()
|
|
|
+ })
|
|
|
+
|
|
|
it('calls dispose on every material/geometry in a TresMesh tree', () => {
|
|
|
const NUM_LEVEL = 5
|
|
|
const NUM_CHILD_PER_NODE = 3
|
|
@@ -417,7 +640,7 @@ describe('nodeOps', () => {
|
|
|
expect(spy1).not.toBeCalled()
|
|
|
})
|
|
|
|
|
|
- it.skip('does not dispose primitive material/geometries on remove(ascestorOfPrimitive)', () => {
|
|
|
+ it('does not dispose primitive material/geometries on remove(ascestorOfPrimitive)', () => {
|
|
|
const { primitive, material, geometry } = createElementPrimitiveMesh(nodeOps)
|
|
|
const spy0 = vi.spyOn(material, 'dispose')
|
|
|
const spy1 = vi.spyOn(geometry, 'dispose')
|
|
@@ -430,7 +653,7 @@ describe('nodeOps', () => {
|
|
|
expect(spy1).not.toBeCalled()
|
|
|
})
|
|
|
|
|
|
- it.skip('does not call dispose on primitive materials/geometries in a tree of Mesh/Groups/Primitives created by nodeOps', () => {
|
|
|
+ it('does not call dispose on primitive materials/geometries in a tree of Mesh/Groups/Primitives created by nodeOps', () => {
|
|
|
const NUM_LEVEL = 5
|
|
|
const NUM_CHILD_PER_NODE = 3
|
|
|
const rootNode = mockTresObjectRootInObject(nodeOps.createElement('Group'))
|
|
@@ -466,7 +689,31 @@ describe('nodeOps', () => {
|
|
|
})
|
|
|
|
|
|
describe(':dispose="null"', () => {
|
|
|
- it.skip('does not call dispose on any element in a subtree where the root :dispose==="null"', () => {
|
|
|
+ it('does not call dispose on geometry/material in a Mesh where :dispose==="null"', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh, geometry, material } = createElementMesh(nodeOps)
|
|
|
+ const spy0 = vi.spyOn(geometry, 'dispose')
|
|
|
+ const spy1 = vi.spyOn(material, 'dispose')
|
|
|
+ nodeOps.patchProp(mesh, 'dispose', undefined, null)
|
|
|
+ nodeOps.insert(mesh, parent)
|
|
|
+ nodeOps.remove(mesh)
|
|
|
+ expect(spy0).not.toBeCalled()
|
|
|
+ expect(spy1).not.toBeCalled()
|
|
|
+ })
|
|
|
+ it('does not call dispose on child\'s geometry/material, for remove(<parent><child :dispose="null" /></parent>)', () => {
|
|
|
+ const { mesh: grandparent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child, geometry, material } = createElementMesh(nodeOps)
|
|
|
+ const spy0 = vi.spyOn(geometry, 'dispose')
|
|
|
+ const spy1 = vi.spyOn(material, 'dispose')
|
|
|
+ nodeOps.patchProp(child, 'dispose', undefined, null)
|
|
|
+ nodeOps.insert(parent, grandparent)
|
|
|
+ nodeOps.insert(child, parent)
|
|
|
+ nodeOps.remove(parent)
|
|
|
+ expect(spy0).not.toBeCalled()
|
|
|
+ expect(spy1).not.toBeCalled()
|
|
|
+ })
|
|
|
+ it('does not call dispose on any element in a subtree where the root :dispose==="null"', () => {
|
|
|
const NUM_LEVEL = 5
|
|
|
const NUM_CHILD_PER_NODE = 3
|
|
|
const rootNode = mockTresObjectRootInObject(nodeOps.createElement('Group'))
|
|
@@ -528,44 +775,46 @@ describe('nodeOps', () => {
|
|
|
nodeOps.remove(child)
|
|
|
expect(child.parent?.uuid).toBeFalsy()
|
|
|
})
|
|
|
- it.skip('detaches mesh (in primitive :object) from mesh', () => {
|
|
|
- const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
- const { primitive, mesh } = createElementPrimitiveMesh(nodeOps)
|
|
|
- nodeOps.insert(primitive, parent)
|
|
|
- expect(primitive.parent?.uuid).toBe(mesh.uuid)
|
|
|
+ describe.skip('primitive', () => {
|
|
|
+ it('detaches mesh (in primitive :object) from mesh', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { primitive, mesh } = createElementPrimitiveMesh(nodeOps)
|
|
|
+ nodeOps.insert(primitive, parent)
|
|
|
+ expect(primitive.parent?.uuid).toBe(parent.uuid)
|
|
|
|
|
|
- nodeOps.remove(primitive)
|
|
|
- expect(mesh.parent?.uuid).toBeFalsy()
|
|
|
- })
|
|
|
- it.skip('detaches mesh (in primitive :object) when mesh ancestor is removed', () => {
|
|
|
- const { mesh: grandparent } = createElementMesh(nodeOps)
|
|
|
- const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
- const { primitive, mesh: primitiveMesh } = createElementPrimitiveMesh(nodeOps)
|
|
|
- nodeOps.insert(parent, grandparent)
|
|
|
- nodeOps.insert(primitive, parent)
|
|
|
- expect(primitiveMesh.parent?.uuid).toBe(parent.uuid)
|
|
|
+ nodeOps.remove(primitive)
|
|
|
+ expect(mesh.parent?.uuid).toBeFalsy()
|
|
|
+ })
|
|
|
+ it('detaches mesh (in primitive :object) when mesh ancestor is removed', () => {
|
|
|
+ const { mesh: grandparent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { primitive, mesh: primitiveMesh } = createElementPrimitiveMesh(nodeOps)
|
|
|
+ nodeOps.insert(parent, grandparent)
|
|
|
+ nodeOps.insert(primitive, parent)
|
|
|
+ expect(primitiveMesh.parent?.uuid).toBe(parent.uuid)
|
|
|
|
|
|
- nodeOps.remove(parent)
|
|
|
- expect(primitiveMesh.parent?.uuid).toBeFalsy()
|
|
|
- })
|
|
|
- it('does not detach primitive :object descendants', () => {
|
|
|
- const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
- const { primitive, mesh: primitiveMesh } = createElementPrimitiveMesh(nodeOps)
|
|
|
- const grandChild0 = new THREE.Mesh()
|
|
|
- const grandChild1 = new THREE.Group()
|
|
|
- primitiveMesh.add(grandChild0, grandChild1)
|
|
|
+ nodeOps.remove(parent)
|
|
|
+ expect(primitiveMesh.parent?.type).toBeFalsy()
|
|
|
+ })
|
|
|
+ it('does not detach primitive :object descendants', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { primitive, mesh: primitiveMesh } = createElementPrimitiveMesh(nodeOps)
|
|
|
+ const grandChild0 = new THREE.Mesh()
|
|
|
+ const grandChild1 = new THREE.Group()
|
|
|
+ primitiveMesh.add(grandChild0, grandChild1)
|
|
|
|
|
|
- nodeOps.insert(primitive, parent)
|
|
|
- expect(grandChild0.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
- expect(grandChild1.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
+ nodeOps.insert(primitive, parent)
|
|
|
+ expect(grandChild0.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
+ expect(grandChild1.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
|
|
|
- nodeOps.remove(primitive)
|
|
|
- expect(grandChild0.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
- expect(grandChild1.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
+ nodeOps.remove(primitive)
|
|
|
+ expect(grandChild0.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
+ expect(grandChild1.parent.uuid).toBe(primitiveMesh.uuid)
|
|
|
+ })
|
|
|
})
|
|
|
})
|
|
|
- describe('in the __tres parent-object graph', () => {
|
|
|
- it('removes parent-object relationship when object is removed', () => {
|
|
|
+ describe('in the __tres parent-objects graph', () => {
|
|
|
+ it('removes parent-objects relationship when object is removed', () => {
|
|
|
const parent = nodeOps.createElement('Mesh', undefined, undefined, {})
|
|
|
const material = nodeOps.createElement('MeshNormalMaterial')
|
|
|
const geometry = nodeOps.createElement('BoxGeometry')
|
|
@@ -578,17 +827,17 @@ describe('nodeOps', () => {
|
|
|
expect(fog.__tres.parent).toBe(parent)
|
|
|
|
|
|
nodeOps.remove(fog)
|
|
|
- expect(fog.__tres.parent).toBe(null)
|
|
|
+ expect(fog.__tres?.parent).toBeFalsy()
|
|
|
expect(parent.__tres.objects.length).toBe(2)
|
|
|
expect(parent.__tres.objects.includes(fog)).toBe(false)
|
|
|
|
|
|
nodeOps.remove(material)
|
|
|
- expect(material.__tres.parent).toBe(null)
|
|
|
+ expect(material.__tres?.parent).toBeFalsy()
|
|
|
expect(parent.__tres.objects.length).toBe(1)
|
|
|
expect(parent.__tres.objects.includes(material)).toBe(false)
|
|
|
|
|
|
nodeOps.remove(geometry)
|
|
|
- expect(geometry.__tres.parent).toBe(null)
|
|
|
+ expect(geometry.__tres?.parent).toBeFalsy()
|
|
|
expect(parent.__tres.objects.length).toBe(0)
|
|
|
expect(parent.__tres.objects.includes(geometry)).toBe(false)
|
|
|
})
|
|
@@ -699,20 +948,135 @@ describe('nodeOps', () => {
|
|
|
expect(spy).toHaveBeenCalledTimes(3)
|
|
|
})
|
|
|
|
|
|
- describe('patch `:object` on primitives', () => {
|
|
|
+ describe.skip('patch `:object` on primitives', () => {
|
|
|
it('replaces original object', () => {
|
|
|
const material0 = new THREE.MeshNormalMaterial()
|
|
|
- const material1 = new THREE.MeshNormalMaterial()
|
|
|
+ const material1 = new THREE.MeshBasicMaterial()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object: material0 })
|
|
|
nodeOps.patchProp(primitive, 'object', material0, material1)
|
|
|
expect(primitive.object).toBe(material1)
|
|
|
})
|
|
|
+
|
|
|
+ it('does not alter __tres on another primitive sharing the same object', () => {
|
|
|
+ const materialA = new THREE.MeshNormalMaterial()
|
|
|
+ const materialB = new THREE.MeshNormalMaterial()
|
|
|
+ const primitive0 = nodeOps.createElement('primitive', undefined, undefined, { object: materialA })
|
|
|
+ const primitive0TresJson = JSON.stringify(primitive0.__tres)
|
|
|
+ const primitive1 = nodeOps.createElement('primitive', undefined, undefined, { object: materialA })
|
|
|
+
|
|
|
+ expect(primitive0.__tres).not.toBe(primitive1.__tres)
|
|
|
+ expect(JSON.stringify(primitive0.__tres)).toBe(primitive0TresJson)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive1, 'object', undefined, materialB)
|
|
|
+ expect(primitive0.__tres).not.toBe(primitive1.__tres)
|
|
|
+ expect(JSON.stringify(primitive0.__tres)).toBe(primitive0TresJson)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive1, 'object', undefined, materialA)
|
|
|
+ expect(primitive0.__tres).not.toBe(primitive1.__tres)
|
|
|
+ expect(JSON.stringify(primitive0.__tres)).toBe(primitive0TresJson)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('does not replace the object in other primitives who point to the same object', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child0 } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child1 } = createElementMesh(nodeOps)
|
|
|
+ const materialA = new THREE.MeshNormalMaterial()
|
|
|
+ const materialB = new THREE.MeshBasicMaterial()
|
|
|
+ const primitive1 = nodeOps.createElement('primitive', undefined, undefined, { object: materialA })
|
|
|
+ const primitive0 = nodeOps.createElement('primitive', undefined, undefined, { object: materialA })
|
|
|
+
|
|
|
+ nodeOps.insert(primitive0, child0)
|
|
|
+ nodeOps.insert(primitive1, child1)
|
|
|
+ nodeOps.insert(child0, parent)
|
|
|
+ nodeOps.insert(child1, parent)
|
|
|
+
|
|
|
+ expect(child0.material).toBe(materialA)
|
|
|
+ expect(child1.material).toBe(materialA)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive1, 'object', undefined, materialB)
|
|
|
+ expect(child0.material).toBe(materialA)
|
|
|
+ expect(child1.material).not.toBe(materialA)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive1, 'object', undefined, materialA)
|
|
|
+ expect(child0.material).toBe(materialA)
|
|
|
+ expect(child1.material).toBe(materialA)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive0, 'object', undefined, materialB)
|
|
|
+ expect(child0.material).not.toBe(materialA)
|
|
|
+ expect(child1.material).toBe(materialA)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive1, 'object', undefined, materialB)
|
|
|
+ expect(child0.material).not.toBe(materialA)
|
|
|
+ expect(child1.material).not.toBe(materialA)
|
|
|
+ expect(child0.material).toBe(materialB)
|
|
|
+ expect(child1.material).toBe(materialB)
|
|
|
+ })
|
|
|
+ it('attaches the new object to the old object\'s parent; clears old object\'s parent', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child0 } = createThreeBox()
|
|
|
+ const { mesh: child1 } = createThreeBox()
|
|
|
+ const primitive = nodeOps.createElement('primitive', undefined, undefined, { object: child0 })
|
|
|
+ nodeOps.insert(primitive, parent)
|
|
|
+ expect(child0.parent).toBe(parent)
|
|
|
+ expect(parent.children[0]).toBe(child0)
|
|
|
+ expect(parent.children.length).toBe(1)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'object', undefined, child1)
|
|
|
+ expect(child0.parent?.uuid).toBeFalsy()
|
|
|
+ expect(child1.parent?.uuid).toBe(parent.uuid)
|
|
|
+ expect(parent.children[0]).toBe(child1)
|
|
|
+ expect(parent.children.length).toBe(1)
|
|
|
+ })
|
|
|
+ it('if old :object had been patched, those patches are applied to new :object', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child0 } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child1 } = createElementMesh(nodeOps)
|
|
|
+ const primitive = nodeOps.createElement('primitive', undefined, undefined, { object: child0 })
|
|
|
+ nodeOps.insert(primitive, parent)
|
|
|
+ nodeOps.patchProp(primitive, 'position-x', undefined, -999)
|
|
|
+ expect(child0.position.x).toBe(-999)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'object', undefined, child1)
|
|
|
+ expect(child1.position.x).toBe(-999)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'position-x', undefined, 1000)
|
|
|
+ nodeOps.patchProp(primitive, 'object', undefined, child0)
|
|
|
+ expect(child0.position.x).toBe(1000)
|
|
|
+ })
|
|
|
+ it('does not attach old :object children to new :object', () => {
|
|
|
+ const { mesh: parent } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child0 } = createElementMesh(nodeOps)
|
|
|
+ const { mesh: child1 } = createElementMesh(nodeOps)
|
|
|
+ const grandchild0 = new THREE.Mesh()
|
|
|
+ const grandchild1 = new THREE.Mesh()
|
|
|
+ child0.add(grandchild0)
|
|
|
+ child1.add(grandchild1)
|
|
|
+ const primitive = nodeOps.createElement('primitive', undefined, undefined, { object: child0 })
|
|
|
+ nodeOps.insert(primitive, parent)
|
|
|
+ expect(primitive.children[0]).toBe(grandchild0)
|
|
|
+ expect(primitive.children.length).toBe(1)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'object', undefined, child1)
|
|
|
+ expect(primitive.children[0]).toBe(grandchild1)
|
|
|
+ expect(primitive.children.length).toBe(1)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'object', undefined, child0)
|
|
|
+ expect(primitive.children[0].uuid).toBe(grandchild0.uuid)
|
|
|
+ expect(primitive.children.length).toBe(1)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'object', undefined, child1)
|
|
|
+ expect(primitive.children[0]).toBe(grandchild1)
|
|
|
+ expect(primitive.children.length).toBe(1)
|
|
|
+ })
|
|
|
it('does not copy UUID', () => {
|
|
|
const material0 = new THREE.MeshNormalMaterial()
|
|
|
const material1 = new THREE.MeshNormalMaterial()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object: material0 })
|
|
|
nodeOps.patchProp(primitive, 'object', material0, material1)
|
|
|
expect(material0.uuid).not.toBe(material1.uuid)
|
|
|
+
|
|
|
+ nodeOps.patchProp(primitive, 'object', material1, material0)
|
|
|
+ expect(material0.uuid).not.toBe(material1.uuid)
|
|
|
})
|
|
|
})
|
|
|
|
|
@@ -817,6 +1181,13 @@ function mockTresContext() {
|
|
|
} as unknown as TresContext
|
|
|
}
|
|
|
|
|
|
+function createThreeBox() {
|
|
|
+ const geometry = new THREE.BoxGeometry()
|
|
|
+ const material = new THREE.MeshNormalMaterial()
|
|
|
+ const mesh = new THREE.Mesh(geometry, material)
|
|
|
+ return { mesh, geometry, material }
|
|
|
+}
|
|
|
+
|
|
|
function createElementMesh(nodeOps: ReturnType<typeof getNodeOps>) {
|
|
|
const geometry = nodeOps.createElement('BoxGeometry')
|
|
|
const material = nodeOps.createElement('MeshNormalMaterial')
|
|
@@ -827,9 +1198,7 @@ function createElementMesh(nodeOps: ReturnType<typeof getNodeOps>) {
|
|
|
}
|
|
|
|
|
|
function createElementPrimitiveMesh(nodeOps: ReturnType<typeof getNodeOps>) {
|
|
|
- const geometry = new THREE.BoxGeometry()
|
|
|
- const material = new THREE.MeshNormalMaterial()
|
|
|
- const mesh = new THREE.Mesh(geometry, material)
|
|
|
+ const { mesh, geometry, material } = createThreeBox()
|
|
|
const primitive = nodeOps.createElement('primitive', undefined, undefined, { object: mesh })
|
|
|
return { primitive, mesh, geometry, material }
|
|
|
}
|