瀏覽代碼

Merge pull request #289 from Palmer-JC/master

TOB 1.1
David Catuhe 11 年之前
父節點
當前提交
57ac790dd3

+ 0 - 2
Babylon/Cameras/babylon.camera.ts

@@ -25,8 +25,6 @@
         private _worldMatrix: Matrix;
         private _worldMatrix: Matrix;
         public _postProcesses = new Array<PostProcess>();
         public _postProcesses = new Array<PostProcess>();
         public _postProcessesTakenIndices = [];
         public _postProcessesTakenIndices = [];
-        
-        public _waitingParentId: string;
 
 
         constructor(name: string, public position: Vector3, scene: Scene) {
         constructor(name: string, public position: Vector3, scene: Scene) {
             super(name, scene);
             super(name, scene);

+ 0 - 2
Babylon/Cameras/babylon.targetCamera.ts

@@ -21,8 +21,6 @@
 
 
         public _reset:() => void;
         public _reset:() => void;
 
 
-        public _waitingLockedTargetId:string;
-
         constructor(name:string, position:Vector3, scene:Scene) {
         constructor(name:string, position:Vector3, scene:Scene) {
             super(name, position, scene);
             super(name, position, scene);
         }
         }

+ 79 - 32
Babylon/Loading/Plugins/babylon.babylonFileLoader.ts

@@ -353,14 +353,64 @@
     };
     };
 
 
     var parseCamera = (parsedCamera, scene) => {
     var parseCamera = (parsedCamera, scene) => {
-        var camera = new BABYLON.FreeCamera(parsedCamera.name, BABYLON.Vector3.FromArray(parsedCamera.position), scene);
+        var camera;
+        var position = Vector3.FromArray(parsedCamera.position);
+        var lockedTargetMesh = (parsedCamera.lockedTargetId) ? scene.getMeshByID(parsedCamera.lockedTargetId) : null; // cannot use getLastEntryByID due to FollowCamera
+        
+        if (parsedCamera.type === "AnaglyphArcRotateCamera" || parsedCamera.type === "ArcRotateCamera"){
+            var alpha = parsedCamera.alpha;
+            var beta = parsedCamera.beta;
+            var radius = parsedCamera.radius;
+            if (parsedCamera.type === "AnaglyphArcRotateCamera"){
+                var eye_space = parsedCamera.eye_space;
+                camera = new AnaglyphArcRotateCamera(parsedCamera.name, alpha, beta, radius, lockedTargetMesh, eye_space, scene);
+            }else{
+                camera = new ArcRotateCamera(parsedCamera.name, alpha, beta, radius, lockedTargetMesh, scene);
+            }
+                     
+        }else if (parsedCamera.type === "AnaglyphFreeCamera"){
+            var eye_space = parsedCamera.eye_space;
+            camera = new AnaglyphFreeCamera(parsedCamera.name, position, eye_space, scene);
+            
+        }else if (parsedCamera.type === "DeviceOrientationCamera"){
+            camera = new DeviceOrientationCamera(parsedCamera.name, position, scene);
+            
+        }else if (parsedCamera.type === "FollowCamera"){
+            camera = new FollowCamera(parsedCamera.name, position, scene);
+            camera.heightOffset = parsedCamera.heightOffset;
+            camera.radius = parsedCamera.radius;
+            camera.rotationOffset = parsedCamera.rotationOffset;
+            if (lockedTargetMesh)
+                (<FollowCamera>camera).target = lockedTargetMesh;
+            
+        }else if (parsedCamera.type === "GamepadCamera"){
+            camera = new GamepadCamera(parsedCamera.name, position, scene);
+            
+        }else if (parsedCamera.type === "OculusCamera"){
+            camera = new OculusCamera(parsedCamera.name, position, scene);
+            
+        }else if (parsedCamera.type === "TouchCamera"){
+            camera = new TouchCamera(parsedCamera.name, position, scene);
+            
+        }else if (parsedCamera.type === "VirtualJoysticksCamera"){
+            camera = new VirtualJoysticksCamera(parsedCamera.name, position, scene);
+
+        }else{
+            // Free Camera is not tested for due to some exporters that may not set a 'type'
+            camera = new FreeCamera(parsedCamera.name, position, scene);
+        }
+        
+        // test for lockedTargetMesh & FreeCamera outside of if-else-if nest, since things like GamepadCamera extend FreeCamera
+        if (lockedTargetMesh && camera instanceof FreeCamera)
+             (<FreeCamera>camera).lockedTarget = lockedTargetMesh;
+
         camera.id = parsedCamera.id;
         camera.id = parsedCamera.id;
 
 
         BABYLON.Tags.AddTagsTo(camera, parsedCamera.tags);
         BABYLON.Tags.AddTagsTo(camera, parsedCamera.tags);
 
 
         // Parent
         // Parent
         if (parsedCamera.parentId) {
         if (parsedCamera.parentId) {
-            camera._waitingParentId = parsedCamera.parentId;
+            camera.parentId = parsedCamera.parentId;
         }
         }
 
 
         // Target
         // Target
@@ -370,11 +420,6 @@
             camera.rotation = BABYLON.Vector3.FromArray(parsedCamera.rotation);
             camera.rotation = BABYLON.Vector3.FromArray(parsedCamera.rotation);
         }
         }
 
 
-        // Locked target
-        if (parsedCamera.lockedTargetId) {
-            camera._waitingLockedTargetId = parsedCamera.lockedTargetId;
-        }
-
         camera.fov = parsedCamera.fov;
         camera.fov = parsedCamera.fov;
         camera.minZ = parsedCamera.minZ;
         camera.minZ = parsedCamera.minZ;
         camera.maxZ = parsedCamera.maxZ;
         camera.maxZ = parsedCamera.maxZ;
@@ -553,7 +598,12 @@
     };
     };
 
 
     var parseMesh = (parsedMesh, scene, rootUrl) => {
     var parseMesh = (parsedMesh, scene, rootUrl) => {
-        var mesh = new BABYLON.Mesh(parsedMesh.name, scene);
+         var mesh : any;
+        if (parsedMesh.isAutomaton){
+            mesh = new BABYLON.Automaton(parsedMesh.name, scene);
+        }else{
+            mesh = new BABYLON.Mesh(parsedMesh.name, scene);
+        }
         mesh.id = parsedMesh.id;
         mesh.id = parsedMesh.id;
 
 
         BABYLON.Tags.AddTagsTo(mesh, parsedMesh.tags);
         BABYLON.Tags.AddTagsTo(mesh, parsedMesh.tags);
@@ -683,6 +733,20 @@
             mesh.layerMask = 0xFFFFFFFF;
             mesh.layerMask = 0xFFFFFFFF;
         }
         }
 
 
