UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

315 lines (233 loc) • 8.46 kB
import { min2 } from "../../../../../core/math/min2.js"; import { ResourceAccessKind } from "../../../../../core/model/ResourceAccessKind.js"; import { ResourceAccessSpecification } from "../../../../../core/model/ResourceAccessSpecification.js"; import { STATIC_ASSET_CACHE } from "../../../../asset/STATIC_ASSET_CACHE.js"; import { EntityNode } from "../../../../ecs/parent/EntityNode.js"; import { ParentEntity } from "../../../../ecs/parent/ParentEntity.js"; import { System } from "../../../../ecs/System.js"; import { TransformAttachment } from "../../../../ecs/transform-attachment/TransformAttachment.js"; import { TransformAttachmentSystem } from "../../../../ecs/transform-attachment/TransformAttachmentSystem.js"; import { Transform } from "../../../../ecs/transform/Transform.js"; import { assetTypeByPath } from "../../mesh/assetTypeByPath.js"; import { ShadedGeometry } from "../ShadedGeometry.js"; import { ShadedGeometryFlags } from "../ShadedGeometryFlags.js"; import { three_object_to_entity_composition } from "../three_object_to_entity_composition.js"; import { SGMesh } from "./SGMesh.js"; import { SGMeshEvents } from "./SGMeshEvents.js"; /** * @readonly * @type {number} */ const MAX_CYCLE_COUNT = 128; /** * In milliseconds * @readonly * @type {number} */ const MAX_CYCLE_TIME = 40; /** * * @param {string} url * @param {AssetManager} assetManager * @returns {Promise<THREE.Object3D>} */ async function getAsset(url, assetManager) { const assetType = assetTypeByPath(url); const asset = await assetManager.promise(url, assetType); } export class SGMeshSystem extends System { /** * * @param {Engine} engine */ constructor(engine) { super(); /** * * @type {Engine} * @private */ this.__engine = engine; /** * * @type {AssetManager} * @private */ this.__assetManager = engine.assetManager; this.dependencies = [SGMesh, Transform]; this.components_used = [ ResourceAccessSpecification.from(SGMesh, ResourceAccessKind.Read | ResourceAccessKind.Write), ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read), ResourceAccessSpecification.from(ShadedGeometry, ResourceAccessKind.Create), ResourceAccessSpecification.from(TransformAttachment, ResourceAccessKind.Create), ]; /** * * @type {number[]} * @private */ this.__wait_queue = []; /** * * @type {number} * @private */ this.__wait_queue_process_index = 0; } async startup(em) { if (!em.hasSystem(TransformAttachmentSystem)) { await em.addSystem(new TransformAttachmentSystem()); } } /** * * @param {number} entity * @param {SGMesh} mesh * @param {Transform} transform */ async process(entity, mesh, transform) { const am = this.__assetManager; const ecd = this.entityManager.dataset; const assetType = assetTypeByPath(mesh.url); let asset; try { asset = await am.promise(mesh.url, assetType); } catch (e) { ecd.sendEvent(entity, SGMeshEvents.AssetLoadFailed); return; } if (!ecd.entityExists(entity)) { // entity no longer exists return; } if (ecd.getComponent(entity, SGMesh) !== mesh) { // mesh has changed return; } if (asset.boundingBox !== undefined) { mesh.__initial_bounds.copy(asset.boundingBox); if (asset.has_root_transform) { // TODO potentially move this to the GLTF Asset Loader to skip matrix operations here mesh.__initial_bounds.applyMatrix4(asset.root_transform.matrix); } } else { console.error(`No bounds for asset: ${asset.description}`); } let object; const cached_object = STATIC_ASSET_CACHE.get(asset); if (cached_object !== null) { object = cached_object; } else { object = asset.create(); STATIC_ASSET_CACHE.set(asset, object); } let entity_node = three_object_to_entity_composition(object); if (asset.has_root_transform) { // apply root transform const new_root = new EntityNode(); new_root.entity.add(new Transform()); entity_node.transform.copy(asset.root_transform); new_root.addChild(entity_node); entity_node = new_root; } // attach parent entity to link hierarchy to this entity entity_node.entity.add(ParentEntity.from(entity)); mesh.__node = entity_node; function copy_transform() { entity_node.transform.copy(transform); } copy_transform(); entity_node.build(ecd); transform.subscribe(copy_transform); entity_node.on.destroyed.addOne(() => { transform.unsubscribe(copy_transform); }); const material_override = mesh.materialOverride; // apply flags to the hierarchy entity_node.traverse(node => { /** * * @type {ShadedGeometry|null} */ const sg = node.entity.getComponent(ShadedGeometry); if (sg === null) { return; } sg.writeFlag(ShadedGeometryFlags.CastShadow, mesh.castShadow); sg.writeFlag(ShadedGeometryFlags.ReceiveShadow, mesh.receiveShadow); if (material_override !== null) { sg.material = material_override; } }); ecd.sendEvent(entity, SGMeshEvents.AssetLoaded); } /** * * @param {SGMesh} mesh * @param {Transform} transform * @param {number} entity */ link(mesh, transform, entity) { const p = transform.position; // set initial bounds to single point mesh.__initial_bounds.setBounds( p.x, p.y, p.z, p.x, p.y, p.z, ); if (mesh.url === null) { this.__wait_queue.push(entity); return; } this.process(entity, mesh, transform); } /** * * @param {SGMesh} mesh * @param {Transform} transform * @param {number} entity */ unlink(mesh, transform, entity) { if (mesh.__node !== null) { mesh.__node.destroy(); mesh.__node = null; } // check the queue const i = this.__wait_queue.indexOf(entity); if (i !== -1) { this.__wait_queue.splice(i, 1); } } update(time_delta) { const entityManager = this.entityManager; const dataset = entityManager.dataset; if (dataset === null) { return; } const waiting = this.__wait_queue; let l = waiting.length; if (l > 0) { const time_0 = performance.now(); let pointer = this.__wait_queue_process_index; const pointer_limit = pointer + min2(l, MAX_CYCLE_COUNT); while (pointer < pointer_limit) { const index = pointer % l; pointer++; const entity = waiting[index]; const mesh = dataset.getComponent(entity, SGMesh); if (mesh.url === null) { // no URL yet continue; } waiting.splice(index, 1); const transform = dataset.getComponent(entity, Transform); this.process(entity, mesh, transform); const time_1 = performance.now(); if ((time_1 - time_0) > MAX_CYCLE_TIME) { // too much time has passed break; } } this.__wait_queue_process_index = pointer; } } }