Skip to content

Commit

Permalink
Lighting and other Viewer improvements (#15606)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryantrem authored Sep 23, 2024
1 parent e9cc43c commit 865f503
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 120 deletions.
1 change: 1 addition & 0 deletions packages/dev/core/src/Animations/animationGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { EngineStore } from "../Engines/engineStore";
import type { AbstractScene } from "../abstractScene";
import { Tags } from "../Misc/tags";
import type { AnimationGroupMask } from "./animationGroupMask";
import "./animatable";

/**
* This class defines the direct association between an animation and a target
Expand Down
1 change: 1 addition & 0 deletions packages/dev/loaders/src/STL/stlFileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { registerSceneLoaderPlugin } from "core/Loading/sceneLoader";
import { AssetContainer } from "core/assetContainer";
import type { Scene } from "core/scene";
import { STLFileLoaderMetadata } from "./stlFileLoader.metadata";
import "core/Materials/standardMaterial";

declare module "core/Loading/sceneLoader" {
// eslint-disable-next-line jsdoc/require-jsdoc
Expand Down
45 changes: 24 additions & 21 deletions packages/dev/loaders/src/glTF/2.0/glTFLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { Animation } from "core/Animations/animation";
import type { IAnimatable } from "core/Animations/animatable.interface";
import type { IAnimationKey } from "core/Animations/animationKey";
import { AnimationKeyInterpolation } from "core/Animations/animationKey";
import { AnimationGroup } from "core/Animations/animationGroup";
import type { AnimationGroup } from "core/Animations/animationGroup";
import { Bone } from "core/Bones/bone";
import { Skeleton } from "core/Bones/skeleton";
import { Material } from "core/Materials/material";
Expand Down Expand Up @@ -1595,30 +1595,33 @@ export class GLTFLoader implements IGLTFLoader {
return promise;
}

this._babylonScene._blockEntityCollection = !!this._assetContainer;
const babylonAnimationGroup = new AnimationGroup(animation.name || `animation${animation.index}`, this._babylonScene);
babylonAnimationGroup._parentContainer = this._assetContainer;
this._babylonScene._blockEntityCollection = false;
animation._babylonAnimationGroup = babylonAnimationGroup;
// eslint-disable-next-line @typescript-eslint/naming-convention
return import("core/Animations/animationGroup").then(({ AnimationGroup }) => {
this._babylonScene._blockEntityCollection = !!this._assetContainer;
const babylonAnimationGroup = new AnimationGroup(animation.name || `animation${animation.index}`, this._babylonScene);
babylonAnimationGroup._parentContainer = this._assetContainer;
this._babylonScene._blockEntityCollection = false;
animation._babylonAnimationGroup = babylonAnimationGroup;

const promises = new Array<Promise<unknown>>();
const promises = new Array<Promise<unknown>>();

ArrayItem.Assign(animation.channels);
ArrayItem.Assign(animation.samplers);
ArrayItem.Assign(animation.channels);
ArrayItem.Assign(animation.samplers);

for (const channel of animation.channels) {
promises.push(
this._loadAnimationChannelAsync(`${context}/channels/${channel.index}`, context, animation, channel, (babylonTarget, babylonAnimation) => {
babylonTarget.animations = babylonTarget.animations || [];
babylonTarget.animations.push(babylonAnimation);
babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTarget);
})
);
}
for (const channel of animation.channels) {
promises.push(
this._loadAnimationChannelAsync(`${context}/channels/${channel.index}`, context, animation, channel, (babylonTarget, babylonAnimation) => {
babylonTarget.animations = babylonTarget.animations || [];
babylonTarget.animations.push(babylonAnimation);
babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTarget);
})
);
}

return Promise.all(promises).then(() => {
babylonAnimationGroup.normalize(0);
return babylonAnimationGroup;
return Promise.all(promises).then(() => {
babylonAnimationGroup.normalize(0);
return babylonAnimationGroup;
});
});
}

Expand Down
4 changes: 2 additions & 2 deletions packages/public/@babylonjs/viewer-alpha/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ To use the higher level `HTML3DElement` you can import the `@babylonjs/viewer` m
<script type="module">
import '@babylonjs/viewer';
</script>
<babylon-viewer src="https://playground.babylonjs.com/scenes/BoomBox.glb"></babylon-viewer>
<babylon-viewer source="https://playground.babylonjs.com/scenes/BoomBox.glb"></babylon-viewer>
</body>
</html>
```
Expand All @@ -46,7 +46,7 @@ If you want to use the viewer directly in a browser without any build tools, you
<html lang="en">
<body>
<script type="module" src="https://unpkg.com/@babylonjs/viewer@preview/dist/babylon-viewer.esm.min.js"></script>
<babylon-viewer src="https://playground.babylonjs.com/scenes/BoomBox.glb"></babylon-viewer>
<babylon-viewer source="https://playground.babylonjs.com/scenes/BoomBox.glb"></babylon-viewer>
</body>
</html>
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
}
</style>
<body>
<script type="module" src="../../../dist/babylon-viewer.esm.js"></script>
<babylon-viewer src="https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/ufo.glb" env="https://assets.babylonjs.com/environments/ulmerMuenster.env">
<script type="module" src="../../../dist/babylon-viewer.esm.min.js"></script>
<babylon-viewer source="https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/ufo.glb" environment="https://assets.babylonjs.com/environments/ulmerMuenster.env">
</babylon-viewer>
</body>
</html>
82 changes: 53 additions & 29 deletions packages/tools/viewer-alpha/src/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@ import { PBRMaterial } from "core/Materials/PBR/pbrMaterial";
import { CubeTexture } from "core/Materials/Textures/cubeTexture";
import { Texture } from "core/Materials/Textures/texture";
import { Color4 } from "core/Maths/math.color";
import { Scalar } from "core/Maths/math.scalar";
import { Clamp } from "core/Maths/math.scalar.functions";
import { Vector3 } from "core/Maths/math.vector";
import { CreateBox } from "core/Meshes/Builders/boxBuilder";
import { AsyncLock } from "core/Misc/asyncLock";
import { Observable } from "core/Misc/observable";
import { Scene } from "core/scene";
import { registerBuiltInLoaders } from "loaders/dynamic";

// TODO: Dynamic imports?
import "core/Animations/animatable";

function createSkybox(scene: Scene, camera: Camera, environmentTexture: CubeTexture, blur: number): Mesh {
const hdrSkybox = CreateBox("hdrSkyBox", undefined, scene);
const hdrSkyboxMaterial = new PBRMaterial("skyBox", scene);
Expand Down Expand Up @@ -150,6 +147,7 @@ export class Viewer implements IDisposable {
private readonly _autoRotationBehavior: AutoRotationBehavior;
private readonly _renderLoopController: IDisposable;
private _skybox: Nullable<Mesh> = null;
private _light: Nullable<HemisphericLight> = null;

private _isDisposed = false;

Expand Down Expand Up @@ -181,7 +179,7 @@ export class Viewer implements IDisposable {
this._autoRotationBehavior = this._camera.getBehaviorByName("AutoRotation") as AutoRotationBehavior;

// Load a default light, but ignore errors as the user might be immediately loading their own environment.
this.resetEnvironmentAsync().catch(() => {});
this.resetEnvironment().catch(() => {});

// TODO: render at least back ground. Maybe we can only run renderloop when a mesh is loaded. What to render until then?
const render = () => {
Expand Down Expand Up @@ -220,7 +218,7 @@ export class Viewer implements IDisposable {
}

public set selectedAnimation(value: number) {
value = Math.round(Scalar.Clamp(value, -1, this.animations.length - 1));
value = Math.round(Clamp(value, -1, this.animations.length - 1));
if (value !== this._selectedAnimation) {
const startAnimation = this.isAnimationPlaying;
if (this._activeAnimation) {
Expand Down Expand Up @@ -307,19 +305,19 @@ export class Viewer implements IDisposable {
* @param options The options to use when loading the model.
* @param abortSignal An optional signal that can be used to abort the loading process.
*/
public async loadModelAsync(source: string | File | ArrayBufferView, options?: LoadAssetContainerOptions, abortSignal?: AbortSignal): Promise<void> {
await this._updateModelAsync(source, options, abortSignal);
public async loadModel(source: string | File | ArrayBufferView, options?: LoadAssetContainerOptions, abortSignal?: AbortSignal): Promise<void> {
await this._updateModel(source, options, abortSignal);
}

/**
* Resets the model to an empty scene.
* Unloads the current 3D model if one is loaded.
* @param abortSignal An optional signal that can be used to abort the reset.
*/
public async resetModelAsync(abortSignal?: AbortSignal): Promise<void> {
await this._updateModelAsync(undefined, undefined, abortSignal);
public async resetModel(abortSignal?: AbortSignal): Promise<void> {
await this._updateModel(undefined, undefined, abortSignal);
}

private async _updateModelAsync(source: string | File | ArrayBufferView | undefined, options?: LoadAssetContainerOptions, abortSignal?: AbortSignal): Promise<void> {
private async _updateModel(source: string | File | ArrayBufferView | undefined, options?: LoadAssetContainerOptions, abortSignal?: AbortSignal): Promise<void> {
this._throwIfDisposedOrAborted(abortSignal);

this._loadModelAbortController?.abort("New model is being loaded before previous model finished loading.");
Expand All @@ -328,10 +326,10 @@ export class Viewer implements IDisposable {
await this._loadModelLock.lockAsync(async () => {
this._throwIfDisposedOrAborted(abortSignal, abortController.signal);
this._details.model?.dispose();
this._details.model = null;
this.selectedAnimation = -1;

try {
this._details.model = null;
if (source) {
this._details.model = await loadAssetContainerAsync(source, this._details.scene, options);
this._details.model.animationGroups.forEach((group) => group.stop());
Expand All @@ -340,6 +338,7 @@ export class Viewer implements IDisposable {
}

this._updateCamera();
this._updateLight();
this._applyAnimationSpeed();
this.onModelChanged.notifyObservers();
} catch (e) {
Expand All @@ -357,19 +356,19 @@ export class Viewer implements IDisposable {
* @param options The options to use when loading the environment.
* @param abortSignal An optional signal that can be used to abort the loading process.
*/
public async loadEnvironmentAsync(url: string, options?: {}, abortSignal?: AbortSignal): Promise<void> {
await this._updateEnvironmentAsync(url, options, abortSignal);
public async loadEnvironment(url: string, options?: {}, abortSignal?: AbortSignal): Promise<void> {
await this._updateEnvironment(url, options, abortSignal);
}

/**
* Resets the environment to a simple hemispheric light.
* Unloads the current environment if one is loaded.
* @param abortSignal An optional signal that can be used to abort the reset.
*/
public async resetEnvironmentAsync(abortSignal?: AbortSignal): Promise<void> {
await this._updateEnvironmentAsync(undefined, undefined, abortSignal);
public async resetEnvironment(abortSignal?: AbortSignal): Promise<void> {
await this._updateEnvironment(undefined, undefined, abortSignal);
}

private async _updateEnvironmentAsync(url: Nullable<string | undefined>, options?: {}, abortSignal?: AbortSignal): Promise<void> {
private async _updateEnvironment(url: Nullable<string | undefined>, options?: {}, abortSignal?: AbortSignal): Promise<void> {
this._throwIfDisposedOrAborted(abortSignal);

this._loadEnvironmentAbortController?.abort("New environment is being loaded before previous environment finished loading.");
Expand All @@ -378,14 +377,12 @@ export class Viewer implements IDisposable {
await this._loadEnvironmentLock.lockAsync(async () => {
this._throwIfDisposedOrAborted(abortSignal, abortController.signal);
this._environment?.dispose();
this._environment = null;
this._details.scene.autoClear = true;

try {
this._environment = await new Promise<IDisposable>((resolve, reject) => {
if (!url) {
const light = new HemisphericLight("hemilight", Vector3.Up(), this._details.scene);
this._details.scene.autoClear = true;
resolve(light);
} else {
if (url) {
this._environment = await new Promise<IDisposable>((resolve, reject) => {
const cubeTexture = CubeTexture.CreateFromPrefilteredData(url, this._details.scene);
this._details.scene.environmentTexture = cubeTexture;

Expand Down Expand Up @@ -416,10 +413,11 @@ export class Viewer implements IDisposable {
reject(new Error("Failed to load environment texture."));
}
});
}
});
}

this.onEnvironmentChanged.notifyObservers();
});
this._updateLight();
this.onEnvironmentChanged.notifyObservers();
} catch (e) {
this.onEnvironmentError.notifyObservers(e);
throw e;
Expand Down Expand Up @@ -474,7 +472,6 @@ export class Viewer implements IDisposable {
this._isDisposed = true;
}

// copy/paste from sandbox and scene helpers
private _updateCamera(): void {
// Enable camera's behaviors
this._camera.useFramingBehavior = true;
Expand Down Expand Up @@ -522,6 +519,33 @@ export class Viewer implements IDisposable {
updateSkybox(this._skybox, this._camera);
}

private _updateLight() {
let shouldHaveDefaultLight: boolean;
if (!this._details.model) {
shouldHaveDefaultLight = false;
} else {
const hasModelProvidedLights = this._details.model.lights.length > 0;
const hasImageBasedLighting = !!this._environment;
const hasMaterials = this._details.model.materials.length > 0;
const hasNonPBRMaterials = this._details.model.materials.some((material) => !(material instanceof PBRMaterial));

if (hasModelProvidedLights) {
shouldHaveDefaultLight = false;
} else {
shouldHaveDefaultLight = !hasImageBasedLighting || !hasMaterials || hasNonPBRMaterials;
}
}

if (shouldHaveDefaultLight) {
if (!this._light) {
this._light = new HemisphericLight("defaultLight", Vector3.Up(), this._details.scene);
}
} else {
this._light?.dispose();
this._light = null;
}
}

private _applyAnimationSpeed() {
this._details.model?.animationGroups.forEach((group) => (group.speedRatio = this._animationSpeed));
}
Expand Down
Loading

0 comments on commit 865f503

Please sign in to comment.