Parcourir la source

preparing for sphere testing and moving functionalities
Sphere-testing for validation tests is now enabled.

Raanan Weber il y a 7 ans
Parent
commit
36fb1a2e6e

+ 132 - 1
Viewer/src/configuration/configuration.ts

@@ -93,6 +93,28 @@ export interface IModelConfiguration {
         playOnce?: boolean;
     }
 
+    material?: {
+        directEnabled?: boolean;
+        directIntensity?: number;
+        emissiveIntensity?: number;
+        environmentIntensity?: number;
+        [propName: string]: any;
+    }
+
+    /** 
+     * Rotation offset axis definition
+     */
+    rotationOffsetAxis?: {
+        x: number;
+        y: number;
+        z: number;
+    };
+
+    /**
+     * the offset angle
+     */
+    rotationOffsetAngle?: number;
+
     // [propName: string]: any; // further configuration, like title and creator
 }
 
@@ -138,8 +160,113 @@ export interface IGroundConfiguration {
 export interface ISceneConfiguration {
     debug?: boolean;
     clearColor?: { r: number, g: number, b: number, a: number };
+    mainColor?: { r: number, g: number, b: number, a: number };
     imageProcessingConfiguration?: IImageProcessingConfiguration;
     environmentTexture?: string;
+    colorGrading?: IColorGradingConfiguration;
+    environmentRotationY?: number;
+}
+
+/**
+ * The Color Grading Configuration groups the different settings used to define the color grading used in the viewer.
+ */
+export interface IColorGradingConfiguration {
+
+    /**
+     * Transform data string, encoded as determined by transformDataFormat.
+     */
+    transformData: string;
+
+    /**
+     * The encoding format of TransformData (currently only raw-base16 is supported).
+     */
+    transformDataFormat: string;
+
+    /**
+     * The weight of the transform
+     */
+    transformWeight: number;
+
+    /**
+     * Color curve colorFilterHueGlobal value
+     */
+    colorFilterHueGlobal: number;
+
+    /**
+     * Color curve colorFilterHueShadows value
+     */
+    colorFilterHueShadows: number;
+
+    /**
+     * Color curve colorFilterHueMidtones value
+     */
+    colorFilterHueMidtones: number;
+
+    /**
+     * Color curve colorFilterHueHighlights value
+     */
+    colorFilterHueHighlights: number;
+
+    /**
+     * Color curve colorFilterDensityGlobal value
+     */
+    colorFilterDensityGlobal: number;
+
+    /**
+     * Color curve colorFilterDensityShadows value
+     */
+    colorFilterDensityShadows: number;
+
+    /**
+     * Color curve colorFilterDensityMidtones value
+     */
+    colorFilterDensityMidtones: number;
+
+    /**
+     * Color curve colorFilterDensityHighlights value
+     */
+    colorFilterDensityHighlights: number;
+
+    /**
+     * Color curve saturationGlobal value
+     */
+    saturationGlobal: number;
+
+    /**
+     * Color curve saturationShadows value
+     */
+    saturationShadows: number;
+
+    /**
+     * Color curve saturationMidtones value
+     */
+    saturationMidtones: number;
+
+    /**
+     * Color curve saturationHighlights value
+     */
+    saturationHighlights: number;
+
+    /**
+     * Color curve exposureGlobal value
+     */
+    exposureGlobal: number;
+
+    /**
+     * Color curve exposureShadows value
+     */
+    exposureShadows: number;
+
+    /**
+     * Color curve exposureMidtones value
+     */
+    exposureMidtones: number;
+
+    /**
+     * Color curve exposureHighlights value
+     */
+    exposureHighlights: number;
+
 }
 
 export interface ISceneOptimizerConfiguration {
@@ -208,8 +335,12 @@ export interface ILightConfiguration {
         maxZ?: number;
         frustumSize?: number;
         angleScale?: number;
+        frustumEdgeFalloff?: number;
         [propName: string]: any;
-    }
+    };
+    spotAngle?: number;
+    shadowFieldOfView?: number;
+    shadowBufferSize?: number;
     [propName: string]: any;
 
     // no behaviors for light at the moment, but allowing configuration for future reference.

+ 47 - 2
Viewer/src/model/viewerModel.ts

@@ -1,10 +1,11 @@
-import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, Quaternion } from "babylonjs";
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, Quaternion, Material } from "babylonjs";
 import { GLTFFileLoader } from "babylonjs-loaders";
 import { IModelConfiguration } from "../configuration/configuration";
 import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
 
 import * as deepmerge from '../../assets/deepmerge.min.js';
 import { AbstractViewer } from "..";
