@shopware-ag/dive
Advanced tools
Comparing version 1.5.0 to 1.6.0
@@ -1,3 +0,4 @@ | ||
import { ShadowMapType, ToneMapping, WebGLRenderer, Scene, Camera, PerspectiveCamera, Vector3Like, Mesh, ColorRepresentation, Object3D, Intersection, Vector2, Raycaster, Vector3 } from 'three'; | ||
import { ShadowMapType, ToneMapping, WebGLRenderer, Scene, Camera, PerspectiveCamera, Box3, Vector3Like, Mesh, ColorRepresentation, Object3D, Intersection, Vector2, Raycaster, Vector3 } from 'three'; | ||
import { OrbitControls } from 'three/examples/jsm/Addons.js'; | ||
import { TransformControls } from 'three/examples/jsm/Addons'; | ||
@@ -26,2 +27,3 @@ type DIVERendererSettings = { | ||
constructor(rendererSettings?: DIVERendererSettings); | ||
Dispose(): void; | ||
StartRenderer(scene: Scene, cam: Camera): void; | ||
@@ -108,2 +110,6 @@ PauseRenderer(): void; | ||
constructor(camera: DIVEPerspectiveCamera, renderer: DIVERenderer, settings?: DIVEOrbitControlsSettings); | ||
ComputeEncompassingView(bb: Box3): { | ||
position: Vector3Like; | ||
target: Vector3Like; | ||
}; | ||
ZoomIn(by?: number): void; | ||
@@ -116,91 +122,2 @@ ZoomOut(by?: number): void; | ||
type COMBaseEntity = { | ||
id: string; | ||
name: string; | ||
entityType: 'pov' | 'light' | 'model'; | ||
visible: boolean; | ||
}; | ||
type COMPov = COMBaseEntity & { | ||
position: Vector3Like; | ||
target: Vector3Like; | ||
locked?: boolean; | ||
}; | ||
type COMLight = COMBaseEntity & { | ||
type: 'ambient' | 'point' | 'scene'; | ||
intensity: number; | ||
color: string | number; | ||
enabled: boolean; | ||
position?: Vector3Like; | ||
}; | ||
type COMModel = COMBaseEntity & { | ||
uri: string; | ||
position: Vector3Like; | ||
rotation: Vector3Like; | ||
scale: Vector3Like; | ||
loaded: boolean; | ||
}; | ||
type COMEntity = COMPov | COMLight | COMModel; | ||
/** | ||
* A basic floor geometry. | ||
* | ||
* Can change the color and visibility of the floor. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEFloor extends Mesh { | ||
isFloor: true; | ||
constructor(); | ||
SetVisibility(visible: boolean): void; | ||
SetColor(color: ColorRepresentation): void; | ||
} | ||
/** | ||
* A basic grid for the scene. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEGrid extends Object3D { | ||
constructor(); | ||
} | ||
/** | ||
* A basic scene node to hold grid, floor and all lower level roots. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVERoot extends Object3D { | ||
private lightRoot; | ||
private modelRoot; | ||
private floor; | ||
private grid; | ||
get Floor(): DIVEFloor; | ||
get Grid(): DIVEGrid; | ||
constructor(); | ||
GetSceneObject(object: Partial<COMEntity>): Object3D | undefined; | ||
AddSceneObject(object: COMEntity): void; | ||
UpdateSceneObject(object: Partial<COMEntity>): void; | ||
DeleteSceneObject(object: Partial<COMEntity>): void; | ||
PlaceOnFloor(object: Partial<COMModel>): void; | ||
} | ||
/** | ||
* A basic scene class. | ||
* | ||
* Comes with a root object that contains all the scene objects. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEScene extends Scene { | ||
private root; | ||
get Root(): DIVERoot; | ||
constructor(); | ||
SetBackground(color: ColorRepresentation): void; | ||
GetSceneObject(object: Partial<COMEntity>): Object3D | undefined; | ||
AddSceneObject(object: COMEntity): void; | ||
UpdateSceneObject(object: Partial<COMEntity>): void; | ||
DeleteSceneObject(object: Partial<COMEntity>): void; | ||
PlaceOnFloor(object: Partial<COMModel>): void; | ||
} | ||
interface SET_BACKGROUND { | ||
@@ -271,2 +188,29 @@ 'PAYLOAD': { | ||
type COMBaseEntity = { | ||
id: string; | ||
name: string; | ||
entityType: 'pov' | 'light' | 'model'; | ||
visible: boolean; | ||
}; | ||
type COMPov = COMBaseEntity & { | ||
position: Vector3Like; | ||
target: Vector3Like; | ||
locked?: boolean; | ||
}; | ||
type COMLight = COMBaseEntity & { | ||
type: 'ambient' | 'point' | 'scene'; | ||
intensity: number; | ||
color: string | number; | ||
enabled: boolean; | ||
position?: Vector3Like; | ||
}; | ||
type COMModel = COMBaseEntity & { | ||
uri: string; | ||
position: Vector3Like; | ||
rotation: Vector3Like; | ||
scale: Vector3Like; | ||
loaded: boolean; | ||
}; | ||
type COMEntity = COMPov | COMLight | COMModel; | ||
interface GET_ALL_OBJECTS { | ||
@@ -314,2 +258,3 @@ 'PAYLOAD': Map<string, COMEntity>; | ||
backgroundColor?: string | number; | ||
gridEnabled?: boolean; | ||
floorEnabled?: boolean; | ||
@@ -389,2 +334,10 @@ floorColor?: string | number; | ||
interface COMPUTE_ENCOMPASSING_VIEW { | ||
'PAYLOAD': object; | ||
'RETURN': { | ||
position: Vector3Like; | ||
target: Vector3Like; | ||
}; | ||
} | ||
type Actions = { | ||
@@ -406,2 +359,3 @@ GET_ALL_SCENE_DATA: GET_ALL_SCENE_DATA; | ||
RESET_CAMERA: RESET_CAMERA; | ||
COMPUTE_ENCOMPASSING_VIEW: COMPUTE_ENCOMPASSING_VIEW; | ||
SET_CAMERA_LAYER: SET_CAMERA_LAYER; | ||
@@ -416,2 +370,67 @@ ZOOM_CAMERA: ZOOM_CAMERA; | ||
/** | ||
* A basic floor geometry. | ||
* | ||
* Can change the color and visibility of the floor. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEFloor extends Mesh { | ||
isFloor: true; | ||
constructor(); | ||
SetVisibility(visible: boolean): void; | ||
SetColor(color: ColorRepresentation): void; | ||
} | ||
/** | ||
* A basic grid for the scene. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEGrid extends Object3D { | ||
constructor(); | ||
SetVisibility(visible: boolean): void; | ||
} | ||
/** | ||
* A basic scene node to hold grid, floor and all lower level roots. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVERoot extends Object3D { | ||
private lightRoot; | ||
private modelRoot; | ||
private floor; | ||
private grid; | ||
get Floor(): DIVEFloor; | ||
get Grid(): DIVEGrid; | ||
constructor(); | ||
ComputeSceneBB(): Box3; | ||
GetSceneObject(object: Partial<COMEntity>): Object3D | undefined; | ||
AddSceneObject(object: COMEntity): void; | ||
UpdateSceneObject(object: Partial<COMEntity>): void; | ||
DeleteSceneObject(object: Partial<COMEntity>): void; | ||
PlaceOnFloor(object: Partial<COMModel>): void; | ||
} | ||
/** | ||
* A basic scene class. | ||
* | ||
* Comes with a root object that contains all the scene objects. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEScene extends Scene { | ||
private root; | ||
get Root(): DIVERoot; | ||
constructor(); | ||
SetBackground(color: ColorRepresentation): void; | ||
ComputeSceneBB(): Box3; | ||
GetSceneObject(object: Partial<COMEntity>): Object3D | undefined; | ||
AddSceneObject(object: COMEntity): void; | ||
UpdateSceneObject(object: Partial<COMEntity>): void; | ||
DeleteSceneObject(object: Partial<COMEntity>): void; | ||
PlaceOnFloor(object: Partial<COMModel>): void; | ||
} | ||
interface DIVEDraggable { | ||
@@ -476,2 +495,49 @@ isDraggable: true; | ||
/** | ||
* A Tool to select and move objects in the scene. | ||
* | ||
* Objects have to implement the DIVESelectable interface to be selectable and DIVEMoveable to be moveable. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVETransformTool extends DIVEBaseTool { | ||
readonly isTransformTool: boolean; | ||
protected _gizmo: TransformControls; | ||
constructor(scene: DIVEScene, controller: DIVEOrbitControls); | ||
Activate(): void; | ||
SetGizmoMode(mode: 'translate' | 'rotate' | 'scale'): void; | ||
SetGizmoVisibility(active: boolean): void; | ||
} | ||
/** | ||
* Interface for objects that can be selected in the scene. | ||
* | ||
* @module | ||
*/ | ||
interface DIVESelectable { | ||
isSelectable: true; | ||
onSelect?: () => void; | ||
onDeselect?: () => void; | ||
} | ||
/** | ||
* A Tool to select and move objects in the scene. | ||
* | ||
* Objects have to implement the DIVESelectable interface to be selectable and DIVEMoveable to be moveable. | ||
* | ||
* @module | ||
*/ | ||
declare class DIVESelectTool extends DIVETransformTool { | ||
readonly isSelectTool: boolean; | ||
constructor(scene: DIVEScene, controller: DIVEOrbitControls); | ||
Activate(): void; | ||
Select(selectable: DIVESelectable): void; | ||
Deselect(selectable: DIVESelectable): void; | ||
AttachGizmo(selectable: DIVESelectable): void; | ||
DetachGizmo(): void; | ||
onClick(e: PointerEvent): void; | ||
} | ||
type ToolType = 'select' | 'none'; | ||
/** | ||
* A Toolbox to activate and deactivate tools to use with the pointer. | ||
@@ -483,9 +549,11 @@ * | ||
static readonly DefaultTool = "select"; | ||
private activeTool; | ||
private selectTool; | ||
private removeListenersCallback; | ||
private _scene; | ||
private _controller; | ||
private _activeTool; | ||
private _selectTool; | ||
get selectTool(): DIVESelectTool; | ||
constructor(scene: DIVEScene, controller: DIVEOrbitControls); | ||
dispose(): void; | ||
GetActiveTool(): DIVEBaseTool; | ||
UseTool(tool: string): void; | ||
Dispose(): void; | ||
GetActiveTool(): DIVEBaseTool | null; | ||
UseTool(tool: ToolType): void; | ||
SetGizmoMode(mode: 'translate' | 'rotate' | 'scale'): void; | ||
@@ -497,18 +565,6 @@ SetGizmoVisibility(active: boolean): void; | ||
onWheel(e: WheelEvent): void; | ||
private addEventListeners; | ||
private removeEventListeners; | ||
} | ||
/** | ||
* Creates renderings of the current scene | ||
* | ||
* @module | ||
*/ | ||
declare class DIVEMediaCreator { | ||
private renderer; | ||
private scene; | ||
private controller; | ||
constructor(renderer: DIVERenderer, scene: DIVEScene, controller: DIVEOrbitControls); | ||
GenerateMedia(position: Vector3Like, target: Vector3Like, width: number, height: number): string; | ||
DrawCanvas(canvasElement?: HTMLCanvasElement): HTMLCanvasElement; | ||
} | ||
type EventListener<Action extends keyof Actions> = (payload: Actions[Action]['PAYLOAD']) => void; | ||
@@ -539,9 +595,11 @@ type Unsubscribe = () => boolean; | ||
private id; | ||
private renderer; | ||
private scene; | ||
private controller; | ||
private toolbox; | ||
private mediaGenerator; | ||
private _mediaGenerator; | ||
private get mediaGenerator(); | ||
private registered; | ||
private listeners; | ||
constructor(scene: DIVEScene, controls: DIVEOrbitControls, toolbox: DIVEToolbox, mediaGenerator: DIVEMediaCreator); | ||
constructor(renderer: DIVERenderer, scene: DIVEScene, controls: DIVEOrbitControls, toolbox: DIVEToolbox); | ||
DestroyInstance(): boolean; | ||
@@ -567,2 +625,3 @@ PerformAction<Action extends keyof Actions>(action: Action, payload: Actions[Action]['PAYLOAD']): Actions[Action]['RETURN']; | ||
private resetCamera; | ||
private computeEncompassingView; | ||
private zoomCamera; | ||
@@ -606,2 +665,3 @@ private setGizmoMode; | ||
autoResize: boolean; | ||
displayAxes: boolean; | ||
renderer: DIVERendererSettings; | ||
@@ -635,2 +695,3 @@ perspectiveCamera: DIVEPerspectiveCameraSettings; | ||
declare class DIVE { | ||
static QuickView(uri: string): DIVE; | ||
private _settings; | ||
@@ -644,3 +705,2 @@ private _resizeObserverId; | ||
private orbitControls; | ||
private mediaCreator; | ||
private toolbox; | ||
@@ -654,2 +714,3 @@ private communication; | ||
constructor(settings?: Partial<DIVESettings>); | ||
Dispose(): void; | ||
OnResize(width: number, height: number): void; | ||
@@ -656,0 +717,0 @@ private addResizeObserver; |
{ | ||
"name": "@shopware-ag/dive", | ||
"version": "1.5.0", | ||
"version": "1.6.0", | ||
"description": "Shopware Spatial Framework", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -80,2 +80,14 @@ | ||
# Quick View | ||
QuickView is used to quickly display your assets with as few lines of code as possible. Simply call the static ``QuickView()`` method (with your data-uri as a parameter) to create an instance of DIVE with your asset to use in further code. | ||
```ts | ||
import { DIVE } from '@shopware-ag/dive'; | ||
const dive = DIVE.QuickView('your/asset/uri.glb'); // <-- call QuickView() | ||
const myCanvasWrapper = document.createElement('div'); | ||
myCanvasWrapper.appendChild(dive.Canvas); | ||
``` | ||
# Getting started | ||
@@ -163,26 +175,27 @@ Import: | ||
| Action | Description | ||
|:-------------------------------------------------------------------------------| :--- | ||
| [GET_ALL_SCENE_DATA](./src/com/actions/scene/getallscenedata.ts) | Return all scene data that is currently set | ||
| [GET_ALL_OBJECTS](./src/com/actions/object/getallobjects.ts) | Return a map of all objects | ||
| [GET_OBJECTS](./src/com/actions/object/getobjects.ts) | Return an array of all objects with given ids | ||
| [PLACE_ON_FLOOR](./src/com/actions/object/model/placeonfloor.ts) | Set a model onto to the floor | ||
| [ADD_OBJECT](./src/com/actions/object/addobject.ts) | Add an object to the scene | ||
| [UPDATE_OBJECT](./src/com/actions/object/updateobject.ts) | Update an existing object | ||
| [DELETE_OBJECT](./src/com/actions/object/deleteobject.ts) | Delete an existing object | ||
| [SELECT_OBJECT](./src/com/actions/object/selectobject.ts) | Select an existing object in the scene | ||
| [DESELECT_OBJECT](./src/com/actions/object/deselectobject.ts) | Deselect an existing object in the scene | ||
| [SET_BACKGROUND](./src/com/actions/scene/setbackground.ts) | Set a background color | ||
| [DROP_IT](./src/com/actions/object/model/dropit.ts) | Places the model onto the next underlying object's bounding box | ||
| [PLACE_ON_FLOOR](./src/com/actions/object/model/placeonfloor.ts) | Places the model onto the floor (zero plane) | ||
| [SET_CAMERA_TRANSFORM](./src/com/actions/camera/setcameratransform.ts) | Set camera transformation (w/o animation, used to initially set up camera) | ||
| [GET_CAMERA_TRANSFORM](./src/com/actions/camera/getcameratransform.ts) | Return currenty camera transformation | ||
| [MOVE_CAMERA](./src/com/actions/camera/movecamera.ts) | Move camera to a specific position or the position of a previously defined POV (with an animation) | ||
| [RESET_CAMERA](./src/com/actions/camera/resetcamera.ts) | Reset camera to original position after MOVE_CAMERA was performed | ||
| [SET_CAMERA_LAYER](./src/com/actions/camera/setcameralayer.ts) | Set camera layer to switch between live view and editor view | ||
| [ZOOM_CAMERA](./src/com/actions/camera/zoomcamera.ts) | Zoom in or out | ||
| [SET_GIZMO_MODE](./src/com/actions/toolbox/select/setgizmomode.ts) | Set gizmo mode | ||
| [SET_GIZMO_VISIBILITY](./src/com/actions/toolbox/select/setgizmovisibility.ts) | Set gizmo visibility | ||
| [MODEL_LOADED](./src/com/actions/object/model/modelloaded.ts) | Is performed when a model file is completely loaded | ||
| [UPDATE_SCENE](./src/com/actions/scene/updatescene.ts) | Update scene data | ||
| [GENERATE_MEDIA](./src/com/actions/media/generatemedia.ts) | Generate a screenshot with the specified parameters | ||
| Action | Description | ||
|:--------------------------------------------------------------------------------------| :--- | ||
| [GET_ALL_SCENE_DATA](./src/com/actions/scene/getallscenedata.ts) | Return all scene data that is currently set | ||
| [GET_ALL_OBJECTS](./src/com/actions/object/getallobjects.ts) | Return a map of all objects | ||
| [GET_OBJECTS](./src/com/actions/object/getobjects.ts) | Return an array of all objects with given ids | ||
| [PLACE_ON_FLOOR](./src/com/actions/object/model/placeonfloor.ts) | Set a model onto to the floor | ||
| [ADD_OBJECT](./src/com/actions/object/addobject.ts) | Add an object to the scene | ||
| [UPDATE_OBJECT](./src/com/actions/object/updateobject.ts) | Update an existing object | ||
| [DELETE_OBJECT](./src/com/actions/object/deleteobject.ts) | Delete an existing object | ||
| [SELECT_OBJECT](./src/com/actions/object/selectobject.ts) | Select an existing object in the scene | ||
| [DESELECT_OBJECT](./src/com/actions/object/deselectobject.ts) | Deselect an existing object in the scene | ||
| [SET_BACKGROUND](./src/com/actions/scene/setbackground.ts) | Set a background color | ||
| [DROP_IT](./src/com/actions/object/model/dropit.ts) | Places the model onto the next underlying object's bounding box | ||
| [PLACE_ON_FLOOR](./src/com/actions/object/model/placeonfloor.ts) | Places the model onto the floor (zero plane) | ||
| [SET_CAMERA_TRANSFORM](./src/com/actions/camera/setcameratransform.ts) | Set camera transformation (w/o animation, used to initially set up camera) | ||
| [GET_CAMERA_TRANSFORM](./src/com/actions/camera/getcameratransform.ts) | Return currenty camera transformation | ||
| [MOVE_CAMERA](./src/com/actions/camera/movecamera.ts) | Move camera to a specific position or the position of a previously defined POV (with an animation) | ||
| [RESET_CAMERA](./src/com/actions/camera/resetcamera.ts) | Reset camera to original position after MOVE_CAMERA was performed | ||
| [COMPUTE_ENCOMPASSING_VIEW](./src/com/actions/camera/computeencompassingview.ts), | Calculates the camera position and target to view the whole scene | ||
| [SET_CAMERA_LAYER](./src/com/actions/camera/setcameralayer.ts) | Set camera layer to switch between live view and editor view | ||
| [ZOOM_CAMERA](./src/com/actions/camera/zoomcamera.ts) | Zoom in or out | ||
| [SET_GIZMO_MODE](./src/com/actions/toolbox/select/setgizmomode.ts) | Set gizmo mode | ||
| [SET_GIZMO_VISIBILITY](./src/com/actions/toolbox/select/setgizmovisibility.ts) | Set gizmo visibility | ||
| [MODEL_LOADED](./src/com/actions/object/model/modelloaded.ts) | Is performed when a model file is completely loaded | ||
| [UPDATE_SCENE](./src/com/actions/scene/updatescene.ts) | Update scene data | ||
| [GENERATE_MEDIA](./src/com/actions/media/generatemedia.ts) | Generate a screenshot with the specified parameters |
@@ -33,25 +33,19 @@ import DIVE, { DIVESettings } from '../dive.ts'; | ||
jest.mock('../renderer/Renderer.ts', () => { | ||
jest.mock('three/src/math/MathUtils', () => { | ||
return { | ||
generateUUID: () => { return 'test_uuid'; }, | ||
} | ||
}); | ||
jest.mock('../com/Communication.ts', () => { | ||
return jest.fn(function () { | ||
this.domElement = { | ||
clientWidth: 800, | ||
clientHeight: 600, | ||
style: { | ||
position: 'absolute', | ||
}, | ||
}; | ||
this.domElement.parentElement = this.domElement; | ||
this.AddPreRenderCallback = (callback: () => void) => { | ||
callback(); | ||
}; | ||
this.RemovePreRenderCallback = jest.fn(); | ||
this.AddPostRenderCallback = (callback: () => void) => { | ||
callback(); | ||
}; | ||
this.getViewport = jest.fn(); | ||
this.setViewport = jest.fn(); | ||
this.autoClear = false; | ||
this.render = jest.fn(); | ||
this.StartRenderer = jest.fn(); | ||
this.OnResize = jest.fn(); | ||
this.PerformAction = jest.fn().mockReturnValue({ | ||
position: { x: 0, y: 0, z: 0 }, | ||
target: { x: 0, y: 0, z: 0 }, | ||
}); | ||
this.Subscribe = jest.fn((action: string, callback: (data: { id: string }) => void) => { | ||
callback({ id: 'incorrect id' }); | ||
callback({ id: 'test_uuid' }); | ||
}); | ||
this.DestroyInstance = jest.fn(); | ||
@@ -62,2 +56,32 @@ return this; | ||
jest.mock('../renderer/Renderer.ts', () => { | ||
return { | ||
DIVERenderer: jest.fn(function () { | ||
this.domElement = { | ||
clientWidth: 800, | ||
clientHeight: 600, | ||
style: { | ||
position: 'absolute', | ||
}, | ||
}; | ||
this.domElement.parentElement = this.domElement; | ||
this.AddPreRenderCallback = (callback: () => void) => { | ||
callback(); | ||
}; | ||
this.RemovePreRenderCallback = jest.fn(); | ||
this.AddPostRenderCallback = (callback: () => void) => { | ||
callback(); | ||
}; | ||
this.getViewport = jest.fn(); | ||
this.setViewport = jest.fn(); | ||
this.autoClear = false; | ||
this.render = jest.fn(); | ||
this.StartRenderer = jest.fn(); | ||
this.OnResize = jest.fn(); | ||
this.Dispose = jest.fn(); | ||
return this; | ||
}), | ||
} | ||
}); | ||
jest.mock('../scene/Scene.ts', () => { | ||
@@ -136,2 +160,3 @@ return jest.fn(function () { | ||
} | ||
this.Dispose = jest.fn(); | ||
this.removeFromParent = jest.fn(); | ||
@@ -158,2 +183,3 @@ return this; | ||
this.SetFromCameraMatrix = jest.fn(); | ||
this.Dispose = jest.fn(); | ||
return this; | ||
@@ -164,2 +190,7 @@ }); | ||
describe('dive/DIVE', () => { | ||
it('should QuickView', () => { | ||
const dive = DIVE.QuickView('test_uri'); | ||
expect(dive).toBeDefined(); | ||
}); | ||
it('should instantiate', () => { | ||
@@ -173,5 +204,17 @@ const dive = new DIVE(); | ||
it('should dispose', () => { | ||
let dive = new DIVE(); | ||
expect(() => dive.Dispose()).not.toThrow(); | ||
const settings = { | ||
displayAxes: true, | ||
} | ||
dive = new DIVE(settings); | ||
expect(() => dive.Dispose()).not.toThrow(); | ||
}); | ||
it('should instantiate with settings', () => { | ||
const settings = { | ||
autoResize: false, | ||
displayAxes: true, | ||
renderer: { | ||
@@ -218,2 +261,3 @@ antialias: false, | ||
autoResize: false, | ||
displayAxes: true, | ||
renderer: { | ||
@@ -220,0 +264,0 @@ antialias: false, |
@@ -0,1 +1,2 @@ | ||
import { DIVERenderer } from '../../renderer/Renderer'; | ||
import DIVEAnimationSystem from '../AnimationSystem'; | ||
@@ -9,5 +10,20 @@ | ||
const mockRenderer = { | ||
render: jest.fn(), | ||
OnResize: jest.fn(), | ||
getViewport: jest.fn(), | ||
setViewport: jest.fn(), | ||
AddPreRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
AddPostRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
RemovePreRenderCallback: jest.fn(), | ||
RemovePostRenderCallback: jest.fn(), | ||
} as unknown as DIVERenderer; | ||
describe('dive/animation/DIVEAnimationSystem', () => { | ||
it('should instantiate', () => { | ||
const anim = new DIVEAnimationSystem(); | ||
const anim = new DIVEAnimationSystem(mockRenderer); | ||
expect(anim).toBeDefined(); | ||
@@ -17,5 +33,10 @@ }); | ||
it('should update', () => { | ||
const anim = new DIVEAnimationSystem(); | ||
const anim = new DIVEAnimationSystem(mockRenderer); | ||
expect(() => anim.update()).not.toThrow(); | ||
}); | ||
it('should dispose', () => { | ||
const anim = new DIVEAnimationSystem(mockRenderer); | ||
expect(() => anim.Dispose()).not.toThrow(); | ||
}); | ||
}); |
import { update as updateTween } from "@tweenjs/tween.js"; | ||
import { DIVERenderer } from "../renderer/Renderer"; | ||
@@ -11,2 +12,17 @@ /** | ||
export default class DIVEAnimationSystem { | ||
private _renderer: DIVERenderer; | ||
private _rendererCallbackId: string; | ||
constructor(renderer: DIVERenderer) { | ||
this._renderer = renderer; | ||
this._rendererCallbackId = this._renderer.AddPreRenderCallback(() => { | ||
this.update(); | ||
}) | ||
} | ||
public Dispose(): void { | ||
this._renderer.RemovePreRenderCallback(this._rendererCallbackId); | ||
} | ||
public update(): void { | ||
@@ -13,0 +29,0 @@ updateTween(); |
@@ -1,4 +0,62 @@ | ||
import { Matrix4 } from 'three'; | ||
import { AxesHelper, Matrix4, OrthographicCamera, Vector4 } from 'three'; | ||
import DIVEAxisCamera from '../AxisCamera'; | ||
import { DIVERenderer } from '../../renderer/Renderer'; | ||
import DIVEScene from '../../scene/Scene'; | ||
import DIVEOrbitControls from '../../controls/OrbitControls'; | ||
jest.mock('three', () => { | ||
return { | ||
Vector4: jest.fn(), | ||
Color: jest.fn(function () { | ||
this.getHexString = jest.fn().mockReturnValue('ffffff'); | ||
return this; | ||
}), | ||
Matrix4: jest.fn(function () { | ||
this.extractRotation = jest.fn(() => { return this; }); | ||
this.invert = jest.fn(() => { return this; }); | ||
this.elements = [ | ||
1, 0, 0, 0, | ||
0, 1, 0, 0, | ||
0, 0, 1, 0, | ||
0, 0, 0, 1, | ||
]; | ||
return this; | ||
}), | ||
OrthographicCamera: jest.fn(function () { | ||
this.isObject3D = true; | ||
this.parent = null; | ||
this.dispatchEvent = jest.fn(); | ||
this.layers = { | ||
mask: 0, | ||
}; | ||
this.position = { | ||
set: jest.fn(), | ||
}; | ||
this.add = jest.fn(); | ||
return this; | ||
}), | ||
AxesHelper: jest.fn(function () { | ||
this.isObject3D = true; | ||
this.parent = null; | ||
this.dispatchEvent = jest.fn(); | ||
this.layers = { | ||
mask: 0, | ||
}; | ||
this.position = { | ||
set: jest.fn(), | ||
}; | ||
this.add = jest.fn(); | ||
this.material = { | ||
depthTest: false, | ||
}; | ||
this.setColors = jest.fn(); | ||
this.rotation = { | ||
setFromRotationMatrix: jest.fn(), | ||
} | ||
return this; | ||
}), | ||
} | ||
}); | ||
jest.mock('three-spritetext', () => { | ||
@@ -22,6 +80,118 @@ return jest.fn(() => { | ||
const mockRenderer = { | ||
render: jest.fn(), | ||
OnResize: jest.fn(), | ||
getViewport: jest.fn(), | ||
setViewport: jest.fn(), | ||
AddPostRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
RemovePostRenderCallback: jest.fn(), | ||
} as unknown as DIVERenderer; | ||
const mockScene = { | ||
add: jest.fn(), | ||
remove: jest.fn(), | ||
SetBackground: jest.fn(), | ||
AddSceneObject: jest.fn(), | ||
UpdateSceneObject: jest.fn(), | ||
DeleteSceneObject: jest.fn(), | ||
PlaceOnFloor: jest.fn(), | ||
GetSceneObject: jest.fn(), | ||
background: { | ||
getHexString: jest.fn().mockReturnValue('ffffff'), | ||
}, | ||
Root: { | ||
Floor: { | ||
isFloor: true, | ||
visible: true, | ||
material: { | ||
color: { | ||
getHexString: jest.fn().mockReturnValue('ffffff'), | ||
}, | ||
}, | ||
SetVisibility: jest.fn(), | ||
SetColor: jest.fn(), | ||
}, | ||
Grid: { | ||
SetVisibility: jest.fn(), | ||
}, | ||
}, | ||
} as unknown as DIVEScene; | ||
const mockController = { | ||
enableDamping: true, | ||
dampingFactor: 0.25, | ||
enableZoom: true, | ||
enablePan: true, | ||
minPolarAngle: 0, | ||
maxPolarAngle: Math.PI, | ||
minDistance: 0, | ||
maxDistance: Infinity, | ||
rotateSpeed: 0.5, | ||
panSpeed: 0.5, | ||
zoomSpeed: 0.5, | ||
keyPanSpeed: 0.5, | ||
screenSpacePanning: true, | ||
autoRotate: false, | ||
autoRotateSpeed: 2.0, | ||
enableKeys: true, | ||
keys: { | ||
LEFT: 37, | ||
UP: 38, | ||
RIGHT: 39, | ||
BOTTOM: 40, | ||
}, | ||
mouseButtons: { | ||
LEFT: 0, | ||
MIDDLE: 1, | ||
RIGHT: 2, | ||
}, | ||
target: { | ||
x: 4, | ||
y: 5, | ||
z: 6, | ||
set: jest.fn(), | ||
clone: jest.fn().mockReturnValue({ x: 4, y: 5, z: 6 }), | ||
copy: jest.fn(), | ||
}, | ||
update: jest.fn(), | ||
dispose: jest.fn(), | ||
ZoomIn: jest.fn(), | ||
ZoomOut: jest.fn(), | ||
object: { | ||
position: { | ||
x: 1, | ||
y: 2, | ||
z: 3, | ||
clone: jest.fn().mockReturnValue({ x: 1, y: 2, z: 3 }), | ||
copy: jest.fn(), | ||
}, | ||
quaternion: { | ||
x: 1, | ||
y: 2, | ||
z: 3, | ||
w: 4, | ||
clone: jest.fn().mockReturnValue({ x: 1, y: 2, z: 3, w: 4 }), | ||
copy: jest.fn(), | ||
}, | ||
SetCameraLayer: jest.fn(), | ||
OnResize: jest.fn(), | ||
layers: { | ||
mask: 1, | ||
}, | ||
}, | ||
MoveTo: jest.fn(), | ||
RevertLast: jest.fn(), | ||
} as unknown as DIVEOrbitControls; | ||
let textAxisCamera: DIVEAxisCamera; | ||
describe('dive/axiscamera/DIVEAxisCamera', () => { | ||
beforeEach(() => { | ||
textAxisCamera = new DIVEAxisCamera(mockRenderer, mockScene, mockController); | ||
}); | ||
it('should instantiate', () => { | ||
const cam = new DIVEAxisCamera(); | ||
expect(cam).toBeDefined(); | ||
expect(textAxisCamera).toBeDefined(); | ||
}); | ||
@@ -39,5 +209,8 @@ | ||
} as Matrix4; | ||
const cam = new DIVEAxisCamera(); | ||
cam.SetFromCameraMatrix(matrix); | ||
textAxisCamera.SetFromCameraMatrix(matrix); | ||
}); | ||
it('should dispose', () => { | ||
textAxisCamera.Dispose(); | ||
}); | ||
}); |
@@ -1,5 +0,8 @@ | ||
import { AxesHelper, Color, Material, Matrix4, OrthographicCamera } from "three"; | ||
import { AxesHelper, Color, Material, Matrix4, OrthographicCamera, Vector4 } from "three"; | ||
import SpriteText from "three-spritetext"; | ||
import { COORDINATE_LAYER_MASK } from "../constant/VisibilityLayerMask.ts"; | ||
import { AxesColorRed, AxesColorGreen, AxesColorBlue, AxesColorRedLetter, AxesColorGreenLetter, AxesColorBlueLetter } from "../constant/AxisHelperColors.ts"; | ||
import { DIVERenderer } from "../renderer/Renderer.ts"; | ||
import DIVEScene from "../scene/Scene.ts"; | ||
import DIVEOrbitControls from "../controls/OrbitControls.ts"; | ||
@@ -15,3 +18,8 @@ /** | ||
constructor() { | ||
private _renderer: DIVERenderer; | ||
private _scene: DIVEScene; | ||
private _renderCallbackId: string; | ||
constructor(renderer: DIVERenderer, scene: DIVEScene, controls: DIVEOrbitControls) { | ||
super(-1, 1, 1, -1, 0.1, 100); | ||
@@ -46,4 +54,34 @@ | ||
this.add(this.axesHelper); | ||
// attach everything to current scene and render cycle | ||
this._renderer = renderer; | ||
this._scene = scene; | ||
this._scene.add(this); | ||
const restoreViewport = new Vector4(); | ||
this._renderCallbackId = renderer.AddPostRenderCallback(() => { | ||
const restoreBackground = scene.background; | ||
scene.background = null; | ||
renderer.getViewport(restoreViewport); | ||
renderer.setViewport(0, 0, 150, 150); | ||
renderer.autoClear = false; | ||
this.SetFromCameraMatrix(controls.object.matrix); | ||
renderer.render(scene, this); | ||
renderer.setViewport(restoreViewport); | ||
renderer.autoClear = true; | ||
scene.background = restoreBackground; | ||
}); | ||
} | ||
public Dispose(): void { | ||
this._renderer.RemovePostRenderCallback(this._renderCallbackId); | ||
this._scene.remove(this); | ||
} | ||
public SetFromCameraMatrix(matrix: Matrix4): void { | ||
@@ -50,0 +88,0 @@ this.axesHelper.rotation.setFromRotationMatrix(new Matrix4().extractRotation(matrix).invert()); |
import DIVECommunication from '../Communication'; | ||
import DIVEScene from '../../scene/Scene'; | ||
import DIVEToolbox from '../../toolbox/Toolbox'; | ||
import DIVEMediaCreator from '../../mediacreator/MediaCreator'; | ||
import '..'; | ||
@@ -28,5 +25,9 @@ import '../types'; | ||
import '../actions/camera/getcameratransform'; | ||
import DIVEOrbitControls from '../../controls/OrbitControls'; | ||
import { COMLight, COMModel, COMPov } from '../types'; | ||
import { Object3D } from 'three'; | ||
import type DIVEScene from '../../scene/Scene'; | ||
import type DIVEToolbox from '../../toolbox/Toolbox'; | ||
import type DIVEOrbitControls from '../../controls/OrbitControls'; | ||
import { type DIVERenderer } from '../../renderer/Renderer'; | ||
import { type COMLight, type COMModel, type COMPov } from '../types'; | ||
import { type Object3D } from 'three'; | ||
import { DIVESelectTool, isSelectTool } from '../../toolbox/select/SelectTool'; | ||
@@ -39,2 +40,29 @@ jest.mock('three/src/math/MathUtils', () => { | ||
jest.mock('../../mediacreator/MediaCreator', () => { | ||
return { | ||
default: jest.fn(function () { | ||
this.GenerateMedia = jest.fn(); | ||
return this; | ||
}), | ||
} | ||
}); | ||
jest.mock('../../toolbox/select/SelectTool', () => { | ||
return { | ||
isSelectTool: jest.fn().mockReturnValue(true), | ||
DIVESelectTool: jest.fn().mockImplementation(() => { | ||
return { | ||
AttachGizmo: jest.fn(), | ||
DetachGizmo: jest.fn(), | ||
}; | ||
}), | ||
} | ||
}); | ||
const mockRenderer = { | ||
render: jest.fn(), | ||
OnResize: jest.fn(), | ||
} as unknown as DIVERenderer; | ||
const mockScene = { | ||
@@ -61,4 +89,8 @@ SetBackground: jest.fn(), | ||
SetColor: jest.fn(), | ||
} | ||
}, | ||
Grid: { | ||
SetVisibility: jest.fn(), | ||
}, | ||
}, | ||
ComputeSceneBB: jest.fn(), | ||
} as unknown as DIVEScene; | ||
@@ -114,15 +146,29 @@ | ||
}, | ||
quaternion: { | ||
x: 1, | ||
y: 2, | ||
z: 3, | ||
w: 4, | ||
clone: jest.fn().mockReturnValue({ x: 1, y: 2, z: 3, w: 4 }), | ||
copy: jest.fn(), | ||
}, | ||
SetCameraLayer: jest.fn(), | ||
OnResize: jest.fn(), | ||
layers: { | ||
mask: 1, | ||
}, | ||
}, | ||
MoveTo: jest.fn(), | ||
RevertLast: jest.fn(), | ||
ComputeEncompassingView: jest.fn().mockReturnValue({ | ||
position: { x: 1, y: 2, z: 3 }, | ||
target: { x: 4, y: 5, z: 6 } | ||
}), | ||
} as unknown as DIVEOrbitControls; | ||
const mockAttach = jest.fn(); | ||
const mockDetach = jest.fn(); | ||
const mockToolBox = { | ||
UseTool: jest.fn(), | ||
GetActiveTool: jest.fn().mockReturnValue({ | ||
AttachGizmo: mockAttach, | ||
DetachGizmo: mockDetach, | ||
AttachGizmo: jest.fn(), | ||
DetachGizmo: jest.fn(), | ||
}), | ||
@@ -133,5 +179,2 @@ SetGizmoMode: jest.fn(), | ||
const mockMediaCreator = { | ||
GenerateMedia: jest.fn(), | ||
} as unknown as DIVEMediaCreator; | ||
let testCom: DIVECommunication; | ||
@@ -142,3 +185,3 @@ | ||
beforeEach(() => { | ||
testCom = new DIVECommunication(mockScene, mockController, mockToolBox, mockMediaCreator); | ||
testCom = new DIVECommunication(mockRenderer, mockScene, mockController, mockToolBox); | ||
}); | ||
@@ -202,9 +245,2 @@ | ||
// it('should not dispatch with invalid action type', () => { | ||
// const listener = jest.fn(); | ||
// testCom.Subscribe('GET_ALL_OBJECTS', listener); | ||
// testCom.dispatch('INVALID_ACTION_TYPE', {}); | ||
// expect(listener).toHaveBeenCalledTimes(0); | ||
// }); | ||
it('should perform action ADD_OBJECT', () => { | ||
@@ -444,2 +480,15 @@ const payload = { | ||
it('should perform action COMPUTE_ENCOMPASSING_VIEW', () => { | ||
const payload = {}; | ||
const transform = testCom.PerformAction('COMPUTE_ENCOMPASSING_VIEW', payload); | ||
expect(transform).toStrictEqual({ | ||
position: { x: 1, y: 2, z: 3 }, | ||
target: { x: 4, y: 5, z: 6 } | ||
}); | ||
expect(payload).toStrictEqual({ | ||
position: { x: 1, y: 2, z: 3 }, | ||
target: { x: 4, y: 5, z: 6 } | ||
}); | ||
}); | ||
it('should perform action GET_ALL_SCENE_DATA', () => { | ||
@@ -555,3 +604,2 @@ testCom.PerformAction('ADD_OBJECT', { | ||
expect(success3).toBe(true); | ||
expect(mockAttach).toHaveBeenCalledTimes(1); | ||
}); | ||
@@ -582,3 +630,2 @@ | ||
expect(success3).toBe(true); | ||
expect(mockDetach).toHaveBeenCalledTimes(1); | ||
}); | ||
@@ -660,2 +707,3 @@ | ||
floorColor: 'ffffff', | ||
gridEnabled: true, | ||
}); | ||
@@ -669,2 +717,3 @@ expect(success0).toBe(true); | ||
floorColor: undefined, | ||
gridEnabled: undefined, | ||
}); | ||
@@ -671,0 +720,0 @@ expect(success1).toBe(true); |
@@ -23,2 +23,3 @@ import SET_BACKGROUND from "./scene/setbackground.ts"; | ||
import SET_GIZMO_VISIBILITY from "./toolbox/transform/setgizmovisible.js"; | ||
import COMPUTE_ENCOMPASSING_VIEW from "./camera/computeencompassingview.ts"; | ||
@@ -41,2 +42,3 @@ export type Actions = { | ||
RESET_CAMERA: RESET_CAMERA, | ||
COMPUTE_ENCOMPASSING_VIEW: COMPUTE_ENCOMPASSING_VIEW, | ||
SET_CAMERA_LAYER: SET_CAMERA_LAYER, | ||
@@ -43,0 +45,0 @@ ZOOM_CAMERA: ZOOM_CAMERA, |
@@ -5,2 +5,3 @@ export default interface UPDATE_SCENE { | ||
backgroundColor?: string | number, | ||
gridEnabled?: boolean, | ||
floorEnabled?: boolean, | ||
@@ -7,0 +8,0 @@ floorColor?: string | number |
@@ -1,11 +0,15 @@ | ||
import { Color, MeshStandardMaterial, MathUtils } from "three"; | ||
import DIVEScene from "../scene/Scene.ts"; | ||
import { Actions } from "./actions/index.ts"; | ||
import { COMLight, COMModel, COMEntity, COMPov } from "./types.ts"; | ||
import DIVEToolbox from "../toolbox/Toolbox.ts"; | ||
import DIVEMediaCreator from "../mediacreator/MediaCreator.ts"; | ||
import DIVEOrbitControls from "../controls/OrbitControls.ts"; | ||
import { DIVESelectable } from "../interface/Selectable.ts"; | ||
import DIVESelectTool from "../toolbox/select/SelectTool.ts"; | ||
import { generateUUID } from 'three/src/math/MathUtils'; | ||
// type imports | ||
import { type Color, type MeshStandardMaterial } from "three"; | ||
import { type COMLight, type COMModel, type COMEntity, type COMPov } from "./types.ts"; | ||
import type DIVEScene from "../scene/Scene.ts"; | ||
import type DIVEToolbox from "../toolbox/Toolbox.ts"; | ||
import type DIVEOrbitControls from "../controls/OrbitControls.ts"; | ||
import type DIVEModel from "../model/Model.ts"; | ||
import { type DIVEMediaCreator } from "../mediacreator/MediaCreator.ts"; | ||
import { type DIVERenderer } from "../renderer/Renderer.ts"; | ||
import { type DIVESelectable } from "../interface/Selectable.ts"; | ||
import { isSelectTool } from "../toolbox/select/SelectTool.ts"; | ||
@@ -44,7 +48,16 @@ type EventListener<Action extends keyof Actions> = (payload: Actions[Action]['PAYLOAD']) => void; | ||
private id: string; | ||
private renderer: DIVERenderer; | ||
private scene: DIVEScene; | ||
private controller: DIVEOrbitControls; | ||
private toolbox: DIVEToolbox; | ||
private mediaGenerator: DIVEMediaCreator; | ||
private _mediaGenerator: DIVEMediaCreator | null; | ||
private get mediaGenerator(): DIVEMediaCreator { | ||
if (!this._mediaGenerator) { | ||
const DIVEMediaCreator = require('../mediacreator/MediaCreator.ts').default as typeof import('../mediacreator/MediaCreator.ts').DIVEMediaCreator; | ||
this._mediaGenerator = new DIVEMediaCreator(this.renderer, this.scene, this.controller); | ||
} | ||
return this._mediaGenerator; | ||
} | ||
private registered: Map<string, COMEntity> = new Map(); | ||
@@ -55,8 +68,9 @@ | ||
constructor(scene: DIVEScene, controls: DIVEOrbitControls, toolbox: DIVEToolbox, mediaGenerator: DIVEMediaCreator) { | ||
this.id = MathUtils.generateUUID(); | ||
constructor(renderer: DIVERenderer, scene: DIVEScene, controls: DIVEOrbitControls, toolbox: DIVEToolbox) { | ||
this.id = generateUUID(); | ||
this.renderer = renderer; | ||
this.scene = scene; | ||
this.controller = controls; | ||
this.toolbox = toolbox; | ||
this.mediaGenerator = mediaGenerator; | ||
this._mediaGenerator = null; | ||
@@ -137,2 +151,6 @@ DIVECommunication.__instances.push(this); | ||
} | ||
case 'COMPUTE_ENCOMPASSING_VIEW': { | ||
returnValue = this.computeEncompassingView(payload as Actions['COMPUTE_ENCOMPASSING_VIEW']['PAYLOAD']); | ||
break; | ||
} | ||
case 'SET_CAMERA_LAYER': { | ||
@@ -282,4 +300,6 @@ returnValue = this.setCameraLayer(payload as Actions['SET_CAMERA_LAYER']['PAYLOAD']); | ||
this.toolbox.UseTool('select'); | ||
(this.toolbox.GetActiveTool() as DIVESelectTool).AttachGizmo(sceneObject as DIVESelectable); | ||
const activeTool = this.toolbox.GetActiveTool(); | ||
if (activeTool && isSelectTool(activeTool)) { | ||
activeTool.AttachGizmo(sceneObject as DIVESelectable); | ||
} | ||
@@ -301,4 +321,6 @@ // copy object to payload to use later | ||
this.toolbox.UseTool('select'); | ||
(this.toolbox.GetActiveTool() as DIVESelectTool).DetachGizmo(); | ||
const activeTool = this.toolbox.GetActiveTool(); | ||
if (activeTool && isSelectTool(activeTool)) { | ||
activeTool.DetachGizmo(); | ||
} | ||
@@ -380,2 +402,11 @@ // copy object to payload to use later | ||
private computeEncompassingView(payload: Actions['COMPUTE_ENCOMPASSING_VIEW']['PAYLOAD']): Actions['COMPUTE_ENCOMPASSING_VIEW']['RETURN'] { | ||
const sceneBB = this.scene.ComputeSceneBB(); | ||
const transform = this.controller.ComputeEncompassingView(sceneBB); | ||
Object.assign(payload, transform); | ||
return transform; | ||
} | ||
private zoomCamera(payload: Actions['ZOOM_CAMERA']['PAYLOAD']): Actions['ZOOM_CAMERA']['RETURN'] { | ||
@@ -407,2 +438,4 @@ if (payload.direction === 'IN') this.controller.ZoomIn(payload.by); | ||
if (payload.gridEnabled !== undefined) this.scene.Root.Grid.SetVisibility(payload.gridEnabled); | ||
if (payload.floorEnabled !== undefined) this.scene.Root.Floor.SetVisibility(payload.floorEnabled); | ||
@@ -416,2 +449,3 @@ if (payload.floorColor !== undefined) this.scene.Root.Floor.SetColor(payload.floorColor); | ||
payload.backgroundColor = '#' + (this.scene.background as Color).getHexString(); | ||
payload.gridEnabled = this.scene.Root.Grid.visible; | ||
payload.floorEnabled = this.scene.Root.Floor.visible; | ||
@@ -418,0 +452,0 @@ payload.floorColor = '#' + (this.scene.Root.Floor.material as MeshStandardMaterial).color.getHexString(); |
import DIVEOrbitControls from '../OrbitControls'; | ||
import DIVEPerspectiveCamera from '../../camera/PerspectiveCamera'; | ||
import DIVERenderer, { DIVERendererDefaultSettings } from '../../renderer/Renderer'; | ||
import type DIVEPerspectiveCamera from '../../camera/PerspectiveCamera'; | ||
import { DIVERenderer } from '../../renderer/Renderer'; | ||
import { Box3 } from 'three'; | ||
@@ -91,7 +92,28 @@ jest.mock('three/examples/jsm/Addons.js', () => { | ||
position: { | ||
clone: jest.fn(), | ||
clone: jest.fn(() => { | ||
return mockCamera.position; | ||
}), | ||
normalize: jest.fn(() => { | ||
return mockCamera.position; | ||
}), | ||
multiplyScalar: jest.fn(() => { | ||
return mockCamera.position; | ||
}), | ||
}, | ||
lookAt: jest.fn(), | ||
} as unknown as DIVEPerspectiveCamera; | ||
const mockRenderer: DIVERenderer = new DIVERenderer(DIVERendererDefaultSettings); | ||
const mockRenderer = { | ||
render: jest.fn(), | ||
OnResize: jest.fn(), | ||
getViewport: jest.fn(), | ||
setViewport: jest.fn(), | ||
AddPreRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
AddPostRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
RemovePreRenderCallback: jest.fn(), | ||
RemovePostRenderCallback: jest.fn(), | ||
} as unknown as DIVERenderer; | ||
@@ -108,2 +130,7 @@ describe('dive/controls/DIVEOrbitControls', () => { | ||
it('should compute encompassing view', () => { | ||
const controller = new DIVEOrbitControls(mockCamera, mockRenderer); | ||
expect(() => controller.ComputeEncompassingView(new Box3())).not.toThrow(); | ||
}); | ||
it('should zoom in with default value', () => { | ||
@@ -110,0 +137,0 @@ const controller = new DIVEOrbitControls(mockCamera, mockRenderer); |
import { OrbitControls } from "three/examples/jsm/Addons.js"; | ||
import DIVEPerspectiveCamera from "../camera/PerspectiveCamera.ts"; | ||
import DIVERenderer from "../renderer/Renderer.ts"; | ||
import { MathUtils, Vector3Like } from "three"; | ||
import { DIVERenderer } from "../renderer/Renderer.ts"; | ||
import { type Box3, MathUtils, Vector3, Vector3Like } from "three"; | ||
import { Easing, Tween } from "@tweenjs/tween.js"; | ||
@@ -52,2 +52,14 @@ | ||
public ComputeEncompassingView(bb: Box3): { position: Vector3Like, target: Vector3Like } { | ||
const center = bb.getCenter(new Vector3()); | ||
const size = bb.getSize(new Vector3()); | ||
const distance = Math.max(size.x, size.y, size.z) * 1.25; | ||
const direction = this.object.position.clone().normalize(); | ||
return { | ||
position: direction.multiplyScalar(distance), | ||
target: center, | ||
}; | ||
} | ||
public ZoomIn(by?: number): void { | ||
@@ -54,0 +66,0 @@ const zoomBy = by || DIVEOrbitControls.DEFAULT_ZOOM_FACTOR; |
126
src/dive.ts
@@ -1,7 +0,5 @@ | ||
import { Vector4 } from "three"; | ||
import DIVERenderer, { DIVERendererDefaultSettings, DIVERendererSettings } from "./renderer/Renderer.ts"; | ||
import { DIVERenderer, DIVERendererDefaultSettings, DIVERendererSettings } from "./renderer/Renderer.ts"; | ||
import DIVEScene from "./scene/Scene.ts"; | ||
import DIVEPerspectiveCamera, { DIVEPerspectiveCameraDefaultSettings, DIVEPerspectiveCameraSettings } from "./camera/PerspectiveCamera.ts"; | ||
import DIVEOrbitControls, { DIVEOrbitControlsDefaultSettings, DIVEOrbitControlsSettings } from "./controls/OrbitControls.ts"; | ||
import DIVEMediaCreator from "./mediacreator/MediaCreator.ts"; | ||
import DIVEToolbox from "./toolbox/Toolbox.ts"; | ||
@@ -16,5 +14,7 @@ import DIVECommunication from "./com/Communication.ts"; | ||
import { DIVEMath } from './math/index.ts'; | ||
import { generateUUID } from "three/src/math/MathUtils"; | ||
export type DIVESettings = { | ||
autoResize: boolean; | ||
displayAxes: boolean; | ||
renderer: DIVERendererSettings; | ||
@@ -27,2 +27,3 @@ perspectiveCamera: DIVEPerspectiveCameraSettings; | ||
autoResize: true, | ||
displayAxes: false, | ||
renderer: DIVERendererDefaultSettings, | ||
@@ -57,2 +58,67 @@ perspectiveCamera: DIVEPerspectiveCameraDefaultSettings, | ||
export default class DIVE { | ||
// static members | ||
public static QuickView(uri: string): DIVE { | ||
const dive = new DIVE(); | ||
dive.Communication.PerformAction('SET_CAMERA_TRANSFORM', { | ||
position: { x: 0, y: 2, z: 2 }, | ||
target: { x: 0, y: 0.5, z: 0 }, | ||
}); | ||
// generate scene light id | ||
const lightid = generateUUID(); | ||
// add scene light | ||
dive.Communication.PerformAction('ADD_OBJECT', { | ||
entityType: 'light', | ||
type: 'scene', | ||
name: 'light', | ||
id: lightid, | ||
enabled: true, | ||
visible: true, | ||
intensity: 1, | ||
color: 0xffffff, | ||
}); | ||
// generate model id | ||
const modelid = generateUUID(); | ||
// add loaded listener | ||
dive.Communication.Subscribe('MODEL_LOADED', (data) => { | ||
if (data.id !== modelid) return; | ||
dive.Communication.PerformAction('PLACE_ON_FLOOR', { | ||
id: modelid, | ||
}); | ||
const transform = dive.Communication.PerformAction('COMPUTE_ENCOMPASSING_VIEW', {}); | ||
dive.Communication.PerformAction('SET_CAMERA_TRANSFORM', { | ||
position: transform.position, | ||
target: transform.target, | ||
}); | ||
}); | ||
// instantiate model | ||
dive.Communication.PerformAction('ADD_OBJECT', { | ||
entityType: 'model', | ||
name: 'object', | ||
id: modelid, | ||
position: { x: 0, y: 0, z: 0 }, | ||
rotation: { x: 0, y: 0, z: 0 }, | ||
scale: { x: 1, y: 1, z: 1 }, | ||
uri: uri, | ||
visible: true, | ||
loaded: false, | ||
}); | ||
// set scene properties | ||
dive.Communication.PerformAction('UPDATE_SCENE', { | ||
backgroundColor: 0xffffff, | ||
gridEnabled: false, | ||
floorColor: 0xffffff, | ||
}); | ||
return dive; | ||
} | ||
// descriptive members | ||
@@ -69,3 +135,2 @@ private _settings: DIVESettings; | ||
private orbitControls: DIVEOrbitControls; | ||
private mediaCreator: DIVEMediaCreator; | ||
private toolbox: DIVEToolbox; | ||
@@ -75,4 +140,4 @@ private communication: DIVECommunication; | ||
// additional components | ||
private animationSystem: DIVEAnimationSystem; | ||
private axisCamera: DIVEAxisCamera; | ||
private animationSystem: DIVEAnimationSystem | null; | ||
private axisCamera: DIVEAxisCamera | null; | ||
@@ -116,2 +181,9 @@ // getters | ||
if (settingsDelta.displayAxes) { | ||
this.axisCamera = new DIVEAxisCamera(this.renderer, this.scene, this.orbitControls); | ||
} else { | ||
this.axisCamera?.Dispose(); | ||
this.axisCamera = null; | ||
} | ||
Object.assign(this._settings, settings); | ||
@@ -132,35 +204,15 @@ } | ||
this.orbitControls = new DIVEOrbitControls(this.perspectiveCamera, this.renderer, this._settings.orbitControls); | ||
this.mediaCreator = new DIVEMediaCreator(this.renderer, this.scene, this.orbitControls); | ||
this.toolbox = new DIVEToolbox(this.scene, this.orbitControls); | ||
this.communication = new DIVECommunication(this.scene, this.orbitControls, this.toolbox, this.mediaCreator); | ||
this.communication = new DIVECommunication(this.renderer, this.scene, this.orbitControls, this.toolbox); | ||
// initialize animation system | ||
this.animationSystem = new DIVEAnimationSystem(); | ||
this.renderer.AddPreRenderCallback(() => { | ||
this.animationSystem.update(); | ||
}) | ||
this.animationSystem = null; | ||
// initialize axis camera | ||
this.axisCamera = new DIVEAxisCamera(); | ||
this.scene.add(this.axisCamera); | ||
const restoreViewport = new Vector4(); | ||
if (this._settings.displayAxes) { | ||
this.axisCamera = new DIVEAxisCamera(this.renderer, this.scene, this.orbitControls); | ||
} else { | ||
this.axisCamera = null; | ||
} | ||
this.renderer.AddPostRenderCallback(() => { | ||
const restoreBackground = this.scene.background; | ||
this.scene.background = null; | ||
this.renderer.getViewport(restoreViewport); | ||
this.renderer.setViewport(0, 0, 150, 150); | ||
this.renderer.autoClear = false; | ||
this.axisCamera.SetFromCameraMatrix(this.perspectiveCamera.matrix); | ||
this.renderer.render(this.scene, this.axisCamera); | ||
this.renderer.setViewport(restoreViewport); | ||
this.renderer.autoClear = true; | ||
this.scene.background = restoreBackground; | ||
}); | ||
// add resize observer if autoResize is enabled | ||
@@ -182,2 +234,10 @@ if (this._settings.autoResize) { | ||
public Dispose(): void { | ||
this.removeResizeObserver(); | ||
this.renderer.Dispose(); | ||
this.axisCamera?.Dispose(); | ||
this.toolbox.Dispose(); | ||
this.communication.DestroyInstance(); | ||
} | ||
// methods | ||
@@ -184,0 +244,0 @@ public OnResize(width: number, height: number): void { |
@@ -19,2 +19,9 @@ import DIVEGrid from '../Grid.ts'; | ||
}); | ||
it('should set visibility', () => { | ||
grid.SetVisibility(false); | ||
expect(grid.visible).toBe(false); | ||
grid.SetVisibility(true); | ||
expect(grid.visible).toBe(true); | ||
}); | ||
}); |
@@ -22,2 +22,6 @@ import { GRID_SIDE_LINE_COLOR, GRID_CENTER_LINE_COLOR } from "../constant/GridColors.ts"; | ||
} | ||
public SetVisibility(visible: boolean): void { | ||
this.visible = visible; | ||
} | ||
} |
@@ -24,2 +24,20 @@ import { type Object3D } from 'three'; | ||
it('should find Selectable', () => { | ||
let Selectable = { | ||
isSelectable: true, | ||
} as unknown as Object3D & Selectable_DEF.DIVESelectable; | ||
expect(Selectable_DEF.findSelectableInterface(Selectable as unknown as Object3D)).toBe(Selectable); | ||
let parent = { | ||
isSelectable: true, | ||
} | ||
Selectable = { | ||
parent: parent, | ||
} as unknown as Object3D & Selectable_DEF.DIVESelectable; | ||
expect(Selectable_DEF.findSelectableInterface(Selectable as unknown as Object3D)).toBe(parent); | ||
Selectable = { isSelectable: true, parent: null } as unknown as Object3D & Selectable_DEF.DIVESelectable; | ||
expect(Selectable_DEF.findSelectableInterface(Selectable as unknown as Object3D)).toBe(undefined); | ||
}); | ||
it('should identify Draggable', () => { | ||
@@ -26,0 +44,0 @@ const Draggable = { isDraggable: true }; |
@@ -17,2 +17,19 @@ /** | ||
return 'isSelectable' in object; | ||
} | ||
export function findSelectableInterface(child: Object3D): (Object3D & DIVESelectable) | undefined { | ||
if (child === undefined) return undefined; | ||
if (child.parent === null) { | ||
// in this case it is the scene itself | ||
return undefined; | ||
} | ||
if (isSelectable(child)) { | ||
// in this case it is the Selectable | ||
return child; | ||
} | ||
// search recursively in parent | ||
return findSelectableInterface(child.parent); | ||
} |
@@ -1,3 +0,3 @@ | ||
import DIVEMediaCreator from '../MediaCreator'; | ||
import DIVERenderer from '../../renderer/Renderer'; | ||
import { DIVEMediaCreator } from '../MediaCreator'; | ||
import { DIVERenderer } from '../../renderer/Renderer'; | ||
import DIVEScene from '../../scene/Scene'; | ||
@@ -74,10 +74,12 @@ import DIVEPerspectiveCamera, { DIVEPerspectiveCameraDefaultSettings } from '../../camera/PerspectiveCamera'; | ||
jest.mock('../../renderer/Renderer', () => { | ||
return jest.fn(function () { | ||
this.domElement = { | ||
toDataURL: mock_toDataURL, | ||
} | ||
this.render = mock_render; | ||
this.OnResize = jest.fn(); | ||
return this; | ||
}); | ||
return { | ||
DIVERenderer: jest.fn(function () { | ||
this.domElement = { | ||
toDataURL: mock_toDataURL, | ||
} | ||
this.render = mock_render; | ||
this.OnResize = jest.fn(); | ||
return this; | ||
}) | ||
} | ||
}); | ||
@@ -84,0 +86,0 @@ |
import DIVEPerspectiveCamera from "../camera/PerspectiveCamera.ts"; | ||
import DIVEScene from "../scene/Scene.ts"; | ||
import DIVERenderer from "../renderer/Renderer.ts"; | ||
import { DIVERenderer } from "../renderer/Renderer.ts"; | ||
import DIVEOrbitControls from "../controls/OrbitControls.ts"; | ||
@@ -13,3 +13,3 @@ import { Vector3Like } from "three"; | ||
export default class DIVEMediaCreator { | ||
export class DIVEMediaCreator { | ||
private renderer: DIVERenderer; | ||
@@ -16,0 +16,0 @@ private scene: DIVEScene; |
@@ -1,4 +0,4 @@ | ||
import DIVEPerspectiveCamera from '../../camera/PerspectiveCamera'; | ||
import DIVEScene from '../../scene/Scene'; | ||
import DIVERenderer, { DIVERendererDefaultSettings } from '../Renderer'; | ||
import type DIVEPerspectiveCamera from '../../camera/PerspectiveCamera'; | ||
import type DIVEScene from '../../scene/Scene'; | ||
import { DIVERenderer, DIVERendererDefaultSettings } from '../Renderer'; | ||
@@ -23,2 +23,3 @@ /** | ||
}; | ||
this.dispose = jest.fn(); | ||
this.debug = { | ||
@@ -47,3 +48,3 @@ checkShaderErrors: true, | ||
jest.clearAllMocks(); | ||
renderer = new DIVERenderer(DIVERendererDefaultSettings); | ||
renderer = new DIVERenderer(); | ||
}); | ||
@@ -53,5 +54,13 @@ | ||
expect(renderer).toBeDefined(); | ||
renderer = new DIVERenderer(); | ||
}); | ||
it('should instantiate with settings parameter', () => { | ||
renderer = new DIVERenderer(DIVERendererDefaultSettings); | ||
expect(renderer).toBeDefined(); | ||
}); | ||
it('should dispose', () => { | ||
renderer.Dispose(); | ||
}); | ||
it('should start render', () => { | ||
@@ -58,0 +67,0 @@ expect(() => { renderer.StartRenderer({} as DIVEScene, {} as DIVEPerspectiveCamera) }).not.toThrow(); |
@@ -29,3 +29,3 @@ import { Camera, MathUtils, NoToneMapping, PCFSoftShadowMap, Scene, ShadowMapType, ToneMapping, WebGLRenderer } from "three"; | ||
export default class DIVERenderer extends WebGLRenderer { | ||
export class DIVERenderer extends WebGLRenderer { | ||
// basic functionality members | ||
@@ -56,2 +56,8 @@ private paused: boolean = false; | ||
// Stops renderings and disposes the renderer. | ||
public Dispose(): void { | ||
this.StopRenderer(); | ||
this.dispose(); | ||
} | ||
// Starts the renderer with the given scene and camera. | ||
@@ -58,0 +64,0 @@ public StartRenderer(scene: Scene, cam: Camera): void { |
@@ -20,2 +20,3 @@ import { Color } from 'three'; | ||
this.removeFromParent = jest.fn(); | ||
this.ComputeSceneBB = jest.fn(); | ||
return this; | ||
@@ -42,2 +43,7 @@ }); | ||
it('should ComputeSceneBB', () => { | ||
const scene = new DIVEScene(); | ||
expect(() => scene.ComputeSceneBB()).not.toThrow(); | ||
}); | ||
it('should add object', () => { | ||
@@ -44,0 +50,0 @@ const scene = new DIVEScene(); |
@@ -1,3 +0,4 @@ | ||
import { COMLight, COMModel, COMPov } from '../../../com'; | ||
import DIVERoot from '../Root'; | ||
import { type Vector3, type Object3D } from 'three'; | ||
import { type COMLight, type COMModel, type COMPov } from '../../../com'; | ||
@@ -12,2 +13,58 @@ const mock_UpdateLight = jest.fn(); | ||
jest.mock('three', () => { | ||
return { | ||
Box3: jest.fn(() => { | ||
return { | ||
expandByObject: jest.fn(), | ||
setFromObject: jest.fn(), | ||
applyMatrix4: jest.fn(), | ||
union: jest.fn(), | ||
isEmpty: jest.fn(), | ||
getCenter: jest.fn(), | ||
getSize: jest.fn(), | ||
getBoundingSphere: jest.fn(), | ||
}; | ||
}), | ||
Object3D: jest.fn(function () { | ||
this.clear = jest.fn(); | ||
this.color = {}; | ||
this.intensity = 0; | ||
this.layers = { | ||
mask: 0, | ||
}; | ||
this.shadow = { | ||
radius: 0, | ||
mapSize: { width: 0, height: 0 }, | ||
bias: 0, | ||
camera: { | ||
near: 0, | ||
far: 0, | ||
fov: 0, | ||
}, | ||
} | ||
this.add = jest.fn(); | ||
this.userData = {}; | ||
this.rotation = { | ||
x: 0, | ||
y: 0, | ||
z: 0, | ||
setFromVector3: jest.fn(), | ||
}; | ||
this.scale = { | ||
x: 1, | ||
y: 1, | ||
z: 1, | ||
set: jest.fn(), | ||
}; | ||
this.localToWorld = (vec3: Vector3) => { | ||
return vec3; | ||
}; | ||
this.traverse = jest.fn((callback) => { | ||
callback(this.children[0]) | ||
}); | ||
return this; | ||
}), | ||
} | ||
}); | ||
jest.mock('../../../primitive/floor/Floor', () => { | ||
@@ -56,2 +113,5 @@ return jest.fn(function () { | ||
this.removeFromParent = jest.fn(); | ||
this.traverse = jest.fn((callback: (object: Object3D) => void) => { | ||
callback(this); | ||
}); | ||
return this; | ||
@@ -69,3 +129,3 @@ }); | ||
expect(root).toBeDefined(); | ||
expect(root.children).toHaveLength(4); | ||
expect(root.add).toHaveBeenCalledTimes(4); | ||
}); | ||
@@ -83,2 +143,8 @@ | ||
it('should ComputeSceneBB', () => { | ||
const root = new DIVERoot(); | ||
const bb = root.ComputeSceneBB(); | ||
expect(bb).toBeDefined(); | ||
}); | ||
it('should add object', () => { | ||
@@ -85,0 +151,0 @@ const root = new DIVERoot(); |
@@ -1,2 +0,2 @@ | ||
import { Object3D } from "three"; | ||
import { Box3, Object3D } from "three"; | ||
import DIVELightRoot from "./lightroot/LightRoot.ts"; | ||
@@ -42,2 +42,12 @@ import DIVEModelRoot from "./modelroot/ModelRoot.ts"; | ||
public ComputeSceneBB(): Box3 { | ||
const bb = new Box3(); | ||
this.modelRoot.traverse((object: Object3D) => { | ||
if ('isObject3D' in object) { | ||
bb.expandByObject(object); | ||
} | ||
}); | ||
return bb; | ||
} | ||
public GetSceneObject(object: Partial<COMEntity>): Object3D | undefined { | ||
@@ -44,0 +54,0 @@ switch (object.entityType) { |
@@ -1,3 +0,3 @@ | ||
import { Color, ColorRepresentation, Object3D, Scene } from 'three'; | ||
import { COMModel, COMEntity } from '../com/types'; | ||
import { Color, Scene, type Box3, type ColorRepresentation, type Object3D } from 'three'; | ||
import { type COMModel, type COMEntity } from '../com/types'; | ||
import DIVERoot from './root/Root'; | ||
@@ -22,2 +22,4 @@ | ||
this.background = new Color(0xffffff); | ||
this.root = new DIVERoot(); | ||
@@ -31,2 +33,6 @@ this.add(this.root); | ||
public ComputeSceneBB(): Box3 { | ||
return this.Root.ComputeSceneBB(); | ||
} | ||
public GetSceneObject(object: Partial<COMEntity>): Object3D | undefined { | ||
@@ -33,0 +39,0 @@ return this.Root.GetSceneObject(object); |
import { DIVEBaseTool } from '../BaseTool'; | ||
import type DIVEOrbitControls from '../../controls/OrbitControls'; | ||
import type DIVEScene from '../../scene/Scene'; | ||
import { type Object3D, type Vector3 } from 'three'; | ||
import DIVEOrbitControls from '../../controls/OrbitControls'; | ||
import DIVEScene from '../../scene/Scene'; | ||
import DIVEBaseTool from '../BaseTool'; | ||
import DIVEToolbox from '../Toolbox'; | ||
import { type DIVEHoverable } from '../../interface/Hoverable'; | ||
import { DIVEDraggable } from '../../interface/Draggable'; | ||
import { type DIVEDraggable } from '../../interface/Draggable'; | ||
@@ -50,6 +49,16 @@ /** | ||
it('should instantiate', () => { | ||
const toolBox = new abstractWrapper(mockScene, mockController); | ||
expect(toolBox).toBeDefined(); | ||
const baseTool = new abstractWrapper(mockScene, mockController); | ||
expect(baseTool).toBeDefined(); | ||
}); | ||
it('should Activate', () => { | ||
const baseTool = new abstractWrapper(mockScene, mockController); | ||
expect(() => baseTool.Activate()).not.toThrow(); | ||
}); | ||
it('should Deactivate', () => { | ||
const baseTool = new abstractWrapper(mockScene, mockController); | ||
expect(() => baseTool.Deactivate()).not.toThrow(); | ||
}); | ||
it('should raycast', () => { | ||
@@ -67,4 +76,21 @@ const toolBox = new abstractWrapper(mockScene, mockController); | ||
toolBox['_pointerPrimaryDown'] = false; | ||
toolBox['_pointerMiddleDown'] = false; | ||
toolBox['_pointerSecondaryDown'] = false; | ||
expect(toolBox['_pointerAnyDown']).toBe(false); | ||
toolBox['_pointerPrimaryDown'] = true; | ||
toolBox['_pointerMiddleDown'] = false; | ||
toolBox['_pointerSecondaryDown'] = false; | ||
expect(toolBox['_pointerAnyDown']).toBe(true); | ||
toolBox['_pointerPrimaryDown'] = false; | ||
toolBox['_pointerMiddleDown'] = true; | ||
toolBox['_pointerSecondaryDown'] = false; | ||
expect(toolBox['_pointerAnyDown']).toBe(true); | ||
toolBox['_pointerPrimaryDown'] = false; | ||
toolBox['_pointerMiddleDown'] = false; | ||
toolBox['_pointerSecondaryDown'] = true; | ||
expect(toolBox['_pointerAnyDown']).toBe(true); | ||
}); | ||
@@ -393,2 +419,7 @@ | ||
it('should execute onCLick correctly', () => { | ||
const toolBox = new abstractWrapper(mockScene, mockController); | ||
expect(() => toolBox.onClick({} as PointerEvent)).not.toThrow(); | ||
}); | ||
it('should execute onDragEnd correctly', () => { | ||
@@ -398,2 +429,7 @@ const toolBox = new abstractWrapper(mockScene, mockController); | ||
}); | ||
it('should execute onWheel correctly', () => { | ||
const toolBox = new abstractWrapper(mockScene, mockController); | ||
expect(() => toolBox.onWheel({} as WheelEvent)).not.toThrow(); | ||
}); | ||
}); |
@@ -1,4 +0,4 @@ | ||
import DIVEOrbitControls from '../../controls/OrbitControls'; | ||
import DIVEScene from '../../scene/Scene'; | ||
import DIVEToolbox from '../Toolbox'; | ||
import DIVEToolbox, { type ToolType } from '../Toolbox'; | ||
import type DIVEOrbitControls from '../../controls/OrbitControls'; | ||
import type DIVEScene from '../../scene/Scene'; | ||
@@ -24,23 +24,16 @@ /** | ||
const mock_Activate = jest.fn(); | ||
const mock_Deactivate = jest.fn(); | ||
const mock_onPointerDown = jest.fn(); | ||
const mock_onPointerMove = jest.fn(); | ||
const mock_onPointerUp = jest.fn(); | ||
const mock_onWheel = jest.fn(); | ||
const mock_SetGizmoMode = jest.fn(); | ||
const mock_SetGizmoVisibility = jest.fn(); | ||
jest.mock('../select/SelectTool.ts', () => { | ||
return jest.fn(function () { | ||
this.Activate = mock_Activate; | ||
this.Deactivate = mock_Deactivate; | ||
this.onPointerDown = mock_onPointerDown; | ||
this.onPointerMove = mock_onPointerMove; | ||
this.onPointerUp = mock_onPointerUp; | ||
this.onWheel = mock_onWheel; | ||
this.SetGizmoMode = mock_SetGizmoMode; | ||
this.SetGizmoVisibility = mock_SetGizmoVisibility; | ||
return this; | ||
}); | ||
return { | ||
DIVESelectTool: jest.fn(function () { | ||
this.Activate = jest.fn(); | ||
this.Deactivate = jest.fn(); | ||
this.onPointerDown = jest.fn(); | ||
this.onPointerMove = jest.fn(); | ||
this.onPointerUp = jest.fn(); | ||
this.onWheel = jest.fn(); | ||
this.SetGizmoMode = jest.fn(); | ||
this.SetGizmoVisibility = jest.fn(); | ||
return this; | ||
}) | ||
} | ||
}); | ||
@@ -62,4 +55,2 @@ | ||
expect(toolBox).toBeDefined(); | ||
expect(mock_Activate).toHaveBeenCalledTimes(1); | ||
expect(mock_addEventListener).toHaveBeenCalled(); | ||
}); | ||
@@ -69,3 +60,3 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.dispose(); | ||
toolBox.Dispose(); | ||
expect(mock_removeEventListener).toHaveBeenCalled(); | ||
@@ -76,12 +67,14 @@ }); | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
expect(() => toolBox.UseTool('not a real tool')).toThrow(); | ||
expect(mock_Deactivate).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.UseTool('not a real tool' as unknown as ToolType)).toThrow(); | ||
}); | ||
it('should use no tool', () => { | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
expect(() => toolBox.UseTool('select')).not.toThrow(); | ||
expect(() => toolBox.UseTool('none')).not.toThrow(); | ||
}); | ||
it('should use select tool', () => { | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
expect(mock_Activate).toHaveBeenCalledTimes(1); | ||
toolBox.UseTool(DIVEToolbox.DefaultTool); | ||
expect(mock_Deactivate).toHaveBeenCalledTimes(1); | ||
expect(mock_Activate).toHaveBeenCalledTimes(2); | ||
expect(() => toolBox.UseTool(DIVEToolbox.DefaultTool)).not.toThrow(); | ||
}); | ||
@@ -91,4 +84,5 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent); | ||
expect(mock_onPointerDown).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent)).not.toThrow(); | ||
expect(() => toolBox.UseTool('select')).not.toThrow(); | ||
expect(() => toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent)).not.toThrow(); | ||
}); | ||
@@ -98,4 +92,5 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent); | ||
expect(mock_onPointerMove).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent)).not.toThrow(); | ||
expect(() => toolBox.UseTool('select')).not.toThrow(); | ||
expect(() => toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent)).not.toThrow(); | ||
}); | ||
@@ -105,4 +100,5 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent); | ||
expect(mock_onPointerUp).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent)).not.toThrow(); | ||
expect(() => toolBox.UseTool('select')).not.toThrow(); | ||
expect(() => toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent)).not.toThrow(); | ||
}); | ||
@@ -112,4 +108,5 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.onWheel({ type: 'wheel' } as WheelEvent); | ||
expect(mock_onWheel).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.onWheel({ type: 'wheel' } as WheelEvent)).not.toThrow(); | ||
expect(() => toolBox.UseTool('select')).not.toThrow(); | ||
expect(() => toolBox.onWheel({ type: 'wheel' } as WheelEvent)).not.toThrow(); | ||
}); | ||
@@ -124,4 +121,3 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.SetGizmoMode('translate'); | ||
expect(mock_SetGizmoMode).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.SetGizmoMode('translate')).not.toThrow(); | ||
}); | ||
@@ -131,5 +127,4 @@ | ||
const toolBox = new DIVEToolbox({} as DIVEScene, mockController); | ||
toolBox.SetGizmoVisibility(true); | ||
expect(mock_SetGizmoVisibility).toHaveBeenCalledTimes(1); | ||
expect(() => toolBox.SetGizmoVisibility(true)).not.toThrow(); | ||
}); | ||
}); |
@@ -16,3 +16,3 @@ import { Intersection, Object3D, Raycaster, Vector2, Vector3 } from "three"; | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
export default abstract class DIVEBaseTool { | ||
export abstract class DIVEBaseTool { | ||
readonly POINTER_DRAG_THRESHOLD: number = 0.001; | ||
@@ -19,0 +19,0 @@ |
@@ -1,8 +0,9 @@ | ||
import DIVESelectTool from '../SelectTool'; | ||
import { DIVESelectTool, isSelectTool } from '../SelectTool'; | ||
import DIVEScene from '../../../scene/Scene'; | ||
import DIVEOrbitControls from '../../../controls/OrbitControls'; | ||
import DIVEPerspectiveCamera from '../../../camera/PerspectiveCamera'; | ||
import DIVERenderer, { DIVERendererDefaultSettings } from '../../../renderer/Renderer'; | ||
import { DIVESelectable, isSelectable } from '../../../interface/Selectable'; | ||
import { DIVERenderer, DIVERendererDefaultSettings } from '../../../renderer/Renderer'; | ||
import { DIVESelectable } from '../../../interface/Selectable'; | ||
import type DIVEPerspectiveCamera from '../../../camera/PerspectiveCamera'; | ||
import { type Object3D } from 'three'; | ||
import { type DIVEBaseTool } from '../../BaseTool'; | ||
@@ -112,3 +113,6 @@ jest.mock('../../../renderer/Renderer', () => { | ||
const mockCamera: DIVEPerspectiveCamera = {} as DIVEPerspectiveCamera; | ||
const mockRenderer: DIVERenderer = new DIVERenderer(DIVERendererDefaultSettings); | ||
const mockRenderer = { | ||
render: jest.fn(), | ||
OnResize: jest.fn(), | ||
} as unknown as DIVERenderer; | ||
const mockScene: DIVEScene = new DIVEScene(); | ||
@@ -118,2 +122,7 @@ const mockController: DIVEOrbitControls = new DIVEOrbitControls(mockCamera, mockRenderer); | ||
describe('dive/toolbox/select/DIVESelectTool', () => { | ||
it('should test if it is SelectTool', () => { | ||
const selectTool = { isSelectTool: true } as unknown as DIVEBaseTool; | ||
expect(isSelectTool(selectTool)).toBeDefined(); | ||
}); | ||
it('should instantiate', () => { | ||
@@ -137,9 +146,9 @@ const selectTool = new DIVESelectTool(mockScene, mockController); | ||
mock_intersectObjects.mockReturnValueOnce( | ||
[{ | ||
object: { | ||
uuid: 'test', | ||
visible: true, | ||
parent: { name: 'this is the test scene root!!!', parent: null } | ||
} | ||
}] | ||
[{ | ||
object: { | ||
uuid: 'test', | ||
visible: true, | ||
parent: { name: 'this is the test scene root!!!', parent: null } | ||
} | ||
}] | ||
); | ||
@@ -146,0 +155,0 @@ const selectTool = new DIVESelectTool(mockScene, mockController); |
@@ -1,8 +0,13 @@ | ||
import { Object3D } from "three"; | ||
import { DIVESelectable, isSelectable } from "../../interface/Selectable.ts"; | ||
import { type Object3D } from "three"; | ||
import DIVEScene from "../../scene/Scene.ts"; | ||
import { DIVEMoveable } from "../../interface/Moveable.ts"; | ||
import DIVEOrbitControls from "../../controls/OrbitControls.ts"; | ||
import DIVETransformTool from "../transform/TransformTool.ts"; | ||
import type DIVEOrbitControls from "../../controls/OrbitControls.ts"; | ||
import { type DIVESelectable, findSelectableInterface } from "../../interface/Selectable.ts"; | ||
import { type DIVEMoveable } from "../../interface/Moveable.ts"; | ||
import { type DIVEBaseTool } from "../BaseTool.ts"; | ||
export const isSelectTool = (tool: DIVEBaseTool): tool is DIVESelectTool => { | ||
return (tool as DIVESelectTool).isSelectTool !== undefined; | ||
} | ||
export interface DIVEObjectEventMap { | ||
@@ -20,3 +25,4 @@ select: object | ||
export default class DIVESelectTool extends DIVETransformTool { | ||
export class DIVESelectTool extends DIVETransformTool { | ||
readonly isSelectTool: boolean = true; | ||
@@ -43,6 +49,2 @@ constructor(scene: DIVEScene, controller: DIVEOrbitControls) { | ||
public DetachGizmo(): void { | ||
this._gizmo.detach(); | ||
} | ||
public AttachGizmo(selectable: DIVESelectable): void { | ||
@@ -56,7 +58,11 @@ if ('isMoveable' in selectable) { | ||
public DetachGizmo(): void { | ||
this._gizmo.detach(); | ||
} | ||
public onClick(e: PointerEvent): void { | ||
super.onClick(e); | ||
const first = this._raycaster.intersectObjects(this._scene.Root.children, true).filter((intersect) => intersect.object.visible )[0]; | ||
const selectable = this.findSelectableInterface(first?.object); | ||
const first = this._raycaster.intersectObjects(this._scene.Root.children, true).filter((intersect) => intersect.object.visible)[0]; | ||
const selectable = findSelectableInterface(first?.object); | ||
@@ -83,19 +89,2 @@ // if nothing is hit | ||
} | ||
private findSelectableInterface(child: Object3D): (Object3D & DIVESelectable) | undefined { | ||
if (child === undefined) return undefined; | ||
if (child.parent === null) { | ||
// in this case it is the scene itself | ||
return undefined; | ||
} | ||
if (isSelectable(child)) { | ||
// in this case it is the Selectable | ||
return child; | ||
} | ||
// search recursively in parent | ||
return this.findSelectableInterface(child.parent); | ||
} | ||
} |
@@ -1,6 +0,8 @@ | ||
import DIVEOrbitControls from "../controls/OrbitControls.ts"; | ||
import DIVEScene from "../scene/Scene.ts"; | ||
import DIVEBaseTool from "./BaseTool.ts"; | ||
import DIVESelectTool from "./select/SelectTool.ts"; | ||
import type DIVEOrbitControls from "../controls/OrbitControls.ts"; | ||
import type DIVEScene from "../scene/Scene.ts"; | ||
import { type DIVEBaseTool } from "./BaseTool.ts"; | ||
import { type DIVESelectTool } from "./select/SelectTool.ts"; | ||
export type ToolType = 'select' | 'none'; | ||
/** | ||
@@ -15,49 +17,49 @@ * A Toolbox to activate and deactivate tools to use with the pointer. | ||
private activeTool: DIVEBaseTool; | ||
private _scene: DIVEScene; | ||
private _controller: DIVEOrbitControls; | ||
private selectTool: DIVESelectTool; | ||
private _activeTool: DIVEBaseTool | null; | ||
private removeListenersCallback: () => void = () => { }; | ||
private _selectTool: DIVESelectTool | null; | ||
public get selectTool(): DIVESelectTool { | ||
if (!this._selectTool) { | ||
const DIVESelectTool = require('./select/SelectTool.ts').DIVESelectTool as typeof import('./select/SelectTool.ts').DIVESelectTool; | ||
this._selectTool = new DIVESelectTool(this._scene, this._controller); | ||
} | ||
return this._selectTool; | ||
} | ||
constructor(scene: DIVEScene, controller: DIVEOrbitControls) { | ||
this.selectTool = new DIVESelectTool(scene, controller); | ||
this._scene = scene; | ||
this._controller = controller; | ||
const pointerMove = this.onPointerMove.bind(this); | ||
const pointerDown = this.onPointerDown.bind(this); | ||
const pointerUp = this.onPointerUp.bind(this); | ||
const wheel = this.onWheel.bind(this); | ||
// toolset | ||
this._selectTool = null; | ||
controller.domElement.addEventListener('pointermove', pointerMove); | ||
controller.domElement.addEventListener('pointerdown', pointerDown); | ||
controller.domElement.addEventListener('pointerup', pointerUp); | ||
controller.domElement.addEventListener('wheel', wheel); | ||
this.removeListenersCallback = () => { | ||
controller.domElement.removeEventListener('pointermove', pointerMove); | ||
controller.domElement.removeEventListener('pointerdown', pointerDown); | ||
controller.domElement.removeEventListener('pointerup', pointerUp); | ||
controller.domElement.removeEventListener('wheel', wheel); | ||
}; | ||
// default tool | ||
this.activeTool = this.selectTool; | ||
this.activeTool.Activate(); | ||
this._activeTool = null; | ||
} | ||
public dispose(): void { | ||
this.removeListenersCallback(); | ||
public Dispose(): void { | ||
this.removeEventListeners(); | ||
} | ||
public GetActiveTool(): DIVEBaseTool { | ||
return this.activeTool; | ||
public GetActiveTool(): DIVEBaseTool | null { | ||
return this._activeTool; | ||
} | ||
public UseTool(tool: string): void { | ||
this.activeTool.Deactivate(); | ||
public UseTool(tool: ToolType): void { | ||
this._activeTool?.Deactivate(); | ||
switch (tool) { | ||
case "select": { | ||
this.addEventListeners(); | ||
this.selectTool.Activate(); | ||
this.activeTool = this.selectTool; | ||
this._activeTool = this.selectTool; | ||
break; | ||
} | ||
case "none": { | ||
this.removeEventListeners(); | ||
this._activeTool = null; | ||
break; | ||
} | ||
default: { | ||
@@ -78,16 +80,30 @@ throw new Error(`ToolBox.UseTool: Unknown tool: ${tool}`); | ||
public onPointerMove(e: PointerEvent): void { | ||
this.activeTool.onPointerMove(e); | ||
this._activeTool?.onPointerMove(e); | ||
} | ||
public onPointerDown(e: PointerEvent): void { | ||
this.activeTool.onPointerDown(e); | ||
this._activeTool?.onPointerDown(e); | ||
} | ||
public onPointerUp(e: PointerEvent): void { | ||
this.activeTool.onPointerUp(e); | ||
this._activeTool?.onPointerUp(e); | ||
} | ||
public onWheel(e: WheelEvent): void { | ||
this.activeTool.onWheel(e); | ||
this._activeTool?.onWheel(e); | ||
} | ||
private addEventListeners(): void { | ||
this._controller.domElement.addEventListener('pointermove', (e) => this.onPointerMove(e)); | ||
this._controller.domElement.addEventListener('pointerdown', (e) => this.onPointerDown(e)); | ||
this._controller.domElement.addEventListener('pointerup', (e) => this.onPointerUp(e)); | ||
this._controller.domElement.addEventListener('wheel', (e) => this.onWheel(e)); | ||
} | ||
private removeEventListeners(): void { | ||
this._controller.domElement.removeEventListener('pointermove', (e) => this.onPointerMove(e)); | ||
this._controller.domElement.removeEventListener('pointerdown', (e) => this.onPointerDown(e)); | ||
this._controller.domElement.removeEventListener('pointerup', (e) => this.onPointerUp(e)); | ||
this._controller.domElement.removeEventListener('wheel', (e) => this.onWheel(e)); | ||
} | ||
} |
@@ -1,8 +0,7 @@ | ||
import DIVETransformTool from '../TransformTool'; | ||
import DIVETransformTool, { isTransformTool } from '../TransformTool'; | ||
import DIVEScene from '../../../scene/Scene'; | ||
import DIVEOrbitControls from '../../../controls/OrbitControls'; | ||
import DIVEPerspectiveCamera from '../../../camera/PerspectiveCamera'; | ||
import DIVERenderer, { DIVERendererDefaultSettings } from '../../../renderer/Renderer'; | ||
import { type Object3D } from 'three'; | ||
import { type DIVEMoveable } from '../../../interface/Moveable'; | ||
import { DIVERenderer } from '../../../renderer/Renderer'; | ||
import { type DIVEBaseTool } from '../../BaseTool'; | ||
@@ -115,3 +114,16 @@ jest.mock('../../../renderer/Renderer', () => { | ||
const mockCamera: DIVEPerspectiveCamera = {} as DIVEPerspectiveCamera; | ||
const mockRenderer: DIVERenderer = new DIVERenderer(DIVERendererDefaultSettings); | ||
const mockRenderer = { | ||
render: jest.fn(), | ||
OnResize: jest.fn(), | ||
getViewport: jest.fn(), | ||
setViewport: jest.fn(), | ||
AddPreRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
AddPostRenderCallback: jest.fn((callback) => { | ||
callback(); | ||
}), | ||
RemovePreRenderCallback: jest.fn(), | ||
RemovePostRenderCallback: jest.fn(), | ||
} as unknown as DIVERenderer; | ||
const mockScene: DIVEScene = new DIVEScene(); | ||
@@ -121,2 +133,7 @@ const mockController: DIVEOrbitControls = new DIVEOrbitControls(mockCamera, mockRenderer); | ||
describe('dive/toolbox/select/DIVETransformTool', () => { | ||
it('should test if it is SelectTool', () => { | ||
const selectTool = { isTransformTool: true } as unknown as DIVEBaseTool; | ||
expect(isTransformTool(selectTool)).toBeDefined(); | ||
}); | ||
it('should instantiate', () => { | ||
@@ -123,0 +140,0 @@ const transformTool = new DIVETransformTool(mockScene, mockController); |
@@ -1,2 +0,2 @@ | ||
import DIVEBaseTool from "../BaseTool.ts"; | ||
import { DIVEBaseTool } from "../BaseTool.ts"; | ||
import DIVEScene from "../../scene/Scene.ts"; | ||
@@ -7,2 +7,6 @@ import DIVEOrbitControls from "../../controls/OrbitControls.ts"; | ||
export const isTransformTool = (tool: DIVEBaseTool): tool is DIVETransformTool => { | ||
return (tool as DIVETransformTool).isTransformTool !== undefined; | ||
} | ||
export interface DIVEObjectEventMap { | ||
@@ -21,2 +25,4 @@ select: object | ||
export default class DIVETransformTool extends DIVEBaseTool { | ||
readonly isTransformTool: boolean = true; | ||
protected _gizmo: TransformControls; | ||
@@ -23,0 +29,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
749345
121
11898
200