Selaa lähdekoodia

Clear animations array for node and bones before importing an animation file
Add Import animations button in Inspector
Update icon in sandbox
Beautify code

noalak 5 vuotta sitten
vanhempi
commit
157243d6e7

+ 1 - 1
inspector/src/components/actionTabs/actionTabs.scss

@@ -294,7 +294,7 @@ $line-padding-left: 2px;
                     .file-upload {
                         background: #222222;
                         border: 1px solid rgb(51, 122, 183);
-                        margin: 0px 10px;
+                        margin: 5px 10px 5px 10px;
                         color:white;
                         padding: 4px 5px;
                         opacity: 0.9;

+ 36 - 0
inspector/src/components/actionTabs/lines/fileMultipleButtonLineComponent.tsx

@@ -0,0 +1,36 @@
+import * as React from "react";
+
+interface IFileMultipleButtonLineComponentProps {
+    label: string;
+    onClick: (event: any) => void;
+    accept: string;
+}
+
+export class FileMultipleButtonLineComponent extends React.Component<IFileMultipleButtonLineComponentProps> {
+    private static _IDGenerator = 0;
+    private _id = FileMultipleButtonLineComponent._IDGenerator++;
+
+    constructor(props: IFileMultipleButtonLineComponentProps) {
+        super(props);
+    }
+
+    onChange(evt: any) {
+        var files: File[] = evt.target.files;
+        if (files && files.length) {
+            this.props.onClick(evt);
+        }
+
+        evt.target.value = "";
+    }
+
+    render() {
+        return (
+            <div className="buttonLine">
+                <label htmlFor={"file-upload" + this._id} className="file-upload">
+                    {this.props.label}
+                </label>
+                <input ref="upload" id={"file-upload" + this._id} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} multiple />
+            </div>
+        );
+    }
+}

+ 26 - 0
inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx

@@ -23,6 +23,8 @@ import { IScreenshotSize } from 'babylonjs/Misc/interfaces/screenshotSize';
 import { NumericInputComponent } from '../lines/numericInputComponent';
 import { CheckBoxLineComponent } from '../lines/checkBoxLineComponent';
 import { TextLineComponent } from '../lines/textLineComponent';
