Kaynağa Gözat

Add ImportAnimations and MergeAnimations methods
Update sandbox js and html to include these additions

noalak 5 yıl önce
ebeveyn
işleme
7eed66eefc
4 değiştirilmiş dosya ile 184 ekleme ve 7 silme
  1. 8 2
      sandbox/index-local.html
  2. 6 3
      sandbox/index.html
  3. 45 2
      sandbox/index.js
  4. 125 0
      src/Loading/sceneLoader.ts

+ 8 - 2
sandbox/index-local.html

@@ -56,6 +56,12 @@
                 </a>
                 <a href="javascript:void(null);" id="btnInspector" class="hidden"><img src="./Assets/Icon_EditModel.svg"
                         alt="Display inspector" title="Display inspector" /></a>
+                <a href="javascript:void(null);">
+                    <div class="custom-upload"
+                        title="Open your animation scene from your hard drive (.babylon, .gltf, .glb, .obj)">
+                        <input type="file" id="animationFiles" multiple />
+                    </div>
+                </a>
                 <div id="dropdownContent-env" class="hidden">
                 </div>
             </div>
@@ -67,7 +73,7 @@
     </div>
     <script>
         // prevent drag and drop of file until local scripts are loaded
-        document.ondragover = function(e) {
+        document.ondragover = function (e) {
             e.dataTransfer.dropEffect = "none";
             e.dataTransfer.effectAllowed = "none";
             e.preventDefault();
@@ -77,7 +83,7 @@
             .require('environment.js')
             .require('animation.js')
             .require('index.js')
-            .load(function() {
+            .load(function () {
                 BABYLON.DracoCompression.Configuration.decoder = {
                     wasmUrl: "../dist/preview%20release/draco_wasm_wrapper_gltf.js",
                     wasmBinaryUrl: "../dist/preview%20release/draco_decoder_gltf.wasm",

+ 6 - 3
sandbox/index.html

@@ -9,19 +9,16 @@
     <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
     <link rel="stylesheet" href="https://use.typekit.net/cta4xsb.css">
     <link rel="shortcut icon" href="https://www.babylonjs.com/favicon.ico">
-
     <link href="index.css" rel="stylesheet" />
     <link href="index-media.css" rel="stylesheet" />
     <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
     <script src="https://playground.babylonjs.com/js/libs/split.js"></script>
-
     <script src="https://preview.babylonjs.com/ammo.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>
     <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/loaders/babylonjs.loaders.min.js"></script>
     <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
     <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
@@ -69,6 +66,12 @@
                 </a>
                 <a href="javascript:void(null);" id="btnInspector" class="hidden"><img src="./Assets/Icon_EditModel.svg"
                         alt="Display inspector" title="Display inspector" /></a>
+                <a href="javascript:void(null);">
+                    <div class="custom-upload"
+                        title="Open your animation scene from your hard drive (.babylon, .gltf, .glb, .obj)">
+                        <input type="file" id="animationFiles" multiple />
+                    </div>
+                </a>
                 <div id="dropdownContent-env" class="hidden">
                 </div>
             </div>

+ 45 - 2
sandbox/index.js

@@ -45,9 +45,11 @@ if (kiosk) {
 if (BABYLON.Engine.isSupported()) {
     var engine = new BABYLON.Engine(canvas, true, { premultipliedAlpha: false, preserveDrawingBuffer: true });
     var htmlInput = document.getElementById("files");
+    var htmlInputAnimation = document.getElementById("animationFiles");
     var btnInspector = document.getElementById("btnInspector");
     var errorZone = document.getElementById("errorZone");
     var filesInput;
+    var filesInputAnimation;
     var currentScene;
     var currentSkybox;
     var currentPluginName;
@@ -58,6 +60,7 @@ if (BABYLON.Engine.isSupported()) {
 
     btnInspector.classList.add("hidden");
     btnEnvironment.classList.add("hidden");
+    // htmlInputAnimation.classList.add("hidden");
 
     canvas.addEventListener("contextmenu", function(evt) {
         evt.preventDefault();
@@ -86,7 +89,7 @@ if (BABYLON.Engine.isSupported()) {
         engine.resize();
     });
 
-    var sceneLoaded = function(sceneFile, babylonScene) {
+    var anyLoaded = function(sceneFile, babylonScene) {
         engine.clearInternalTexturesCache();
 
         // Clear dropdown that contains animation names
@@ -126,6 +129,19 @@ if (BABYLON.Engine.isSupported()) {
 
         currentScene = babylonScene;
         document.title = "Babylon.js - " + sceneFile.name;
+        htmlInputAnimation.value = "";
+    }
+
+    var assetContainerLoaded = function(sceneFile, babylonScene) {
+        anyLoaded(sceneFile, babylonScene);
+
+        // Fix for IE, otherwise it will change the default filter for files selection after first use
+        htmlInputAnimation.value = "";
+    }
+
+    var sceneLoaded = function(sceneFile, babylonScene) {
+        anyLoaded(sceneFile, babylonScene);
+
         // Fix for IE, otherwise it will change the default filter for files selection after first use
         htmlInput.value = "";
 
@@ -289,9 +305,36 @@ if (BABYLON.Engine.isSupported()) {
             }
             filesInput.loadFiles(event);
         }, false);
+
+
+        var reload = function (sceneFile) {
+            // If a scene file has been provided
+            if (sceneFile) {
+                var onSuccess = function (scene) {
+                    assetContainerLoaded(sceneFile, scene);
+                };
+                BABYLON.SceneLoader.ImportAnimations("file:", sceneFile, currentScene, null, onSuccess);
+            }
+            else {
+                Logger.Error("Please provide a valid .babylon file.");
+            }
+        };
+        filesInputAnimation = new BABYLON.FilesInput(engine, null, null, null, null, null, startProcessingFiles, reload, sceneError);
+
+        htmlInputAnimation.addEventListener('change', function (event) {
+            // Handling data transfer via drag'n'drop
+            if (event && event.dataTransfer && event.dataTransfer.files) {
+                filesToLoad = event.dataTransfer.files;
+            }
+            // Handling files from input files
+            if (event && event.target && event.target.files) {
+                filesToLoad = event.target.files;
+            }
+            filesInputAnimation.loadFiles(event);
+        }, false);
     }
 
-    window.addEventListener("keydown", function(event) {
+    window.addEventListener("keydown", function (event) {
         // Press R to reload
         if (event.keyCode === 82 && event.target.nodeName !== "INPUT" && currentScene) {
             if (assetUrl) {

+ 125 - 0
src/Loading/sceneLoader.ts

@@ -5,6 +5,7 @@ import { Nullable } from "../types";
 import { Scene } from "../scene";
 import { Engine } from "../Engines/engine";
 import { EngineStore } from "../Engines/engineStore";
+import { Node } from "../node";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { AnimationGroup } from "../Animations/animationGroup";
 import { _TimeToken } from "../Instrumentation/timeToken";
@@ -17,6 +18,7 @@ import { SceneLoaderFlags } from "./sceneLoaderFlags";
 import { IFileRequest } from "../Misc/fileRequest";
 import { WebRequest } from "../Misc/webRequest";
 import { RequestFileError, ReadFileError } from '../Misc/fileTools';
+import { Animatable } from '../Animations';
 
 /**
  * Class used to represent data loading progression
@@ -976,4 +978,127 @@ export class SceneLoader {
             }, pluginExtension);
         });
     }
+
+    /**
+     * Import animations from a file into a scene
+     * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
+     * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
+     * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
+     * @param targetConverter defines a function used to convert animation targets from loaded scene to current scene (default: search node by name)
+     * @param onSuccess a callback with the scene when import succeeds
+     * @param onProgress a callback with a progress event for each file being loaded
+     * @param onError a callback with the scene, a message, and possibly an exception when import fails
+     */
+    public static ImportAnimations(rootUrl: string, sceneFilename: string | File = "", scene: Nullable<Scene> = EngineStore.LastCreatedScene, targetConverter: Nullable<(target: any) => Nullable<Node>> = null, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null): void {
+        if (!scene) {
+            Logger.Error("No scene available to load animations to");
+            return;
+        }
+
+        // Default target converter is searching node by name
+        let _targetConverter = targetConverter ? targetConverter : (target: any) => {
+            return scene.getNodeByName(target.name);
+        };
+
+        let onAssetContainerLoaded = (container: AssetContainer) => {
+            SceneLoader.MergeAnimations(scene, container, _targetConverter);
+
+            if (onSuccess) {
+                onSuccess(scene);
+            }
+        };
+
+        // Reset, stop and dispose all animations before loading new ones
+        for (let animatable of scene.animatables) {
+            animatable.reset();
+        }
+        scene.stopAllAnimations();
+        scene.animationGroups.forEach(animationGroup => {
+            animationGroup.dispose();
+        });
+
+        this.LoadAssetContainer(rootUrl, sceneFilename, scene, onAssetContainerLoaded, onProgress, onError);
+    }
+
+    /**
+     * Import animations from a file into a scene
+     * @param rootUrl a string that defines the root url for the scene and resources or the concatenation of rootURL and filename (e.g. http://example.com/test.glb)
+     * @param sceneFilename a string that defines the name of the scene file or starts with "data:" following by the stringified version of the scene or a File object (default: empty string)
+     * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
+     * @param targetConverter defines a function used to convert animation targets from loaded scene to current scene (default: search node by name)
+     * @param onSuccess a callback with the scene when import succeeds
+     * @param onProgress a callback with a progress event for each file being loaded
+     * @param onError a callback with the scene, a message, and possibly an exception when import fails
+     */
+    public static ImportAnimationsAsync(rootUrl: string, sceneFilename: string | File = "", scene: Nullable<Scene> = EngineStore.LastCreatedScene, targetConverter: Nullable<(target: any) => Nullable<Node>> = null, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null): Promise<Scene> {
+        return new Promise((resolve, reject) => {
+            SceneLoader.ImportAnimations(rootUrl, sceneFilename, scene, targetConverter, (_scene: Scene) => {
+                resolve(_scene);
+            }, onProgress, (_scene: Scene, message: string, exception: any) => {
+                reject(exception || new Error(message));
+            });
+        });
+    }
+
+    /**
+     * Merge animations from an asset container into a scene
+     * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
+     * @param animationAssetContainer is the instance of BABYLON.AssetContainer containing animations
+     * @param targetConverter defines a function used to convert animation targets from the asset container to the scene (default: search node by name)
+     */
+    public static MergeAnimations(scene: Scene, animationAssetContainer: AssetContainer, targetConverter: Nullable<(target: any) => any> = null): void {
+        // Default target converter is searching node by name
+        let _targetConverter = targetConverter ? targetConverter : (target: any) => {
+            return scene.getNodeByName(target.name);
+        };
+
+        // Concat nodes of all types from animation container
+        let animatedNodes = new Array<Node>();
+        animatedNodes = animatedNodes.concat(animationAssetContainer.meshes);
+        animatedNodes = animatedNodes.concat(animationAssetContainer.lights);
+        animatedNodes = animatedNodes.concat(animationAssetContainer.cameras);
+        animatedNodes = animatedNodes.concat(animationAssetContainer.transformNodes); // dummies
+
+        // Copy node animations
+        animatedNodes.forEach(animatedNode => {
+            if (animatedNode.animations && animatedNode.animations.length > 0) {
+                let geometryNode: Node = _targetConverter(animatedNode);
+                if (geometryNode != null) {
+                    geometryNode.animations = animatedNode.animations;
+                }
+            }
+        });
+
+        // Copy animation groups
+        animationAssetContainer.animationGroups.forEach(animationGroupInAC => {
+            // Dispose animation groups with same name as one being loaded
+            scene.animationGroups.forEach(animationGroup => {
+                if (animationGroup.name == animationGroupInAC.name) {
+                    animationGroup.dispose();
+                }
+            })
+
+            // Clone the animation group and all its animatables
+            animationGroupInAC.clone(animationGroupInAC.name, _targetConverter);
+
+            // Remove animatables related to the animation asset container
+            animationGroupInAC.animatables.forEach(animatable => {
+                animatable.stop();
+            })
+        });
+
+        // Copy animatables
+        scene.animatables.forEach(animatable => {
+            let target = _targetConverter(animatable.target);
+
+            // If the animatable has just been loaded
+            if (target && target != animatable.target) {
+                // Clone the animatable and retarget it
+                scene.beginAnimation(target, animatable.fromFrame, animatable.toFrame, animatable.loopAnimation, animatable.speedRatio, animatable.onAnimationEnd ? animatable.onAnimationEnd : undefined, undefined, true, undefined, animatable.onAnimationLoop ? animatable.onAnimationLoop : undefined);
+
+                // Stop animation for the target in the animation asset container
+                scene.stopAnimation(animatable.target);
+            }
+        });
+    }
 }