Skip to content
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

Animations, reframeCamera, pick multiple models #16145

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

alexandremottet
Copy link
Contributor

@alexandremottet alexandremottet commented Feb 5, 2025

Add support for multiple models for :

  • animations
  • _reframeCamera with option to use a subset of models
  • _pick

Add support for multiple models for animations, reframing the camera and picking
@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 5, 2025

@RaananW RaananW requested a review from ryantrem February 5, 2025 14:21
*/
public get animations(): readonly string[] {
return this._activeModelBacking?.assetContainer.animationGroups.map((group) => group.name) ?? [];
return this._loadedModelsContainer.animationGroups.map((group) => group.name) ?? [];
Copy link
Member

Choose a reason for hiding this comment

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

I think the base Viewer class should remain single-model-centric in the public interface. The rest of the public interface basically operates on a single (active) model - playing an animation, selecting a material variant, etc. If we change this, then if there are multiple models you'd see animations for all the models in the animation list, but when playing an animation it would target the active model, which wouldn't work.

Can we leave this as is, and if a subclass needs to enumerate all animations across all models, this can still be done through the _loadedModels property?

if (this._activeModel) {
const model = this._activeModel?.assetContainer;
if (this._loadedModels.length > 0) {
const container = this._loadedModelsContainer;
Copy link
Member

Choose a reason for hiding this comment

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

Having the extra _loadedModelsContainer state seems like it is mostly here for convenience in enumerating all meshes, all animations, etc. But the added (redundant) state seems like it creates more opportunity for bugs and to me makes it a bit harder to understand the code.

Instead of adding this state, whenever we need to enumerate meshes/animations/etc. across multiple models, can we just do something like:

this._loadedModels.map((model) => model.meshes).flat().foreach((mesh) => {

I think this bit of code is simple enough to use as is and not introduce additional state (_loadedModelsContainer).

Comment on lines 1459 to 1471
protected _reframeCamera(interpolateCamera?: boolean): void;
protected _reframeCamera(interpolateCamera: boolean, models?: Model[]): void {
if (!models) {
const selectedAnimation = this._selectedAnimation === -1 ? 0 : this._selectedAnimation;
const worldBounds = this._activeModel?.getWorldBounds(selectedAnimation);
this._reframeCameraFromBounds(worldBounds, interpolateCamera);
return;
}

const container = models.length === this._loadedModelsBacking.length ? this._loadedModelsContainer : this._reduceModels(models);
const worldBounds = computeBoundingInfos(container);
this._reframeCameraFromBounds(worldBounds, interpolateCamera);
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think computing bounds without specifying an animation group (when there is at least one animation group) totally makes sense here. Depending on which animation group is active, the bounds of the model (even when paused on the first frame of the animation) can be wildly different (UFO model for example). I think it would make more sense to always include an animation group in the calculation when a model has animation groups.

To make this work, I think it would make sense to add a selectedAnimation to Model, and move ~half of the _selectedAnimation setter logic to this new model specific property (e.g. initializing the animation group). This way, every model with at least one animation group would have the concept of a selected animation and could be properly framed in the camera.

Once this is in place, then the logic for framing the camera could always be the same - just frame a set of models. It would default to _loadedModels, which in the case of the base/default Viewer would be the single loaded model. E.g.
protected _reframeCamera(interpolateCamera?: boolean, models = this._loadedModels)

@alexandremottet alexandremottet marked this pull request as draft February 6, 2025 17:00
@bjsplat
Copy link
Collaborator

bjsplat commented Feb 6, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 6, 2025

@@ -77,7 +77,7 @@
<div id="viewerContainer" style="width: 100vw; height: 100vh">
<babylon-viewer
engine="WebGPU"
source="https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/ufo.glb"
source="http://localhost:3000/glb/fox.glb"
Copy link
Member

Choose a reason for hiding this comment

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

Revert :)

get selectedAnimation() {
return selectedAnimation;
},
selectAnimation: (index: number) => {
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be fine for this to be a property setter. The class level selectAnimation is a function because it needed additional params.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree that it should be a setter but Model is marked as Readonly so I cannot change it.
This is why I left it as a function.
I could make change so this._activeModel return a Readonly<Model> and this._activeModelBacking return just a Model. Should I do it ?

packages/tools/viewer/src/viewer.ts Show resolved Hide resolved
@@ -1063,7 +1081,7 @@ export class Viewer implements IDisposable {
getWorldBounds: (animationIndex: number): Nullable<ViewerBoundingInfo> => {
let worldBounds: Nullable<ViewerBoundingInfo> = cachedWorldBounds[animationIndex];
if (!worldBounds) {
worldBounds = computeBoundingInfos(assetContainer, assetContainer.animationGroups[animationIndex]);
worldBounds = computeBoundingInfos(assetContainer.meshes, assetContainer.animationGroups[animationIndex]);
Copy link
Member

Choose a reason for hiding this comment

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

This could be:
worldBounds = computeModelsBoundingInfos(model);
and then we could remove computeBoundingInfos, right?

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 10, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Feb 10, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants