瀏覽代碼

Merge remote-tracking branch 'BabylonJS/master' into gui-module

# Conflicts:
#	Tools/Gulp/config.json
#	gui/src/2D/controls/inputText.ts
#	gui/src/2D/controls/textBlock.ts
#	gui/src/3D/controls/cylinderPanel.ts
#	gui/src/3D/controls/planePanel.ts
#	gui/src/3D/controls/spherePanel.ts
Raanan Weber 7 年之前
父節點
當前提交
020a50c0da
共有 86 個文件被更改,包括 135902 次插入131930 次删除
  1. 7532 7359
      Playground/babylon.d.txt
  2. 297 245
      Playground/js/index.js
  3. 80 0
      Playground/js/libs/typescript.js
  4. 60 0
      Playground/js/ts.js
  5. 32 0
      Playground/scripts/basic scene.txt
  6. 467 0
      Playground/ts.html
  7. 24 2
      Tools/Gulp/config.json
  8. 3 3
      Viewer/src/configuration/types/extended.ts
  9. 3 2
      Viewer/src/managers/sceneManager.ts
  10. 2 4
      Viewer/src/templating/plugins/hdButtonPlugin.ts
  11. 20 2
      Viewer/src/templating/viewerTemplatePlugin.ts
  12. 1 1
      contributing.md
  13. 7904 7778
      dist/preview release/babylon.d.ts
  14. 62 62
      dist/preview release/babylon.js
  15. 29343 28897
      dist/preview release/babylon.max.js
  16. 29343 28897
      dist/preview release/babylon.no-module.max.js
  17. 60 60
      dist/preview release/babylon.worker.js
  18. 29345 28899
      dist/preview release/es6.js
  19. 48 2
      dist/preview release/gui/babylon.gui.d.ts
  20. 174 36
      dist/preview release/gui/babylon.gui.js
  21. 3 3
      dist/preview release/gui/babylon.gui.min.js
  22. 48 2
      dist/preview release/gui/babylon.gui.module.d.ts
  23. 4 3
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  24. 3 3
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  25. 4 3
      dist/preview release/loaders/babylon.glTFFileLoader.js
  26. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  27. 4 3
      dist/preview release/loaders/babylonjs.loaders.js
  28. 3 3
      dist/preview release/loaders/babylonjs.loaders.min.js
  29. 1 0
      dist/preview release/serializers/babylon.glTF2Serializer.d.ts
  30. 41 8
      dist/preview release/serializers/babylon.glTF2Serializer.js
  31. 2 2
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  32. 20 9
      dist/preview release/serializers/babylon.objSerializer.js
  33. 1 1
      dist/preview release/serializers/babylon.objSerializer.min.js
  34. 1 0
      dist/preview release/serializers/babylonjs.serializers.d.ts
  35. 61 17
      dist/preview release/serializers/babylonjs.serializers.js
  36. 2 2
      dist/preview release/serializers/babylonjs.serializers.min.js
  37. 1 0
      dist/preview release/serializers/babylonjs.serializers.module.d.ts
  38. 77 2
      dist/preview release/typedocValidationBaseline.json
  39. 4 2
      dist/preview release/viewer/babylon.viewer.d.ts
  40. 71 71
      dist/preview release/viewer/babylon.viewer.js
  41. 29378 28918
      dist/preview release/viewer/babylon.viewer.max.js
  42. 4 2
      dist/preview release/viewer/babylon.viewer.module.d.ts
  43. 10 0
      dist/preview release/what's new.md
  44. 16 0
      gui/src/2D/controls/inputPassword.ts
  45. 60 16
      gui/src/2D/controls/inputText.ts
  46. 86 20
      gui/src/2D/controls/textBlock.ts
  47. 5 6
      gui/src/3D/controls/cylinderPanel.ts
  48. 11 6
      gui/src/3D/controls/planePanel.ts
  49. 7 8
      gui/src/3D/controls/spherePanel.ts
  50. 5 3
      loaders/src/glTF/2.0/Extensions/EXT_lights_imageBased.ts
  51. 31 18
      serializers/src/OBJ/babylon.objSerializer.ts
  52. 44 7
      serializers/src/glTF/2.0/babylon.glTFExporter.ts
  53. 8 9
      src/Animations/babylon.runtimeAnimation.ts
  54. 18 13
      src/Behaviors/Mesh/babylon.pointerDragBehavior.ts
  55. 27 10
      src/Behaviors/Mesh/babylon.sixDofDragBehavior.ts
  56. 2 4
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  57. 11 1
      src/Cameras/VR/babylon.webVRCamera.ts
  58. 4 4
      src/Cameras/babylon.arcRotateCamera.ts
  59. 19 0
      src/Culling/babylon.boundingBox.ts
  60. 12 0
      src/Culling/babylon.boundingInfo.ts
  61. 17 0
      src/Culling/babylon.boundingSphere.ts
  62. 308 0
      src/Engine/Extensions/babylon.engine.occlusionQuery.ts
  63. 74 0
      src/Engine/Extensions/babylon.engine.transformFeedback.ts
  64. 21 290
      src/Engine/babylon.engine.ts
  65. 1 1
      src/Engine/babylon.nullEngine.ts
  66. 38 3
      src/Gamepad/Controllers/babylon.gearVRController.ts
  67. 109 23
      src/Gizmos/babylon.boundingBoxGizmo.ts
  68. 5 1
      src/Gizmos/babylon.gizmo.ts
  69. 138 46
      src/Gizmos/babylon.gizmoManager.ts
  70. 20 7
      src/Materials/Textures/babylon.videoTexture.ts
  71. 7 2
      src/Materials/babylon.materialHelper.ts
  72. 10 55
      src/Mesh/babylon.abstractMesh.ts
  73. 7 6
      src/Mesh/babylon.transformNode.ts
  74. 3 2
      src/Particles/babylon.IParticleSystem.ts
  75. 11 4
      src/Particles/babylon.gpuParticleSystem.ts
  76. 11 3
      src/Particles/babylon.particleSystem.ts
  77. 10 3
      src/Physics/Plugins/babylon.oimoJSPlugin.ts
  78. 4 4
      src/Tools/babylon.dds.ts
  79. 54 10
      src/Tools/babylon.environmentTextureTools.ts
  80. 4 4
      src/Tools/babylon.sceneOptimizer.ts
  81. 1 1
      src/Tools/babylon.tga.ts
  82. 1 1
      src/Tools/babylon.tools.ts
  83. 128 35
      tests/nullEngine/app.js
  84. 二進制
      tests/validation/ReferenceImages/dds.png
  85. 二進制
      tests/validation/ReferenceImages/tga.png
  86. 10 0
      tests/validation/config.json

File diff suppressed because it is too large
+ 7532 - 7359
Playground/babylon.d.txt


File diff suppressed because it is too large
+ 297 - 245
Playground/js/index.js


File diff suppressed because it is too large
+ 80 - 0
Playground/js/libs/typescript.js


+ 60 - 0
Playground/js/ts.js

@@ -0,0 +1,60 @@
+
+var compilerTriggerTimeoutID;
+function triggerCompile(d, func) {
+    if (compilerTriggerTimeoutID !== null) {
+        window.clearTimeout(compilerTriggerTimeoutID);
+    }
+    compilerTriggerTimeoutID = window.setTimeout(function () {
+        try {
+             
+            var output = transpileModule(d, {
+                module: ts.ModuleKind.AMD,
+                target: ts.ScriptTarget.ES5,
+                noLib: true,
+                noResolve: true,
+                suppressOutputPathCheck: true
+            });
+            if (typeof output === "string") {
+                func(output);
+            }
+        }
+        catch (e) {
+            showError(e.message, e);
+        }
+    }, 100);
+}
+function transpileModule(input, options) {
+    var inputFileName = options.jsx ? "module.tsx" : "module.ts";
+    var sourceFile = ts.createSourceFile(inputFileName, input, options.target || ts.ScriptTarget.ES5);
+    // Output
+    var outputText;
+    var program = ts.createProgram([inputFileName], options, {
+        getSourceFile: function (fileName) { return fileName.indexOf("module") === 0 ? sourceFile : undefined; },
+        writeFile: function (_name, text) { outputText = text; },
+        getDefaultLibFileName: function () { return "lib.d.ts"; },
+        useCaseSensitiveFileNames: function () { return false; },
+        getCanonicalFileName: function (fileName) { return fileName; },
+        getCurrentDirectory: function () { return ""; },
+        getNewLine: function () { return "\r\n"; },
+        fileExists: function (fileName) { return fileName === inputFileName; },
+        readFile: function () { return ""; },
+        directoryExists: function () { return true; },
+        getDirectories: function () { return []; }
+    });
+    // Emit
+    program.emit();
+    if (outputText === undefined) {
+        throw new Error("Output generation failed");
+    }
+    return outputText;
+}
+
+function getRunCode(jsEditor, callBack) {
+    triggerCompile(jsEditor.getValue(), function(result) {
+        callBack(result + "var createScene = function() { return Playground.CreateScene(engine, engine.getRenderingCanvas()); }")
+    });
+}
+
+var defaultScene = "scripts/basic scene.txt";
+var monacoMode = "typescript";
+

+ 32 - 0
Playground/scripts/basic scene.txt

@@ -0,0 +1,32 @@
+class Playground { 
+    public static CreateScene(engine: BABYLON.Engine, canvas: HTMLCanvasElement): BABYLON.Scene {
+        // This creates a basic Babylon Scene object (non-mesh)
+        var scene = new BABYLON.Scene(engine);
+
+        // This creates and positions a free camera (non-mesh)
+        var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
+
+        // This targets the camera to scene origin
+        camera.setTarget(BABYLON.Vector3.Zero());
+
+        // This attaches the camera to the canvas
+        camera.attachControl(canvas, true);
+
+        // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
+        var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
+
+        // Default intensity is 1. Let's dim the light a small amount
+        light.intensity = 0.7;
+
+        // Our built-in 'sphere' shape. Params: name, subdivs, size, scene
+        var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
+
+        // Move the sphere upward 1/2 its height
+        sphere.position.y = 1;
+
+        // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
+        var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
+
+        return scene;
+    }
+}

+ 467 - 0
Playground/ts.html

@@ -0,0 +1,467 @@
+<!DOCTYPE html>
+<html>
+
+    <head>
+        <title>Babylon.js Playground</title>
+        <meta charset='utf-8' />
+        <meta name="viewport" content="width=device-width, user-scalable=no">
+        <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
+        <link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
+        <link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
+        <link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
+        <link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
+        <link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
+        <link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
+        <link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
+        <link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
+        <link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
+        <link rel="icon" type="image/png" sizes="192x192" href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
+        <link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
+        <link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
+        <link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
+        <link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
+        <meta name="msapplication-TileColor" content="#ffffff">
+        <meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
+        <meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
+        <meta name="theme-color" content="#ffffff">
+
+        <script src="js/libs/pep.min.js"></script>
+        <!--For canvas/code separator-->
+        <script src="js/libs/split.js"></script>
+
+        <script src="js/libs/dat.gui.min.js"></script>
+        <!-- jszip -->
+        <script src="js/libs/jszip.min.js"></script>
+        <script src="js/libs/fileSaver.js"></script>
+        <!-- Dependencies -->
+        <script src="https://preview.babylonjs.com/cannon.js"></script>
+        <script src="https://preview.babylonjs.com/Oimo.js"></script>
+        <script src="https://preview.babylonjs.com/earcut.min.js"></script>
+        <!-- Monaco -->
+        <script src="node_modules/monaco-editor/min/vs/loader.js"></script>
+        <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/babylon.js"></script>
+        <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
+        <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
+        <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
+        <script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
+        <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script>
+        <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
+        <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
+
+        <!-- Extensions -->
+        <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js" async></script>
+        <script src="https://rawgit.com/BabylonJS/Extensions/master/CompoundShader/src/babylonx.CompoundShader.js" async></script>
+        <script src="https://www.babylontoolkit.com/playground/scripts/babylon.navmesh.js"></script>
+        <script src="https://www.babylontoolkit.com/playground/scripts/babylon.manager.js"></script>
+                               
+        <link href="css/index.css" rel="stylesheet" />
+    </head>
+
+    <body>
+        <div class="navbar navBar1600">
+            <div class="title">
+                Babylon.js Playground
+            </div>
+            <div class="version" id="mainTitle">
+            </div>
+
+            <div class="category">
+                <div class="button run" id="runButton1600">Run
+                    <i class="fa fa-play" aria-hidden="true"></i>
+                </div>
+            </div>
+
+
+            <div class="category">
+                <div class="button" id="newButton1600">New
+                    <i class="fa fa-file" aria-hidden="true"></i>
+                </div>
+                <div class="button removeOnPhone" id="clearButton1600">Clear
+                    <i class="fa fa-trash" aria-hidden="true"></i>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button" id="saveButton1600">Save
+                    <i class="fa fa-floppy-o" aria-hidden="true"></i>
+                </div>
+                <div class="button removeOnPhone" id="zipButton1600">Zip
+                    <i class="fa fa-download" aria-hidden="true"></i>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button select">Settings
+                    <div class="toDisplay">
+                        <div class="option subSelect">Theme
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" id="darkTheme1600">Dark</div>
+                                <div class="option" id="lightTheme1600">Light</div>
+                            </div>
+                        </div>
+                        <div class="option subSelect">
+                            <span id="currentFontSize1600">Font: 14</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setFontSize(12);">12</div>
+                                <div class="option" onclick="setFontSize(14);">14</div>
+                                <div class="option" onclick="setFontSize(16);">16</div>
+                                <div class="option" onclick="setFontSize(18);">18</div>
+                                <div class="option" onclick="setFontSize(20);">20</div>
+                                <div class="option" onclick="setFontSize(22);">22</div>
+                            </div>
+                        </div>
+                        <div class="option" id="safemodeToggle1600">Safe mode
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option checked" id="editorButton1600">Editor
+                            <i class="fa fa-check-square" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="fullscreenButton1600">Fullscreen</div>
+                        <div class="option" id="editorFullscreenButton1600">Editor Fullscreen</div>
+                        <div class="option" id="formatButton1600">Format code</div>
+                        <div class="option" id="minimapToggle1600">Minimap
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="button uncheck" id="debugButton1600">Inspector
+                    <i class="fa fa-square-o" aria-hidden="true"></i>
+                </div>
+                <div class="button" id="metadataButton1600">Metadata</div>
+            </div>
+
+
+
+            <div class="category right">
+                <div class="button select">
+                    <span id="currentVersion1600">Version: Latest</span>
+                    <div class="toDisplay">
+                        <div class="option" onclick="setVersion('latest');">Latest</div>
+                        <div class="option" onclick="setVersion('stable');">Stable</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="navbar navBar1475">
+            <div class="title">
+                Babylon.js Playground
+            </div>
+            <div class="version" id="mainTitle">
+            </div>
+
+            <div class="category">
+                <div class="button run" id="runButton1475">Run
+                    <i class="fa fa-play" aria-hidden="true"></i>
+                </div>
+            </div>
+
+
+            <div class="category">
+                <div class="button" id="newButton1475">New
+                    <i class="fa fa-file" aria-hidden="true"></i>
+                </div>
+                <div class="button removeOnPhone" id="clearButton1475">Clear
+                    <i class="fa fa-trash" aria-hidden="true"></i>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button" id="saveButton1475">Save
+                    <i class="fa fa-floppy-o" aria-hidden="true"></i>
+                </div>
+                <div class="button removeOnPhone" id="zipButton1475">Zip
+                    <i class="fa fa-download" aria-hidden="true"></i>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button select">Settings
+                    <div class="toDisplay">
+                        <div class="option subSelect">Theme
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" id="darkTheme1475">Dark</div>
+                                <div class="option" id="lightTheme1475">Light</div>
+                            </div>
+                        </div>
+                        <div class="option subSelect">
+                            <span id="currentFontSize1475">Font: 14</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setFontSize(12);">12</div>
+                                <div class="option" onclick="setFontSize(14);">14</div>
+                                <div class="option" onclick="setFontSize(16);">16</div>
+                                <div class="option" onclick="setFontSize(18);">18</div>
+                                <div class="option" onclick="setFontSize(20);">20</div>
+                                <div class="option" onclick="setFontSize(22);">22</div>
+                            </div>
+                        </div>
+                        <div class="option" id='safemodeToggle1475'>Safe mode
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option checked" id="editorButton1475">Editor
+                            <i class="fa fa-check-square" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="fullscreenButton1475">Fullscreen</div>
+                        <div class="option" id="editorFullscreenButton1475">Editor Fullscreen</div>
+                        <div class="option" id="formatButton1475">Format code</div>
+                        <div class="option" id="minimapToggle1475">Minimap
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="debugButton1475">Inspector
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="metadataButton1475">Metadata</div>
+                        <div class="option subSelect">
+                            <span id="currentVersion1475">Vers. : Latest</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setVersion('latest');">Latest</div>
+                                <div class="option" onclick="setVersion('stable');">Stable</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="navbar navBar1030">
+            <div class="category">
+                <div class="button run" id="runButton1030">Run
+                    <i class="fa fa-play" aria-hidden="true"></i>
+                </div>
+            </div>
+
+
+            <div class="category">
+                <div class="button" id="newButton1030">New
+                    <i class="fa fa-file" aria-hidden="true"></i>
+                </div>
+                <div class="button removeOnPhone" id="clearButton1030">Clear
+                    <i class="fa fa-trash" aria-hidden="true"></i>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button" id="saveButton1030">Save
+                    <i class="fa fa-floppy-o" aria-hidden="true"></i>
+                </div>
+                <div class="button removeOnPhone" id="zipButton1030">Zip
+                    <i class="fa fa-download" aria-hidden="true"></i>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button select">Settings
+                    <div class="toDisplay">
+                        <div class="option subSelect">Theme
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" id="darkTheme1030">Dark</div>
+                                <div class="option" id="lightTheme1030">Light</div>
+                            </div>
+                        </div>
+                        <div class="option subSelect">
+                            <span id="currentFontSize1030">Font: 14</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setFontSize(12);">12</div>
+                                <div class="option" onclick="setFontSize(14);">14</div>
+                                <div class="option" onclick="setFontSize(16);">16</div>
+                                <div class="option" onclick="setFontSize(18);">18</div>
+                                <div class="option" onclick="setFontSize(20);">20</div>
+                                <div class="option" onclick="setFontSize(22);">22</div>
+                            </div>
+                        </div>
+                        <div class="option" id="safemodeToggle1030">Safe mode
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option checked" id="editorButton1030">Editor
+                            <i class="fa fa-check-square" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="fullscreenButton1030">Fullscreen</div>
+                        <div class="option" id="editorFullscreenButton1030">Editor Fullscreen</div>
+                        <div class="option" id="formatButton1030">Format code</div>
+                        <div class="option" id="minimapToggle1030">Minimap
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="debugButton1030">Inspector
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="metadataButton1030">Metadata</div>
+                        <div class="option subSelect">
+                            <span id="currentVersion1030">Vers. : Latest</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setVersion('latest');">Latest</div>
+                                <div class="option" onclick="setVersion('stable');">Stable</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="navbar navBar750">
+            <div class="category">
+                <div class="button select">File
+                    <div class="toDisplay">
+                        <div class="option" id="runButton750">Run
+                            <i class="fa fa-play" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="newButton750">New
+                            <i class="fa fa-file" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="clearButton750">Clear
+                            <i class="fa fa-trash" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="saveButton750">Save
+                            <i class="fa fa-floppy-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="zipButton750">Zip
+                            <i class="fa fa-download" aria-hidden="true"></i>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="category">
+                <div class="button select">Settings
+                    <div class="toDisplay">
+                        <div class="option subSelect">Theme
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" id="darkTheme750">Dark</div>
+                                <div class="option" id="lightTheme750">Light</div>
+                            </div>
+                        </div>
+                        <div class="option subSelect">
+                            <span id="currentFontSize750">Font: 14</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setFontSize(12);">12</div>
+                                <div class="option" onclick="setFontSize(14);">14</div>
+                                <div class="option" onclick="setFontSize(16);">16</div>
+                                <div class="option" onclick="setFontSize(18);">18</div>
+                                <div class="option" onclick="setFontSize(20);">20</div>
+                                <div class="option" onclick="setFontSize(22);">22</div>
+                            </div>
+                        </div>
+                        <div class="option" id="safemodeToggle750">Safe mode
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div style="display:none;" class="option checked" id="editorButton750">Editor
+                            <i class="fa fa-check-square" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="fullscreenButton750">Fullscreen</div>
+                        <div class="option" id="editorFullscreenButton750">Editor Fullscreen</div>
+                        <div class="option" id="formatButton750">Format code</div>
+                        <div class="option" id="minimapToggle750">Minimap
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="debugButton750">Inspector
+                            <i class="fa fa-square-o" aria-hidden="true"></i>
+                        </div>
+                        <div class="option" id="metadataButton750">Metadata</div>
+                        <div class="option subSelect">
+                            <span id="currentVersion750">Vers. : Latest</span>
+                            <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                            <div class="toDisplaySub">
+                                <div class="option" onclick="setVersion('latest');">Latest</div>
+                                <div class="option" onclick="setVersion('stable');">Stable</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="wrapper">
+            <div id="jsEditor"></div>
+            <div id="canvasZone">
+                <canvas touch-action="none" id="renderCanvas"></canvas>
+            </div>
+        </div>
+
+        <span class="label" id="fpsLabel">FPS</span>
+
+        <div id="errorZone">
+        </div>
+
+        <div class="navbarBottom">
+            <div id="statusBar"></div>
+            <div class="links">
+                <div class='link'>
+                    <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
+                </div>
+                <div class='link'>
+                    <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+                </div>
+                <div class='link'>
+                    <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>
+                </div>
+                <div class='link'>
+                    <a target='_new' href="https://doc.babylonjs.com">Documentation</a>
+                </div>
+                <div class='link'>
+                    <a target='_new' href="https://doc.babylonjs.com/playground">Search</a>
+                </div>
+            </div>
+        </div>
+
+        <div id="saveLayer" class="save-layer">
+            <div class="save-form">
+                <label for="saveFormTitle">TITLE</label>
+                <div class="separator"></div>
+                <input type="text" maxlength="120" id="saveFormTitle" class="save-form-title">
+
+                <label for="saveFormDescription">DESCRIPTION</label>
+                <div class="separator"></div>
+                <textarea id="saveFormDescription" rows="4" cols="10"></textarea>
+
+                <label for="saveFormTags">TAGS (separated by comma)</label>
+                <div class="separator"></div>
+                <textarea id="saveFormTags" rows="4" cols="10"></textarea>
+
+                <div class="save-form-buttons" id="saveFormButtons">
+
+                    <div id="saveFormButtonOk" class="button">OK</div>
+                    <div id="saveFormButtonCancel" class="button">Cancel</div>
+                </div>
+            </div>
+        </div>
+
+        <div id="waitDiv">
+            <span id="waitTitle">Babylon.js Playground
+                <BR>
+                <BR>
+                <BR>
+            </span>
+            <img src="waitlogo.png" id="waitLogo" />
+        </div>
+
+        <script src="js/libs/jquery.min.js"></script>
+
+        <script src="js/actions.js"></script>
+        <script src="js/pbt.js"></script>
+        <script src="js/libs/typescript.js"></script>
+        <script src="js/index.js"></script>
+        <script src="js/ts.js"></script>
+        
+        <!-- Global site tag (gtag.js) - Google Analytics -->
+        <script async src="https://www.googletagmanager.com/gtag/js?id=UA-41767310-2"></script>
+        <script>
+        window.dataLayer = window.dataLayer || [];
+        function gtag(){dataLayer.push(arguments);}
+        gtag('js', new Date());
+
+        gtag('config', 'UA-41767310-2');
+        </script>        
+    </body>
+
+</html>

+ 24 - 2
Tools/Gulp/config.json

@@ -100,6 +100,7 @@
             "morphTargets",
             "octrees",
             "anaglyph",
+            "stereoscopic",
             "vr",
             "virtualJoystick",
             "optimizations",
@@ -119,7 +120,9 @@
             "videoDome",
             "photoDome",
             "behaviors",
-            "imageProcessing"
+            "imageProcessing",
+            "occlusionQuery",
+            "transformFeedback"
         ],
         "minimal": [
             "meshBuilder",
@@ -233,6 +236,24 @@
                 "fogFragment"
             ]
         },
