Skip to content

Commit

Permalink
Merge pull request #46 from shopware/bug/group-size-on-child-move
Browse files Browse the repository at this point in the history
Bug: Group size on child move
  • Loading branch information
ffrank913 authored Oct 22, 2024
2 parents fc94b3d + cb4c04a commit 89f9cf9
Show file tree
Hide file tree
Showing 24 changed files with 298 additions and 270 deletions.
184 changes: 116 additions & 68 deletions src/group/Group.ts
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;
// }
}
24 changes: 20 additions & 4 deletions src/group/__test__/Group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ describe('dive/group/DIVEGroup', () => {
expect(group).toBeDefined();
});

it('should set bounding box visibility', () => {
expect(() => group.SetBoundingBoxVisibility(true)).not.toThrow();
});

it('should add an object', () => {
const mockObject = new DIVEGroup();

Expand All @@ -54,6 +50,26 @@ describe('dive/group/DIVEGroup', () => {
expect(() => group.remove(mockObject)).not.toThrow();
});

it('should set lines visibility', () => {
expect(() => group.SetLinesVisibility(true)).not.toThrow();

const mockObject = new DIVEGroup();
expect(() => group.SetLinesVisibility(false, mockObject)).not.toThrow();

expect(() => group.attach(mockObject)).not.toThrow();
expect(() => group.SetLinesVisibility(false)).not.toThrow();

expect(() => group.SetLinesVisibility(true, mockObject)).not.toThrow();
});

it('update lines', () => {
const mockObject = new DIVEGroup();
expect(() => group.UpdateLineTo(mockObject)).not.toThrow();

expect(() => group.attach(mockObject)).not.toThrow();
expect(() => group.UpdateLineTo(mockObject)).not.toThrow();
});

it('should onMove', () => {
group.userData.id = 'something';

Expand Down
6 changes: 3 additions & 3 deletions src/helper/applyMixins/__test__/applyMixins.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { applyMixins } from '../applyMixins.ts';

class Moveable {
class Movable {
move() { }
}

Expand All @@ -14,9 +14,9 @@ describe('dive/helper/applyMixins', () => {
doProductThings() { }
}

interface Product extends Moveable, Selectable { }
interface Product extends Movable, Selectable { }

applyMixins(Product, [Moveable, Selectable]);
applyMixins(Product, [Movable, Selectable]);

const instance = new Product();
expect(instance).toBeDefined();
Expand Down
53 changes: 53 additions & 0 deletions src/helper/findInterface/__test__/findInterface.test.ts
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);
});
});
10 changes: 10 additions & 0 deletions src/helper/findInterface/findInterface.ts
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);
}
2 changes: 0 additions & 2 deletions src/helper/getObjectDelta/__test__/getObjectDelta.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { getObjectDelta } from '../getObjectDelta.ts';



describe('dive/helper/getObjectDelta', () => {

// NO DELTAS
Expand Down
19 changes: 19 additions & 0 deletions src/helper/isInterface/__test__/implementsInterface.test.ts
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);
});
});
6 changes: 6 additions & 0 deletions src/helper/isInterface/implementsInterface.ts
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;
}
Loading

0 comments on commit 89f9cf9

Please sign in to comment.