+        // shape key groups
+        if (parsedMesh.shapeKeyGroups) {
+            var shapeKeyGroup : BABYLON.ShapeKeyGroup;
+            for (var index = 0; index < parsedMesh.shapeKeyGroups.length; index++) {
+                var parsedShapeKeyGroup = parsedMesh.shapeKeyGroups[index];
+                shapeKeyGroup = new BABYLON.ShapeKeyGroup(mesh, parsedShapeKeyGroup.group, parsedShapeKeyGroup.affectedIndices, parsedShapeKeyGroup.basisState);
+                for (var stateIdx = 0; stateIdx < parsedShapeKeyGroup.states.length; stateIdx++) {
+                    var parsedState = parsedShapeKeyGroup.states[stateIdx];
+                    shapeKeyGroup.addShapeKey(parsedState.stateName, parsedState.state);
+                }
+                mesh.addShapeKeyGroup(shapeKeyGroup);
+            }
+        }
+        
         // Instances
         // Instances
         if (parsedMesh.instances) {
         if (parsedMesh.instances) {
             for (var index = 0; index < parsedMesh.instances.length; index++) {
             for (var index = 0; index < parsedMesh.instances.length; index++) {
@@ -1031,16 +1095,6 @@
                 parseLight(parsedLight, scene);
                 parseLight(parsedLight, scene);
             }
             }
 
 
-            // Cameras
-            for (index = 0; index < parsedData.cameras.length; index++) {
-                var parsedCamera = parsedData.cameras[index];
-                parseCamera(parsedCamera, scene);
-            }
-
-            if (parsedData.activeCameraID) {
-                scene.setActiveCameraByID(parsedData.activeCameraID);
-            }
-
             // Materials
             // Materials
             if (parsedData.materials) {
             if (parsedData.materials) {
                 for (index = 0; index < parsedData.materials.length; index++) {
                 for (index = 0; index < parsedData.materials.length; index++) {
@@ -1146,21 +1200,14 @@
                 parseMesh(parsedMesh, scene, rootUrl);
                 parseMesh(parsedMesh, scene, rootUrl);
             }
             }
 
 
-            // Connecting cameras parents and locked target
-            for (index = 0; index < scene.cameras.length; index++) {
-                var camera = scene.cameras[index];
-                if (camera._waitingParentId) {
-                    camera.parent = scene.getLastEntryByID(camera._waitingParentId);
-                    delete camera._waitingParentId;
-                }
+            // Cameras
+            for (index = 0; index < parsedData.cameras.length; index++) {
+                var parsedCamera = parsedData.cameras[index];
+                parseCamera(parsedCamera, scene);
+            }
 
 
-                if (camera instanceof BABYLON.FreeCamera) {
-                    var freecamera = <FreeCamera>camera;
-                    if (freecamera._waitingLockedTargetId) {
-                        freecamera.lockedTarget = scene.getLastEntryByID(freecamera._waitingLockedTargetId);
-                        delete freecamera._waitingLockedTargetId;
-                    }
-                }
+            if (parsedData.activeCameraID) {
+                scene.setActiveCameraByID(parsedData.activeCameraID);
             }
             }
 
 
             // Particles Systems
             // Particles Systems

二進制
Exporters/Blender/blender-test/armature.png


二進制
Exporters/Blender/blender-test/automaton.png


+ 64 - 0
Exporters/Blender/blender-test/automaton_JSON.html

@@ -0,0 +1,64 @@
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>automaton</title>
+    <!-- edit path - name of babylon library as required -->
+    <script src="./lib/babylon.js"></script>
+    <script src="./lib/automaton_common.js"></script>
+    <style>
+         html, body   { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } 
+         #renderCanvas{ width: 100%; height: 100%; } 
+         #button      {
+			color: white; background-color: Dimgray;
+			font-size: 14pt; font-weight: bold;
+			padding-left:4pt; padding-right:4pt;
+			
+			border: black outset 2pt; line-height: 2em;
+			cursor: pointer;
+		}     
+    </style>
+</head>
+<body>
+	<div id="buttonbar" style="background-color: Darkorange;">
+		<span id="button" onclick="pausePlay()"> Pause - Play </span>
+		<span id="button" onclick="queueAnimation()"> Go Again </span>
+	</div>
+<canvas id="renderCanvas"></canvas>
+<script>
+    if (BABYLON.Engine.isSupported()) {
+        var canvas = document.getElementById("renderCanvas");
+        var engine = new BABYLON.Engine(canvas, true);
+        console.log("Babylon version:  " + BABYLON.Engine.Version);
+        
+        var url = "./TOB-out/"; // edit when .babylon / texture files in a different dir than html
+        BABYLON.SceneLoader.Load(url, "automaton.babylon", engine, 
+            function (newScene) {
+                newScene.executeWhenReady(function () {
+                	prep(newScene);
+                	queueAnimation();
+                    // Attach camera to canvas inputs
+                    newScene.activeCamera.attachControl(canvas);
+
+                    // Once the scene is loaded, register a render loop
+                    engine.runRenderLoop(function() {
+                        newScene.render();
+                    });
+                });
+            },
+            function (progress) {
+                // To do: give progress feedback to user
+            }
+        );
+    }else{
+        alert("WebGL not supported in this browser.\n\n" + 
+              "If in Safari browser, check 'Show Develop menu in menu bar' on the Advanced tab of Preferences.  " +
+              "On the 'Develop' menu, check the 'Enable WebGL' menu item.");
+    }
+
+    //Resize
+    window.addEventListener("resize", function () {
+        engine.resize();
+    });
+</script>
+</body>
+</html>

+ 56 - 0
Exporters/Blender/blender-test/automaton_inline.html

@@ -0,0 +1,56 @@
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>automaton</title>
+    <!-- edit path - name of babylon library as required -->
+    <script src="./lib/babylon.js"></script>
+    <script src="./TOB-out/automaton.js"></script>
+    <script src="./lib/automaton_common.js"></script>
+    <style>
+         html, body   { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } 
+         #renderCanvas{ width: 100%; height: 100%; } 
+         #button      {
+			color: white; background-color: Dimgray;
+			font-size: 14pt; font-weight: bold;
+			padding-left:4pt; padding-right:4pt;
+			
+			border: black outset 2pt; line-height: 2em;
+			cursor: pointer;
+		}     
+    </style>
+</head>
+<body>
+	<div id="buttonbar" style="background-color: Darkorange;">
+		<span id="button" onclick="pausePlay()"> Pause - Play </span>
+		<span id="button" onclick="queueAnimation()"> Go Again </span>
+	</div>
+<canvas id="renderCanvas"></canvas>
+<script>
+    if (BABYLON.Engine.isSupported()) {
+        var canvas = document.getElementById("renderCanvas");
+        var engine = new BABYLON.Engine(canvas, true);
+        console.log("Babylon version:  " + BABYLON.Engine.Version);
+
+        var scene = new BABYLON.Scene(engine);
+        materialsRootDir = "./TOB-out"; // edit when texture files in a different dir than html
+        automaton.initScene(scene, materialsRootDir);
+    	prep(scene);
+    	queueAnimation();
+    	
+        scene.activeCamera.attachControl(canvas);
+        engine.runRenderLoop(function () {
+            scene.render();
+        });
+    }else{
+        alert("WebGL not supported in this browser.\n\n" + 
+              "If in Safari browser, check 'Show Develop menu in menu bar' on the Advanced tab of Preferences.  " +
+              "On the 'Develop' menu, check the 'Enable WebGL' menu item.");
+    }
+
+    //Resize
+    window.addEventListener("resize", function () {
+        engine.resize();
+    });
+</script>
+</body>
+</html>

二進制
Exporters/Blender/blender-test/blender-in/armature.blend


二進制
Exporters/Blender/blender-test/blender-in/automaton.blend


二進制
Exporters/Blender/blender-test/blender-in/mesh_parent.blend


二進制
Exporters/Blender/blender-test/blender-in/multi_group.blend


二進制
Exporters/Blender/blender-test/blender-in/tableCloth.jpg


二進制
Exporters/Blender/blender-test/camera_anim.png


+ 70 - 0
Exporters/Blender/blender-test/index.html

@@ -0,0 +1,70 @@
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>Tower of Babel Regression Testing</title>
+    <style>
+         body {font:100% Verdana,Tahoma,Arial,sans-serif; color:#303030; line-height:1.3em;}
+         p {margin:0 0 18px 8px;text-align:justify;}
+         table.my-tbl {margin:0 0 18px 8px;background-color:#808080;}
+         th.my-tbl {text-align:left; background-color:silver;}
+         td.my-tbl {background-color: #FBFBFB;}
+    </style>
+</head>
+<body>
+<p>This directory is for regression testing of the Tower of Babel.  It is assumed that the add-in has already been installed & enabled in Blender.</p>
+
+<p>Place the version of babylon.js you wish to use in the 'lib' directory.  The earliest supported is 1.14.  All of the html files reference it as 'babylon.js' to avoid having to edit them.  FYI, the htmls write a message into the browser console, listing the version, in-case you forget.  This is also for any scripts which should be common across a _JSON & _inline.html pair.</p>
+
+<p>The 'blender-in' directory holds each of the .blend files.  The name reflects the main thing being tested.  Refer to the table below, for a more complete list of features being tested.</p>
+
+<p>The 'TOB-out' directory is where you should direct the .babylon/.js/.ts/.html/.log output from Blender.  This directory is isolated, so that everything can easily be deleted.  This ensures that you are not actually running with stuff exported previously.  This directory is empty in Github (except for a .gitignore), so each .html  in this directory will not run unless, you open Blender with the respective .blend & export to this directory.</p>
+
+<p>Speaking of htmls, this directory has a xxx_JSON.html & xxx_inline.html for each .blend.</p>
+
+<p>Here is the list of tests & secondary features they test:</p>
+<table class="my-tbl">
+        <tr>
+            <th class="my-tbl">.blend</th>
+            <th class="my-tbl">inline</th>
+            <th class="my-tbl">JSON</th>
+            <th class="my-tbl">Camera</th>
+            <th class="my-tbl">Secondary features, notes</th>
+        </tr>
+        <tr>
+            <td class="my-tbl" >armature</td>
+            <td class="my-tbl" align="center"><a href="armature_inline.html" ><img src="armature.png"   alt=""></img></a></td>
+            <td class="my-tbl" align="center"><a href="armature_JSON.html"><img src="armature.png" alt=""></img></a></td>
+            <td class="my-tbl" >Device Orientation</td>
+            <td class="my-tbl">skeletal animation</td>
+        </tr>
+        <tr>
+            <td class="my-tbl" >automaton</td>
+            <td class="my-tbl" align="center"><a href="automaton_inline.html" ><img src="automaton.png"   alt=""></img></a></td>
+            <td class="my-tbl" align="center"><a href="automaton_JSON.html"><img src="automaton.png" alt=""></img></a></td>
+            <td class="my-tbl" >Free, Locked Target</td>
+            <td class="my-tbl">Single ShapeKeyGroup, function() part of a Event Series, back-face culling, instance level pause / resume</td>
+        </tr>
+        <tr>
+            <td class="my-tbl" >camera_anim</td>
+            <td class="my-tbl" align="center"><a href="camera_anim_inline.html" ><img src="camera_anim.png"   alt=""></img></a></td>
+            <td class="my-tbl" align="center"><a href="camera_anim_JSON.html"><img src="camera_anim.png" alt=""></img></a></td>
+            <td class="my-tbl" >Free</td>
+            <td class="my-tbl">Multi-materials, textures</td>
+        </tr>
+        <tr>
+            <td class="my-tbl" >mesh_parent</td>
+            <td class="my-tbl" align="center"><a href="mesh_parent_inline.html" ><img src="mesh_parent.png"   alt=""></img></a></td>
+            <td class="my-tbl" align="center"><a href="mesh_parent_JSON.html"><img src="mesh_parent.png" alt=""></img></a></td>
+            <td class="my-tbl" >Game Pad</td>
+            <td class="my-tbl">Shadows, animation to prove parenthood</td>
+        </tr>
+        <tr>
+            <td class="my-tbl" >multi_group</td>
+            <td class="my-tbl" align="center"><a href="multi_group_inline.html" ><img src="multi_group.png"   alt=""></img></a></td>
+            <td class="my-tbl" align="center"><a href="multi_group_JSON.html"><img src="multi_group.png" alt=""></img></a></td>
+            <td class="my-tbl" >Arc Rotation</td>
+            <td class="my-tbl">Multiple ShapeKeyGroup, independent & multi-group Event Series, AutomatonEventSeriesAction triggered through pick, normals, Group Conflicts, back-face culling, system wide pause / resume</td>
+        </tr>
+    </table>
+</body>
+</html>

+ 146 - 0
Exporters/Blender/blender-test/lib/automaton_common.js

@@ -0,0 +1,146 @@
+/**
+ *  central location for automaton script, called by automaton_JSON.html & automaton_inline.html 
+ */
+var scene; // for later camera jiggle in reset
+var cloth;
+var originalPos;
+var originalRot;
+var inAnim = false;
+
+// a custom non-linear Pace   completionRatios    durationRatios
+var hiccup = new BABYLON.Pace([.1, .8, .6, 1.0],[.25, .6, .8, 1.0]);
+
+// The creation of Deformations & Event Series need only be done once; requires no actual mesh; queue as often as you wish to run
+/**
+ * Deformation is a sub-class of ReferenceDeformation, where the referenceStateName is Fixed to "BASIS"
+ * @param {string} shapeKeyGroupName -  Used by Automaton to place in the correct ShapeKeyGroup queue(s).
+ * @param {string} endStateName - Name of state key to deform to
+ * @param {number} milliDuration - The number of milli seconds the deformation is to be completed in
+ * @param {number} millisBefore - Fixed wait period, once a syncPartner (if any) is also ready (default 0)
+ * @param {number} endStateRatio - ratio of the end state to be obtained from reference state: -1 (mirror) to 1 (default 1)
+ * @param {Vector3} movePOV - Mesh movement relative to its current position/rotation to be performed at the same time (default null)
+ *                  right-up-forward
+ * @param {Vector3} rotatePOV - Incremental Mesh rotation to be performed at the same time (default null)
+ *                  flipBack-twirlClockwise-tiltRight
+ * @param {Pace} pace - Any Object with the function: getCompletionMilestone(currentDurationRatio) (default Pace.LINEAR)
+ */
+//                                          Shape                                end-                                     flip back -
+//                                          Key                     dur-         state                                    twirl clockwise -
+//                                          Group          State    ation  wait  ratio  right-up-forward                  tilt right                        pace
+ var stretching  = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  900, 500,   0.9), 
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED", 1500,   0,  -0.1, null                             , null                            , hiccup) 
+                   ];//  illustrates the millisBefore parameter & the non-linear pace
+ 
+ var hardFlap    = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  800,   0,   0.1),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  300,   0,  -0.2, new BABYLON.Vector3( 0,   2,  0))  
+                   ];// when your horizontal, up is really up; not all deformations need the same movePOV
+ 
+ var away        = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  200,   0,   0.3, new BABYLON.Vector3( 0, 1.5, 3.3)),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  400,   0,  -0.2, new BABYLON.Vector3( 0, 1.5, 6.7)),
+                   ];// climbing forward; series repeat acceleration applied when queued to avoid jerk start
+                     // forward velocity: (3.3 + 6.7) / (200 + 400) = 0.016666 units / milli
+ 
+ var bankRight   = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  750,   0,   0.1, new BABYLON.Vector3(-2,   0, 16), new BABYLON.Vector3(0, .4,  .2)),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  750,   0,  -0.2, new BABYLON.Vector3(-2,   0, 16), new BABYLON.Vector3(0, .4,  .2))  
+                   ];// twirl clockwise while tilting right; going left while on your right side is really up
+                     // forward velocity: (16 + 16) / (750 + 750) = 0.021333 units / milli
+ 
+ var backStretch = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  450,   0,   0.3, new BABYLON.Vector3( 0,   0, 12)),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  450,   0,  -0.2, new BABYLON.Vector3( 0,   0, 12))  
+                   ];// need to make range (0.3 to -0.2), same as away, so can be seen so far away from camera
+                     // forward velocity: (12 + 12) / (450 + 450) = 0.026666 units / milli
+ 
+ var turnRight   = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  450,   0,  -0.1, new BABYLON.Vector3( 3,  0,  24), new BABYLON.Vector3(0, .6,   0)),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  450,   0,  -0.2, new BABYLON.Vector3( 3,  0,  24), new BABYLON.Vector3(0, .6,   0)) 
+                   ];// twirl without aditional tilt; going right which starts to make it go down;
+                     // forward velocity: (24 + 24) / (450 + 450) = 0.053333 units / milli
+ 
+ var tiltToHoriz = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  250,   0,   0.3, new BABYLON.Vector3( 0,  -1,  8), new BABYLON.Vector3(0,  0, -.2)),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  250,   0,  -0.1, new BABYLON.Vector3( 0,  -1,  8), new BABYLON.Vector3(0,  0, -.2))  
+                   ];// reverse the tilt from 'transRight' and 'bankRight'; down hill
+                     // forward velocity: (8 + 8) / (250 + 250) = 0.032 units / milli
+ 
+ var woosh       = [new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  400,   0  , 0.3, new BABYLON.Vector3( 12, -1, 25)),
+                    new BABYLON.Deformation("ENTIRE MESH", "DRAPED",  400,   0,  -0.1, new BABYLON.Vector3( 12, -1, 25))  
+                   ];// cross over right / down hill; eat your heart out Roddenberry
+                     // forward velocity: (25 + 25) / (400 + 400) = 0.0625 units / milli
+ 
+                    // using the version of Deformation which does not default on the reference state, here "DRAPED", to going back to 'BASIS'
+ var reset       = [new BABYLON.ReferenceDeformation("ENTIRE MESH", "DRAPED", "BASIS",  1, 0, 1), 
+                    function(){
+                         cloth.position = originalPos;
+                         cloth.rotation = originalRot;
+                         scene.activeCamera._getViewMatrix(); // jiggle camera to re-lock on target,  also done by ShapeKeyGroup.incrementallyDeform()
+                       	 var report = cloth.getTrackingReport();
+                       	 window.alert(report);
+                       	 console.log(report);
+                         setInAnim(false);
+                    } 
+                   ];                        
+ /**
+  * @param {Array} _eventSeries - Elements must either be a ReferenceDeformation, Action, or function.  Min # of Deformations: 1
+  * @param {number} _nRepeats - Number of times to run through series elements.  There is sync across runs. (Default 1)
+  * @param {number} _initialWallclockProrating - The factor to multiply the duration of a Deformation before passing to a
+  *                 ShapeKeyGroup.  Amount is decreased or increased across repeats, so that it is 1 for the final repeat.
+  *                 Facilitates acceleration when > 1, & deceleration when < 1.  (Default 1)
+  * @param {string} _debug - Write progress messages to console when true (Default false)
+  */
+  //                                                                        first run
+  //                                                  Series    nRepeats    prorating             debug    
+var stretchSeries     = new BABYLON.AutomatonEventSeries(stretching , 2);
+var hardFlapSeries    = new BABYLON.AutomatonEventSeries(hardFlap   , 4);                        
+var awaySeries        = new BABYLON.AutomatonEventSeries(away       , 5     , 2.0                , true); // demo extra message     
+var bankRightSeries   = new BABYLON.AutomatonEventSeries(bankRight  , 3     , 0.021333 / 0.016666);   
+var backStretchSeries = new BABYLON.AutomatonEventSeries(backStretch, 2     , 0.026666 / 0.021333);
+var turnRightSeries   = new BABYLON.AutomatonEventSeries(turnRight  , 2     , 0.053333 / 0.026666);         
+var tiltToHorizSeries = new BABYLON.AutomatonEventSeries(tiltToHoriz, 3);            
+var wooshSeries       = new BABYLON.AutomatonEventSeries(woosh      , 3);            
+var resetSeries       = new BABYLON.AutomatonEventSeries(reset);            	
+
+function prep(sceneArg){
+	scene = sceneArg;
+	cloth = scene.getMeshByID("Cloth");
+	cloth.debug = true;
+	originalPos = cloth.position.clone();
+	originalRot = cloth.rotation.clone();
+
+	var entireGrp = cloth.getShapeKeyGroup("ENTIRE MESH");
+	entireGrp.mirrorAxisOnY(); // mirror on Y, so wings flapping up past horizontal created, using negative end state ratios 
+	
+	// set test to false to try to compare performance in final reports
+	if (1 === 1){
+    	entireGrp.addDerivedKey("BASIS", "DRAPED", -0.2);	
+    	entireGrp.addDerivedKey("BASIS", "DRAPED", -0.1);	
+    	entireGrp.addDerivedKey("BASIS", "DRAPED",  0.1);
+    	entireGrp.addDerivedKey("BASIS", "DRAPED",  0.3);
+    	entireGrp.addDerivedKey("BASIS", "DRAPED",  0.9);
+	}	
+}
+
+function setInAnim(inAnimArg){inAnim = inAnimArg;}
+
+function pausePlay() {
+	console.log("Requesting " + (cloth.isPaused() ? "resume" : "pause"));
+	// test instance pause-play
+	if (cloth.isPaused()) cloth.resumePlay();
+	else cloth.pausePlay();
+}
+
+function queueAnimation(){
+	if (inAnim){
+		console.log("queueAnimation while in progress ignored.");
+		return;
+	}
+	setInAnim(true);
+	 
+	 // the following calls return immediately, due to being put on the appropriate ShapeKeyGroup(s) queues
+     cloth.queueEventSeries(stretchSeries);
+     cloth.queueEventSeries(hardFlapSeries);                        
+     cloth.queueEventSeries(awaySeries);
+     cloth.queueEventSeries(bankRightSeries);   
+     cloth.queueEventSeries(backStretchSeries);
+     cloth.queueEventSeries(turnRightSeries);         
+     cloth.queueEventSeries(tiltToHorizSeries);            
+     cloth.queueEventSeries(wooshSeries);            
+     cloth.queueEventSeries(resetSeries);            
+}