+        "transformFeedback": {
+            "files": [
+                "../../src/Engine/Extensions/babylon.engine.transformFeedback.js"
+            ],
+            "dependUpon": [
+                "core",
+                "debug"
+            ]
+        },
+        "occlusionQuery": {
+            "files": [
+                "../../src/Engine/Extensions/babylon.engine.occlusionQuery.js"
+            ],
+            "dependUpon": [
+                "core",
+                "debug"
+            ]
+        },
         "behaviors": {
             "files": [
                 "../../src/Behaviors/babylon.behavior.js"
@@ -274,7 +295,8 @@
             ],
             "dependUpon": [
                 "core",
-                "particles"
+                "particles",
+                "transformFeedback"
             ],
             "shaders": [
                 "gpuRenderParticles.vertex",

+ 3 - 3
Viewer/src/configuration/types/extended.ts

@@ -62,7 +62,7 @@ export let extendedConfiguration: ViewerConfiguration = {
             intensity: 7,
             intensityMode: 0,
             radius: 0.6,
-            range: 0.6,
+            range: 4.4,
             spotAngle: 60,
             diffuse: {
                 r: 1,
@@ -101,7 +101,7 @@ export let extendedConfiguration: ViewerConfiguration = {
             intensity: 7,
             intensityMode: 0,
             radius: 0.4,
-            range: 0.4,
+            range: 5.8,
             spotAngle: 57,
             diffuse: {
                 r: 1,
@@ -132,7 +132,7 @@ export let extendedConfiguration: ViewerConfiguration = {
             intensity: 1,
             intensityMode: 0,
             radius: 0.5,
-            range: 0.5,
+            range: 6,
             spotAngle: 42.85,
             diffuse: {
                 r: 0.8,

+ 3 - 2
Viewer/src/managers/sceneManager.ts

@@ -7,6 +7,7 @@ import { ViewerLabs } from '../labs/viewerLabs';
 import { getCustomOptimizerByName } from '../optimizer/custom/';
 import { ObservablesManager } from '../managers/observablesManager';
 import { ConfigurationContainer } from '../configuration/configurationContainer';
+import { deepmerge } from '../helper';
 
 /**
  * This interface describes the structure of the variable sent with the configuration observables of the scene manager.
@@ -796,11 +797,11 @@ export class SceneManager {
             }
             return;
         }
-        let vrOptions: VRExperienceHelperOptions = vrConfig.vrOptions || {
+        let vrOptions: VRExperienceHelperOptions = deepmerge({
             useCustomVRButton: true,
             createDeviceOrientationCamera: false,
             trackPosition: true
-        }
+        }, vrConfig.vrOptions || {});
 
         this._vrHelper = this.scene.createDefaultVRExperience(vrOptions);
         if (!vrConfig.disableInteractions) {

+ 2 - 4
Viewer/src/templating/plugins/hdButtonPlugin.ts

@@ -4,10 +4,8 @@ import { EventCallback, Template } from "../templateManager";
 
 export class HDButtonPlugin extends AbstractViewerNavbarButton {
 
-    protected _buttonClass = "hd-button";
-
     constructor(private _viewer: DefaultViewer) {
-        super();
+        super("hd", "hd-button", HDButtonPlugin.HtmlTemplate);
     }
 
     onEvent(event: EventCallback): void {
@@ -18,7 +16,7 @@ export class HDButtonPlugin extends AbstractViewerNavbarButton {
         this._viewer.toggleHD();
     }
 
-    protected _htmlTemplate: string = `
+    protected static HtmlTemplate: string = `
 {{#unless hideHd}}
 <style>
 .hd-icon:after {

+ 20 - 2
Viewer/src/templating/viewerTemplatePlugin.ts

@@ -16,8 +16,26 @@ export abstract class AbstractViewerNavbarButton implements IViewerTemplatePlugi
     public readonly templateName: string = "navBar";
     public readonly eventsToAttach: Array<string> = ['pointerdown'];
     protected _prepend: boolean = true;
-    protected abstract _buttonClass: string;
-    protected abstract _htmlTemplate: string;
+    protected _buttonName: string;
+    protected _buttonClass: string;
+    protected _htmlTemplate: string;
+
+    constructor(buttonName: string, buttonClass?: string, htmlTemplate?: string) {
+        this._buttonName = buttonName;
+        if (buttonClass) {
+            this._buttonClass = buttonClass;
+        } else {
+            this._buttonClass = buttonName + '-button';
+        }
+        if (htmlTemplate) { this._htmlTemplate = htmlTemplate }
+        else {
+            this._htmlTemplate = `
+<button class="${this._buttonClass}">
+    <span class="icon ${this._buttonName}-icon"></span>
+</button>
+`;
+        }
+    }
 
     interactionPredicate(event: EventCallback): boolean {
         let pointerDown = <PointerEvent>event.event;

+ 1 - 1
contributing.md

@@ -23,7 +23,7 @@ We will try to enforce these rules as we consider the forum is a better place fo
 
 ## Pull requests
 
-We are not complicated people, but we still have some [coding guidelines](http://doc.babylonjs.com/generals/Approved_Naming_Conventions)
+We are not complicated people, but we still have some [coding guidelines](http://doc.babylonjs.com/how_to/approved_naming_conventions)
 Before submitting your PR, just check that everything goes well by [creating the minified version](http://doc.babylonjs.com/generals/Creating_the_Mini-fied_Version)
 
 Need help contributing, here are some links:

File diff suppressed because it is too large
+ 7904 - 7778
dist/preview release/babylon.d.ts


File diff suppressed because it is too large
+ 62 - 62
dist/preview release/babylon.js


File diff suppressed because it is too large
+ 29343 - 28897
dist/preview release/babylon.max.js


File diff suppressed because it is too large
+ 29343 - 28897
dist/preview release/babylon.no-module.max.js


File diff suppressed because it is too large
+ 60 - 60
dist/preview release/babylon.worker.js


File diff suppressed because it is too large
+ 29345 - 28899
dist/preview release/es6.js


+ 48 - 2
dist/preview release/gui/babylon.gui.d.ts

@@ -1316,6 +1316,23 @@ declare module BABYLON.GUI {
 
 declare module BABYLON.GUI {
     /**
+     * Enum that determines the text-wrapping mode to use.
+     */
+    enum TextWrapping {
+        /**
+         * Clip the text when it's larger than Control.width; this is the default mode.
+         */
+        Clip = 0,
+        /**
+         * Wrap the text word-wise, i.e. try to add line-breaks at word boundary to fit within Control.width.
+         */
+        WordWrap = 1,
+        /**
+         * Ellipsize the text, i.e. shrink with trailing … when text is larger than Control.width.
+         */
+        Ellipsis = 2,
+    }
+    /**
      * Class used to create text block control
      */
     class TextBlock extends Control {
@@ -1357,7 +1374,7 @@ declare module BABYLON.GUI {
         /**
          * Gets or sets a boolean indicating if text must be wrapped
          */
-        textWrapping: boolean;
+        textWrapping: TextWrapping | boolean;
         /**
          * Gets or sets text to display
          */
@@ -1416,9 +1433,16 @@ declare module BABYLON.GUI {
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
         protected _applyStates(context: CanvasRenderingContext2D): void;
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[];
         protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
-        protected _parseLineWithTextWrapping(line: string | undefined, context: CanvasRenderingContext2D): object;
+        protected _parseLineEllipsis(line: string | undefined, width: number, context: CanvasRenderingContext2D): object;
+        protected _parseLineWordWrap(line: string | undefined, width: number, context: CanvasRenderingContext2D): object[];
         protected _renderLines(context: CanvasRenderingContext2D): void;
+        /**
+         * Given a width constraint applied on the text block, find the expected height
+         * @returns expected height
+         */
+        computeExpectedHeight(): number;
         dispose(): void;
     }
 }
@@ -1664,10 +1688,15 @@ declare module BABYLON.GUI {
         private _scrollLeft;
         private _textWidth;
         private _clickedCoordinate;
+        private _deadKey;
+        private _addKey;
+        private _currentKey;
         /** Gets or sets a string representing the message displayed on mobile when the control gets the focus */
         promptMessage: string;
         /** Observable raised when the text changes */
         onTextChangedObservable: Observable<InputText>;
+        /** Observable raised just before an entered character is to be added */
+        onBeforeKeyAddObservable: Observable<InputText>;
         /** Observable raised when the control gets the focus */
         onFocusObservable: Observable<InputText>;
         /** Observable raised when the control loses the focus */
@@ -1692,6 +1721,12 @@ declare module BABYLON.GUI {
         placeholderColor: string;
         /** Gets or sets the text displayed when the control is empty */
         placeholderText: string;
+        /** Gets or sets the dead key flag */
+        deadKey: boolean;
+        /** Gets or sets if the current key should be added */
+        addKey: boolean;
+        /** Gets or sets the value of the current key being entered */
+        currentKey: string;
         /** Gets or sets the text displayed in the control */
         text: string;
         /** Gets or sets control width */
@@ -1714,6 +1749,7 @@ declare module BABYLON.GUI {
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
         _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
         _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
+        protected _beforeRenderText(text: string): string;
         dispose(): void;
     }
 }
@@ -1721,6 +1757,16 @@ declare module BABYLON.GUI {
 
 declare module BABYLON.GUI {
     /**
+     * Class used to create a password control
+     */
+    class InputPassword extends InputText {
+        protected _beforeRenderText(text: string): string;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
      * Class used to store key control properties
      */
     class KeyPropertySet {

+ 174 - 36
dist/preview release/gui/babylon.gui.js

@@ -4483,6 +4483,24 @@ var BABYLON;
     var GUI;
     (function (GUI) {
         /**
+         * Enum that determines the text-wrapping mode to use.
+         */
+        var TextWrapping;
+        (function (TextWrapping) {
+            /**
+             * Clip the text when it's larger than Control.width; this is the default mode.
+             */
+            TextWrapping[TextWrapping["Clip"] = 0] = "Clip";
+            /**
+             * Wrap the text word-wise, i.e. try to add line-breaks at word boundary to fit within Control.width.
+             */
+            TextWrapping[TextWrapping["WordWrap"] = 1] = "WordWrap";
+            /**
+             * Ellipsize the text, i.e. shrink with trailing … when text is larger than Control.width.
+             */
+            TextWrapping[TextWrapping["Ellipsis"] = 2] = "Ellipsis";
+        })(TextWrapping = GUI.TextWrapping || (GUI.TextWrapping = {}));
+        /**
          * Class used to create text block control
          */
         var TextBlock = /** @class */ (function (_super) {
@@ -4501,7 +4519,7 @@ var BABYLON;
                 var _this = _super.call(this, name) || this;
                 _this.name = name;
                 _this._text = "";
-                _this._textWrapping = false;
+                _this._textWrapping = TextWrapping.Clip;
                 _this._textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
                 _this._textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_CENTER;
                 _this._resizeToFit = false;
@@ -4563,7 +4581,7 @@ var BABYLON;
                     if (this._textWrapping === value) {
                         return;
                     }
-                    this._textWrapping = value;
+                    this._textWrapping = +value;
                     this._markAsDirty();
                 },
                 enumerable: true,
@@ -4734,37 +4752,59 @@ var BABYLON;
                 }
             };
             TextBlock.prototype._additionalProcessing = function (parentMeasure, context) {
-                this._lines = [];
+                this._lines = this._breakLines(this._currentMeasure.width, context);
+                this.onLinesReadyObservable.notifyObservers(this);
+            };
+            TextBlock.prototype._breakLines = function (refWidth, context) {
+                var lines = [];
                 var _lines = this.text.split("\n");
-                if (this._textWrapping && !this._resizeToFit) {
+                if (this._textWrapping === TextWrapping.Ellipsis && !this._resizeToFit) {
                     for (var _i = 0, _lines_1 = _lines; _i < _lines_1.length; _i++) {
                         var _line = _lines_1[_i];
-                        this._lines.push(this._parseLineWithTextWrapping(_line, context));
+                        lines.push(this._parseLineEllipsis(_line, refWidth, context));
                     }
                 }
-                else {
+                else if (this._textWrapping === TextWrapping.WordWrap && !this._resizeToFit) {
                     for (var _a = 0, _lines_2 = _lines; _a < _lines_2.length; _a++) {
                         var _line = _lines_2[_a];
-                        this._lines.push(this._parseLine(_line, context));
+                        lines.push.apply(lines, this._parseLineWordWrap(_line, refWidth, context));
                     }
                 }
-                this.onLinesReadyObservable.notifyObservers(this);
+                else {
+                    for (var _b = 0, _lines_3 = _lines; _b < _lines_3.length; _b++) {
+                        var _line = _lines_3[_b];
+                        lines.push(this._parseLine(_line, context));
+                    }
+                }
+                return lines;
             };
             TextBlock.prototype._parseLine = function (line, context) {
                 if (line === void 0) { line = ''; }
                 return { text: line, width: context.measureText(line).width };
             };
-            TextBlock.prototype._parseLineWithTextWrapping = function (line, context) {
+            TextBlock.prototype._parseLineEllipsis = function (line, width, context) {
                 if (line === void 0) { line = ''; }
+                var lineWidth = context.measureText(line).width;
+                if (lineWidth > width) {
+                    line += '…';
+                }
+                while (line.length > 2 && lineWidth > width) {
+                    line = line.slice(0, -2) + '…';
+                    lineWidth = context.measureText(line).width;
+                }
+                return { text: line, width: lineWidth };
+            };
+            TextBlock.prototype._parseLineWordWrap = function (line, width, context) {
+                if (line === void 0) { line = ''; }
+                var lines = [];
                 var words = line.split(' ');
-                var width = this._currentMeasure.width;
                 var lineWidth = 0;
                 for (var n = 0; n < words.length; n++) {
                     var testLine = n > 0 ? line + " " + words[n] : words[0];
                     var metrics = context.measureText(testLine);
                     var testWidth = metrics.width;
                     if (testWidth > width && n > 0) {
-                        this._lines.push({ text: line, width: lineWidth });
+                        lines.push({ text: line, width: lineWidth });
                         line = words[n];
                         lineWidth = context.measureText(line).width;
                     }
@@ -4773,7 +4813,8 @@ var BABYLON;
                         line = testLine;
                     }
                 }
-                return { text: line, width: lineWidth };
+                lines.push({ text: line, width: lineWidth });
+                return lines;
             };
             TextBlock.prototype._renderLines = function (context) {
                 var height = this._currentMeasure.height;
@@ -4814,6 +4855,24 @@ var BABYLON;
                     this.height = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length + 'px';
                 }
             };
+            /**
+             * Given a width constraint applied on the text block, find the expected height
+             * @returns expected height
+             */
+            TextBlock.prototype.computeExpectedHeight = function () {
+                if (this.text && this.widthInPixels) {
+                    var context = document.createElement('canvas').getContext('2d');
+                    if (context) {
+                        this._applyStates(context);
+                        if (!this._fontOffset) {
+                            this._fontOffset = GUI.Control._GetFontOffset(context.font);
+                        }
+                        var lines = this._lines ? this._lines : this._breakLines(this.widthInPixels - this.paddingLeftInPixels - this.paddingRightInPixels, context);
+                        return this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * lines.length;
+                    }
+                }
+                return 0;
+            };
             TextBlock.prototype.dispose = function () {
                 _super.prototype.dispose.call(this);
                 this.onTextChangedObservable.clear();
@@ -5751,10 +5810,15 @@ var BABYLON;
                 _this._isFocused = false;
                 _this._blinkIsEven = false;
                 _this._cursorOffset = 0;
+                _this._deadKey = false;
+                _this._addKey = true;
+                _this._currentKey = "";
                 /** Gets or sets a string representing the message displayed on mobile when the control gets the focus */
                 _this.promptMessage = "Please enter text:";
                 /** Observable raised when the text changes */
                 _this.onTextChangedObservable = new BABYLON.Observable();
+                /** Observable raised just before an entered character is to be added */
+                _this.onBeforeKeyAddObservable = new BABYLON.Observable();
                 /** Observable raised when the control gets the focus */
                 _this.onFocusObservable = new BABYLON.Observable();
                 /** Observable raised when the control loses the focus */
@@ -5900,6 +5964,39 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(InputText.prototype, "deadKey", {
+                /** Gets or sets the dead key flag */
+                get: function () {
+                    return this._deadKey;
+                },
+                set: function (flag) {
+                    this._deadKey = flag;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(InputText.prototype, "addKey", {
+                /** Gets or sets if the current key should be added */
+                get: function () {
+                    return this._addKey;
+                },
+                set: function (flag) {
+                    this._addKey = flag;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(InputText.prototype, "currentKey", {
+                /** Gets or sets the value of the current key being entered */
+                get: function () {
+                    return this._currentKey;
+                },
+                set: function (key) {
+                    this._currentKey = key;
+                },
+                enumerable: true,
+                configurable: true
+            });
             Object.defineProperty(InputText.prototype, "text", {
                 /** Gets or sets the text displayed in the control */
                 get: function () {
@@ -5967,7 +6064,7 @@ var BABYLON;
                 // Specific cases
                 switch (keyCode) {
                     case 32: //SPACE
-                        key = " "; //ie11 key for space is "Spacebar" 
+                        key = " "; //ie11 key for space is "Spacebar"
                         break;
                     case 8: // BACKSPACE
                         if (this._text && this._text.length > 0) {
@@ -6018,21 +6115,30 @@ var BABYLON;
                         this._blinkIsEven = false;
                         this._markAsDirty();
                         return;
+                    case 222: // Dead
+                        this.deadKey = true;
+                        return;
                 }
                 // Printable characters
-                if ((keyCode === -1) || // Direct access
-                    (keyCode === 32) || // Space
-                    (keyCode > 47 && keyCode < 58) || // Numbers
-                    (keyCode > 64 && keyCode < 91) || // Letters
-                    (keyCode > 185 && keyCode < 193) || // Special characters
-                    (keyCode > 218 && keyCode < 223) || // Special characters
-                    (keyCode > 95 && keyCode < 112)) { // Numpad
-                    if (this._cursorOffset === 0) {
-                        this.text += key;
-                    }
-                    else {
-                        var insertPosition = this._text.length - this._cursorOffset;
-                        this.text = this._text.slice(0, insertPosition) + key + this._text.slice(insertPosition);
+                if (key &&
+                    ((keyCode === -1) || // Direct access
+                        (keyCode === 32) || // Space
+                        (keyCode > 47 && keyCode < 58) || // Numbers
+                        (keyCode > 64 && keyCode < 91) || // Letters
+                        (keyCode > 185 && keyCode < 193) || // Special characters
+                        (keyCode > 218 && keyCode < 223) || // Special characters
+                        (keyCode > 95 && keyCode < 112))) { // Numpad
+                    this._currentKey = key;
+                    this.onBeforeKeyAddObservable.notifyObservers(this);
+                    key = this._currentKey;
+                    if (this._addKey) {
+                        if (this._cursorOffset === 0) {
+                            this.text += key;
+                        }
+                        else {
+                            var insertPosition = this._text.length - this._cursorOffset;
+                            this.text = this._text.slice(0, insertPosition) + key + this._text.slice(insertPosition);
+                        }
                     }
                 }
             };
@@ -6075,7 +6181,7 @@ var BABYLON;
                     if (this.color) {
                         context.fillStyle = this.color;
                     }
-                    var text = this._text;
+                    var text = this._beforeRenderText(this._text);
                     if (!this._isFocused && !this._text && this._placeholderText) {
                         text = this._placeholderText;
                         if (this._placeholderColor) {
@@ -6178,6 +6284,9 @@ var BABYLON;
             InputText.prototype._onPointerUp = function (target, coordinates, pointerId, buttonIndex, notifyClick) {
                 _super.prototype._onPointerUp.call(this, target, coordinates, pointerId, buttonIndex, notifyClick);
             };
+            InputText.prototype._beforeRenderText = function (text) {
+                return text;
+            };
             InputText.prototype.dispose = function () {
                 _super.prototype.dispose.call(this);
                 this.onBlurObservable.clear();
@@ -6197,6 +6306,33 @@ var BABYLON;
     var GUI;
     (function (GUI) {
         /**
+         * Class used to create a password control
+         */
+        var InputPassword = /** @class */ (function (_super) {
+            __extends(InputPassword, _super);
+            function InputPassword() {
+                return _super !== null && _super.apply(this, arguments) || this;
+            }
+            InputPassword.prototype._beforeRenderText = function (text) {
+                var txt = "";
+                for (var i = 0; i < text.length; i++) {
+                    txt += "\u2022";
+                }
+                return txt;
+            };
+            return InputPassword;
+        }(GUI.InputText));
+        GUI.InputPassword = InputPassword;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        /**
          * Class used to store key control properties
          */
         var KeyPropertySet = /** @class */ (function () {
@@ -8766,13 +8902,12 @@ var BABYLON;
                         mesh.lookAt(new BABYLON.Vector3(-newPos.x, -newPos.y, -newPos.z));
                         break;
                     case GUI.Container3D.FACEORIGINREVERSED_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(newPos.x, newPos.y, newPos.z));
+                        mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, 2 * newPos.y, 2 * newPos.z));
                         break;
                     case GUI.Container3D.FACEFORWARD_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(0, 0, 1));
                         break;
                     case GUI.Container3D.FACEFORWARDREVERSED_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(0, 0, -1));
+                        mesh.rotate(BABYLON.Axis.Y, Math.PI, BABYLON.Space.LOCAL);
                         break;
                 }
             };
@@ -8809,14 +8944,18 @@ var BABYLON;
                     return;
                 }
                 control.position = nodePosition.clone();
+                var target = BABYLON.Tmp.Vector3[0];
+                target.copyFrom(nodePosition);
                 switch (this.orientation) {
                     case GUI.Container3D.FACEORIGIN_ORIENTATION:
                     case GUI.Container3D.FACEFORWARD_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(0, 0, -1));
+                        target.addInPlace(new BABYLON.Vector3(0, 0, -1));
+                        mesh.lookAt(target);
                         break;
                     case GUI.Container3D.FACEFORWARDREVERSED_ORIENTATION:
                     case GUI.Container3D.FACEORIGINREVERSED_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(0, 0, 1));
+                        target.addInPlace(new BABYLON.Vector3(0, 0, 1));
+                        mesh.lookAt(target);
                         break;
                 }
             };
@@ -8983,16 +9122,15 @@ var BABYLON;
                 control.position = newPos;
                 switch (this.orientation) {
                     case GUI.Container3D.FACEORIGIN_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(-newPos.x, 0, -newPos.z));
+                        mesh.lookAt(new BABYLON.Vector3(-newPos.x, newPos.y, -newPos.z));
                         break;
                     case GUI.Container3D.FACEORIGINREVERSED_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(newPos.x, 0, newPos.z));
+                        mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, newPos.y, 2 * newPos.z));
                         break;
                     case GUI.Container3D.FACEFORWARD_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(0, 0, 1));
                         break;
                     case GUI.Container3D.FACEFORWARDREVERSED_ORIENTATION:
-                        mesh.lookAt(new BABYLON.Vector3(0, 0, -1));
+                        mesh.rotate(BABYLON.Axis.Y, Math.PI, BABYLON.Space.LOCAL);
                         break;
                 }
             };

File diff suppressed because it is too large
+ 3 - 3
dist/preview release/gui/babylon.gui.min.js


+ 48 - 2
dist/preview release/gui/babylon.gui.module.d.ts

@@ -1321,6 +1321,23 @@ declare module BABYLON.GUI {
 
 declare module BABYLON.GUI {
     /**
+     * Enum that determines the text-wrapping mode to use.
+     */
+    enum TextWrapping {
+        /**
+         * Clip the text when it's larger than Control.width; this is the default mode.
+         */
+        Clip = 0,
+        /**
+         * Wrap the text word-wise, i.e. try to add line-breaks at word boundary to fit within Control.width.
+         */
+        WordWrap = 1,
+        /**
+         * Ellipsize the text, i.e. shrink with trailing … when text is larger than Control.width.
+         */
+        Ellipsis = 2,
+    }
+    /**
      * Class used to create text block control
      */
     class TextBlock extends Control {
@@ -1362,7 +1379,7 @@ declare module BABYLON.GUI {
         /**
          * Gets or sets a boolean indicating if text must be wrapped
          */
-        textWrapping: boolean;
+        textWrapping: TextWrapping | boolean;
         /**
          * Gets or sets text to display
          */
@@ -1421,9 +1438,16 @@ declare module BABYLON.GUI {
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
         protected _applyStates(context: CanvasRenderingContext2D): void;
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[];
         protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
-        protected _parseLineWithTextWrapping(line: string | undefined, context: CanvasRenderingContext2D): object;
+        protected _parseLineEllipsis(line: string | undefined, width: number, context: CanvasRenderingContext2D): object;
+        protected _parseLineWordWrap(line: string | undefined, width: number, context: CanvasRenderingContext2D): object[];
         protected _renderLines(context: CanvasRenderingContext2D): void;
+        /**
+         * Given a width constraint applied on the text block, find the expected height
+         * @returns expected height
+         */
+        computeExpectedHeight(): number;
         dispose(): void;
     }
 }
@@ -1669,10 +1693,15 @@ declare module BABYLON.GUI {
         private _scrollLeft;
         private _textWidth;
         private _clickedCoordinate;
+        private _deadKey;
+        private _addKey;
+        private _currentKey;
         /** Gets or sets a string representing the message displayed on mobile when the control gets the focus */
         promptMessage: string;
         /** Observable raised when the text changes */
         onTextChangedObservable: Observable<InputText>;
+        /** Observable raised just before an entered character is to be added */
+        onBeforeKeyAddObservable: Observable<InputText>;
         /** Observable raised when the control gets the focus */
         onFocusObservable: Observable<InputText>;
         /** Observable raised when the control loses the focus */
@@ -1697,6 +1726,12 @@ declare module BABYLON.GUI {
         placeholderColor: string;
         /** Gets or sets the text displayed when the control is empty */
         placeholderText: string;
+        /** Gets or sets the dead key flag */
+        deadKey: boolean;
+        /** Gets or sets if the current key should be added */
+        addKey: boolean;
+        /** Gets or sets the value of the current key being entered */
+        currentKey: string;
         /** Gets or sets the text displayed in the control */
         text: string;
         /** Gets or sets control width */
@@ -1719,6 +1754,7 @@ declare module BABYLON.GUI {
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
         _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
         _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
+        protected _beforeRenderText(text: string): string;
         dispose(): void;
     }
 }
@@ -1726,6 +1762,16 @@ declare module BABYLON.GUI {
 
 declare module BABYLON.GUI {
     /**
+     * Class used to create a password control
+     */
+    class InputPassword extends InputText {
+        protected _beforeRenderText(text: string): string;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
      * Class used to store key control properties
      */
     class KeyPropertySet {

+ 4 - 3
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -3194,8 +3194,7 @@ var BABYLON;
                         }
                         this._loader._parent._logClose();
                         light._loaded = Promise.all(promises).then(function () {
-                            var size = Math.pow(2, imageData_1.length - 1);
-                            var babylonTexture = new BABYLON.RawCubeTexture(_this._loader._babylonScene, null, size);
+                            var babylonTexture = new BABYLON.RawCubeTexture(_this._loader._babylonScene, null, light.specularImageSize);
                             light._babylonTexture = babylonTexture;
                             if (light.intensity != undefined) {
                                 babylonTexture.level = light.intensity;
@@ -3212,7 +3211,9 @@ var BABYLON;
                             sphericalHarmonics.scale(light.intensity);
                             sphericalHarmonics.convertIrradianceToLambertianRadiance();
                             var sphericalPolynomial = BABYLON.SphericalPolynomial.FromHarmonics(sphericalHarmonics);
-                            return babylonTexture.updateRGBDAsync(imageData_1, sphericalPolynomial);
+                            // Compute the lod generation scale to fit exactly to the number of levels available.
+                            var lodGenerationScale = (imageData_1.length - 1) / BABYLON.Scalar.Log2(light.specularImageSize);
+                            return babylonTexture.updateRGBDAsync(imageData_1, sphericalPolynomial, lodGenerationScale);
                         });
                     }
                     return light._loaded.then(function () {

File diff suppressed because it is too large
+ 3 - 3
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 4 - 3
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -5391,8 +5391,7 @@ var BABYLON;
                         }
                         this._loader._parent._logClose();
                         light._loaded = Promise.all(promises).then(function () {
-                            var size = Math.pow(2, imageData_1.length - 1);
-                            var babylonTexture = new BABYLON.RawCubeTexture(_this._loader._babylonScene, null, size);
+                            var babylonTexture = new BABYLON.RawCubeTexture(_this._loader._babylonScene, null, light.specularImageSize);
                             light._babylonTexture = babylonTexture;
                             if (light.intensity != undefined) {
                                 babylonTexture.level = light.intensity;
@@ -5409,7 +5408,9 @@ var BABYLON;
                             sphericalHarmonics.scale(light.intensity);
                             sphericalHarmonics.convertIrradianceToLambertianRadiance();
                             var sphericalPolynomial = BABYLON.SphericalPolynomial.FromHarmonics(sphericalHarmonics);
-                            return babylonTexture.updateRGBDAsync(imageData_1, sphericalPolynomial);
+                            // Compute the lod generation scale to fit exactly to the number of levels available.
+                            var lodGenerationScale = (imageData_1.length - 1) / BABYLON.Scalar.Log2(light.specularImageSize);
+                            return babylonTexture.updateRGBDAsync(imageData_1, sphericalPolynomial, lodGenerationScale);
                         });
                     }
                     return light._loaded.then(function () {

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 4 - 3
dist/preview release/loaders/babylonjs.loaders.js

@@ -6311,8 +6311,7 @@ var BABYLON;
                         }
                         this._loader._parent._logClose();
                         light._loaded = Promise.all(promises).then(function () {
-                            var size = Math.pow(2, imageData_1.length - 1);
-                            var babylonTexture = new BABYLON.RawCubeTexture(_this._loader._babylonScene, null, size);
+                            var babylonTexture = new BABYLON.RawCubeTexture(_this._loader._babylonScene, null, light.specularImageSize);
                             light._babylonTexture = babylonTexture;
                             if (light.intensity != undefined) {
                                 babylonTexture.level = light.intensity;
@@ -6329,7 +6328,9 @@ var BABYLON;
                             sphericalHarmonics.scale(light.intensity);
                             sphericalHarmonics.convertIrradianceToLambertianRadiance();
                             var sphericalPolynomial = BABYLON.SphericalPolynomial.FromHarmonics(sphericalHarmonics);
-                            return babylonTexture.updateRGBDAsync(imageData_1, sphericalPolynomial);
+                            // Compute the lod generation scale to fit exactly to the number of levels available.
+                            var lodGenerationScale = (imageData_1.length - 1) / BABYLON.Scalar.Log2(light.specularImageSize);
+                            return babylonTexture.updateRGBDAsync(imageData_1, sphericalPolynomial, lodGenerationScale);
                         });
                     }
                     return light._loaded.then(function () {

File diff suppressed because it is too large
+ 3 - 3
dist/preview release/loaders/babylonjs.loaders.min.js


+ 1 - 0
dist/preview release/serializers/babylon.glTF2Serializer.d.ts

@@ -287,6 +287,7 @@ declare module BABYLON.GLTF2 {
          * @param binaryWriter Buffer to write binary data to
          */
         private createSceneAsync(babylonScene, binaryWriter);
+        private getRootNodes(babylonScene, nodes, shouldExportTransformNode);
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene

+ 41 - 8
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -668,7 +668,9 @@ var BABYLON;
                     var glbFile = new Blob(glbData, { type: 'application/octet-stream' });
                     var container = new BABYLON.GLTFData();
                     container.glTFFiles[glbFileName] = glbFile;
-                    _this._localEngine.dispose();
+                    if (_this._localEngine != null) {
+                        _this._localEngine.dispose();
+                    }
                     return container;
                 });
             };
@@ -1016,13 +1018,16 @@ var BABYLON;
                             }
                             directDescendents = babylonTransformNode.getDescendants(true);
                             if (!glTFNode.children && directDescendents && directDescendents.length) {
-                                glTFNode.children = [];
+                                var children = [];
                                 for (var _a = 0, directDescendents_1 = directDescendents; _a < directDescendents_1.length; _a++) {
                                     var descendent = directDescendents_1[_a];
                                     if (_this._nodeMap[descendent.uniqueId] != null) {
-                                        glTFNode.children.push(_this._nodeMap[descendent.uniqueId]);
+                                        children.push(_this._nodeMap[descendent.uniqueId]);
                                     }
                                 }
+                                if (children.length) {
+                                    glTFNode.children = children;
+                                }
                             }
                         }
                     }
@@ -1032,6 +1037,18 @@ var BABYLON;
                     }
                 });
             };
+            _Exporter.prototype.getRootNodes = function (babylonScene, nodes, shouldExportTransformNode) {
+                var rootNodes = [];
+                for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
+                    var babylonTransformNode = nodes_2[_i];
+                    if (shouldExportTransformNode(babylonTransformNode)) {
+                        if (babylonTransformNode.parent == null) {
+                            rootNodes.push(babylonTransformNode);
+                        }
+                    }
+                }
+                return rootNodes;
+            };
             /**
              * Creates a mapping of Node unique id to node index and handles animations
              * @param babylonScene Babylon Scene
@@ -1051,13 +1068,29 @@ var BABYLON;
                 };
                 var idleGLTFAnimations = [];
                 var node;
-                for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
-                    var babylonTransformNode = nodes_2[_i];
+                var negScaleRootNode = null;
+                var rootNodes = this.getRootNodes(babylonScene, nodes, shouldExportTransformNode);
+                if (rootNodes.length === 1) {
+                    var node_1 = rootNodes[0];
+                    if (node_1.scaling.equalsToFloats(1, 1, -1)) {
+                        this._convertToRightHandedSystem = !this._convertToRightHandedSystem;
+                        negScaleRootNode = node_1;
+                    }
+                }
+                for (var _i = 0, nodes_3 = nodes; _i < nodes_3.length; _i++) {
+                    var babylonTransformNode = nodes_3[_i];
                     if (shouldExportTransformNode(babylonTransformNode)) {
                         node = this.createNode(babylonTransformNode, binaryWriter);
-                        this._nodes.push(node);
-                        nodeIndex = this._nodes.length - 1;
-                        nodeMap[babylonTransformNode.uniqueId] = nodeIndex;
+                        if (negScaleRootNode && babylonTransformNode === negScaleRootNode) {
+                            node.scale = [1, 1, 1];
+                            node.rotation = [0, 0, 0, 1];
+                        }
+                        var directDescendents = babylonTransformNode.getDescendants(true, function (node) { return (node instanceof BABYLON.TransformNode); });
+                        if (directDescendents.length || node.mesh != null) {
+                            this._nodes.push(node);
+                            nodeIndex = this._nodes.length - 1;
+                            nodeMap[babylonTransformNode.uniqueId] = nodeIndex;
+                        }
                         if (!babylonScene.animationGroups.length && babylonTransformNode.animations.length) {
                             GLTF2._GLTFAnimation._CreateNodeAnimationFromTransformNodeAnimations(babylonTransformNode, runtimeGLTFAnimation, idleGLTFAnimations, nodeMap, this._nodes, binaryWriter, this._bufferViews, this._accessors, this._convertToRightHandedSystem, this._animationSampleRate);
                         }

File diff suppressed because it is too large
+ 2 - 2
dist/preview release/serializers/babylon.glTF2Serializer.min.js


+ 20 - 9
dist/preview release/serializers/babylon.objSerializer.js

@@ -4,7 +4,7 @@ var BABYLON;
     var OBJExport = /** @class */ (function () {
         function OBJExport() {
         }
-        //Exports the geometrys of a Mesh array in .OBJ file format (text)
+        //Exports the geometry of a Mesh array in .OBJ file format (text)
         OBJExport.OBJ = function (mesh, materials, matlibname, globalposition) {
             var output = [];
             var v = 1;
@@ -34,6 +34,7 @@ var BABYLON;
                 }
                 var g = mesh[j].geometry;
                 if (!g) {
+                    BABYLON.Tools.Warn("No geometry is present on the mesh");
                     continue;
                 }
                 var trunkVerts = g.getVerticesData('position');
@@ -41,23 +42,33 @@ var BABYLON;
                 var trunkUV = g.getVerticesData('uv');
                 var trunkFaces = g.getIndices();
                 var curV = 0;
-                if (!trunkVerts || !trunkNormals || !trunkUV || !trunkFaces) {
+                if (!trunkVerts || !trunkFaces) {
+                    BABYLON.Tools.Warn("There are no position vertices or indices on the mesh!");
                     continue;
                 }
                 for (var i = 0; i < trunkVerts.length; i += 3) {
                     output.push("v " + trunkVerts[i] + " " + trunkVerts[i + 1] + " " + trunkVerts[i + 2]);
                     curV++;
                 }
-                for (i = 0; i < trunkNormals.length; i += 3) {
-                    output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]);
+                if (trunkNormals != null) {
+                    for (i = 0; i < trunkNormals.length; i += 3) {
+                        output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]);
+                    }
                 }
-                for (i = 0; i < trunkUV.length; i += 2) {
-                    output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]);
+                if (trunkUV != null) {
+                    for (i = 0; i < trunkUV.length; i += 2) {
+                        output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]);
+                    }
                 }
                 for (i = 0; i < trunkFaces.length; i += 3) {
-                    output.push("f " + (trunkFaces[i + 2] + v) + "/" + (trunkFaces[i + 2] + v) + "/" + (trunkFaces[i + 2] + v) +
-                        " " + (trunkFaces[i + 1] + v) + "/" + (trunkFaces[i + 1] + v) + "/" + (trunkFaces[i + 1] + v) +
-                        " " + (trunkFaces[i] + v) + "/" + (trunkFaces[i] + v) + "/" + (trunkFaces[i] + v));
+                    var indices = [String(trunkFaces[i + 2] + v), String(trunkFaces[i + 1] + v), String(trunkFaces[i] + v)];
+                    var blanks = ["", "", ""];
+                    var facePositions = indices;
+                    var faceUVs = trunkUV != null ? indices : blanks;
+                    var faceNormals = trunkNormals != null ? indices : blanks;
+                    output.push("f " + facePositions[0] + "/" + faceUVs[0] + "/" + faceNormals[0] +
+                        " " + facePositions[1] + "/" + faceUVs[1] + "/" + faceNormals[1] +
+                        " " + facePositions[2] + "/" + faceUVs[2] + "/" + faceNormals[2]);
                 }
                 //back de previous matrix, to not change the original mesh in the scene
                 if (globalposition && lastMatrix) {

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/serializers/babylon.objSerializer.min.js


+ 1 - 0
dist/preview release/serializers/babylonjs.serializers.d.ts

@@ -295,6 +295,7 @@ declare module BABYLON.GLTF2 {
          * @param binaryWriter Buffer to write binary data to
          */
         private createSceneAsync(babylonScene, binaryWriter);
+        private getRootNodes(babylonScene, nodes, shouldExportTransformNode);
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene

+ 61 - 17
dist/preview release/serializers/babylonjs.serializers.js

@@ -29,7 +29,7 @@ var BABYLON;
     var OBJExport = /** @class */ (function () {
         function OBJExport() {
         }
-        //Exports the geometrys of a Mesh array in .OBJ file format (text)
+        //Exports the geometry of a Mesh array in .OBJ file format (text)
         OBJExport.OBJ = function (mesh, materials, matlibname, globalposition) {
             var output = [];
             var v = 1;
@@ -59,6 +59,7 @@ var BABYLON;
                 }
                 var g = mesh[j].geometry;
                 if (!g) {
+                    BABYLON.Tools.Warn("No geometry is present on the mesh");
                     continue;
                 }
                 var trunkVerts = g.getVerticesData('position');
@@ -66,23 +67,33 @@ var BABYLON;
                 var trunkUV = g.getVerticesData('uv');
                 var trunkFaces = g.getIndices();
                 var curV = 0;
-                if (!trunkVerts || !trunkNormals || !trunkUV || !trunkFaces) {
+                if (!trunkVerts || !trunkFaces) {
+                    BABYLON.Tools.Warn("There are no position vertices or indices on the mesh!");
                     continue;
                 }
                 for (var i = 0; i < trunkVerts.length; i += 3) {
                     output.push("v " + trunkVerts[i] + " " + trunkVerts[i + 1] + " " + trunkVerts[i + 2]);
                     curV++;
                 }
-                for (i = 0; i < trunkNormals.length; i += 3) {
-                    output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]);
+                if (trunkNormals != null) {
+                    for (i = 0; i < trunkNormals.length; i += 3) {
+                        output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]);
+                    }
                 }
-                for (i = 0; i < trunkUV.length; i += 2) {
-                    output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]);
+                if (trunkUV != null) {
+                    for (i = 0; i < trunkUV.length; i += 2) {
+                        output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]);
+                    }
                 }
                 for (i = 0; i < trunkFaces.length; i += 3) {
-                    output.push("f " + (trunkFaces[i + 2] + v) + "/" + (trunkFaces[i + 2] + v) + "/" + (trunkFaces[i + 2] + v) +
-                        " " + (trunkFaces[i + 1] + v) + "/" + (trunkFaces[i + 1] + v) + "/" + (trunkFaces[i + 1] + v) +
-                        " " + (trunkFaces[i] + v) + "/" + (trunkFaces[i] + v) + "/" + (trunkFaces[i] + v));
+                    var indices = [String(trunkFaces[i + 2] + v), String(trunkFaces[i + 1] + v), String(trunkFaces[i] + v)];
+                    var blanks = ["", "", ""];
+                    var facePositions = indices;
+                    var faceUVs = trunkUV != null ? indices : blanks;
+                    var faceNormals = trunkNormals != null ? indices : blanks;
+                    output.push("f " + facePositions[0] + "/" + faceUVs[0] + "/" + faceNormals[0] +
+                        " " + facePositions[1] + "/" + faceUVs[1] + "/" + faceNormals[1] +
+                        " " + facePositions[2] + "/" + faceUVs[2] + "/" + faceNormals[2]);
                 }
                 //back de previous matrix, to not change the original mesh in the scene
                 if (globalposition && lastMatrix) {
@@ -818,7 +829,9 @@ var BABYLON;
                     var glbFile = new Blob(glbData, { type: 'application/octet-stream' });
                     var container = new BABYLON.GLTFData();
                     container.glTFFiles[glbFileName] = glbFile;
-                    _this._localEngine.dispose();
+                    if (_this._localEngine != null) {
+                        _this._localEngine.dispose();
+                    }
                     return container;
                 });
             };
@@ -1166,13 +1179,16 @@ var BABYLON;
                             }
                             directDescendents = babylonTransformNode.getDescendants(true);
                             if (!glTFNode.children && directDescendents && directDescendents.length) {
-                                glTFNode.children = [];
+                                var children = [];
                                 for (var _a = 0, directDescendents_1 = directDescendents; _a < directDescendents_1.length; _a++) {
                                     var descendent = directDescendents_1[_a];
                                     if (_this._nodeMap[descendent.uniqueId] != null) {
-                                        glTFNode.children.push(_this._nodeMap[descendent.uniqueId]);
+                                        children.push(_this._nodeMap[descendent.uniqueId]);
                                     }
                                 }
+                                if (children.length) {
+                                    glTFNode.children = children;
+                                }
                             }
                         }
                     }
@@ -1182,6 +1198,18 @@ var BABYLON;
                     }
                 });
             };
+            _Exporter.prototype.getRootNodes = function (babylonScene, nodes, shouldExportTransformNode) {
+                var rootNodes = [];
+                for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
+                    var babylonTransformNode = nodes_2[_i];
+                    if (shouldExportTransformNode(babylonTransformNode)) {
+                        if (babylonTransformNode.parent == null) {
+                            rootNodes.push(babylonTransformNode);
+                        }
+                    }
+                }
+                return rootNodes;
+            };
             /**
              * Creates a mapping of Node unique id to node index and handles animations
              * @param babylonScene Babylon Scene
@@ -1201,13 +1229,29 @@ var BABYLON;
                 };
                 var idleGLTFAnimations = [];
                 var node;
-                for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
-                    var babylonTransformNode = nodes_2[_i];
+                var negScaleRootNode = null;
+                var rootNodes = this.getRootNodes(babylonScene, nodes, shouldExportTransformNode);
+                if (rootNodes.length === 1) {
+                    var node_1 = rootNodes[0];
+                    if (node_1.scaling.equalsToFloats(1, 1, -1)) {
+                        this._convertToRightHandedSystem = !this._convertToRightHandedSystem;
+                        negScaleRootNode = node_1;
+                    }
+                }
+                for (var _i = 0, nodes_3 = nodes; _i < nodes_3.length; _i++) {
+                    var babylonTransformNode = nodes_3[_i];
                     if (shouldExportTransformNode(babylonTransformNode)) {
                         node = this.createNode(babylonTransformNode, binaryWriter);
-                        this._nodes.push(node);
-                        nodeIndex = this._nodes.length - 1;
-                        nodeMap[babylonTransformNode.uniqueId] = nodeIndex;
+                        if (negScaleRootNode && babylonTransformNode === negScaleRootNode) {
+                            node.scale = [1, 1, 1];
+                            node.rotation = [0, 0, 0, 1];
+                        }
+                        var directDescendents = babylonTransformNode.getDescendants(true, function (node) { return (node instanceof BABYLON.TransformNode); });
+                        if (directDescendents.length || node.mesh != null) {
+                            this._nodes.push(node);
+                            nodeIndex = this._nodes.length - 1;
+                            nodeMap[babylonTransformNode.uniqueId] = nodeIndex;
+                        }
                         if (!babylonScene.animationGroups.length && babylonTransformNode.animations.length) {
                             GLTF2._GLTFAnimation._CreateNodeAnimationFromTransformNodeAnimations(babylonTransformNode, runtimeGLTFAnimation, idleGLTFAnimations, nodeMap, this._nodes, binaryWriter, this._bufferViews, this._accessors, this._convertToRightHandedSystem, this._animationSampleRate);
                         }

File diff suppressed because it is too large
+ 2 - 2
dist/preview release/serializers/babylonjs.serializers.min.js


+ 1 - 0
dist/preview release/serializers/babylonjs.serializers.module.d.ts

@@ -302,6 +302,7 @@ declare module BABYLON.GLTF2 {
          * @param binaryWriter Buffer to write binary data to
          */
         private createSceneAsync(babylonScene, binaryWriter);
+        private getRootNodes(babylonScene, nodes, shouldExportTransformNode);
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene

+ 77 - 2
dist/preview release/typedocValidationBaseline.json

@@ -1,7 +1,7 @@
 {
-  "errors": 4225,
+  "errors": 4240,
   "babylon.typedoc.json": {
-    "errors": 4225,
+    "errors": 4240,
     "AbstractMesh": {
       "Property": {
         "showBoundingBox": {
@@ -5185,10 +5185,85 @@
     },
     "Engine": {
       "Method": {
+        "beginOcclusionQuery": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "beginTransformFeedback": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "bindTransformFeedback": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "bindTransformFeedbackBuffer": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
         "createEffectForParticles": {
           "Naming": {
             "NotPascalCase": true
           }
+        },
+        "createQuery": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "createTransformFeedback": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "deleteQuery": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "deleteTransformFeedback": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "endOcclusionQuery": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "endTimeQuery": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "endTransformFeedback": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "getQueryResult": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "isQueryResultAvailable": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "setTranformFeedbackVaryings": {
+          "Naming": {
+            "NotPascalCase": true
+          }
+        },
+        "startTimeQuery": {
+          "Naming": {
+            "NotPascalCase": true
+          }
         }
       }
     },

+ 4 - 2
dist/preview release/viewer/babylon.viewer.d.ts

@@ -962,8 +962,10 @@ declare module BabylonViewer {
         readonly templateName: string;
         readonly eventsToAttach: Array<string>;
         protected _prepend: boolean;
-        protected abstract _buttonClass: string;
-        protected abstract _htmlTemplate: string;
+        protected _buttonName: string;
+        protected _buttonClass: string;
+        protected _htmlTemplate: string;
+        constructor(buttonName: string, buttonClass?: string, htmlTemplate?: string);
         interactionPredicate(event: EventCallback): boolean;
         abstract onEvent(event: EventCallback): void;
         addHTMLTemplate(template: Template): void;

File diff suppressed because it is too large
+ 71 - 71
dist/preview release/viewer/babylon.viewer.js


File diff suppressed because it is too large
+ 29378 - 28918
dist/preview release/viewer/babylon.viewer.max.js


+ 4 - 2
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -962,8 +962,10 @@ declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
         readonly templateName: string;
         readonly eventsToAttach: Array<string>;
         protected _prepend: boolean;
-        protected abstract _buttonClass: string;
-        protected abstract _htmlTemplate: string;
+        protected _buttonName: string;
+        protected _buttonClass: string;
+        protected _htmlTemplate: string;
+        constructor(buttonName: string, buttonClass?: string, htmlTemplate?: string);
         interactionPredicate(event: EventCallback): boolean;
         abstract onEvent(event: EventCallback): void;
         addHTMLTemplate(template: Template): void;

+ 10 - 0
dist/preview release/what's new.md

@@ -25,14 +25,19 @@
   - Added support for life time gradients. [Doc](https://doc.babylonjs.com/babylon101/particles#lifetime)
   - Added support for angular speed gradients. [Doc](https://doc.babylonjs.com/babylon101/particles#rotation)
 - Added SceneComponent to help decoupling Scene from its components ([sebavan](http://www.github.com/sebavan))
+- Playground can now be used with TypeScript directly!. [Demo](https://www.babylonjs-playground.com/ts.html) ([Deltakosh](https://github.com/deltakosh), [NasimiAsl](https://github.com/NasimiAsl))
+- New GUI control: [InputPassword](https://doc.babylonjs.com/how_to/gui#inputpassword) ([theom](https://github.com/theom))
+- Added dead key support and before key add observable to InputText. [Doc](https://doc.babylonjs.com/how_to/gui#using-onbeforekeyaddobservable-for-extended-keyboard-layouts-and-input-masks)([theom](https://github.com/theom))
 
 ## Updates
 
 - All NPM packages have `latest`and `preview` streams [#3055](https://github.com/BabylonJS/Babylon.js/issues/3055) ([RaananW](https://github.com/RaananW))
 - Added New Tools Tab in the inspector (env texture and screenshot tools so far) ([sebavan](http://www.github.com/sebavan))
+- Added `TextBlock.computeExpectedHeight`, added `TextWrapping.Ellipsis` as `TextBlock.wordWrapping` possible value ([adrientetar](https://github.com/adrientetar))
 
 ### Core Engine
 
+- Added new `BoundingInfo.scale()` function to let users control the size of the bounding info ([Deltakosh](https://github.com/deltakosh))
 - Added new `Animatable.waitAsync` function to use Promises with animations. Demo [Here](https://www.babylonjs-playground.com/#HZBCXR) ([Deltakosh](https://github.com/deltakosh))
 - Added the choice of [forming a closed loop](http://doc.babylonjs.com/how_to/how_to_use_curve3#catmull-rom-spline) to the catmull-rom-spline curve3 ([johnk](https://github.com/babylonjsguide))
 - Added support for specifying the center of rotation to textures ([bghgary](http://www.github.com/bghgary))
@@ -65,6 +70,9 @@
 - Added `RawCubeTexture` class with RGBD and mipmap support ([bghgary](http://www.github.com/bghgary))
 - Added effect layer per rendering group addressing [Issue 4463](https://github.com/BabylonJS/Babylon.js/issues/4463) ([sebavan](http://www.github.com/sebavan))
 - Added predicate function `targetMask` argument to `scene.beginWeightedAnimation`, `scene.beginAnimation`, `scene.stopAnimation`, and `animatable.stop` to allow for selective application of animations.  ([fmmoret](http://github.com/fmmoret))
+- Oculus GO and GearVR 3dof controllers will now rotate with the user's head if they turn around in their room ([TrevorDev](https://github.com/TrevorDev))
+- Added onPoseUpdatedFromDeviceObservable to webVRCamera to detect when the camera's pose has been updated ([TrevorDev](https://github.com/TrevorDev))
+- Gizmo manager's internal gizmos are now public ([TrevorDev](https://github.com/TrevorDev))
 
 ### glTF Loader
 
@@ -120,6 +128,8 @@
 - Fixed support for R and RG texture formats ([sebavan](http://www.github.com/sebavan))
 - Fixed `updatable` parameter setting in the SPS ([jerome](https://github.com/jbousquie))
 - Angular and linear velocity were using the wrong method to copy values to the physics engine ([RaananW](https://github.com/RaananW))
+- Fixed env texture generation in Byte Mode ([sebavan](http://www.github.com/sebavan))
+- Oimo.js now receives quaternion and not euler when a body is being constructed ([RaananW](https://github.com/RaananW))
 
 ### Viewer
 

+ 16 - 0
gui/src/2D/controls/inputPassword.ts

@@ -0,0 +1,16 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    /**
+     * Class used to create a password control
+     */
+    export class InputPassword extends InputText {
+        protected _beforeRenderText(text: string): string {
+            let txt = "";
+            for (let i = 0; i < text.length; i++) {
+                txt += "\u2022";
+            }
+            return txt;
+        }
+    }
+}

+ 60 - 16
gui/src/2D/controls/inputText.ts

@@ -24,12 +24,17 @@ export class InputText extends Control implements IFocusableControl {
     private _scrollLeft: Nullable<number>;
     private _textWidth: number;
     private _clickedCoordinate: Nullable<number>;
+    private _deadKey = false;
+    private _addKey = true;
+    private _currentKey = "";
 
     /** Gets or sets a string representing the message displayed on mobile when the control gets the focus */
     public promptMessage = "Please enter text:";
 
     /** Observable raised when the text changes */
     public onTextChangedObservable = new Observable<InputText>();
+    /** Observable raised just before an entered character is to be added */
+    public onBeforeKeyAddObservable = new Observable<InputText>();
     /** Observable raised when the control gets the focus */
     public onFocusObservable = new Observable<InputText>();
     /** Observable raised when the control loses the focus */
@@ -158,6 +163,33 @@ export class InputText extends Control implements IFocusableControl {
         this._markAsDirty();
     }
 
+    /** Gets or sets the dead key flag */
+    public get deadKey(): boolean {
+        return this._deadKey;
+    }
+
+    public set deadKey(flag: boolean) {
+        this._deadKey = flag;
+    }
+
+    /** Gets or sets if the current key should be added */
+    public get addKey(): boolean {
+        return this._addKey;
+    }
+
+    public set addKey(flag: boolean) {
+        this._addKey = flag;
+    }
+
+    /** Gets or sets the value of the current key being entered */
+    public get currentKey(): string {
+        return this._currentKey;
+    }
+
+    public set currentKey(key: string) {
+        this._currentKey = key;
+    }
+
     /** Gets or sets the text displayed in the control */
     public get text(): string {
         return this._text;
@@ -242,7 +274,7 @@ export class InputText extends Control implements IFocusableControl {
         // Specific cases
         switch (keyCode) {
             case 32: //SPACE
-                key = " "; //ie11 key for space is "Spacebar" 
+                key = " "; //ie11 key for space is "Spacebar"
                 break;
             case 8: // BACKSPACE
                 if (this._text && this._text.length > 0) {
@@ -292,23 +324,31 @@ export class InputText extends Control implements IFocusableControl {
                 this._blinkIsEven = false;
                 this._markAsDirty();
                 return;
+            case 222: // Dead
+                this.deadKey = true;
+                return;
         }
 
         // Printable characters
-        if (
-            (keyCode === -1) ||                     // Direct access
-            (keyCode === 32) ||                     // Space
-            (keyCode > 47 && keyCode < 58) ||       // Numbers
-            (keyCode > 64 && keyCode < 91) ||       // Letters
-            (keyCode > 185 && keyCode < 193) ||     // Special characters
-            (keyCode > 218 && keyCode < 223) ||    // Special characters
-            (keyCode > 95 && keyCode < 112)) {      // Numpad
-            if (this._cursorOffset === 0) {
-                this.text += key;
-            } else {
-                let insertPosition = this._text.length - this._cursorOffset;
-
-                this.text = this._text.slice(0, insertPosition) + key + this._text.slice(insertPosition);
+        if (key &&
+            ((keyCode === -1) ||                     // Direct access
+                (keyCode === 32) ||                     // Space
+                (keyCode > 47 && keyCode < 58) ||       // Numbers
+                (keyCode > 64 && keyCode < 91) ||       // Letters
+                (keyCode > 185 && keyCode < 193) ||     // Special characters
+                (keyCode > 218 && keyCode < 223) ||     // Special characters
+                (keyCode > 95 && keyCode < 112))) {     // Numpad
+            this._currentKey = key;
+            this.onBeforeKeyAddObservable.notifyObservers(this);
+            key = this._currentKey;
+            if (this._addKey) {
+                if (this._cursorOffset === 0) {
+                    this.text += key;
+                } else {
+                    let insertPosition = this._text.length - this._cursorOffset;
+
+                    this.text = this._text.slice(0, insertPosition) + key + this._text.slice(insertPosition);
+                }
             }
         }
     }
@@ -360,7 +400,7 @@ export class InputText extends Control implements IFocusableControl {
                 context.fillStyle = this.color;
             }
 
-            let text = this._text;
+            let text = this._beforeRenderText(this._text);
 
             if (!this._isFocused && !this._text && this._placeholderText) {
                 text = this._placeholderText;
@@ -484,6 +524,10 @@ export class InputText extends Control implements IFocusableControl {
         super._onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick);
     }
 
+    protected _beforeRenderText(text: string): string {
+        return text;
+    }
+
     public dispose() {
         super.dispose();
 

+ 86 - 20
gui/src/2D/controls/textBlock.ts

@@ -1,14 +1,34 @@
-import { Control } from "./control";
-import { ValueAndUnit } from "../valueAndUnit";
 import { Observable } from "babylonjs";
 import { Measure } from "../measure";
+import { ValueAndUnit } from "../valueAndUnit";
+import { Control } from "./control";
+
+/**
+ * Enum that determines the text-wrapping mode to use.
+ */
+export enum TextWrapping {
+    /**
+     * Clip the text when it's larger than Control.width; this is the default mode.
+     */
+    Clip = 0,
+
+    /**
+     * Wrap the text word-wise, i.e. try to add line-breaks at word boundary to fit within Control.width.
+     */
+    WordWrap = 1,
+
+    /**
+     * Ellipsize the text, i.e. shrink with trailing … when text is larger than Control.width.
+     */
+    Ellipsis,
+}
 
 /**
  * Class used to create text block control
  */
 export class TextBlock extends Control {
     private _text = "";
-    private _textWrapping = false;
+    private _textWrapping = TextWrapping.Clip;
     private _textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
     private _textVerticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
 
@@ -56,18 +76,18 @@ export class TextBlock extends Control {
     /**
      * Gets or sets a boolean indicating if text must be wrapped
      */
-    public get textWrapping(): boolean {
+    public get textWrapping(): TextWrapping | boolean {
         return this._textWrapping;
     }
 
     /**
      * Gets or sets a boolean indicating if text must be wrapped
      */
-    public set textWrapping(value: boolean) {
+    public set textWrapping(value: TextWrapping | boolean) {
         if (this._textWrapping === value) {
             return;
         }
-        this._textWrapping = value;
+        this._textWrapping = +value;
         this._markAsDirty();
     }
 
@@ -92,14 +112,14 @@ export class TextBlock extends Control {
     }
 
     /**
-     * Gets or sets text horizontal alignment (Control.HORIZONTAL_ALIGNMENT_CENTER by default)
+     * Gets or sets text horizontal alignment (BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER by default)
      */
     public get textHorizontalAlignment(): number {
         return this._textHorizontalAlignment;
     }
 
     /**
-     * Gets or sets text horizontal alignment (Control.HORIZONTAL_ALIGNMENT_CENTER by default)
+     * Gets or sets text horizontal alignment (BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER by default)
      */
     public set textHorizontalAlignment(value: number) {
         if (this._textHorizontalAlignment === value) {
@@ -111,14 +131,14 @@ export class TextBlock extends Control {
     }
 
     /**
-     * Gets or sets text vertical alignment (Control.VERTICAL_ALIGNMENT_CENTER by default)
+     * Gets or sets text vertical alignment (BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER by default)
      */
     public get textVerticalAlignment(): number {
         return this._textVerticalAlignment;
     }
 
     /**
-     * Gets or sets text vertical alignment (Control.VERTICAL_ALIGNMENT_CENTER by default)
+     * Gets or sets text vertical alignment (BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER by default)
      */
     public set textVerticalAlignment(value: number) {
         if (this._textVerticalAlignment === value) {
@@ -251,29 +271,54 @@ export class TextBlock extends Control {
     }
 
     protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
-        this._lines = [];
+        this._lines = this._breakLines(this._currentMeasure.width, context);
+        this.onLinesReadyObservable.notifyObservers(this);
+    }
+
+    protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[] {
+        var lines = [];
         var _lines = this.text.split("\n");
 
-        if (this._textWrapping && !this._resizeToFit) {
+        if (this._textWrapping === TextWrapping.Ellipsis && !this._resizeToFit) {
             for (var _line of _lines) {
-                this._lines.push(this._parseLineWithTextWrapping(_line, context));
+                lines.push(this._parseLineEllipsis(_line, refWidth, context));
+            }
+        } else if (this._textWrapping === TextWrapping.WordWrap && !this._resizeToFit) {
+            for (var _line of _lines) {
+                lines.push(...this._parseLineWordWrap(_line, refWidth, context));
             }
         } else {
             for (var _line of _lines) {
-                this._lines.push(this._parseLine(_line, context));
+                lines.push(this._parseLine(_line, context));
             }
         }
 
-        this.onLinesReadyObservable.notifyObservers(this);
+        return lines;
     }
 
     protected _parseLine(line: string = '', context: CanvasRenderingContext2D): object {
         return { text: line, width: context.measureText(line).width };
     }
 
-    protected _parseLineWithTextWrapping(line: string = '', context: CanvasRenderingContext2D): object {
+    protected _parseLineEllipsis(line: string = '', width: number,
+        context: CanvasRenderingContext2D): object {
+        var lineWidth = context.measureText(line).width;
+
+        if (lineWidth > width) {
+            line += '…';
+        }
+        while (line.length > 2 && lineWidth > width) {
+            line = line.slice(0, -2) + '…';
+            lineWidth = context.measureText(line).width;
+        }
+
+        return { text: line, width: lineWidth };
+    }
+
+    protected _parseLineWordWrap(line: string = '', width: number,
+        context: CanvasRenderingContext2D): object[] {
+        var lines = [];
         var words = line.split(' ');
-        var width = this._currentMeasure.width;
         var lineWidth = 0;
 
         for (var n = 0; n < words.length; n++) {
@@ -281,7 +326,7 @@ export class TextBlock extends Control {
             var metrics = context.measureText(testLine);
             var testWidth = metrics.width;
             if (testWidth > width && n > 0) {
-                this._lines.push({ text: line, width: lineWidth });
+                lines.push({ text: line, width: lineWidth });
                 line = words[n];
                 lineWidth = context.measureText(line).width;
             }
@@ -290,8 +335,9 @@ export class TextBlock extends Control {
                 line = testLine;
             }
         }
+        lines.push({ text: line, width: lineWidth });
 
-        return { text: line, width: lineWidth };
+        return lines;
     }
 
     protected _renderLines(context: CanvasRenderingContext2D): void {
@@ -341,9 +387,29 @@ export class TextBlock extends Control {
         }
     }
 
+    /**
+     * Given a width constraint applied on the text block, find the expected height
+     * @returns expected height
+     */
+    public computeExpectedHeight(): number {
+        if (this.text && this.widthInPixels) {
+            const context = document.createElement('canvas').getContext('2d');
+            if (context) {
+                this._applyStates(context);
+                if (!this._fontOffset) {
+                    this._fontOffset = Control._GetFontOffset(context.font);
+                }
+                const lines = this._lines ? this._lines : this._breakLines(
+                    this.widthInPixels - this.paddingLeftInPixels - this.paddingRightInPixels, context);
+                return this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * lines.length;
+            }
+        }
+        return 0;
+    }
+
     dispose(): void {
         super.dispose();
 
         this.onTextChangedObservable.clear();
     }
-}
+}

+ 5 - 6
gui/src/3D/controls/cylinderPanel.ts

@@ -1,5 +1,5 @@
-import { float, Tools, Vector3, Matrix, Tmp } from "babylonjs";
 import { VolumeBasedPanel } from "./volumeBasedPanel";
+import { float, Tools, Vector3, Matrix, Tmp } from "babylonjs";
 import { Control3D } from "./control3D";
 import { Container3D } from "./container3D";
 
@@ -39,16 +39,15 @@ export class CylinderPanel extends VolumeBasedPanel {
 
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
-                mesh.lookAt(new Vector3(-newPos.x, 0, -newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(-newPos.x, newPos.y, -newPos.z));
                 break;
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                mesh.lookAt(new Vector3(newPos.x, 0, newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, newPos.y, 2 * newPos.z));
                 break;
             case Container3D.FACEFORWARD_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, 1));
                 break;
             case Container3D.FACEFORWARDREVERSED_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, -1));
+                mesh.rotate(BABYLON.Axis.Y, Math.PI, BABYLON.Space.LOCAL);
                 break;
         }
     }
@@ -62,4 +61,4 @@ export class CylinderPanel extends VolumeBasedPanel {
 
         return Vector3.TransformNormal(newPos, Tmp.Matrix[0]);
     }
-}
+}

+ 11 - 6
gui/src/3D/controls/planePanel.ts

@@ -1,7 +1,7 @@
-import { VolumeBasedPanel } from "./volumeBasedPanel";
-import { Control3D } from "./control3D";
-import { Vector3 } from "babylonjs";
+import { Tmp, Vector3 } from "babylonjs";
 import { Container3D } from "./container3D";
+import { Control3D } from "./control3D";
+import { VolumeBasedPanel } from "./volumeBasedPanel";
 
 /**
  * Class used to create a container panel deployed on the surface of a plane
@@ -15,17 +15,22 @@ export class PlanePanel extends VolumeBasedPanel {
         }
 
         control.position = nodePosition.clone();
+        let target = Tmp.Vector3[0];
+
+        target.copyFrom(nodePosition);
 
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
             case Container3D.FACEFORWARD_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, -1));
+                target.addInPlace(new BABYLON.Vector3(0, 0, -1));
+                mesh.lookAt(target);
                 break;
             case Container3D.FACEFORWARDREVERSED_ORIENTATION:
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, 1));
+                target.addInPlace(new BABYLON.Vector3(0, 0, 1));
+                mesh.lookAt(target);
                 break;
         }
 
     }
-}
+}

+ 7 - 8
gui/src/3D/controls/spherePanel.ts

@@ -1,5 +1,5 @@
 import { VolumeBasedPanel } from "./volumeBasedPanel";
-import { Tools, Vector3, Matrix, Tmp } from "babylonjs";
+import { float, Tools, Vector3, Matrix, Tmp } from "babylonjs";
 import { Control3D } from "./control3D";
 import { Container3D } from "./container3D";
 
@@ -12,11 +12,11 @@ export class SpherePanel extends VolumeBasedPanel {
     /**
      * Gets or sets the radius of the sphere where to project controls (5 by default)
      */
-    public get radius(): number {
+    public get radius(): float {
         return this._radius;
     }
 
-    public set radius(value: number) {
+    public set radius(value: float) {
         if (this._radius === value) {
             return;
         }
@@ -40,16 +40,15 @@ export class SpherePanel extends VolumeBasedPanel {
 
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
-                mesh.lookAt(new Vector3(-newPos.x, -newPos.y, -newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(-newPos.x, -newPos.y, -newPos.z));
                 break;
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                mesh.lookAt(new Vector3(newPos.x, newPos.y, newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, 2 * newPos.y, 2 * newPos.z));
                 break;
             case Container3D.FACEFORWARD_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, 1));
                 break;
             case Container3D.FACEFORWARDREVERSED_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, -1));
+                mesh.rotate(BABYLON.Axis.Y, Math.PI, BABYLON.Space.LOCAL);
                 break;
         }
     }
@@ -64,4 +63,4 @@ export class SpherePanel extends VolumeBasedPanel {
 
         return Vector3.TransformNormal(newPos, Tmp.Matrix[0]);
     }
-}
+}

+ 5 - 3
loaders/src/glTF/2.0/Extensions/EXT_lights_imageBased.ts

@@ -10,6 +10,7 @@ module BABYLON.GLTF2.Extensions {
     interface ILight extends IChildRootProperty {
         intensity: number;
         rotation: number[];
+        specularImageSize: number;
         specularImages: number[][];
         irradianceCoefficients: number[][];
 
@@ -73,8 +74,7 @@ module BABYLON.GLTF2.Extensions {
                 this._loader._parent._logClose();
 
                 light._loaded = Promise.all(promises).then(() => {
-                    const size = Math.pow(2, imageData.length - 1);
-                    const babylonTexture = new RawCubeTexture(this._loader._babylonScene, null, size);
+                    const babylonTexture = new RawCubeTexture(this._loader._babylonScene, null, light.specularImageSize);
                     light._babylonTexture = babylonTexture;
 
                     if (light.intensity != undefined) {
@@ -98,7 +98,9 @@ module BABYLON.GLTF2.Extensions {
                     sphericalHarmonics.convertIrradianceToLambertianRadiance();
                     const sphericalPolynomial = SphericalPolynomial.FromHarmonics(sphericalHarmonics);
 
-                    return babylonTexture.updateRGBDAsync(imageData, sphericalPolynomial);
+                    // Compute the lod generation scale to fit exactly to the number of levels available.
+                    const lodGenerationScale = (imageData.length - 1) / Scalar.Log2(light.specularImageSize);
+                    return babylonTexture.updateRGBDAsync(imageData, sphericalPolynomial, lodGenerationScale);
                 });
             }
 

+ 31 - 18
serializers/src/OBJ/babylon.objSerializer.ts

@@ -2,17 +2,17 @@
 
 module BABYLON {
     export class OBJExport {
-        //Exports the geometrys of a Mesh array in .OBJ file format (text)
+        //Exports the geometry of a Mesh array in .OBJ file format (text)
         public static OBJ(mesh: Mesh[], materials?: boolean, matlibname?: string, globalposition?: boolean): string {
-            var output = [];
-            var v = 1;
+            const output:string[] = [];
+            let v = 1;
             if (materials) {
                 if (!matlibname) {
                     matlibname = 'mat';
                 }
                 output.push("mtllib " + matlibname + ".mtl");
             }
-            for (var j = 0; j < mesh.length; j++) {
+            for (let j = 0; j < mesh.length; j++) {
                 output.push("g object" + j);
                 output.push("o object_" + j);
 
@@ -33,19 +33,21 @@ module BABYLON {
                         output.push("usemtl " + mat.id);
                     }
                 }
-                var g = mesh[j].geometry;
+                const g: Nullable<Geometry> = mesh[j].geometry;
 
                 if (!g) {
+                    Tools.Warn("No geometry is present on the mesh");
                     continue;
                 }
 
-                var trunkVerts = g.getVerticesData('position');
-                var trunkNormals = g.getVerticesData('normal');
-                var trunkUV = g.getVerticesData('uv');
-                var trunkFaces = g.getIndices();
+                const trunkVerts = g.getVerticesData('position');
+                const trunkNormals = g.getVerticesData('normal');
+                const trunkUV = g.getVerticesData('uv');
+                const trunkFaces = g.getIndices();
                 var curV = 0;
 
-                if (!trunkVerts || !trunkNormals || !trunkUV || !trunkFaces) {
+                if (!trunkVerts || !trunkFaces) {
+                    Tools.Warn("There are no position vertices or indices on the mesh!");
                     continue;
                 }
 
@@ -54,19 +56,30 @@ module BABYLON {
                     curV++;
                 }
 
-                for (i = 0; i < trunkNormals.length; i += 3) {
-                    output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]);
+                if (trunkNormals != null) {
+                    for (i = 0; i < trunkNormals.length; i += 3) {
+                        output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]);
+                    }
                 }
+                if (trunkUV != null) {
 
-                for (i = 0; i < trunkUV.length; i += 2) {
-                    output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]);
+                    for (i = 0; i < trunkUV.length; i += 2) {
+                        output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]);
+                    }
                 }
 
                 for (i = 0; i < trunkFaces.length; i += 3) {
+                    const indices = [String(trunkFaces[i + 2] + v), String(trunkFaces[i + 1] + v), String(trunkFaces[i] + v)];
+                    const blanks: string[] = ["","",""];
+
+                    const facePositions = indices;
+                    const faceUVs = trunkUV != null ? indices : blanks;
+                    const faceNormals = trunkNormals != null ? indices : blanks;
+                         
                     output.push(
-                        "f " + (trunkFaces[i + 2] + v) + "/" + (trunkFaces[i + 2] + v) + "/" + (trunkFaces[i + 2] + v) +
-                        " " + (trunkFaces[i + 1] + v) + "/" + (trunkFaces[i + 1] + v) + "/" + (trunkFaces[i + 1] + v) +
-                        " " + (trunkFaces[i] + v) + "/" + (trunkFaces[i] + v) + "/" + (trunkFaces[i] + v)
+                        "f " + facePositions[0] + "/" + faceUVs[0] + "/" + faceNormals[0] +
+                        " " + facePositions[1] + "/" + faceUVs[1] + "/" + faceNormals[1] +
+                        " " + facePositions[2] + "/" + faceUVs[2] + "/" + faceNormals[2]
                     );
                 }
                 //back de previous matrix, to not change the original mesh in the scene
@@ -75,7 +88,7 @@ module BABYLON {
                 }
                 v += curV;
             }
-            var text = output.join("\n");
+            const text: string = output.join("\n");
             return (text);
         }
         //Exports the material(s) of a mesh in .MTL file format (text)

+ 44 - 7
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -768,7 +768,11 @@ module BABYLON.GLTF2 {
                 const container = new GLTFData();
                 container.glTFFiles[glbFileName] = glbFile;
 
-                this._localEngine.dispose();
+                if (this._localEngine != null) {
+                    this._localEngine.dispose();
+                }
+
+                
 
                 return container;
             });
@@ -1149,12 +1153,15 @@ module BABYLON.GLTF2 {
 
                         directDescendents = babylonTransformNode.getDescendants(true);
                         if (!glTFNode.children && directDescendents && directDescendents.length) {
-                            glTFNode.children = [];
+                            const children: number[] = [];
                             for (let descendent of directDescendents) {
                                 if (this._nodeMap[descendent.uniqueId] != null) {
-                                    glTFNode.children.push(this._nodeMap[descendent.uniqueId]);
+                                    children.push(this._nodeMap[descendent.uniqueId]);
                                 }
                             }
+                            if (children.length) {
+                                glTFNode.children = children;
+                            }
                         }
                     }
                 };
@@ -1164,6 +1171,18 @@ module BABYLON.GLTF2 {
             });
         }
 
+        private getRootNodes(babylonScene: Scene, nodes: TransformNode[], shouldExportTransformNode: (babylonTransformNode: TransformNode) => boolean): TransformNode[] {
+            const rootNodes: TransformNode[] = [];
+            for (let babylonTransformNode of nodes) {
+                if (shouldExportTransformNode(babylonTransformNode)) {
+                    if (babylonTransformNode.parent == null) {
+                        rootNodes.push(babylonTransformNode);
+                    }
+                }
+            }
+            return rootNodes;
+        }
+
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene
@@ -1183,13 +1202,31 @@ module BABYLON.GLTF2 {
             let idleGLTFAnimations: IAnimation[] = [];
             let node: INode;
 
+            let negScaleRootNode: Nullable<TransformNode> = null;
+
+            const rootNodes = this.getRootNodes(babylonScene, nodes, shouldExportTransformNode);
+            if (rootNodes.length === 1) {
+                const node = rootNodes[0];
+                if (node.scaling.equalsToFloats(1,1, -1)) {
+                    this._convertToRightHandedSystem = !this._convertToRightHandedSystem;
+                    negScaleRootNode = node;
+                }  
+            }
+
             for (let babylonTransformNode of nodes) {
                 if (shouldExportTransformNode(babylonTransformNode)) {
                     node = this.createNode(babylonTransformNode, binaryWriter);
-
-                    this._nodes.push(node);
-                    nodeIndex = this._nodes.length - 1;
-                    nodeMap[babylonTransformNode.uniqueId] = nodeIndex;
+                    if (negScaleRootNode && babylonTransformNode === negScaleRootNode) {
+                        node.scale = [1,1,1];
+                        node.rotation = [0,0,0,1];
+                    }
+                    
+                    const directDescendents = babylonTransformNode.getDescendants(true, (node: Node) => {return (node instanceof TransformNode);});
+                    if (directDescendents.length || node.mesh != null) {
+                        this._nodes.push(node);
+                        nodeIndex = this._nodes.length - 1;
+                        nodeMap[babylonTransformNode.uniqueId] = nodeIndex;
+                    }
 
                     if (!babylonScene.animationGroups.length && babylonTransformNode.animations.length) {
                         _GLTFAnimation._CreateNodeAnimationFromTransformNodeAnimations(babylonTransformNode, runtimeGLTFAnimation, idleGLTFAnimations, nodeMap, this._nodes, binaryWriter, this._bufferViews, this._accessors, this._convertToRightHandedSystem, this._animationSampleRate);

+ 8 - 9
src/Animations/babylon.runtimeAnimation.ts

@@ -391,7 +391,7 @@
          * @param loop defines if the current animation must loop
          * @param speedRatio defines the current speed ratio
          * @param weight defines the weight of the animation (default is -1 so no weight)
-         * @returns a boolean indicating if the animation has ended
+         * @returns a boolean indicating if the animation is running
          */
         public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, weight = -1.0): boolean {
             let targetPropertyPath = this._animation.targetPropertyPath
@@ -400,21 +400,20 @@
                 return false;
             }
 
-            let keys = this._animation.getKeys();
-
-            // Return immediately if there is only one key frame.
-            if (keys.length === 1) {
-                this.setValue(keys[0].value, weight);
-                return !loop;
-            }
-
             var returnValue = true;
 
+            let keys = this._animation.getKeys();
+
             // Adding a start key at frame 0 if missing
             if (keys[0].frame !== 0) {
                 var newKey = { frame: 0, value: keys[0].value };
                 keys.splice(0, 0, newKey);
             }
+            // Adding a duplicate key when there is only one key at frame zero
+            else if (keys.length === 1) {
+                var newKey = { frame: 0.001, value: keys[0].value };
+                keys.push(newKey);
+            }
 
             // Check limits
             if (from < keys[0].frame || from > keys[keys.length - 1].frame) {

+ 18 - 13
src/Behaviors/Mesh/babylon.pointerDragBehavior.ts

@@ -7,6 +7,7 @@ module BABYLON {
         private _dragPlane: Mesh;
         private _scene:Scene;
         private _pointerObserver:Nullable<Observer<PointerInfo>>;
+        private _beforeRenderObserver:Nullable<Observer<Scene>>;
         private static _planeScene:Scene;
         /**
          * The maximum tolerated angle between the drag plane and dragging pointer rays to trigger pointer events. Set to 0 to allow any angle (default: 0)
@@ -70,19 +71,19 @@ module BABYLON {
          */
         public useObjectOrienationForDragging = true;
 
+        private _options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3};
         /**
          * Creates a pointer drag behavior that can be attached to a mesh
          * @param options The drag axis or normal of the plane that will be dragged across. If no options are specified the drag plane will always face the ray's origin (eg. camera)
          */
-        constructor(private options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3}){
+        constructor(options?:{dragAxis?:Vector3, dragPlaneNormal?:Vector3}){
+            this._options = options ? options : {};
+            
             var optionCount = 0;
-            if(options === undefined){
-                options = {}
-            }
-            if(options.dragAxis){
+            if(this._options.dragAxis){
                 optionCount++;
             }
-            if(options.dragPlaneNormal){
+            if(this._options.dragPlaneNormal){
                 optionCount++;
             }
             if(optionCount > 1){
@@ -119,6 +120,7 @@ module BABYLON {
                     PointerDragBehavior._planeScene = this._scene;
                 }else{
                     PointerDragBehavior._planeScene = new BABYLON.Scene(this._scene.getEngine());
+                    PointerDragBehavior._planeScene.detachControl();
                     this._scene.getEngine().scenes.pop();
                 }
             }
@@ -167,9 +169,9 @@ module BABYLON {
                             }
                             
                             // depending on the drag mode option drag accordingly
-                            if(this.options.dragAxis){
+                            if(this._options.dragAxis){
                                 // Convert local drag axis to world
-                                Vector3.TransformCoordinatesToRef(this.options.dragAxis, this._attachedNode.getWorldMatrix().getRotationMatrix(), this._worldDragAxis);
+                                Vector3.TransformCoordinatesToRef(this._options.dragAxis, this._attachedNode.getWorldMatrix().getRotationMatrix(), this._worldDragAxis);
 
                                 // Project delta drag from the drag plane onto the drag axis
                                 pickedPoint.subtractToRef(this.lastDragPosition, this._tmpVector);
@@ -187,7 +189,7 @@ module BABYLON {
                 }
             });
 
-            this._scene.onBeforeRenderObservable.add(()=>{
+            this._beforeRenderObserver = this._scene.onBeforeRenderObservable.add(()=>{
                 if(this._moving && this.moveAttached){
                     // Slowly move mesh to avoid jitter
                     targetPosition.subtractToRef((<Mesh>this._attachedNode).absolutePosition, this._tmpVector);
@@ -257,8 +259,8 @@ module BABYLON {
         // Position the drag plane based on the attached mesh position, for single axis rotate the plane along the axis to face the camera
         private _updateDragPlanePosition(ray:Ray, dragPlanePosition:Vector3){
             this._pointA.copyFrom(dragPlanePosition);
-            if(this.options.dragAxis){
-                this.useObjectOrienationForDragging ? Vector3.TransformCoordinatesToRef(this.options.dragAxis, this._attachedNode.getWorldMatrix().getRotationMatrix(), this._localAxis) : this._localAxis.copyFrom(this.options.dragAxis);
+            if(this._options.dragAxis){
+                this.useObjectOrienationForDragging ? Vector3.TransformCoordinatesToRef(this._options.dragAxis, this._attachedNode.getWorldMatrix().getRotationMatrix(), this._localAxis) : this._localAxis.copyFrom(this._options.dragAxis);
 
                 // Calculate plane normal in direction of camera but perpendicular to drag axis
                 this._pointA.addToRef(this._localAxis, this._pointB); // towards drag axis
@@ -275,8 +277,8 @@ module BABYLON {
                 this._dragPlane.position.copyFrom(this._pointA);
                 this._pointA.subtractToRef(this._lookAt, this._lookAt);
                 this._dragPlane.lookAt(this._lookAt);
-            }else if(this.options.dragPlaneNormal){
-                this.useObjectOrienationForDragging ? Vector3.TransformCoordinatesToRef(this.options.dragPlaneNormal, this._attachedNode.getWorldMatrix().getRotationMatrix(),this._localAxis) : this._localAxis.copyFrom(this.options.dragPlaneNormal);
+            }else if(this._options.dragPlaneNormal){
+                this.useObjectOrienationForDragging ? Vector3.TransformCoordinatesToRef(this._options.dragPlaneNormal, this._attachedNode.getWorldMatrix().getRotationMatrix(),this._localAxis) : this._localAxis.copyFrom(this._options.dragPlaneNormal);
                 this._dragPlane.position.copyFrom(this._pointA);
                 this._pointA.subtractToRef(this._localAxis, this._lookAt);
                 this._dragPlane.lookAt(this._lookAt);
@@ -294,6 +296,9 @@ module BABYLON {
             if(this._pointerObserver){
                 this._scene.onPointerObservable.remove(this._pointerObserver);
             }
+            if(this._beforeRenderObserver){
+                this._scene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
+            }
         }
     }
 }

+ 27 - 10
src/Behaviors/Mesh/babylon.sixDofDragBehavior.ts

@@ -14,9 +14,9 @@ module BABYLON {
         private _moving = false;
         private _startingOrientation = new Quaternion();
         /**
-         * How much faster the object should move when the controller is moving towards it. This is useful to bring objects that are far away from the user to them faster. Set this to 0 to avoid any speed increase. (Default: 5)
+         * How much faster the object should move when the controller is moving towards it. This is useful to bring objects that are far away from the user to them faster. Set this to 0 to avoid any speed increase. (Default: 3)
          */
-         private zDragFactor = 5;
+         private zDragFactor = 3;
         /**
          * If the behavior is currently in a dragging state
          */
@@ -74,7 +74,11 @@ module BABYLON {
             this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo, eventState)=>{                
                 if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
                     if(!this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)){
-                        pickedMesh = pointerInfo.pickInfo.pickedMesh;
+                        if(this._scene.activeCamera && this._scene.activeCamera.cameraRigMode == Camera.RIG_MODE_NONE){
+                            pointerInfo.pickInfo.ray.origin.copyFrom(this._scene.activeCamera!.position)
+                        }
+                        
+                        pickedMesh = this._ownerNode;
                         lastSixDofOriginPosition.copyFrom(pointerInfo.pickInfo.ray.origin);
 
                         // Set position and orientation of the controller
@@ -108,15 +112,20 @@ module BABYLON {
                     }
                 }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
                     if(this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId && this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray && pickedMesh){
+                        var zDragFactor = this.zDragFactor;
+                        if(this._scene.activeCamera && this._scene.activeCamera.cameraRigMode == Camera.RIG_MODE_NONE){
+                            pointerInfo.pickInfo.ray.origin.copyFrom(this._scene.activeCamera!.position)
+                            zDragFactor = 0;
+                        }
+
                         // Calculate controller drag distance in controller space
                         var originDragDifference = pointerInfo.pickInfo.ray.origin.subtract(lastSixDofOriginPosition);
                         lastSixDofOriginPosition.copyFrom(pointerInfo.pickInfo.ray.origin);
-                        var localOriginDragDifference = Vector3.TransformCoordinates(originDragDifference, Matrix.Invert(this._virtualOriginMesh.getWorldMatrix().getRotationMatrix()));
+                        var localOriginDragDifference = -Vector3.Dot(originDragDifference, pointerInfo.pickInfo.ray.direction);
 
                         this._virtualOriginMesh.addChild(this._virtualDragMesh);
                         // Determine how much the controller moved to/away towards the dragged object and use this to move the object further when its further away
-                        var zDragDistance = Vector3.Dot(localOriginDragDifference, this._virtualOriginMesh.position.normalizeToNew());
-                        this._virtualDragMesh.position.z -= this._virtualDragMesh.position.z < 1 ? zDragDistance*this.zDragFactor : zDragDistance*this.zDragFactor*this._virtualDragMesh.position.z;
+                        this._virtualDragMesh.position.z -= this._virtualDragMesh.position.z < 1 ? localOriginDragDifference*this.zDragFactor : localOriginDragDifference*zDragFactor*this._virtualDragMesh.position.z;
                         if(this._virtualDragMesh.position.z < 0){
                             this._virtualDragMesh.position.z = 0;
                         }
@@ -168,10 +177,18 @@ module BABYLON {
          *  Detaches the behavior from the mesh
          */
         public detach(): void {
-            this._scene.onPointerObservable.remove(this._pointerObserver);
-            this._ownerNode.getScene().onBeforeRenderObservable.remove(this._sceneRenderObserver);
-            this._virtualOriginMesh.dispose()
-            this._virtualDragMesh.dispose();
+            if(this._scene){
+                this._scene.onPointerObservable.remove(this._pointerObserver);
+            }
+            if(this._ownerNode){
+                this._ownerNode.getScene().onBeforeRenderObservable.remove(this._sceneRenderObserver);
+            }
+            if(this._virtualOriginMesh){
+                this._virtualOriginMesh.dispose();
+            }
+            if(this._virtualDragMesh){
+                this._virtualDragMesh.dispose();
+            }
         }
     }
 }

+ 2 - 4
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -167,7 +167,6 @@ module BABYLON {
 
         public _setLaserPointerParent(mesh:AbstractMesh){
             var makeNotPick = (root: AbstractMesh) => {
-                root.name += " laserPointer";
                 root.isPickable = false;
                 root.getChildMeshes().forEach((c) => {
                     makeNotPick(c);
@@ -921,7 +920,7 @@ module BABYLON {
                 }
 
                 this.raySelectionPredicate = (mesh) => {
-                    return mesh.isVisible;
+                    return mesh.isVisible && mesh.isPickable;
                 }
 
                 this.meshSelectionPredicate = (mesh) => {
@@ -931,8 +930,7 @@ module BABYLON {
                 this._raySelectionPredicate = (mesh) => {
                     if (this._isTeleportationFloor(mesh) || (mesh.name.indexOf("gazeTracker") === -1
                         && mesh.name.indexOf("teleportationTarget") === -1
-                        && mesh.name.indexOf("torusTeleportation") === -1
-                        && mesh.name.indexOf("laserPointer") === -1)) {
+                        && mesh.name.indexOf("torusTeleportation") === -1)) {
                         return this.raySelectionPredicate(mesh);
                     }
                     return false;

+ 11 - 1
src/Cameras/VR/babylon.webVRCamera.ts

@@ -148,7 +148,8 @@ module BABYLON {
 
         // Represents device position and rotation in room space. Should only be used to help calculate babylon space values
         private _deviceRoomPosition = Vector3.Zero();
-        private _deviceRoomRotationQuaternion = Quaternion.Identity();
+        /** @hidden */
+        public _deviceRoomRotationQuaternion = Quaternion.Identity();
 
         private _standingMatrix: Nullable<Matrix> = null;
 
@@ -182,6 +183,11 @@ module BABYLON {
          */
         public onControllerMeshLoadedObservable = new Observable<WebVRController>();
         /**
+         * Emits an event when the HMD's pose has been updated.
+         */
+        public onPoseUpdatedFromDeviceObservable = new Observable<any>();
+        private _poseSet = false;
+        /**
          * If the rig cameras be used as parent instead of this camera.
          */
         public rigParenting: boolean = true;
@@ -430,6 +436,7 @@ module BABYLON {
                         this._deviceRoomPosition.z *= -1;
                     }
                 }
+                this._poseSet = true;
             }
         }
 
@@ -558,6 +565,9 @@ module BABYLON {
             this._workingMatrix.multiplyToRef(this._deviceToWorld, this._workingMatrix)
             Quaternion.FromRotationMatrixToRef(this._workingMatrix, this.deviceRotationQuaternion);
 
+            if(this._poseSet){
+                this.onPoseUpdatedFromDeviceObservable.notifyObservers(null);
+            }
             super.update();
         }
 

+ 4 - 4
src/Cameras/babylon.arcRotateCamera.ts

@@ -573,17 +573,17 @@
                 }
             }
 
-            if (this.lowerAlphaLimit && this.alpha < this.lowerAlphaLimit) {
+            if (this.lowerAlphaLimit !== null && this.alpha < this.lowerAlphaLimit) {
                 this.alpha = this.lowerAlphaLimit;
             }
-            if (this.upperAlphaLimit && this.alpha > this.upperAlphaLimit) {
+            if (this.upperAlphaLimit !== null && this.alpha > this.upperAlphaLimit) {
                 this.alpha = this.upperAlphaLimit;
             }
 
-            if (this.lowerRadiusLimit && this.radius < this.lowerRadiusLimit) {
+            if (this.lowerRadiusLimit !== null && this.radius < this.lowerRadiusLimit) {
                 this.radius = this.lowerRadiusLimit;
             }
-            if (this.upperRadiusLimit && this.radius > this.upperRadiusLimit) {
+            if (this.upperRadiusLimit !== null && this.radius > this.upperRadiusLimit) {
                 this.radius = this.upperRadiusLimit;
             }
         }

+ 19 - 0
src/Culling/babylon.boundingBox.ts

@@ -73,6 +73,25 @@
             this._update(this._worldMatrix || Matrix.Identity());
         }
 
+        /**
+         * Scale the current bounding box by applying a scale factor
+         * @param factor defines the scale factor to apply
+         * @returns the current bounding box
+         */
+        public scale(factor: number): BoundingBox {
+            let diff = this.maximum.subtract(this.minimum);
+            let distance = diff.length() * factor;
+            diff.normalize();
+            let newRadius = diff.scale(distance / 2);
+
+            let min = this.center.subtract(newRadius);
+            let max = this.center.add(newRadius);
+
+            this.reConstruct(min, max);
+
+            return this;
+        }
+
         public getWorldMatrix(): Matrix {
             return this._worldMatrix;
         }

+ 12 - 0
src/Culling/babylon.boundingInfo.ts

@@ -70,6 +70,18 @@
             return this;
         }
 
+        /**
+         * Scale the current bounding info by applying a scale factor
+         * @param factor defines the scale factor to apply
+         * @returns the current bounding info
+         */
+        public scale(factor: number): BoundingInfo {
+            this.boundingBox.scale(factor);
+            this.boundingSphere.scale(factor);
+
+            return this;
+        }
+
         public isInFrustum(frustumPlanes: Plane[]): boolean {
             if (!this.boundingSphere.isInFrustum(frustumPlanes))
                 return false;

+ 17 - 0
src/Culling/babylon.boundingSphere.ts

@@ -36,6 +36,23 @@
             this._update(Matrix.Identity());            
         }
 
+        /**
+         * Scale the current bounding sphere by applying a scale factor
+         * @param factor defines the scale factor to apply
+         * @returns the current bounding box
+         */
+        public scale(factor: number): BoundingSphere {
+            let newRadius = this.radius * factor;
+            let newRadiusVector = new Vector3(newRadius, newRadius, newRadius);
+
+            let min = this.center.subtract(newRadiusVector);
+            let max = this.center.add(newRadiusVector);
+
+            this.reConstruct(min, max);
+
+            return this;
+        }
+
         // Methods
         public _update(world: Matrix): void {
             Vector3.TransformCoordinatesToRef(this.center, world, this.centerWorld);

+ 308 - 0
src/Engine/Extensions/babylon.engine.occlusionQuery.ts

@@ -0,0 +1,308 @@
+module BABYLON {
+    export interface Engine {       
+        /**
+         * Create a new webGL query (you must be sure that queries are supported by checking getCaps() function)
+         * @return the new query
+         */
+        createQuery(): WebGLQuery;
+
+        /**
+         * Delete and release a webGL query
+         * @param query defines the query to delete
+         * @return the current engine
+         */
+        deleteQuery(query: WebGLQuery): Engine;
+
+        /**
+         * Check if a given query has resolved and got its value
+         * @param query defines the query to check
+         * @returns true if the query got its value
+         */
+        isQueryResultAvailable(query: WebGLQuery): boolean;
+
+        /**
+         * Gets the value of a given query
+         * @param query defines the query to check
+         * @returns the value of the query
+         */
+        getQueryResult(query: WebGLQuery): number;        
+
+        /**
+         * Initiates an occlusion query
+         * @param algorithmType defines the algorithm to use
+         * @param query defines the query to use
+         * @returns the current engine
+         * @see http://doc.babylonjs.com/features/occlusionquery
+         */
+        beginOcclusionQuery(algorithmType: number, query: WebGLQuery): Engine;
+
+        /**
+         * Ends an occlusion query
+         * @see http://doc.babylonjs.com/features/occlusionquery
+         * @param algorithmType defines the algorithm to use
+         * @returns the current engine
+         */
+        endOcclusionQuery(algorithmType: number): Engine;   
+
+        /**
+         * Starts a time query (used to measure time spent by the GPU on a specific frame)
+         * Please note that only one query can be issued at a time
+         * @returns a time token used to track the time span
+         */
+        startTimeQuery(): Nullable<_TimeToken>;
+
+        /**
+         * Ends a time query
+         * @param token defines the token used to measure the time span
+         * @returns the time spent (in ns)
+         */
+        endTimeQuery(token: _TimeToken): int;
+
+        /** @hidden */
+        _currentNonTimestampToken: Nullable<_TimeToken>;
+        
+        /** @hidden */
+        _createTimeQuery(): WebGLQuery;
+
+        /** @hidden */
+        _deleteTimeQuery(query: WebGLQuery): void;
+
+        /** @hidden */
+        _getGlAlgorithmType(algorithmType: number): number;
+
+        /** @hidden */
+        _getTimeQueryResult(query: WebGLQuery): any;
+
+        /** @hidden */
+        _getTimeQueryAvailability(query: WebGLQuery): any;
+    }
+
+    Engine.prototype.createQuery = function(): WebGLQuery {
+        return this._gl.createQuery();
+    }
+
+    Engine.prototype.deleteQuery = function(query: WebGLQuery): Engine {
+        this._gl.deleteQuery(query);
+
+        return this;
+    }
+
+    Engine.prototype.isQueryResultAvailable = function(query: WebGLQuery): boolean {
+        return this._gl.getQueryParameter(query, this._gl.QUERY_RESULT_AVAILABLE) as boolean;
+    }
+
+    Engine.prototype.getQueryResult = function(query: WebGLQuery): number {
+        return this._gl.getQueryParameter(query, this._gl.QUERY_RESULT) as number;
+    }
+
+    Engine.prototype.beginOcclusionQuery = function(algorithmType: number, query: WebGLQuery): Engine {
+        var glAlgorithm = this._getGlAlgorithmType(algorithmType);
+        this._gl.beginQuery(glAlgorithm, query);
+
+        return this;
+    }
+
+    Engine.prototype.endOcclusionQuery = function(algorithmType: number): Engine {
+        var glAlgorithm = this._getGlAlgorithmType(algorithmType);
+        this._gl.endQuery(glAlgorithm);
+
+        return this;
+    }
+
+    Engine.prototype._createTimeQuery = function(): WebGLQuery {
+        let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
+
+        if (timerQuery.createQueryEXT) {
+            return timerQuery.createQueryEXT();
+        }
+
+        return this.createQuery();
+    }
+
+    Engine.prototype._deleteTimeQuery = function(query: WebGLQuery): void {
+        let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
+
+        if (timerQuery.deleteQueryEXT) {
+            timerQuery.deleteQueryEXT(query);
+            return;
+        }
+
+        this.deleteQuery(query);
+    }
+
+    Engine.prototype._getTimeQueryResult = function(query: WebGLQuery): any {
+        let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
+
+        if (timerQuery.getQueryObjectEXT) {
+            return timerQuery.getQueryObjectEXT(query, timerQuery.QUERY_RESULT_EXT);
+        }
+        return this.getQueryResult(query);
+    }
+
+    Engine.prototype._getTimeQueryAvailability = function(query: WebGLQuery): any {
+        let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
+
+        if (timerQuery.getQueryObjectEXT) {
+            return timerQuery.getQueryObjectEXT(query, timerQuery.QUERY_RESULT_AVAILABLE_EXT);
+        }
+        return this.isQueryResultAvailable(query);
+    }
+
+    Engine.prototype.startTimeQuery = function(): Nullable<_TimeToken> {
+        let caps = this.getCaps();
+        let timerQuery = caps.timerQuery;
+        if (!timerQuery) {
+            return null;
+        }
+
+        let token = new _TimeToken();
+        this._gl.getParameter(timerQuery.GPU_DISJOINT_EXT);
+        if (caps.canUseTimestampForTimerQuery) {
+            token._startTimeQuery = this._createTimeQuery();
+
+            timerQuery.queryCounterEXT(token._startTimeQuery, timerQuery.TIMESTAMP_EXT);
+        } else {
+            if (this._currentNonTimestampToken) {
+                return this._currentNonTimestampToken;
+            }
+
+            token._timeElapsedQuery = this._createTimeQuery();
+            if (timerQuery.beginQueryEXT) {
+                timerQuery.beginQueryEXT(timerQuery.TIME_ELAPSED_EXT, token._timeElapsedQuery);
+            } else {
+                this._gl.beginQuery(timerQuery.TIME_ELAPSED_EXT, token._timeElapsedQuery);
+            }
+
+            this._currentNonTimestampToken = token;
+        }
+        return token;
+    }
+
+    Engine.prototype.endTimeQuery = function(token: _TimeToken): int {
+        let caps = this.getCaps();
+        let timerQuery = caps.timerQuery;
+        if (!timerQuery || !token) {
+            return -1;
+        }
+
+        if (caps.canUseTimestampForTimerQuery) {
+            if (!token._startTimeQuery) {
+                return -1;
+            }
+            if (!token._endTimeQuery) {
+                token._endTimeQuery = this._createTimeQuery();
+                timerQuery.queryCounterEXT(token._endTimeQuery, timerQuery.TIMESTAMP_EXT);
+            }
+        } else if (!token._timeElapsedQueryEnded) {
+            if (!token._timeElapsedQuery) {
+                return -1;
+            }
+            if (timerQuery.endQueryEXT) {
+                timerQuery.endQueryEXT(timerQuery.TIME_ELAPSED_EXT);
+            } else {
+                this._gl.endQuery(timerQuery.TIME_ELAPSED_EXT);
+            }
+            token._timeElapsedQueryEnded = true;
+        }
+
+        let disjoint = this._gl.getParameter(timerQuery.GPU_DISJOINT_EXT);
+        let available: boolean = false;
+        if (token._endTimeQuery) {
+            available = this._getTimeQueryAvailability(token._endTimeQuery);
+        } else if (token._timeElapsedQuery) {
+            available = this._getTimeQueryAvailability(token._timeElapsedQuery);
+        }
+
+        if (available && !disjoint) {
+            let result = 0;
+            if (caps.canUseTimestampForTimerQuery) {
+                if (!token._startTimeQuery || !token._endTimeQuery) {
+                    return -1;
+                }
+                let timeStart = this._getTimeQueryResult(token._startTimeQuery);
+                let timeEnd = this._getTimeQueryResult(token._endTimeQuery);
+
+                result = timeEnd - timeStart;
+                this._deleteTimeQuery(token._startTimeQuery);
+                this._deleteTimeQuery(token._endTimeQuery);
+                token._startTimeQuery = null;
+                token._endTimeQuery = null;
+            } else {
+                if (!token._timeElapsedQuery) {
+                    return -1;
+                }
+
+                result = this._getTimeQueryResult(token._timeElapsedQuery);
+                this._deleteTimeQuery(token._timeElapsedQuery);
+                token._timeElapsedQuery = null;
+                token._timeElapsedQueryEnded = false;
+                this._currentNonTimestampToken = null;
+            }
+            return result;
+        }
+
+        return -1;
+    }
+
+    Engine.prototype._getGlAlgorithmType = function(algorithmType: number): number {
+        return algorithmType === AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE ? this._gl.ANY_SAMPLES_PASSED_CONSERVATIVE : this._gl.ANY_SAMPLES_PASSED;
+    }
+
+    // We also need to update AbstractMesh as there is a portion of the code there
+    AbstractMesh.prototype._checkOcclusionQuery = function() {
+        var engine = this.getEngine();
+
+        if (!engine.isQueryResultAvailable) { // Occlusion query where not referenced
+            this._isOccluded = false;
+            return;
+        }
+
+        if (engine.webGLVersion < 2 || this.occlusionType === AbstractMesh.OCCLUSION_TYPE_NONE) {
+            this._isOccluded = false;
+            return;
+        }
+
+        if (this.isOcclusionQueryInProgress && this._occlusionQuery) {
+
+            var isOcclusionQueryAvailable = engine.isQueryResultAvailable(this._occlusionQuery);
+            if (isOcclusionQueryAvailable) {
+                var occlusionQueryResult = engine.getQueryResult(this._occlusionQuery);
+
+                this._isOcclusionQueryInProgress = false;
+                this._occlusionInternalRetryCounter = 0;
+                this._isOccluded = occlusionQueryResult === 1 ? false : true;
+            }
+            else {
+
+                this._occlusionInternalRetryCounter++;
+
+                if (this.occlusionRetryCount !== -1 && this._occlusionInternalRetryCounter > this.occlusionRetryCount) {
+                    this._isOcclusionQueryInProgress = false;
+                    this._occlusionInternalRetryCounter = 0;
+
+                    // if optimistic set isOccluded to false regardless of the status of isOccluded. (Render in the current render loop)
+                    // if strict continue the last state of the object.
+                    this._isOccluded = this.occlusionType === AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC ? false : this._isOccluded;
+                }
+                else {
+                    return;
+                }
+
+            }
+        }
+
+        var scene = this.getScene();
+        if (scene.getBoundingBoxRenderer) {
+        var occlusionBoundingBoxRenderer = scene.getBoundingBoxRenderer();
+
+            if (!this._occlusionQuery) {
+                this._occlusionQuery = engine.createQuery();
+            }
+
+            engine.beginOcclusionQuery(this.occlusionQueryAlgorithmType, this._occlusionQuery);
+            occlusionBoundingBoxRenderer.renderOcclusionBoundingBox(this);
+            engine.endOcclusionQuery(this.occlusionQueryAlgorithmType);
+            this._isOcclusionQueryInProgress = true;
+        }
+    } 
+}

+ 74 - 0
src/Engine/Extensions/babylon.engine.transformFeedback.ts

@@ -0,0 +1,74 @@
+module BABYLON {
+    export interface Engine {
+        /**
+         * Creates a webGL transform feedback object
+         * Please makes sure to check webGLVersion property to check if you are running webGL 2+
+         * @returns the webGL transform feedback object
+         */
+        createTransformFeedback(): WebGLTransformFeedback;      
+        
+        /**
+         * Delete a webGL transform feedback object 
+         * @param value defines the webGL transform feedback object to delete
+         */
+        deleteTransformFeedback(value: WebGLTransformFeedback): void; 
+
+        /**
+         * Bind a webGL transform feedback object to the webgl context
+         * @param value defines the webGL transform feedback object to bind
+         */        
+        bindTransformFeedback(value: Nullable<WebGLTransformFeedback>): void;
+
+        /**
+         * Begins a transform feedback operation
+         * @param usePoints defines if points or triangles must be used
+         */              
+        beginTransformFeedback(usePoints: boolean): void;
+
+        /**
+         * Ends a transform feedback operation
+         */           
+        endTransformFeedback(): void;
+
+        /**
+         * Specify the varyings to use with transform feedback
+         * @param program defines the associated webGL program
+         * @param value defines the list of strings representing the varying names
+         */
+        setTranformFeedbackVaryings(program: WebGLProgram, value: string[]): void;
+        
+        /**
+         * Bind a webGL buffer for a transform feedback operation
+         * @param value defines the webGL buffer to bind
+         */          
+        bindTransformFeedbackBuffer(value: Nullable<WebGLBuffer>): void;
+    }
+
+    Engine.prototype.createTransformFeedback = function(): WebGLTransformFeedback {
+        return this._gl.createTransformFeedback();
+    }
+
+    Engine.prototype.deleteTransformFeedback = function(value: WebGLTransformFeedback): void {
+        this._gl.deleteTransformFeedback(value);
+    }
+
+    Engine.prototype.bindTransformFeedback = function(value: Nullable<WebGLTransformFeedback>): void {
+        this._gl.bindTransformFeedback(this._gl.TRANSFORM_FEEDBACK, value);
+    }
+
+    Engine.prototype.beginTransformFeedback = function(usePoints: boolean = true): void {
+        this._gl.beginTransformFeedback(usePoints ? this._gl.POINTS : this._gl.TRIANGLES);
+    }
+
+    Engine.prototype.endTransformFeedback = function(): void {
+        this._gl.endTransformFeedback();
+    }
+
+    Engine.prototype.setTranformFeedbackVaryings = function(program: WebGLProgram, value: string[]): void {
+        this._gl.transformFeedbackVaryings(program, value, this._gl.INTERLEAVED_ATTRIBS);
+    }
+
+    Engine.prototype.bindTransformFeedbackBuffer = function(value: Nullable<WebGLBuffer>): void {
+        this._gl.bindBufferBase(this._gl.TRANSFORM_FEEDBACK_BUFFER, 0, value);
+    }
+}

+ 21 - 290
src/Engine/babylon.engine.ts

@@ -589,7 +589,9 @@
         public onAfterShaderCompilationObservable = new Observable<Engine>();
 
         // Private Members
-        private _gl: WebGLRenderingContext;
+
+        /** @hidden */
+        public _gl: WebGLRenderingContext;
         private _renderingCanvas: Nullable<HTMLCanvasElement>;
         private _windowIsBackground = false;
         private _webGLVersion = 1.0;
@@ -5227,7 +5229,7 @@
         }
 
         /** @hidden */
-        public _uploadDataToTextureDirectly(texture: InternalTexture, width: number, height: number, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void {
+        public _uploadDataToTextureDirectly(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void {
             var gl = this._gl;
 
             var textureType = this._getWebGLTextureType(texture.type);
@@ -5241,6 +5243,11 @@
                 target = gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex;
             }
 
+            const lodMaxWidth = Math.round(Scalar.Log2(texture.width));
+            const lodMaxHeight = Math.round(Scalar.Log2(texture.height));
+            const width = Math.pow(2, Math.max(lodMaxWidth - lod, 0));
+            const height = Math.pow(2, Math.max(lodMaxHeight - lod, 0));
+
             gl.texImage2D(target, lod, internalFormat, width, height, 0, format, textureType, imageData);
         }
 
@@ -5251,7 +5258,7 @@
 
             this._bindTextureDirectly(bindTarget, texture, true);
 
-            this._uploadDataToTextureDirectly(texture, texture.width, texture.height, imageData, faceIndex, lod);
+            this._uploadDataToTextureDirectly(texture, imageData, faceIndex, lod);
 
             this._bindTextureDirectly(bindTarget, null, true);
         }
@@ -5569,6 +5576,10 @@
                                 let data = imgs[index];
                                 info = DDSTools.GetDDSInfo(data);
 
+                                texture.width = info.width;
+                                texture.height = info.height;
+                                width = info.width;
+
                                 loadMipmap = (info.isRGB || info.isLuminance || info.mipmapCount > 1) && !noMipmap;
 
                                 this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
@@ -5579,11 +5590,6 @@
                                 if (!noMipmap && !info.isFourCC && info.mipmapCount === 1) {
                                     gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
                                 }
-
-                                texture.width = info.width;
-                                texture.height = info.height;
-                                texture.type = info.textureType;
-                                width = info.width;
                             }
 
                             this.setCubeMapTextureParams(gl, loadMipmap);
@@ -5595,15 +5601,18 @@
                         },
                         files,
                         onError);
-
                 } else {
                     this._loadFile(rootUrl,
                         data => {
                             var info = DDSTools.GetDDSInfo(data);
-                            if(createPolynomials){
+
+                            texture.width = info.width;
+                            texture.height = info.height;
+
+                            if (createPolynomials) {
                                 info.sphericalPolynomial = new SphericalPolynomial();
                             }
-                            
+
                             var loadMipmap = (info.isRGB || info.isLuminance || info.mipmapCount > 1) && !noMipmap;
 
                             this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
@@ -5616,12 +5625,8 @@
                             }
 
                             this.setCubeMapTextureParams(gl, loadMipmap);
-
-                            texture.width = info.width;
-                            texture.height = info.height;
                             texture.isReady = true;
-                            texture.type = info.textureType;
-                            
+
                             if (onLoad) {
                                 onLoad({ isDDS: true, width: info.width, info, data, texture });
                             }
@@ -7214,280 +7219,6 @@
             return this._gl.RGBA8;
         };
 
-        /**
-         * Create a new webGL query (you must be sure that queries are supported by checking getCaps() function)
-         * @return the new query
-         */
-        public createQuery(): WebGLQuery {
-            return this._gl.createQuery();
-        }
-
-        /**
-         * Delete and release a webGL query
-         * @param query defines the query to delete
-         * @return the current engine
-         */
-        public deleteQuery(query: WebGLQuery): Engine {
-            this._gl.deleteQuery(query);
-
-            return this;
-        }
-
-        /**
-         * Check if a given query has resolved and got its value
-         * @param query defines the query to check
-         * @returns true if the query got its value
-         */
-        public isQueryResultAvailable(query: WebGLQuery): boolean {
-            return this._gl.getQueryParameter(query, this._gl.QUERY_RESULT_AVAILABLE) as boolean;
-        }
-
-        /**
-         * Gets the value of a given query
-         * @param query defines the query to check
-         * @returns the value of the query
-         */
-        public getQueryResult(query: WebGLQuery): number {
-            return this._gl.getQueryParameter(query, this._gl.QUERY_RESULT) as number;
-        }
-
-        /**
-         * Initiates an occlusion query
-         * @param algorithmType defines the algorithm to use
-         * @param query defines the query to use
-         * @returns the current engine
-         * @see http://doc.babylonjs.com/features/occlusionquery
-         */
-        public beginOcclusionQuery(algorithmType: number, query: WebGLQuery): Engine {
-            var glAlgorithm = this.getGlAlgorithmType(algorithmType);
-            this._gl.beginQuery(glAlgorithm, query);
-
-            return this;
-        }
-
-        /**
-         * Ends an occlusion query
-         * @see http://doc.babylonjs.com/features/occlusionquery
-         * @param algorithmType defines the algorithm to use
-         * @returns the current engine
-         */
-        public endOcclusionQuery(algorithmType: number): Engine {
-            var glAlgorithm = this.getGlAlgorithmType(algorithmType);
-            this._gl.endQuery(glAlgorithm);
-
-            return this;
-        }
-
-        /* Time queries */
-
-        private _createTimeQuery(): WebGLQuery {
-            let timerQuery = <EXT_disjoint_timer_query>this._caps.timerQuery;
-
-            if (timerQuery.createQueryEXT) {
-                return timerQuery.createQueryEXT();
-            }
-
-            return this.createQuery();
-        }
-
-        private _deleteTimeQuery(query: WebGLQuery): void {
-            let timerQuery = <EXT_disjoint_timer_query>this._caps.timerQuery;
-
-            if (timerQuery.deleteQueryEXT) {
-                timerQuery.deleteQueryEXT(query);
-                return;
-            }
-
-            this.deleteQuery(query);
-        }
-
-        private _getTimeQueryResult(query: WebGLQuery): any {
-            let timerQuery = <EXT_disjoint_timer_query>this._caps.timerQuery;
-
-            if (timerQuery.getQueryObjectEXT) {
-                return timerQuery.getQueryObjectEXT(query, timerQuery.QUERY_RESULT_EXT);
-            }
-            return this.getQueryResult(query);
-        }
-
-        private _getTimeQueryAvailability(query: WebGLQuery): any {
-            let timerQuery = <EXT_disjoint_timer_query>this._caps.timerQuery;
-
-            if (timerQuery.getQueryObjectEXT) {
-                return timerQuery.getQueryObjectEXT(query, timerQuery.QUERY_RESULT_AVAILABLE_EXT);
-            }
-            return this.isQueryResultAvailable(query);
-        }
-
-        private _currentNonTimestampToken: Nullable<_TimeToken>;
-
-        /**
-         * Starts a time query (used to measure time spent by the GPU on a specific frame)
-         * Please note that only one query can be issued at a time
-         * @returns a time token used to track the time span
-         */
-        public startTimeQuery(): Nullable<_TimeToken> {
-            let timerQuery = this._caps.timerQuery;
-            if (!timerQuery) {
-                return null;
-            }
-
-            let token = new _TimeToken();
-            this._gl.getParameter(timerQuery.GPU_DISJOINT_EXT);
-            if (this._caps.canUseTimestampForTimerQuery) {
-                token._startTimeQuery = this._createTimeQuery();
-
-                timerQuery.queryCounterEXT(token._startTimeQuery, timerQuery.TIMESTAMP_EXT);
-            } else {
-                if (this._currentNonTimestampToken) {
-                    return this._currentNonTimestampToken;
-                }
-
-                token._timeElapsedQuery = this._createTimeQuery();
-                if (timerQuery.beginQueryEXT) {
-                    timerQuery.beginQueryEXT(timerQuery.TIME_ELAPSED_EXT, token._timeElapsedQuery);
-                } else {
-                    this._gl.beginQuery(timerQuery.TIME_ELAPSED_EXT, token._timeElapsedQuery);
-                }
-
-                this._currentNonTimestampToken = token;
-            }
-            return token;
-        }
-
-        /**
-         * Ends a time query
-         * @param token defines the token used to measure the time span
-         * @returns the time spent (in ns)
-         */
-        public endTimeQuery(token: _TimeToken): int {
-            let timerQuery = this._caps.timerQuery;
-            if (!timerQuery || !token) {
-                return -1;
-            }
-
-            if (this._caps.canUseTimestampForTimerQuery) {
-                if (!token._startTimeQuery) {
-                    return -1;
-                }
-                if (!token._endTimeQuery) {
-                    token._endTimeQuery = this._createTimeQuery();
-                    timerQuery.queryCounterEXT(token._endTimeQuery, timerQuery.TIMESTAMP_EXT);
-                }
-            } else if (!token._timeElapsedQueryEnded) {
-                if (!token._timeElapsedQuery) {
-                    return -1;
-                }
-                if (timerQuery.endQueryEXT) {
-                    timerQuery.endQueryEXT(timerQuery.TIME_ELAPSED_EXT);
-                } else {
-                    this._gl.endQuery(timerQuery.TIME_ELAPSED_EXT);
-                }
-                token._timeElapsedQueryEnded = true;
-            }
-
-            let disjoint = this._gl.getParameter(timerQuery.GPU_DISJOINT_EXT);
-            let available: boolean = false;
-            if (token._endTimeQuery) {
-                available = this._getTimeQueryAvailability(token._endTimeQuery);
-            } else if (token._timeElapsedQuery) {
-                available = this._getTimeQueryAvailability(token._timeElapsedQuery);
-            }
-
-            if (available && !disjoint) {
-                let result = 0;
-                if (this._caps.canUseTimestampForTimerQuery) {
-                    if (!token._startTimeQuery || !token._endTimeQuery) {
-                        return -1;
-                    }
-                    let timeStart = this._getTimeQueryResult(token._startTimeQuery);
-                    let timeEnd = this._getTimeQueryResult(token._endTimeQuery);
-
-                    result = timeEnd - timeStart;
-                    this._deleteTimeQuery(token._startTimeQuery);
-                    this._deleteTimeQuery(token._endTimeQuery);
-                    token._startTimeQuery = null;
-                    token._endTimeQuery = null;
-                } else {
-                    if (!token._timeElapsedQuery) {
-                        return -1;
-                    }
-
-                    result = this._getTimeQueryResult(token._timeElapsedQuery);
-                    this._deleteTimeQuery(token._timeElapsedQuery);
-                    token._timeElapsedQuery = null;
-                    token._timeElapsedQueryEnded = false;
-                    this._currentNonTimestampToken = null;
-                }
-                return result;
-            }
-
-            return -1;
-        }
-
-        private getGlAlgorithmType(algorithmType: number): number {
-            return algorithmType === AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE ? this._gl.ANY_SAMPLES_PASSED_CONSERVATIVE : this._gl.ANY_SAMPLES_PASSED;
-        }
-
-        // Transform feedback
-
-        /**
-         * Creates a webGL transform feedback object
-         * Please makes sure to check webGLVersion property to check if you are running webGL 2+
-         * @returns the webGL transform feedback object
-         */
-        public createTransformFeedback(): WebGLTransformFeedback {
-            return this._gl.createTransformFeedback();
-        }
-
-        /**
-         * Delete a webGL transform feedback object 
-         * @param value defines the webGL transform feedback object to delete
-         */
-        public deleteTransformFeedback(value: WebGLTransformFeedback): void {
-            this._gl.deleteTransformFeedback(value);
-        }
-
-        /**
-         * Bind a webGL transform feedback object to the webgl context
-         * @param value defines the webGL transform feedback object to bind
-         */        
-        public bindTransformFeedback(value: Nullable<WebGLTransformFeedback>): void {
-            this._gl.bindTransformFeedback(this._gl.TRANSFORM_FEEDBACK, value);
-        }
-
-        /**
-         * Begins a transform feedback operation
-         * @param usePoints defines if points or triangles must be used
-         */              
-        public beginTransformFeedback(usePoints: boolean = true): void {
-            this._gl.beginTransformFeedback(usePoints ? this._gl.POINTS : this._gl.TRIANGLES);
-        }
-
-        /**
-         * Ends a transform feedback operation
-         */           
-        public endTransformFeedback(): void {
-            this._gl.endTransformFeedback();
-        }
-
-        /**
-         * Specify the varyings to use with transform feedback
-         * @param program defines the associated webGL program
-         * @param value defines the list of strings representing the varying names
-         */
-        public setTranformFeedbackVaryings(program: WebGLProgram, value: string[]): void {
-            this._gl.transformFeedbackVaryings(program, value, this._gl.INTERLEAVED_ATTRIBS);
-        }
-
-        /**
-         * Bind a webGL buffer for a transform feedback operation
-         * @param value defines the webGL buffer to bind
-         */          
-        public bindTransformFeedbackBuffer(value: Nullable<WebGLBuffer>): void {
-            this._gl.bindBufferBase(this._gl.TRANSFORM_FEEDBACK_BUFFER, 0, value);
-        }
-
         /** @hidden */
         public _loadFile(url: string, onSuccess: (data: string | ArrayBuffer, responseURL?: string) => void, onProgress?: (data: any) => void, database?: Database, useArrayBuffer?: boolean, onError?: (request?: XMLHttpRequest, exception?: any) => void): IFileRequest {
             let request = Tools.LoadFile(url, onSuccess, onProgress, database, useArrayBuffer, onError);

+ 1 - 1
src/Engine/babylon.nullEngine.ts

@@ -472,7 +472,7 @@
         }
 
         /** @hidden */
-        public _uploadDataToTextureDirectly(texture: InternalTexture, width: number, height: number, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void {
+        public _uploadDataToTextureDirectly(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void {
         }
 
         /** @hidden */

+ 38 - 3
src/Gamepad/Controllers/babylon.gearVRController.ts

@@ -12,11 +12,14 @@ module BABYLON {
          */
         public static MODEL_FILENAME:string = 'generic.babylon';
 
+        private _maxRotationDistFromHeadset = Math.PI/5;
+        private _draggedRoomRotation = 0;
+        private _tmpVector = new BABYLON.Vector3();
         /**
          * Gamepad Id prefix used to identify this controller.
          */
         public static readonly GAMEPAD_ID_PREFIX: string = 'Gear VR'; // id is 'Gear VR Controller'
-
+        
         private readonly _buttonIndexToObservableNameMap = [
             'onTrackpadChangedObservable', // Trackpad
             'onTriggerStateChangedObservable' // Trigger
@@ -30,7 +33,35 @@ module BABYLON {
             super(vrGamepad);
             this.controllerType = PoseEnabledControllerType.GEAR_VR;
             // Initial starting position defaults to where hand would be (incase of only 3dof controller)
-            this._calculatedPosition = new Vector3(this.hand == "left" ? -0.15 : 0.15,-0.5, 0.4)
+            this._calculatedPosition = new Vector3(this.hand == "left" ? -0.15 : 0.15,-0.5, 0.25)
+        }
+
+        /**
+         * Updates the state of the pose enbaled controller based on the raw pose data from the device
+         * @param poseData raw pose fromthe device
+         */
+        public updateFromDevice(poseData: DevicePose) {
+            super.updateFromDevice(poseData);
+            if(BABYLON.Engine.LastCreatedScene && BABYLON.Engine.LastCreatedScene.activeCamera){
+                if((<WebVRFreeCamera>BABYLON.Engine.LastCreatedScene.activeCamera).deviceRotationQuaternion){
+                    var camera = (<WebVRFreeCamera>BABYLON.Engine.LastCreatedScene.activeCamera);
+                    camera._deviceRoomRotationQuaternion.toEulerAnglesToRef(this._tmpVector);
+                    
+                    // Find the radian distance away that the headset is from the controllers rotation
+                    var distanceAway = Math.atan2(Math.sin(this._tmpVector.y - this._draggedRoomRotation), Math.cos(this._tmpVector.y - this._draggedRoomRotation))
+                    if(Math.abs(distanceAway) > this._maxRotationDistFromHeadset){
+                        // Only rotate enouph to be within the _maxRotationDistFromHeadset
+                        var rotationAmount = distanceAway - (distanceAway < 0 ? -this._maxRotationDistFromHeadset : this._maxRotationDistFromHeadset);
+                        this._draggedRoomRotation += rotationAmount;
+                        
+                        // Rotate controller around headset
+                        var sin = Math.sin(-rotationAmount);
+                        var cos = Math.cos(-rotationAmount);
+                        this._calculatedPosition.x = this._calculatedPosition.x * cos - this._calculatedPosition.z * sin;
+                        this._calculatedPosition.z = this._calculatedPosition.x * sin + this._calculatedPosition.z * cos;
+                    }                  
+                }
+            }
         }
 
         /**
@@ -40,7 +71,11 @@ module BABYLON {
          */
         public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void) {
             SceneLoader.ImportMesh("", GearVRController.MODEL_BASE_URL, GearVRController.MODEL_FILENAME, scene, (newMeshes) => {
-                this._defaultModel = newMeshes[1];
+                // Offset the controller so it will rotate around the users wrist
+                var mesh = new BABYLON.Mesh("", scene);
+                newMeshes[1].parent = mesh;
+                newMeshes[1].position.z = -0.15;
+                this._defaultModel = mesh;
                 this.attachToMesh(this._defaultModel);
                 if (meshLoaded) {
                     meshLoaded(this._defaultModel);

+ 109 - 23
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -43,7 +43,7 @@ module BABYLON {
          * Fired when a rotation sphere or scale box drag is needed
          */
         public onDragEndObservable = new Observable<{}>();
-        
+        private _anchorMesh:AbstractMesh;
         /**
          * Creates an BoundingBoxGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
@@ -55,6 +55,7 @@ module BABYLON {
             // Do not update the gizmo's scale so it has a fixed size to the object its attached to
             this._updateScale = false;
 
+            this._anchorMesh = new AbstractMesh("anchor", gizmoLayer.utilityLayerScene);
             // Create Materials
             var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
             coloredMaterial.disableLighting = true;
@@ -122,6 +123,9 @@ module BABYLON {
                         if(!this.attachedMesh.rotationQuaternion){
                             this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y,this.attachedMesh.rotation.x,this.attachedMesh.rotation.z);
                         }
+                        if(!this._anchorMesh.rotationQuaternion){
+                            this._anchorMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._anchorMesh.rotation.y,this._anchorMesh.rotation.x,this._anchorMesh.rotation.z);
+                        }
                        
                         // Do not allow the object to turn more than a full circle
                         totalTurnAmountOfDrag+=projectDist;
@@ -133,7 +137,11 @@ module BABYLON {
                             }else{
                                 Quaternion.RotationYawPitchRollToRef(0,projectDist,0, this._tmpQuaternion);
                             }
-                            this.attachedMesh.rotationQuaternion!.multiplyInPlace(this._tmpQuaternion);
+
+                            // Rotate around center of bounding box
+                            this._anchorMesh.addChild(this.attachedMesh);
+                            this._anchorMesh.rotationQuaternion!.multiplyToRef(this._tmpQuaternion,this._anchorMesh.rotationQuaternion!);
+                            this._anchorMesh.removeChild(this.attachedMesh);
                         }
                     }
                 });
@@ -169,25 +177,19 @@ module BABYLON {
                         _dragBehavior.onDragObservable.add((event)=>{
                             this.onDragObservable.notifyObservers({});
                             if(this.attachedMesh){
-                                // Current boudning box dimensions
-                                var boundingInfo = this.attachedMesh.getBoundingInfo().boundingBox;
-                                var boundBoxDimensions = boundingInfo.maximum.subtract(boundingInfo.minimum).multiplyInPlace(this.attachedMesh.scaling);
-                                
-                                // Get the change in bounding box size/2 and add this to the mesh's position to offset from scaling with center pivot point
                                 var deltaScale = new Vector3(event.dragDistance,event.dragDistance,event.dragDistance);
                                 deltaScale.scaleInPlace(this._scaleDragSpeed);
-                                var scaleRatio = deltaScale.divide(this.attachedMesh.scaling).scaleInPlace(0.5);
-                                var moveDirection = boundBoxDimensions.multiply(scaleRatio).multiplyInPlace(dragAxis);
-                                var worldMoveDirection = Vector3.TransformCoordinates(moveDirection, this.attachedMesh.getWorldMatrix().getRotationMatrix());
-                                
-                                // Update scale and position
-                                this.attachedMesh.scaling.addInPlace(deltaScale);
-                                if(this.attachedMesh.scaling.x < 0 || this.attachedMesh.scaling.y < 0 || this.attachedMesh.scaling.z < 0){
-                                    this.attachedMesh.scaling.subtractInPlace(deltaScale);
-                                }else{
-                                    this.attachedMesh.getAbsolutePosition().addToRef(worldMoveDirection, this._tmpVector)
-                                    this.attachedMesh.setAbsolutePosition(this._tmpVector);
+                                this._updateBoundingBox(); 
+
+                                 // Scale from the position of the opposite corner                   
+                                box.absolutePosition.subtractToRef(this._anchorMesh.position, this._tmpVector);
+                                this._anchorMesh.position.subtractInPlace(this._tmpVector);
+                                this._anchorMesh.addChild(this.attachedMesh);
+                                this._anchorMesh.scaling.addInPlace(deltaScale);
+                                if(this._anchorMesh.scaling.x < 0 || this._anchorMesh.scaling.y < 0 || this._anchorMesh.scaling.z < 0){
+                                    this._anchorMesh.scaling.subtractInPlace(deltaScale);
                                 }
+                                this._anchorMesh.removeChild(this.attachedMesh);
                             }
                         })
 
@@ -231,6 +233,16 @@ module BABYLON {
             })
             this._updateBoundingBox();
         }
+        
+        protected _attachedMeshChanged(value:Nullable<AbstractMesh>){
+            if(value){
+                // Reset anchor mesh to match attached mesh's scale
+                // This is needed to avoid invalid box/sphere position on first drag
+                this._anchorMesh.addChild(value);
+                this._anchorMesh.removeChild(value);
+                this._updateBoundingBox();
+            }
+        }
 
         private _selectNode(selectedMesh:Nullable<Mesh>){
             this._rotateSpheresParent.getChildMeshes()
@@ -239,13 +251,46 @@ module BABYLON {
             })
         }
 
+        private _recurseComputeWorld(mesh:AbstractMesh){
+            mesh.computeWorldMatrix(true);
+            mesh.getChildMeshes().forEach((m)=>{
+                this._recurseComputeWorld(m);
+            });
+        }
+
         private _updateBoundingBox(){
-            if(this.attachedMesh){
-                // Update bounding dimensions/positions
-                var boundingInfo = this.attachedMesh.getBoundingInfo().boundingBox;
-                var boundBoxDimensions = boundingInfo.maximum.subtract(boundingInfo.minimum).multiplyInPlace(this.attachedMesh.scaling);
-                this._boundingDimensions.copyFrom(boundBoxDimensions);
+            if(this.attachedMesh){             
+                // Rotate based on axis
+                if(!this.attachedMesh.rotationQuaternion){
+                    this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y,this.attachedMesh.rotation.x,this.attachedMesh.rotation.z);
+                }
+                if(!this._anchorMesh.rotationQuaternion){
+                    this._anchorMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._anchorMesh.rotation.y,this._anchorMesh.rotation.x,this._anchorMesh.rotation.z);
+                }
+                this._anchorMesh.rotationQuaternion.copyFrom(this.attachedMesh.rotationQuaternion);
+
+                // Store original position and reset mesh to origin before computing the bounding box
+                this._tmpQuaternion.copyFrom(this.attachedMesh.rotationQuaternion);
+                this._tmpVector.copyFrom(this.attachedMesh.position);
+                this.attachedMesh.rotationQuaternion.set(0,0,0,1);
+                this.attachedMesh.position.set(0,0,0);
+                
+                // Update bounding dimensions/positions   
+                var boundingMinMax = this.attachedMesh.getHierarchyBoundingVectors();
+                boundingMinMax.max.subtractToRef(boundingMinMax.min, this._boundingDimensions);
+
+                // Update gizmo to match bounding box scaling and rotation
                 this._lineBoundingBox.scaling.copyFrom(this._boundingDimensions);
+                this._lineBoundingBox.position.set((boundingMinMax.max.x+boundingMinMax.min.x)/2,(boundingMinMax.max.y+boundingMinMax.min.y)/2,(boundingMinMax.max.z+boundingMinMax.min.z)/2);
+                this._rotateSpheresParent.position.copyFrom(this._lineBoundingBox.position);
+                this._scaleBoxesParent.position.copyFrom(this._lineBoundingBox.position);
+                this._lineBoundingBox.computeWorldMatrix();
+                this._anchorMesh.position.copyFrom(this._lineBoundingBox.absolutePosition);
+
+                // restore position/rotation values
+                this.attachedMesh.rotationQuaternion.copyFrom(this._tmpQuaternion);
+                this.attachedMesh.position.copyFrom(this._tmpVector);
+                this._recurseComputeWorld(this.attachedMesh);
             }
 
             // Update rotation sphere locations
@@ -329,5 +374,46 @@ module BABYLON {
             this._scaleBoxesParent.dispose();
             super.dispose();
         } 
+
+        /**
+         * Makes a mesh not pickable and wraps the mesh inside of a bounding box mesh that is pickable. (This is useful to avoid picking within complex geometry)
+         * @param mesh the mesh to wrap in the bounding box mesh and make not pickable
+         * @returns the bounding box mesh with the passed in mesh as a child
+         */
+        public static MakeNotPickableAndWrapInBoundingBox(mesh:Mesh):Mesh{
+            var makeNotPickable = (root:AbstractMesh) => {
+                root.isPickable = false;
+                root.getChildMeshes().forEach((c) => {
+                    makeNotPickable(c);
+                });
+            }
+            makeNotPickable(mesh);
+
+            // Reset position to get boudning box from origin with no rotation
+            if(!mesh.rotationQuaternion){
+                mesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(mesh.rotation.y,mesh.rotation.x,mesh.rotation.z);
+            }
+            var oldPos = mesh.position.clone()
+            var oldRot = mesh.rotationQuaternion.clone();
+            mesh.rotationQuaternion.set(0,0,0,1);
+            mesh.position.set(0,0,0)
+
+            // Update bounding dimensions/positions   
+            var box = BABYLON.MeshBuilder.CreateBox("box", {size: 1}, mesh.getScene());
+            var boundingMinMax = mesh.getHierarchyBoundingVectors();
+            boundingMinMax.max.subtractToRef(boundingMinMax.min, box.scaling);
+            box.position.set((boundingMinMax.max.x+boundingMinMax.min.x)/2,(boundingMinMax.max.y+boundingMinMax.min.y)/2,(boundingMinMax.max.z+boundingMinMax.min.z)/2);
+            
+            // Restore original positions
+            mesh.addChild(box);
+            mesh.rotationQuaternion.copyFrom(oldRot);
+            mesh.position.copyFrom(oldPos);
+
+            // Reverse parenting
+            mesh.removeChild(box);
+            box.addChild(mesh);
+            box.visibility = 0;
+            return box;
+        }
     }
 }

+ 5 - 1
src/Gizmos/babylon.gizmo.ts

@@ -74,7 +74,11 @@ module BABYLON {
                         this._rootMesh.position.copyFrom(this.attachedMesh.absolutePosition);
                     }
                     if(this._updateScale && this.gizmoLayer.utilityLayerScene.activeCamera && this.attachedMesh){
-                        this._rootMesh.position.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.position, tempVector);
+                        var cameraPosition = this.gizmoLayer.utilityLayerScene.activeCamera.position;
+                        if((<WebVRFreeCamera>this.gizmoLayer.utilityLayerScene.activeCamera).devicePosition){
+                            cameraPosition = (<WebVRFreeCamera>this.gizmoLayer.utilityLayerScene.activeCamera).devicePosition;
+                        }
+                        this._rootMesh.position.subtractToRef(cameraPosition, tempVector);
                         var dist = tempVector.length()/this._scaleFactor;
                         this._rootMesh.scaling.set(dist, dist, dist);
                     }

+ 138 - 46
src/Gizmos/babylon.gizmoManager.ts

@@ -3,80 +3,172 @@ module BABYLON {
      * Helps setup gizmo's in the scene to rotate/scale/position meshes
      */
     export class GizmoManager implements IDisposable{
-
+        /**
+         * Gizmo's created by the gizmo manager, gizmo will be null until gizmo has been enabled for the first time
+         */
+        public gizmos:{positionGizmo: Nullable<PositionGizmo>, rotationGizmo: Nullable<RotationGizmo>, scaleGizmo: Nullable<ScaleGizmo>, boundingBoxGizmo: Nullable<BoundingBoxGizmo>};
+        private _gizmosEnabled = {positionGizmo: false, rotationGizmo: false, scaleGizmo: false, boundingBoxGizmo: false};
         private _gizmoLayer:UtilityLayerRenderer;
-        // Set of gizmos that are currently in the scene for each mesh
-        private _gizmoSet:{[meshID: string]: {positionGizmo: PositionGizmo, rotationGizmo: RotationGizmo}} = {}
         private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
+        private _attachedMesh:Nullable<AbstractMesh> = null;
+        private _boundingBoxColor = BABYLON.Color3.FromHexString("#0984e3");
+        private _boundingBoxUtilLayer:Nullable<UtilityLayerRenderer> = null;
+        private _dragBehavior = new BABYLON.SixDofDragBehavior();
+        /**
+         * Array of meshes which will have the gizmo attached when a pointer selected them. If null, all meshes are attachable. (Default: null)
+         */
+        public attachableMeshes:Nullable<Array<AbstractMesh>> = null;
+        /**
+         * If pointer events should perform attaching/detaching a gizmo, if false this can be done manually via attachToMesh. (Default: true)
+         */
+        public usePointerToAttachGizmos = true;
 
         /**
          * Instatiates a gizmo manager
          * @param scene the scene to overlay the gizmos on top of
-         * @param options If only a single gizmo should exist at one time
          */
-        constructor(private scene:Scene, options?:{singleGizmo?:boolean}){
-            this._gizmoLayer = new UtilityLayerRenderer(scene);
-
-            // Options parsing
-            if(!options){
-                options = {}
-            }
-            if(options.singleGizmo === undefined){
-                options.singleGizmo = true;
-            }
+        constructor(private scene:Scene){
+            this.gizmos = {positionGizmo: null, rotationGizmo: null, scaleGizmo: null, boundingBoxGizmo: null};
 
             // Instatiate/dispose gizmos based on pointer actions
             this._pointerObserver = scene.onPointerObservable.add((pointerInfo, state)=>{
+                if(!this.usePointerToAttachGizmos){
+                    return;
+                }
                 if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN){
                     if(pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh){
-                        if(!this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId]){
-                            if(options!.singleGizmo){
-                                this._clearGizmos();
-                            }                            
-                            // Enable gizmo when mesh is selected
-                            this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId] = {positionGizmo: new PositionGizmo(this._gizmoLayer), rotationGizmo: new RotationGizmo(this._gizmoLayer)}
-                            this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId].positionGizmo.attachedMesh = pointerInfo.pickInfo.pickedMesh;
-                            this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId].rotationGizmo.attachedMesh = pointerInfo.pickInfo.pickedMesh;
-                        }else{
-                            if(!options!.singleGizmo){
-                                // Disable gizmo when clicked again
-                                this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId].positionGizmo.dispose();
-                                this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId].rotationGizmo.dispose();
-                                delete this._gizmoSet[pointerInfo.pickInfo.pickedMesh.uniqueId];
+                        var node:Nullable<Node> = pointerInfo.pickInfo.pickedMesh;
+                        if(this.attachableMeshes == null){
+                            // Attach to the most parent node
+                            while(node && node.parent != null){
+                                node = node.parent;
                             }
-                        }
-                    }else {
-                        if(options!.singleGizmo){
-                            // Disable gizmo when clicked away
-                            if(pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
-                                var gizmoPick = this._gizmoLayer.utilityLayerScene.pickWithRay(pointerInfo.pickInfo.ray);
-                                if(gizmoPick && !gizmoPick.hit){
-                                    this._clearGizmos();
+                        }else{
+                            // Attach to the parent node that is an attachableMesh
+                            var found = false;
+                            this.attachableMeshes.forEach((mesh)=>{
+                                if(node && (node == mesh || node.isDescendantOf(mesh))){
+                                    node = mesh;
+                                    found = true;
                                 }
+                            })
+                            if(!found){
+                                node = null;
                             }
                         }
+                        if(node instanceof AbstractMesh){
+                            this.attachToMesh(node);
+                        }
+                    }else{
+                        this.attachToMesh(null);
                     }
                 }
             })
         }
 
         /**
+         * Attaches a set of gizmos to the specified mesh
+         * @param mesh The mesh the gizmo's should be attached to
+         */
+        public attachToMesh(mesh:Nullable<AbstractMesh>){
+            if(this._attachedMesh){
+                this._attachedMesh.removeBehavior(this._dragBehavior);
+            }
+            this._attachedMesh = mesh;
+            for(var key in this.gizmos){
+                var gizmo = <Nullable<Gizmo>>((<any>this.gizmos)[key]);
+                if(gizmo && (<any>this._gizmosEnabled)[key]){
+                    gizmo.attachedMesh = mesh;
+                }
+            }
+            if(this.boundingBoxGizmoEnabled && this._attachedMesh){
+                this._attachedMesh.addBehavior(this._dragBehavior);
+            }
+        }
+
+        /**
+         * If the position gizmo is enabled
+         */
+        public set positionGizmoEnabled(value:boolean){
+            if(value){
+                this.gizmos.positionGizmo = this.gizmos.positionGizmo || new PositionGizmo();
+                this.gizmos.positionGizmo.updateGizmoRotationToMatchAttachedMesh = false;
+                this.gizmos.positionGizmo.attachedMesh = this._attachedMesh;
+            }else if(this.gizmos.positionGizmo){
+                this.gizmos.positionGizmo.attachedMesh = null;
+            }
+            this._gizmosEnabled.positionGizmo = value;
+        }
+        public get positionGizmoEnabled():boolean{
+            return this._gizmosEnabled.positionGizmo;
+        }
+        /**
+         * If the rotation gizmo is enabled
+         */
+        public set rotationGizmoEnabled(value:boolean){
+            if(value){
+                this.gizmos.rotationGizmo = this.gizmos.rotationGizmo || new RotationGizmo();
+                this.gizmos.rotationGizmo.updateGizmoRotationToMatchAttachedMesh = false;
+                this.gizmos.rotationGizmo.attachedMesh = this._attachedMesh;
+            }else if(this.gizmos.rotationGizmo){
+                this.gizmos.rotationGizmo.attachedMesh = null;
+            }
+            this._gizmosEnabled.rotationGizmo = value;
+        }
+        public get rotationGizmoEnabled():boolean{
+            return this._gizmosEnabled.rotationGizmo;
+        }
+        /**
+         * If the scale gizmo is enabled
+         */
+        public set scaleGizmoEnabled(value:boolean){
+            if(value){
+                this.gizmos.scaleGizmo = this.gizmos.scaleGizmo || new ScaleGizmo();
+                this.gizmos.scaleGizmo.attachedMesh = this._attachedMesh;
+            }else if(this.gizmos.scaleGizmo){
+                this.gizmos.scaleGizmo.attachedMesh = null;
+            }
+            this._gizmosEnabled.scaleGizmo = value;
+        }
+        public get scaleGizmoEnabled():boolean{
+            return this._gizmosEnabled.scaleGizmo;
+        }
+        /**
+         * If the boundingBox gizmo is enabled
+         */
+        public set boundingBoxGizmoEnabled(value:boolean){
+            if(value){
+                if(!this._boundingBoxUtilLayer){
+                    this._boundingBoxUtilLayer = new BABYLON.UtilityLayerRenderer(this.scene);
+                    this._boundingBoxUtilLayer.utilityLayerScene.autoClearDepthAndStencil = false;
+                }
+                this.gizmos.boundingBoxGizmo = this.gizmos.boundingBoxGizmo || new BoundingBoxGizmo(this._boundingBoxColor, this._boundingBoxUtilLayer);
+                this.gizmos.boundingBoxGizmo.attachedMesh = this._attachedMesh;
+                if(this._attachedMesh){
+                    this._attachedMesh.removeBehavior(this._dragBehavior);
+                    this._attachedMesh.addBehavior(this._dragBehavior);
+                }
+            }else if(this.gizmos.boundingBoxGizmo){
+                this.gizmos.boundingBoxGizmo.attachedMesh = null;
+            }
+            this._gizmosEnabled.boundingBoxGizmo = value;
+        }
+        public get boundingBoxGizmoEnabled():boolean{
+            return this._gizmosEnabled.boundingBoxGizmo;
+        }
+
+        /**
          * Disposes of the gizmo manager
          */
         public dispose(){
             this.scene.onPointerObservable.remove(this._pointerObserver);
-            this._clearGizmos();
-            this._gizmoLayer.dispose();
-        }
-
-        private _clearGizmos(){
-            for(var key in this._gizmoSet){
-                if(this._gizmoSet.hasOwnProperty(key)){
-                    this._gizmoSet[key].positionGizmo.dispose();
-                    this._gizmoSet[key].rotationGizmo.dispose();
-                    delete this._gizmoSet[key];
+            for(var key in this.gizmos){
+                var gizmo = <Nullable<Gizmo>>((<any>this.gizmos)[key]);
+                if(gizmo){
+                    gizmo.dispose();
                 }
             }
+            this._gizmoLayer.dispose();
         }
     }
 }

+ 20 - 7
src/Materials/Textures/babylon.videoTexture.ts

@@ -47,6 +47,7 @@
         private _generateMipMaps: boolean;
         private _engine: Engine;
         private _stillImageCaptured = false;
+        private _poster = false;
 
         /**
          * Creates a video texture.
@@ -102,6 +103,11 @@
             if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) {
                 this._createInternalTexture();
             }
+
+            if (settings.poster) {
+                this._texture = this._engine.createTexture(settings.poster!, false, true, scene);
+                this._poster = true;
+            }
         }
 
         private _getName(src: string | string[] | HTMLVideoElement): string {
@@ -138,13 +144,17 @@
 
         private _createInternalTexture = (): void => {
             if (this._texture != null) {
-                return;
+                if (this._poster) {
+                    this._texture.dispose();
+                    this._poster = false;
+                }
+                else {
+                    return;
+                }
             }
 
-            if (
-                !this._engine.needPOTTextures ||
-                (Tools.IsExponentOfTwo(this.video.videoWidth) && Tools.IsExponentOfTwo(this.video.videoHeight))
-            ) {
+            if (!this._engine.needPOTTextures ||
+                (Tools.IsExponentOfTwo(this.video.videoWidth) && Tools.IsExponentOfTwo(this.video.videoHeight))) {
                 this.wrapU = Texture.WRAP_ADDRESSMODE;
                 this.wrapV = Texture.WRAP_ADDRESSMODE;
             } else {
@@ -209,8 +219,11 @@
             if (this._texture == null) {
                 return;
             }
-            this._texture.dispose();
-            this._texture = null;
+
+            if (!this._poster) {
+                this._texture.dispose();
+                this._texture = null;
+            }
         };
 
         /**

+ 7 - 2
src/Materials/babylon.materialHelper.ts

@@ -236,8 +236,13 @@ module BABYLON {
                     if (mesh && mesh.receiveShadows && scene.shadowsEnabled && light.shadowEnabled) {
                         var shadowGenerator = light.getShadowGenerator();
                         if (shadowGenerator) {
-                            shadowEnabled = true;
-                            shadowGenerator.prepareDefines(defines, lightIndex);
+                            const shadowMap = shadowGenerator.getShadowMap();
+                            if (shadowMap) {
+                                if (shadowMap.renderList && shadowMap.renderList.length > 0) {
+                                    shadowEnabled = true;
+                                    shadowGenerator.prepareDefines(defines, lightIndex);
+                                }
+                            }
                         }
                     }
 

+ 10 - 55
src/Mesh/babylon.abstractMesh.ts

@@ -205,9 +205,11 @@
         * @see http://doc.babylonjs.com/features/occlusionquery
         */
         public occlusionRetryCount = -1;
-        private _occlusionInternalRetryCounter = 0;
+        /** @hidden */
+        public _occlusionInternalRetryCounter = 0;
 
-        protected _isOccluded = false;
+        /** @hidden */
+        public _isOccluded = false;
 
         /**
         * Gets or sets whether the mesh is occluded or not, it is used also to set the intial state of the mesh to be occluded or not
@@ -221,7 +223,8 @@
             this._isOccluded = value;
         }
 
-        private _isOcclusionQueryInProgress = false;
+        /** @hidden */
+        public _isOcclusionQueryInProgress = false;
 
         /**
          * Flag to check the progress status of the query
@@ -231,7 +234,8 @@
             return this._isOcclusionQueryInProgress;
         }
 
-        private _occlusionQuery: Nullable<WebGLQuery>;
+        /** @hidden */
+        public _occlusionQuery: Nullable<WebGLQuery>;
 
         private _visibility = 1.0;
 
@@ -2104,57 +2108,8 @@
         }
 
         /** @hidden */
-        protected _checkOcclusionQuery() {
-            var engine = this.getEngine();
-
-            if (engine.webGLVersion < 2 || this.occlusionType === AbstractMesh.OCCLUSION_TYPE_NONE) {
-                this._isOccluded = false;
-                return;
-            }
-
-            if (this.isOcclusionQueryInProgress && this._occlusionQuery) {
-
-                var isOcclusionQueryAvailable = engine.isQueryResultAvailable(this._occlusionQuery);
-                if (isOcclusionQueryAvailable) {
-                    var occlusionQueryResult = engine.getQueryResult(this._occlusionQuery);
-
-                    this._isOcclusionQueryInProgress = false;
-                    this._occlusionInternalRetryCounter = 0;
-                    this._isOccluded = occlusionQueryResult === 1 ? false : true;
-                }
-                else {
-
-                    this._occlusionInternalRetryCounter++;
-
-                    if (this.occlusionRetryCount !== -1 && this._occlusionInternalRetryCounter > this.occlusionRetryCount) {
-                        this._isOcclusionQueryInProgress = false;
-                        this._occlusionInternalRetryCounter = 0;
-
-                        // if optimistic set isOccluded to false regardless of the status of isOccluded. (Render in the current render loop)
-                        // if strict continue the last state of the object.
-                        this._isOccluded = this.occlusionType === AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC ? false : this._isOccluded;
-                    }
-                    else {
-                        return;
-                    }
-
-                }
-            }
-
-            // Todo. Move into an occlusion query component.
-            var scene = this.getScene();
-            if (scene.getBoundingBoxRenderer) {
-               var occlusionBoundingBoxRenderer = scene.getBoundingBoxRenderer();
-
-                if (!this._occlusionQuery) {
-                    this._occlusionQuery = engine.createQuery();
-                }
-
-                engine.beginOcclusionQuery(this.occlusionQueryAlgorithmType, this._occlusionQuery);
-                occlusionBoundingBoxRenderer.renderOcclusionBoundingBox(this);
-                engine.endOcclusionQuery(this.occlusionQueryAlgorithmType);
-                this._isOcclusionQueryInProgress = true;
-            }
+        public _checkOcclusionQuery() { // Will be replaced by correct code if Occlusion queries are referenced
+            this._isOccluded = false;
         }
     }
 }

+ 7 - 6
src/Mesh/babylon.transformNode.ts

@@ -792,7 +792,7 @@ module BABYLON {
                 Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, Tmp.Matrix[0]);
                 this._cache.rotation.copyFrom(this.rotation);
             }
-
+          
             // Translation
             let camera = (<Camera>this.getScene().activeCamera);
 
@@ -809,7 +809,7 @@ module BABYLON {
             }
 
             // Composing transformations
-            this._pivotMatrix.multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[4]);
+            this._pivotMatrix.multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[4]);           
             Tmp.Matrix[4].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[5]);
 
             // Billboarding (testing PG:http://www.babylonjs-playground.com/#UJEIL#13)
@@ -856,6 +856,11 @@ module BABYLON {
                 Tmp.Matrix[1].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[5]);
             }
 
+            // Post multiply inverse of pivotMatrix
+            if (this._postMultiplyPivotMatrix) {
+                Tmp.Matrix[5].multiplyToRef(this._pivotMatrixInverse, Tmp.Matrix[5]);
+            }           
+
             // Local world
             Tmp.Matrix[5].multiplyToRef(Tmp.Matrix[2], this._localWorld);
 
@@ -887,10 +892,6 @@ module BABYLON {
                 this._worldMatrix.copyFrom(this._localWorld);
             }
 
-            // Post multiply inverse of pivotMatrix
-            if (this._postMultiplyPivotMatrix) {
-                this._worldMatrix.multiplyToRef(this._pivotMatrixInverse, this._worldMatrix);
-            }
 
             // Normal matrix
             if (!this.ignoreNonUniformScaling) {

+ 3 - 2
src/Particles/babylon.IParticleSystem.ts

@@ -231,9 +231,10 @@ module BABYLON {
         rebuild(): void;
 
         /**
-         * Starts the particle system and begins to emit.
+         * Starts the particle system and begins to emit
+         * @param delay defines the delay in milliseconds before starting the system (0 by default)
          */
-        start(): void;
+        start(delay?: number): void;
 
         /**
          * Stops the particle system.

+ 11 - 4
src/Particles/babylon.gpuParticleSystem.ts

@@ -372,9 +372,16 @@
         }
 
         /**
-         * Starts the particle system and begins to emit.
-         */
-        public start(): void {
+         * Starts the particle system and begins to emit
+         * @param delay defines the delay in milliseconds before starting the system (0 by default)
+         */
+        public start(delay = 0): void {
+            if (delay) {
+                setTimeout(()=> {
+                    this.start(0);
+                }, delay);
+                return;
+            }
             this._started = true;
             this._stopped = false;
             this._preWarmDone = false;
@@ -1211,7 +1218,7 @@
             // Update
             this._engine.bindTransformFeedbackBuffer(this._targetBuffer.getBuffer());
             this._engine.setRasterizerState(false);
-            this._engine.beginTransformFeedback();
+            this._engine.beginTransformFeedback(true);
             this._engine.drawArraysType(Material.PointListDrawMode, 0, this._currentActiveCount);
             this._engine.endTransformFeedback();
             this._engine.setRasterizerState(true);

+ 11 - 3
src/Particles/babylon.particleSystem.ts

@@ -902,9 +902,17 @@
         }
 
         /**
-         * Starts the particle system and begins to emit.
-         */
-        public start(): void {
+         * Starts the particle system and begins to emit
+         * @param delay defines the delay in milliseconds before starting the system (0 by default)
+         */
+        public start(delay = 0): void {
+            if (delay) {
+                setTimeout(()=> {
+                    this.start(0);
+                }, delay);
+                return;
+            }
+
             this._started = true;
             this._stopped = false;
             this._actualFrame = 0;

+ 10 - 3
src/Physics/Plugins/babylon.oimoJSPlugin.ts

@@ -122,12 +122,15 @@ module BABYLON {
                     return Math.max(value, PhysicsEngine.Epsilon);
                 }
 
+                let globalQuaternion: Quaternion = new Quaternion();
+
                 impostors.forEach((i) => {
                     if (!i.object.rotationQuaternion) {
                         return;
                     }
                     //get the correct bounding box
                     var oldQuaternion = i.object.rotationQuaternion;
+                    globalQuaternion = oldQuaternion.clone();
 
                     var rot = oldQuaternion.toEulerAngles();
                     var extendSize = i.getObjectExtendSize();
@@ -147,9 +150,9 @@ module BABYLON {
                         bodyConfig.posShape.push(0, 0, 0);
 
                         //tmp solution
-                        bodyConfig.rot.push(rot.x * radToDeg);
-                        bodyConfig.rot.push(rot.y * radToDeg);
-                        bodyConfig.rot.push(rot.z * radToDeg);
+                        bodyConfig.rot.push(0);
+                        bodyConfig.rot.push(0);
+                        bodyConfig.rot.push(0);
                         bodyConfig.rotShape.push(0, 0, 0);
                     } else {
                         let localPosition = i.object.getAbsolutePosition().subtract(impostor.object.getAbsolutePosition());
@@ -222,6 +225,10 @@ module BABYLON {
                 });
 
                 impostor.physicsBody = this.world.add(bodyConfig);
+                // set the quaternion, ignoring the previously defined (euler) rotation
+                impostor.physicsBody.resetQuaternion(globalQuaternion);
+                // update with delta 0, so the body will reveive the new rotation.
+                impostor.physicsBody.updatePosition(0);
 
             } else {
                 this._tmpPositionVector.copyFromFloats(0, 0, 0);

+ 4 - 4
src/Tools/babylon.dds.ts

@@ -525,7 +525,7 @@
                             }
 
                             if (floatArray) {
-                                engine._uploadDataToTextureDirectly(texture, width, height, floatArray, face, i);
+                                engine._uploadDataToTextureDirectly(texture, floatArray, face, i);
                             }
                         } else if (info.isRGB) {
                             texture.type = Engine.TEXTURETYPE_UNSIGNED_INT;
@@ -533,12 +533,12 @@
                                 texture.format = Engine.TEXTUREFORMAT_RGB;
                                 dataLength = width * height * 3;
                                 byteArray = DDSTools._GetRGBArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, rOffset, gOffset, bOffset);
-                                engine._uploadDataToTextureDirectly(texture, width, height, byteArray, face, i);
+                                engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
                             } else { // 32
                                 texture.format = Engine.TEXTUREFORMAT_RGBA;
                                 dataLength = width * height * 4;
                                 byteArray = DDSTools._GetRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, rOffset, gOffset, bOffset, aOffset);
-                                engine._uploadDataToTextureDirectly(texture, width, height, byteArray, face, i);
+                                engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
                             }
                         } else if (info.isLuminance) {
                             var unpackAlignment = engine._getUnpackAlignement();
@@ -550,7 +550,7 @@
                             texture.format = Engine.TEXTUREFORMAT_LUMINANCE;
                             texture.type = Engine.TEXTURETYPE_UNSIGNED_INT;
 
-                            engine._uploadDataToTextureDirectly(texture, width, height, byteArray, face, i);
+                            engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
                         } else {
                             dataLength = Math.max(4, width) / 4 * Math.max(4, height) / 4 * blockBytes;
                             byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);

+ 54 - 10
src/Tools/babylon.environmentTextureTools.ts

@@ -52,6 +52,10 @@ module BABYLON {
          * This contains all the images data needed to reconstruct the cubemap.
          */
         mipmaps: Array<BufferImageData>
+        /**
+         * Defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness.
+         */
+        lodGenerationScale: number
     }
 
     /**
@@ -110,6 +114,8 @@ module BABYLON {
             if (manifest.specular) {
                 // Extend the header with the position of the payload.
                 manifest.specular.specularDataPosition = pos;
+                // Fallback to 0.8 exactly if lodGenerationScale is not defined for backward compatibility.
+                manifest.specular.lodGenerationScale = manifest.specular.lodGenerationScale || 0.8;
             }
 
             return manifest;
@@ -135,6 +141,10 @@ module BABYLON {
                 return Promise.reject("Env texture can only be created when the engine is created with the premultipliedAlpha option set to false.");
             }
 
+            if (texture.textureType === Engine.TEXTURETYPE_UNSIGNED_INT) {
+                return Promise.reject("The cube texture should allow HDR (Full Float or Half Float).");
+            }
+
             let canvas = engine.getRenderingCanvas();
             if (!canvas) {
                 return Promise.reject("Env texture can only be created when the engine is associated to a canvas.");
@@ -211,7 +221,8 @@ module BABYLON {
                     width: cubeWidth,
                     irradiance: this._CreateEnvTextureIrradiance(texture),
                     specular: {
-                        mipmaps: []
+                        mipmaps: [],
+                        lodGenerationScale: texture.lodGenerationScale
                     }
                 };
 
@@ -303,7 +314,7 @@ module BABYLON {
          */
         public static UploadEnvLevelsAsync(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): Promise<void> {
             if (info.version !== 1) {
-                Tools.Warn('Unsupported babylon environment map version "' + info.version + '"');
+                throw new Error(`Unsupported babylon environment map version "${info.version}"`);
             }
 
             let specularInfo = info.specular as EnvironmentTextureSpecularInfoV1;
@@ -316,9 +327,11 @@ module BABYLON {
             let mipmapsCount = Scalar.Log2(info.width);
             mipmapsCount = Math.round(mipmapsCount) + 1;
             if (specularInfo.mipmaps.length !== 6 * mipmapsCount) {
-                Tools.Warn('Unsupported specular mipmaps number "' + specularInfo.mipmaps.length + '"');
+                throw new Error(`Unsupported specular mipmaps number "${specularInfo.mipmaps.length}"`);
             }
 
+            texture._lodGenerationScale = specularInfo.lodGenerationScale;
+
             const imageData = new Array<Array<ArrayBufferView>>(mipmapsCount);
             for (let i = 0; i < mipmapsCount; i++) {
                 imageData[i] = new Array<ArrayBufferView>(6);
@@ -338,7 +351,11 @@ module BABYLON {
          * @returns a promise
          */
         public static UploadLevelsAsync(texture: InternalTexture, imageData: ArrayBufferView[][]): Promise<void> {
-            const mipmapsCount = imageData.length;
+            if (!Tools.IsExponentOfTwo(texture.width)) {
+                throw new Error("Texture size must be a power of two");
+            }
+
+            const mipmapsCount = Math.round(Scalar.Log2(texture.width)) + 1;
 
             // Gets everything ready.
             let engine = texture.getEngine();
@@ -351,7 +368,8 @@ module BABYLON {
 
             texture.format = Engine.TEXTUREFORMAT_RGBA;
             texture.type = Engine.TEXTURETYPE_UNSIGNED_INT;
-            texture.samplingMode = Texture.TRILINEAR_SAMPLINGMODE;
+            texture.generateMipMaps = true;
+            engine.updateTextureSamplingMode(Texture.TRILINEAR_SAMPLINGMODE, texture);
 
             // Add extra process if texture lod is not supported
             if (!caps.textureLOD) {
@@ -439,8 +457,8 @@ module BABYLON {
             }
 
             let promises: Promise<void>[] = [];
-            // All mipmaps
-            for (let i = 0; i < mipmapsCount; i++) {
+            // All mipmaps up to provided number of images
+            for (let i = 0; i < imageData.length; i++) {
                 // All faces
                 for (let face = 0; face < 6; face++) {
                     // Constructs an image element from image data
@@ -479,7 +497,7 @@ module BABYLON {
                             else {
                                 engine._uploadImageToTexture(texture, image, face, i);
 
-                                // Upload the face to the none lod texture support
+                                // Upload the face to the non lod texture support
                                 if (generateNonLODTextures) {
                                     let lodTexture = lodTextures![i];
                                     if (lodTexture) {
@@ -497,14 +515,40 @@ module BABYLON {
                 }
             }
 
+            // Fill remaining mipmaps with black textures.
+            if (imageData.length < mipmapsCount) {
+                let data: ArrayBufferView;
+                const size = Math.pow(2, mipmapsCount - 1 - imageData.length);
+                const dataLength = size * size * 4;
+                switch (texture.type) {
+                    case Engine.TEXTURETYPE_UNSIGNED_INT: {
+                        data = new Uint8Array(dataLength);
+                        break;
+                    }
+                    case Engine.TEXTURETYPE_HALF_FLOAT: {
+                        data = new Uint16Array(dataLength);
+                        break;
+                    }
+                    case Engine.TEXTURETYPE_FLOAT: {
+                        data = new Float32Array(dataLength);
+                        break;
+                    }
+                }
+                for (let i = imageData.length; i < mipmapsCount; i++) {
+                    for (let face = 0; face < 6; face++) {
+                        engine._uploadArrayBufferViewToTexture(texture, data!, face, i);
+                    }
+                }
+            }
+
             // Once all done, finishes the cleanup and return
             return Promise.all(promises).then(() => {
-                // Relase temp RTT.
+                // Release temp RTT.
                 if (cubeRtt) {
                     engine._releaseFramebufferObjects(cubeRtt);
                     cubeRtt._swapAndDie(texture);
                 }
-                // Relase temp Post Process.
+                // Release temp Post Process.
                 if (rgbdPostProcess) {
                     rgbdPostProcess.dispose();
                 }

+ 4 - 4
src/Tools/babylon.sceneOptimizer.ts

@@ -362,19 +362,19 @@
 
             var mesh = <Mesh>abstractMesh;
 
-            if (!mesh.isVisible || !mesh.isEnabled()) {
+            if (mesh.isDisposed()) {
                 return false;
             }
 
-            if (mesh.instances.length > 0) {
+            if (!mesh.isVisible || !mesh.isEnabled()) {
                 return false;
             }
 
-            if (mesh.skeleton || mesh.hasLODLevels) {
+            if (mesh.instances.length > 0) {
                 return false;
             }
 
-            if (mesh.parent) {
+            if (mesh.skeleton || mesh.hasLODLevels) {
                 return false;
             }
 

+ 1 - 1
src/Tools/babylon.tga.ts

@@ -196,7 +196,7 @@
             var imageData = (<any>TGATools)[func](header, palettes, pixel_data, y_start, y_step, y_end, x_start, x_step, x_end);
 
             const engine = texture.getEngine();
-            engine._uploadArrayBufferViewToTexture(texture, imageData);
+            engine._uploadDataToTextureDirectly(texture, imageData);
         }
 
         static _getImageData8bits(header: any, palettes: Uint8Array, pixel_data: Uint8Array, y_start: number, y_step: number, y_end: number, x_start: number, x_step: number, x_end: number): Uint8Array {

+ 1 - 1
src/Tools/babylon.tools.ts

@@ -197,7 +197,7 @@
         }
 
         public static SetImmediate(action: () => void) {
-            if (window.setImmediate) {
+            if (Tools.IsWindowObjectExist() && window.setImmediate) {
                 window.setImmediate(action);
             } else {
                 setTimeout(action, 1);

+ 128 - 35
tests/nullEngine/app.js

@@ -174,50 +174,143 @@ var engine = new BABYLON.NullEngine();
 // scene.render();
 // engine.dispose();
 
-var scene = new BABYLON.Scene(engine);
-var light = new BABYLON.PointLight("Omni", new BABYLON.Vector3(20, 20, 100), scene);
+// var scene = new BABYLON.Scene(engine);
+// var light = new BABYLON.PointLight("Omni", new BABYLON.Vector3(20, 20, 100), scene);
 
-var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0.8, 100, BABYLON.Vector3.Zero(), scene);
+// var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0.8, 100, BABYLON.Vector3.Zero(), scene);
 
-var assetsManager = new BABYLON.AssetsManager(scene);
-var meshTask = assetsManager.addMeshTask("skull task", "", "https://raw.githubusercontent.com/RaggarDK/Baby/baby/", "he4.babylon");
+// var assetsManager = new BABYLON.AssetsManager(scene);
+// var meshTask = assetsManager.addMeshTask("skull task", "", "https://raw.githubusercontent.com/RaggarDK/Baby/baby/", "he4.babylon");
+
+// meshTask.onSuccess = function (task) {
+//     
+//     //0 = ground plane, 1,2,3 = boxes
+//     for(var i=1;i<task.loadedMeshes.length;i++){
+//         var mesh = task.loadedMeshes[i];
+//         //mesh.computeWorldMatrix(true);
+
+//         var position = mesh.position.clone();
+//         var rotation = mesh.rotationQuaternion.clone();
+//         var scaling = mesh.getBoundingInfo().boundingBox.extendSize;
+//         var centerWorld = mesh.getBoundingInfo().boundingBox.centerWorld;
+//         console.log(position);
+//         console.log(mesh.getBoundingInfo());
+
+//         var box = BABYLON.MeshBuilder.CreateBox("box"+i,{height:2,width:2,depth:2});
+//         box.scaling.copyFromFloats(scaling.x,scaling.y,scaling.z);    
+//         box.rotationQuaternion = rotation;
+//         //box.position = position;
+//         box.position.set(centerWorld.x,centerWorld.y,centerWorld.z);
+
+//         var material = new BABYLON.StandardMaterial("mat", scene);
+//         material.diffuseColor = new BABYLON.Color3(0.5,1,0.5); 
+//         box.material = material;       
 
-meshTask.onSuccess = function (task) {
-    
-    //0 = ground plane, 1,2,3 = boxes
-    for(var i=1;i<task.loadedMeshes.length;i++){
-        var mesh = task.loadedMeshes[i];
-        //mesh.computeWorldMatrix(true);
+//     }
 
-        var position = mesh.position.clone();
-        var rotation = mesh.rotationQuaternion.clone();
-        var scaling = mesh.getBoundingInfo().boundingBox.extendSize;
-        var centerWorld = mesh.getBoundingInfo().boundingBox.centerWorld;
-        console.log(position);
-        console.log(mesh.getBoundingInfo());
+// }	
 
-        var box = BABYLON.MeshBuilder.CreateBox("box"+i,{height:2,width:2,depth:2});
-        box.scaling.copyFromFloats(scaling.x,scaling.y,scaling.z);    
-        box.rotationQuaternion = rotation;
-        //box.position = position;
-        box.position.set(centerWorld.x,centerWorld.y,centerWorld.z);
+// scene.registerBeforeRender(function () {
+//     light.position = camera.position;
+// });
 
-        var material = new BABYLON.StandardMaterial("mat", scene);
-        material.diffuseColor = new BABYLON.Color3(0.5,1,0.5); 
-        box.material = material;       
+// assetsManager.onFinish = function (tasks) {
+//     engine.runRenderLoop(function () {
+//         scene.render();
+//     });
+// };
 
-    }
+// assetsManager.load();
+
+var scene = new BABYLON.Scene(engine);
+var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 1,  Math.PI / 1, 5, BABYLON.Vector3.Zero(), scene);
+camera.setPosition(new BABYLON.Vector3(-800,1200,-2000));
+camera.setTarget(BABYLON.Vector3.Zero());
+var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
+light.intensity = 1;
+
+
+function createPart(name,opt,parent){
 
-}	
+    var part = BABYLON.MeshBuilder.CreateBox(name, opt.size, scene);
+    part.position = new BABYLON.Vector3(opt.pos.x, opt.pos.y, opt.pos.z);
 
-scene.registerBeforeRender(function () {
-    light.position = camera.position;
-});
+    let mate = new BABYLON.StandardMaterial('mat-'+name, scene);
 
-assetsManager.onFinish = function (tasks) {
-    engine.runRenderLoop(function () {
-        scene.render();
+    if(parent) {
+        mate.specularPower = 200;
+        mate.specularColor = new BABYLON.Color3(0.5, 0.5, 0.5);
+        mate.diffuseTexture = new BABYLON.Texture(opt.mat.url,scene);
+        mate.diffuseTexture.wAng = opt.mat.grain*Math.PI/180;
+        part.parent = parent;
+    }else{
+        mate.alpha = 0;
+    }
+
+    part.material = mate;
+
+    return part;
+}
+
+
+
+var parent;
+
+function createUnit(x,y,z,b){
+
+    var item = {
+        size:{width:x,depth:y,height:z},
+        pos:{x:0,y:0,z:0},
+        mat:{url:false,grain:0},
+        child:{
+            left:{
+                size:{width:b,depth:y,height:z},
+                pos:{x:-(x-b)/2,y:0,z:0},
+                mat:{url:"/playground/textures/crate.png",grain:90}
+            },
+            right:{
+                size:{width:b,depth:y,height:z},
+                pos:{x:(x-b)/2,y:0,z:0},
+                mat:{url:"/playground/textures/crate.png",grain:90}
+            },
+            top:{
+                size:{width:x-(b*2),depth:y,height:b},
+                pos:{x:0,y:(z-b-1)/2,z:0},
+                mat:{url:"/playground/textures/albedo.png",grain:0}
+            },
+            bottom:{
+                size:{width:x-(b*2),depth:y,height:b},
+                pos:{x:0,y:-(z-b-1)/2,z:0},
+                mat:{url:"/playground/textures/albedo.png",grain:0}
+            },
+            back:{
+                size:{width:x-(b*2),depth:b,height:z-(b*2)-1},
+                pos:{x:0,y:0,z:(y-b)/2-20},
+                mat:{url:"/playground/textures/albedo.png",grain:0}
+            },
+            shelf:{
+                size:{width:x-(b*2)-1,depth:y-b-30,height:b},
+                pos:{x:0,y:0,z:-((b+20)/2)+5},
+                mat:{url:"textures/crate.png",grain:45}
+            }
+        }
+    };
+
+    if(parent){
+        parent.dispose();
+    }
+    
+    parent = createPart("Unit",item,false);
+
+    Object.keys(item.child).forEach(function(key) {
+        createPart(key,item.child[key],parent);
     });
-};
 
-assetsManager.load();
+    return item;
+}
+
+createUnit(600,300,900,18);
+
+
+var serialized = BABYLON.SceneSerializer.SerializeMesh(parent, true, true);
+console.log(serialized);

二進制
tests/validation/ReferenceImages/dds.png


二進制
tests/validation/ReferenceImages/tga.png


+ 10 - 0
tests/validation/config.json

@@ -475,6 +475,16 @@
       "title": "Local cubemaps",
       "playgroundId": "#RNASML#4",
       "referenceImage": "local cubemaps.png"
+    },
+    {
+      "title": "TGA",
+      "playgroundId": "#ZI77S7#0",
+      "referenceImage": "tga.png"
+    },
+    {
+      "title": "DDS",
+      "playgroundId": "#ZI77S7#3",
+      "referenceImage": "dds.png"
     }
   ]
 }