+import { extendClassWithConfig } from "../helper";
 
 
 export enum ModelState {
@@ -98,6 +99,7 @@ export class ViewerModel implements IDisposable {
         this._viewer.onModelAddedObservable.notifyObservers(this);
         this.onLoadedObservable.add(() => {
             this._configureModel();
+            this._viewer.onModelLoadedObservable.notifyObservers(this);
         });
     }
 
@@ -115,7 +117,7 @@ export class ViewerModel implements IDisposable {
         mesh.receiveShadows = !!this.configuration.receiveShadows;
         this._meshes.push(mesh);
         if (triggerLoaded) {
-            this.onLoadedObservable.notifyObservers(this);
+            return this.onLoadedObservable.notifyObserversWithPromise(this);
         }
     }
 
@@ -342,10 +344,53 @@ export class ViewerModel implements IDisposable {
                 });
             }
         }
+
+        let meshes = this.rootMesh.getChildMeshes(false);
+        meshes.push(this.rootMesh);
+        meshes.filter(m => m.material).forEach((mesh) => {
+            this._applyModelMaterialConfiguration(mesh.material!);
+        });
+
         this.onAfterConfigure.notifyObservers(this);
     }
 
     /**
+     * Apply a material configuration to a material
+     * @param material Material to apply configuration to
+     */
+    private _applyModelMaterialConfiguration(material: Material) {
+        if (!this._modelConfiguration.material) return;
+
+        extendClassWithConfig(material, this._modelConfiguration.material);
+
+        if (material instanceof BABYLON.PBRMaterial) {
+            if (this._modelConfiguration.material.directIntensity !== undefined) {
+                material.directIntensity = this._modelConfiguration.material.directIntensity;
+            }
+
+            if (this._modelConfiguration.material.emissiveIntensity !== undefined) {
+                material.emissiveIntensity = this._modelConfiguration.material.emissiveIntensity;
+            }
+
+            if (this._modelConfiguration.material.environmentIntensity !== undefined) {
+                material.environmentIntensity = this._modelConfiguration.material.environmentIntensity;
+            }
+
+            //material.disableLighting = !this._modelConfiguration.material.directEnabled;
+
+            material.reflectionColor = this._viewer.sceneManager.mainColor;
+        }
+        else if (material instanceof BABYLON.MultiMaterial) {
+            for (let i = 0; i < material.subMaterials.length; i++) {
+                const subMaterial = material.subMaterials[i];
+                if (subMaterial) {
+                    this._applyModelMaterialConfiguration(subMaterial);
+                }
+            }
+        }
+    }
+
+    /**
      * Will remove this model from the viewer (but NOT dispose it).
      */
     public remove() {

+ 19 - 17
Viewer/src/viewer/defaultViewer.ts

@@ -91,24 +91,26 @@ export class DefaultViewer extends AbstractViewer {
             this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerup');
             this.templateManager.eventManager.registerCallback('navBar', triggerNavbar.bind(this, true), 'pointerover');
 
-            // other events
-            let viewerTemplate = this.templateManager.getTemplate('viewer');
-            let viewerElement = viewerTemplate && viewerTemplate.parent;
-            // full screen
-            let triggerFullscren = (eventData: EventCallback) => {
-                if (viewerElement) {
-                    let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || (<any>document).mozFullScreenElement || (<any>document).msFullscreenElement;
-                    if (!fullscreenElement) {
-                        let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (<any>viewerElement).msRequestFullscreen || (<any>viewerElement).mozRequestFullScreen;
-                        requestFullScreen.call(viewerElement);
-                    } else {
-                        let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || (<any>document).msExitFullscreen || (<any>document).mozCancelFullScreen
-                        exitFullscreen.call(document);
-                    }
-                }
-            }
+            this.templateManager.eventManager.registerCallback('navBar', this.toggleFullscreen, 'pointerdown', '#fullscreen-button');
+        }
+    }
 
