From 3c4e3ed907e9cf8b526829c5324504afbcd26147 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Wed, 4 Sep 2024 16:23:47 +0200 Subject: [PATCH 1/5] add model origin point prototype [WIP] implement separate Origin helper to show transform position. Make brackets children of their objects (needs cleanup and verification) begin implementation of scene axes helper remove Origin helper, merge into Bracket --- .../source/components/CPickSelection.ts | 68 ++++++++++--------- libs/ff-three/source/Bracket.ts | 50 +++++++++++--- 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/libs/ff-scene/source/components/CPickSelection.ts b/libs/ff-scene/source/components/CPickSelection.ts index 8378ab15..8477c6a1 100644 --- a/libs/ff-scene/source/components/CPickSelection.ts +++ b/libs/ff-scene/source/components/CPickSelection.ts @@ -32,6 +32,7 @@ const helpers = [ const _inputs = { viewportPicking: types.Boolean("Viewport.Picking", true), viewportBrackets: types.Boolean("Viewport.Brackets", true), + viewportAxes: types.Boolean("Viewport.Axes", false), }; export default class CPickSelection extends CSelection @@ -40,7 +41,7 @@ export default class CPickSelection extends CSelection ins = this.addInputs(_inputs); - private _brackets = new Map(); + private _brackets = new Mapvoid}>(); private _sceneTracker: ComponentTracker = null; @@ -50,11 +51,6 @@ export default class CPickSelection extends CSelection this.system.on("pointer-up", this.onPointerUp, this); - this._sceneTracker = new ComponentTracker(this.system.components, CScene, component => { - component.on("after-render", this.onSceneAfterRender, this); - }, component => { - component.off("after-render", this.onSceneAfterRender, this); - }); } dispose() @@ -69,6 +65,14 @@ export default class CPickSelection extends CSelection update() { + if(this.ins.viewportBrackets.changed){ + for(let bracket of this._brackets.values()){ + bracket.visible = this.ins.viewportBrackets.value; + } + } + if(this.ins.viewportAxes.changed){ + //FIXME : add axes helper to scene + } return true; } @@ -87,7 +91,7 @@ export default class CPickSelection extends CSelection super.onSelectComponent(component, selected); if (component instanceof CObject3D || component instanceof CTransform) { - this.updateBracket(component, selected); + this.updateBracket(component, selected); } } @@ -120,24 +124,6 @@ export default class CPickSelection extends CSelection } } - protected onSceneAfterRender(event: ISceneAfterRenderEvent) - { - if (!this.ins.viewportBrackets.value) { - return; - } - - const renderer = event.context.renderer; - const camera = event.context.camera; - - for (let bracket of this._brackets.values()) { - if(helpers.some(([HelperCl])=> bracket instanceof HelperCl)){ - bracket.update(); - /** @bug PointLightHelper doesn't call it internally in its update() method. */ - bracket.updateWorldMatrix( true, false ); - } - renderer.render(bracket as any, camera); - } - } protected updateBracket(component: CTransform | CObject3D, selected: boolean) { @@ -145,8 +131,9 @@ export default class CPickSelection extends CSelection return; } + const object3D = component.object3D; + const transform = component.transform; if (selected) { - const object3D = component.object3D; if (object3D) { let bracket; for(let [HelperCl, Cl] of helpers){ @@ -157,17 +144,34 @@ export default class CPickSelection extends CSelection } } if(!bracket){ - bracket = new Bracket(component.object3D); - + bracket = new Bracket(object3D); } + object3D.add(bracket); this._brackets.set(component, bracket); } + + if(transform){ + let o = new Bracket(transform.object3D, {axes: true}); + this._brackets.set(transform, o); + transform.object3D.add(o); + }else{ + console.warn("Component has no transform"); + } } else { - const bracket = this._brackets.get(component); - if (bracket) { - this._brackets.delete(component); - bracket.dispose(); + if(object3D){ + const bracket = this._brackets.get(component); + if (bracket) { + this._brackets.delete(component); + bracket.dispose(); + } + } + if(transform){ + const bracket = this._brackets.get(transform); + if (bracket) { + this._brackets.delete(transform); + bracket.dispose(); + } } } diff --git a/libs/ff-three/source/Bracket.ts b/libs/ff-three/source/Bracket.ts index 735897ed..a0736aef 100644 --- a/libs/ff-three/source/Bracket.ts +++ b/libs/ff-three/source/Bracket.ts @@ -15,6 +15,8 @@ import { Matrix4, Box3, Color, + Points, + PointsMaterial, } from "three"; import { computeLocalBoundingBox } from "./helpers"; @@ -30,6 +32,7 @@ export interface IBracketProps color?: Color; /** Length of the bracket lines relative to the size of the object. Default is 0.25. */ length?: number; + axes?:boolean; } /** @@ -39,7 +42,8 @@ export default class Bracket extends LineSegments { static readonly defaultProps = { color: new Color("#ffd633"), - length: 0.25 + length: 0.25, + axes: false, }; constructor(target: Object3D, props?: IBracketProps) @@ -55,9 +59,22 @@ export default class Bracket extends LineSegments const min = [ box.min.x, box.min.y, box.min.z ]; const max = [ box.max.x, box.max.y, box.max.z ]; const size = [ (max[0] - min[0]) * length, (max[1] - min[1]) * length, (max[2] - min[2]) * length ]; - let vertices; + let vertices :number[]; + let colors :number[]; + let has_volume = isFinite(size[0]) && isFinite(size[1]) && isFinite(size[2]) + if ( has_volume && props.axes){ + vertices = [ + 0, 0, 0, length, 0, 0, + 0, 0, 0, 0, length, 0, + 0, 0, 0, 0, 0, length, + ]; + colors = [ + 1, 0, 0, 1, 0.6, 0, + 0, 1, 0, 0.6, 1, 0, + 0, 0, 1, 0, 0.6, 1 - if (isFinite(size[0]) && isFinite(size[1]) && isFinite(size[2])) { + ] + }else if(has_volume) { vertices = [ min[0], min[1], min[2], min[0] + size[0], min[1], min[2], min[0], min[1], min[2], min[0], min[1] + size[1], min[2], @@ -91,8 +108,7 @@ export default class Bracket extends LineSegments max[0], max[1], max[2], max[0], max[1] - size[1], max[2], max[0], max[1], max[2], max[0], max[1], max[2] - size[2], ]; - } - else { + }else { vertices = [ -1, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, @@ -102,20 +118,32 @@ export default class Bracket extends LineSegments const geometry = new BufferGeometry(); geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3)); - + if(colors) geometry.setAttribute("color", new Float32BufferAttribute(colors, 3)); const material = new LineBasicMaterial({ color: props.color, + vertexColors: !!colors, + depthTest: false }); super(geometry, material); - this.renderOrder = 1; + // Origin, as in "geometry's internal (0,0,0) point". We generally don't need this? + // const originGeometry = new BufferGeometry(); + // originGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0], 3)); + // const originMaterial = new PointsMaterial({ + // size: 3, + // color: props.color, + // sizeAttenuation: false, + // depthTest: false + // }); + // const originPoint = new Points(originGeometry, originMaterial); + // originPoint.renderOrder = 2; + // this.add(originPoint); - this.onBeforeRender = () => { - target.updateMatrixWorld(false); - this.matrixWorld.copy(target.matrixWorld); - } + + + this.renderOrder = 1; } dispose() From 7b3328de3aff506f8755e87fc47d7dada40b4f85 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Wed, 4 Sep 2024 17:45:54 +0200 Subject: [PATCH 2/5] handle selection from the Node list sidebar (break lights helpers) --- .../source/components/CPickSelection.ts | 26 +++++++++++-------- libs/ff-three/source/Bracket.ts | 5 ++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/libs/ff-scene/source/components/CPickSelection.ts b/libs/ff-scene/source/components/CPickSelection.ts index 8477c6a1..d755b0ad 100644 --- a/libs/ff-scene/source/components/CPickSelection.ts +++ b/libs/ff-scene/source/components/CPickSelection.ts @@ -17,16 +17,16 @@ import { IPointerEvent } from "../RenderView"; import CObject3D from "./CObject3D"; import CTransform from "./CTransform"; import CScene, { ISceneAfterRenderEvent } from "./CScene"; -import { DirectionalLight, DirectionalLightHelper, HemisphereLight, HemisphereLightHelper, Object3D, PointLight, PointLightHelper, RectAreaLight, SpotLight, SpotLightHelper } from "three"; +import { DirectionalLightHelper, HemisphereLightHelper, Object3D, PointLightHelper, SpotLightHelper } from "three"; //////////////////////////////////////////////////////////////////////////////// const helpers = [ - [DirectionalLightHelper, DirectionalLight], - [PointLightHelper, PointLight], - [SpotLightHelper, SpotLight], - [HemisphereLightHelper, HemisphereLight], - [PointLightHelper, RectAreaLight], + [DirectionalLightHelper, "DirectionalLight"], + [PointLightHelper, "PointLight"], + [SpotLightHelper, "SpotLight"], + [HemisphereLightHelper, "HemisphereLight"], + [PointLightHelper, "RectAreaLight"], ] as const; const _inputs = { @@ -80,7 +80,7 @@ export default class CPickSelection extends CSelection { super.onSelectNode(node, selected); - const transform = node.getComponent(CTransform, true); + const transform = node.getComponent(CObject3D, true); if (transform) { this.updateBracket(transform, selected); } @@ -135,14 +135,16 @@ export default class CPickSelection extends CSelection const transform = component.transform; if (selected) { if (object3D) { - let bracket; - for(let [HelperCl, Cl] of helpers){ - if(object3D.children[0] instanceof Cl){ - bracket = new HelperCl(object3D.children[0] as any, 1.0); + let bracket :Object3D & { dispose: () => void; }; + if((object3D as any).isLight){ + let HelperCl = helpers.find(([h,type])=>type === object3D.type)?.[0]; + if(HelperCl){ + bracket = new HelperCl(object3D as any, 1.0); /** @bug PointLightHelper doesn't call it internally in its update() method. */ bracket.updateWorldMatrix( true, false ); } } + if(!bracket){ bracket = new Bracket(object3D); } @@ -163,6 +165,7 @@ export default class CPickSelection extends CSelection const bracket = this._brackets.get(component); if (bracket) { this._brackets.delete(component); + bracket.removeFromParent(); bracket.dispose(); } } @@ -170,6 +173,7 @@ export default class CPickSelection extends CSelection const bracket = this._brackets.get(transform); if (bracket) { this._brackets.delete(transform); + bracket.removeFromParent(); bracket.dispose(); } } diff --git a/libs/ff-three/source/Bracket.ts b/libs/ff-three/source/Bracket.ts index a0736aef..d37437a4 100644 --- a/libs/ff-three/source/Bracket.ts +++ b/libs/ff-three/source/Bracket.ts @@ -17,6 +17,7 @@ import { Color, Points, PointsMaterial, + Material, } from "three"; import { computeLocalBoundingBox } from "./helpers"; @@ -148,11 +149,9 @@ export default class Bracket extends LineSegments dispose() { - if (this.parent) { - this.parent.remove(this); - } this.geometry.dispose(); + (this.material as Material).dispose(); } protected static expandBoundingBox(object: Object3D, root: Object3D, box: Box3) From 8748fb695d61b8a10cdb28bad91b51702ea17935 Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Fri, 18 Oct 2024 10:46:42 +0200 Subject: [PATCH 3/5] add global scene axes helpers in Pose task --- .../source/components/CPickSelection.ts | 39 ++++-- libs/ff-three/source/Axes.ts | 125 ++++++++++++++++++ libs/ff-three/source/Bracket.ts | 33 ++--- source/client/components/CVPoseTask.ts | 1 + source/client/components/CVTask.ts | 28 +++- 5 files changed, 186 insertions(+), 40 deletions(-) create mode 100644 libs/ff-three/source/Axes.ts diff --git a/libs/ff-scene/source/components/CPickSelection.ts b/libs/ff-scene/source/components/CPickSelection.ts index d755b0ad..11d7b29d 100644 --- a/libs/ff-scene/source/components/CPickSelection.ts +++ b/libs/ff-scene/source/components/CPickSelection.ts @@ -11,13 +11,14 @@ import Node from "@ff/graph/Node"; import CSelection from "@ff/graph/components/CSelection"; import Bracket from "@ff/three/Bracket"; +import Axes from "@ff/three/Axes"; import { IPointerEvent } from "../RenderView"; import CObject3D from "./CObject3D"; import CTransform from "./CTransform"; import CScene, { ISceneAfterRenderEvent } from "./CScene"; -import { DirectionalLightHelper, HemisphereLightHelper, Object3D, PointLightHelper, SpotLightHelper } from "three"; +import { Box3, Color, DirectionalLightHelper, HemisphereLightHelper, Object3D, PointLightHelper, SpotLightHelper, Vector3 } from "three"; //////////////////////////////////////////////////////////////////////////////// @@ -35,14 +36,15 @@ const _inputs = { viewportAxes: types.Boolean("Viewport.Axes", false), }; +type Disposable = Object3D & {dispose: ()=>void}; export default class CPickSelection extends CSelection { static readonly typeName: string = "CPickSelection"; ins = this.addInputs(_inputs); - private _brackets = new Mapvoid}>(); - private _sceneTracker: ComponentTracker = null; + private _brackets = new Map(); + private _axes :Disposable; create() @@ -50,15 +52,12 @@ export default class CPickSelection extends CSelection super.create(); this.system.on("pointer-up", this.onPointerUp, this); - } dispose() { - this._sceneTracker.dispose(); this.system.off("pointer-up", this.onPointerUp, this); - this._sceneTracker.dispose(); super.dispose(); } @@ -72,6 +71,28 @@ export default class CPickSelection extends CSelection } if(this.ins.viewportAxes.changed){ //FIXME : add axes helper to scene + if(this._axes){ + console.debug("Remove scene axes"); + this._axes.removeFromParent(); + this._axes.dispose(); + } + if(this.ins.viewportAxes.value){ + console.debug("Create scene axes : ", this.ins.viewportAxes.value); + //Length should be half CVGrid's size. + const scene = this.system.getMainComponent(CScene); + let bbox = new Box3(); + bbox.expandByObject(scene.scene); + let length = Math.max(bbox.max.x, bbox.max.y, bbox.max.z); + let f = 1; + + while (length / f > 5) { + f = f * 10; + } + + length = Math.ceil(length / f) * f/2; + this._axes = new Axes(scene.scene, {length: length, width: 3, colors: [new Color(0x9a3c4a), new Color(0x628928), new Color(0x3d5e8b)], depthTest: true}); + scene.scene.add(this._axes); + } } return true; } @@ -130,12 +151,12 @@ export default class CPickSelection extends CSelection if (!component) { return; } - + if(!this.ins.viewportBrackets.value) return; //Don't create brackets to be hidden const object3D = component.object3D; const transform = component.transform; if (selected) { if (object3D) { - let bracket :Object3D & { dispose: () => void; }; + let bracket :Disposable; if((object3D as any).isLight){ let HelperCl = helpers.find(([h,type])=>type === object3D.type)?.[0]; if(HelperCl){ @@ -153,7 +174,7 @@ export default class CPickSelection extends CSelection } if(transform){ - let o = new Bracket(transform.object3D, {axes: true}); + let o = new Axes(transform.object3D); this._brackets.set(transform, o); transform.object3D.add(o); }else{ diff --git a/libs/ff-three/source/Axes.ts b/libs/ff-three/source/Axes.ts new file mode 100644 index 00000000..84b7fc6f --- /dev/null +++ b/libs/ff-three/source/Axes.ts @@ -0,0 +1,125 @@ +/** + * FF Typescript Foundation Library + * Copyright 2020 Ralph Wiedemeier, Frame Factory GmbH + * + * License: MIT + */ + +import { + Object3D, + Vector3, + Matrix4, + Box3, + Color, + Material, + BufferGeometry, + Float32BufferAttribute, + Points, + PointsMaterial, +} from "three"; + +import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2"; +import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry"; +import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; + +import { computeLocalBoundingBox } from "./helpers"; + +//////////////////////////////////////////////////////////////////////////////// + +export interface IAxesProps +{ + /** + * Color of the bracket lines. Default is to use color-coded xyz axes + * If four colors are provided, the first one will be used to show the origin point + */ + colors?: [Color, Color, Color]| [Color, Color, Color, Color]; + /** Length of the bracket lines relative to the size of the object. Default is 0.25. */ + length?: number; + width?:number; + depthTest?: boolean, +} + +/** +* Wireframe selection bracket. +*/ +export default class Axes extends LineSegments2 +{ + static readonly defaultProps = { + length: 0.25, + width: 2, + depthTest: false, + colors: [new Color(0xffd633), new Color(0xa63b4a), new Color(0x6fa21c), new Color(0x2f83e1)], + }; + + private originPoint?:Points; + + constructor(target: Object3D, props?: IAxesProps) + { + props = Object.assign({}, Axes.defaultProps, props); + + const box = new Box3(); + box.makeEmpty(); + + computeLocalBoundingBox(target, box); + + const length = props.length; + + const originColor :Color = props.colors.length === 4? props.colors[0]: null; + + let vertices :number[] = [ + 0, 0, 0, length, 0, 0, + 0, 0, 0, 0, length, 0, + 0, 0, 0, 0, 0, length, + ]; + + + let colors :number[] = []; + for(let color of props.colors.slice(-3)){ + let a = color.toArray(); + colors.push(...a, ...a); + } + + const geometry = new LineSegmentsGeometry(); + geometry.setPositions(vertices); + geometry.setColors(colors); + + const material = new LineMaterial({ + vertexColors: true, + toneMapped: false, + depthTest: props.depthTest, + worldUnits: false, //Disables size attenuation + linewidth: props.width, + }); + + super(geometry, material); + + if(originColor){ + const originGeometry = new BufferGeometry(); + originGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0], 3)); + const originMaterial = new PointsMaterial({ + size: props.width+1, + toneMapped: false, + color: originColor, + sizeAttenuation: false, + depthTest: props.depthTest + }); + this.originPoint = new Points(originGeometry, originMaterial); + this.originPoint.renderOrder = 2; + this.add(this.originPoint); + } + + this.renderOrder = 1; + } + + dispose() + { + if(this.originPoint){ + this.remove(this.originPoint); + this.originPoint.geometry.dispose(); + (this.originPoint.material as Material).dispose(); + } + + this.geometry.dispose(); + (this.material as Material).dispose(); + } +} \ No newline at end of file diff --git a/libs/ff-three/source/Bracket.ts b/libs/ff-three/source/Bracket.ts index d37437a4..207a9c2b 100644 --- a/libs/ff-three/source/Bracket.ts +++ b/libs/ff-three/source/Bracket.ts @@ -15,8 +15,6 @@ import { Matrix4, Box3, Color, - Points, - PointsMaterial, Material, } from "three"; @@ -65,16 +63,16 @@ export default class Bracket extends LineSegments let has_volume = isFinite(size[0]) && isFinite(size[1]) && isFinite(size[2]) if ( has_volume && props.axes){ vertices = [ - 0, 0, 0, length, 0, 0, - 0, 0, 0, 0, length, 0, - 0, 0, 0, 0, 0, length, + 0, 0, 0, length, 0, 0, 0, 0, 0, + 0, 0, 0, 0, length, 0, 0, 0, 0, + 0, 0, 0, 0, 0, length, 0, 0, 0, ]; colors = [ - 1, 0, 0, 1, 0.6, 0, - 0, 1, 0, 0.6, 1, 0, - 0, 0, 1, 0, 0.6, 1 + .65, .23, .29, .65, .23, .29, .65, .23, .29, + .43, .63, .11, .43, .63, .11, .43, .63, .11, + .25, .3, .88, .25, .3, .88, .25, .3, .88, - ] + ]; }else if(has_volume) { vertices = [ min[0], min[1], min[2], min[0] + size[0], min[1], min[2], @@ -123,27 +121,12 @@ export default class Bracket extends LineSegments const material = new LineBasicMaterial({ color: props.color, vertexColors: !!colors, - + toneMapped: false, depthTest: false }); super(geometry, material); - // Origin, as in "geometry's internal (0,0,0) point". We generally don't need this? - // const originGeometry = new BufferGeometry(); - // originGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0], 3)); - // const originMaterial = new PointsMaterial({ - // size: 3, - // color: props.color, - // sizeAttenuation: false, - // depthTest: false - // }); - // const originPoint = new Points(originGeometry, originMaterial); - // originPoint.renderOrder = 2; - // this.add(originPoint); - - - this.renderOrder = 1; } diff --git a/source/client/components/CVPoseTask.ts b/source/client/components/CVPoseTask.ts index 9d8b0dd1..c1c2ca55 100644 --- a/source/client/components/CVPoseTask.ts +++ b/source/client/components/CVPoseTask.ts @@ -84,6 +84,7 @@ export default class CVPoseTask extends CVTask configuration.annotationsVisible = false; configuration.interfaceVisible = false; configuration.bracketsVisible = true; + configuration.axesVisible = true; } protected get renderer() { diff --git a/source/client/components/CVTask.ts b/source/client/components/CVTask.ts index ec15195d..d1f22f8b 100644 --- a/source/client/components/CVTask.ts +++ b/source/client/components/CVTask.ts @@ -22,11 +22,21 @@ import CVNodeObserver from "./CVNodeObserver"; import CVDocument from "./CVDocument"; import NodeView, { customElement, property, html } from "../ui/explorer/NodeView"; +import Property from "@ff/graph/Property"; +import CVDocumentProvider from "./CVDocumentProvider"; //////////////////////////////////////////////////////////////////////////////// export { types, customElement, property, html }; +export interface CVTaskConfiguration{ + bracketsVisible?: boolean, + interfaceVisible?: boolean, + gridVisible?: boolean, + annotationsVisible?: boolean, + axesVisible?: boolean, +} + /** * Base class for tasks in the Voyager Story authoring environment. A task provides a number of tools. The tools operate * in the context of the currently selected node or component. @@ -59,14 +69,14 @@ export default class CVTask extends CVNodeObserver private _isActiveTask = false; - protected configuration = { + protected configuration :CVTaskConfiguration = { bracketsVisible: undefined, interfaceVisible: undefined, gridVisible: undefined, annotationsVisible: undefined, }; - private _savedConfig = { + private _savedConfig :CVTaskConfiguration = { bracketsVisible: undefined, interfaceVisible: undefined, gridVisible: undefined, @@ -95,15 +105,18 @@ export default class CVTask extends CVNodeObserver this._isActiveTask = true; this.outs.isActive.setValue(true); - const configuration = this.configuration; const savedConfig = this._savedConfig; - - if (configuration.bracketsVisible !== undefined) { + if (typeof configuration.bracketsVisible !== "undefined") { const prop = this.selection.ins.viewportBrackets; savedConfig.bracketsVisible = prop.value; prop.setValue(!!configuration.bracketsVisible); } + if(typeof configuration.axesVisible !== "undefined"){ + const prop = this.selection.ins.viewportAxes; + savedConfig.axesVisible = prop.value; + prop.setValue(!!configuration.bracketsVisible); + } } /** @@ -113,9 +126,12 @@ export default class CVTask extends CVNodeObserver { const savedConfig = this._savedConfig; - if (savedConfig.bracketsVisible !== undefined) { + if (typeof savedConfig.bracketsVisible !== "undefined") { this.selection.ins.viewportBrackets.setValue(savedConfig.bracketsVisible); } + if(typeof savedConfig.axesVisible !== "undefined"){ + this.selection.ins.viewportAxes.setValue(savedConfig.axesVisible); + } this._isActiveTask = false; From b89056e704cbf4b0a74c88a42c2ee3b450188a6f Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Fri, 18 Oct 2024 17:07:33 +0200 Subject: [PATCH 4/5] fix light helpers initial position update all light helpers --- .../source/components/CDirectionalLight.ts | 5 +- .../source/components/CHemisphereLight.ts | 5 +- libs/ff-scene/source/components/CLight.ts | 5 +- .../source/components/CPickSelection.ts | 50 ++++++------ libs/ff-scene/source/components/CRectLight.ts | 19 ++++- libs/ff-scene/source/components/CSpotLight.ts | 11 ++- libs/ff-three/source/Axes.ts | 5 ++ libs/ff-three/source/Bracket.ts | 4 + .../source/lights/AmbientLightHelper.ts | 66 +++++++++++++++ .../source/lights/DirectionalLightHelper.ts | 57 +++++++++++++ libs/ff-three/source/lights/LightHelper.ts | 43 ++++++++++ .../source/lights/PointLightHelper.ts | 40 +++++++++ .../ff-three/source/lights/RectLightHelper.ts | 81 +++++++++++++++++++ .../ff-three/source/lights/SpotLightHelper.ts | 43 ++++++++++ 14 files changed, 399 insertions(+), 35 deletions(-) create mode 100644 libs/ff-three/source/lights/AmbientLightHelper.ts create mode 100644 libs/ff-three/source/lights/DirectionalLightHelper.ts create mode 100644 libs/ff-three/source/lights/LightHelper.ts create mode 100644 libs/ff-three/source/lights/PointLightHelper.ts create mode 100644 libs/ff-three/source/lights/RectLightHelper.ts create mode 100644 libs/ff-three/source/lights/SpotLightHelper.ts diff --git a/libs/ff-scene/source/components/CDirectionalLight.ts b/libs/ff-scene/source/components/CDirectionalLight.ts index 4e261b2e..3ddb4b52 100644 --- a/libs/ff-scene/source/components/CDirectionalLight.ts +++ b/libs/ff-scene/source/components/CDirectionalLight.ts @@ -20,7 +20,10 @@ export default class CDirectionalLight extends CLight protected static readonly dirLightIns = { position: types.Vector3("Light.Position"), target: types.Vector3("Light.Target", [ 0, -1, 0 ]), - shadowSize: types.Number("Shadow.Size", 100), + shadowSize: types.Number("Shadow.Size", { + preset: 100, + min: 0, + }), }; ins = this.addInputs(CDirectionalLight.dirLightIns); diff --git a/libs/ff-scene/source/components/CHemisphereLight.ts b/libs/ff-scene/source/components/CHemisphereLight.ts index e4a5d1a7..57980594 100644 --- a/libs/ff-scene/source/components/CHemisphereLight.ts +++ b/libs/ff-scene/source/components/CHemisphereLight.ts @@ -7,7 +7,10 @@ import CLight from "./CLight"; //////////////////////////////////////////////////////////////////////////////// - +/** + * Implementeation of [HemisphereLight](https://threejs.org/docs/?q=HemisphereLight#api/en/lights/HemisphereLight) from three.js + * It does NOT work on Standard materials that have a metallic value of 1 + */ export default class CHemisphereLight extends CLight { static readonly typeName: string = "CHemisphereLight"; diff --git a/libs/ff-scene/source/components/CLight.ts b/libs/ff-scene/source/components/CLight.ts index 79446455..5e06684d 100644 --- a/libs/ff-scene/source/components/CLight.ts +++ b/libs/ff-scene/source/components/CLight.ts @@ -26,7 +26,10 @@ export default class CLight extends CObject3D protected static readonly lightIns = { color: types.ColorRGB("Light.Color"), - intensity: types.Number("Light.Intensity", 1), + intensity: types.Number("Light.Intensity", { + preset:1, + min: 0, + }), shadowEnabled: types.Boolean("Shadow.Enabled"), shadowResolution: types.Enum("Shadow.Resolution", EShadowMapResolution, EShadowMapResolution.Medium), shadowBlur: types.Number("Shadow.Blur", 1), diff --git a/libs/ff-scene/source/components/CPickSelection.ts b/libs/ff-scene/source/components/CPickSelection.ts index 11d7b29d..82b9f34e 100644 --- a/libs/ff-scene/source/components/CPickSelection.ts +++ b/libs/ff-scene/source/components/CPickSelection.ts @@ -5,7 +5,7 @@ * License: MIT */ -import Component, { types } from "@ff/graph/Component"; +import Component, { IUpdateContext, types } from "@ff/graph/Component"; import ComponentTracker from "@ff/graph/ComponentTracker"; import Node from "@ff/graph/Node"; import CSelection from "@ff/graph/components/CSelection"; @@ -15,19 +15,25 @@ import Axes from "@ff/three/Axes"; import { IPointerEvent } from "../RenderView"; +import SpotLightHelper from "@ff/three/lights/SpotLightHelper"; +import DirectionalLightHelper from "@ff/three/lights/DirectionalLightHelper"; +import PointLightHelper from "@ff/three/lights/PointLightHelper"; +import AmbientLightHelper from "@ff/three/lights/AmbientLightHelper"; +import RectLightHelper from "@ff/three/lights/RectLightHelper"; + import CObject3D from "./CObject3D"; import CTransform from "./CTransform"; import CScene, { ISceneAfterRenderEvent } from "./CScene"; -import { Box3, Color, DirectionalLightHelper, HemisphereLightHelper, Object3D, PointLightHelper, SpotLightHelper, Vector3 } from "three"; - +import { Box3, Color, Object3D } from "three"; //////////////////////////////////////////////////////////////////////////////// const helpers = [ [DirectionalLightHelper, "DirectionalLight"], [PointLightHelper, "PointLight"], [SpotLightHelper, "SpotLight"], - [HemisphereLightHelper, "HemisphereLight"], - [PointLightHelper, "RectAreaLight"], + [AmbientLightHelper, "HemisphereLight"], + [AmbientLightHelper, "AmbientLight"], + [RectLightHelper, "RectAreaLight"], ] as const; const _inputs = { @@ -36,15 +42,15 @@ const _inputs = { viewportAxes: types.Boolean("Viewport.Axes", false), }; -type Disposable = Object3D & {dispose: ()=>void}; +type HelperClass = Object3D & {dispose: ()=>void, update: ()=>void}; export default class CPickSelection extends CSelection { static readonly typeName: string = "CPickSelection"; ins = this.addInputs(_inputs); - private _brackets = new Map(); - private _axes :Disposable; + private _brackets = new Map(); + private _axes :HelperClass; create() @@ -116,21 +122,6 @@ export default class CPickSelection extends CSelection } } - // protected onActiveGraph(graph: Graph) - // { - // if (this._sceneTracker) { - // this._sceneTracker.dispose(); - // } - // - // if (graph) { - // this._sceneTracker = new ComponentTracker(graph.components, CScene, component => { - // component.on("after-render", this.onSceneAfterRender, this); - // }, component => { - // component.off("after-render", this.onSceneAfterRender, this); - // }); - // } - // } - protected onPointerUp(event: IPointerEvent) { if (!this.ins.viewportPicking.value || !event.isPrimary || event.isDragging) { @@ -144,7 +135,12 @@ export default class CPickSelection extends CSelection this.clearSelection(); } } - + tick(ctx:IUpdateContext) :boolean{ + for(let b of this._brackets.values()){ + b.update(); + } + return false; + } protected updateBracket(component: CTransform | CObject3D, selected: boolean) { @@ -156,11 +152,13 @@ export default class CPickSelection extends CSelection const transform = component.transform; if (selected) { if (object3D) { - let bracket :Disposable; + let bracket :HelperClass; if((object3D as any).isLight){ + let HelperCl = helpers.find(([h,type])=>type === object3D.type)?.[0]; if(HelperCl){ - bracket = new HelperCl(object3D as any, 1.0); + object3D.updateMatrix(); + bracket = new HelperCl(object3D as any); /** @bug PointLightHelper doesn't call it internally in its update() method. */ bracket.updateWorldMatrix( true, false ); } diff --git a/libs/ff-scene/source/components/CRectLight.ts b/libs/ff-scene/source/components/CRectLight.ts index 899f2856..b40cf763 100644 --- a/libs/ff-scene/source/components/CRectLight.ts +++ b/libs/ff-scene/source/components/CRectLight.ts @@ -12,8 +12,8 @@ export default class CRectLight extends CLight static readonly typeName: string = "CRectLight"; protected static readonly rectLightIns = { - position: types.Vector3("Light.Position", [ 0, 1, 0 ]), - target: types.Vector3("Light.Target", [ 0, 0, 0 ]), + position: types.Vector3("Light.Position", [ 0, 0, 0 ]), + target: types.Vector3("Light.Target", [ 0, -1, 0 ]), size: types.Vector2("Light.Size", [10, 10]), }; @@ -24,7 +24,10 @@ export default class CRectLight extends CLight super(node, id); this.object3D = new RectAreaLight(); - + this.light.width = 1; + this.light.height = 1; + this.object3D.matrixAutoUpdate = false; + this.transform.ins.scale.addEventListener("value",this.update, this); } get light(): RectAreaLight { @@ -40,10 +43,18 @@ export default class CRectLight extends CLight if (ins.position.changed || ins.target.changed) { light.position.fromArray(ins.position.value); + light.lookAt(new Vector3().fromArray(ins.target.value)); light.updateMatrix(); } - + //RectAreaLight's size ignores scaling + this.light.width = this.transform.ins.scale.value[0]*10; + this.light.height = this.transform.ins.scale.value[2]*10; return true; } + + dispose(){ + super.dispose(); + this.transform.ins.scale.removeEventListener("value",this.update); + } } \ No newline at end of file diff --git a/libs/ff-scene/source/components/CSpotLight.ts b/libs/ff-scene/source/components/CSpotLight.ts index 16f82b48..64a6ea10 100644 --- a/libs/ff-scene/source/components/CSpotLight.ts +++ b/libs/ff-scene/source/components/CSpotLight.ts @@ -20,9 +20,16 @@ export default class CSpotLight extends CLight protected static readonly spotLightIns = { position: types.Vector3("Light.Position"), target: types.Vector3("Light.Target", [ 0, -1, 0 ]), - distance: types.Number("Light.Distance"), + distance: types.Number("Light.Distance", { + preset: 0, + min: 0 + }), decay: types.Number("Light.Decay", 1), - angle: types.Number("Light.Angle", 45), + angle: types.Number("Light.Angle", { + preset:45, + min: 0, + max: 89 + }), penumbra: types.Percent("Light.Penumbra", 0.5), }; diff --git a/libs/ff-three/source/Axes.ts b/libs/ff-three/source/Axes.ts index 84b7fc6f..c49cd666 100644 --- a/libs/ff-three/source/Axes.ts +++ b/libs/ff-three/source/Axes.ts @@ -109,6 +109,11 @@ export default class Axes extends LineSegments2 } this.renderOrder = 1; + this.update(); + } + + update(){ + } dispose() diff --git a/libs/ff-three/source/Bracket.ts b/libs/ff-three/source/Bracket.ts index 207a9c2b..af7dabe4 100644 --- a/libs/ff-three/source/Bracket.ts +++ b/libs/ff-three/source/Bracket.ts @@ -130,6 +130,10 @@ export default class Bracket extends LineSegments this.renderOrder = 1; } + update(){ + + } + dispose() { diff --git a/libs/ff-three/source/lights/AmbientLightHelper.ts b/libs/ff-three/source/lights/AmbientLightHelper.ts new file mode 100644 index 00000000..8e2ccaeb --- /dev/null +++ b/libs/ff-three/source/lights/AmbientLightHelper.ts @@ -0,0 +1,66 @@ +import { ColorRepresentation, MeshBasicMaterial, Mesh, Light, SpotLight, SphereGeometry, PointLight, HemisphereLight, AmbientLight, Object3D, OctahedronGeometry, Color, PlaneGeometry, DoubleSide, BufferGeometry } from "three"; +import LightHelper from "./LightHelper"; + +import {Line2} from "three/examples/jsm/lines/Line2"; +import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; +import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; +import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2"; +import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry"; + +type SphereLight = AmbientLight|HemisphereLight; + +export default class AmbientLightHelper extends Object3D { + public readonly type = 'AmbientLightHelper'; + light :SphereLight; + + protected geometry :BufferGeometry; + protected upper :Mesh; + protected lower :Mesh; + + constructor( light :SphereLight, size :number = 1) { + + super(); + this.light = light; + this.geometry = new SphereGeometry( size/2, 8, 4, 0, Math.PI*2, 0, Math.PI/2 ); + this.upper = new Mesh( + this.geometry, + new MeshBasicMaterial({ + opacity: 0.4, + transparent: true, + toneMapped: false, + side: DoubleSide, + }), + ); + this.upper.receiveShadow = false; + console.log("Color", light.color); + (this.upper.material as MeshBasicMaterial).color = light.color; + this.add(this.upper); + + this.lower = new Mesh( + this.geometry, + new MeshBasicMaterial({ + opacity: 0.4, + transparent: true, + toneMapped: false, + side: DoubleSide, + }), + ); + this.lower.rotateX(Math.PI); + this.lower.updateMatrix(); + (this.lower.material as MeshBasicMaterial).color = ("groundColor" in light)? light.groundColor as Color : light.color; + this.add(this.lower); + this.position.set(0, -1, 0); + + } + + update(){ + + } + + dispose() { + this.geometry.dispose(); + (this.upper.material as MeshBasicMaterial).dispose(); + (this.lower.material as MeshBasicMaterial).dispose(); + } + +} diff --git a/libs/ff-three/source/lights/DirectionalLightHelper.ts b/libs/ff-three/source/lights/DirectionalLightHelper.ts new file mode 100644 index 00000000..e9e3735a --- /dev/null +++ b/libs/ff-three/source/lights/DirectionalLightHelper.ts @@ -0,0 +1,57 @@ +import { Vector3, BufferGeometry, Line, ColorRepresentation, SpotLight, DirectionalLight, Light, LightShadow } from "three"; +import LightHelper from "./LightHelper"; + +import {Line2} from "three/examples/jsm/lines/Line2"; +import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; +import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; + +type TargetLight = Light & (DirectionalLight|SpotLight); + +export default class DirectionalLightHelper extends LightHelper { + public readonly type :string = 'DirectionalLightHelper'; + light :TargetLight; + + protected target :Line2; + protected targetMaterial :LineMaterial; + + constructor( light :TargetLight, size ?:number) { + + super(light, size); + + this.targetMaterial = new LineMaterial({ + opacity: 0.8, + transparent: true, + toneMapped: false, + depthTest: false, //Directional lights have no real "position" so there is no point in using depth + worldUnits: false, + + linewidth: 2, + }); + this.targetMaterial.color = this.light.color; + + this.target = new Line2( + new LineGeometry().setPositions([ + 0, 0, 0, ...light.target.position.toArray(), + ]), + this.targetMaterial + ); + this.target.scale.setScalar(10); + this.target.matrixAutoUpdate = false; + this.add(this.target); + } + + update(){ + super.update(); + if(this.light.castShadow){ + this.target.scale.setScalar(this.light.shadow.camera.far- this.light.shadow.camera.near); + this.target.position.set( 0, -this.light.shadow.camera.near, 0); + this.target.updateMatrix(); + } + } + + dispose() { + this.target.geometry.dispose(); + super.dispose(); + } + +} diff --git a/libs/ff-three/source/lights/LightHelper.ts b/libs/ff-three/source/lights/LightHelper.ts new file mode 100644 index 00000000..3d0e1320 --- /dev/null +++ b/libs/ff-three/source/lights/LightHelper.ts @@ -0,0 +1,43 @@ +import { Object3D, ColorRepresentation, Material, MeshBasicMaterial, Light, SphereGeometry, WireframeGeometry, LineSegments, LineBasicMaterial } from "three"; + + + + +export default class LightHelper extends Object3D { + public readonly type :string = 'LightHelper'; + public light :Light; + + protected material :LineBasicMaterial; + protected source :LineSegments; + + constructor( light:Light, size :number = 1) { + + super(); + + this.light = light; + + + this.material = new LineBasicMaterial({ + opacity: 0.8, + transparent: true, + toneMapped: false, + }); + this.material.color = this.light.color; + + //Ponctual light indicator + this.source = new LineSegments( + new WireframeGeometry(new SphereGeometry(size/10, 8, 4)), + this.material, + ); + this.add(this.source); + } + + update(){ + } + + dispose() { + this.source.geometry.dispose(); + this.material.dispose(); + } + +} \ No newline at end of file diff --git a/libs/ff-three/source/lights/PointLightHelper.ts b/libs/ff-three/source/lights/PointLightHelper.ts new file mode 100644 index 00000000..811add42 --- /dev/null +++ b/libs/ff-three/source/lights/PointLightHelper.ts @@ -0,0 +1,40 @@ +import { MeshBasicMaterial, Mesh, SphereGeometry, PointLight } from "three"; +import LightHelper from "./LightHelper"; + + +export default class PointLightHelper extends LightHelper { + public readonly type = 'PointLightHelper'; + light :PointLight; + + protected distance :Mesh; + + constructor( light :PointLight) { + + super(light, 0.6); + + this.distance = new Mesh( + new SphereGeometry(1), + new MeshBasicMaterial({ + opacity: 0.15, + transparent: true, + }), + ); + (this.distance.material as MeshBasicMaterial).color = light.color; + this.distance.matrixAutoUpdate = false; + this.add(this.distance); + } + + update(){ + super.update(); + this.distance.scale.setScalar(this.light.distance); + this.distance.updateMatrix() + + } + + dispose() { + this.distance.geometry.dispose(); + (this.distance.material as MeshBasicMaterial).dispose(); + super.dispose(); + } + +} diff --git a/libs/ff-three/source/lights/RectLightHelper.ts b/libs/ff-three/source/lights/RectLightHelper.ts new file mode 100644 index 00000000..1445ec7b --- /dev/null +++ b/libs/ff-three/source/lights/RectLightHelper.ts @@ -0,0 +1,81 @@ +import { BoxGeometry, FrontSide, Mesh, MeshBasicMaterial, Object3D, RectAreaLight } from "three"; + +import {Line2} from "three/examples/jsm/lines/Line2"; +import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; +import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; +import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2"; +import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry"; + + +export default class RectLightHelper extends Object3D { + public readonly type :string = 'RectLightHelper'; + light :RectAreaLight; + + protected lines :LineSegments2; + protected area :Mesh; + + constructor( light :RectAreaLight) { + + super(); + this.light = light; + const material = new LineMaterial({ + opacity: 0.8, + transparent: true, + toneMapped: false, + worldUnits: false, + + linewidth: 2, + }); + material.color = this.light.color; + + this.lines = new LineSegments2( + new LineSegmentsGeometry().setPositions([ + 0, 0, 0, 0, 0, -1, + 5, -5, 0, 5, 5, 0, + 5, 5, 0, -5, 5, 0, + -5, 5, 0, -5, -5, 0, + -5, -5, 0, 5, -5, 0, + ]), + material + ); + this.add(this.lines); + + const boxMats = [ + null, null, null, null, + new MeshBasicMaterial({ + opacity: 0.1, //Back side + transparent: true, + toneMapped: false, + side: FrontSide, + }), + new MeshBasicMaterial({ + opacity: 0.4, //Front side + transparent: true, + toneMapped: false, + side: FrontSide, + }), + ]; + boxMats[4].color = boxMats[5].color = light.color; + + this.area = new Mesh( + new BoxGeometry(light.width, light.height, 0), + boxMats, + ) + this.add(this.area) + } + + update(){ + + } + + dispose() { + this.lines.geometry.dispose(); + this.area.geometry.dispose(); + + (this.lines.material as LineMaterial).dispose(); + let mats = (this.area.material as MeshBasicMaterial[]); + mats[4].dispose(); + mats[5].dispose(); + } + +} diff --git a/libs/ff-three/source/lights/SpotLightHelper.ts b/libs/ff-three/source/lights/SpotLightHelper.ts new file mode 100644 index 00000000..01b007e8 --- /dev/null +++ b/libs/ff-three/source/lights/SpotLightHelper.ts @@ -0,0 +1,43 @@ +import { ColorRepresentation, SpotLight, WireframeGeometry, LineSegments, ConeGeometry } from "three"; + +import DirectionalLightHelper from "./DirectionalLightHelper"; + + + +export default class SpotLightHelper extends DirectionalLightHelper { + public readonly type = 'SpotLightHelper'; + light :SpotLight; + + protected cone :LineSegments; + + constructor( light :SpotLight) { + + super(light as any, 0.6); + this.targetMaterial.depthTest = true; + + + this.cone = new LineSegments( + new WireframeGeometry(new ConeGeometry(1, 1, 16, 1, true)), + this.material, + ); + this.cone.matrixAutoUpdate = false; + this.cone.position.set(0, -0.5*length, 0); + this.cone.updateMatrix(); + this.add(this.cone); + } + + update(){ + let length = this.light.distance || 10; + let r = Math.min(Math.tan(this.light.angle), 1000)*length; + this.cone.scale.set(r, length, r); + this.cone.position.set(0, -0.5*length, 0); + this.cone.updateMatrix(); + super.update() + } + + dispose() { + this.cone.geometry.dispose(); + super.dispose(); + } + +} From fb9a2e02a1be499933e966ff2c2aa9b66fcdd22d Mon Sep 17 00:00:00 2001 From: Sebastien DUMETZ Date: Tue, 12 Nov 2024 17:09:41 +0100 Subject: [PATCH 5/5] make axes size proportional to the parent model's bounding box --- libs/ff-three/source/Axes.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/ff-three/source/Axes.ts b/libs/ff-three/source/Axes.ts index c49cd666..a8a1a09c 100644 --- a/libs/ff-three/source/Axes.ts +++ b/libs/ff-three/source/Axes.ts @@ -16,6 +16,7 @@ import { Float32BufferAttribute, Points, PointsMaterial, + Sphere, } from "three"; import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2"; @@ -45,7 +46,7 @@ export interface IAxesProps export default class Axes extends LineSegments2 { static readonly defaultProps = { - length: 0.25, + length: 0.15, width: 2, depthTest: false, colors: [new Color(0xffd633), new Color(0xa63b4a), new Color(0x6fa21c), new Color(0x2f83e1)], @@ -62,14 +63,14 @@ export default class Axes extends LineSegments2 computeLocalBoundingBox(target, box); - const length = props.length; + const size = (box.isEmpty()? 1: box.getBoundingSphere(new Sphere()).radius) * props.length; const originColor :Color = props.colors.length === 4? props.colors[0]: null; let vertices :number[] = [ - 0, 0, 0, length, 0, 0, - 0, 0, 0, 0, length, 0, - 0, 0, 0, 0, 0, length, + 0, 0, 0, size, 0, 0, + 0, 0, 0, 0, size, 0, + 0, 0, 0, 0, 0, size, ]; @@ -108,7 +109,7 @@ export default class Axes extends LineSegments2 this.add(this.originPoint); } - this.renderOrder = 1; + this.renderOrder = 2; this.update(); }