Files
GDevelop/Extensions/3D/DirectionalLight.ts

282 lines
10 KiB
TypeScript

namespace gdjs {
interface DirectionalLightFilterNetworkSyncData {
i: number;
c: number;
e: number;
r: number;
t: string;
}
const shadowHelper = false;
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::DirectionalLight',
new (class implements gdjs.PixiFiltersTools.FilterCreator {
makeFilter(
target: EffectsTarget,
effectData: EffectData
): gdjs.PixiFiltersTools.Filter {
if (typeof THREE === 'undefined') {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
private _top: string = 'Z+';
private _elevation: float = 45;
private _rotation: float = 0;
private _shadowMapSize: float = 1024;
private _minimumShadowBias: float = 0;
private _distanceFromCamera: float = 1500;
private _frustumSize: float = 4000;
private _isEnabled: boolean = false;
private _light: THREE.DirectionalLight;
private _shadowMapDirty = true;
private _shadowCameraDirty = true;
private _shadowCameraHelper: THREE.CameraHelper | null;
constructor() {
this._light = new THREE.DirectionalLight();
if (shadowHelper) {
this._shadowCameraHelper = new THREE.CameraHelper(
this._light.shadow.camera
);
} else {
this._shadowCameraHelper = null;
}
this._light.shadow.camera.updateProjectionMatrix();
}
private _updateShadowCamera(): void {
if (!this._shadowCameraDirty) {
return;
}
this._shadowCameraDirty = false;
this._light.shadow.camera.near = 1;
this._light.shadow.camera.far = this._distanceFromCamera + 10000;
this._light.shadow.camera.right = this._frustumSize / 2;
this._light.shadow.camera.left = -this._frustumSize / 2;
this._light.shadow.camera.top = this._frustumSize / 2;
this._light.shadow.camera.bottom = -this._frustumSize / 2;
}
private _updateShadowMapSize(): void {
if (!this._shadowMapDirty) {
return;
}
this._shadowMapDirty = false;
this._light.shadow.mapSize.set(
this._shadowMapSize,
this._shadowMapSize
);
// Force the recreation of the shadow map texture:
this._light.shadow.map?.dispose();
this._light.shadow.map = null;
this._light.shadow.needsUpdate = true;
}
isEnabled(target: EffectsTarget): boolean {
return this._isEnabled;
}
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
if (this._isEnabled === enabled) {
return true;
}
if (enabled) {
return this.applyEffect(target);
} else {
return this.removeEffect(target);
}
}
applyEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
scene.add(this._light);
scene.add(this._light.target);
if (this._shadowCameraHelper) {
scene.add(this._shadowCameraHelper);
}
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
scene.remove(this._light);
scene.remove(this._light.target);
if (this._shadowCameraHelper) {
scene.remove(this._shadowCameraHelper);
}
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {
// Apply any update to the camera or shadow map size.
this._updateShadowCamera();
this._updateShadowMapSize();
// Avoid shadow acne due to depth buffer precision.
const biasMultiplier =
this._shadowMapSize < 1024
? 2
: this._shadowMapSize < 2048
? 1.25
: 1;
this._light.shadow.bias = -this._minimumShadowBias * biasMultiplier;
// Apply update to the light position and its target.
// By doing this, the shadows are "following" the GDevelop camera.
if (!target.getRuntimeLayer) {
return;
}
const layer = target.getRuntimeLayer();
const x = layer.getCameraX();
const y = layer.getCameraY();
const z = layer.getCameraZ(layer.getInitialCamera3DFieldOfView());
const roundedX = Math.floor(x / 100) * 100;
const roundedY = Math.floor(y / 100) * 100;
const roundedZ = Math.floor(z / 100) * 100;
if (this._top === 'Y-') {
const posLightX =
roundedX +
this._distanceFromCamera *
Math.cos(gdjs.toRad(-this._rotation + 90)) *
Math.cos(gdjs.toRad(this._elevation));
const posLightY =
roundedY -
this._distanceFromCamera *
Math.sin(gdjs.toRad(this._elevation));
const posLightZ =
roundedZ +
this._distanceFromCamera *
Math.sin(gdjs.toRad(-this._rotation + 90)) *
Math.cos(gdjs.toRad(this._elevation));
this._light.position.set(posLightX, posLightY, posLightZ);
this._light.target.position.set(roundedX, roundedY, roundedZ);
} else {
const posLightX =
roundedX +
this._distanceFromCamera *
Math.cos(gdjs.toRad(this._rotation)) *
Math.cos(gdjs.toRad(this._elevation));
const posLightY =
roundedY +
this._distanceFromCamera *
Math.sin(gdjs.toRad(this._rotation)) *
Math.cos(gdjs.toRad(this._elevation));
const posLightZ =
roundedZ +
this._distanceFromCamera *
Math.sin(gdjs.toRad(this._elevation));
this._light.position.set(posLightX, posLightY, posLightZ);
this._light.target.position.set(roundedX, roundedY, roundedZ);
}
}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'intensity') {
this._light.intensity = value;
} else if (parameterName === 'elevation') {
this._elevation = value;
} else if (parameterName === 'rotation') {
this._rotation = value;
} else if (parameterName === 'distanceFromCamera') {
this._distanceFromCamera = value;
} else if (parameterName === 'frustumSize') {
this._frustumSize = value;
} else if (parameterName === 'minimumShadowBias') {
this._minimumShadowBias = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'intensity') {
return this._light.intensity;
} else if (parameterName === 'elevation') {
return this._elevation;
} else if (parameterName === 'rotation') {
return this._rotation;
} else if (parameterName === 'distanceFromCamera') {
return this._distanceFromCamera;
} else if (parameterName === 'frustumSize') {
return this._frustumSize;
} else if (parameterName === 'minimumShadowBias') {
return this._minimumShadowBias;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'color') {
this._light.color = new THREE.Color(
gdjs.rgbOrHexStringToNumber(value)
);
}
if (parameterName === 'top') {
this._top = value;
}
if (parameterName === 'shadowQuality') {
if (value === 'low' && this._shadowMapSize !== 512) {
this._shadowMapSize = 512;
this._shadowMapDirty = true;
}
if (value === 'medium' && this._shadowMapSize !== 1024) {
this._shadowMapSize = 1024;
this._shadowMapDirty = true;
}
if (value === 'high' && this._shadowMapSize !== 2048) {
this._shadowMapSize = 2048;
this._shadowMapDirty = true;
}
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this._light.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this._light.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {
if (parameterName === 'isCastingShadow') {
this._light.castShadow = value;
}
}
getNetworkSyncData(): DirectionalLightFilterNetworkSyncData {
return {
i: this._light.intensity,
c: this._light.color.getHex(),
e: this._elevation,
r: this._rotation,
t: this._top,
};
}
updateFromNetworkSyncData(syncData: any): void {
this._light.intensity = syncData.i;
this._light.color.setHex(syncData.c);
this._elevation = syncData.e;
this._rotation = syncData.r;
this._top = syncData.t;
}
})();
}
})()
);
}