UNPKG

@polygonjs/polygonjs

Version:

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

280 lines (279 loc) 9.52 kB
"use strict"; import { ParamConfig } from "../../engine/nodes/utils/params/ParamsConfig"; import { Object3D, Group, Vector2, Color } from "three"; import { VolumetricSpotLight } from "./spotlight/VolumetricSpotLight"; import { isBooleanTrue } from "../Type"; import { CoreSpotLightHelper } from "./spotlight/CoreSpotLightHelper"; import { CoreSceneObjectsFactory, GeneratorName } from "../CoreSceneObjectsFactory"; import { TypedNodePathParamValue } from "../Walker"; import { NodeContext } from "../../engine/poly/NodeContext"; export const DEFAULT_SPOT_LIGHT_PARAMS = { 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: 1e-4, 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(Base) { return class Mixin extends Base { constructor() { super(...arguments); this.light = ParamConfig.FOLDER(); /** @param light color */ this.color = ParamConfig.COLOR(DEFAULT.color.toArray(), { // conversion: ColorConversion.SRGB_TO_LINEAR, }); /** @param light intensity */ this.intensity = ParamConfig.FLOAT(DEFAULT.intensity, { range: [0, 10], rangeLocked: [true, false] }); /** @param angle */ this.angle = ParamConfig.FLOAT(DEFAULT.angle, { range: [0, 180], rangeLocked: [true, false] }); /** @param penumbra */ this.penumbra = ParamConfig.FLOAT(DEFAULT.penumbra, { range: [0, 1], rangeLocked: [true, true] }); /** @param decay */ this.decay = ParamConfig.FLOAT(DEFAULT.decay, { range: [0, 10], rangeLocked: [true, false] }); /** @param distance */ this.distance = ParamConfig.FLOAT(DEFAULT.distance, { range: [0, 100], rangeLocked: [true, false] }); // helper /** @param toggle on to show helper */ this.showHelper = ParamConfig.BOOLEAN(DEFAULT.showHelper); /** @param helper size */ this.helperSize = ParamConfig.FLOAT(DEFAULT.helperSize, { visibleIf: { showHelper: 1 } }); // helper /** @param toggle on to show helper */ this.tmap = ParamConfig.BOOLEAN(DEFAULT.tmap); /** @param helper size */ this.map = ParamConfig.NODE_PATH("", { nodeSelection: { context: NodeContext.COP }, dependentOnFoundNode: false, visibleIf: { tmap: 1 } }); // /** @param light name */ this.name = ParamConfig.STRING("`$OS`"); // shadows this.shadow = ParamConfig.FOLDER(); /** @param toggle on to cast shadows */ this.castShadow = ParamConfig.BOOLEAN(DEFAULT.castShadow); /** @param toggle off if the shadows do not need to be regenerated */ this.shadowAutoUpdate = ParamConfig.BOOLEAN(DEFAULT.shadowAutoUpdate, { visibleIf: { castShadow: 1 } }); /** @param press button to update the shadows on next render */ this.shadowUpdateOnNextRender = ParamConfig.BOOLEAN(DEFAULT.shadowUpdateOnNextRender, { visibleIf: { castShadow: 1, shadowAutoUpdate: 0 } }); /** @param shadows res */ this.shadowRes = ParamConfig.VECTOR2(DEFAULT.shadowRes.toArray(), { visibleIf: { castShadow: 1 } }); /** @param shadows bias */ this.shadowBias = ParamConfig.FLOAT(DEFAULT.shadowBias, { visibleIf: { castShadow: 1 }, range: [-0.01, 0.01], rangeLocked: [false, false] }); /** @param shadows near */ this.shadowNear = ParamConfig.FLOAT(DEFAULT.shadowNear, { visibleIf: { castShadow: 1 }, range: [0, 100], rangeLocked: [true, false] }); /** @param shadows far */ this.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 */ this.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 this.volumetric = ParamConfig.FOLDER(); /** @param toggle on to add a volumetric effect to the spotlight */ this.tvolumetric = ParamConfig.BOOLEAN(DEFAULT.tvolumetric); /** @param volumetric attenuation */ this.volAttenuation = ParamConfig.FLOAT(DEFAULT.volAttenuation, { range: [0, 10], rangeLocked: [true, false] }); /** @param volumetric angle power */ this.volAnglePower = ParamConfig.FLOAT(DEFAULT.volAnglePower, { range: [0, 20], rangeLocked: [true, false] }); // raymarching this.raymarching = ParamConfig.FOLDER(); /** @param this affects the shadows cast inside raymarchingBuilder materials */ this.raymarchingPenumbra = ParamConfig.FLOAT(DEFAULT.raymarchingPenumbra); /** @param shadow bias */ this.raymarchingShadowBiasAngle = ParamConfig.FLOAT(DEFAULT.raymarchingShadowBiasAngle, { range: [0, 1], rangeLocked: [true, false] }); /** @param shadow bias */ this.raymarchingShadowBiasDistance = ParamConfig.FLOAT(DEFAULT.raymarchingShadowBiasDistance, { range: [0, 1], rangeLocked: [true, false] }); } }; } export class SpotLightContainer extends Group { constructor(params, nodeName) { super(); this.nodeName = nodeName; this._target = new Object3D(); this.matrixAutoUpdate = false; this.params = { showHelper: false, helperSize: 1, tvolumetric: false, volAnglePower: 1, volAttenuation: 1 }; 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; 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) { 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; } copy(source, recursive) { 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; } clone(recursive) { const cloned = new SpotLightContainer(this.params, this.nodeName); cloned.copy(this); return cloned; } 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); } } } 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); } } } }