@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
351 lines (328 loc) • 10.5 kB
text/typescript
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);
}
}
}
}