Skip to content
Open
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
2 changes: 2 additions & 0 deletions Config/eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"Frame": "writable",
"GLOBAL_MESSAGE_DB": "writable",
"GameMenuFrame": "writable",
"GeometryBlueprint": "writable",
"HeightMap": "writable",
"JpegDecoder": "writable",
"Label": "writable",
Expand All @@ -93,6 +94,7 @@
"OptionsFrame": "writable",
"OptionsGroup": "writable",
"Paragraph": "writable",
"PolygonMesh": "writable",
"PngDecoder": "writable",
"Renderer": "writable",
"Resource": "writable",
Expand Down
15 changes: 15 additions & 0 deletions Core/API/WebGL/PolygonMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,19 @@ class PolygonMesh {
C_Rendering.removeMesh(this.sceneObject);
this.sceneObject = null;
}
isShown() {
if (!this.sceneObject) return false;

return this.sceneObject.isVisible === true;
}
show() {
if (!this.sceneObject) return;

this.sceneObject.isVisible = true;
}
hide() {
if (!this.sceneObject) return;

this.sceneObject.isVisible = false;
}
}
19 changes: 19 additions & 0 deletions Core/APIs/C_Rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,22 @@ C_Rendering.getNumActiveMeshes = function () {
C_Rendering.getNumActiveAnimations = function () {
return this.renderer.activeScene.animations.length;
};

C_Rendering.getNumActiveLights = function () {
return this.renderer.activeScene.lights.length;
};

C_Rendering.getNumActiveCameras = function () {
return this.renderer.activeScene.cameras.length;
};

C_Rendering.getNumSceneObjects = function () {
return (
this.getNumActiveMeshes() +
this.getNumActiveAnimations() +
this.getNumActiveMaterials() +
this.getNumActiveTextures() +
this.getNumActiveLights() +
this.getNumActiveCameras()
);
};
55 changes: 55 additions & 0 deletions Core/APIs/C_WebGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const C_WebGL = {
[Enum.FOG_MODE_EXPONENTIAL]: BABYLON.Scene.FOGMODE_EXP,
[Enum.FOG_MODE_EXPONENTIAL_DENSE]: BABYLON.Scene.FOGMODE_EXP2,
},
meshPrototypes: {},
meshInstances: {},
};

