@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
383 lines (282 loc) • 9.33 kB
JavaScript
import { DoubleSide, Mesh, MeshDepthMaterial, RGBADepthPacking, Scene } from "three";
import { ThreeBypassRenderer } from "../../../../render/utils/ThreeBypassRenderer.js";
import { CameraView } from "../../../../render/view/CameraView.js";
import { three_setSceneAutoUpdate } from "../../../../three/three_setSceneAutoUpdate.js";
import { threeUpdateTransform } from "../../../../util/threeUpdateTransform.js";
import { LightType } from "../../LightType.js";
import { ShadowMap } from "../../shadow/ShadowMap.js";
import { LightBinding } from "../LightBinding.js";
import { applyRotation } from "./applyRotation.js";
import { threeEnsureLightObject } from "./threeEnsureLightObject.js";
const shadow_scene = new Scene();
three_setSceneAutoUpdate(shadow_scene, false);
shadow_scene.matrixAutoUpdate = false;
shadow_scene.matrixWorldNeedsUpdate = false;
const __depth_material = new MeshDepthMaterial({
depthPacking: RGBADepthPacking,
side: DoubleSide
});
/**
*
* @param {Mesh} object
* @returns {Material}
* @private
*/
function __getDepthMaterial(object) {
let result;
const customDepthMaterial = object.customDepthMaterial;
if (customDepthMaterial !== undefined) {
result = customDepthMaterial;
} else {
const source_material = object.material;
if (
typeof source_material.alphaTest === "number"
&& source_material.alphaTest > 0
&& source_material.alphaTest < 1
&& source_material.map !== null
&& source_material.map !== undefined
) {
// alpha-tested
result = new MeshDepthMaterial({
depthPacking: RGBADepthPacking,
side: DoubleSide,
map: source_material.map,
alphaTest: source_material.alphaTest
});
} else {
// generic
result = __depth_material;
}
}
return result;
}
/**
*
* @param {THREE.Object3D} object3D
* @return {boolean}
*/
function scene_object_filter(object3D) {
if (object3D.castShadow === false) {
return false;
}
if (object3D.visible === false) {
return false;
}
if (object3D.isMesh) {
/**
* @type {THREE.Material|null|undefined}
*/
const material = (object3D.customDepthMaterial ? object3D.customDepthMaterial : object3D.material);
if (material && material.visible === false) {
//material's main
return false;
}
}
return true;
}
/**
*
* @param {LightShadow} shadow
* @param {number} resolution
*/
export function three_set_shadow_resolution(shadow, resolution) {
const mapSize = shadow.mapSize;
if (mapSize.width !== resolution || mapSize.height !== resolution) {
mapSize.width = mapSize.height = resolution;
//destroy old map
if (shadow.map !== null) {
shadow.map.dispose(); // important
shadow.map = null;
}
}
}
export class ThreeLightBinding extends LightBinding {
/**
*
* @type {LightSystem|null}
* @private
*/
__system = null;
/**
*
* @type {ShadowMap|null}
* @private
*/
__shadow_map = null;
/**
*
* @type {WeakMap<THREE.Object3D, THREE.Object3D>}
* @private
*/
__object_map = new WeakMap();
link(ctx) {
this.__system = ctx.system;
/**
* @type {ThreeLightCache}
*/
const cache = this.__system.__three_light_cache;
const c_light = this.__c_light;
threeEnsureLightObject(c_light, cache);
super.link(ctx);
}
unlink(ctx) {
/**
* @type {ThreeLightCache}
*/
const cache = this.__system.__three_light_cache;
cache.release(this.__three_getLight());
// clear bound light
this.__c_light.__threeObject = null;
this.__shadowmap_unlink();
super.unlink(ctx);
}
/**
*
* @return {THREE.Light}
* @private
*/
__three_getLight() {
return this.__c_light.__threeObject;
}
__apply_intensity() {
this.__three_getLight().intensity = this.__c_light.intensity.getValue();
}
__apply_color() {
const c = this.__c_light.color;
this.__three_getLight().color.setRGB(c.r, c.g, c.b);
}
__apply_distance(v) {
this.__three_getLight().distance = this.scaled_distance;
}
__apply_angle() {
this.__three_getLight().angle = this.__c_light.angle.getValue();
}
__apply_penumbra() {
this.__three_getLight().penumbra = this.__c_light.penumbra.getValue();
}
__shadowmap_link() {
this.__shadow_map = new ShadowMap();
const cameraView = new CameraView();
cameraView.name = 'Shadow Map';
this.__shadow_map.views.push(cameraView)
/**
*
* @type {LightSystem}
*/
const system = this.__system;
const shadows = system.__shadows;
shadows.add(this.__shadow_map);
//
cameraView.on.preVisibilityBuild.add(this.__shadowmap_prepare_view, this);
cameraView.on.postVisibilityBuild.add(this.__shadowmap_render, this);
}
__shadowmap_unlink() {
if (this.__shadow_map === null) {
return;
}
/**
*
* @type {LightSystem}
*/
const system = this.__system;
const shadows = system.__shadows;
shadows.remove(this.__shadow_map);
this.__shadow_map = null;
}
__shadowmap_prepare_view() {
const cameraView = this.__shadow_map.views[0];
/**
* @type {THREE.Light}
*/
const light = this.__three_getLight();
/**
*
* @type {LightShadow}
*/
const shadow = light.shadow;
/**
* @type {THREE.Camera}
*/
const camera = shadow.camera;
// update camera matrices
camera.updateMatrix();
camera.updateMatrixWorld();
cameraView.set_from_camera(camera);
}
__shadowmap_render() {
/**
*
* @type {CameraView}
*/
const cameraView = this.__shadow_map.views[0];
// signal that we're about to render this view
cameraView.on.preRender.send1(cameraView);
/**
*
* @type {LightSystem}
*/
const system = this.__system;
/**
*
* @type {GraphicsEngine}
*/
const graphics = system.__graphics;
const threeLight = this.__three_getLight();
const shadow = threeLight.shadow;
// prepare shadow scene
const visible_objects = cameraView.visible_objects;
const visible_object_count = visible_objects.size;
ThreeBypassRenderer.INSTANCE.build_scene({
scene: shadow_scene,
input: visible_objects.elements,
input_size: visible_object_count,
material_extractor: __getDepthMaterial,
object_filter: scene_object_filter
})
// render map out
graphics.shadowmap_renderer.update(graphics.renderer, shadow, shadow_scene, threeLight);
}
__apply_castShadow() {
const v = this.__c_light.castShadow.getValue();
const light = this.__three_getLight();
light.castShadow = v;
if (v && this.__shadow_map === null) {
this.__shadowmap_link();
} else if (!v && this.__shadow_map !== null) {
this.__shadowmap_unlink();
}
}
__apply_position() {
const transform = this.__c_transform;
const p = transform.position;
const threeLight = this.__three_getLight();
threeLight.position.set(p.x, p.y, p.z);
const cLight = this.__c_light;
const lightType = cLight.type.getValue();
if (lightType === LightType.SPOT || lightType === LightType.DIRECTION) {
applyRotation(cLight, transform.rotation);
} else {
// just update transform
threeUpdateTransform(threeLight);
}
}
__apply_rotation() {
applyRotation(this.__c_light, this.__c_transform.rotation);
}
__apply_scale() {
this.__three_getLight().distance = this.scaled_distance;
}
applySettings(settings) {
const light = this.__three_getLight();
const shadow = light.shadow;
if (shadow !== undefined) {
// disable automatic shadowmap rendering in three.js
shadow.autoUpdate = false;
shadow.camera.matrixAutoUpdate = false;
const resolution = parseInt(settings.shadowResolution);
shadow.radius = 1;
shadow.blurSamples = 8;
three_set_shadow_resolution(shadow, resolution);
}
}
}