| light.position.set(2, 2, 2) | light.position.set(2, 2, 2) | ||||
| light.lookAt(0, 0, 0) | light.lookAt(0, 0, 0) | ||||
| light.castShadow = true | light.castShadow = true | ||||
| light.shadow.mapSize.setScalar(1024) | |||||
| light.shadow.camera.near = 0.1 | |||||
| light.shadow.camera.far = 10 | |||||
| light.shadow.camera.top = 2 | |||||
| light.shadow.camera.bottom = -2 | |||||
| light.shadow.camera.left = -2 | |||||
| light.shadow.camera.right = 2 | |||||
| light.shadowMapSize.setScalar(1024) | |||||
| light.shadowNear = 0.1 | |||||
| light.shadowFar = 10 | |||||
| light.shadowFrustum = 4 | |||||
| viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | ||||
| light.distance = 10 | light.distance = 10 | ||||
| light.decay = 1 | light.decay = 1 | ||||
| light.castShadow = true | light.castShadow = true | ||||
| light.shadow.mapSize.setScalar(1024) | |||||
| light.shadow.camera.near = 0.1 | |||||
| light.shadow.camera.far = 10 | |||||
| light.shadowMapSize.setScalar(1024) | |||||
| light.shadowNear = 0.1 | |||||
| light.shadowFar = 10 | |||||
| viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | ||||
| BasicShadowMap, | BasicShadowMap, | ||||
| Color, | Color, | ||||
| DataUtils, | DataUtils, | ||||
| DirectionalLight, | |||||
| DirectionalLight, DirectionalLight2, | |||||
| IObject3D, | IObject3D, | ||||
| LoadingScreenPlugin, | LoadingScreenPlugin, | ||||
| MaterialExtension, | MaterialExtension, | ||||
| } | } | ||||
| function createDirLight(viewer: ThreeViewer) { | function createDirLight(viewer: ThreeViewer) { | ||||
| const directionalLight = new DirectionalLight(0xffffff, 4) | |||||
| const directionalLight = new DirectionalLight2(0xffffff, 4) | |||||
| directionalLight.position.set(-2, -2, 2) | directionalLight.position.set(-2, -2, 2) | ||||
| directionalLight.lookAt(0, 0, 0) | directionalLight.lookAt(0, 0, 0) | ||||
| directionalLight.color.set(0xffffff) | directionalLight.color.set(0xffffff) | ||||
| directionalLight.intensity = 0 | directionalLight.intensity = 0 | ||||
| directionalLight.castShadow = true | directionalLight.castShadow = true | ||||
| directionalLight.shadow.mapSize.setScalar(1024) | |||||
| directionalLight.shadow.camera.near = 0.1 | |||||
| directionalLight.shadow.camera.far = 10 | |||||
| directionalLight.shadow.camera.top = 2 | |||||
| directionalLight.shadow.camera.bottom = -2 | |||||
| directionalLight.shadow.camera.left = -2 | |||||
| directionalLight.shadow.camera.right = 2 | |||||
| directionalLight.shadowMapSize.setScalar(1024) | |||||
| directionalLight.shadowNear = 0.1 | |||||
| directionalLight.shadowFar = 10 | |||||
| directionalLight.shadowFrustum = 4 | |||||
| viewer.scene.addObject(directionalLight, {addToRoot: true}) | viewer.scene.addObject(directionalLight, {addToRoot: true}) | ||||
| // move to index 0 in parent.children, so that directionalLight always has index 0 in shader. required for material extension | // move to index 0 in parent.children, so that directionalLight always has index 0 in shader. required for material extension | ||||
| const parent = directionalLight.parent! | const parent = directionalLight.parent! |
| _testFinish, | _testFinish, | ||||
| BasicShadowMap, | BasicShadowMap, | ||||
| Box3B, | Box3B, | ||||
| DirectionalLight, | |||||
| DirectionalLight2, | |||||
| IObject3D, | IObject3D, | ||||
| LoadingScreenPlugin, | LoadingScreenPlugin, | ||||
| Mesh, | Mesh, | ||||
| ground.receiveShadow = true | ground.receiveShadow = true | ||||
| viewer.scene.addObject(ground) | viewer.scene.addObject(ground) | ||||
| const directionalLight = viewer.scene.addObject(new DirectionalLight(0xffffff, 4)) | |||||
| const directionalLight = viewer.scene.addObject(new DirectionalLight2(0xffffff, 4)) | |||||
| directionalLight.position.set(2, 2, 2) | directionalLight.position.set(2, 2, 2) | ||||
| directionalLight.lookAt(0, 0, 0) | directionalLight.lookAt(0, 0, 0) | ||||
| directionalLight.castShadow = true | directionalLight.castShadow = true | ||||
| directionalLight.shadow.mapSize.setScalar(1024) | |||||
| directionalLight.shadow.camera.near = 0.1 | |||||
| directionalLight.shadow.camera.far = 10 | |||||
| directionalLight.shadow.camera.top = 2 | |||||
| directionalLight.shadow.camera.bottom = -2 | |||||
| directionalLight.shadow.camera.left = -2 | |||||
| directionalLight.shadow.camera.right = 2 | |||||
| directionalLight.shadowMapSize.setScalar(1024) | |||||
| directionalLight.shadowNear = 0.1 | |||||
| directionalLight.shadowFar = 10 | |||||
| directionalLight.shadowFrustum = 4 | |||||
| viewer.renderManager.renderer.shadowMap.type = BasicShadowMap | viewer.renderManager.renderer.shadowMap.type = BasicShadowMap | ||||
| light.distance = 5 | light.distance = 5 | ||||
| light.decay = 0.5 | light.decay = 0.5 | ||||
| light.castShadow = true | light.castShadow = true | ||||
| light.shadow.mapSize.setScalar(1024) | |||||
| light.shadow.camera.near = 0.1 | |||||
| light.shadow.camera.far = 10 | |||||
| light.shadow.camera.aspect = 1 | |||||
| light.shadow.camera.fov = 45 | |||||
| light.shadowMapSize.setScalar(1024) | |||||
| light.shadowNear = 0.1 | |||||
| light.shadowFar = 10 | |||||
| light.shadowAspect = 1 | |||||
| light.shadowFov = 45 | |||||
| viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | ||||
| light.position.set(2, 2, 2) | light.position.set(2, 2, 2) | ||||
| light.lookAt(0, 0, 0) | light.lookAt(0, 0, 0) | ||||
| light.castShadow = true | light.castShadow = true | ||||
| light.shadow.mapSize.setScalar(1024) | |||||
| light.shadow.camera.near = 0.1 | |||||
| light.shadow.camera.far = 10 | |||||
| light.shadow.camera.top = 2 | |||||
| light.shadow.camera.bottom = -2 | |||||
| light.shadow.camera.left = -2 | |||||
| light.shadow.camera.right = 2 | |||||
| light.shadowMapSize.setScalar(1024) | |||||
| light.shadowNear = 0.1 | |||||
| light.shadowFar = 10 | |||||
| light.shadowFrustum = 4 | |||||
| viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | viewer.renderManager.renderer.shadowMap.type = PCFSoftShadowMap | ||||
| light.lookAt(-25, 0, 0) | light.lookAt(-25, 0, 0) | ||||
| light.intensity = 30 | light.intensity = 30 | ||||
| light.castShadow = true | light.castShadow = true | ||||
| light.shadow.camera.left = -25 | |||||
| light.shadow.camera.right = 25 | |||||
| light.shadow.camera.top = 25 | |||||
| light.shadow.camera.bottom = -25 | |||||
| light.shadow.mapSize.set(1024, 1024) | |||||
| // todo add to DirectionalLight | |||||
| light.uiConfig.children!.push({ | |||||
| type: 'vec2', | |||||
| label: 'Shadow Map Size', | |||||
| property: [light?.shadow, 'mapSize'], | |||||
| onChange: ()=>{ | |||||
| light.shadow.map?.dispose() | |||||
| light.shadow.mapPass?.dispose() | |||||
| light.shadow.map = null as any | |||||
| light.shadow.mapPass = null as any | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'slider', | |||||
| bounds: [-0.001, 0.001], | |||||
| stepSize: 0.00002, | |||||
| label: 'Shadow Bias', | |||||
| property: [light?.shadow, 'bias'], | |||||
| onChange: (e)=>light.setDirty(e), | |||||
| }, | |||||
| { | |||||
| type: 'slider', | |||||
| bounds: [-0.1, 0.1], | |||||
| stepSize: 0.005, | |||||
| label: 'Shadow Normal Bias', | |||||
| property: [light?.shadow, 'normalBias'], | |||||
| onChange: (e)=>light.setDirty(e), | |||||
| }, | |||||
| { | |||||
| type: 'slider', | |||||
| bounds: [0, 5], | |||||
| label: 'Shadow radius', | |||||
| property: [light?.shadow, 'radius'], | |||||
| onChange: (e)=>light.setDirty(e), | |||||
| }, | |||||
| { | |||||
| type: 'slider', | |||||
| bounds: [0.1, 50], | |||||
| label: 'Shadow frustum', | |||||
| // property: [light.shadow, 'radius'], | |||||
| getValue: ()=>{ | |||||
| return light.shadow.camera.right * 2 | |||||
| }, | |||||
| setValue: (v: number)=>{ | |||||
| light.shadow.camera.left = -v / 2 | |||||
| light.shadow.camera.right = v / 2 | |||||
| light.shadow.camera.top = v / 2 | |||||
| light.shadow.camera.bottom = -v / 2 | |||||
| }, | |||||
| onChange: (e)=>light.setDirty(e), | |||||
| }) | |||||
| light.shadowFrustum = 50 | |||||
| light.shadowMapSize.set(1024, 1024) | |||||
| ui.appendChild(light.uiConfig) | ui.appendChild(light.uiConfig) | ||||
| } | } |
| PhysicalMaterial, | PhysicalMaterial, | ||||
| UnlitLineMaterial, | UnlitLineMaterial, | ||||
| UnlitMaterial, | UnlitMaterial, | ||||
| ObjectShaderMaterial, | |||||
| } from '../core' | } from '../core' | ||||
| import {downloadFile} from 'ts-browser-helpers' | import {downloadFile} from 'ts-browser-helpers' | ||||
| import {MaterialExtension} from '../materials' | import {MaterialExtension} from '../materials' | ||||
| UnlitLineMaterial.MaterialTemplate, | UnlitLineMaterial.MaterialTemplate, | ||||
| LineMaterial2.MaterialTemplate, | LineMaterial2.MaterialTemplate, | ||||
| LegacyPhongMaterial.MaterialTemplate, | LegacyPhongMaterial.MaterialTemplate, | ||||
| ObjectShaderMaterial.MaterialTemplate, | |||||
| ] | ] | ||||
| private _materials: IMaterial[] = [] | private _materials: IMaterial[] = [] |
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| import {Color, ColorRepresentation, DirectionalLight, DirectionalLightShadow, Euler, Vector3} from 'three' | |||||
| import {Color, ColorRepresentation, DirectionalLight, DirectionalLightShadow, Euler, Vector2, Vector3} from 'three' | |||||
| import {ILight, ILightEvent, ILightEventTypes} from './ILight' | import {ILight, ILightEvent, ILightEventTypes} from './ILight' | ||||
| import {iLightCommons} from '../object/iLightCommons' | import {iLightCommons} from '../object/iLightCommons' | ||||
| import {IObject3D} from '../IObject' | import {IObject3D} from '../IObject' | ||||
| import {uiColor, UiObjectConfig, uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {onChange3} from 'ts-browser-helpers' | |||||
| import {uiColor, uiNumber, UiObjectConfig, uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {onChange2, onChange3} from 'ts-browser-helpers' | |||||
| import {bindToValue} from '../../three' | |||||
| // todo: add LightShadow uiconfig | |||||
| /** | |||||
| * Extension of three.js DirectionalLight with additional properties for serialization and UI | |||||
| * A directional light is a light source that has a position but no dimensions - a single point in space that emits light in a specific direction. | |||||
| * | |||||
| * Note - gltf serialization is handled by {@link GLTFLightExtrasExtension} | |||||
| * | |||||
| * @category Lights | |||||
| */ | |||||
| // todo: add Light section in the readme detailing these ...2 lights | // todo: add Light section in the readme detailing these ...2 lights | ||||
| @uiPanelContainer('Directional Light') | @uiPanelContainer('Directional Light') | ||||
| export class DirectionalLight2< | export class DirectionalLight2< | ||||
| @onChange3('setDirty') | @onChange3('setDirty') | ||||
| declare castShadow: boolean | declare castShadow: boolean | ||||
| @uiVector('Shadow Map Size') | |||||
| @bindToValue({obj: 'shadow', key: 'mapSize', onChange: DirectionalLight2.prototype._mapSizeChanged, onChangeParams: false}) | |||||
| shadowMapSize: Vector2 | |||||
| protected _mapSizeChanged() { | |||||
| this.shadow.map?.dispose() | |||||
| this.shadow.mapPass?.dispose() | |||||
| this.shadow.map = null as any | |||||
| this.shadow.mapPass = null as any | |||||
| this.setDirty({change: 'shadowMapSize'}) | |||||
| } | |||||
| @uiSlider('Shadow Bias', [-0.001, 0.001], 0.00001) | |||||
| @bindToValue({obj: 'shadow', key: 'bias', onChange: 'setDirty'}) | |||||
| shadowBias: number | |||||
| @uiSlider('Shadow Normal Bias', [-0.1, 0.1], 0.005) | |||||
| @bindToValue({obj: 'shadow', key: 'normalBias', onChange: 'setDirty'}) | |||||
| shadowNormalBias: number | |||||
| @uiSlider('Shadow Radius', [0, 5], 0.01) | |||||
| @bindToValue({obj: 'shadow', key: 'radius', onChange: 'setDirty'}) | |||||
| shadowRadius: number | |||||
| @uiSlider('Shadow Frustum', [0.1, 50], 0.01) | |||||
| @onChange2(DirectionalLight2.prototype._shadowFrustumChanged) | |||||
| shadowFrustum: number | |||||
| @uiNumber('Shadow Near') | |||||
| @bindToValue({obj: 'shadow', key: ['camera', 'near'], onChange: DirectionalLight2.prototype._shadowCamUpdate}) | |||||
| shadowNear: number | |||||
| @uiNumber('Shadow Far') | |||||
| @bindToValue({obj: 'shadow', key: ['camera', 'far'], onChange: DirectionalLight2.prototype._shadowCamUpdate}) | |||||
| shadowFar: number | |||||
| protected _shadowFrustumChanged() { | |||||
| const v = this.shadowFrustum | |||||
| this.shadow.camera.left = -v / 2 | |||||
| this.shadow.camera.right = v / 2 | |||||
| this.shadow.camera.top = v / 2 | |||||
| this.shadow.camera.bottom = -v / 2 | |||||
| this.shadow.camera.updateProjectionMatrix() | |||||
| this.setDirty({change: 'shadowFrustum'}) | |||||
| } | |||||
| protected _shadowCamUpdate(change?: string) { | |||||
| this.shadow.camera.updateProjectionMatrix() | |||||
| this.setDirty({change}) | |||||
| } | |||||
| constructor(color?: ColorRepresentation, intensity?: number) { | constructor(color?: ColorRepresentation, intensity?: number) { | ||||
| super(color, intensity) | super(color, intensity) | ||||
| this.target.position.set(0, 0, -1) // because of GLTF spec: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual | this.target.position.set(0, 0, -1) // because of GLTF spec: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual | ||||
| this.add(this.target) // todo: make sure the child isn't exported in gltf | this.add(this.target) // todo: make sure the child isn't exported in gltf | ||||
| iLightCommons.upgradeLight.call(this) | iLightCommons.upgradeLight.call(this) | ||||
| this.shadowFrustum = 10 | |||||
| } | } | ||||
| autoScale() { | autoScale() { | ||||
| console.warn('AutoScale not supported on Lights') | |||||
| console.warn('DirectionalLight2: AutoScale not supported on Lights') | |||||
| return this | return this | ||||
| } | } | ||||
| autoCenter() { | autoCenter() { | ||||
| console.warn('AutoCenter not supported on Lights') | |||||
| console.warn('DirectionalLight2: AutoCenter not supported on Lights') | |||||
| return this | return this | ||||
| } | } | ||||
| return this | return this | ||||
| } | } | ||||
| // region inherited type fixes | // region inherited type fixes | ||||
| // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 | // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 | ||||
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| import {Color, ColorRepresentation, PointLight, PointLightShadow, Vector3} from 'three' | |||||
| import {Color, ColorRepresentation, PointLight, PointLightShadow, Vector2, Vector3} from 'three' | |||||
| import {ILight, ILightEvent} from './ILight' | import {ILight, ILightEvent} from './ILight' | ||||
| import {iLightCommons} from '../object/iLightCommons' | import {iLightCommons} from '../object/iLightCommons' | ||||
| import {IObject3D} from '../IObject' | import {IObject3D} from '../IObject' | ||||
| import {uiColor, uiNumber, UiObjectConfig, uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | import {uiColor, uiNumber, UiObjectConfig, uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | ||||
| import {onChange3} from 'ts-browser-helpers' | import {onChange3} from 'ts-browser-helpers' | ||||
| import {bindToValue} from '../../three' | |||||
| /** | |||||
| * Extension of three.js PointLight with additional properties for serialization and UI | |||||
| * | |||||
| * Note - gltf serialization is handled by {@link GLTFLightExtrasExtension} | |||||
| */ | |||||
| @uiPanelContainer('Point Light') | @uiPanelContainer('Point Light') | ||||
| export class PointLight2 extends PointLight implements ILight<PointLightShadow> { | export class PointLight2 extends PointLight implements ILight<PointLightShadow> { | ||||
| assetType = 'light' as const | assetType = 'light' as const | ||||
| @uiNumber('Decay') | @uiNumber('Decay') | ||||
| @onChange3('setDirty') | @onChange3('setDirty') | ||||
| declare decay: number | declare decay: number | ||||
| @uiNumber('Power') | |||||
| @onChange3('setDirty') | |||||
| declare power: number | |||||
| @uiVector('Position', undefined, undefined, (that: PointLight2)=>({onChange: ()=>that.setDirty()})) | @uiVector('Position', undefined, undefined, (that: PointLight2)=>({onChange: ()=>that.setDirty()})) | ||||
| declare readonly position: Vector3 | declare readonly position: Vector3 | ||||
| @uiToggle('Cast Shadow') | @uiToggle('Cast Shadow') | ||||
| @onChange3('setDirty') | @onChange3('setDirty') | ||||
| declare castShadow: boolean | declare castShadow: boolean | ||||
| @uiVector('Shadow Map Size') | |||||
| @bindToValue({obj: 'shadow', key: 'mapSize', onChange: PointLight2.prototype._mapSizeChanged, onChangeParams: false}) | |||||
| shadowMapSize: Vector2 | |||||
| protected _mapSizeChanged() { | |||||
| this.shadow.map?.dispose() | |||||
| this.shadow.mapPass?.dispose() | |||||
| this.shadow.map = null as any | |||||
| this.shadow.mapPass = null as any | |||||
| this.setDirty({change: 'shadowMapSize'}) | |||||
| } | |||||
| @uiSlider('Shadow Bias', [-0.001, 0.001], 0.00001) | |||||
| @bindToValue({obj: 'shadow', key: 'bias', onChange: 'setDirty'}) | |||||
| shadowBias: number | |||||
| @uiSlider('Shadow Radius', [0, 5], 0.01) | |||||
| @bindToValue({obj: 'shadow', key: 'radius', onChange: 'setDirty'}) | |||||
| shadowRadius: number | |||||
| @uiNumber('Shadow Near') | |||||
| @bindToValue({obj: 'shadow', key: ['camera', 'near'], onChange: PointLight2.prototype._shadowCamUpdate}) | |||||
| shadowNear: number | |||||
| @uiNumber('Shadow Far') | |||||
| @bindToValue({obj: 'shadow', key: ['camera', 'far'], onChange: PointLight2.prototype._shadowCamUpdate}) | |||||
| shadowFar: number | |||||
| @uiNumber('Shadow Aspect') | |||||
| @bindToValue({obj: 'shadow', key: 'aspect', onChange: PointLight2.prototype._shadowCamUpdate}) | |||||
| shadowAspect: number | |||||
| @uiSlider('Shadow FOV', [1, 179], 1) | |||||
| @bindToValue({obj: 'shadow', key: 'fov', onChange: PointLight2.prototype._shadowCamUpdate}) | |||||
| shadowFov: number | |||||
| protected _shadowCamUpdate(change?: string) { | |||||
| this.shadow.camera.updateProjectionMatrix() | |||||
| this.setDirty({change}) | |||||
| } | |||||
| constructor(color?: ColorRepresentation, intensity?: number, distance?: number, decay?: number) { | constructor(color?: ColorRepresentation, intensity?: number, distance?: number, decay?: number) { | ||||
| super(color, intensity, distance, decay) | super(color, intensity, distance, decay) | ||||
| iLightCommons.upgradeLight.call(this) | iLightCommons.upgradeLight.call(this) | ||||
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| import {Color, ColorRepresentation, Euler, SpotLight, SpotLightShadow, Vector3} from 'three' | |||||
| import {Color, ColorRepresentation, Euler, SpotLight, SpotLightShadow, Vector2, Vector3} from 'three' | |||||
| import {ILight, ILightEvent} from './ILight' | import {ILight, ILightEvent} from './ILight' | ||||
| import {iLightCommons} from '../object/iLightCommons' | import {iLightCommons} from '../object/iLightCommons' | ||||
| import {IObject3D} from '../IObject' | import {IObject3D} from '../IObject' | ||||
| import {uiColor, uiInput, UiObjectConfig, uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {uiColor, uiInput, uiNumber, UiObjectConfig, uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {onChange3} from 'ts-browser-helpers' | import {onChange3} from 'ts-browser-helpers' | ||||
| import {bindToValue} from '../../three' | |||||
| /** | |||||
| * Extension of three.js SpotLight with additional properties for serialization and UI | |||||
| * | |||||
| * Note - gltf serialization is handled by {@link GLTFLightExtrasExtension} | |||||
| */ | |||||
| @uiPanelContainer('Spot Light') | @uiPanelContainer('Spot Light') | ||||
| export class SpotLight2 extends SpotLight implements ILight<SpotLightShadow> { | export class SpotLight2 extends SpotLight implements ILight<SpotLightShadow> { | ||||
| assetType = 'light' as const | assetType = 'light' as const | ||||
| @onChange3('setDirty') | @onChange3('setDirty') | ||||
| declare castShadow: boolean | declare castShadow: boolean | ||||
| @uiVector('Shadow Map Size') | |||||
| @bindToValue({obj: 'shadow', key: 'mapSize', onChange: SpotLight2.prototype._mapSizeChanged, onChangeParams: false}) | |||||
| shadowMapSize: Vector2 | |||||
| protected _mapSizeChanged() { | |||||
| this.shadow.map?.dispose() | |||||
| this.shadow.mapPass?.dispose() | |||||
| this.shadow.map = null as any | |||||
| this.shadow.mapPass = null as any | |||||
| this.setDirty({change: 'shadowMapSize'}) | |||||
| } | |||||
| @uiSlider('Shadow Bias', [-0.001, 0.001], 0.00001) | |||||
| @bindToValue({obj: 'shadow', key: 'bias', onChange: 'setDirty'}) | |||||
| shadowBias: number | |||||
| @uiSlider('Shadow Radius', [0, 5], 0.01) | |||||
| @bindToValue({obj: 'shadow', key: 'radius', onChange: 'setDirty'}) | |||||
| shadowRadius: number | |||||
| @uiSlider('Shadow Focus', [0, 1], 0.001) | |||||
| @bindToValue({obj: 'shadow', key: 'focus', onChange: 'setDirty'}) | |||||
| shadowFocus: number | |||||
| @uiNumber('Shadow Near') | |||||
| @bindToValue({obj: 'shadow', key: ['camera', 'near'], onChange: SpotLight2.prototype._shadowCamUpdate}) | |||||
| shadowNear: number | |||||
| @uiNumber('Shadow Far') | |||||
| @bindToValue({obj: 'shadow', key: ['camera', 'far'], onChange: SpotLight2.prototype._shadowCamUpdate}) | |||||
| shadowFar: number | |||||
| @uiNumber('Shadow Aspect') | |||||
| @bindToValue({obj: 'shadow', key: 'aspect', onChange: SpotLight2.prototype._shadowCamUpdate}) | |||||
| shadowAspect: number | |||||
| @uiSlider('Shadow FOV', [1, 179], 1) | |||||
| @bindToValue({obj: 'shadow', key: 'fov', onChange: SpotLight2.prototype._shadowCamUpdate}) | |||||
| shadowFov: number | |||||
| protected _shadowCamUpdate(change?: string) { | |||||
| this.shadow.camera.updateProjectionMatrix() | |||||
| this.setDirty({change}) | |||||
| } | |||||
| constructor(color?: ColorRepresentation, intensity?: number, distance?: number, | constructor(color?: ColorRepresentation, intensity?: number, distance?: number, | ||||
| angle?: number, | angle?: number, | ||||
| penumbra?: number, | penumbra?: number, | ||||
| return this | return this | ||||
| } | } | ||||
| // region inherited type fixes | // region inherited type fixes | ||||
| // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 | // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 | ||||
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ILightEvent) => void | dispatchEvent: (event: ILightEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| dispose: (removeFromParent?: boolean) => void | dispose: (removeFromParent?: boolean) => void | ||||
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ISceneEvent) => void | dispatchEvent: (event: ISceneEvent) => void | ||||
| declare parent: null | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | declare children: IObject3D[] | ||||
| // endregion | // endregion |
| import {iObjectCommons} from './iObjectCommons' | import {iObjectCommons} from './iObjectCommons' | ||||
| export const iLightCommons = { | export const iLightCommons = { | ||||
| setDirty: function(this: ILight, options?: IObjectSetDirtyOptions): void { | |||||
| this.dispatchEvent({bubbleToParent: true, ...options, type: 'lightUpdate', light: this, object: this}) // this sets sceneUpdate in root scene | |||||
| iObjectCommons.setDirty.call(this, options) | |||||
| setDirty: function(this: ILight, options?: IObjectSetDirtyOptions, ...args: any[]): void { | |||||
| if (typeof options === 'string') { // just incase called by decorators | |||||
| options = {change: options} | |||||
| } | |||||
| this.dispatchEvent({bubbleToParent: true, ...options, type: 'lightUpdate', light: this, object: this, args}) // this sets sceneUpdate in root scene | |||||
| iObjectCommons.setDirty.call(this, options, ...args) | |||||
| }, | }, | ||||
| upgradeLight: upgradeLight, | upgradeLight: upgradeLight, | ||||
| refreshUi: iObjectCommons.refreshUi, | refreshUi: iObjectCommons.refreshUi, |
| import {ILight} from '../light/ILight' | import {ILight} from '../light/ILight' | ||||
| export const iObjectCommons = { | export const iObjectCommons = { | ||||
| setDirty: function(this: IObject3D, options?: IObjectSetDirtyOptions): void { | |||||
| this.dispatchEvent({bubbleToParent: true, ...options, type: 'objectUpdate', object: this}) // this sets sceneUpdate in root scene | |||||
| setDirty: function(this: IObject3D, options?: IObjectSetDirtyOptions, ...args: any[]): void { | |||||
| if (typeof options === 'string') { // just incase called by decorators | |||||
| options = {change: options} | |||||
| } | |||||
| this.dispatchEvent({bubbleToParent: true, ...options, type: 'objectUpdate', object: this, args}) // this sets sceneUpdate in root scene | |||||
| if (options?.refreshUi !== false && options?.last !== false) this.refreshUi?.() | if (options?.refreshUi !== false && options?.last !== false) this.refreshUi?.() | ||||
| // console.log('object update') | // console.log('object update') | ||||
| }, | }, |
| import {FnCaller, getOrCall, objectHasOwn, safeSetProperty, ValOrFunc} from 'ts-browser-helpers' | |||||
| import {FnCaller, getOrCall, objectHasOwn, safeSetProperty, ValOrArr, ValOrFunc} from 'ts-browser-helpers' | |||||
| /** | /** | ||||
| * | * | ||||
| * @param processVal - function that processes the value before setting it. | * @param processVal - function that processes the value before setting it. | ||||
| * @param invProcessVal - function that processes the value before returning it. | * @param invProcessVal - function that processes the value before returning it. | ||||
| */ | */ | ||||
| export function bindToValue({obj, key, processVal, invProcessVal, onChange, onChangeParams = true}: {obj?: ValOrFunc<any>, key?: ValOrFunc<string | symbol>, onChange?: ((...args: any[]) => any)|string, processVal?: (newVal: any) => any, invProcessVal?: (val: any) => any, onChangeParams?: boolean}): PropertyDecorator { | |||||
| export function bindToValue({obj, key, processVal, invProcessVal, onChange, onChangeParams = true}: {obj?: ValOrFunc<any>, key?: ValOrFunc<ValOrArr<string | symbol>>, onChange?: ((...args: any[]) => any)|string, processVal?: (newVal: any) => any, invProcessVal?: (val: any) => any, onChangeParams?: boolean}): PropertyDecorator { | |||||
| const cPropKey = !!key | const cPropKey = !!key | ||||
| return (targetPrototype: any, propertyKey: string|symbol, descriptor?: TypedPropertyDescriptor<any>) => { | return (targetPrototype: any, propertyKey: string|symbol, descriptor?: TypedPropertyDescriptor<any>) => { | ||||
| const getTarget = (_this: any)=>{ | const getTarget = (_this: any)=>{ | ||||
| let t = getOrCall(obj) || _this | let t = getOrCall(obj) || _this | ||||
| if (typeof t === 'string') t = _this[t] | if (typeof t === 'string') t = _this[t] | ||||
| const p = cPropKey ? getOrCall(key) || propertyKey : propertyKey | |||||
| return {t, p} | |||||
| let p1 = cPropKey ? getOrCall(key) || propertyKey : propertyKey | |||||
| let p: string|symbol | |||||
| if (Array.isArray(p1)) { | |||||
| while (p1.length > 1 && t && typeof t === 'object') { | |||||
| t = t[p1[0]] | |||||
| p1 = p1.slice(1) | |||||
| } | |||||
| p = p1.length ? p1[0] : propertyKey | |||||
| } else p = p1 | |||||
| return {t, p: p} | |||||
| } | } | ||||
| const prop = { | const prop = { | ||||
| get() { | get() { |