-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46 from shopware/bug/group-size-on-child-move
Bug: Group size on child move
- Loading branch information
Showing
24 changed files
with
298 additions
and
270 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,110 +1,158 @@ | ||
import { BoxGeometry, Mesh, MeshBasicMaterial, Vector3 } from "three"; | ||
import { BufferGeometry, Line, LineDashedMaterial, Vector3 } from "three"; | ||
import { DIVENode } from "../node/Node"; | ||
import { DIVECommunication } from "../com/Communication"; | ||
import { type Object3D } from "three"; | ||
import { type DIVESceneObject } from "../types"; | ||
|
||
export class DIVEGroup extends DIVENode { | ||
readonly isDIVEGroup: true = true; | ||
|
||
private _boxMesh: Mesh; | ||
private _members: Object3D[]; // lines to children | ||
|
||
private _lines: Line[]; // lines to children | ||
|
||
constructor() { | ||
super(); | ||
|
||
this.name = 'DIVEGroup'; | ||
|
||
this._boxMesh = new Mesh(new BoxGeometry(0, 0, 0), new MeshBasicMaterial({ color: 0xff0000, wireframe: true })); | ||
this._boxMesh.visible = false; | ||
this.add(this._boxMesh); | ||
this._members = []; | ||
|
||
this._lines = []; | ||
} | ||
|
||
public SetBoundingBoxVisibility(visible: boolean): void { | ||
this._boxMesh.visible = visible; | ||
public SetLinesVisibility(visible: boolean, object?: Object3D): void { | ||
if (!object) { | ||
this._lines.forEach((line) => { | ||
line.visible = visible; | ||
}); | ||
return; | ||
} | ||
|
||
const index = this._members.indexOf(object); | ||
if (index === -1) return; | ||
|
||
this._lines[index].visible = visible; | ||
} | ||
|
||
public attach(object: DIVESceneObject): this { | ||
// create a line to the new object | ||
const line = this.createLine(); | ||
this.add(line); | ||
this._lines.push(line); | ||
|
||
|
||
// attach (insted of add) object to keep it's world position | ||
super.attach(object); | ||
this._members.push(object); | ||
|
||
// set position to it's bb's center | ||
this.recalculatePosition(); | ||
|
||
// update box mesh | ||
this.updateBoxMesh(); | ||
// update line to object points | ||
this.updateLineTo(line, object); | ||
this.SetLinesVisibility(true, object); | ||
|
||
return this; | ||
} | ||
|
||
public remove(object: DIVESceneObject): this { | ||
// removes object from group while keeping it's world position | ||
super.remove(object); | ||
// remove line first | ||
const index = this._members.indexOf(object); | ||
if (index === -1) return this; | ||
|
||
// set position to it's bb's center | ||
this.recalculatePosition(); | ||
const line = this._lines[index]; | ||
super.remove(line); | ||
this._lines.splice(index, 1); | ||
|
||
// update box mesh | ||
this.updateBoxMesh(); | ||
// removes object from group while keeping it's world position | ||
super.remove(object); | ||
this._members.splice(index, 1); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Recalculates the position of the group based on it's bounding box. | ||
* Children's world positions are kept. | ||
*/ | ||
private recalculatePosition(): void { | ||
// store all children's world positions | ||
const childrensWorldPositions: Vector3[] = this.children.map((child) => child.getWorldPosition(new Vector3())); | ||
|
||
// calculate new center and set it as the group's position | ||
const bbcenter = this.updateBB(); | ||
this.position.copy(bbcenter); | ||
|
||
// set childrens's positions so their world positions are kept | ||
this.children.forEach((child, i) => { | ||
if (child.uuid === this._boxMesh.uuid) return; | ||
child.position.copy(this.worldToLocal(childrensWorldPositions[i])); | ||
}); | ||
public UpdateLineTo(object: Object3D): void { | ||
const index = this._members.indexOf(object); | ||
if (index === -1) return; | ||
|
||
DIVECommunication.get(this.userData.id)?.PerformAction('UPDATE_OBJECT', { id: this.userData.id, position: this.position }); | ||
this.updateLineTo(this._lines[index], object); | ||
} | ||
|
||
/** | ||
* Updates the bounding box of the group. | ||
* @returns {Vector3} The new center of the bounding box. | ||
* Adds a line to this grouo as last child. | ||
*/ | ||
private updateBB(): Vector3 { | ||
this._boundingBox.makeEmpty(); | ||
|
||
if (this.children.length === 1) { | ||
// because we always have the box mesh as 1 child | ||
return this.position.clone(); | ||
} | ||
|
||
this.children.forEach((child) => { | ||
if (child.uuid === this._boxMesh.uuid) return; | ||
this._boundingBox.expandByObject(child); | ||
private createLine(): Line { | ||
const geo = new BufferGeometry(); | ||
const mat = new LineDashedMaterial({ | ||
color: 0x666666, | ||
dashSize: 0.05, | ||
gapSize: 0.025, | ||
}); | ||
|
||
return this._boundingBox.getCenter(new Vector3()); | ||
const line = new Line(geo, mat); | ||
line.visible = false; | ||
return line; | ||
} | ||
|
||
private updateBoxMesh(): void { | ||
if (this.children.length === 1) { | ||
// because we always have the box mesh as 1 child | ||
this._boxMesh.visible = false; | ||
return; | ||
} | ||
|
||
this._boxMesh.quaternion.copy(this.quaternion.clone().invert()); | ||
this._boxMesh.scale.set(1 / this.scale.x, 1 / this.scale.y, 1 / this.scale.z); | ||
this._boxMesh.geometry = new BoxGeometry(this._boundingBox.max.x - this._boundingBox.min.x, this._boundingBox.max.y - this._boundingBox.min.y, this._boundingBox.max.z - this._boundingBox.min.z); | ||
this._boxMesh.visible = true; | ||
/** | ||
* Updates a line to the object. | ||
*/ | ||
private updateLineTo(line: Line, object: Object3D): void { | ||
line.geometry.setFromPoints([new Vector3(0, 0, 0), object.position.clone()]); | ||
line.computeLineDistances(); | ||
} | ||
|
||
public onMove(): void { | ||
super.onMove(); | ||
this.updateBB(); | ||
this.updateBoxMesh(); | ||
} | ||
// public SetBoundingBoxVisibility(visible: boolean): void { | ||
// this._boxMesh.visible = visible; | ||
// } | ||
|
||
// /** | ||
// * Recalculates the position of the group based on it's bounding box. | ||
// * Children's world positions are kept. | ||
// */ | ||
// private recalculatePosition(): void { | ||
// // store all children's world positions | ||
// const childrensWorldPositions: Vector3[] = this.children.map((child) => child.getWorldPosition(new Vector3())); | ||
|
||
// // calculate new center and set it as the group's position | ||
// const bbcenter = this.updateBB(); | ||
// this.position.copy(bbcenter); | ||
|
||
// // set childrens's positions so their world positions are kept | ||
// this.children.forEach((child, i) => { | ||
// if (child.uuid === this._boxMesh.uuid) return; | ||
// child.position.copy(this.worldToLocal(childrensWorldPositions[i])); | ||
// }); | ||
|
||
// DIVECommunication.get(this.userData.id)?.PerformAction('UPDATE_OBJECT', { id: this.userData.id, position: this.position }); | ||
// } | ||
|
||
// /** | ||
// * Updates the bounding box of the group. | ||
// * @returns {Vector3} The new center of the bounding box. | ||
// */ | ||
// private updateBB(): Vector3 { | ||
// this._boundingBox.makeEmpty(); | ||
|
||
// if (this.children.length === 1) { | ||
// // because we always have the box mesh as 1 child | ||
// return this.position.clone(); | ||
// } | ||
|
||
// this.children.forEach((child) => { | ||
// if (child.uuid === this._boxMesh.uuid) return; | ||
// this._boundingBox.expandByObject(child); | ||
// }); | ||
|
||
// return this._boundingBox.getCenter(new Vector3()); | ||
// } | ||
|
||
// private updateBoxMesh(): void { | ||
// if (this.children.length === 1) { | ||
// // because we always have the box mesh as 1 child | ||
// this._boxMesh.visible = false; | ||
// return; | ||
// } | ||
|
||
// this._boxMesh.quaternion.copy(this.quaternion.clone().invert()); | ||
// this._boxMesh.scale.set(1 / this.scale.x, 1 / this.scale.y, 1 / this.scale.z); | ||
// this._boxMesh.geometry = new BoxGeometry(this._boundingBox.max.x - this._boundingBox.min.x, this._boundingBox.max.y - this._boundingBox.min.y, this._boundingBox.max.z - this._boundingBox.min.z); | ||
// this._boxMesh.visible = true; | ||
// } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { Object3D } from 'three'; | ||
import { findInterface } from '../findInterface.ts'; | ||
|
||
describe('dive/helper/findInterface', () => { | ||
it('should not find interface', () => { | ||
expect(findInterface(null, 'isInterface')).toBe(undefined); | ||
|
||
const obj = {} as unknown as Object3D; | ||
|
||
expect(findInterface(obj, 'isInterface')).toBe(undefined); | ||
|
||
// add traverse function | ||
obj.traverseAncestors = jest.fn((callback: (object: Object3D) => any) => { | ||
callback(obj); | ||
}); | ||
|
||
expect(findInterface(obj, 'isInterface')).toBe(undefined); | ||
}); | ||
|
||
it('should find interface in object', () => { | ||
const obj = { | ||
isInterface: true, | ||
traverseAncestors: jest.fn((callback: (object: Object3D) => any) => { | ||
callback(obj); | ||
}), | ||
} as unknown as Object3D; | ||
|
||
expect(findInterface(obj, 'isInterface')).toBe(obj); | ||
}); | ||
|
||
it('should find interface in parent', () => { | ||
const obj = { | ||
traverseAncestors: jest.fn((callback: (object: Object3D) => any) => { | ||
callback(obj); | ||
}), | ||
parent: { | ||
isInterface: true, | ||
|
||
traverseAncestors: jest.fn((callback: (object: Object3D) => any) => { | ||
callback(obj.parent!); | ||
}), | ||
|
||
parent: { | ||
traverseAncestors: jest.fn((callback: (object: Object3D) => any) => { | ||
callback(obj.parent!); | ||
}), | ||
} | ||
} | ||
} as unknown as Object3D; | ||
|
||
expect(findInterface(obj, 'isInterface')).toBe(obj.parent); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { implementsInterface } from "../isInterface/implementsInterface"; | ||
import { type Object3D } from "three"; | ||
|
||
export function findInterface<T>(object: Object3D | null | undefined, discriminator: string): (Object3D & T) | undefined { | ||
if (!object) return undefined; | ||
|
||
if (implementsInterface<T>(object, discriminator)) return object; | ||
|
||
return findInterface<T>(object.parent, discriminator); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
src/helper/isInterface/__test__/implementsInterface.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Object3D } from 'three'; | ||
import { implementsInterface } from '../implementsInterface.ts'; | ||
|
||
describe('dive/helper/implementsInterface', () => { | ||
it('should not find interface', () => { | ||
expect(implementsInterface(undefined, 'isInterface')).toBe(false); | ||
|
||
expect(implementsInterface(null, 'isInterface')).toBe(false); | ||
|
||
const obj = {} as unknown as Object3D; | ||
|
||
expect(implementsInterface(obj, 'isInterface')).toBe(false); | ||
}); | ||
|
||
it('should find interface', () => { | ||
const obj = { isInterface: true } as unknown as Object3D; | ||
expect(implementsInterface(obj, 'isInterface')).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { type Object3D } from "three"; | ||
|
||
export function implementsInterface<T>(object: Object3D | null | undefined, discriminator: string): object is (Object3D & T) { | ||
if (!object) return false; | ||
return discriminator in object; | ||
} |
Oops, something went wrong.