-            this.templateManager.eventManager.registerCallback('navBar', triggerFullscren, 'pointerdown', '#fullscreen-button');
+    /**
+     * Toggle fullscreen of the entire viewer
+     */
+    public toggleFullscreen = () => {
+        let viewerTemplate = this.templateManager.getTemplate('viewer');
+        let viewerElement = viewerTemplate && viewerTemplate.parent;
+
+        if (viewerElement) {
+            let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || (<any>document).mozFullScreenElement || (<any>document).msFullscreenElement;
+            if (!fullscreenElement) {
+                let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (<any>viewerElement).msRequestFullscreen || (<any>viewerElement).mozRequestFullScreen;
+                requestFullScreen.call(viewerElement);
+            } else {
+                let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || (<any>document).msExitFullscreen || (<any>document).mozCancelFullScreen
+                exitFullscreen.call(document);
+            }
         }
     }
 

+ 140 - 27
Viewer/src/viewer/sceneManager.ts

@@ -1,4 +1,4 @@
-import { Scene, ArcRotateCamera, Engine, Light, ShadowLight, Vector3, ShadowGenerator, Tags, CubeTexture, Quaternion, SceneOptimizer, EnvironmentHelper, SceneOptimizerOptions, Color3, IEnvironmentHelperOptions, AbstractMesh, FramingBehavior, Behavior, Observable } from 'babylonjs';
+import { Scene, ArcRotateCamera, Engine, Light, ShadowLight, Vector3, ShadowGenerator, Tags, CubeTexture, Quaternion, SceneOptimizer, EnvironmentHelper, SceneOptimizerOptions, Color3, IEnvironmentHelperOptions, AbstractMesh, FramingBehavior, Behavior, Observable, Color4 } from 'babylonjs';
 import { AbstractViewer } from './viewer';
 import { ILightConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, ViewerConfiguration, IGroundConfiguration, IModelConfiguration } from '../configuration/configuration';
 import { ViewerModel } from '../model/viewerModel';
