UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

383 lines (282 loc) • 9.33 kB
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); } } }