소스 검색

Merge pull request #6128 from BabylonJS/perf

Perf
David Catuhe 6 년 전
부모
커밋
079f196131

+ 2 - 3
sandbox/index.js

@@ -214,14 +214,13 @@ if (BABYLON.Engine.isSupported()) {
             currentScene.debugLayer.show();
         }
 
-        currentScene.dispatchAllSubMeshesOfActiveMeshes = true;
-        currentScene.meshes.forEach((mesh) => mesh.alwaysSelectAsActiveMesh = true);
-        currentScene.getEngine().disableTextureBindingOptimization = true;
         currentScene.meshes.forEach((mesh) => mesh.doNotSyncBoundingInfo = true);
         currentScene.materials.forEach((mat) => mat.freeze());
 
         currentScene.meshes.forEach((mesh) => mesh.ignoreNonUniformScaling = true);
         currentScene.transformNodes.forEach((node) => node.ignoreNonUniformScaling = true);
+
+        currentScene.freezeActiveMeshes();
     };
 
     var sceneError = function(sceneFile, babylonScene, message) {

+ 1 - 1
src/Animations/animation.ts

@@ -576,7 +576,7 @@ export class Animation {
             return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
         }
 
-        const keys = this.getKeys();
+        const keys = this._keys;
         if (keys.length === 1) {
             return this._getKeyValue(keys[0].value);
         }

+ 121 - 103
src/Animations/runtimeAnimation.ts

@@ -6,6 +6,7 @@ import { AnimationEvent } from "./animationEvent";
 declare type Animatable = import("./animatable").Animatable;
 
 import { Scene } from "../scene";
+import { IAnimationKey } from './animationKey';
 
 // Static values to help the garbage collector
 
@@ -98,6 +99,7 @@ export class RuntimeAnimation {
      */
     private _activeTargets: any[];
     private _currentActiveTarget: any;
+    private _directTarget: any;
 
     /**
      * The target path of the runtime animation
@@ -127,6 +129,12 @@ export class RuntimeAnimation {
     private _enableBlending: boolean;
     private _correctLoopMode: number | undefined;
 
+    private _keys: IAnimationKey[];
+    private _minFrame: number;
+    private _maxFrame: number;
+    private _minValue: any;
+    private _maxValue: any;
+
     /**
      * Gets the current frame of the runtime animation
      */
@@ -178,6 +186,24 @@ export class RuntimeAnimation {
 
         animation._runtimeAnimations.push(this);
 
+        // Normalization
+        if (this._host && this._host.syncRoot) {
+            this._normalizationProcessor = this._defaultNormalizationProcessor;
+        }
+
+        // Limits
+        this._keys = this._animation.getKeys();
+        this._minFrame = this._keys[0].frame;
+        this._maxFrame = this._keys[this._keys.length - 1].frame;
+        this._minValue = this._keys[0].value;
+        this._maxValue = this._keys[this._keys.length - 1].value;
+
+        // Add a start key at frame 0 if missing
+        if (this._minFrame !== 0) {
+            const newKey = { frame: 0, value: this._minValue };
+            this._keys.splice(0, 0, newKey);
+        }
+
         // Check data
         if (this._target instanceof Array) {
             var index = 0;
@@ -186,10 +212,13 @@ export class RuntimeAnimation {
                 this._getOriginalValues(index);
                 index++;
             }
+            this.setValue = this._setValueForArray;
         }
         else {
             this._preparePath(this._target);
             this._getOriginalValues();
+            this.setValue = this._setValueForDirect;
+            this._directTarget = this._activeTargets[0];
         }
 
         // Cloning events locally
@@ -210,6 +239,15 @@ export class RuntimeAnimation {
         }
     }
 
+    private _normalizationProcessor = (returnValue: boolean, range: number, ratio: number, from: number, to: number) => {
+        return (returnValue && range !== 0) ? from + ratio % range : to;;
+    };
+    private _defaultNormalizationProcessor = (returnValue: boolean, range: number, ratio: number, from: number, to: number) => {
+        const syncRoot = this._host.syncRoot;
+        const hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
+        return from + (to - from) * hostNormalizedFrame;
+    }
+
     private _preparePath(target: any, targetIndex = 0) {
         let targetPropertyPath = this._animation.targetPropertyPath;
 
@@ -245,14 +283,14 @@ export class RuntimeAnimation {
                 var index = 0;
                 for (const target of this._target) {
                     if (this._originalValue[index] !== undefined) {
-                        this._setValue(target, this._originalValue[index], -1);
+                        this._setValue(target, this._activeTargets[index], this._originalValue[index], -1, index);
                     }
                     index++;
                 }
             }
             else {
                 if (this._originalValue[0] !== undefined) {
-                    this._setValue(this._target, this._originalValue[0], -1);
+                    this._setValue(this._target, this._directTarget, this._originalValue[0], -1, 0);
                 }
             }
         }
@@ -261,7 +299,6 @@ export class RuntimeAnimation {
         this._highLimitsCache = {};
         this._currentFrame = 0;
         this._blendingFactor = 0;
-        this._originalValue = new Array<any>();
 
         // Events
         for (var index = 0; index < this._events.length; index++) {
@@ -312,19 +349,19 @@ export class RuntimeAnimation {
      * @param currentValue defines the value computed by the animation
      * @param weight defines the weight to apply to this value (Defaults to 1.0)
      */
-    public setValue(currentValue: any, weight = 1.0): void {
-        if (this._target instanceof Array) {
-            var index = 0;
-            for (const target of this._target) {
-                this._setValue(target, currentValue, weight, index);
-                index++;
-            }
-        }
-        else {
-            this._setValue(this._target, currentValue, weight);
+    public setValue: (currentValue: any, weight: number) => void;
+
+    private _setValueForArray = (currentValue: any, weight = 1.0) => {
+        for (var index = 0; index < this._target.length; index++) {
+            const target = this._target[index];
+            this._setValue(target, this._activeTargets[index], currentValue, weight, index);
         }
     }
 
+    private _setValueForDirect = (currentValue: any, weight = 1.0) => {
+        this._setValue(this._target, this._directTarget, currentValue, weight, 0);
+    }
+
     private _getOriginalValues(targetIndex = 0) {
         let originalValue: any;
         let target = this._activeTargets[targetIndex];
@@ -385,10 +422,8 @@ export class RuntimeAnimation {
         }
     }
 
-    private _setValue(target: any, currentValue: any, weight: number, targetIndex = 0): void {
+    private _setValue(target: any, destination: any, currentValue: any, weight: number, targetIndex: number): void {
         // Set value
-        var path = this._targetPath;
-        var destination = this._activeTargets[targetIndex];
         this._currentActiveTarget = destination;
 
         this._weight = weight;
@@ -398,8 +433,8 @@ export class RuntimeAnimation {
         if (weight !== -1.0) {
             this._scene._registerTargetForLateAnimationBinding(this, this._originalValue[targetIndex]);
         } else {
-            destination[path] = this._currentValue;
-        }
+            destination[this._targetPath] = this._currentValue;
+       }
 
         if (target.markAsDirty) {
             target.markAsDirty(this._animation.targetProperty);
@@ -465,22 +500,12 @@ export class RuntimeAnimation {
 
         let returnValue = true;
 
-        let keys = this._animation.getKeys();
-        let min = keys[0].frame;
-        let max = keys[keys.length - 1].frame;
-
-        // Add a start key at frame 0 if missing
-        if (min !== 0) {
-            const newKey = { frame: 0, value: keys[0].value };
-            keys.splice(0, 0, newKey);
-        }
-
         // Check limits
-        if (from < min || from > max) {
-            from = min;
+        if (from < this._minFrame || from > this._maxFrame) {
+            from = this._minFrame;
         }
-        if (to < min || to > max) {
-            to = max;
+        if (to < this._minFrame || to > this._maxFrame) {
+            to = this._maxFrame;
         }
 
         const range = to - from;
@@ -493,52 +518,47 @@ export class RuntimeAnimation {
         this._previousDelay = delay;
         this._previousRatio = ratio;
 
-        if ((to > from && ratio >= range) && !loop) { // If we are out of range and not looping get back to caller
+        if (!loop && (to > from && ratio >= range)) { // If we are out of range and not looping get back to caller
             returnValue = false;
-            highLimitValue = this._animation._getKeyValue(keys[keys.length - 1].value);
-        } else if ((from > to && ratio <= range) && !loop) {
+            highLimitValue = this._animation._getKeyValue(this._maxValue);
+        } else if (!loop && (from > to && ratio <= range)) {
             returnValue = false;
-            highLimitValue = this._animation._getKeyValue(keys[0].value);
-        } else {
-            // Get max value if required
-
-            if (this._correctLoopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
-
-                var keyOffset = to.toString() + from.toString();
-                if (!this._offsetsCache[keyOffset]) {
-                    var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
-                    var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
-                    switch (this._animation.dataType) {
-                        // Float
-                        case Animation.ANIMATIONTYPE_FLOAT:
-                            this._offsetsCache[keyOffset] = toValue - fromValue;
-                            break;
-                        // Quaternion
-                        case Animation.ANIMATIONTYPE_QUATERNION:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                            break;
-                        // Vector3
-                        case Animation.ANIMATIONTYPE_VECTOR3:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        // Vector2
-                        case Animation.ANIMATIONTYPE_VECTOR2:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        // Size
-                        case Animation.ANIMATIONTYPE_SIZE:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        // Color3
-                        case Animation.ANIMATIONTYPE_COLOR3:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        default:
-                            break;
-                    }
-
-                    this._highLimitsCache[keyOffset] = toValue;
+            highLimitValue = this._animation._getKeyValue(this._minValue);
+        } else if (this._correctLoopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
+            var keyOffset = to.toString() + from.toString();
+            if (!this._offsetsCache[keyOffset]) {
+                var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
+                var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
+                switch (this._animation.dataType) {
+                    // Float
+                    case Animation.ANIMATIONTYPE_FLOAT:
+                        this._offsetsCache[keyOffset] = toValue - fromValue;
+                        break;
+                    // Quaternion
+                    case Animation.ANIMATIONTYPE_QUATERNION:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                        break;
+                    // Vector3
+                    case Animation.ANIMATIONTYPE_VECTOR3:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    // Vector2
+                    case Animation.ANIMATIONTYPE_VECTOR2:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    // Size
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    // Color3
+                    case Animation.ANIMATIONTYPE_COLOR3:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    default:
+                        break;
                 }
 
-                highLimitValue = this._highLimitsCache[keyOffset];
-                offsetValue = this._offsetsCache[keyOffset];
+                this._highLimitsCache[keyOffset] = toValue;
             }
+
+            highLimitValue = this._highLimitsCache[keyOffset];
+            offsetValue = this._offsetsCache[keyOffset];
         }
 
         if (offsetValue === undefined) {
@@ -570,14 +590,7 @@ export class RuntimeAnimation {
         }
 
         // Compute value
-        let currentFrame = (returnValue && range !== 0) ? from + ratio % range : to;
-
-        // Need to normalize?
-        if (this._host && this._host.syncRoot) {
-            const syncRoot = this._host.syncRoot;
-            const hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
-            currentFrame = from + (to - from) * hostNormalizedFrame;
-        }
+        let currentFrame = this._normalizationProcessor(returnValue, range, ratio, from, to);
 
         // Reset events if looping
         const events = this._events;
@@ -588,13 +601,15 @@ export class RuntimeAnimation {
             }
 
             // Need to reset animation events
-            for (var index = 0; index < events.length; index++) {
-                if (!events[index].onlyOnce) {
-                    // reset event, the animation is looping
-                    events[index].isDone = false;
+            if (events.length) {
+                for (var index = 0; index < events.length; index++) {
+                    if (!events[index].onlyOnce) {
+                        // reset event, the animation is looping
+                        events[index].isDone = false;
+                    }
                 }
             }
-        }
+       }
 
         const repeatCount = range === 0 ? 0 : (ratio / range) >> 0;
         const currentValue = this._interpolate(currentFrame, repeatCount, this._correctLoopMode, offsetValue, highLimitValue);
@@ -603,25 +618,28 @@ export class RuntimeAnimation {
         this.setValue(currentValue, weight);
 
         // Check events
-        for (var index = 0; index < events.length; index++) {
-            // Make sure current frame has passed event frame and that event frame is within the current range
-            // Also, handle both forward and reverse animations
-            if (
-                (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
-                (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
-            ) {
-                var event = events[index];
-                if (!event.isDone) {
-                    // If event should be done only once, remove it.
-                    if (event.onlyOnce) {
-                        events.splice(index, 1);
-                        index--;
-                    }
-                    event.isDone = true;
-                    event.action(currentFrame);
-                } // Don't do anything if the event has already be done.
+        if (events.length) {
+            for (var index = 0; index < events.length; index++) {
+                // Make sure current frame has passed event frame and that event frame is within the current range
+                // Also, handle both forward and reverse animations
+                if (
+                    (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
+                    (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
+                ) {
+                    var event = events[index];
+                    if (!event.isDone) {
+                        // If event should be done only once, remove it.
+                        if (event.onlyOnce) {
+                            events.splice(index, 1);
+                            index--;
+                        }
+                        event.isDone = true;
+                        event.action(currentFrame);
+                    } // Don't do anything if the event has already be done.
+                }
             }
         }
+
         if (!returnValue) {
             this._stopped = true;
         }

+ 5 - 6
src/Engines/engine.ts

@@ -6357,7 +6357,6 @@ export class Engine {
      */
     public bindSamplers(effect: Effect): void {
         this.setProgram(effect.getProgram());
-
         var samplers = effect.getSamplers();
         for (var index = 0; index < samplers.length; index++) {
             var uniform = effect.getUniform(samplers[index]);
@@ -6365,7 +6364,7 @@ export class Engine {
             if (uniform) {
                 this._boundUniforms[index] = uniform;
             }
-        }
+        }        
         this._currentEffect = null;
     }
 
@@ -6414,7 +6413,7 @@ export class Engine {
 
     /** @hidden */
     public _bindTexture(channel: number, texture: Nullable<InternalTexture>): void {
-        if (channel < 0) {
+        if (channel === undefined) {
             return;
         }
 
@@ -6465,7 +6464,7 @@ export class Engine {
      * @param texture The texture to apply
      */
     public setTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<BaseTexture>): void {
-        if (channel < 0) {
+        if (channel === undefined) {
             return;
         }
 
@@ -6483,7 +6482,7 @@ export class Engine {
      * @param texture The render target texture containing the depth stencil texture to apply
      */
     public setDepthStencilTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<RenderTargetTexture>): void {
-        if (channel < 0) {
+        if (channel === undefined) {
             return;
         }
 
@@ -6642,7 +6641,7 @@ export class Engine {
      * @param textures defines the array of textures to bind
      */
     public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: BaseTexture[]): void {
-        if (channel < 0 || !uniform) {
+        if (channel === undefined || !uniform) {
             return;
         }
 

+ 59 - 48
src/Materials/effect.ts

@@ -241,12 +241,13 @@ export class Effect {
     private _engine: Engine;
     private _uniformBuffersNames: { [key: string]: number } = {};
     private _uniformsNames: string[];
-    private _samplers: string[];
+    private _samplerList: string[];
+    private _samplers: {[key: string]: number} = {};
     private _isReady = false;
     private _compilationError = "";
     private _attributesNames: string[];
     private _attributes: number[];
-    private _uniforms: Nullable<WebGLUniformLocation>[];
+    private _uniforms: {[key: string] :Nullable<WebGLUniformLocation>} = {};
     /**
      * Key for the effect.
      * @hidden
@@ -291,7 +292,7 @@ export class Effect {
 
             this._attributesNames = options.attributes;
             this._uniformsNames = options.uniformsNames.concat(options.samplers);
-            this._samplers = options.samplers.slice();
+            this._samplerList = options.samplers.slice();
             this.defines = options.defines;
             this.onError = options.onError;
             this.onCompiled = options.onCompiled;
@@ -308,7 +309,7 @@ export class Effect {
             this._engine = <Engine>engine;
             this.defines = (defines == null ? "" : defines);
             this._uniformsNames = (<string[]>uniformsNamesOrEngine).concat(<string[]>samplers);
-            this._samplers = samplers ? <string[]>samplers.slice() : [];
+            this._samplerList = samplers ? <string[]>samplers.slice() : [];
             this._attributesNames = (<string[]>attributesNamesOrOptions);
 
             this.onError = onError;
@@ -453,7 +454,7 @@ export class Effect {
      * @returns the location of the uniform.
      */
     public getUniform(uniformName: string): Nullable<WebGLUniformLocation> {
-        return this._uniforms[this._uniformsNames.indexOf(uniformName)];
+        return this._uniforms[uniformName];
     }
 
     /**
@@ -461,7 +462,7 @@ export class Effect {
      * @returns The array of sampler variable neames.
      */
     public getSamplers(): string[] {
-        return this._samplers;
+        return this._samplerList;
     }
 
     /**
@@ -834,19 +835,27 @@ export class Effect {
                     }
                 }
 
-                this._uniforms = engine.getUniforms(this._program, this._uniformsNames);
+                let uniforms = engine.getUniforms(this._program, this._uniformsNames);
+                uniforms.forEach((uniform, index) => {
+                    this._uniforms[this._uniformsNames[index]] = uniform;
+                });
+
                 this._attributes = engine.getAttributes(this._program, attributesNames);
 
                 var index: number;
-                for (index = 0; index < this._samplers.length; index++) {
-                    var sampler = this.getUniform(this._samplers[index]);
+                for (index = 0; index < this._samplerList.length; index++) {
+                    var sampler = this.getUniform(this._samplerList[index]);
 
                     if (sampler == null) {
-                        this._samplers.splice(index, 1);
+                        this._samplerList.splice(index, 1);
                         index--;
                     }
                 }
 
+                this._samplerList.forEach((name, index) => {
+                    this._samplers[name] = index;
+                })
+
                 engine.bindSamplers(this);
 
                 this._compilationError = "";
@@ -926,7 +935,7 @@ export class Effect {
      * @hidden
      */
     public _bindTexture(channel: string, texture: InternalTexture): void {
-        this._engine._bindTexture(this._samplers.indexOf(channel), texture);
+        this._engine._bindTexture(this._samplers[channel], texture);
     }
 
     /**
@@ -935,7 +944,7 @@ export class Effect {
      * @param texture Texture to set.
      */
     public setTexture(channel: string, texture: Nullable<BaseTexture>): void {
-        this._engine.setTexture(this._samplers.indexOf(channel), this.getUniform(channel), texture);
+        this._engine.setTexture(this._samplers[channel], this._uniforms[channel], texture);
     }
 
     /**
@@ -944,7 +953,7 @@ export class Effect {
      * @param texture Texture to set.
      */
     public setDepthStencilTexture(channel: string, texture: Nullable<RenderTargetTexture>): void {
-        this._engine.setDepthStencilTexture(this._samplers.indexOf(channel), this.getUniform(channel), texture);
+        this._engine.setDepthStencilTexture(this._samplers[channel], this._uniforms[channel], texture);
     }
 
     /**
@@ -953,14 +962,16 @@ export class Effect {
      * @param textures Textures to set.
      */
     public setTextureArray(channel: string, textures: BaseTexture[]): void {
-        if (this._samplers.indexOf(channel + "Ex") === -1) {
-            var initialPos = this._samplers.indexOf(channel);
+        let exName = channel + "Ex";
+        if (this._samplerList.indexOf(exName) === -1) {
+            var initialPos = this._samplers[channel];
             for (var index = 1; index < textures.length; index++) {
-                this._samplers.splice(initialPos + index, 0, channel + "Ex");
+                this._samplerList.splice(initialPos + index, 0, exName);
+                this._samplers[exName] = initialPos + index;
             }
         }
 
-        this._engine.setTextureArray(this._samplers.indexOf(channel), this.getUniform(channel), textures);
+        this._engine.setTextureArray(this._samplers[channel], this._uniforms[channel], textures);
     }
 
     /**
@@ -969,7 +980,7 @@ export class Effect {
      * @param postProcess Post process to get the input texture from.
      */
     public setTextureFromPostProcess(channel: string, postProcess: Nullable<PostProcess>): void {
-        this._engine.setTextureFromPostProcess(this._samplers.indexOf(channel), postProcess);
+        this._engine.setTextureFromPostProcess(this._samplers[channel], postProcess);
     }
 
     /**
@@ -979,7 +990,7 @@ export class Effect {
      * @param postProcess Post process to get the output texture from.
      */
     public setTextureFromPostProcessOutput(channel: string, postProcess: Nullable<PostProcess>): void {
-        this._engine.setTextureFromPostProcessOutput(this._samplers.indexOf(channel), postProcess);
+        this._engine.setTextureFromPostProcessOutput(this._samplers[channel], postProcess);
     }
 
     /** @hidden */
@@ -1110,7 +1121,7 @@ export class Effect {
 
         this._valueCache[uniformName] = value;
 
-        this._engine.setInt(this.getUniform(uniformName), value);
+        this._engine.setInt(this._uniforms[uniformName], value);
 
         return this;
     }
@@ -1123,7 +1134,7 @@ export class Effect {
      */
     public setIntArray(uniformName: string, array: Int32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setIntArray(this.getUniform(uniformName), array);
+        this._engine.setIntArray(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1136,7 +1147,7 @@ export class Effect {
      */
     public setIntArray2(uniformName: string, array: Int32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setIntArray2(this.getUniform(uniformName), array);
+        this._engine.setIntArray2(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1149,7 +1160,7 @@ export class Effect {
      */
     public setIntArray3(uniformName: string, array: Int32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setIntArray3(this.getUniform(uniformName), array);
+        this._engine.setIntArray3(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1162,7 +1173,7 @@ export class Effect {
      */
     public setIntArray4(uniformName: string, array: Int32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setIntArray4(this.getUniform(uniformName), array);
+        this._engine.setIntArray4(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1175,7 +1186,7 @@ export class Effect {
      */
     public setFloatArray(uniformName: string, array: Float32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setFloatArray(this.getUniform(uniformName), array);
+        this._engine.setFloatArray(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1188,7 +1199,7 @@ export class Effect {
      */
     public setFloatArray2(uniformName: string, array: Float32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setFloatArray2(this.getUniform(uniformName), array);
+        this._engine.setFloatArray2(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1201,7 +1212,7 @@ export class Effect {
      */
     public setFloatArray3(uniformName: string, array: Float32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setFloatArray3(this.getUniform(uniformName), array);
+        this._engine.setFloatArray3(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1214,7 +1225,7 @@ export class Effect {
      */
     public setFloatArray4(uniformName: string, array: Float32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setFloatArray4(this.getUniform(uniformName), array);
+        this._engine.setFloatArray4(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1227,7 +1238,7 @@ export class Effect {
      */
     public setArray(uniformName: string, array: number[]): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setArray(this.getUniform(uniformName), array);
+        this._engine.setArray(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1240,7 +1251,7 @@ export class Effect {
      */
     public setArray2(uniformName: string, array: number[]): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setArray2(this.getUniform(uniformName), array);
+        this._engine.setArray2(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1253,7 +1264,7 @@ export class Effect {
      */
     public setArray3(uniformName: string, array: number[]): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setArray3(this.getUniform(uniformName), array);
+        this._engine.setArray3(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1266,7 +1277,7 @@ export class Effect {
      */
     public setArray4(uniformName: string, array: number[]): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setArray4(this.getUniform(uniformName), array);
+        this._engine.setArray4(this._uniforms[uniformName], array);
 
         return this;
     }
@@ -1283,7 +1294,7 @@ export class Effect {
         }
 
         this._valueCache[uniformName] = null;
-        this._engine.setMatrices(this.getUniform(uniformName), matrices);
+        this._engine.setMatrices(this._uniforms[uniformName], matrices);
 
         return this;
     }
@@ -1296,7 +1307,7 @@ export class Effect {
      */
     public setMatrix(uniformName: string, matrix: Matrix): Effect {
         if (this._cacheMatrix(uniformName, matrix)) {
-            this._engine.setMatrix(this.getUniform(uniformName), matrix);
+            this._engine.setMatrix(this._uniforms[uniformName], matrix);
         }
         return this;
     }
@@ -1309,7 +1320,7 @@ export class Effect {
      */
     public setMatrix3x3(uniformName: string, matrix: Float32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setMatrix3x3(this.getUniform(uniformName), matrix);
+        this._engine.setMatrix3x3(this._uniforms[uniformName], matrix);
 
         return this;
     }
@@ -1322,7 +1333,7 @@ export class Effect {
      */
     public setMatrix2x2(uniformName: string, matrix: Float32Array): Effect {
         this._valueCache[uniformName] = null;
-        this._engine.setMatrix2x2(this.getUniform(uniformName), matrix);
+        this._engine.setMatrix2x2(this._uniforms[uniformName], matrix);
 
         return this;
     }
@@ -1341,7 +1352,7 @@ export class Effect {
 
         this._valueCache[uniformName] = value;
 
-        this._engine.setFloat(this.getUniform(uniformName), value);
+        this._engine.setFloat(this._uniforms[uniformName], value);
 
         return this;
     }
@@ -1360,7 +1371,7 @@ export class Effect {
 
         this._valueCache[uniformName] = bool;
 
-        this._engine.setBool(this.getUniform(uniformName), bool ? 1 : 0);
+        this._engine.setBool(this._uniforms[uniformName], bool ? 1 : 0);
 
         return this;
     }
@@ -1373,7 +1384,7 @@ export class Effect {
      */
     public setVector2(uniformName: string, vector2: Vector2): Effect {
         if (this._cacheFloat2(uniformName, vector2.x, vector2.y)) {
-            this._engine.setFloat2(this.getUniform(uniformName), vector2.x, vector2.y);
+            this._engine.setFloat2(this._uniforms[uniformName], vector2.x, vector2.y);
         }
         return this;
     }
@@ -1387,7 +1398,7 @@ export class Effect {
      */
     public setFloat2(uniformName: string, x: number, y: number): Effect {
         if (this._cacheFloat2(uniformName, x, y)) {
-            this._engine.setFloat2(this.getUniform(uniformName), x, y);
+            this._engine.setFloat2(this._uniforms[uniformName], x, y);
         }
         return this;
     }
@@ -1400,7 +1411,7 @@ export class Effect {
      */
     public setVector3(uniformName: string, vector3: Vector3): Effect {
         if (this._cacheFloat3(uniformName, vector3.x, vector3.y, vector3.z)) {
-            this._engine.setFloat3(this.getUniform(uniformName), vector3.x, vector3.y, vector3.z);
+            this._engine.setFloat3(this._uniforms[uniformName], vector3.x, vector3.y, vector3.z);
         }
         return this;
     }
@@ -1415,7 +1426,7 @@ export class Effect {
      */
     public setFloat3(uniformName: string, x: number, y: number, z: number): Effect {
         if (this._cacheFloat3(uniformName, x, y, z)) {
-            this._engine.setFloat3(this.getUniform(uniformName), x, y, z);
+            this._engine.setFloat3(this._uniforms[uniformName], x, y, z);
         }
         return this;
     }
@@ -1428,7 +1439,7 @@ export class Effect {
      */
     public setVector4(uniformName: string, vector4: Vector4): Effect {
         if (this._cacheFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w)) {
-            this._engine.setFloat4(this.getUniform(uniformName), vector4.x, vector4.y, vector4.z, vector4.w);
+            this._engine.setFloat4(this._uniforms[uniformName], vector4.x, vector4.y, vector4.z, vector4.w);
         }
         return this;
     }
@@ -1444,7 +1455,7 @@ export class Effect {
      */
     public setFloat4(uniformName: string, x: number, y: number, z: number, w: number): Effect {
         if (this._cacheFloat4(uniformName, x, y, z, w)) {
-            this._engine.setFloat4(this.getUniform(uniformName), x, y, z, w);
+            this._engine.setFloat4(this._uniforms[uniformName], x, y, z, w);
         }
         return this;
     }
@@ -1458,7 +1469,7 @@ export class Effect {
     public setColor3(uniformName: string, color3: Color3): Effect {
 
         if (this._cacheFloat3(uniformName, color3.r, color3.g, color3.b)) {
-            this._engine.setColor3(this.getUniform(uniformName), color3);
+            this._engine.setColor3(this._uniforms[uniformName], color3);
         }
         return this;
     }
@@ -1472,7 +1483,7 @@ export class Effect {
      */
     public setColor4(uniformName: string, color3: Color3, alpha: number): Effect {
         if (this._cacheFloat4(uniformName, color3.r, color3.g, color3.b, alpha)) {
-            this._engine.setColor4(this.getUniform(uniformName), color3, alpha);
+            this._engine.setColor4(this._uniforms[uniformName], color3, alpha);
         }
         return this;
     }
@@ -1485,7 +1496,7 @@ export class Effect {
      */
     public setDirectColor4(uniformName: string, color4: Color4): Effect {
         if (this._cacheFloat4(uniformName, color4.r, color4.g, color4.b, color4.a)) {
-            this._engine.setDirectColor4(this.getUniform(uniformName), color4);
+            this._engine.setDirectColor4(this._uniforms[uniformName], color4);
         }
         return this;
     }
@@ -1521,4 +1532,4 @@ export class Effect {
     public static ResetCache() {
         Effect._baseCache = {};
     }
-}
+}

+ 18 - 1
src/Meshes/abstractMesh.ts

@@ -25,6 +25,7 @@ import { AbstractActionManager } from '../Actions/abstractActionManager';
 declare type Ray = import("../Culling/ray").Ray;
 declare type Collider = import("../Collisions/collider").Collider;
 declare type TrianglePickingPredicate = import("../Culling/ray").TrianglePickingPredicate;
+declare type RenderingGroup = import("../Rendering/renderingGroup").RenderingGroup;
 
 /** @hidden */
 class _FacetDataStorage {
@@ -267,6 +268,11 @@ export class AbstractMesh extends TransformNode implements IDisposable, ICullabl
 
     private _visibility = 1.0;
 
+    /** @hidden */
+    public _isActive = false;
+    /** @hidden */
+    public _renderingGroup: RenderingGroup;
+
     /**
      * Gets or sets mesh visibility between 0 and 1 (default is 1)
      */
@@ -1025,8 +1031,19 @@ export class AbstractMesh extends TransformNode implements IDisposable, ICullabl
     }
 
     /** @hidden */
-    public _activate(renderId: number): void {
+    public _activate(renderId: number): boolean {
         this._renderId = renderId;
+        return true;
+    }
+
+    /** @hidden */
+    public _freeze() {
+        // Do nothing
+    }
+
+    /** @hidden */
+    public _unFreeze() {
+        // Do nothing
     }
 
     /**

+ 7 - 2
src/Meshes/instancedMesh.ts

@@ -259,11 +259,16 @@ export class InstancedMesh extends AbstractMesh {
     }
 
     /** @hidden */
-    public _activate(renderId: number): InstancedMesh {
+    public _activate(renderId: number): boolean {
         if (this._currentLOD) {
             this._currentLOD._registerInstanceForRenderId(this, renderId);
         }
-        return this;
+
+        if (this._edgesRenderer && this._edgesRenderer.isEnabled && this._sourceMesh._renderingGroup) {
+            this._sourceMesh._renderingGroup._edgesRenderers.push(this._edgesRenderer);
+        }
+
+        return !this._sourceMesh._isActive;
     }
 
     /**

+ 32 - 22
src/Meshes/mesh.ts

@@ -76,12 +76,14 @@ export class _CreationDataStorage {
  **/
 class _InstanceDataStorage {
     public visibleInstances: any = {};
-    public renderIdForInstances = new Array<number>();
     public batchCache = new _InstancesBatch();
     public instancesBufferSize = 32 * 16 * 4; // let's start with a maximum of 32 instances
     public instancesBuffer: Nullable<Buffer>;
     public instancesData: Float32Array;
     public overridenInstanceCount: number;
+    public isFrozen: boolean;
+    public _previousBatch: _InstancesBatch;
+    public hardwareInstancedRendering: boolean;
 }
 
 /**
@@ -91,6 +93,7 @@ export class _InstancesBatch {
     public mustReturn = false;
     public visibleInstances = new Array<Nullable<Array<InstancedMesh>>>();
     public renderSelf = new Array<boolean>();
+    public hardwareInstancedRendering = new Array<boolean>();
 }
 
 /**
@@ -437,6 +440,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             this.parent = parent;
         }
 
+        this._instanceDataStorage.hardwareInstancedRendering = this.getEngine().getCaps().instancedArrays;
     }
 
     // Methods
@@ -1363,6 +1367,9 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
 
     /** @hidden */
     public _getInstancesRenderList(subMeshId: number): _InstancesBatch {
+        if (this._instanceDataStorage.isFrozen && this._instanceDataStorage._previousBatch) {
+            return this._instanceDataStorage._previousBatch;
+        }
         var scene = this.getScene();
         let batchCache = this._instanceDataStorage.batchCache;
         batchCache.mustReturn = false;
@@ -1374,29 +1381,13 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             var currentRenderId = scene.getRenderId();
             var defaultRenderId = (scene._isInIntermediateRendering() ? visibleInstances.intermediateDefaultRenderId : visibleInstances.defaultRenderId);
             batchCache.visibleInstances[subMeshId] = visibleInstances[currentRenderId];
-            var selfRenderId = this._renderId;
 
             if (!batchCache.visibleInstances[subMeshId] && defaultRenderId) {
                 batchCache.visibleInstances[subMeshId] = visibleInstances[defaultRenderId];
-                currentRenderId = Math.max(defaultRenderId, currentRenderId);
-                selfRenderId = Math.max(visibleInstances.selfDefaultRenderId, currentRenderId);
-            }
-
-            let visibleInstancesForSubMesh = batchCache.visibleInstances[subMeshId];
-            if (visibleInstancesForSubMesh && visibleInstancesForSubMesh.length) {
-                if (this._instanceDataStorage.renderIdForInstances[subMeshId] === currentRenderId) {
-                    batchCache.mustReturn = true;
-                    return batchCache;
-                }
-
-                if (currentRenderId !== selfRenderId) {
-                    batchCache.renderSelf[subMeshId] = false;
-                }
-
             }
-            this._instanceDataStorage.renderIdForInstances[subMeshId] = currentRenderId;
         }
-
+        batchCache.hardwareInstancedRendering[subMeshId] = this._instanceDataStorage.hardwareInstancedRendering && (batchCache.visibleInstances[subMeshId] !== null) && (batchCache.visibleInstances[subMeshId] !== undefined);
+        this._instanceDataStorage._previousBatch = batchCache;
         return batchCache;
     }
 
@@ -1502,6 +1493,25 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         return this;
     }
 
+    /** @hidden */
+    public _freeze() {
+        this._instanceDataStorage.isFrozen = true;
+
+        if (!this.subMeshes) {
+            return;
+        }
+
+        // Prepare batches
+        for (var index = 0; index < this.subMeshes.length; index++) {
+            this._getInstancesRenderList(index);
+        }
+    }
+
+    /** @hidden */
+    public _unFreeze() {
+        this._instanceDataStorage.isFrozen = false;
+    }
+
     /**
      * Triggers the draw call for the mesh. Usually, you don't need to call this method by your own because the mesh rendering is handled by the scene rendering manager
      * @param subMesh defines the subMesh to render
@@ -1531,7 +1541,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         }
 
         var engine = scene.getEngine();
-        var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
+        var hardwareInstancedRendering = batch.hardwareInstancedRendering[subMesh._id];
 
         // Material
         let material = subMesh.getMaterial();
@@ -2420,7 +2430,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
                 for (var j = 0; j < 3; j++) {
                     a = vertexIndex[j];
                     b = vertexIndex[(j + 1) % 3];
-                    if (side[a] === undefined  && side[b] ===  undefined) {
+                    if (side[a] === undefined && side[b] === undefined) {
                         side[a] = new Array();
                         side[b] = new Array();
                     }
@@ -2432,7 +2442,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
                             side[b] = new Array();
                         }
                     }
-                    if (side[a][b]  === undefined  && side[b][a] === undefined) {
+                    if (side[a][b] === undefined && side[b][a] === undefined) {
                         side[a][b] = [];
                         deltaPosition.x = (positions[3 * b] - positions[3 * a]) / segments;
                         deltaPosition.y = (positions[3 * b + 1] - positions[3 * a + 1]) / segments;

+ 51 - 20
src/Meshes/transformNode.ts

@@ -108,11 +108,30 @@ export class TransformNode extends Node {
     @serialize()
     public scalingDeterminant = 1;
 
+
+    @serialize("infiniteDistance")
+    private _infiniteDistance = false;
+
     /**
-     * Sets the distance of the object to max, often used by skybox
+     * Gets or sets the distance of the object to max, often used by skybox
      */
-    @serialize()
-    public infiniteDistance = false;
+    public get infiniteDistance() {
+        return this._infiniteDistance;
+    }
+
+    public set infiniteDistance(value: boolean) {
+        if (this._infiniteDistance === value) {
+            return;
+        }
+
+        this._infiniteDistance = value;
+
+        if (value) {
+            this._translationProcessor = this._infiniteDistanceTranslationProcessor;
+        } else {
+            this._translationProcessor = this._defaultTranslationProcessor;
+        }
+    }
 
     /**
      * Gets or sets a boolean indicating that non uniform scaling (when at least one component is different from others) should be ignored.
@@ -166,7 +185,8 @@ export class TransformNode extends Node {
         }
 
         this._activeParentProcessor = this._defaultParentProcessor;
-        this._activeCompositionProcess = this._defaultCompositionProcessor;
+        this._activeCompositionProcessor = this._defaultCompositionProcessor;
+        this._translationProcessor = this._defaultTranslationProcessor;
     }
 
     /**
@@ -227,7 +247,7 @@ export class TransformNode extends Node {
         this._rotationQuaternion = quaternion;
         //reset the rotation vector.
         if (quaternion) {
-            this.rotation.setAll(0.0);
+            this._rotation.setAll(0.0);
         }
         this._isDirty = true;
     }
@@ -363,9 +383,9 @@ export class TransformNode extends Node {
     public setPivotMatrix(matrix: DeepImmutable<Matrix>, postMultiplyPivotMatrix = true): TransformNode {
         this._pivotMatrix.copyFrom(matrix);
         if (this._pivotMatrix.isIdentity()) {
-            this._activeCompositionProcess = this._defaultCompositionProcessor;
+            this._activeCompositionProcessor = this._defaultCompositionProcessor;
         } else {
-            this._activeCompositionProcess = this._pivotCompositionProcessor;
+            this._activeCompositionProcessor = this._pivotCompositionProcessor;
         }
         this._cache.pivotMatrixUpdated = true;
         this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
@@ -903,7 +923,7 @@ export class TransformNode extends Node {
         return this.parent;
     }
 
-    private _activeCompositionProcess: (scaling: Vector3, rotation: Quaternion, translation: Vector3) => void;
+    private _activeCompositionProcessor: (scaling: Vector3, rotation: Quaternion, translation: Vector3) => void;
 
     private _defaultCompositionProcessor = (scaling: Vector3, rotation: Quaternion, translation: Vector3) => {
         Matrix.ComposeToRef(scaling, rotation, translation, this._localMatrix);
@@ -929,6 +949,26 @@ export class TransformNode extends Node {
         this._localMatrix.addTranslationFromFloats(translation.x, translation.y, translation.z);
     }
 
+    // Infinite distance
+    private _translationProcessor: (translation: Vector3) => void;
+
+    private _defaultTranslationProcessor = (translation: Vector3) => {
+        translation.copyFrom(this._position);
+    }
+
+    private _infiniteDistanceTranslationProcessor = (translation: Vector3) => {
+        let camera = (<Camera>this.getScene().activeCamera);
+
+        if (!this.parent && camera) {
+            var cameraWorldMatrix = camera.getWorldMatrix();
+            var cameraGlobalPosition = new Vector3(cameraWorldMatrix.m[12], cameraWorldMatrix.m[13], cameraWorldMatrix.m[14]);
+
+            translation.copyFromFloats(this._position.x + cameraGlobalPosition.x, this._position.y + cameraGlobalPosition.y, this._position.z + cameraGlobalPosition.z);
+        } else {
+            translation.copyFrom(this._position);
+        }
+    }
+
     // Billboards
     private _activeParentProcessor: (parent: Node) => void;
     private _activeBillboardPostProcessor = () => { };
@@ -1006,7 +1046,7 @@ export class TransformNode extends Node {
     public computeWorldMatrix(force?: boolean): Matrix {
         let currentRenderId = this.getScene().getRenderId();
 
-        if (this._isWorldMatrixFrozen) {
+        if (this._isWorldMatrixFrozen && !this._isDirty) {
             return this._worldMatrix;
         }
 
@@ -1030,16 +1070,7 @@ export class TransformNode extends Node {
         let translation: Vector3 = this._cache.position;
 
         // Translation
-        let camera = (<Camera>this.getScene().activeCamera);
-
-        if (this.infiniteDistance && !this.parent && camera) {
-            var cameraWorldMatrix = camera.getWorldMatrix();
-            var cameraGlobalPosition = new Vector3(cameraWorldMatrix.m[12], cameraWorldMatrix.m[13], cameraWorldMatrix.m[14]);
-
-            translation.copyFromFloats(this._position.x + cameraGlobalPosition.x, this._position.y + cameraGlobalPosition.y, this._position.z + cameraGlobalPosition.z);
-        } else {
-            translation.copyFrom(this._position);
-        }
+        this._translationProcessor(translation);
 
         // Scaling
         scaling.copyFromFloats(this._scaling.x * this.scalingDeterminant, this._scaling.y * this.scalingDeterminant, this._scaling.z * this.scalingDeterminant);
@@ -1061,7 +1092,7 @@ export class TransformNode extends Node {
         }
 
         // Compose
-        this._activeCompositionProcess(scaling, rotation, translation);
+        this._activeCompositionProcessor(scaling, rotation, translation);
 
         // Parent
         if (parent && parent.getWorldMatrix) {

+ 4 - 1
src/Rendering/renderingGroup.ts

@@ -34,7 +34,8 @@ export class RenderingGroup {
     private _renderAlphaTest: (subMeshes: SmartArray<SubMesh>) => void;
     private _renderTransparent: (subMeshes: SmartArray<SubMesh>) => void;
 
-    private _edgesRenderers = new SmartArray<IEdgesRenderer>(16);
+    /** @hidden */
+    public _edgesRenderers = new SmartArray<IEdgesRenderer>(16);
 
     public onBeforeTransparentRendering: () => void;
 
@@ -363,6 +364,8 @@ export class RenderingGroup {
             this._opaqueSubMeshes.push(subMesh); // Opaque
         }
 
+        mesh._renderingGroup = this;
+
         if (mesh._edgesRenderer && mesh._edgesRenderer.isEnabled) {
             this._edgesRenderers.push(mesh._edgesRenderer);
         }

+ 20 - 2
src/scene.ts

@@ -3869,6 +3869,10 @@ export class Scene extends AbstractScene implements IAnimatable {
 
         this._evaluateActiveMeshes();
         this._activeMeshesFrozen = true;
+
+        for (var mesh of this._activeMeshes.data) {
+            mesh._freeze();
+        }
         return this;
     }
 
@@ -3877,12 +3881,23 @@ export class Scene extends AbstractScene implements IAnimatable {
      * @returns the current scene
      */
     public unfreezeActiveMeshes(): Scene {
+        for (var mesh of this._activeMeshes.data) {
+            mesh._unFreeze();
+        }
+
         this._activeMeshesFrozen = false;
         return this;
     }
 
     private _evaluateActiveMeshes(): void {
         if (this._activeMeshesFrozen && this._activeMeshes.length) {
+
+            const len = this._activeMeshes.length;
+            for (let i = 0; i < len; i++) {
+                let mesh = this._activeMeshes.data[i];
+                mesh.computeWorldMatrix();
+            }
+
             return;
         }
 
@@ -3910,6 +3925,7 @@ export class Scene extends AbstractScene implements IAnimatable {
         const len = meshes.length;
         for (let i = 0; i < len; i++) {
             const mesh = meshes.data[i];
+            mesh._isActive = false;
             if (mesh.isBlocked) {
                 continue;
             }
@@ -3944,12 +3960,14 @@ export class Scene extends AbstractScene implements IAnimatable {
                 this._activeMeshes.push(mesh);
                 this.activeCamera._activeMeshes.push(mesh);
 
-                mesh._activate(this._renderId);
                 if (meshLOD !== mesh) {
                     meshLOD._activate(this._renderId);
                 }
 
-                this._activeMesh(mesh, meshLOD);
+                if (mesh._activate(this._renderId)) {
+                    mesh._isActive = true;
+                    this._activeMesh(mesh, meshLOD);
+                }
             }
         }
 

BIN
tests/validation/ReferenceImages/weighted animations.png


+ 6 - 0
tests/validation/config.json

@@ -2,6 +2,12 @@
   "root": "https://rawgit.com/BabylonJS/Website/master",
   "tests": [
     {
+      "title": "Weighted animations",
+      "playgroundId": "#LL5BIQ#72",
+      "renderCount": 50,
+      "referenceImage": "weighted animations.png"
+    },
+    {
       "title": "Anisotropic",
       "playgroundId": "#MAXCNU#1",
       "referenceImage": "anisotropic.png"