| @@ -1,5 +1,15 @@ | |||
| /* eslint-disable */ | |||
| import {Camera, Mesh, MOUSE, Object3D, Quaternion, Raycaster, Vector3} from 'three'; | |||
| import { | |||
| Camera, | |||
| LineBasicMaterial, | |||
| Mesh, | |||
| MeshBasicMaterial, | |||
| MOUSE, | |||
| Object3D, | |||
| Quaternion, | |||
| Raycaster, | |||
| Vector3 | |||
| } from 'three'; | |||
| export class TransformControls extends Object3D { | |||
| constructor(object: Camera, domElement?: HTMLElement); | |||
| @@ -36,6 +46,12 @@ export class TransformControls extends Object3D { | |||
| setSpace(space: 'world' | 'local'): void; | |||
| reset(): void; | |||
| dispose(): void; | |||
| static ObjectConstructors: { | |||
| MeshBasicMaterial: typeof MeshBasicMaterial; | |||
| LineBasicMaterial: typeof LineBasicMaterial; | |||
| } | |||
| } | |||
| export class TransformControlsGizmo extends Object3D { | |||
| @@ -662,6 +662,11 @@ class TransformControls extends Object3D { | |||
| } | |||
| TransformControls.ObjectConstructors = { | |||
| 'MeshBasicMaterial': MeshBasicMaterial, | |||
| 'LineBasicMaterial': LineBasicMaterial, | |||
| }; | |||
| // mouse / touch event handlers | |||
| function getPointer( event ) { | |||
| @@ -793,7 +798,7 @@ class TransformControlsGizmo extends Object3D { | |||
| // shared materials | |||
| const gizmoMaterial = new MeshBasicMaterial( { | |||
| const gizmoMaterial = new TransformControls.ObjectConstructors.MeshBasicMaterial( { | |||
| depthTest: false, | |||
| depthWrite: false, | |||
| fog: false, | |||
| @@ -801,7 +806,7 @@ class TransformControlsGizmo extends Object3D { | |||
| transparent: true | |||
| } ); | |||
| const gizmoLineMaterial = new LineBasicMaterial( { | |||
| const gizmoLineMaterial = new TransformControls.ObjectConstructors.LineBasicMaterial( { | |||
| depthTest: false, | |||
| depthWrite: false, | |||
| fog: false, | |||
| @@ -1519,7 +1524,7 @@ class TransformControlsPlane extends Mesh { | |||
| super( | |||
| new PlaneGeometry( 100000, 100000, 2, 2 ), | |||
| new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } ) | |||
| new TransformControls.ObjectConstructors.MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } ) | |||
| ); | |||
| this.isTransformControlsPlane = true; | |||
| @@ -1,6 +1,7 @@ | |||
| import {TransformControls} from './TransformControls.js' | |||
| import {MathUtils} from 'three' | |||
| import {ICamera, IObject3D, iObjectCommons, ISceneEvent, IWidget} from '../../core' | |||
| import type {ICamera, IObject3D, ISceneEvent, IWidget} from '../../core' | |||
| import {iObjectCommons} from '../../core' | |||
| import {uiDropdown, uiNumber, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||
| @uiPanelContainer('Transform Controls') | |||
| @@ -1,3 +1,6 @@ | |||
| export {OrbitControls3, type TOrbitControlsEvents} from './controls/OrbitControls3' | |||
| export {TransformControls2} from './controls/TransformControls2' | |||
| export {TransformControls, TransformControlsGizmo, TransformControlsPlane} from './controls/TransformControls' | |||
| export {Box3B} from './math/Box3B' | |||
| export * from './utils/index' | |||
| export * from './widgets/index' | |||
| @@ -7,10 +7,7 @@ export {generateUUID, toIndexedGeometry, isInScene, localToWorldQuaternion, worl | |||
| export {getTextureDataType, textureToCanvas, textureDataToImageData, textureToDataUrl, textureToBlob, texImageToCanvas} from './texture' | |||
| export {threeConstMappings} from './const-mappings' | |||
| export {ObjectPicker} from './ObjectPicker' | |||
| export {SelectionWidget, BoxSelectionWidget} from './SelectionWidget' | |||
| export {autoGPUInstanceMeshes} from './gpu-instancing' | |||
| export {ViewHelper2, type GizmoOrientation, type DomPlacement} from './ViewHelper2' | |||
| export {TransformControls2} from './TransformControls2' | |||
| export {TransformControls, TransformControlsGizmo, TransformControlsPlane} from './TransformControls' | |||
| // export {} from './constants' | |||
| @@ -0,0 +1,17 @@ | |||
| import {Camera} from 'three' | |||
| import {IUiConfigContainer} from 'uiconfig.js' | |||
| import {AHelperWidget} from './AHelperWidget' | |||
| export abstract class ACameraHelperWidget extends AHelperWidget { | |||
| camera: (Camera & IUiConfigContainer) | undefined | |||
| protected constructor(object: Camera & IUiConfigContainer) { | |||
| super(object) | |||
| this.camera = object | |||
| this.traverse(o => { | |||
| o.userData.__keepShadowDef = true | |||
| o.castShadow = false | |||
| o.receiveShadow = false | |||
| }) | |||
| } | |||
| } | |||
| @@ -0,0 +1,79 @@ | |||
| import {Object3D, PerspectiveCamera} from 'three' | |||
| import {generateUiFolder, IUiConfigContainer, uiToggle} from 'uiconfig.js' | |||
| import {iObjectCommons, IWidget} from '../../core' | |||
| import {onChange2} from 'ts-browser-helpers' | |||
| export abstract class AHelperWidget extends Object3D implements IWidget { | |||
| modelObject = this | |||
| isWidget = true as const | |||
| assetType = 'widget' | |||
| object: (Object3D & IUiConfigContainer) | undefined | |||
| @uiToggle() | |||
| @onChange2(AHelperWidget.prototype.update) | |||
| visible = true | |||
| protected constructor(object: Object3D & IUiConfigContainer) { | |||
| super() | |||
| this.object = object | |||
| this.object.updateMatrixWorld() | |||
| if ((this.object as PerspectiveCamera).updateProjectionMatrix) | |||
| (this.object as PerspectiveCamera).updateProjectionMatrix() | |||
| this.matrix = object.matrixWorld | |||
| this.matrixAutoUpdate = false | |||
| this._objectUpdate = this._objectUpdate.bind(this) | |||
| this.attach(object) | |||
| this.traverse(o => { | |||
| o.userData.__keepShadowDef = true | |||
| o.castShadow = false | |||
| o.receiveShadow = false | |||
| }) | |||
| } | |||
| dispose() { | |||
| this.detach() | |||
| } | |||
| update() { | |||
| iObjectCommons.setDirty.call(this) | |||
| } | |||
| private _objectUpdate() { | |||
| if (this.object) this.update() | |||
| } | |||
| attach(object: Object3D): this { | |||
| if (this.object) this.detach() | |||
| this.object = object | |||
| if (this.object) { | |||
| this.update() | |||
| this.object.addEventListener('objectUpdate', this._objectUpdate) | |||
| this.object.addEventListener('dispose', this.dispose) | |||
| this.uiConfig && this.object.uiConfig?.children?.push(this.uiConfig) | |||
| this.visible = true | |||
| } | |||
| return this | |||
| } | |||
| detach(): this { | |||
| if (this.object) { | |||
| this.object.removeEventListener('objectUpdate', this._objectUpdate) | |||
| this.object.removeEventListener('dispose', this.dispose) | |||
| if (this.uiConfig) { | |||
| const i = this.object.uiConfig?.children?.indexOf(this.uiConfig) | |||
| if (i !== undefined && i >= 0) | |||
| this.object.uiConfig?.children?.splice(i, 1) | |||
| } | |||
| this.object = undefined | |||
| this.visible = false | |||
| } | |||
| return this | |||
| } | |||
| uiConfig = generateUiFolder('Widget', this) | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| import {Light} from 'three' | |||
| import {IUiConfigContainer} from 'uiconfig.js' | |||
| import {AHelperWidget} from './AHelperWidget' | |||
| export abstract class ALightHelperWidget extends AHelperWidget { | |||
| light: (Light & IUiConfigContainer)|undefined | |||
| protected constructor(object: Light & IUiConfigContainer) { | |||
| super(object) | |||
| this.light = object | |||
| this.traverse(o => { | |||
| o.userData.__keepShadowDef = true | |||
| o.castShadow = false | |||
| o.receiveShadow = false | |||
| }) | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| import {LineMaterial2} from '../../core/material/LineMaterial2' | |||
| import {Vector2} from 'three' | |||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | |||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | |||
| import {Box3B} from '../math/Box3B' | |||
| import {SelectionWidget} from './SelectionWidget' | |||
| export class BoxSelectionWidget extends SelectionWidget { | |||
| constructor() { | |||
| super() | |||
| const matLine = new LineMaterial2({ | |||
| color: '#ff2222' as any, transparent: true, opacity: 0.9, | |||
| linewidth: 5, // in pixels | |||
| resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | |||
| dashed: false, | |||
| toneMapped: false, | |||
| }) | |||
| this.lineMaterial = matLine | |||
| const ls = new LineSegmentsGeometry() | |||
| ls.setPositions([1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1].map(v => v - 0.5)) | |||
| const wireframe = new LineSegments2(ls, matLine as any) | |||
| wireframe.computeLineDistances() | |||
| wireframe.scale.set(1, 1, 1) | |||
| wireframe.visible = true | |||
| this.add(wireframe) | |||
| } | |||
| protected _updater() { | |||
| super._updater() | |||
| const selected = this.object | |||
| if (selected) { | |||
| const bbox = new Box3B().expandByObject(selected, false) | |||
| // const scale = bbox.getBoundingSphere(new Sphere()).radius | |||
| bbox.getSize(this.scale).multiplyScalar(this.boundingScaleMultiplier).clampScalar(0.1, 100) | |||
| this.setVisible(true) | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,311 @@ | |||
| import {Camera, Color, Object3D, OrthographicCamera, PerspectiveCamera, Vector3} from 'three' | |||
| import {ACameraHelperWidget} from './ACameraHelperWidget' | |||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | |||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | |||
| import {InterleavedBufferAttribute} from 'three/src/core/InterleavedBufferAttribute' | |||
| import {LineMaterial2} from '../../core' | |||
| /** | |||
| * Fork of CameraHelper from three.js | |||
| * - shows frustum, line of sight and up of the camera | |||
| * - suitable for fast updates | |||
| * - based on frustum visualization in lightgl.js shadowmap example | |||
| * https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html | |||
| */ | |||
| export class CameraHelper2 extends ACameraHelperWidget { | |||
| protected _vector = new Vector3() | |||
| // @ts-expect-error update three-ts-types remove abstract | |||
| protected _camera = new Camera() | |||
| line: LineSegments2 | |||
| pointMap: Record<string, number[]> | |||
| constructor(camera: PerspectiveCamera|OrthographicCamera) { | |||
| super(camera) | |||
| const geometry = new LineSegmentsGeometry() | |||
| const material = new LineMaterial2({ | |||
| color: 0xffffff, | |||
| linewidth: 0.005, // in world units with size attenuation, pixels otherwise | |||
| vertexColors: true, | |||
| dashed: false, | |||
| alphaToCoverage: true, | |||
| toneMapped: false, | |||
| transparent: true, | |||
| depthTest: false, | |||
| depthWrite: false, | |||
| }) | |||
| const {vertices, colors, pointMap} = generateVertices() | |||
| geometry.setPositions(vertices) | |||
| geometry.setColors(colors) | |||
| // @ts-expect-error update three-ts-types | |||
| this.line = new LineSegments2(geometry, material) | |||
| this.line.frustumCulled = false | |||
| this.add(this.line) | |||
| this.pointMap = pointMap | |||
| this.update() | |||
| // colors | |||
| const colorFrustum = new Color(0xffaa00) | |||
| const colorCone = new Color(0xff0000) | |||
| const colorUp = new Color(0x00aaff) | |||
| const colorTarget = new Color(0xffffff) | |||
| const colorCross = new Color(0x333333) | |||
| this.setColors(colorFrustum, colorCone, colorUp, colorTarget, colorCross) | |||
| } | |||
| setColors(frustum: Color, cone: Color, up: Color, target: Color, cross: Color) { | |||
| const geometry = this.line.geometry | |||
| const colorAttribute = geometry.getAttribute('instanceColorStart') | |||
| const colorAttribute2 = geometry.getAttribute('instanceColorEnd') | |||
| function setXYZ(i: number, color: Color) { | |||
| colorAttribute.setXYZ(i / 2, color.r, color.g, color.b) | |||
| colorAttribute2.setXYZ(i / 2, color.r, color.g, color.b) | |||
| } | |||
| // near | |||
| setXYZ(0, frustum) // n1, n2 | |||
| setXYZ(2, frustum) // n2, n4 | |||
| setXYZ(4, frustum) // n4, n3 | |||
| setXYZ(6, frustum) // n3, n1 | |||
| // far | |||
| setXYZ(8, frustum) // f1, f2 | |||
| setXYZ(10, frustum) // f2, f4 | |||
| setXYZ(12, frustum) // f4, f3 | |||
| setXYZ(14, frustum) // f3, f1 | |||
| // sides | |||
| setXYZ(16, frustum) // n1, f1 | |||
| setXYZ(18, frustum) // n2, f2 | |||
| setXYZ(20, frustum) // n3, f3 | |||
| setXYZ(22, frustum) // n4, f4 | |||
| // cone | |||
| setXYZ(24, cone) // p, n1 | |||
| setXYZ(26, cone) // p, n2 | |||
| setXYZ(28, cone) // p, n3 | |||
| setXYZ(30, cone) // p, n4 | |||
| // up | |||
| setXYZ(32, up) // u1, u2 | |||
| setXYZ(34, up) // u2, u3 | |||
| setXYZ(36, up) // u3, u1 | |||
| // target | |||
| setXYZ(38, target) // c, t | |||
| setXYZ(40, cross) // p, c | |||
| // cross | |||
| setXYZ(42, cross) // cn1, cn2 | |||
| setXYZ(44, cross) // cn3, cn4 | |||
| setXYZ(46, cross) // cf1, cf2 | |||
| setXYZ(48, cross) // cf3, cf4 | |||
| colorAttribute.needsUpdate = true | |||
| colorAttribute2.needsUpdate = true | |||
| } | |||
| update() { | |||
| if (!this.camera) return | |||
| const geometry = this.line.geometry | |||
| const pointMap = this.pointMap | |||
| const w = 1, h = 1 | |||
| // we need just camera projection matrix inverse | |||
| // world matrix must be identity | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| const {_camera, _vector} = this | |||
| _camera.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse) | |||
| // center / target | |||
| setPoint('c', pointMap, geometry, _camera, 0, 0, -1, _vector) | |||
| setPoint('t', pointMap, geometry, _camera, 0, 0, 1, _vector) | |||
| // near | |||
| setPoint('n1', pointMap, geometry, _camera, -w, -h, -1, _vector) | |||
| setPoint('n2', pointMap, geometry, _camera, w, -h, -1, _vector) | |||
| setPoint('n3', pointMap, geometry, _camera, -w, h, -1, _vector) | |||
| setPoint('n4', pointMap, geometry, _camera, w, h, -1, _vector) | |||
| // far | |||
| setPoint('f1', pointMap, geometry, _camera, -w, -h, 1, _vector) | |||
| setPoint('f2', pointMap, geometry, _camera, w, -h, 1, _vector) | |||
| setPoint('f3', pointMap, geometry, _camera, -w, h, 1, _vector) | |||
| setPoint('f4', pointMap, geometry, _camera, w, h, 1, _vector) | |||
| // up | |||
| setPoint('u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, -1, _vector) | |||
| setPoint('u2', pointMap, geometry, _camera, -w * 0.7, h * 1.1, -1, _vector) | |||
| setPoint('u3', pointMap, geometry, _camera, 0, h * 2, -1, _vector) | |||
| // cross | |||
| setPoint('cf1', pointMap, geometry, _camera, -w, 0, 1, _vector) | |||
| setPoint('cf2', pointMap, geometry, _camera, w, 0, 1, _vector) | |||
| setPoint('cf3', pointMap, geometry, _camera, 0, -h, 1, _vector) | |||
| setPoint('cf4', pointMap, geometry, _camera, 0, h, 1, _vector) | |||
| setPoint('cn1', pointMap, geometry, _camera, -w, 0, -1, _vector) | |||
| setPoint('cn2', pointMap, geometry, _camera, w, 0, -1, _vector) | |||
| setPoint('cn3', pointMap, geometry, _camera, 0, -h, -1, _vector) | |||
| setPoint('cn4', pointMap, geometry, _camera, 0, h, -1, _vector) | |||
| geometry.getAttribute('instanceStart').needsUpdate = true | |||
| geometry.getAttribute('instanceEnd').needsUpdate = true | |||
| geometry.computeBoundingBox() | |||
| geometry.computeBoundingSphere() | |||
| super.update() | |||
| } | |||
| dispose() { | |||
| this.line.geometry.dispose() | |||
| this.line.material.dispose() | |||
| super.dispose() | |||
| } | |||
| static Check(camera: Object3D) { | |||
| return (camera as any).isCamera | |||
| } | |||
| static Create(camera: Object3D) { | |||
| return new CameraHelper2(camera as any) | |||
| } | |||
| } | |||
| function setPoint(point: string, pointMap: Record<string, number[]>, geometry: LineSegmentsGeometry, camera: Camera, x: number, y: number, z: number, _vector: Vector3) { | |||
| _vector.set(x, y, z).unproject(camera) | |||
| const points = pointMap[ point ] | |||
| if (points !== undefined) { | |||
| const position1 = geometry.getAttribute('instanceStart') as InterleavedBufferAttribute | |||
| const position2 = geometry.getAttribute('instanceEnd') as InterleavedBufferAttribute | |||
| for (let i = 0, l = points.length; i < l; i++) { | |||
| const j = Math.floor(points[ i ] / 2.) | |||
| ;(points[ i ] % 2 === 0 ? position1 : position2).setXYZ(j, _vector.x, _vector.y, _vector.z) | |||
| // (i % 2 === 0 ? position1 : position2).setXYZ(points[ i ], _vector.x, _vector.y, _vector.z) | |||
| } | |||
| } | |||
| } | |||
| function generateVertices() { | |||
| const vertices: number[] = [] | |||
| const colors: number[] = [] | |||
| const pointMap: any = {} | |||
| // near | |||
| addLine('n1', 'n2') | |||
| addLine('n2', 'n4') | |||
| addLine('n4', 'n3') | |||
| addLine('n3', 'n1') | |||
| // far | |||
| addLine('f1', 'f2') | |||
| addLine('f2', 'f4') | |||
| addLine('f4', 'f3') | |||
| addLine('f3', 'f1') | |||
| // sides | |||
| addLine('n1', 'f1') | |||
| addLine('n2', 'f2') | |||
| addLine('n3', 'f3') | |||
| addLine('n4', 'f4') | |||
| // cone | |||
| addLine('p', 'n1') | |||
| addLine('p', 'n2') | |||
| addLine('p', 'n3') | |||
| addLine('p', 'n4') | |||
| // up | |||
| addLine('u1', 'u2') | |||
| addLine('u2', 'u3') | |||
| addLine('u3', 'u1') | |||
| // target | |||
| addLine('c', 't') | |||
| addLine('p', 'c') | |||
| // cross | |||
| addLine('cn1', 'cn2') | |||
| addLine('cn3', 'cn4') | |||
| addLine('cf1', 'cf2') | |||
| addLine('cf3', 'cf4') | |||
| function addLine(a: string, b: string) { | |||
| addPoint(a) | |||
| addPoint(b) | |||
| } | |||
| function addPoint(id: string) { | |||
| vertices.push(0, 0, 0) | |||
| colors.push(0, 0, 0) | |||
| if (pointMap[id] === undefined) { | |||
| pointMap[id] = [] | |||
| } | |||
| pointMap[id].push(vertices.length / 3 - 1) | |||
| } | |||
| return {vertices, colors, pointMap} | |||
| } | |||
| @@ -0,0 +1,117 @@ | |||
| import {ColorRepresentation, DirectionalLight, Object3D, Vector3} from 'three' | |||
| import {Line2} from 'three/examples/jsm/lines/Line2.js' | |||
| import {LineGeometry} from 'three/examples/jsm/lines/LineGeometry.js' | |||
| import {onChange} from 'ts-browser-helpers' | |||
| import {ALightHelperWidget} from './ALightHelperWidget' | |||
| import {IUiConfigContainer, uiSlider} from 'uiconfig.js' | |||
| import {LineMaterial2} from '../../core' | |||
| export class DirectionalLightHelper2 extends ALightHelperWidget { | |||
| color: ColorRepresentation|undefined | |||
| lightPlane: Line2 | |||
| targetLine: Line2 | |||
| light: (DirectionalLight&IUiConfigContainer)|undefined | |||
| @onChange(DirectionalLightHelper2.prototype.update) | |||
| material: LineMaterial2 | |||
| @onChange(DirectionalLightHelper2.prototype.update) | |||
| @uiSlider(undefined, [0.1, 20], 0.01) | |||
| lineWidth = 5 | |||
| @onChange(DirectionalLightHelper2.prototype.update) | |||
| @uiSlider(undefined, [0.01, 10], 0.01) | |||
| size = 0.5 | |||
| constructor(light: DirectionalLight, size?: number, color?: ColorRepresentation) { | |||
| super(light) | |||
| this.color = color | |||
| if (size !== undefined) this.size = size | |||
| let geometry = new LineGeometry() | |||
| this.material = new LineMaterial2({ | |||
| color: 0xff0000, | |||
| linewidth: 0.005, // in world units with size attenuation, pixels otherwise | |||
| vertexColors: false, | |||
| dashed: false, | |||
| alphaToCoverage: true, | |||
| toneMapped: false, | |||
| transparent: true, | |||
| depthTest: false, | |||
| depthWrite: false, | |||
| }) | |||
| // @ts-expect-error update three-ts-types | |||
| this.lightPlane = new Line2(geometry, this.material) | |||
| this.add(this.lightPlane) | |||
| geometry = new LineGeometry() | |||
| geometry.setPositions([0, 0, 0, 0, 0, 1]) | |||
| // @ts-expect-error update three-ts-types | |||
| this.targetLine = new Line2(geometry, this.material) | |||
| this.add(this.targetLine) | |||
| this.update() | |||
| this.traverse(o=>{ | |||
| o.userData.__keepShadowDef = true | |||
| o.castShadow = false | |||
| o.receiveShadow = false | |||
| }) | |||
| } | |||
| dispose() { | |||
| this.lightPlane.geometry.dispose() | |||
| this.lightPlane.material.dispose() | |||
| this.targetLine.geometry.dispose() | |||
| this.targetLine.material.dispose() | |||
| super.dispose() | |||
| } | |||
| private _v1 = new Vector3() | |||
| private _v2 = new Vector3() | |||
| private _v3 = new Vector3() | |||
| update() { | |||
| if (!this.light || !this.lightPlane) return | |||
| this._v1.setFromMatrixPosition(this.light.matrixWorld) | |||
| this._v2.setFromMatrixPosition(this.light.target.matrixWorld) | |||
| this._v3.subVectors(this._v2, this._v1) | |||
| this.lightPlane.geometry.setPositions([ | |||
| -this.size, this.size, 0, | |||
| this.size, this.size, 0, | |||
| this.size, -this.size, 0, | |||
| -this.size, -this.size, 0, | |||
| -this.size, this.size, 0, | |||
| ]) | |||
| this.lightPlane.lookAt(this._v2) | |||
| // @ts-expect-error update three-ts-types | |||
| this.lightPlane.material = this.material | |||
| // @ts-expect-error update three-ts-types | |||
| this.targetLine.material = this.material | |||
| this.material.color.set(this.color ?? this.light.color) | |||
| this.material.linewidth = this.lineWidth * 0.001 | |||
| this.targetLine.lookAt(this._v2) | |||
| this.targetLine.scale.z = this.light.intensity / 3. | |||
| super.update() | |||
| } | |||
| static Check(light: Object3D) { | |||
| return (light as DirectionalLight).isDirectionalLight | |||
| } | |||
| static Create(light: Object3D) { | |||
| return new DirectionalLightHelper2(light as DirectionalLight) | |||
| } | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| import {ColorRepresentation, Object3D, PointLight, SphereGeometry} from 'three' | |||
| import {WireframeGeometry2} from 'three/examples/jsm/lines/WireframeGeometry2.js' | |||
| import {Wireframe} from 'three/examples/jsm/lines/Wireframe.js' | |||
| import {onChange} from 'ts-browser-helpers' | |||
| import {ALightHelperWidget} from './ALightHelperWidget' | |||
| import {IUiConfigContainer, uiSlider} from 'uiconfig.js' | |||
| import {LineMaterial2} from '../../core' | |||
| export class PointLightHelper2 extends ALightHelperWidget { | |||
| color: ColorRepresentation | undefined | |||
| lightSphere: Wireframe | |||
| light: (PointLight & IUiConfigContainer) | undefined | |||
| @onChange(PointLightHelper2.prototype.update) | |||
| material: LineMaterial2 | |||
| @onChange(PointLightHelper2.prototype.update) | |||
| @uiSlider(undefined, [0.1, 20], 0.01) | |||
| lineWidth = 5 | |||
| @onChange(PointLightHelper2.prototype.update) | |||
| @uiSlider(undefined, [0.01, 10], 0.01) | |||
| size = 0.5 | |||
| constructor(light: PointLight, size?: number, color?: ColorRepresentation) { | |||
| super(light) | |||
| this.color = color | |||
| if (size !== undefined) this.size = size | |||
| const geometry = new WireframeGeometry2(new SphereGeometry(0.5, 4, 2)) | |||
| this.material = new LineMaterial2({ | |||
| color: 0xff0000, | |||
| linewidth: 0.005, // in world units with size attenuation, pixels otherwise | |||
| vertexColors: false, | |||
| dashed: false, | |||
| alphaToCoverage: true, | |||
| toneMapped: false, | |||
| transparent: true, | |||
| depthTest: false, | |||
| depthWrite: false, | |||
| }) | |||
| // @ts-expect-error update three-ts-types | |||
| this.lightSphere = new Wireframe(geometry, this.material) | |||
| this.lightSphere.computeLineDistances() | |||
| this.add(this.lightSphere) | |||
| this.update() | |||
| this.traverse(o => { | |||
| o.userData.__keepShadowDef = true | |||
| o.castShadow = false | |||
| o.receiveShadow = false | |||
| }) | |||
| } | |||
| dispose() { | |||
| this.lightSphere.geometry.dispose() | |||
| ;(this.lightSphere.material as any).dispose() | |||
| super.dispose() | |||
| } | |||
| update() { | |||
| if (!this.light || !this.lightSphere) return | |||
| this.material.color.set(this.color ?? this.light.color) | |||
| this.material.linewidth = this.lineWidth * 0.001 | |||
| this.lightSphere.scale.setScalar(this.size) | |||
| super.update() | |||
| } | |||
| static Check(light: Object3D) { | |||
| return (light as PointLight).isPointLight | |||
| } | |||
| static Create(light: Object3D) { | |||
| return new PointLightHelper2(light as PointLight) | |||
| } | |||
| } | |||
| @@ -1,9 +1,7 @@ | |||
| import {Group, Sphere, Vector2} from 'three' | |||
| import {Group, Sphere} from 'three' | |||
| import {AnyOptions} from 'ts-browser-helpers' | |||
| import {Box3B} from '../math/Box3B' | |||
| import {IMaterial, IObject3D, IWidget, LineMaterial2} from '../../core' | |||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | |||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | |||
| import {IMaterial, IObject3D, IWidget} from '../../core' | |||
| export class SelectionWidget extends Group implements IWidget { | |||
| isWidget = true as const | |||
| @@ -78,36 +76,3 @@ export class SelectionWidget extends Group implements IWidget { | |||
| } | |||
| export class BoxSelectionWidget extends SelectionWidget { | |||
| constructor() { | |||
| super() | |||
| const matLine = new LineMaterial2({ | |||
| color: '#ff2222' as any, transparent: true, opacity: 0.9, | |||
| linewidth: 5, // in pixels | |||
| resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | |||
| dashed: false, | |||
| toneMapped: false, | |||
| }) | |||
| this.lineMaterial = matLine | |||
| const ls = new LineSegmentsGeometry() | |||
| ls.setPositions([1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1].map(v=>v - 0.5)) | |||
| const wireframe = new LineSegments2(ls, matLine as any) | |||
| wireframe.computeLineDistances() | |||
| wireframe.scale.set(1, 1, 1) | |||
| wireframe.visible = true | |||
| this.add(wireframe) | |||
| } | |||
| protected _updater() { | |||
| super._updater() | |||
| const selected = this.object | |||
| if (selected) { | |||
| const bbox = new Box3B().expandByObject(selected, false) | |||
| // const scale = bbox.getBoundingSphere(new Sphere()).radius | |||
| bbox.getSize(this.scale).multiplyScalar(this.boundingScaleMultiplier).clampScalar(0.1, 100) | |||
| this.setVisible(true) | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,119 @@ | |||
| import {ColorRepresentation, Object3D, SpotLight, Vector3} from 'three' | |||
| import {LineGeometry} from 'three/examples/jsm/lines/LineGeometry.js' | |||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | |||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | |||
| import {onChange} from 'ts-browser-helpers' | |||
| import {ALightHelperWidget} from './ALightHelperWidget' | |||
| import {IUiConfigContainer, uiSlider} from 'uiconfig.js' | |||
| import {LineMaterial2} from '../../core' | |||
| export class SpotLightHelper2 extends ALightHelperWidget { | |||
| color: ColorRepresentation | undefined | |||
| cone: LineSegments2 | |||
| light: (SpotLight & IUiConfigContainer) | undefined | |||
| @onChange(SpotLightHelper2.prototype.update) | |||
| material: LineMaterial2 | |||
| @onChange(SpotLightHelper2.prototype.update) | |||
| @uiSlider(undefined, [0.1, 20], 0.01) | |||
| lineWidth = 5 | |||
| constructor(light: SpotLight, size?: number, color?: ColorRepresentation) { | |||
| super(light) | |||
| this.color = color | |||
| if (size === undefined) size = 0.5 | |||
| let geometry = new LineSegmentsGeometry() | |||
| const positions = [ | |||
| 0, 0, 0, 0, 0, 1, | |||
| 0, 0, 0, 1, 0, 1, | |||
| 0, 0, 0, -1, 0, 1, | |||
| 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, -1, 1, | |||
| ] | |||
| for (let i = 0, j = 1, l = 32; i < l; i++, j++) { | |||
| const p1 = i / l * Math.PI * 2 | |||
| const p2 = j / l * Math.PI * 2 | |||
| positions.push( | |||
| Math.cos(p1), Math.sin(p1), 1, | |||
| Math.cos(p2), Math.sin(p2), 1 | |||
| ) | |||
| } | |||
| geometry.setPositions(positions) | |||
| this.material = new LineMaterial2({ | |||
| color: 0xff0000, | |||
| linewidth: 0.005, // in world units with size attenuation, pixels otherwise | |||
| vertexColors: false, | |||
| dashed: false, | |||
| alphaToCoverage: true, | |||
| toneMapped: false, | |||
| transparent: true, | |||
| depthTest: false, | |||
| depthWrite: false, | |||
| }) | |||
| // @ts-expect-error update three-ts-types | |||
| this.cone = new LineSegments2(geometry, this.material) | |||
| this.add(this.cone) | |||
| geometry = new LineGeometry() | |||
| geometry.setPositions([0, 0, 0, 0, 0, 1]) | |||
| this.update() | |||
| this.traverse(o => { | |||
| o.userData.__keepShadowDef = true | |||
| o.castShadow = false | |||
| o.receiveShadow = false | |||
| }) | |||
| } | |||
| dispose() { | |||
| this.cone.geometry.dispose() | |||
| this.cone.material.dispose() | |||
| super.dispose() | |||
| } | |||
| private _v1 = new Vector3() | |||
| update() { | |||
| if (!this.light || !this.cone) return | |||
| this.light.updateWorldMatrix(true, false) | |||
| this.light.target.updateWorldMatrix(true, false) | |||
| const coneLength = this.light.distance ? this.light.distance : 1000 | |||
| const coneWidth = coneLength * Math.tan(this.light.angle) | |||
| this.cone.scale.set(coneWidth, coneWidth, coneLength) | |||
| this._v1.setFromMatrixPosition(this.light.target.matrixWorld) | |||
| this.cone.lookAt(this._v1) | |||
| this.material.color.set(this.color ?? this.light.color) | |||
| this.material.linewidth = this.lineWidth * 0.001 | |||
| super.update() | |||
| } | |||
| static Check(light: Object3D) { | |||
| return (light as SpotLight).isSpotLight | |||
| } | |||
| static Create(light: Object3D) { | |||
| return new SpotLightHelper2(light as SpotLight) | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| export {SelectionWidget} from './SelectionWidget' | |||
| export {ACameraHelperWidget} from './ACameraHelperWidget' | |||
| export {AHelperWidget} from './AHelperWidget' | |||
| export {ALightHelperWidget} from './ALightHelperWidget' | |||
| export {BoxSelectionWidget} from './BoxSelectionWidget' | |||
| export {CameraHelper2} from './CameraHelper2' | |||
| export {DirectionalLightHelper2} from './DirectionalLightHelper2' | |||
| export {PointLightHelper2} from './PointLightHelper2' | |||
| export {SpotLightHelper2} from './SpotLightHelper2' | |||