@@ -64,15 +64,7 @@ export class SceneManager {
      */
     private _hdrSupport: boolean;
 
-    private _globalConfiguration: ViewerConfiguration;
-
-
-    /**
-     * Returns a boolean representing HDR support
-     */
-    public get isHdrSupported() {
-        return this._hdrSupport;
-    }
+    private _mainColor: Color3;
 
     constructor(private _viewer: AbstractViewer) {
         this.models = [];
@@ -100,6 +92,38 @@ export class SceneManager {
     }
 
     /**
+     * Returns a boolean representing HDR support
+     */
+    public get isHdrSupported() {
+        return this._hdrSupport;
+    }
+
+    /**
+     * Return the main color defined in the configuration.
+     */
+    public get mainColor(): Color3 {
+        return this._mainColor;
+    }
+
+    /**
+     * Sets the engine flags to unlock all babylon features.
+     */
+    public unlockBabylonFeatures() {
+        this.scene.shadowsEnabled = true;
+        this.scene.particlesEnabled = true;
+        this.scene.postProcessesEnabled = true;
+        this.scene.collisionsEnabled = true;
+        this.scene.lightsEnabled = true;
+        this.scene.texturesEnabled = true;
+        this.scene.lensFlaresEnabled = true;
+        this.scene.proceduralTexturesEnabled = true;
+        this.scene.renderTargetsEnabled = true;
+        this.scene.spritesEnabled = true;
+        this.scene.skeletonsEnabled = true;
+        this.scene.audioEnabled = true;
+    }
+
+    /**
      * initialize the environment for a specific model.
      * Per default it will use the viewer's configuration.
      * @param model the model to use to configure the environment.
@@ -124,6 +148,8 @@ export class SceneManager {
         // create a new scene
         this.scene = new Scene(this._viewer.engine);
 
+        this._mainColor = new Color3();
+
         if (sceneConfiguration) {
             this._configureScene(sceneConfiguration);
 
@@ -199,21 +225,19 @@ export class SceneManager {
             }
         }
 
-        if (sceneConfig.clearColor) {
-            let cc = sceneConfig.clearColor;
-            let oldcc = this.scene.clearColor;
-            if (cc.r !== undefined) {
-                oldcc.r = cc.r;
-            }
-            if (cc.g !== undefined) {
-                oldcc.g = cc.g
-            }
-            if (cc.b !== undefined) {
-                oldcc.b = cc.b
-            }
-            if (cc.a !== undefined) {
-                oldcc.a = cc.a
-            }
+        let cc = sceneConfig.clearColor || { r: 0.9, g: 0.9, b: 0.9, a: 1.0 };
+        let oldcc = this.scene.clearColor;
+        if (cc.r !== undefined) {
+            oldcc.r = cc.r;
+        }
+        if (cc.g !== undefined) {
+            oldcc.g = cc.g
+        }
+        if (cc.b !== undefined) {
+            oldcc.b = cc.b
+        }
+        if (cc.a !== undefined) {
+            oldcc.a = cc.a
         }
 
         // image processing configuration - optional.
@@ -228,6 +252,19 @@ export class SceneManager {
             this.scene.environmentTexture = environmentTexture;
         }
 
+        if (sceneConfig.mainColor) {
+            let mc = sceneConfig.mainColor;
+            if (mc.r !== undefined) {
+                this._mainColor.r = mc.r;
+            }
+            if (mc.g !== undefined) {
+                this._mainColor.g = mc.g
+            }
+            if (mc.b !== undefined) {
+                this._mainColor.b = mc.b
+            }
+        }
+
         this.onSceneConfiguredObservable.notifyObservers({
             sceneManager: this,
             object: this.scene,
@@ -451,6 +488,10 @@ export class SceneManager {
             }
         }
 
+        if (this.environmentHelper.rootMesh && this._viewer.configuration.scene && this._viewer.configuration.scene.environmentRotationY !== undefined) {
+            this.environmentHelper.rootMesh.rotation.y = this._viewer.configuration.scene.environmentRotationY;
+        }
+
         if (postInitSkyboxMaterial) {
             let skyboxMaterial = this.environmentHelper.skyboxMaterial;
             if (skyboxMaterial) {
@@ -536,8 +577,14 @@ export class SceneManager {
 
             extendClassWithConfig(light, lightConfig);
 
+
+
             //position. Some lights don't support shadows
             if (light instanceof ShadowLight) {
+                // set default values
+                light.shadowMinZ = light.shadowMinZ || 0.2;
+                light.shadowMaxZ = Math.min(10, light.shadowMaxZ); //large far clips reduce shadow depth precision
+
                 if (lightConfig.target) {
                     if (light.setDirectionToTarget) {
                         let target = Vector3.Zero().copyFrom(lightConfig.target as Vector3);
@@ -547,8 +594,31 @@ export class SceneManager {
                     let direction = Vector3.Zero().copyFrom(lightConfig.direction as Vector3);
                     light.direction = direction;
                 }
-                let shadowGenerator = light.getShadowGenerator();
-                if (lightConfig.shadowEnabled && this._maxShadows) {
+
+                let isShadowEnabled = false;
+                if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_DIRECTIONALLIGHT) {
+                    (<BABYLON.DirectionalLight>light).shadowFrustumSize = lightConfig.shadowFrustumSize || 2;
+                    isShadowEnabled = true;
+                }
+                else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_SPOTLIGHT) {
+                    let spotLight: BABYLON.SpotLight = <BABYLON.SpotLight>light;
+                    if (lightConfig.spotAngle !== undefined) {
+                        spotLight.angle = lightConfig.spotAngle * Math.PI / 180;
+                    }
+                    if (spotLight.angle && lightConfig.shadowFieldOfView) {
+                        spotLight.shadowAngleScale = lightConfig.shadowFieldOfView / spotLight.angle;
+                    }
+                    isShadowEnabled = true;
+                }
+                else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_POINTLIGHT) {
+                    if (lightConfig.shadowFieldOfView) {
+                        (<BABYLON.PointLight>light).shadowAngle = lightConfig.shadowFieldOfView * Math.PI / 180;
+                    }
+                    isShadowEnabled = true;
+                }
+
+                let shadowGenerator = <BABYLON.ShadowGenerator>light.getShadowGenerator();
+                if (isShadowEnabled && lightConfig.shadowEnabled && this._maxShadows) {
                     if (!shadowGenerator) {
                         shadowGenerator = new ShadowGenerator(512, light);
                         // TODO blur kernel definition
@@ -563,12 +633,31 @@ export class SceneManager {
                             renderList && renderList.push(focusMeshes[index]);
                         }
                     }
+                    let bufferSize = lightConfig.shadowBufferSize || 256;
+                    var blurKernel = this.getBlurKernel(light, bufferSize);
+
+                    shadowGenerator.useBlurCloseExponentialShadowMap = true;
+                    shadowGenerator.useKernelBlur = true;
+                    shadowGenerator.blurScale = 1.0;
+                    shadowGenerator.bias = this._shadowGeneratorBias;
+                    shadowGenerator.blurKernel = blurKernel;
+                    shadowGenerator.depthScale = 50 * (light.shadowMaxZ - light.shadowMinZ);
                 } else if (shadowGenerator) {
                     shadowGenerator.dispose();
                 }
             }
         });
 
+        // render priority
+        let globalLightsConfiguration = this._viewer.configuration.lights || {};
+        Object.keys(globalLightsConfiguration).sort().forEach((name, idx) => {
+            let configuration = globalLightsConfiguration[name];
+            let light = this.scene.getLightByName(name);
+            // sanity check
+            if (!light) return;
+            light.renderPriority = -idx;
+        });
+
         this.onLightsConfiguredObservable.notifyObservers({
             sceneManager: this,
             object: this.scene.lights,
@@ -578,6 +667,30 @@ export class SceneManager {
     }
 
     /**
+     * Gets the shadow map blur kernel according to the light configuration.
+     * @param light The light used to generate the shadows
+     * @param bufferSize The size of the shadow map
+     * @return the kernel blur size
+     */
+    public getBlurKernel(light: BABYLON.IShadowLight, bufferSize: number): number {
+        var normalizedBlurKernel = 0.03; // TODO Should come from the config.
+        if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_DIRECTIONALLIGHT) {
+            normalizedBlurKernel = normalizedBlurKernel / (<BABYLON.DirectionalLight>light).shadowFrustumSize;
+        }
+        else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_POINTLIGHT) {
+            normalizedBlurKernel = normalizedBlurKernel / (<BABYLON.PointLight>light).shadowAngle;
+        }
+        else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_SPOTLIGHT) {
+            normalizedBlurKernel = normalizedBlurKernel / ((<BABYLON.SpotLight>light).angle * (<BABYLON.SpotLight>light).shadowAngleScale);
+        }
+
+        let minimumBlurKernel = 5 / (bufferSize / 256); //magic number that aims to keep away sawtooth shadows
+
+        var blurKernel = Math.max(bufferSize * normalizedBlurKernel, minimumBlurKernel);
+        return blurKernel;
+    }
+
+    /**
      * Alters render settings to reduce features based on hardware feature limitations
      * @param enableHDR Allows the viewer to run in HDR mode.
      */

