Skip to content

Commit

Permalink
Add deepMerge (#15943)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryantrem authored Dec 4, 2024
1 parent 5e2c95f commit db0dbc7
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 56 deletions.
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) => {
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

0 comments on commit db0dbc7

Please sign in to comment.