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

Various small Viewer fixes #15943

Merged
merged 2 commits into from
Dec 4, 2024
Merged
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
26 changes: 26 additions & 0 deletions packages/dev/core/src/Misc/deepMerger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// https://stackoverflow.com/a/48218209
/**
* Merges a series of objects into a single object, deeply.
* @param objects The objects to merge (objects later in the list take precedence).
* @returns The merged object.
*/
export function deepMerge<T extends Object>(...objects: T[]): T {
const isRecord = (obj: unknown): obj is Record<string, unknown> => !!obj && typeof obj === "object";

return objects.reduce<Record<string, unknown>>((prev, obj) => {
Object.keys(obj).forEach((key) => {
RaananW marked this conversation as resolved.
Show resolved Hide resolved
const pVal = prev[key];
const oVal = (obj as Record<string, unknown>)[key];

if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
} else if (isRecord(pVal) && isRecord(oVal)) {
prev[key] = deepMerge(pVal, oVal);
} else {
prev[key] = oVal;
}
});

return prev;
}, {}) as T;
}
1 change: 1 addition & 0 deletions packages/dev/core/src/Misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export * from "./logger";
export * from "./typeStore";
export * from "./filesInputStore";
export * from "./deepCopier";
export * from "./deepMerger";
export * from "./pivotTools";
export * from "./precisionDate";
export * from "./screenshotTools";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,28 @@ import type { INode, IMeshPrimitive, IMesh } from "../glTFLoaderInterfaces";
import type { IKHRMaterialVariants_Mapping, IKHRMaterialVariants_Variant, IKHRMaterialVariants_Variants } from "babylonjs-gltf2interface";
import type { TransformNode } from "core/Meshes/transformNode";
import { registerGLTFExtension, unregisterGLTFExtension } from "../glTFLoaderExtensionRegistry";
import type { GLTFLoaderExtensionOptions } from "../../glTFFileLoader";
import type { MaterialVariantsController } from "../../glTFFileLoader";

const NAME = "KHR_materials_variants";

// NOTE: This "backward" type definition is needed due to the way namespaces are handled in the UMD bundle.
export type MaterialVariantsController = Parameters<Required<GLTFLoaderExtensionOptions[typeof NAME]>["onLoaded"]>[0];
export { MaterialVariantsController };

declare module "../../glTFFileLoader" {
// Define options related types here so they can be referenced in the options,
// but export the types at the module level. This ensures the types are in the
// correct namespace for UMD.
type MaterialVariantsController = {
/**
* The list of available variant names for this asset.
*/
readonly variants: readonly string[];

/**
* Gets or sets the selected variant.
*/
selectedVariant: string;
};

// eslint-disable-next-line jsdoc/require-jsdoc
export interface GLTFLoaderExtensionOptions {
/**
Expand All @@ -29,17 +43,7 @@ declare module "../../glTFFileLoader" {
* Defines a callback that will be called if material variants are loaded.
* @experimental
*/
onLoaded: (controller: {
/**
* The list of available variant names for this asset.
*/
readonly variants: readonly string[];

/**
* Gets or sets the selected variant.
*/
selectedVariant: string;
}) => void;
onLoaded: (controller: MaterialVariantsController) => void;
}>;
}
}
Expand Down
25 changes: 2 additions & 23 deletions packages/dev/loaders/src/glTF/2.0/glTFLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import { nodeAnimationData } from "./glTFLoaderAnimation";
import type { IObjectInfo } from "core/ObjectModel/objectModelInterfaces";
import { registeredGLTFExtensions, registerGLTFExtension, unregisterGLTFExtension } from "./glTFLoaderExtensionRegistry";
import type { GLTFExtensionFactory } from "./glTFLoaderExtensionRegistry";
import { deepMerge } from "core/Misc/deepMerger";
export { GLTFFileLoader };

interface TypedArrayLike extends ArrayBufferView {
Expand All @@ -101,28 +102,6 @@ interface IWithMetadata {
_internalMetadata: any;
}

// https://stackoverflow.com/a/48218209
function mergeDeep(...objects: any[]): any {
const isObject = (obj: any) => obj && typeof obj === "object";

return objects.reduce((prev, obj) => {
Object.keys(obj).forEach((key) => {
const pVal = prev[key];
const oVal = obj[key];

if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
} else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
} else {
prev[key] = oVal;
}
});

return prev;
}, {});
}

