UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

351 lines (328 loc) 10.5 kB
import {ParamConfig} from '../../engine/nodes/utils/params/ParamsConfig'; import {Constructor, Number2, Number3} from '../../types/GlobalTypes'; // import {ColorConversion} from '../Color'; import {DefaultOperationParams} from '../operations/_Base'; import {Object3D, Group, Vector2, Color, SpotLight} from 'three'; import {VolumetricSpotLight} from './spotlight/VolumetricSpotLight'; import {isBooleanTrue} from '../Type'; import {CoreSpotLightHelper, CoreSpotLightHelperParams} from './spotlight/CoreSpotLightHelper'; import {CoreSceneObjectsFactory, GeneratorName} from '../CoreSceneObjectsFactory'; import {TypedNodePathParamValue} from '../Walker'; import {NodeContext} from '../../engine/poly/NodeContext'; export interface SpotLightParams extends DefaultOperationParams { color: Color; intensity: number; angle: number; penumbra: number; decay: number; distance: number; // showHelper: boolean; helperSize: number; // tmap: boolean; map: TypedNodePathParamValue; // name: string; // castShadow: boolean; shadowAutoUpdate: boolean; shadowUpdateOnNextRender: boolean; shadowRes: Vector2; // shadowSize: Vector2; shadowBias: number; shadowNear: number; shadowFar: number; shadowRadius: number; // // debugShadow is temporarily removed, // as it only works for obj lights if toggled after the shadow has been created // it does not work for sop lights at all. // maybe there is a better way to generate those via actor nodes? // debugShadow: boolean; // tvolumetric: boolean; volAttenuation: number; volAnglePower: number; // raymarchingPenumbra: number; raymarchingShadowBiasAngle: number; raymarchingShadowBiasDistance: number; } export const DEFAULT_SPOT_LIGHT_PARAMS: SpotLightParams = { color: new Color(1, 1, 1), intensity: 2, angle: 45, penumbra: 0.1, decay: 2, distance: 100, // showHelper: false, helperSize: 1, // tmap: false, map: new TypedNodePathParamValue(''), // name: 'pointLight', // castShadow: false, shadowAutoUpdate: true, shadowUpdateOnNextRender: false, shadowRes: new Vector2(1024, 1024), // used to be 256 for performance, but is now higher to start with a better look dev // shadowSize: new Vector2(2, 2), shadowBias: 0.0001, shadowNear: 0.1, shadowFar: 100, shadowRadius: 0, // debugShadow: false, // tvolumetric: false, volAttenuation: 5, volAnglePower: 10, // raymarchingPenumbra: 0, // keep as 0 by default since it's more performant raymarchingShadowBiasAngle: 0.01, raymarchingShadowBiasDistance: 0.1, }; const DEFAULT = DEFAULT_SPOT_LIGHT_PARAMS; export function SpotLightParamConfig<TBase extends Constructor>(Base: TBase) { return class Mixin extends Base { light = ParamConfig.FOLDER(); /** @param light color */ color = ParamConfig.COLOR(DEFAULT.color.toArray() as Number3, { // conversion: ColorConversion.SRGB_TO_LINEAR, }); /** @param light intensity */ intensity = ParamConfig.FLOAT(DEFAULT.intensity, { range: [0, 10], rangeLocked: [true, false], }); /** @param angle */ angle = ParamConfig.FLOAT(DEFAULT.angle, { range: [0, 180], rangeLocked: [true, false], }); /** @param penumbra */ penumbra = ParamConfig.FLOAT(DEFAULT.penumbra, { range: [0, 1], rangeLocked: [true, true], }); /** @param decay */ decay = ParamConfig.FLOAT(DEFAULT.decay, { range: [0, 10], rangeLocked: [true, false], }); /** @param distance */ distance = ParamConfig.FLOAT(DEFAULT.distance, { range: [0, 100], rangeLocked: [true, false], }); // helper /** @param toggle on to show helper */ showHelper = ParamConfig.BOOLEAN(DEFAULT.showHelper); /** @param helper size */ helperSize = ParamConfig.FLOAT(DEFAULT.helperSize, {visibleIf: {showHelper: 1}}); // helper /** @param toggle on to show helper */ tmap = ParamConfig.BOOLEAN(DEFAULT.tmap); /** @param helper size */ map = ParamConfig.NODE_PATH('', { nodeSelection: { context: NodeContext.COP, }, dependentOnFoundNode: false, visibleIf: {tmap: 1}, }); // /** @param light name */ name = ParamConfig.STRING('`$OS`'); // shadows shadow = ParamConfig.FOLDER(); /** @param toggle on to cast shadows */ castShadow = ParamConfig.BOOLEAN(DEFAULT.castShadow); /** @param toggle off if the shadows do not need to be regenerated */ shadowAutoUpdate = ParamConfig.BOOLEAN(DEFAULT.shadowAutoUpdate, { visibleIf: {castShadow: 1}, }); /** @param press button to update the shadows on next render */ shadowUpdateOnNextRender = ParamConfig.BOOLEAN(DEFAULT.shadowUpdateOnNextRender, { visibleIf: {castShadow: 1, shadowAutoUpdate: 0}, }); /** @param shadows res */ shadowRes = ParamConfig.VECTOR2(DEFAULT.shadowRes.toArray() as Number2, { visibleIf: {castShadow: 1}, }); /** @param shadows bias */ shadowBias = ParamConfig.FLOAT(DEFAULT.shadowBias, { visibleIf: {castShadow: 1}, range: [-0.01, 0.01], rangeLocked: [false, false], }); /** @param shadows near */ shadowNear = ParamConfig.FLOAT(DEFAULT.shadowNear, { visibleIf: {castShadow: 1}, range: [0, 100], rangeLocked: [true, false], }); /** @param shadows far */ shadowFar = ParamConfig.FLOAT(DEFAULT.shadowFar, { visibleIf: {castShadow: 1}, range: [0, 100], rangeLocked: [true, false], }); /** @param shadows radius. This only has effect when setting the ROP/WebGLRenderer's shadowMapType to VSM */ shadowRadius = ParamConfig.FLOAT(DEFAULT.shadowRadius, { visibleIf: {castShadow: 1}, range: [0, 10], rangeLocked: [true, false], }); /** @param display shadow on a plane behind the light */ // debugShadow = ParamConfig.BOOLEAN(DEFAULT.debugShadow, { // visibleIf: {castShadow: 1}, // }); // shadows volumetric = ParamConfig.FOLDER(); /** @param toggle on to add a volumetric effect to the spotlight */ tvolumetric = ParamConfig.BOOLEAN(DEFAULT.tvolumetric); /** @param volumetric attenuation */ volAttenuation = ParamConfig.FLOAT(DEFAULT.volAttenuation, { range: [0, 10], rangeLocked: [true, false], }); /** @param volumetric angle power */ volAnglePower = ParamConfig.FLOAT(DEFAULT.volAnglePower, { range: [0, 20], rangeLocked: [true, false], }); // raymarching raymarching = ParamConfig.FOLDER(); /** @param this affects the shadows cast inside raymarchingBuilder materials */ raymarchingPenumbra = ParamConfig.FLOAT(DEFAULT.raymarchingPenumbra); /** @param shadow bias */ raymarchingShadowBiasAngle = ParamConfig.FLOAT(DEFAULT.raymarchingShadowBiasAngle, { range: [0, 1], rangeLocked: [true, false], }); /** @param shadow bias */ raymarchingShadowBiasDistance = ParamConfig.FLOAT(DEFAULT.raymarchingShadowBiasDistance, { range: [0, 1], rangeLocked: [true, false], }); }; } export interface SpotLightContainerParams extends CoreSpotLightHelperParams { showHelper: boolean; tvolumetric: boolean; volAnglePower: number; volAttenuation: number; } export class SpotLightContainer extends Group { private _light: SpotLight; private _target: Object3D = new Object3D(); public override matrixAutoUpdate = false; public params: SpotLightContainerParams = { showHelper: false, helperSize: 1, tvolumetric: false, volAnglePower: 1, volAttenuation: 1, }; constructor(params: Partial<SpotLightContainerParams>, public readonly nodeName: string) { super(); if (params.showHelper != null) { this.params.showHelper = params.showHelper; } if (params.tvolumetric != null) { this.params.tvolumetric = params.tvolumetric; } if (params.volAnglePower != null) { this.params.volAnglePower = params.volAnglePower; } if (params.volAttenuation != null) { this.params.volAttenuation = params.volAttenuation; } this._light = CoreSceneObjectsFactory.generator(GeneratorName.SPOT_LIGHT)(); CoreSceneObjectsFactory.generator(GeneratorName.SPOT_LIGHT_UPDATE)({ spotLight: this._light, textureName: 'IES_PROFILE_LM_63_1995', }); this._target.copy(this._light.target, false); this._light.target = this._target; // set light pos to 0,0,1 // in order to have it face z axis // update: set z to 0.01 so that the spotlight volume can appear to be in the same position // update: move target to 0,0,-1 so that the spotlight can be used with lookAt this._light.position.set(0, 0, 0.01); this._light.target.position.set(0, 0, -1); this._light.updateMatrix(); this._target.updateMatrix(); this._light.matrixAutoUpdate = false; this._target.matrixAutoUpdate = false; this.name = `SpotLightContainer_${this.nodeName}`; this._light.name = `SpotLight_${this.nodeName}`; this._target.name = `SpotLightDefaultTarget_${this.nodeName}`; this.add(this._light); this.add(this._target); this.updateHelper(); } updateParams(params: Partial<SpotLightContainerParams>) { if (params.showHelper != null) { this.params.showHelper = params.showHelper; } if (params.helperSize != null) { this.params.helperSize = params.helperSize; } if (params.tvolumetric != null) { this.params.tvolumetric = params.tvolumetric; } if (params.volAnglePower != null) { this.params.volAnglePower = params.volAnglePower; } if (params.volAttenuation != null) { this.params.volAttenuation = params.volAttenuation; } } light() { return this._light; } override copy(source: this, recursive?: boolean): this { const srcLight = source.light(); this._light.copy(srcLight); super.copy(source, false); this.updateParams(source.params); this.updateHelper(); this._light.target = this._target; if (recursive) { this.updateVolumetric(); } return this as this; } override clone(recursive?: boolean): this { const cloned = new SpotLightContainer(this.params, this.nodeName); cloned.copy(this); return cloned as this; } private __helper__: CoreSpotLightHelper | undefined; updateHelper() { if (isBooleanTrue(this.params.showHelper)) { this.__helper__ = this.__helper__ || new CoreSpotLightHelper(this); this.add(this.__helper__.object); this.__helper__.update({helperSize: this.params.helperSize}); } else { if (this.__helper__) { this.remove(this.__helper__.object); } } } private __volumetric__: VolumetricSpotLight | undefined; updateVolumetric() { if (isBooleanTrue(this.params.tvolumetric)) { this.__volumetric__ = this.__volumetric__ || new VolumetricSpotLight(this); this.__volumetric__.update(this.params); } else { if (this.__volumetric__) { this.__volumetric__.update(this.params); } } } }