+import { Scene } from 'babylonjs/scene';
+import { FileMultipleButtonLineComponent } from '../lines/fileMultipleButtonLineComponent';
 
 export class ToolsTabComponent extends PaneComponent {
     private _videoRecorder: Nullable<VideoRecorder>;
@@ -93,6 +95,27 @@ export class ToolsTabComponent extends PaneComponent {
         this.setState({ tag: "Stop recording" });
     }
 
+    importAnimations(event: any) {
+
+        const scene = this.props.scene;
+
+        var reload = function (sceneFile: File) {
+            // If a scene file has been provided
+            if (sceneFile) {
+                var onSuccess = function (scene: Scene) {
+                    if (scene.animationGroups.length > 0) {
+                        let currentGroup = scene.animationGroups[0];
+                        currentGroup.play(true);
+                    }
+                };
+                (BABYLON.SceneLoader as any).ImportAnimationsAsync("file:", sceneFile, scene, null, onSuccess);
+            }
+        };
+        let filesInputAnimation = new BABYLON.FilesInput(scene.getEngine() as any, scene as any, () => { }, () => { }, () => { }, (remaining: number) => { }, () => { }, reload, () => { });
+
+        filesInputAnimation.loadFiles(event);
+    }
+
     shouldExport(node: Node): boolean {
 
         // No skybox
@@ -192,6 +215,9 @@ export class ToolsTabComponent extends PaneComponent {
                     <ButtonLineComponent label="Generate replay code" onClick={() => this.exportReplay()} />
                     <ButtonLineComponent label="Reset" onClick={() => this.resetReplay()} />
                 </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="SCENE IMPORT">
+                    <FileMultipleButtonLineComponent label="Import Animations" accept="gltf" onClick={(evt: any) => this.importAnimations(evt)} />
+                </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="SCENE EXPORT">
                     {
                         this._isExporting && 

BIN
sandbox/Assets/Icon_OpenAnimFile.png


+ 2 - 2
sandbox/animation.js

@@ -45,8 +45,8 @@ dropdownBtn.addEventListener("click", function() {
 });
 
 function selectCurrentGroup(group, index, animation) {
-    if (currentGroupIndex !== undefined) {
-        document.getElementById(formatId(currentGroup.name + "-" + currentGroupIndex)).classList.remove("active");
+    for (var i = 0; i < dropdownContent.children.length; i++) {
+        dropdownContent.children[i].classList.remove("active");
     }
     playBtn.classList.remove("play");
     playBtn.classList.add("pause");

+ 2 - 2
sandbox/index-local.html

@@ -56,8 +56,8 @@
                 </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"
+                <a href="javascript:void(null);" id="animationFilesButton" class="hidden">
+                    <div class="custom-upload custom-upload-animation"
                         title="Open your animation scene from your hard drive (.babylon, .gltf, .glb, .obj)">
                         <input type="file" id="animationFiles" multiple />
                     </div>

+ 5 - 1
sandbox/index.css

@@ -72,7 +72,7 @@ a:visited {
     font-size: 0;
     display: grid;
     grid-template-rows: 100%;
-    grid-template-columns: 201px 1fr 210px
+    grid-template-columns: 201px 1fr 280px
 }
 
 #logoImg {
@@ -129,6 +129,10 @@ a:visited {
     height: var(--footer-height);
 }
 
+.custom-upload-animation {
+    background:url(./Assets/Icon_OpenAnimFile.png) center right no-repeat;
+}
+
 .custom-upload input[type=file]
 {
     outline:none;

+ 2 - 2
sandbox/index.html

@@ -66,8 +66,8 @@
                 </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"
+                <a href="javascript:void(null);" id="animationFilesButton" class="hidden">
+                    <div class="custom-upload custom-upload-animation"
                         title="Open your animation scene from your hard drive (.babylon, .gltf, .glb, .obj)">
                         <input type="file" id="animationFiles" multiple />
                     </div>

+ 19 - 17
sandbox/index.js

@@ -48,6 +48,7 @@ if (BABYLON.Engine.isSupported()) {
     var htmlInputAnimation = document.getElementById("animationFiles");
     var btnInspector = document.getElementById("btnInspector");
     var errorZone = document.getElementById("errorZone");
+    var btnAnimationFiles = document.getElementById("animationFilesButton");
     var filesInput;
     var filesInputAnimation;
     var currentScene;
@@ -60,7 +61,7 @@ if (BABYLON.Engine.isSupported()) {
 
     btnInspector.classList.add("hidden");
     btnEnvironment.classList.add("hidden");
-    // htmlInputAnimation.classList.add("hidden");
+    btnAnimationFiles.classList.add("hidden");
 
     canvas.addEventListener("contextmenu", function(evt) {
         evt.preventDefault();
@@ -89,9 +90,7 @@ if (BABYLON.Engine.isSupported()) {
         engine.resize();
     });
 
-    var anyLoaded = function(sceneFile, babylonScene) {
-        engine.clearInternalTexturesCache();
-
+    var anyLoaded = function(babylonScene) {
         // Clear dropdown that contains animation names
         dropdownContent.innerHTML = "";
         animationBar.style.display = "none";
@@ -103,8 +102,8 @@ if (BABYLON.Engine.isSupported()) {
                 var group = babylonScene.animationGroups[index];
                 createDropdownLink(group, index);
             }
-            currentGroup = babylonScene.animationGroups[0];
-            currentGroupIndex = 0;
+            currentGroupIndex = babylonScene.animationGroups.length - 1;
+            currentGroup = babylonScene.animationGroups[currentGroupIndex];
             currentGroup.play(true);
         }
 
@@ -123,28 +122,31 @@ if (BABYLON.Engine.isSupported()) {
 
         // Clear the error
         errorZone.style.display = 'none';
-
-        btnInspector.classList.remove("hidden");
-        btnEnvironment.classList.remove("hidden");
-
-        currentScene = babylonScene;
-        document.title = "Babylon.js - " + sceneFile.name;
-        htmlInputAnimation.value = "";
     }
 
-    var assetContainerLoaded = function(sceneFile, babylonScene) {
-        anyLoaded(sceneFile, babylonScene);
+    var assetContainerLoaded = function (sceneFile, babylonScene) {
+        anyLoaded(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);
+    var sceneLoaded = function (sceneFile, babylonScene) {
+        engine.clearInternalTexturesCache();
+
+        anyLoaded(babylonScene);
 
         // Fix for IE, otherwise it will change the default filter for files selection after first use
         htmlInput.value = "";
 
+        currentScene = babylonScene;
+
+        document.title = "Babylon.js - " + sceneFile.name;
+
+        btnInspector.classList.remove("hidden");
+        btnEnvironment.classList.remove("hidden");
+        btnAnimationFiles.classList.remove("hidden");
+
         // Attach camera to canvas inputs
         if (!currentScene.activeCamera || currentScene.lights.length === 0) {
             currentScene.createDefaultCamera(true);

+ 44 - 28
src/Loading/sceneLoader.ts

@@ -5,7 +5,6 @@ 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";
@@ -18,7 +17,8 @@ import { SceneLoaderFlags } from "./sceneLoaderFlags";
 import { IFileRequest } from "../Misc/fileRequest";
 import { WebRequest } from "../Misc/webRequest";
 import { RequestFileError, ReadFileError } from '../Misc/fileTools';
-import { Animatable } from '../Animations';
+import { Animation } from '../Animations';
+import { AbstractScene } from '..';
 
 /**
  * Class used to represent data loading progression
@@ -989,20 +989,19 @@ export class SceneLoader {
      * @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 {
+    public static ImportAnimations(rootUrl: string, sceneFilename: string | File = "", scene: Nullable<Scene> = EngineStore.LastCreatedScene, targetConverter: Nullable<(target: any) => any> = 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 _targetConverter = targetConverter ? targetConverter : this._defaultTargetConverter(scene);
 
         let onAssetContainerLoaded = (container: AssetContainer) => {
             SceneLoader.MergeAnimations(scene, container, _targetConverter);
 
+            container.dispose();
+
             if (onSuccess) {
                 onSuccess(scene);
             }
@@ -1013,8 +1012,12 @@ export class SceneLoader {
             animatable.reset();
         }
         scene.stopAllAnimations();
-        scene.animationGroups.forEach(animationGroup => {
-            animationGroup.dispose();
+        scene.animationGroups.slice().forEach(animationGroup => {
+            // animationGroup.dispose();
+        });
+        let animatableObjects = this._getAllAnimatableObjects(scene);
+        animatableObjects.forEach(animatableObject => {
+            animatableObject.animations = new Array<Animation>();
         });
 
         this.LoadAssetContainer(rootUrl, sceneFilename, scene, onAssetContainerLoaded, onProgress, onError);
@@ -1030,7 +1033,7 @@ export class SceneLoader {
      * @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> {
+    public static ImportAnimationsAsync(rootUrl: string, sceneFilename: string | File = "", scene: Nullable<Scene> = EngineStore.LastCreatedScene, targetConverter: Nullable<(target: any) => any> = 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);
@@ -1047,25 +1050,15 @@ export class SceneLoader {
      * @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;
-                }
+        let _targetConverter = targetConverter ? targetConverter : this._defaultTargetConverter(scene);
+
+        // Copy node and bone animations
+        let animatableObjectsInAC = this._getAllAnimatableObjects(animationAssetContainer);
+        animatableObjectsInAC.forEach(animatableObjectInAC => {
+            let objectInScene = _targetConverter(animatableObjectInAC);
+            if (objectInScene != null) {
+                objectInScene.animations = animatableObjectInAC.animations;
             }
         });
 
@@ -1101,4 +1094,27 @@ export class SceneLoader {
             }
         });
     }
+
+    /**
+     * Default target converter is searching bones and nodes by name
+     * @param scene 
+     */
+    private static _defaultTargetConverter(scene: Scene): (target: any) => any {
+        return (target: any) => { return scene.getBoneByName(target.name) || scene.getNodeByName(target.name) };
+    };
+
+    /**
+     * Return all objects that can hold an animations array
+     * @param abstractScene 
+     */
+    private static _getAllAnimatableObjects(abstractScene: AbstractScene) {
+        let animatableObjects = new Array<any>();
+        animatableObjects = animatableObjects.concat(abstractScene.meshes);
+        animatableObjects = animatableObjects.concat(abstractScene.lights);
+        animatableObjects = animatableObjects.concat(abstractScene.cameras);
+        animatableObjects = animatableObjects.concat(abstractScene.transformNodes); // dummies
+        abstractScene.skeletons.forEach(skeleton => animatableObjects = animatableObjects.concat(skeleton.bones));
+        animatableObjects = animatableObjects.filter(animatableObject => animatableObject.animations);
+        return animatableObjects;
+    }
 }