C_WebGL.createCanvasRenderer = function (canvas, options) {
Expand Down Expand Up @@ -177,6 +179,59 @@ C_WebGL.createMesh = function (name, geometryBlueprint) {
return mesh;
};

C_WebGL.getInstantiationPrototype = function (rootMeshID) {
return this.meshPrototypes[rootMeshID];
};

C_WebGL.createInstancedMesh = function (rootMeshID, geometryBlueprint = null) {
const rootMesh = this.meshPrototypes[rootMeshID];
const prototypeSceneObject = rootMesh?.sceneObject ?? this.createMesh(rootMeshID + "#Prototype", geometryBlueprint);
const instanceMeshID = rootMeshID + "#Instance" + (this.getNumInstances(rootMeshID) + 1);
const instance = prototypeSceneObject.createInstance(instanceMeshID);

this.meshInstances[rootMeshID] = this.meshInstances[rootMeshID] ?? [];
this.meshInstances[rootMeshID].push(instance);

const prototypeMesh = new PolygonMesh(rootMeshID);
prototypeMesh.sceneObject = prototypeSceneObject;
this.meshPrototypes[rootMeshID] = prototypeMesh;

const mesh = new PolygonMesh(instanceMeshID);
mesh.sceneObject = instance;

// Prototype meshes shouldn't be rendered since they're only used for instantiation
prototypeMesh.hide();

return mesh;
};

C_WebGL.getNumInstances = function (rootMeshID) {
if (!this.meshInstances[rootMeshID]) return 0;

return this.meshInstances[rootMeshID].length;
};

C_WebGL.destroyInstancedMeshes = function (rootMeshID) {
const instances = this.meshInstances[rootMeshID];

if (!instances) return; // Invalid root mesh ID

// Destroy instances
instances.forEach((instancedMesh) => {
instancedMesh.dispose();
});
delete this.meshInstances[rootMeshID];

// Destroy prototype mesh
const rootMesh = this.meshPrototypes[rootMeshID];
if (!rootMesh) return; // Invalid root mesh ID

// Have to make sure the mesh is disposed first here; otherwise there will be an error inside BJS ?_?
rootMesh.sceneObject.dispose();
rootMesh.sceneObject.material.dispose();
delete this.meshPrototypes[rootMeshID];
};

C_WebGL.createDirectionalLight = function (name, properties) {
const specularColor = new BABYLON.Color3(
properties.specularColor.red,
Expand Down
42 changes: 42 additions & 0 deletions Tests/API/WebGL/createInstancedMesh.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
describe("createInstancedMesh", () => {
const rootMeshID = new UniqueID().toString();
it("should create a prototype if none was previously created", () => {
const numMeshesBefore = C_Rendering.getNumActiveMeshes();
const numMaterialsBefore = C_Rendering.getNumActiveMaterials();
assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype"));

C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint());

const rootMesh = C_WebGL.getInstantiationPrototype(rootMeshID);
assertFalse(rootMesh.isShown());

const numMeshesAfter = C_Rendering.getNumActiveMeshes();
const numMaterialsAfter = C_Rendering.getNumActiveMaterials();

assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype"));
assertEquals(numMeshesAfter, numMeshesBefore + 2); // One instance, and the prototype itself
assertEquals(numMaterialsAfter, numMaterialsBefore + 1); // One material, shared between all instances
});

it("should return a mesh representation of the given geometry", () => {
const blueprint = new GeometryBlueprint();
const instance = C_WebGL.createInstancedMesh(rootMeshID, blueprint);
assertTrue(instance instanceof PolygonMesh);
});

it("should use the existing prototype if one was previously created", () => {
assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype"));
const numMeshesBefore = C_Rendering.getNumActiveMeshes();
const numMaterialsBefore = C_Rendering.getNumActiveMaterials();

C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint());
const numMeshesAfter = C_Rendering.getNumActiveMeshes();
const numMaterialsAfter = C_Rendering.getNumActiveMaterials();

assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype"));
assertEquals(numMeshesAfter, numMeshesBefore + 1); // Only one instance, no new prototype
assertEquals(numMaterialsAfter, numMaterialsBefore); // The shared material should be used
});

after(() => C_WebGL.destroyInstancedMeshes(rootMeshID));
});
41 changes: 41 additions & 0 deletions Tests/API/WebGL/destroyInstancedMeshes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
describe("destroyInstancedMeshes", () => {
it("should destroy any instanced meshes that were created from the prototype", () => {
const rootMeshID = new UniqueID().toString();
C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint());
C_WebGL.createInstancedMesh(rootMeshID);
C_WebGL.createInstancedMesh(rootMeshID);

assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype"));
assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance1"));
assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance2"));
assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance3"));

C_WebGL.destroyInstancedMeshes(rootMeshID);

assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype"));
assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance1"));
assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance2"));
assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance3"));
});

it("should remove the prototype mesh itself from the scene", () => {
const numMeshesBefore = C_Rendering.getNumActiveMeshes();
const numMaterialsBefore = C_Rendering.getNumActiveMaterials();

const rootMeshID = new UniqueID().toString();
C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint());
assertEquals(C_Rendering.getNumActiveMeshes(), numMeshesBefore + 2); // Prototype + first instance
assertEquals(C_Rendering.getNumActiveMaterials(), numMaterialsBefore + 1); // Shared material

C_WebGL.destroyInstancedMeshes(rootMeshID);

assertEquals(C_Rendering.getNumActiveMeshes(), numMeshesBefore);
assertEquals(C_Rendering.getNumActiveMaterials(), numMaterialsBefore);
});

it("should do nothing if no prototype exists with the given ID", () => {
const numSceneObjects = C_Rendering.getNumSceneObjects();
C_WebGL.destroyInstancedMeshes("invalid root mesh ID");
assertEquals(C_Rendering.getNumSceneObjects(), numSceneObjects);
});
});
25 changes: 25 additions & 0 deletions Tests/API/WebGL/getNumInstances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
describe("getNumInstances", () => {
const rootMeshID = "PrototypeMesh1";
const blueprint = new GeometryBlueprint(); // Can be empty; we don't need any actual geometry here

it("should return the number of mesh instances for a given prototype", () => {
// The prototype itself isn't visible and therefore not counted
assertEquals(C_WebGL.getNumInstances(rootMeshID), 0);
C_WebGL.createInstancedMesh(rootMeshID, blueprint);
assertEquals(C_WebGL.getNumInstances(rootMeshID), 1);
C_WebGL.createInstancedMesh(rootMeshID);
assertEquals(C_WebGL.getNumInstances(rootMeshID), 2);
C_WebGL.createInstancedMesh(rootMeshID);
assertEquals(C_WebGL.getNumInstances(rootMeshID), 3);

// Cleanup
C_WebGL.destroyInstancedMeshes(rootMeshID);
assertEquals(C_WebGL.getNumInstances(rootMeshID), 0);
});

it("should return zero if no prototype exists with the given ID", () => {
assertEquals(C_WebGL.getNumInstances("invalidRootMeshID"), 0);
});

after(() => C_WebGL.destroyInstancedMeshes(rootMeshID));
});
1 change: 1 addition & 0 deletions Tests/run-renderer-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const testSuites = {
"API/C_WebAudio/isAudioAvailable.js",
"API/C_WebAudio/isAudioContextInitialized.js",
],
C_WebGL: ["API/WebGL/createInstancedMesh.js", "API/WebGL/getNumInstances.js", "API/WebGL/destroyInstancedMeshes.js"],
C_Macro: ["API/Macro/restoreMacroCache.js"],
C_Resources: ["API/Resources/test-resource-builtin.js"],
};
Expand Down