+ 38 - 4
Viewer/src/viewer/viewer.ts

@@ -261,13 +261,48 @@ export abstract class AbstractViewer {
      * render loop that will be executed by the engine
      */
     protected _render = (force: boolean = false): void => {
-        if (force || (this.runRenderLoop && this.sceneManager.scene && this.sceneManager.scene.activeCamera)) {
-            this.sceneManager.scene.render();
-            this.onFrameRenderedObservable.notifyObservers(this);
+        if (force || (this.sceneManager.scene && this.sceneManager.scene.activeCamera)) {
+            if (this.runRenderLoop) {
+                this.engine.performanceMonitor.enable();
+                this.sceneManager.scene.render();
+                this.onFrameRenderedObservable.notifyObservers(this);
+            } else {
+                this.engine.performanceMonitor.disable();
+
+                // Need to update behaviors
+                this.sceneManager.scene.activeCamera && this.sceneManager.scene.activeCamera.update();
+            }
         }
     }
 
     /**
+     * Takes a screenshot of the scene and returns it as a base64 encoded png.
+     * @param callback optional callback that will be triggered when screenshot is done.
+     * @param width Optional screenshot width (default to 512).
+     * @param height Optional screenshot height (default to 512).
+     * @returns a promise with the screenshot data
+     */
+    public takeScreenshot(callback?: (data: string) => void, width = 0, height = 0): Promise<string> {
+        width = width || this.canvas.clientWidth;
+        height = height || this.canvas.clientHeight;
+
+        // Create the screenshot
+        return new Promise<string>((resolve, reject) => {
+            try {
+                BABYLON.Tools.CreateScreenshot(this.engine, this.sceneManager.camera, { width, height }, (data) => {
+                    if (callback) {
+                        callback(data);
+                    }
+                    resolve(data);
+                });
+            } catch (e) {
+                reject(e);
+            }
+        });
+
+    }
+
+    /**
      * Update the current viewer configuration with new values.
      * Only provided information will be updated, old configuration values will be kept.
      * If this.configuration was manually changed, you can trigger this function with no parameters, 
@@ -495,7 +530,6 @@ export abstract class AbstractViewer {
 
         model.onLoadedObservable.add(() => {
             this._isLoading = false;
-            return this.onModelLoadedObservable.notifyObserversWithPromise(model);
         });
 
         return model;

+ 73 - 2
Viewer/tests/validation/config.json

@@ -1,16 +1,87 @@
 {
-    "root": "https://viewer.babylonjs.com",
+    "root": "",
     "tests": [
         {
+            "title": "Control",
+            "createMesh": true,
+            "configuration": {
+                "extends": "minimal"
+            }
+        },
+        {
+            "title": "Diffuse",
+            "createMesh": true,
+            "createMaterial": true,
+            "configuration": {
+                "extends": "minimal",
+                "model": {
+                    "material": {
+                        "albedoColor": {
+                            "r": 0,
+                            "g": 0,
+                            "b": 1
+                        },
+                        "reflectivityColor": {
+                            "r": 0,
+                            "g": 0,
+                            "b": 0
+                        },
+                        "microSurface": 0
+                    }
+                }
+            }
+        },
+        {
             "title": "Basic Helmet",
             "configuration": {
                 "extends": "minimal",
                 "model": {
                     "title": "Basic Helmet",
-                    "url": "https://www.babylonjs.com/Assets/DamagedHelmet/glTF/DamagedHelmet.gltf"
+                    "url": "/dist/assets/BrainStem/BrainStem.gltf"
                 }
             },
             "referenceImage": "basicHelmet.png"
+        },
+        {
+            "title": "Basic Helmet default",
+            "configuration": {
+                "extends": "default",
+                "model": {
+                    "title": "Basic Helmet",
+                    "url": "/dist/assets/BrainStem/BrainStem.gltf"
+                }
+            },
+            "waitForFrame": 300,
+            "referenceImage": "basicHelmetDefault.png"
+        },
+        {
+            "title": "Basic helmet transformation",
+            "configuration": {
+                "extends": "minimal",
+                "model": {
+                    "title": "Basic Helmet",
+                    "url": "https://www.babylonjs.com/Assets/DamagedHelmet/glTF/DamagedHelmet.gltf",
+                    "position": {
+                        "x": 20,
+                        "y": 10
+                    },
+                    "rotation": {
+                        "y": -0.3
+                    }
+                }
+            },
+            "referenceImage": "basicHelmetTransformation.png"
+        },
+        {
+            "title": "Position",
+            "configuration": {
+                "extends": "default",
+                "model": {
+                    "title": "Basic Helmet",
+                    "url": "https://www.babylonjs.com/Assets/DamagedHelmet/glTF/DamagedHelmet.gltf"
+                }
+            },
+            "referenceImage": "basicHelmetDefault.png"
         }
     ]
 }

+ 0 - 20
Viewer/tests/validation/integration.js

@@ -11,26 +11,6 @@ xhr.addEventListener("load", function () {
         config = JSON.parse(xhr.responseText);
 
         describe("Validation Tests", function () {
-            /*before(function (done) {
-                this.timeout(180000);
-                require = null;
-                BABYLONDEVTOOLS.Loader
-                    .require('/tests/validation/validation.js')
-                    .useDist()
-                    .load(function () {
-                        var info = engine.getGlInfo();
-                        console.log("Webgl Version: " + info.version);
-                        console.log("Webgl Vendor: " + info.vendor);
-                        // Reduces error ratio on Embedded Firefox for travis.
-                        if (info.vendor === "VMware, Inc.") {
-                            errorRatio = 5;
-                        }
-
-                        console.log("Webgl Renderer: " + info.renderer);
-                        done();
-                    });
-            });*/
-
             // Run tests
             for (let index = 0; index < config.tests.length; index++) {
                 var test = config.tests[index];

+ 32 - 6
Viewer/tests/validation/validation.js

@@ -192,14 +192,40 @@ function runTest(index, done) {
     viewerElement.innerHTML = '';
     currentViewer = new BabylonViewer.DefaultViewer(viewerElement, configuration);
 
-    var currentFrame = 0;
-    var waitForFrame = test.waitForFrame || 80;
-    currentViewer.onFrameRenderedObservable.add(() => {
-        if (currentFrame === waitForFrame) {
-            evaluate(test, resultCanvas, result, renderImage, index, waitRing, done);
+    currentViewer.onInitDoneObservable.add(() => {
+        if (test.createMesh) {
+            prepareMeshForViewer(currentViewer, configuration, test);
         }
-        currentFrame++;
+        var currentFrame = 0;
+        var waitForFrame = test.waitForFrame || 80;
+        currentViewer.onFrameRenderedObservable.add(() => {
+            if (currentFrame === waitForFrame) {
+                evaluate(test, resultCanvas, result, renderImage, index, waitRing, done);
+            }
+            currentFrame++;
+        });
     });
+
+
+}
+
+function prepareMeshForViewer(viewer, configuration, test) {
+    let meshModel = new BabylonViewer.ViewerModel(viewer, configuration.model || {});
+
+    let sphereMesh = BABYLON.Mesh.CreateSphere('sphere-' + test.title, 20, 1.0, viewer.sceneManager.scene);
+    if (test.createMaterial) {
+        let material = new BABYLON.PBRMaterial("sphereMat", viewer.sceneManager.scene);
+        material.environmentBRDFTexture = null;
+        material.useAlphaFresnel = material.needAlphaBlending();
+        material.backFaceCulling = material.forceDepthWrite;
+        material.twoSidedLighting = true;
+        material.useSpecularOverAlpha = false;
+        material.useRadianceOverAlpha = false;
+        material.usePhysicalLightFalloff = true;
+        material.forceNormalForward = true;
+        sphereMesh.material = material;
+    }
+    meshModel.addMesh(sphereMesh, true);
 }
 
 function init() {