Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions src/framework/entity.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Debug } from '../core/debug.js';
import { guid } from '../core/guid.js';
import { BoundingBox } from '../core/shape/bounding-box.js';
import { GraphNode } from '../scene/graph-node.js';
import { getApplication } from './globals.js';

Expand Down Expand Up @@ -28,6 +29,14 @@ import { getApplication } from './globals.js';
* @import { ScrollbarComponent } from './components/scrollbar/component.js'
* @import { SoundComponent } from './components/sound/component.js'
* @import { SpriteComponent } from './components/sprite/component.js'
* @import { MeshInstance } from '../scene/mesh-instance.js'
*/

/**
* @callback RenderMeshInstanceCallback
* @param {MeshInstance} meshInstance - The render mesh instance.
* @param {RenderComponent|ModelComponent} component - The component that owns the mesh instance.
* @param {Entity} entity - The entity that owns the component.
*/

/**
Expand Down Expand Up @@ -466,6 +475,63 @@ class Entity extends GraphNode {
return this.find(entity => entity.c?.[type]).map(entity => entity.c[type]);
}

/**
* Executes a provided function once for each {@link MeshInstance} on this entity and all of
* its descendants.
*
* @param {RenderMeshInstanceCallback} callback - The function to execute on each mesh
* instance.
* @param {object} [thisArg] - Optional value to use as this when executing callback function.
* @example
* entity.forEachRenderMeshInstance((meshInstance) => {
* meshInstance.visible = false;
* });
*/
forEachRenderMeshInstance(callback, thisArg) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it called forEachRenderMeshInstance when it works on model components.
it should be forEachMeshInstance

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a draft for now - the exact names we can modify. I am just collating findings from my experiments with agents

this.forEach((entity) => {
const render = entity.c.render;
const renderMeshes = render?.meshInstances;
for (let i = 0, len = renderMeshes?.length ?? 0; i < len; i++) {
callback.call(thisArg, renderMeshes[i], render, entity);
}

const model = entity.c.model;
const modelMeshes = model?.meshInstances;
for (let i = 0, len = modelMeshes?.length ?? 0; i < len; i++) {
callback.call(thisArg, modelMeshes[i], model, entity);
}
});
}

/**
* Gets the axis-aligned bounding box enclosing all render mesh instances on this entity and all
* of its descendants.
*
* @param {BoundingBox} [result] - The bounding box to receive the result.
* @returns {BoundingBox|null} The bounding box, or null if no render mesh instances were found.
* @example
* const aabb = entity.getAabb();
* if (aabb) {
* // use aabb
* }
*/
getAabb(result) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure this function is good as is, I think meshInstance.aabb is only updated during rendering, so on the first frame it's not valid. we should not expose this for now till that is sorted. separate PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still working on this - this is not merge ready at all

let aabb = result;
let found = false;

this.forEachRenderMeshInstance((meshInstance) => {
if (found) {
aabb.add(meshInstance.aabb);
} else {
aabb = aabb || new BoundingBox();
aabb.copy(meshInstance.aabb);
found = true;
}
});

return found ? aabb : null;
}

/**
* Search the entity and all of its descendants for the first script instance of specified type.
*
Expand Down
69 changes: 69 additions & 0 deletions test/framework/entity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { stub } from 'sinon';

import { DummyComponentSystem } from './test-component/system.mjs';
import { Color } from '../../src/core/math/color.js';
import { Vec3 } from '../../src/core/math/vec3.js';
import { BoundingBox } from '../../src/core/shape/bounding-box.js';
import { AnimComponent } from '../../src/framework/components/anim/component.js';
import { AnimationComponent } from '../../src/framework/components/animation/component.js';
import { AudioListenerComponent } from '../../src/framework/components/audio-listener/component.js';
Expand Down Expand Up @@ -695,6 +697,73 @@ describe('Entity', function () {

});

describe('#forEachRenderMeshInstance', function () {

it('visits render and model mesh instances in hierarchy order', function () {
const root = new Entity('root');
const child = new Entity('child');
const grandchild = new Entity('grandchild');
root.addChild(child);
child.addChild(grandchild);

const rootMesh = { aabb: new BoundingBox() };
const childMesh = { aabb: new BoundingBox() };
const grandchildMesh = { aabb: new BoundingBox() };
const render = { meshInstances: [rootMesh] };
const model = { meshInstances: [childMesh] };
const childRender = { meshInstances: [grandchildMesh] };
root.c.render = render;
child.c.model = model;
grandchild.c.render = childRender;

const calls = [];
root.forEachRenderMeshInstance((meshInstance, component, entity) => {
calls.push([meshInstance, component, entity]);
});

expect(calls).to.deep.equal([
[rootMesh, render, root],
[childMesh, model, child],
[grandchildMesh, childRender, grandchild]
]);
});

});

describe('#getAabb', function () {

it('returns null when no render mesh instances are found', function () {
const entity = new Entity();

expect(entity.getAabb()).to.equal(null);
});

it('unions descendant render mesh instance bounds into the result', function () {
const root = new Entity();
const child = new Entity();
root.addChild(child);

root.c.render = {
meshInstances: [{
aabb: new BoundingBox(new Vec3(0, 0, 0), new Vec3(1, 2, 3))
}]
};
child.c.model = {
meshInstances: [{
aabb: new BoundingBox(new Vec3(5, 0, 0), new Vec3(1, 1, 1))
}]
};

const result = new BoundingBox();
const aabb = root.getAabb(result);

expect(aabb).to.equal(result);
expect(aabb.center.equals(new Vec3(2.5, 0, 0))).to.equal(true);
expect(aabb.halfExtents.equals(new Vec3(3.5, 2, 3))).to.equal(true);
});

});

describe('#findScript', function () {

it('finds script on single entity', function () {
Expand Down