/**
* Helper class for working with arrays when loading the glTF asset
*/
Expand Down Expand Up @@ -907,7 +886,7 @@ export class GLTFLoader implements IGLTFLoader {
const babylonTransformNodeForSkin = node._babylonTransformNodeForSkin!;

// Merge the metadata from the skin node to the skinned mesh in case a loader extension added metadata.
babylonTransformNode.metadata = mergeDeep(babylonTransformNodeForSkin.metadata, babylonTransformNode.metadata || {});
babylonTransformNode.metadata = deepMerge(babylonTransformNodeForSkin.metadata, babylonTransformNode.metadata || {});

const skin = ArrayItem.Get(`${context}/skin`, this._gltf.skins, node.skin);
promises.push(
Expand Down
33 changes: 17 additions & 16 deletions packages/tools/viewer-alpha/src/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@ import { CubeTexture } from "core/Materials/Textures/cubeTexture";
import { Texture } from "core/Materials/Textures/texture";
import { Clamp } from "core/Maths/math.scalar.functions";
import { Matrix, Vector3 } from "core/Maths/math.vector";
import { Viewport } from "core/Maths/math.viewport";
import { GetHotSpotToRef } from "core/Meshes/abstractMesh.hotSpot";
import { CreateBox } from "core/Meshes/Builders/boxBuilder";
import { computeMaxExtents } from "core/Meshes/meshUtils";
import { BuildTuple } from "core/Misc/arrayTools";
import { AsyncLock } from "core/Misc/asyncLock";
import { deepMerge } from "core/Misc/deepMerger";
import { Observable } from "core/Misc/observable";
import { SnapshotRenderingHelper } from "core/Misc/snapshotRenderingHelper";
import { Scene } from "core/scene";
import { registerBuiltInLoaders } from "loaders/dynamic";
import { Viewport } from "core/Maths/math.viewport";
import { GetHotSpotToRef } from "core/Meshes/abstractMesh.hotSpot";
import { SnapshotRenderingHelper } from "core/Misc/snapshotRenderingHelper";
import { BuildTuple } from "core/Misc/arrayTools";

const toneMappingOptions = ["none", "standard", "aces", "neutral"] as const;
export type ToneMapping = (typeof toneMappingOptions)[number];
Expand Down Expand Up @@ -160,7 +161,7 @@ export type ViewerDetails = {
* @param screenY The y coordinate in screen space.
* @returns A PickingInfo if an object was picked, otherwise null.
*/
pick(screenX: number, screenY: number): Nullable<PickingInfo>;
pick(screenX: number, screenY: number): Promise<Nullable<PickingInfo>>;
};

export type ViewerOptions = Partial<
Expand Down Expand Up @@ -384,8 +385,8 @@ export class Viewer implements IDisposable {
const camera = new ArcRotateCamera("Viewer Default Camera", 0, 0, 1, Vector3.Zero(), scene);
camera.useInputToRestoreState = false;

scene.onPointerObservable.add((pointerInfo) => {
const pickingInfo = this._pick(pointerInfo.event.offsetX, pointerInfo.event.offsetY);
scene.onPointerObservable.add(async (pointerInfo) => {
const pickingInfo = await this._pick(pointerInfo.event.offsetX, pointerInfo.event.offsetY);
if (pickingInfo?.pickedPoint) {
const distance = pickingInfo.pickedPoint.subtract(camera.position).dot(camera.getForwardRay().direction);
// Immediately reset the target and the radius based on the distance to the picked point.
Expand Down Expand Up @@ -709,37 +710,36 @@ export class Viewer implements IDisposable {
this.onLoadingProgressChanged.notifyObservers();
}
};
delete options?.onProgress;

const originalOnMaterialVariantsLoaded = options?.pluginOptions?.gltf?.extensionOptions?.KHR_materials_variants?.onLoaded;
const onMaterialVariantsLoaded: typeof originalOnMaterialVariantsLoaded = (controller) => {
originalOnMaterialVariantsLoaded?.(controller);
this._materialVariantsController = controller;
};
delete options?.pluginOptions?.gltf?.extensionOptions?.KHR_materials_variants?.onLoaded;

options = {
...options,
const defaultOptions: LoadModelOptions = {
// Pass a progress callback to update the loading progress.
onProgress,
pluginOptions: {
...options?.pluginOptions,
gltf: {
// Enable transparency as coverage by default to be 3D Commerce compliant by default.
// https://doc.babylonjs.com/setup/support/3D_commerce_certif
transparencyAsCoverage: true,
...options?.pluginOptions?.gltf,
extensionOptions: {
...options?.pluginOptions?.gltf?.extensionOptions,
// eslint-disable-next-line @typescript-eslint/naming-convention
KHR_materials_variants: {
...options?.pluginOptions?.gltf?.extensionOptions?.KHR_materials_variants,
// Capture the material variants controller when it is loaded.
onLoaded: onMaterialVariantsLoaded,
},
},
},
},
// Pass a progress callback to update the loading progress.
onProgress,
};

options = deepMerge(defaultOptions, options ?? {});

this._loadModelAbortController?.abort("New model is being loaded before previous model finished loading.");
const abortController = (this._loadModelAbortController = new AbortController());

Expand Down Expand Up @@ -1121,7 +1121,8 @@ export class Viewer implements IDisposable {
this._details.model?.animationGroups.forEach((group) => (group.speedRatio = this._animationSpeed));
}

private _pick(screenX: number, screenY: number): Nullable<PickingInfo> {
private async _pick(screenX: number, screenY: number): Promise<Nullable<PickingInfo>> {
await import("core/Culling/ray");
if (this._details.model) {
const model = this._details.model;
// Refresh bounding info to ensure morph target and skeletal animations are taken into account.
Expand Down
5 changes: 2 additions & 3 deletions packages/tools/viewer-alpha/test/apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@
</div>
<script type="module" src="/packages/tools/viewer-alpha/src/index.ts"></script>
<script type="module">
import { Inspector } from "inspector/inspector";

const viewerElement = document.querySelector("babylon-viewer");
const lines = document.querySelectorAll("line");
const hotspotResult = { screenPosition: [0, 0], worldPosition: [0, 0, 0] };
Expand Down Expand Up @@ -237,7 +235,8 @@
};

let isInspectorVisible = false;
globalThis.onToggleInspector = () => {
globalThis.onToggleInspector = async () => {
const { Inspector } = await import("inspector/inspector");
console.log("toggle inspector");
if (isInspectorVisible) {
Inspector.Hide();
Expand Down