@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
315 lines (233 loc) • 8.46 kB
JavaScript
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;
}
}
}