UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

568 lines (449 loc) • 18.6 kB
import { GUI } from "dat.gui"; import { ClampToEdgeWrapping, DataTexture, LinearFilter, Matrix4, MeshBasicMaterial, MeshStandardMaterial, NearestFilter, PlaneBufferGeometry, RepeatWrapping, RGBAFormat, TextureLoader, UnsignedByteType } from "three"; import '../../../../../../../css/game.scss'; import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js"; import { array_copy } from "../../../core/collection/array/array_copy.js"; import { is_typed_array_equals } from "../../../core/collection/array/typed/is_typed_array_equals.js"; import { Color } from "../../../core/color/Color.js"; import { kelvin_to_rgb } from "../../../core/color/kelvin/kelvin_to_rgb.js"; import { AABB3 } from "../../../core/geom/3d/aabb/AABB3.js"; import { compose_matrix4_array } from "../../../core/geom/3d/mat4/compose_matrix4_array.js"; import { visualize_tetrahedral_mesh } from "../../../core/geom/3d/tetrahedra/visualize_tetrahedral_mesh.js"; import Quaternion from "../../../core/geom/Quaternion.js"; import { v3_length } from "../../../core/geom/vec3/v3_length.js"; import Vector3 from "../../../core/geom/Vector3.js"; import { max2 } from "../../../core/math/max2.js"; import { min2 } from "../../../core/math/min2.js"; import { RAD_TO_DEG } from "../../../core/math/RAD_TO_DEG.js"; import { randomFloatBetween } from "../../../core/math/random/randomFloatBetween.js"; import { seededRandom } from "../../../core/math/random/seededRandom.js"; import { delay } from "../../../core/process/delay.js"; import ButtonView from "../../../view/elements/button/ButtonView.js"; import { GLTFAssetLoader } from "../../asset/loaders/GLTFAssetLoader.js"; import Entity from "../../ecs/Entity.js"; import GUIElement from "../../ecs/gui/GUIElement.js"; import GUIElementSystem from "../../ecs/gui/GUIElementSystem.js"; import ViewportPositionSystem from "../../ecs/gui/position/ViewportPositionSystem.js"; import { TransformAttachmentSystem } from "../../ecs/transform-attachment/TransformAttachmentSystem.js"; import { Transform } from "../../ecs/transform/Transform.js"; import { EngineHarness } from "../../EngineHarness.js"; import { Camera } from "../ecs/camera/Camera.js"; import { focal_length_to_fov } from "../ecs/camera/focal_length_to_fov.js"; import TopDownCameraController from "../ecs/camera/topdown/TopDownCameraController.js"; import { Light } from "../ecs/light/Light.js"; import LightSystem from "../ecs/light/LightSystem.js"; import { LightType } from "../ecs/light/LightType.js"; import { SGMeshSystem } from "../ecs/mesh-v2/aggregate/SGMeshSystem.js"; import { ShadedGeometry } from "../ecs/mesh-v2/ShadedGeometry.js"; import { ShadedGeometryFlags } from "../ecs/mesh-v2/ShadedGeometryFlags.js"; import { ShadedGeometrySystem } from "../ecs/mesh-v2/ShadedGeometrySystem.js"; import { three_object_to_entity_composition } from "../ecs/mesh-v2/three_object_to_entity_composition.js"; import { GizmoRenderingPlugin } from "../render/gizmo/GizmoRenderingPlugin.js"; import { MaterialTransformer, ProbeResolutionStage } from "./gi/material/MaterialTransformer.js"; import { lpv_volume_bake_via_task } from "./lpv/build_probes_for_scene.js"; import { OctahedralDepthDebuggerWidget } from "./lpv/depth/octahedral/OctahedralDepthDebuggerWidget.js"; import { VLPDepthMapVisualisation } from "./lpv/depth/octahedral/v2/VLPDepthMapVisualisation.js"; import { LightProbeVolume } from "./lpv/LightProbeVolume.js"; import { lpv_obtain_storage_cached_volume } from "./lpv/lpv_obtain_storage_cached_volume.js"; import { lpv_visualize_probes } from "./lpv/util/lpv_visualise_probes.js"; import { lpv_build_editor } from "./lpv_build_editor.js"; /** * * @param {Storage} storage * @param {*} component * @param {BinaryClassSerializationAdapter} adapter * @param {string} key * @param {Signal[]} signals */ async function persist_component(storage, component, adapter, key = 'temp', signals) { const current_state = new BinaryBuffer(); const full_key = `persisted_transform:${key}`; if (await storage.promiseContains(full_key)) { const loaded = await storage.promiseLoadBinary(full_key); const buffer = BinaryBuffer.fromArrayBuffer(loaded); adapter.deserialize(buffer, component); } // make a binary state copy adapter.serialize(current_state, component); for (let i = 0; i < signals.length; i++) { signals[i].add(store); } function store() { const buffer = new BinaryBuffer(); adapter.serialize(buffer, component); buffer.trim(); if (!is_typed_array_equals(current_state.__data_uint8, buffer.__data_uint8)) { // change detected storage.promiseStoreBinary(full_key, buffer.data); current_state.setCapacity(buffer.length); array_copy(buffer.__data_uint8, 0, current_state.__data_uint8, 0, buffer.capacity); } } } /** * * @param {number|Vector3} scale * @param {Vector3} offset * @param {Vector3} direction * @param up * @param shadow * @param material * @param {Engine} engine */ function make_plane({ scale, offset, direction = Vector3.forward, up = Vector3.up, shadow = true, material = new MeshBasicMaterial({ color: 0xFFFFFF }), engine }) { const t1 = Transform.fromJSON({ position: offset, scale: scale }); t1.rotation.lookRotation(direction, up); if (engine) { material = engine.graphics.getMaterialManager().obtain(material).getValue(); } const sg = ShadedGeometry.from(new PlaneBufferGeometry(), material); sg.writeFlag(ShadedGeometryFlags.CastShadow, shadow); sg.writeFlag(ShadedGeometryFlags.ReceiveShadow, shadow); return new Entity() .add(sg) .add(t1); } function make_cornel_box(ecd) { make_plane({ scale: 20, offset: new Vector3(10, 10, 0), direction: Vector3.forward, material: new MeshBasicMaterial({ color: 0xFF0000 }) }).build(ecd); make_plane({ scale: 20, offset: new Vector3(20, 10, 10), direction: Vector3.left, material: new MeshBasicMaterial({ color: 0x00FF00 }) }).build(ecd); make_plane({ scale: 20, offset: new Vector3(0, 10, 10), direction: Vector3.right, material: new MeshBasicMaterial({ color: 0xFFFFFF }) }).build(ecd); } function make_test_texture(t = 1) { const t1 = (1 - t) * 0.5; const tex = new DataTexture(new Uint8ClampedArray([ 255 * t, 255 * t1, 255 * t1, 255, 255 * t1, 255 * t, 255 * t1, 255, 255 * t1, 255 * t1, 255 * t, 255, 255 * t, 255 * t, 255 * t1, 255 ]), 2, 2, RGBAFormat, UnsignedByteType); tex.flipY = false; tex.wrapS = ClampToEdgeWrapping; tex.wrapT = ClampToEdgeWrapping; tex.magFilter = NearestFilter; tex.minFilter = LinearFilter; tex.generateMipmaps = false; tex.needsUpdate = true; return tex; } /** * * @param {EntityComponentDataset} ecd * @param {Vector3} [center] * @param {Vector3} [size] * @param {GraphicsEngine} graphics * @param {number} [texture_frequency] */ function createFloor({ ecd, graphics, center = new Vector3(0, 0, 0), size = new Vector3(1000, 1000, 1000), texture_frequency = 1 }) { const map = new TextureLoader().load('data/textures/utility/checkers_dark_grey_256x256.png', t => { map.needsUpdate = true; map.wrapS = RepeatWrapping; map.wrapT = RepeatWrapping; map.repeat.set(size.x / texture_frequency, size.z / texture_frequency); map.matrixAutoUpdate = true; }); const material = graphics.getMaterialManager().obtain(new MeshStandardMaterial({ map: map })).getValue(); const geometry = new PlaneBufferGeometry(1, 1, 1, 1); const q = Quaternion.fromEulerAngles(-Math.PI * 0.5, 0, 0); const m = new Matrix4(); compose_matrix4_array(m.elements, Vector3.zero, q, Vector3.one); geometry.applyMatrix4(m); geometry.computeVertexNormals(); geometry.computeTangents(); new Entity() .add(Transform.fromJSON({ position: center.toJSON(), // rotation: , scale: size })) .add(ShadedGeometry.from(geometry, material)) .build(ecd); } /** * * @param {Engine} engine * @return {Promise<void>} */ async function main(engine) { const volume = new LightProbeVolume(); const transformer = new MaterialTransformer({ volume , stage: ProbeResolutionStage.Vertex }); engine.graphics.getMaterialManager().addCompileStep(transformer); await EngineHarness.buildBasics({ engine, showFps: false, enableWater: false, enableTerrain: false, enableLights: false, // enableLights: true, cameraFarDistance: 200, cameraController: false, focus: { x: 0, y: 0, z: 0 }, pitch: 1, yaw: -1.54, distance: 54, cameraFieldOfView: focal_length_to_fov(50, 25) * RAD_TO_DEG, // shadowmapResolution: 4096 }); const sun_color = new Color(); kelvin_to_rgb(sun_color, 0, 5000); await EngineHarness.buildLights({ engine, shadowmapResolution: 4096, ambientIntensity: 0.0, sun: sun_color, // sunIntensity: 1.7, sunIntensity: 3, // sunDirection: new Vector3(0.2, -0.8, 1) sunDirection: new Vector3(1.2, -1, 0.2) }) engine.graphics.getRenderer().setClearColor('#1e3441', 1); // load_and_set_cubemap_v0(engine.graphics, 'data/textures/cubemaps/hip_miramar/32/', '.png'); const ecd = engine.entityManager.dataset; // const path = 'data/models/samples/sd_macross_city_standoff_diorama/scene.gltf'; // const path = 'data/models/sibenik/2/model.gltf'; // const path = 'data/models/vokselia_spawn/model.gltf'; // const path = 'data/models/samples/just_a_girl/scene.gltf'; // const path = 'data/models/samples/slum_house/scene.gltf'; // const path = 'data/models/samples/jack_trigger/scene.gltf'; // const path = 'data/models/samples/cornell_box/scene.gltf'; // const path = 'data/models/Slaughter Mech/Slaugter Mech.gltf'; // const path = 'data/models/samples/gi_box_01/model-thick-1.glb'; // const path = 'data/models/samples/ancient_bath_house_-_modular_set/scene.gltf'; // const path = 'data/models/samples/environment_-_library/scene.gltf'; // const path = 'data/models/Scans/green_car_wreck/scene.gltf'; // const path = 'data/models/samples/the_attic_environment/scene1.gltf'; // const path = 'data/models/LowPolyTownshipSet/Small_house/Small_house.gltf'; // const path = 'data/models/samples/cyberpunk_bike/scene.gltf'; // const path = 'data/models/LowPolyTownshipSet/Town_Hall/model.gltf'; // const path = 'data/models/sibenik/3-window-less/model.gltf'; // const path = 'data/models/sponza-pbr/gltf/sponza.glb'; // const path = 'data/models/samples/susanne.glb'; // const path = 'data/models/samples/teapot.gltf'; // const path = 'data/models/samples/salle_de_bain/model.glb'; // const path = 'data/models/samples/conference/model-no-curtains.glb'; const path = 'data/models/pica_pica/pica_pica.gltf'; // const path = 'data/models/samples/gi_box_01/model.glb'; // const path = 'data/models/samples/low_poly_classroom/no-glass/model.gltf'; // const path = 'customer_data/halon_scene.glb'; const mesh_asset = await engine.assetManager.promise(path, 'model/gltf+json'); const gltf = mesh_asset.gltf; console.log(gltf); // add floor // make_cornel_box(ecd); const composition = three_object_to_entity_composition(gltf.scene); // composition.transform.rotation.multiply(Quaternion.fromEulerAngles(DEG_TO_RAD * 30, DEG_TO_RAD * 30, 0)); composition.traverse(n => { /** * @type {ShadedGeometry} */ const sg = n.entity.getComponent(ShadedGeometry); if (sg === null) { return; } sg.writeFlag(ShadedGeometryFlags.CastShadow | ShadedGeometryFlags.ReceiveShadow, true); const material = sg.material; if (material !== null) { material.depthTest = true; material.depthWrite = true; } }); composition.transform.scale.setScalar(1); // // composition.transform.rotation.fromEulerAngles(0, Math.PI, 0); composition.build(ecd); /** * @type {AABB3} */ const mesh_bounds = mesh_asset.boundingBox; // const mesh_bounds = new AABB3(0, 0, 0, 20, 20, 20); createFloor({ center: new Vector3(mesh_bounds.getCenterX(), mesh_bounds.y0, mesh_bounds.getCenterZ()), size: new Vector3( max2(0.1, mesh_bounds.getExtentsX() * 2), 1, max2(0.1, mesh_bounds.getExtentsZ() * 2) ), ecd, graphics: engine.graphics }); const model_footprint = v3_length(mesh_bounds.getExtentsX(), mesh_bounds.getExtentsY(), mesh_bounds.getExtentsZ()); const camera = ecd.getAnyComponent(Camera); camera.component.clip_far = max2(100, model_footprint * 3); const camera_controller = ecd.getComponent(camera.entity, TopDownCameraController); camera_controller.distanceMax = model_footprint * 2; camera_controller.distanceMin = min2(camera_controller.distanceMin, model_footprint * 0.01); mesh_bounds.getCenter(camera_controller.target) const random = seededRandom(); for (let i = 0; i < 0; i++) { new Entity() .add(Transform.fromJSON({ position: { x: randomFloatBetween(random, mesh_bounds.x0, mesh_bounds.x1), y: randomFloatBetween(random, mesh_bounds.y0, mesh_bounds.y1), z: randomFloatBetween(random, mesh_bounds.z0, mesh_bounds.z1), } })) .add(Light.fromJSON({ distance: model_footprint * 0.1, type: LightType.POINT, intensity: 0.5, color: `hsv(${random()},${1},1)` })) .build(ecd); } new Entity() .add(GUIElement.fromView(new ButtonView({ name: "Toggle Model", action() { composition.isBuilt ? composition.destroy() : composition.build(ecd) } }))) .build(ecd); await delay(200); console.time('getVolume'); // console.profile('getVolume'); await lpv_obtain_storage_cached_volume({ engine, path, volume, density: 50 }); // console.profileEnd('getVolume'); console.timeEnd('getVolume'); console.log('LPV:', volume); transformer.update(); const probe_viz = lpv_visualize_probes({ volume, size: model_footprint * 0.002 }); // probe_viz.build(ecd); const mesh_viz = visualize_tetrahedral_mesh({ mesh: volume.mesh, positions: volume.points, opacity: 0.5 }); const depth_viz = VLPDepthMapVisualisation.from(volume).build(); // depth_viz.build(ecd); // volume.visualize_mesh({ ecd, opacity: 0.5 }); const gui = new GUI(); gui.add(transformer, 'intensity').min(0).max(10); gui.add({ capture_camera() { const transform = ecd.getComponent(camera.entity, Transform); console.log(JSON.stringify(transform.toJSON())) } }, "capture_camera"); gui.add({ get probe_vis() { return probe_viz.isBuilt }, set probe_vis(v) { if (v) { probe_viz.build(ecd); } else { probe_viz.destroy(); } } }, "probe_vis").name("Visualise Probes"); gui.add({ get mesh_vis() { return mesh_viz.isBuilt }, set mesh_vis(v) { if (v) { mesh_viz.build(ecd); } else { mesh_viz.destroy(); } } }, "mesh_vis").name("Visualise Mesh"); gui.add({ get depth_vis() { return depth_viz.isBuilt }, set depth_vis(v) { if (v) { depth_viz.build(ecd); } else { depth_viz.destroy(); } } }, "depth_vis").name("Visualise Depth"); gui.add({ bake(){ volume.build_mesh(); lpv_volume_bake_via_task(volume, ecd, engine); } },"bake").name("Bake"); // makeDebugGizmo(engine, mesh_bounds.getCenter()); // mesh_entity.addEventListener(SGMeshEvents.AssetLoaded, build); // // mesh_entity.build(ecd); lpv_build_editor({ engine, volume, path_key: path }); } /** * * @param {Engine} engine * @param {Vector3} position */ function makeDebugGizmo(engine, position) { const widget = new OctahedralDepthDebuggerWidget(); widget.entity.getComponentSafe(Transform).position.copy(position); widget.build(engine.entityManager.dataset, engine); } new EngineHarness().initialize({ configuration(config, engine) { config.addSystem(new ShadedGeometrySystem(engine)); config.addSystem(new LightSystem(engine, { shadowResolution: 8192 })); config.addSystem(new SGMeshSystem(engine)); config.addSystem(new TransformAttachmentSystem()); config.addSystem(new GUIElementSystem(engine.gui.view, engine)); config.addSystem(new ViewportPositionSystem(engine.gui.view.size)); config.addLoader('model/gltf+json', new GLTFAssetLoader()); config.addPlugin(GizmoRenderingPlugin); // config.addPlugin(AmbientOcclusionPostProcessEffect); } }).then(main);