Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/components/boundingbox/__test__/BoundingBox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,5 +425,17 @@ describe('BoundingBox', () => {
expect(boundingBox.children).toBeDefined();
expect(Array.isArray(boundingBox.children)).toBe(true);
});

it('should stay silent, because nothing subscribes to it', () => {
// A bounding box is scaffolding, not an entity. It inherits
// onMove() from DIVENode, but the state layer never wires it up,
// so the inherited call has to be inert rather than guarded away.
const boundingBox = new BoundingBox(mockObject);

expect(
(boundingBox as unknown as { _listeners?: object })._listeners,
).toBeUndefined();
expect(() => boundingBox.onMove()).not.toThrow();
});
});
});
66 changes: 4 additions & 62 deletions src/components/group/Group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { type DIVESceneObject } from '../../types/index.ts';
export class DIVEGroup extends DIVENode {
readonly isDIVEGroup: true = true;

private _members: Object3D[]; // children objects
private _members: DIVESceneObject[];

public get members(): Object3D[] {
public get members(): DIVESceneObject[] {
return this._members;
}

Expand Down Expand Up @@ -47,7 +47,7 @@ export class DIVEGroup extends DIVENode {
return;
}

const index = this._members.indexOf(object);
const index = this._members.findIndex((member) => member === object);
if (index === -1) return;

this._lines[index].visible = visible;
Expand Down Expand Up @@ -99,7 +99,7 @@ export class DIVEGroup extends DIVENode {
}

public updateLineTo(object: Object3D): void {
const index = this._members.indexOf(object);
const index = this._members.findIndex((member) => member === object);
if (index === -1) return;

this._updateLineTo(this._lines[index], object);
Expand Down Expand Up @@ -128,62 +128,4 @@ export class DIVEGroup extends DIVENode {
line.geometry.setFromPoints(points);
line.computeLineDistances();
}

// 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;
// }
}
59 changes: 37 additions & 22 deletions src/components/group/__test__/Group.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import { Object3D, type Vector3Like } from 'three/webgpu';
import { State } from '@shopware-ag/dive/state';
import { type DIVENode } from '../../node/Node.ts';
import { DIVENode } from '../../node/Node.ts';
import { DIVEGroup } from '../Group.ts';

vi.mock('../../../modules/state/State', () => {
return {
State: {
get: vi.fn(() => {
return {
performAction: vi.fn(),
};
}),
},
};
});

vi.spyOn(State, 'get').mockReturnValue({
performAction: vi.fn(),
} as unknown as State);
import { type DIVESceneObject } from '../../../types/components/DIVESceneObject.ts';

let group: DIVEGroup;
let obj: Object3D;
Expand Down Expand Up @@ -108,7 +92,6 @@ describe('dive/group/DIVEGroup', () => {

expect(() => group.onMove()).not.toThrow();

vi.spyOn(State, 'get').mockReturnValueOnce(undefined);
expect(() => group.onMove()).not.toThrow();
});

Expand All @@ -117,7 +100,6 @@ describe('dive/group/DIVEGroup', () => {

expect(() => group.onSelect()).not.toThrow();

vi.spyOn(State, 'get').mockReturnValueOnce(undefined);
expect(() => group.onSelect()).not.toThrow();
});

Expand All @@ -126,7 +108,6 @@ describe('dive/group/DIVEGroup', () => {

expect(() => group.onDeselect()).not.toThrow();

vi.spyOn(State, 'get').mockReturnValueOnce(undefined);
expect(() => group.onDeselect()).not.toThrow();
});

Expand All @@ -135,7 +116,6 @@ describe('dive/group/DIVEGroup', () => {

expect(() => group.onMove()).not.toThrow();

vi.spyOn(State, 'get').mockReturnValueOnce(undefined);
expect(() => group.onMove()).not.toThrow();
});

Expand Down Expand Up @@ -247,4 +227,39 @@ describe('dive/group/DIVEGroup', () => {
expect(() => group.remove(objWithoutId as any)).not.toThrow();
expect(group.members).not.toContain(objWithoutId);
});

describe('cascading moves to its members', () => {
// Moving a group moves everything in it, so every member has to report
// its own new transform — the group's event alone says nothing about
// where the members ended up.

it('should make each node member report a transform', () => {
const memberA = new DIVENode();
const memberB = new DIVENode();
memberA.userData.id = 'a';
memberB.userData.id = 'b';
// DIVENode is the base of every real member; attach() is typed
// to the concrete union, so the test stands in for it
group.attach(memberA as unknown as DIVESceneObject);
group.attach(memberB as unknown as DIVESceneObject);

const onA = vi.fn();
const onB = vi.fn();
memberA.addEventListener('object-transform', onA);
memberB.addEventListener('object-transform', onB);

group.setPosition({ x: 5, y: 0, z: 0 });

expect(onA).toHaveBeenCalledTimes(1);
expect(onB).toHaveBeenCalledTimes(1);
});

it('should skip members that are not nodes', () => {
const plain = new Object3D();
plain.userData.id = 'plain';
group.attach(plain as unknown as DIVESceneObject);

expect(() => group.setPosition({ x: 5, y: 0, z: 0 })).not.toThrow();
});
});
});
6 changes: 5 additions & 1 deletion src/components/light/AmbientLight.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AmbientLight, Color, Object3D } from 'three/webgpu';
import { PRODUCT_LAYER_MASK } from '../../constants/VisibilityLayerMask.ts';
import { DIVESelectable } from '@shopware-ag/dive';
import { type DIVEEntityEventMap } from '../../types/events/index.ts';

