-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Viewer: add a way to match POV of other cameras (from gltf file loaded for example) #16076
base: master
Are you sure you want to change the base?
Conversation
1a18638
to
471b841
Compare
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
Snapshot stored with reference name: Test environment: To test a playground add it to the URL, for example: https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/16076/merge/index.html#WGZLGJ#4600 Links to test babylon tools with this snapshot: https://playground.babylonjs.com/?snapshot=refs/pull/16076/merge To test the snapshot in the playground with a playground ID add it after the snapshot query string: https://playground.babylonjs.com/?snapshot=refs/pull/16076/merge#BCU1XR#0 |
WebGL2 visualization test reporter: |
Visualization tests for WebGPU (Experimental) |
I was thinking from our initial discussions that we would simply translate these cameras directly to hotspot entries on the element (rather than adding a new cameras concept). Any reason not to go that route? If we do, then I think a few changes to this PR would be good:
|
Not really any specific reason. Initially, during development, I wanted a distinct visual indication so the user could understand that this behavior in the viewer was tied to the cameras in the scene. But once I finished my code and realized that focusing on a hotspot or matching a camera’s POV ultimately relies on the same principle (an interpolation) I understood that we could merge the two. :) That said, I’m still wondering how we could clarify for users where the names and values of these "undeclared hotspots" originate. |
I had a similar feeling about that, so it works perfectly for me.
Yes, yes, yes! Having the bounding info in
We will aiming for behavior 4, so i'll try to do something that stay consistent with the Babylon viewer approach and let us fit with our needed 👍 |
packages/tools/viewer/src/viewer.ts
Outdated
*/ | ||
public async getArcRotateCameraInfos(camera: Camera): Promise<Nullable<ViewerArcRotateCameraInfos>> { | ||
await import("core/Culling/ray"); | ||
const ray = camera.getForwardRay(100, camera.getWorldMatrix(), camera.globalPosition); |
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 is equivalent to just getForwardRay()
(which we do in another part of this file).
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 seems that the default origin of the forward ray is actually the camera position rather than the camera’s global position.
Ok, I guess in this case we can just check |
Remove useless ray parameters Factor code on radius calculation Use _tempVectors for computation Handle mesh predicate as parameter of function getArcRotateCameraInfos
Add property camerasAsHotSpots to active cameras hot spots Fix camera forward ray
Add bounding infos into the model
* @param offset The directional offset between the source position and the target position | ||
* @returns The alpha angle in radians | ||
*/ | ||
public static computeAlpha(offset: Vector3): number { |
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.
Since these functions don't access this
, can we just make them module level functions?
export type ModelBoundingInfo = { | ||
worldExtents: { | ||
min: Vector3; | ||
max: Vector3; | ||
}; | ||
worldSize: Vector3; | ||
worldCenter: Vector3; | ||
}; |
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.
A few thoughts on this new type:
- We don't expose
Vector3
directly for other parts of the interface (like hotspots). Could we switch these to[x: number, y: number, z: number]
like we have elsewhere? - Make it readonly either in the definition or in the usage (since it is a stored/persistent value, not a transient value that is just returned from a function with a single usage)? I think definition would be ok, but either works.
- To make this more re-usable (in case in the future we want to use it for something not in world space, or for something other than models), could we rename to not include "world" or "model"?
export type ModelBoundingInfo = { | |
worldExtents: { | |
min: Vector3; | |
max: Vector3; | |
}; | |
worldSize: Vector3; | |
worldCenter: Vector3; | |
}; | |
export type BoundingInfo = { | |
extents: Readonly<{ | |
readonly min: readonly [x: number, y: number, z: number]; | |
readonly max: readonly [x: number, y: number, z: number]; | |
}>; | |
readonly size: readonly [x: number, y: number, z: number]; | |
readonly center: readonly [x: number, y: number, z: number]; | |
}; |
And then in the usage below, have worldBounds?: BoundingInfo
set hotSpots(value: Record<string, HotSpot>) { | ||
this._hotSpots = value; | ||
} | ||
|
||
get hotSpots() { | ||
return this._hotSpots; | ||
} |
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.
Any reason for changing this to have a getter/setter?
* A string value that encodes one or more hotspots. | ||
*/ | ||
private _hotSpots: Record<string, HotSpot> = {}; | ||
|
||
@property({ |
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.
Did we lose this doc comment?
@@ -1125,6 +1136,27 @@ export abstract class ViewerElement<ViewerClass extends Viewer = Viewer> extends | |||
this._dispatchCustomEvent("animationprogresschange", (type) => new Event(type)); | |||
}); | |||
|
|||
details.scene.onNewCameraAddedObservable.add((camera) => { | |||
if (camera.uniqueId !== details.camera.uniqueId && this.camerasAsHotSpots) { |
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.
Can we just compare the camera instance rather than the uniqueId?
if (camera.uniqueId !== details.camera.uniqueId && this.camerasAsHotSpots) { | |
if (camera !== details.camera && this.camerasAsHotSpots) { |
@@ -1125,6 +1136,27 @@ export abstract class ViewerElement<ViewerClass extends Viewer = Viewer> extends | |||
this._dispatchCustomEvent("animationprogresschange", (type) => new Event(type)); | |||
}); | |||
|
|||
details.scene.onNewCameraAddedObservable.add((camera) => { | |||
if (camera.uniqueId !== details.camera.uniqueId && this.camerasAsHotSpots) { |
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.
With the current impl, if you load some models and then toggle camerasAsHotSpots
, it will not actually affect the hotSpots
. I think it would be a good improvement to factor our the handlers to functions like addCameraHotSpot
and removeCameraHotSpot
and then call them for these observables, but also when camerasAsHotSpots
is enabled/disabled (probably in update
if the property has changed).
/** | ||
* Updates the bounding info for the model by computing its maximum extents, size, and center considering animation, skeleton, and morph targets. | ||
*/ | ||
private _updateModelBoundingInfo(): void { |
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.
Instead of having this function mutate this._modelInfo
, can we:
- Move it out to a utility function (within this file, but not inside the class) that takes an
AssetContainer
and returns aBoundingInfo
- Call it in
loadModel
and set the bounding info for the model when it is loaded - Make the
worldBounds
property of theModelInfo
part of the readonly section (since it will always be available as it would be computed when the model loads)
* @param predicate Used to define predicate for selecting meshes and instances (if exist) | ||
* @returns An object containing the alpha, beta, radius and target properties, or null if no model found | ||
*/ | ||
public async getArcRotateCameraInfos(camera: Camera, predicate?: MeshPredicate): Promise<Nullable<ViewerArcRotateCameraInfos>> { |
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.
As we discussed a bit on the side, can we move this now to be a private function inside viewerElement.ts
since ViewerElement
will now have access to the bounding info through the model info?
This change introduces a way to interpolate the viewer's default camera to other existing cameras in the scene. The primary goal of this feature is to enable matching the POV of cameras defined in a GLTF file, without switching the active camera to try to keep a "simple" experience for non-expert users.
getArcRotateCameraInfos
function compute thealpha
,beta
, andradius
angles, as well as the target point for a camera that is not anArcRotateCamera
.meshes
fromAssetContainer
is found, the first hit point is used as the target. If no intersection is detected, a fallback target is calculated by projecting the distance between the camera and theAssetContainer
world center along the forward ray.