UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

447 lines (343 loc) • 12.1 kB
import { BoxBufferGeometry, MeshLambertMaterial } from "three"; import { assert } from "../../../../core/assert.js"; import { BVH } from "../../../../core/bvh2/bvh3/BVH.js"; import { SignalBinding } from "../../../../core/events/signal/SignalBinding.js"; import Vector3 from "../../../../core/geom/Vector3.js"; import { ResourceAccessKind } from "../../../../core/model/ResourceAccessKind.js"; import { ResourceAccessSpecification } from "../../../../core/model/ResourceAccessSpecification.js"; import { GLTFAssetLoader } from "../../../asset/loaders/GLTFAssetLoader.js"; import { System } from "../../../ecs/System.js"; import { Transform } from "../../../ecs/transform/Transform.js"; import { make_bvh_visibility_builder } from "../../render/make_bvh_visibility_builder.js"; import checkerTexture from "../../texture/CheckersTexture.js"; import ThreeFactory from "../../three/ThreeFactory.js"; import { assetTypeByPath } from "./assetTypeByPath.js"; import Mesh, { MeshFlags } from "./Mesh.js"; import { MeshEvents } from "./MeshEvents.js"; import { setMesh } from "./setMesh.js"; const PLACEHOLDER_GEOMETRY = new BoxBufferGeometry(1, 1, 1); PLACEHOLDER_GEOMETRY.computeBoundingSphere(); const PLACEHOLDER_TEXTURE = checkerTexture.create(); const PLACEHOLDER_MATERIAL = new MeshLambertMaterial({ map: PLACEHOLDER_TEXTURE }); export class MeshSystem extends System { /** * * @param {Engine} engine * @constructor * @extends {System} */ constructor(engine) { super(); assert.equal(engine.isEngine, true, 'engine.isEngine !== true'); const graphics = engine.graphics; const assetManager = engine.assetManager; /** * * @type {Engine} * @private */ this.__engine = engine; this.dependencies = [Mesh, Transform]; this.components_used = [ ResourceAccessSpecification.from(Mesh, ResourceAccessKind.Write) ]; /** * * @type {AssetManager} */ this.assetManager = assetManager; this.waitingForMesh = []; /** * * @type {Array.<Array.<SignalBinding>>} */ this.entityData = []; this.graphics = graphics; /** * * @type {RenderLayer|null} */ this.renderLayer = null; /** * * @type {boolean} * @private */ this.__use_placeholder_mesh = false; /** * * @type {BVH} * @private */ this.__bvh_binary = new BVH(); } /** * * @param {number} entity * @param {EntityComponentDataset} dataset * @param {Asset} asset * @param {Mesh} component */ __set_mesh_from_asset(entity, dataset, asset, component) { const mesh = asset.create(); // remember the asset component.asset = asset; if (asset.boundingBox !== undefined) { component.boundingBox.copy(asset.boundingBox); } else if (mesh.isMesh) { const geometry = mesh.geometry; const bb = geometry.boundingBox; component.boundingBox.setBounds( bb.min.x, bb.min.y, bb.min.z, bb.max.x, bb.max.y, bb.max.z ); } component.setFlag(MeshFlags.Loaded); setMesh(dataset, entity, component, mesh); // send event about asset being attached dataset.sendEvent(entity, MeshEvents.AssetLoaded); } async startup(entityManager) { const am = this.assetManager; this.entityManager = entityManager; if (!am.hasLoaderForType('model/gltf+json')) { const gltf_asset_loader = new GLTFAssetLoader(); await am.registerLoader('model/gltf+json', gltf_asset_loader); } const graphics = this.graphics; this.renderLayer = graphics.layers.create("mesh-system"); this.renderLayer.buildVisibleSet = make_bvh_visibility_builder( this.entityManager, this.__bvh_binary, (destination, offset, entity, ecd) => { const component = ecd.getComponent(entity, Mesh); if (component.mesh === null) { return 0; } destination[offset] = component.mesh; return 1; } ) const visibleSet = this.renderLayer.visibleSet; visibleSet.onAdded.add(m => { /** * * @type {Mesh} */ const component = m.__meep_ecs_component; component.setFlag(MeshFlags.InView); }); visibleSet.onRemoved.add(m => { /** * * @type {Mesh} */ const component = m.__meep_ecs_component; if (m !== component.mesh) { //object does not match current component mesh, skip this event return; } component.clearFlag(MeshFlags.InView); }); } /** * * @param {function(Mesh, number)} visitor * @param {*} [thisArg] */ traverseVisible(visitor, thisArg) { const em = this.entityManager; const ecd = em.dataset; if (ecd === null) { return; } const visibleSet = this.renderLayer.visibleSet; const visibleElements = visibleSet.elements; const n = visibleSet.size; for (let i = 0; i < n; i++) { /** * * @type {Object3D} */ const m = visibleElements[i]; /** * * @type {Mesh} */ const component = m.__meep_ecs_component; /** * @type {number} */ const entity = m.__meep_ecs_entity; if (!ecd.entityExists(entity)) { //entity was destroyed at some point, visibility set updates asynchronously from ECS dataset updates //skip return; } if (component !== undefined) { visitor.call(thisArg, component, entity); } } } async shutdown(entityManager) { this.graphics.layers.remove(this.renderLayer); } /** * * @param {Transform} transform * @param {Mesh} model * @param {number} entity */ link(model, transform, entity) { //remember entity for fast lookup this.process(entity, model); function apply_transform() { model.internalApplyTransform(transform); } const bPosition = new SignalBinding( transform.position.onChanged, apply_transform ); const bRotation = new SignalBinding( transform.rotation.onChanged, apply_transform ); const bScale = new SignalBinding( transform.scale.onChanged, apply_transform ); bPosition.link(); bRotation.link(); bScale.link(); this.entityData[entity] = [ bPosition, bRotation, bScale ]; model.__bvh_leaf.link(this.__bvh_binary, entity); } /** * * @param {Transform} transform * @param {Mesh} model * @param {string} entityId */ unlink(model, transform, entityId) { const list = this.entityData[entityId]; if (list !== undefined) { for (let i = 0; i < list.length; i++) { const binding = list[i]; binding.unlink(); } delete this.entityData[entityId]; } //remove from 'waiting for mesh' set const waitingForMesh = this.waitingForMesh; let n = waitingForMesh.length; for (let i = 0; i < n; i++) { const element = waitingForMesh[i]; if (element.entity === entityId) { waitingForMesh.splice(i, 1); i--; n--; } } model.__bvh_leaf.unlink(); // cleanup resources model.dispose(); } /** * * @param {int} entity * @param {Mesh} component */ process(entity, component) { const em = this.entityManager; const am = this.assetManager; const dataset = em.dataset; if (component.hasMesh()) { //do not re-run if mesh is set setMesh(dataset, entity, component, component.mesh); return; } if (component.url === null) { this.waitingForMesh.push({ component, entity }); return; } const assetType = assetTypeByPath(component.url); const asset = am.tryGet(component.url, assetType); if (asset !== null) { // asset is already loaded, use it this.__set_mesh_from_asset(entity, dataset, asset, component); return; } // mesh was not found, use temp mesh and submit a request if (this.__use_placeholder_mesh) { // set placeholder mesh const mesh = ThreeFactory.createMesh(PLACEHOLDER_GEOMETRY, PLACEHOLDER_MATERIAL); setMesh(dataset, entity, component, mesh); } function assetFailure(error) { console.error("failed to load model " + component.url, error); dataset.sendEvent(entity, MeshEvents.AssetLoadFailed, { reason: error }); } if (assetType === null) { // couldn't figure out what the asset type is assetFailure("no asset type deduced"); return; } /** * * @param {Asset<Object3D>} asset */ const assetLoaded = (asset) => { if (!dataset.entityExists(entity)) { //entity no longer exists, probably dataset has been switched, abort return; } //check that component is still actual const actualComponent = dataset.getComponent(entity, Mesh); if (actualComponent === component) { // scene.remove(component.mesh); this.__set_mesh_from_asset(entity, dataset, asset, component); } else { //component is no longer in the manager. do nothing. //console.warn("component is no longer in the manager"); } } // load the asset am.get({ path: component.url, type: assetType, callback: assetLoaded, failure: assetFailure }); } update(timeDelta) { const waiting = this.waitingForMesh; let i = 0; let l = waiting.length; for (; i < l; i++) { const element = waiting[i]; const component = element.component; if (component.url !== null) { const entity = element.entity; this.process(entity, component); waiting.splice(i, 1); i--; l--; } } } /** * * @param {Mesh} component * @param {HumanoidBoneType} boneType * @param {Vector3} [result=new Vector3()] * @returns {Vector3} * @throws {Error} if no such bone exists */ static getBonePosition(component, boneType, result = new Vector3()) { component.getBonePositionByType(result, boneType); return result; } }