/**
* A basic ambient light.
Expand All @@ -10,7 +11,10 @@ import { DIVESelectable } from '@shopware-ag/dive';
* @module
*/

export class DIVEAmbientLight extends Object3D implements DIVESelectable {
export class DIVEAmbientLight
extends Object3D<DIVEEntityEventMap>
implements DIVESelectable
{
readonly isDIVELight: true = true;
readonly isDIVEAmbientLight: true = true;
readonly isSelectable: true = true;
Expand Down
32 changes: 16 additions & 16 deletions src/components/light/PointLight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Mesh,
FrontSide,
Object3D,
Vector3,
} from 'three/webgpu';
import {
PRODUCT_LAYER_MASK,
Expand All @@ -14,6 +15,7 @@ import {
import { DIVEMovable } from '../../interfaces/Movable.ts';
import { DIVESelectable } from '../../interfaces/Selectable.ts';
import type { TransformControls } from 'three/examples/jsm/controls/TransformControls.ts';
import { type DIVEEntityEventMap } from '../../types/events/index.ts';

/**
* A basic point light.
Expand All @@ -26,7 +28,7 @@ import type { TransformControls } from 'three/examples/jsm/controls/TransformCon
*/

export class DIVEPointLight
extends Object3D
extends Object3D<DIVEEntityEventMap>
implements DIVESelectable, DIVEMovable
{
readonly isDIVELight: true = true;
Expand All @@ -39,6 +41,9 @@ export class DIVEPointLight
private light: PointLight;
private mesh: Mesh;

/** Reused so reporting a move does not allocate every frame. */
private _positionWorldBuffer = new Vector3();

constructor() {
super();

Expand Down Expand Up @@ -92,27 +97,22 @@ export class DIVEPointLight
}

public onMove(): void {
import('@shopware-ag/dive/state').then(({ State }) => {
State.get(this.userData.id)?.performAction('UPDATE_OBJECT', {
id: this.userData.id,
position: this.position,
});
// reports the world position, same as every other entity. The local
// one this used to send is only correct while the light hangs
// directly off the root.
this.dispatchEvent({
type: 'object-transform',
position: this.getWorldPosition(this._positionWorldBuffer),
rotation: this.rotation,
scale: this.scale,
});
}

public onSelect(): void {
import('@shopware-ag/dive/state').then(({ State }) => {
State.get(this.userData.id)?.performAction('SELECT_OBJECT', {
id: this.userData.id,
});
});
this.dispatchEvent({ type: 'object-select' });
}

public onDeselect(): void {
import('@shopware-ag/dive/state').then(({ State }) => {
State.get(this.userData.id)?.performAction('DESELECT_OBJECT', {
id: this.userData.id,
});
});
this.dispatchEvent({ type: 'object-deselect' });
}
}
6 changes: 5 additions & 1 deletion src/components/light/SceneLight.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DIVESelectable } from '@shopware-ag/dive';
import { type DIVEEntityEventMap } from '../../types/events/index.ts';
import { PRODUCT_LAYER_MASK } from '../../constants/VisibilityLayerMask.ts';
import {
Color,
Expand All @@ -15,7 +16,10 @@ import {
* @module
*/

export class DIVESceneLight extends Object3D implements DIVESelectable {
export class DIVESceneLight
extends Object3D<DIVEEntityEventMap>
implements DIVESelectable
{
readonly isDIVELight: true = true;
readonly isDIVESceneLight: true = true;
readonly isSelectable: true = true;
Expand Down
Loading
Loading