+ 0 - 1
Exporters/Blender/blender-test/lib/mesh_parent_common.js

@@ -43,7 +43,6 @@ function animate(scene){
  }
  }
  
  
  function orphanConceive() {
  function orphanConceive() {
-	 console.log("in orphan ");
 	 parenting = !parenting;
 	 parenting = !parenting;
      for (index = 0; index < meshes.length; index++) {
      for (index = 0; index < meshes.length; index++) {
          var mesh = meshes[index];
          var mesh = meshes[index];

+ 134 - 0
Exporters/Blender/blender-test/lib/multi_group_common.js

@@ -0,0 +1,134 @@
+/**
+ *  central location for multi_group script, called by multi_group_JSON.html & multi_group_inline.html 
+ */
+
+var plane;
+function prep(scene){
+	plane = scene.getMeshByID("Plane");
+	plane.debug = true;
+
+	// pretty important when there are multiple groups moving at the same time to pre-define your keys
+	var leftGrp = plane.getShapeKeyGroup("LEFT");
+	leftGrp.mirrorAxisOnY();   // mirror on Y, so bump can also be a depression, using negative end state ratios
+	leftGrp.addDerivedKey("BASIS", "BUMP", -.2);	
+	leftGrp.addDerivedKey("BASIS", "BUMP",  .2);	
+
+	var middleGrp = plane.getShapeKeyGroup("MIDDLE");
+	middleGrp.mirrorAxisOnY(); // mirror on Y, so bump can also be a depression, using negative end state ratios
+	middleGrp.addDerivedKey("BASIS", "BUMP", -.2);	
+	middleGrp.addDerivedKey("BASIS", "BUMP",  .2);	
+
+	var rightGrp = plane.getShapeKeyGroup("RIGHT");
+	rightGrp.mirrorAxisOnY();  // mirror on Y, so bump can also be a depression, using negative end state ratios
+	rightGrp.addDerivedKey("BASIS", "BUMP", -.2);	
+	rightGrp.addDerivedKey("BASIS", "BUMP",  .2);	
+
+	// testing of AutomatonEventSeriesAction, trigger on a pick
+	var reset = [new BABYLON.ReferenceDeformation("LEFT"   ,"BUMP", "BASIS",  1, 0, 1),
+	             new BABYLON.ReferenceDeformation("RIGHT"  ,"BUMP", "BASIS",  1, 0, 1),
+	             new BABYLON.ReferenceDeformation("MIDDLE" ,"BUMP", "BASIS",  1, 0, 1),
+	             ];
+	var resetSeries = new BABYLON.AutomatonEventSeries(reset);
+	var resetAction = new BABYLON.AutomatonEventSeriesAction(BABYLON.ActionManager.OnPickTrigger, plane, resetSeries);
+	
+	plane.actionManager = new BABYLON.ActionManager(scene);
+	plane.actionManager.registerAction(resetAction);
+		
+}
+
+function left() {
+	boing("LEFT");
+}
+
+function middle() {
+	boing("MIDDLE");
+}
+
+function right() {
+	boing("RIGHT");
+}
+
+function boing(group){
+    /**
+     * sub-class of ReferenceDeformation, where the referenceStateName is Fixed to "BASIS"
+     * @param {string} shapeKeyGroupName -  Used by Automaton to place in the correct ShapeKeyGroup queue(s).
+     * @param {string} endStateName - Name of state key to deform to
+     * @param {number} milliDuration - The number of milli seconds the deformation is to be completed in
+     * @param {number} millisBefore - Fixed wait period, once a syncPartner (if any) is also ready (default 0)
+     * @param {number} endStateRatio - ratio of the end state to be obtained from reference state: -1 (mirror) to 1 (default 1)
+     * @param {Vector3} movePOV - Mesh movement relative to its current position/rotation to be performed at the same time (default null)
+     *                  right-up-forward
+     * @param {Vector3} rotatePOV - Incremental Mesh rotation to be performed at the same time (default null)
+     *                  flipBack-twirlClockwise-tiltRight
+     * @param {Pace} pace - Any Object with the function: getCompletionMilestone(currentDurationRatio) (default Pace.LINEAR)
+     */
+    //                                          Shape                               end-                                  flip back
+    //                                          Key                     dur-        state                                 twirl clockwise
+    //                                          Group          State    ation  wait ratio  right-up-forward               tilt right               pace
+    var stretch      = [new BABYLON.Deformation(group        ,"BUMP" ,  750,    0,   1.0), 
+                        new BABYLON.Deformation(group        ,"BUMP" ,  150,  100,   -.2)
+                       ];
+    
+    var vibrate      = [new BABYLON.Deformation(group        ,"BUMP" ,   75,    0,    .2), 
+                        new BABYLON.Deformation(group        ,"BUMP" ,   75,    0,   -.2),
+                       ];
+                     
+	var reset        = [new BABYLON.ReferenceDeformation(group  ,"BUMP", "BASIS",  50, 0, 1),
+                       ];
+	
+    plane.queueEventSeries(new BABYLON.AutomatonEventSeries(stretch));
+    plane.queueEventSeries(new BABYLON.AutomatonEventSeries(vibrate, 3, 0.8));
+    plane.queueEventSeries(new BABYLON.AutomatonEventSeries(reset));
+}
+
+function drumming() {
+	var dur = 75;
+	
+	// note right "BUMP" is in the opposite direction of left "BUMP", so down is > 0
+	var rightDown       = new BABYLON.ReferenceDeformation("RIGHT", "BASIS", "BUMP",  dur, 300,  .2); // starts too fast, & each subsequent down also needs to wait
+   	var rightLastDown   = new BABYLON.ReferenceDeformation("RIGHT", "BASIS", "BUMP",  dur, 300,  .2); // in sync with left, but delay for it after both are started
+	var rightUp         = new BABYLON.ReferenceDeformation("RIGHT", "BASIS", "BUMP",  dur,   0, -.2);
+	var rightHorizontal = new BABYLON.ReferenceDeformation("RIGHT", "BUMP", "BASIS",  dur,   0,   1);
+	var rightStall      = new BABYLON.ReferenceDeformation("RIGHT", "BUMP", "BASIS",    1, 150,   1); // same as rightHorizontal, so nothing happens (less CPU to wait)
+	
+   	var leftDown        = new BABYLON.ReferenceDeformation("LEFT" , "BASIS", "BUMP",  dur,   0, -.2);
+   	var leftUp          = new BABYLON.ReferenceDeformation("LEFT" , "BASIS", "BUMP",  dur,   0,  .2);
+	var leftHorizontal  = new BABYLON.ReferenceDeformation("LEFT" , "BUMP", "BASIS",  dur,   0,   1);
+   	
+   	// make last down beats a sync pair
+   	leftDown     .setSyncPartner(rightLastDown);
+   	rightLastDown.setSyncPartner(leftDown     );
+   	
+   	var series = [
+   	              // even though left is first in the series, sync will delay all lefts till rightLastDown is ready
+                  leftDown     , leftUp , leftHorizontal,
+   	              
+                  rightDown    , rightUp, rightHorizontal,
+                  rightDown    , rightUp, rightHorizontal,
+                  rightDown    , rightUp, rightHorizontal,
+                  rightLastDown, rightUp, rightHorizontal, rightStall
+   	             ];
+   	
+    plane.queueEventSeries(new BABYLON.AutomatonEventSeries(series, 3));
+}
+
+function conflict() {	
+	              // all three start at the same time, use delays for demo
+   	var series = [new BABYLON.Deformation("MIDDLE", "BUMP",  500, 1600,  1.0),
+   	              new BABYLON.Deformation("RIGHT" , "BUMP",  500,    0,  1.0),
+   	              new BABYLON.Deformation("LEFT"  , "BUMP",  500,    0,  1.0),
+   	              // functions and Actions run on the queue of the first series, in this case 'MIDDLE'
+                  function(){
+                      window.alert("Overlapping Shape Key Groups can exist, but it is up to the application programmer to manage, unlike here.\n\nAction test:  Pick mesh to reset");
+                  } 
+   	             ];
+
+    plane.queueEventSeries(new BABYLON.AutomatonEventSeries(series));
+}
+
+function pausePlay() {
+	console.log("Requesting " + (BABYLON.Automaton.isSystemPaused() ? "resume" : "pause"));
+	// test Automation system wide pause-play
+	if (BABYLON.Automaton.isSystemPaused()) BABYLON.Automaton.resumeSystem();
+   	else BABYLON.Automaton.pauseSystem();
+}

二進制
Exporters/Blender/blender-test/mesh_parent.png


二進制
Exporters/Blender/blender-test/multi_group.png


+ 67 - 0
Exporters/Blender/blender-test/multi_group_JSON.html

@@ -0,0 +1,67 @@
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>multi_group</title>
+    <!-- edit path - name of babylon library as required -->
+    <script src="./lib/babylon.js"></script>
+    <script src="./lib/multi_group_common.js"></script>
+    <style>
+         html, body   { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } 
+         #renderCanvas{ width: 100%; height: 100%; } 
+         #button      {
+			color: white; background-color: Dimgray;
+			font-size: 14pt; font-weight: bold;
+			padding-left:4pt; padding-right:4pt;
+			
+			border: black outset 2pt; line-height: 2em;
+			cursor: pointer;
+		}     
+    </style>
+</head>
+<body>
+	<div id="buttonbar" style="background-color: Darkorange;">
+		<span id="button" onclick="pausePlay()"> Pause - Play </span>
+		<span id="button" onclick="left()"> Left </span>
+		<span id="button" onclick="middle()"> Middle </span>
+		<span id="button" onclick="right()"> Right </span>
+		<span id="button" onclick="drumming()"> Drumming </span>
+		<span id="button" onclick="conflict()"> Conflict </span>
+	</div>
+<canvas id="renderCanvas"></canvas>
+<script>
+    if (BABYLON.Engine.isSupported()) {
+        var canvas = document.getElementById("renderCanvas");
+        var engine = new BABYLON.Engine(canvas, true);
+        console.log("Babylon version:  " + BABYLON.Engine.Version);
+
+        var url = "./TOB-out/"; // edit when .babylon / texture files in a different dir than html
+        BABYLON.SceneLoader.Load(url, "multi_group.babylon", engine, 
+            function (newScene) {
+                newScene.executeWhenReady(function () {
+                	prep(newScene);
+                    // Attach camera to canvas inputs
+                    newScene.activeCamera.attachControl(canvas);
+
+                    // Once the scene is loaded, register a render loop
+                   engine.runRenderLoop(function () {
+                        newScene.render();
+                    });
+                });
+            },
+            function (progress) {
+                // To do: give progress feedback to user
+            }
+        );
+    }else{
+        alert("WebGL not supported in this browser.\n\n" + 
+              "If in Safari browser, check 'Show Develop menu in menu bar' on the Advanced tab of Preferences.  " +
+              "On the 'Develop' menu, check the 'Enable WebGL' menu item.");
+    }
+
+    //Resize
+    window.addEventListener("resize", function () {
+        engine.resize();
+    });
+</script>
+</body>
+</html>

+ 58 - 0
Exporters/Blender/blender-test/multi_group_inline.html

@@ -0,0 +1,58 @@
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>multi_group</title>
+    <!-- edit path - name of babylon library as required -->
+    <script src="./lib/babylon.js"></script>
+    <script src="./TOB-out/multi_group.js"></script>
+    <script src="./lib/multi_group_common.js"></script>
+    <style>
+         html, body   { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } 
+         #renderCanvas{ width: 100%; height: 100%; } 
+         #button      {
+			color: white; background-color: Dimgray;
+			font-size: 14pt; font-weight: bold;
+			padding-left:4pt; padding-right:4pt;
+			
+			border: black outset 2pt; line-height: 2em;
+			cursor: pointer;
+		}     
+    </style>
+</head>
+<body>
+	<div id="buttonbar" style="background-color: Darkorange;">
+		<span id="button" onclick="pausePlay()"> Pause - Play </span>
+		<span id="button" onclick="left()"> Left </span>
+		<span id="button" onclick="middle()"> Middle </span>
+		<span id="button" onclick="right()"> Right </span>
+		<span id="button" onclick="drumming()"> Drumming </span>
+		<span id="button" onclick="conflict()"> Conflict </span>
+	</div>
+<canvas id="renderCanvas"></canvas>
+<script>
+    if (BABYLON.Engine.isSupported()) {
+        var canvas = document.getElementById("renderCanvas");
+        var engine = new BABYLON.Engine(canvas, true);
+        console.log("Babylon version:  " + BABYLON.Engine.Version);
+
+        var scene = new BABYLON.Scene(engine);
+        materialsRootDir = "."; // edit when texture files in a different dir than html
+        multi_group.initScene(scene, materialsRootDir);
+        prep(scene);
+        scene.activeCamera.attachControl(canvas);
+        engine.runRenderLoop(function () {
+            scene.render();
+        });
+    }else{
+        alert("WebGL not supported in this browser.\n\n" + 
+              "If in Safari browser, check 'Show Develop menu in menu bar' on the Advanced tab of Preferences.  " +
+              "On the 'Develop' menu, check the 'Enable WebGL' menu item.");
+    }
+
+    //Resize
+    window.addEventListener("resize", function () {
+        engine.resize();
+    });
+</script>
+</body>
+</html>

+ 146 - 51
Exporters/Blender/io_tower_of_babel.py

@@ -1,7 +1,7 @@
 bl_info = {
 bl_info = {
     'name': 'Tower of Babel',
     'name': 'Tower of Babel',
     'author': 'David Catuhe, Jeff Palmer',
     'author': 'David Catuhe, Jeff Palmer',
-    'version': (1, 0, 1),
+    'version': (1, 1, 0),
     'blender': (2, 69, 0),
     'blender': (2, 69, 0),
     'location': 'File > Export > Tower of Babel [.babylon + .js + .ts + .html(s)]',
     'location': 'File > Export > Tower of Babel [.babylon + .js + .ts + .html(s)]',
     'description': 'Produce Babylon scene file (.babylon), Translate to inline JavaScript & TypeScript',
     'description': 'Produce Babylon scene file (.babylon), Translate to inline JavaScript & TypeScript',
@@ -80,7 +80,19 @@ CONE_IMPOSTER = 6
 CYLINDER_IMPOSTER = 7
 CYLINDER_IMPOSTER = 7
 CONVEX_HULL_IMPOSTER = 8
 CONVEX_HULL_IMPOSTER = 8
 
 
-# used in Light constructor never formally defined in Babylon, but used in babylonFileLoader
+# camera class names, never formally defined in Babylon, but used in babylonFileLoader
+ANAGLYPH_ARC_CAM = 'AnaglyphArcRotateCamera' 
+ANAGLYPH_FREE_CAM = 'AnaglyphFreeCamera'
+ARC_ROTATE_CAM = 'ArcRotateCamera' 
+DEV_ORIENT_CAM = 'DeviceOrientationCamera'
+FOLLOW_CAM = 'FollowCamera'
+FREE_CAM = 'FreeCamera' 
+GAMEPAD_CAM = 'GamepadCamera'
+OCULUS_CAM = 'OculusCamera'
+TOUCH_CAM = 'TouchCamera'
+V_JOYSTICKS_CAM = 'VirtualJoysticksCamera'
+
+# used in Light constructor, never formally defined in Babylon, but used in babylonFileLoader
 POINT_LIGHT = 0
 POINT_LIGHT = 0
 DIRECTIONAL_LIGHT = 1
 DIRECTIONAL_LIGHT = 1
 SPOT_LIGHT = 2
 SPOT_LIGHT = 2
@@ -136,7 +148,7 @@ class TowerOfBabel(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
     export_typeScript = bpy.props.BoolProperty(
     export_typeScript = bpy.props.BoolProperty(
         name="Export Typescript (.ts) File",
         name="Export Typescript (.ts) File",
         description="Produce an inline TypeScript (xxx.ts) File",
         description="Produce an inline TypeScript (xxx.ts) File",
-        default = True,
+        default = False,
         )
         )
     
     
     export_json = bpy.props.BoolProperty(
     export_json = bpy.props.BoolProperty(
@@ -148,7 +160,7 @@ class TowerOfBabel(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
     export_html = bpy.props.BoolProperty(
     export_html = bpy.props.BoolProperty(
         name="Export applicable .html File(s)",
         name="Export applicable .html File(s)",
         description="Produce a xxx_JSON.html and/or xxx_inline.html as required by other selections",
         description="Produce a xxx_JSON.html and/or xxx_inline.html as required by other selections",
-        default = True,
+        default = False,
         )
         )
     
     
     def draw(self, context):
     def draw(self, context):
@@ -389,10 +401,12 @@ class TowerOfBabel(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
         file_handler.write(',\n"cameras":[')
         file_handler.write(',\n"cameras":[')
         first = True
         first = True
         for camera in self.cameras:
         for camera in self.cameras:
+            if hasattr(camera, 'fatalProblem'): continue
             if first != True:
             if first != True:
                 file_handler.write(',')
                 file_handler.write(',')
 
 
             first = False
             first = False
+            camera.update_for_target_attributes(self.meshesAndNodes)
             camera.to_scene_file(file_handler)
             camera.to_scene_file(file_handler)
         file_handler.write(']')
         file_handler.write(']')
                         
                         
@@ -483,6 +497,8 @@ class TowerOfBabel(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
         file_handler.write(TowerOfBabel.define_static_method('defineCameras', is_typescript))
         file_handler.write(TowerOfBabel.define_static_method('defineCameras', is_typescript))
         file_handler.write(indent2 + 'var camera;\n') # intensionally vague, since sub-classes instances & different specifc propeties set           
         file_handler.write(indent2 + 'var camera;\n') # intensionally vague, since sub-classes instances & different specifc propeties set           
         for camera in self.cameras:
         for camera in self.cameras:
+            if hasattr(camera, 'fatalProblem'): continue
+            camera.update_for_target_attributes(self.meshesAndNodes)
             camera.core_script(file_handler, indent2, is_typescript)
             camera.core_script(file_handler, indent2, is_typescript)
             
             
         if hasattr(self, 'activeCamera'):
         if hasattr(self, 'activeCamera'):
@@ -903,7 +919,7 @@ class Mesh(FCurveAnimatable):
                 if verticesCount + 3 > MAX_VERTEX_ELEMENTS:
                 if verticesCount + 3 > MAX_VERTEX_ELEMENTS:
                     self.offsetFace = faceIndex
                     self.offsetFace = faceIndex
                     break
                     break
-
+                
                 for v in range(3): # For each vertex in face
                 for v in range(3): # For each vertex in face
                     vertex_index = face.vertices[v]
                     vertex_index = face.vertices[v]
 
 
@@ -1044,6 +1060,10 @@ class Mesh(FCurveAnimatable):
         if uvRequired and len(self.uvs) == 0:
         if uvRequired and len(self.uvs) == 0:
             TowerOfBabel.warn('WARNING: textures being used, but no UV Map found', 2)
             TowerOfBabel.warn('WARNING: textures being used, but no UV Map found', 2)
         
         
+        numZeroAreaFaces = self.find_zero_area_faces()    
+        if numZeroAreaFaces > 0:
+            TowerOfBabel.warn('WARNING: # of 0 area faces found:  ' + str(numZeroAreaFaces), 2)
+        
         # shape keys for mesh 
         # shape keys for mesh 
         if object.data.shape_keys:
         if object.data.shape_keys:
             basisFound = False
             basisFound = False
@@ -1088,6 +1108,19 @@ class Mesh(FCurveAnimatable):
                     for group in groupNames:
                     for group in groupNames:
                         self.shapeKeyGroups.append(ShapeKeyGroup(group, rawShapeKeys, self.positions))
                         self.shapeKeyGroups.append(ShapeKeyGroup(group, rawShapeKeys, self.positions))
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+    def find_zero_area_faces(self):
+        nFaces = int(len(self.indices) / 3)
+        nZeroAreaFaces = 0
+        for f in range(0, nFaces):
+            faceOffset = f * 3
+            p1 = self.positions[self.indices[faceOffset    ]]
+            p2 = self.positions[self.indices[faceOffset + 1]]
+            p3 = self.positions[self.indices[faceOffset + 2]]
+            
+            if same_vertex(p1, p2) or same_vertex(p1, p3) or same_vertex(p2, p3): nZeroAreaFaces += 1
+           
+        return nZeroAreaFaces       
+        
     def get_key_order_map(self, basisKey):
     def get_key_order_map(self, basisKey):
         basisData = basisKey.data
         basisData = basisKey.data
         keySz =len(basisData)
         keySz =len(basisData)
@@ -1738,15 +1771,45 @@ class Camera(FCurveAnimatable):
                 self.lockedTargetId = constraint.target.name
                 self.lockedTargetId = constraint.target.name
                 break
                 break
             
             
-        self.useFollowCamera = camera.data.useFollowCamera
-                
-        if self.useFollowCamera:
-            self.followHeight = camera.data.followHeight
-            self.followDistance = camera.data.followDistance
-            self.followRotation = camera.data.followRotation
+        self.CameraType = camera.data.CameraType  
+                   
+        if self.CameraType == ANAGLYPH_ARC_CAM or self.CameraType == ANAGLYPH_FREE_CAM:
+            self.anaglyphEyeSpace = camera.data.anaglyphEyeSpace
+        
+        if self.CameraType == ANAGLYPH_ARC_CAM or self.CameraType == ARC_ROTATE_CAM or self.CameraType == FOLLOW_CAM:
             if not hasattr(self, 'lockedTargetId'):
             if not hasattr(self, 'lockedTargetId'):
-                TowerOfBabel.warn('WARNING: Follow Camera specified, but no target to track', 2)
+                TowerOfBabel.warn('ERROR: Camera type with manditory target specified, but no target to track set', 2)
+                self.fatalProblem = True
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                
+    def update_for_target_attributes(self, meshesAndNodes):  
+        if not hasattr(self, 'lockedTargetId'): return
+        
+        # find the actual mesh tracking, so properties can be derrived
+        targetFound = False
+        for mesh in meshesAndNodes:
+            if mesh.name == self.lockedTargetId: 
+                targetMesh = mesh
+                targetFound = True
+                break;
+            
+        xApart = 3 if not targetFound else self.position.x - targetMesh.position.x
+        yApart = 3 if not targetFound else self.position.y - targetMesh.position.y
+        zApart = 3 if not targetFound else self.position.z - targetMesh.position.z
+        
+        distance3D = math.sqrt(xApart * xApart + yApart * yApart + zApart * zApart)
         
         
+        alpha = math.atan2(yApart, xApart);
+        beta  = math.atan2(yApart, zApart);
+             
+        if self.CameraType == FOLLOW_CAM:
+            self.followHeight   =  zApart
+            self.followDistance = distance3D
+            self.followRotation =  90 + (alpha * 180 / math.pi) 
+                
+        elif self.CameraType ==  ANAGLYPH_ARC_CAM or self.CameraType == ARC_ROTATE_CAM:
+            self.arcRotAlpha  = alpha
+            self.arcRotBeta   = beta 
+            self.arcRotRadius = distance3D
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -                
     def to_scene_file(self, file_handler):     
     def to_scene_file(self, file_handler):     
         file_handler.write('{')
         file_handler.write('{')
@@ -1761,13 +1824,25 @@ class Camera(FCurveAnimatable):
         write_float(file_handler, 'inertia', self.inertia)
         write_float(file_handler, 'inertia', self.inertia)
         write_bool(file_handler, 'checkCollisions', self.checkCollisions)
         write_bool(file_handler, 'checkCollisions', self.checkCollisions)
         write_bool(file_handler, 'applyGravity', self.applyGravity)
         write_bool(file_handler, 'applyGravity', self.applyGravity)
-        write_bool(file_handler, 'useFollowCamera', self.useFollowCamera)
         write_array3(file_handler, 'ellipsoid', self.ellipsoid)
         write_array3(file_handler, 'ellipsoid', self.ellipsoid)
         
         
-        if self.useFollowCamera:
-            write_int(file_handler, 'heightOffset',  self.followHeight)
-            write_int(file_handler, 'radius',  self.followDistance)
-            write_int(file_handler, 'rotationOffset',  self.followRotation)
+        write_string(file_handler, 'type', self.CameraType)
+        
+        if self.CameraType == FOLLOW_CAM:
+            write_float(file_handler, 'heightOffset',  self.followHeight)
+            write_float(file_handler, 'radius',  self.followDistance)
+            write_float(file_handler, 'rotationOffset',  self.followRotation)
+            
+        elif self.CameraType == ANAGLYPH_ARC_CAM or self.CameraType == ARC_ROTATE_CAM:
+            write_float(file_handler, 'alpha', self.arcRotAlpha)
+            write_float(file_handler, 'beta', self.arcRotBeta)
+            write_float(file_handler, 'radius',  self.arcRotRadius)
+            
+            if self.CameraType ==  ANAGLYPH_ARC_CAM:
+                write_float(file_handler, 'eye_space',  self.anaglyphEyeSpace)
+            
+        elif self.CameraType == ANAGLYPH_FREE_CAM:
+            write_float(file_handler, 'eye_space',  self.anaglyphEyeSpace)
             
             
         if hasattr(self, 'lockedTargetId'):
         if hasattr(self, 'lockedTargetId'):
             write_string(file_handler, 'lockedTargetId', self.lockedTargetId)
             write_string(file_handler, 'lockedTargetId', self.lockedTargetId)
@@ -1776,11 +1851,23 @@ class Camera(FCurveAnimatable):
         file_handler.write('}')
         file_handler.write('}')
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -        
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -        
     def core_script(self, file_handler, indent, is_typescript): 
     def core_script(self, file_handler, indent, is_typescript): 
-        cameraClass = 'BABYLON.FollowCamera' if self.useFollowCamera else 'BABYLON.FreeCamera'
+        # constructor args are not the same for each camera type
+        file_handler.write(indent + 'camera = new BABYLON.' + self.CameraType + '("' + self.name + '"')
+        if self.CameraType == ANAGLYPH_ARC_CAM or self.CameraType == ARC_ROTATE_CAM:
+            file_handler.write(', ' + format_f(self.arcRotAlpha) + ', ' + format_f(self.arcRotBeta) + ', ' + format_f(self.arcRotRadius))
+            file_handler.write(', scene.getMeshByID("' + self.lockedTargetId + '")')
+            
+            if self.CameraType ==  ANAGLYPH_ARC_CAM:
+                file_handler.write(', ' + format_f(self.anaglyphEyeSpace))
+                            
+        else:
+            file_handler.write(', new BABYLON.Vector3(' + format_vector(self.position) + ')')
+            if self.CameraType == ANAGLYPH_FREE_CAM:
+                file_handler.write(', ' + format_f(self.anaglyphEyeSpace))
+            
+        file_handler.write(', scene);\n')
         
         
-        file_handler.write(indent + 'camera = new ' + cameraClass + '("' + self.name + '", new BABYLON.Vector3(' + format_vector(self.position) + '), scene);\n')
         file_handler.write(indent + 'camera.id = "' + self.name + '";\n')
         file_handler.write(indent + 'camera.id = "' + self.name + '";\n')
-
         file_handler.write(indent + 'camera.rotation = new BABYLON.Vector3(' + format_vector(self.rotation) + ');\n')
         file_handler.write(indent + 'camera.rotation = new BABYLON.Vector3(' + format_vector(self.rotation) + ');\n')
 
 
         file_handler.write(indent + 'camera.fov = ' + format_f(self.fov) + ';\n')
         file_handler.write(indent + 'camera.fov = ' + format_f(self.fov) + ';\n')
@@ -1794,13 +1881,13 @@ class Camera(FCurveAnimatable):
         file_handler.write(indent + 'camera.applyGravity = ' + format_bool(self.applyGravity) + ';\n')
         file_handler.write(indent + 'camera.applyGravity = ' + format_bool(self.applyGravity) + ';\n')
         file_handler.write(indent + 'camera.ellipsoid = new BABYLON.Vector3(' + format_array3(self.ellipsoid) + ');\n')
         file_handler.write(indent + 'camera.ellipsoid = new BABYLON.Vector3(' + format_array3(self.ellipsoid) + ');\n')
                  
                  
-        if self.useFollowCamera:
-            file_handler.write(indent + 'camera.heightOffset = ' + format_int(self.followHeight) + ';\n')
-            file_handler.write(indent + 'camera.radius = ' + format_int(self.followDistance) + ';\n')
-            file_handler.write(indent + 'camera.rotationOffset = ' + format_int(self.followRotation) + ';\n')
-            
-        if hasattr(self, 'lockedTargetId'):
-            if self.useFollowCamera:
+        if self.CameraType == FOLLOW_CAM:
+            file_handler.write(indent + 'camera.heightOffset = ' + format_f(self.followHeight) + ';\n')
+            file_handler.write(indent + 'camera.radius = ' + format_f(self.followDistance) + ';\n')
+            file_handler.write(indent + 'camera.rotationOffset = ' + format_f(self.followRotation) + ';\n')
+                        
+        if hasattr(self, 'lockedTargetId') and (self.CameraType == FOLLOW_CAM or self.CameraType == FREE_CAM):
+            if self.CameraType == FOLLOW_CAM:
                 file_handler.write(indent + 'camera.target = scene.getMeshByID("' + self.lockedTargetId + '");\n')
                 file_handler.write(indent + 'camera.target = scene.getMeshByID("' + self.lockedTargetId + '");\n')
             else:
             else:
                 file_handler.write(indent + 'camera.lockedTarget = scene.getMeshByID("' + self.lockedTargetId + '");\n')
                 file_handler.write(indent + 'camera.lockedTarget = scene.getMeshByID("' + self.lockedTargetId + '");\n')
@@ -2380,6 +2467,7 @@ def write_bool(file_handler, name, bool, noComma = False):
     file_handler.write('"' + name + '":' + format_bool(bool))
     file_handler.write('"' + name + '":' + format_bool(bool))
 #===============================================================================
 #===============================================================================
 # custom properties definition and display 
 # custom properties definition and display 
+#===============================================================================
 bpy.types.Mesh.useFlatShading = bpy.props.BoolProperty(
 bpy.types.Mesh.useFlatShading = bpy.props.BoolProperty(
     name='Use Flat Shading', 
     name='Use Flat Shading', 
     description='',
     description='',
@@ -2400,6 +2488,24 @@ bpy.types.Mesh.receiveShadows = bpy.props.BoolProperty(
     description='',
     description='',
     default = False
     default = False
 )
 )
+#===============================================================================
+bpy.types.Camera.CameraType = bpy.props.EnumProperty(
+    name='Camera Type', 
+    description='',
+    items = ( 
+             (V_JOYSTICKS_CAM  , 'Virtual Joysticks'  , 'Use Virtual Joysticks Camera'),
+             (TOUCH_CAM        , 'Touch'              , 'Use Touch Camera'),
+             (OCULUS_CAM       , 'Oculus'             , 'Use Oculus Camera'),
+             (GAMEPAD_CAM      , 'Gamepad'            , 'Use Gamepad Camera'),
+             (FREE_CAM         , 'Free'               , 'Use Free Camera'),
+             (FOLLOW_CAM       , 'Follow'             , 'Use Follow Camera'),
+             (DEV_ORIENT_CAM   , 'Device Orientation' , 'Use Device Orientation Camera'),
+             (ARC_ROTATE_CAM   , 'Arc Rotate'         , 'Use Arc Rotate Camera'),
+             (ANAGLYPH_FREE_CAM, 'Anaglyph Free'       , 'Use Anaglyph Free Camera'), 
+             (ANAGLYPH_ARC_CAM , 'Anaglyph Arc Rotate', 'Use Anaglyph Arc Rotate Camera') 
+            ),
+    default = FREE_CAM
+)
 bpy.types.Camera.checkCollisions = bpy.props.BoolProperty(
 bpy.types.Camera.checkCollisions = bpy.props.BoolProperty(
     name='Check Collisions', 
     name='Check Collisions', 
     description='',
     description='',
@@ -2410,31 +2516,17 @@ bpy.types.Camera.applyGravity = bpy.props.BoolProperty(
     description='',
     description='',
     default = False
     default = False
 )    
 )    
-bpy.types.Camera.useFollowCamera = bpy.props.BoolProperty(
-    name='Use Follow Camera', 
-    description='',
-    default = False
-)
-bpy.types.Camera.followHeight = bpy.props.IntProperty(
-    name='Follow Height', 
-    description='how high above the object to maintain the camera',
-    default = 0
-)    
-bpy.types.Camera.followDistance = bpy.props.IntProperty(
-    name='Follow Distance', 
-    description='how far from the object to follow',
-    default = 10
-)    
-bpy.types.Camera.followRotation = bpy.props.IntProperty(
-    name='Follow rotation', 
-    description='rotate around the object, use 180 to follow from the front of the object',
-    default = 0
-)    
 bpy.types.Camera.ellipsoid = bpy.props.FloatVectorProperty(
 bpy.types.Camera.ellipsoid = bpy.props.FloatVectorProperty(
     name='Ellipsoid', 
     name='Ellipsoid', 
     description='',
     description='',
     default = mathutils.Vector((0.2, 0.9, 0.2))
     default = mathutils.Vector((0.2, 0.9, 0.2))
 )
 )
+bpy.types.Camera.anaglyphEyeSpace = bpy.props.IntProperty(
+    name='Anaglyph Eye space', 
+    description='Used by the Anaglyph Arc Rotate camera',
+    default = 1
+)    
+#===============================================================================
 bpy.types.Lamp.shadowMap = bpy.props.EnumProperty(
 bpy.types.Lamp.shadowMap = bpy.props.EnumProperty(
     name='Shadow Map Type', 
     name='Shadow Map Type', 
     description='',
     description='',
@@ -2467,14 +2559,17 @@ class ObjectPanel(bpy.types.Panel):
             layout.prop(ob.data, 'checkCollisions')     
             layout.prop(ob.data, 'checkCollisions')     
             layout.prop(ob.data, 'castShadows')     
             layout.prop(ob.data, 'castShadows')     
             layout.prop(ob.data, 'receiveShadows')   
             layout.prop(ob.data, 'receiveShadows')   
+            
         elif isCamera:
         elif isCamera:
+            layout.prop(ob.data, 'CameraType')
             layout.prop(ob.data, 'checkCollisions')
             layout.prop(ob.data, 'checkCollisions')
             layout.prop(ob.data, 'applyGravity')
             layout.prop(ob.data, 'applyGravity')
             layout.prop(ob.data, 'ellipsoid')
             layout.prop(ob.data, 'ellipsoid')
-            layout.prop(ob.data, 'useFollowCamera')
-            layout.prop(ob.data, 'followHeight')
-            layout.prop(ob.data, 'followDistance')
-            layout.prop(ob.data, 'followRotation')
+
+            layout.separator()
+            
+            layout.prop(ob.data, 'anaglyphEyeSpace')
+            
         elif isLight:
         elif isLight:
             layout.prop(ob.data, 'shadowMap')
             layout.prop(ob.data, 'shadowMap')
             layout.prop(ob.data, 'shadowMapSize')        
             layout.prop(ob.data, 'shadowMapSize')