-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Add back ModelInstance class #12588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add back ModelInstance class #12588
Conversation
Thank you for the pull request, @lukemckinstry! ✅ We can confirm we have a CLA on file for you. |
packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js
Show resolved
Hide resolved
Thanks for getting this updated and itemized @lukemckinstry! Just a few notes on what we should make sure to test. Since the workflow was touched by some refactoring, we should confirm the following are still working for non-instances models:
We also need to check scaling with |
e3a2676
to
1e40c93
Compare
All these are resolved.
Scaling is fixed for regular models, |
I did not add these to the code yet
|
Yes. Let's (a) ignore any other value that it might be set to, and (b) document this behavior in model instances property.
Yes, although a |
I think that would be fine. |
window.startup = async function (Cesium) { | ||
"use strict"; | ||
//Sandcastle_Begin | ||
const viewer = new Cesium.Viewer("cesiumContainer"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukemckinstry Once the public API is where you'd like it, a reminder to clean up the Sandcastle example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apps/Sandcastle/gallery/3D Model Instancing Terrain.html
is the sandcastle I think is best to release. This has a new wind turbine glb added to the SampleData folder. I believe the CC license makes using it ok #12588 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukemckinstry Thanks for resolving the last few features!
I looked over the unit tests so far and left a few comments. Let me know if there are any concerns around those.
packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js
Outdated
Show resolved
Hide resolved
packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js
Outdated
Show resolved
Hide resolved
packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js
Outdated
Show resolved
Hide resolved
0.8146623373031616, 0, 0.5799355506896973, 0, 0, 0, 0, 20, 20, 20, | ||
]); | ||
|
||
const expectedTransformsBuffer = Buffer.createVertexBuffer({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this test is jumping through a few hoops to validate the buffer contents.
Instead of creating a new buffer, perhaps consider validating the output of RuntimeModelInstancingPipelineStage._getTransformsTypedArray
against expectedTransformsTypedArray
, and then checking runtimeNode.instancingTransformsBuffer
's usage
and byteLength
properties directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the crux of this test should be to make sure that when ModelInstancesUpdateStage.update
runs, if sceneGraph.modelInstances._instances
has changed, runtimeNode.instancingTransformsBuffer
is updated accordingly.
The output of RuntimeModelInstancingPipelineStage._getTransformsTypedArray
(when run on the changed values of sceneGraph.modelInstances._instances
in ModelInstancesUpdateStage.update
) is not stored anywhere, so I thought testing the buffer made sense.
packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js
Outdated
Show resolved
Hide resolved
packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js
Show resolved
Hide resolved
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
That works. But it raises some question about API design
|
@lukemckinstry and I discussed this offline— We'll still need Updates can be managed on the |
This comment was marked as resolved.
This comment was marked as resolved.
@javagl I think mostly for API simplicity. We're accounting for double precision for any instance locations on the globe. So a localized |
The question was unrelated to precision. (Precision is a tricky issue here, but ... unrelated for now). I rather thought about cases where people want to create instances in a known, local coordinate space. Think about an airport runway where 200 lights are left and right of the runway, along a straight line, 10 meters apart. And then, users want to put these instanced models at a certain position on the globe, and use the In terms of convenience, the "best" API certainly depends on the use case. For example, in this screenshot, I used lat/lon/height as the input. In other cases, people might only have the local transforms. There may also be cases where the instancing information is given as TRS properties. And people will have to write quite a bit of boilerplate code to assemble these into Matrix4 objects, put them into the array, and maybe squeeze in the ENU-to-FF-matrix for the desired geolocation here. (API design is often sort of a trade-off ... about shifting the responsibility for doing things (correctly!) between the implementor and the user. I think that the API should be easy to use correctly, and hard to use incorrectly. A seemingly(!) very specific aspect here is: People will create instances. They will set the |
I've seen that there are a bunch of conflicts. Most of them are in ModelSceneGraph.prototype.buildDrawCommands = function (frameState) {
const modelRenderResources = this.buildRenderResources(frameState);
this.computeBoundingVolumes(modelRenderResources);
this.createDrawCommands(modelRenderResources, frameState);
}; with This was only an attempt to follow the "boy scout rule". It was not a change that affected the funcitonality. It was supposed to help isolating and fixing that issue (and maybe even to cover it with specs). Now... the current state of the This is done here: https://github.com/CesiumGS/cesium/tree/model-instance-v2-tests Maybe I can allocate time for another dedicated cleanup pass for the |
From a quick test, it looks like the current state also works for a model with a non-identity |
be caused by attempts to work around that bug, but who knows)
These are leftover from the refactor, we will resolve these.
This looks good. The runtime model instance feature appears to be working correctly & the same as this branch
Does the commit fix the bounding volume issues you are referring to. Or is it just an incremental step towards helping isolate and later fix?
That is good to hear. If you have an example sandcastle can you share the link? I had issues when I briefly tried testing this earlier but ran out of time to verify.
Worth looking into further and discussing. |
This comment was marked as resolved.
This comment was marked as resolved.
This commit did not change the functionality (i.e. also not fix the issue). The reason for that change was that
(c.f. "cohesive"). So I wanted to break it into The fix for the bounding volume computation would then go into ... drumroll ... Once the changes from this PR are in, I'll probably ...
I've been doing some tests that had actually been more related to the bounding volume issue, but also to ... all other issues that may be related to "transforms" in one way or another, and I ran some tests with these on the instancing branch as well. These tests included the creation of different flavors of a "unit cube" with a very elaborate texture, to see whether something is right or wrong... However, here is a sandcastle that has a flag
The instance transforms are just a grid of I changed the URL to the MilkTruck - it doesn't matter for now, and also looks neat: const viewer = new Cesium.Viewer("cesiumContainer");
const model = await Cesium.Model.fromGltfAsync({
//url: "../../SampleData/models/unitCube/unitCube_T_TR.glb",
url: "../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
});
viewer.scene.primitives.add(model);
const useModelMatrix = false;
const geoTransform = Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(-75.1652, 39.9526, 20)
);
if (useModelMatrix) {
model.modelMatrix = geoTransform;
}
const size = 5;
const minTranslationX = 0;
const maxTranslationX = 20;
const minTranslationY = 0;
const maxTranslationY = 20;
const minTranslationZ = 0;
const maxTranslationZ = 20;
const minRotationDegX = 0;
const maxRotationDegX = 90;
const minRotationDegY = 0;
const maxRotationDegY = 90;
const minRotationDegZ = 0;
const maxRotationDegZ = 90;
const minScaleX = 1;
const maxScaleX = 2;
const minScaleY = 1;
const maxScaleY = 2;
const minScaleZ = 1;
const maxScaleZ = 2;
function matrixFromAngleDegX(angleDegX) {
const m = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(angleDegX));
return Cesium.Matrix4.fromRotation(m);
}
function matrixFromAngleDegY(angleDegY) {
const m = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(angleDegY));
return Cesium.Matrix4.fromRotation(m);
}
function matrixFromAngleDegZ(angleDegZ) {
const m = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(angleDegZ));
return Cesium.Matrix4.fromRotation(m);
}
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
for (let z = 0; z < size; z++) {
const alphaX = x / (size - 1);
const alphaY = y / (size - 1);
const alphaZ = z / (size - 1);
const translationX =
minTranslationX + alphaX * (maxTranslationX - minTranslationX);
const translationY =
minTranslationY + alphaY * (maxTranslationY - minTranslationY);
const translationZ =
minTranslationZ + alphaZ * (maxTranslationZ - minTranslationZ);
const translation = new Cesium.Cartesian3(
translationX,
translationY,
translationZ
);
const translationMatrix = Cesium.Matrix4.fromTranslation(translation);
const rotationDegX =
minRotationDegX + alphaX * (maxRotationDegX - minRotationDegX);
const rotationDegY =
minRotationDegY + alphaY * (maxRotationDegY - minRotationDegY);
const rotationDegZ =
minRotationDegZ + alphaZ * (maxRotationDegZ - minRotationDegZ);
const rotationMatrix = Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY);
Cesium.Matrix4.multiply(
rotationMatrix,
matrixFromAngleDegX(rotationDegX),
rotationMatrix
);
Cesium.Matrix4.multiply(
rotationMatrix,
matrixFromAngleDegY(rotationDegY),
rotationMatrix
);
Cesium.Matrix4.multiply(
rotationMatrix,
matrixFromAngleDegZ(rotationDegZ),
rotationMatrix
);
const scaleX = minScaleX + alphaX * (maxScaleX - minScaleX);
const scaleY = minScaleY + alphaY * (maxScaleY - minScaleY);
const scaleZ = minScaleZ + alphaZ * (maxScaleZ - minScaleZ);
const scale = new Cesium.Cartesian3(scaleX, scaleY, scaleZ);
const scaleMatrix = Cesium.Matrix4.fromScale(scale);
let instanceTransform = Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY);
Cesium.Matrix4.multiply(
instanceTransform,
translationMatrix,
instanceTransform
);
Cesium.Matrix4.multiply(
instanceTransform,
rotationMatrix,
instanceTransform
);
Cesium.Matrix4.multiply(
instanceTransform,
scaleMatrix,
instanceTransform
);
// When not using the modelMatrix, bake the geoTransform
// into the instanceTransform
if (!useModelMatrix) {
Cesium.Matrix4.multiply(
geoTransform,
instanceTransform,
instanceTransform
);
}
model.instances.add(instanceTransform);
}
}
}
model.readyEvent.addEventListener(() => {
console.log(model.boundingSphere);
//viewer.scene.camera.flyToBoundingSphere(model.boundingSphere);
});
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
1253512.5232461668,
-4732922.214567729,
4074115.474546098
),
orientation: new Cesium.HeadingPitchRoll(
2.205737333179613,
-0.7255022564055849,
6.283181225638178
),
}); Note: The appearance of the instances is different, depending on whether the model matrix is used. This might just be another flavour of #12310 ... |
try { | ||
model = await Cesium.Model.fromGltfAsync({ | ||
//url: "../../SampleData/models/GroundVehicle/GroundVehicle.glb", | ||
url: "../../SampleData/models/wind_turbine.glb", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to be in the repo yet. I'm just mentioning it. It could make sense to add this last, when all other decisions are made, to not bloat the git history with possible additions/removals of large binaries. (BTW: Also make sure to check the model copyright).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I agree it makes sense to wait to add the model until the end and we are sure we want to use them.
The model is free to download here https://www.fab.com/listings/3f826cd9-1caf-4a6a-9e8c-42118c1297cf
As detailed in this comment #12588 (comment) I initially used a much larger model https://www.fab.com/listings/c6efa5d8-d94d-4da0-8747-09e0d4af7fb1 but this caused performance issues.
Both models have the Creative Commons Attribution (CC BY 4.0) license, so to my understanding we are free to use the models as long as we provide attribution
I'm also experiencing this issue, and it's quite impactful for my workflow, especially when dealing with multiple selections. I agree with the original request; being able to highlight selected instances, similar to how it worked in earlier versions (e.g., 1.96 with |
This comment was marked as resolved.
This comment was marked as resolved.
eab030f
to
99bb138
Compare
ac6c8fc
to
e611a7b
Compare
@lukemckinstry testing with these new commits, I noticed a problem. If I have several instances, one with the correct position and another, with Matrix4.ZERO. And when I try to pick, it ends up selecting the wrong instance.
By removing it from ModelInstancesUpdateStage.js, it works again.
|
Can you explain your reasoning for supplying the zero transform in model.instances.add(Matrix4.ZERO)? The design we had in mind is that each instance would have some non-zero and non-identity transform. |
I've seen better performance from having multiple instances already created and simply repositioning them, rather than creating and removing them every time. Since I have thousands, I only instantiate what appears on the screen. |
Another problem I encountered is that if you remove and add another item, it doesn't "remove" or "add"
|
I don't know why there should be a requirement for "non-identity", but haven't been tracking the development here closely (recently). That may depend on whether the transforms are ~"expected to be in some 'local' space or not", and I assume that this will be pointed out clearly in the documentation. The point about the 'zero' transform is an interesting and valid one, and likely what rudacs referred to: I think that it is not uncommon to use a 'scale of 0.0' to essentially hide something. And there are several trade-offs, where the question of what is 'good' or 'bad' strongly depends on the usage pattern. Imagine that you want to start showing 0 instances, then incrementally show 1...10000 instances, then incrementally hide them again (think of something like ~"time-dependent construction sites on the globe" or so). In this case, using 'zero' to make an instance invisible is certainly preferable in terms of performance. The alternative would probably be to start with an instances buffer of length 0, and then do 10000 re-allocations (including copies) to expand that, and then another 10000 re-allocations incrementally shrink that buffer. That's one way of keeping the memory bus busy. Anticipating this difference, from an idealistic, longer-term engineering perspective, could lead to the consideration to differentiate beween |
Thanks for pointing out the behavior you see @rudacs with regards to picking when an instance transform is set to Matrix4.ZERO. And thanks as well for pointing out the issue around the ModelInstanceCollection
Thanks for making this clear @javagl. The design of the current implementation so far:
So definitely seems possible to optimize design in the manner you point out as possible to better fit this use case of showing/hiding instances by setting scale to 0. |
I was implementing a show inside the ModelInstance, passing a variable to the FS and discarding it. What do you think? Would it be ideal? I also implemented it in my branch to allow changing the color of the instance. |
Changing the position also has no effect.
This partially solved it, but I realized that it should actually be adding and removing instances, which would be the same amount. So it would be like updating the position; the vertex should pass through. But if I keep it here, it doesn't work.
I'm trying to see the solution for this. |
Description
ModelInstance and ModelInstanceCollection
Supply transformation matrices (Matrix4) to the Model for each instance by
model.instance.add(transform)
options.instances
toModel.loadGltfAsync()
In this way, the transformation matrix for each instance can be computed from a cartographic coordinate as shown below.
Example:
Shader logic
Cesium already supported instancing from glTF (specifies instancing in model space) and i3DM (specifies instancing in world space). Because this feature wanted to set up instancing in world space, we followed i3DM logic, including for the shader.
i3DM
Runtime instancing
ModelSceneGraph refactor
ToDo:
When instances are supplied to Model, modelMatrix must be the identify matrix.(this is supported)ext_mesh_gpu_instance extension
, throw developer errorPerformance notes
gltf-transform optimize wind_turbine_small.glb wind_turbine_optimize.glb --texture-compress webp
or glTF ReportIssue number and link
#10846
Testing plan
instances
supplied to Model.fromGltfAsync link AAuthor checklist
CONTRIBUTORS.md
CHANGES.md
with a short summary of my change