Ver código fonte

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 5 anos atrás
pai
commit
1d95f68285

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

@@ -76,6 +76,7 @@
 - Added support for CreateScreenshotUsingRenderTarget ([13djwright](https://github.com/13djwright/))
 - Added support for `Material.depthFunction` property ([Popov72](https://github.com/Popov72))
 - Added an optional config option `initialTab` ([ycw](https://github.com/ycw/)) 
+- Added support for ImportAnimations ([noalak](https://github.com/noalak/))
 
 ### Tools
 
@@ -124,6 +125,7 @@
 - Added support for GLTF sheen extension [Sebavan](https://github.com/sebavan/)
 - Added support for GLTF mesh quantization extension ([zeux](https://github.com/zeux))
 - Added support for 8 bone influences to glTF loader ([zeux](https://github.com/zeux))
+- Added support for animations import from separate files ([noalak](https://github.com/noalak/))
 
 ### Materials
 
@@ -252,6 +254,7 @@
 - WebXR UI BUtton will only change to "In XR" after XR Session started ([RaananW](https://github.com/RaananW/))
 - Fix bug when we call `Mesh.render` twice and the material is still not ready on the second call ([barroij](https://github.com/barroij/))
 - Fixed an issue with pose input in webxr ([RaananW](https://github.com/RaananW/))
+- Fixed bug when parsing animation group without 'to' value ([noalak](https://github.com/noalak/))
 
 ## Breaking changes
 

+ 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>
+        );
+    }
+}

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

@@ -14,6 +14,9 @@ import { CubeTexture } from "babylonjs/Materials/Textures/cubeTexture";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { SceneSerializer } from "babylonjs/Misc/sceneSerializer";
 import { Mesh } from "babylonjs/Meshes/mesh";
+import { FilesInput } from 'babylonjs/Misc/filesInput';
+import { Scene } from 'babylonjs/scene';
+import { SceneLoaderAnimationGroupLoadingMode } from 'babylonjs/Loading/sceneLoader';
 
 import { GLTFComponent } from "./tools/gltfComponent";
 
@@ -23,6 +26,8 @@ import { IScreenshotSize } from 'babylonjs/Misc/interfaces/screenshotSize';
 import { NumericInputComponent } from '../lines/numericInputComponent';
 import { CheckBoxLineComponent } from '../lines/checkBoxLineComponent';
 import { TextLineComponent } from '../lines/textLineComponent';
+import { FileMultipleButtonLineComponent } from '../lines/fileMultipleButtonLineComponent';
+import { OptionsLineComponent } from '../lines/optionsLineComponent';
 
 export class ToolsTabComponent extends PaneComponent {
     private _videoRecorder: Nullable<VideoRecorder>;
@@ -34,6 +39,14 @@ export class ToolsTabComponent extends PaneComponent {
         super(props);
 
         this.state = { tag: "Record video" };
+
+        const sceneImportDefaults = this.props.globalState.sceneImportDefaults;
+        if (sceneImportDefaults["overwriteAnimations"] === undefined) {
+            sceneImportDefaults["overwriteAnimations"] = true;
+        }
+        if (sceneImportDefaults["animationGroupLoadingMode"] === undefined) {
+            sceneImportDefaults["animationGroupLoadingMode"] = SceneLoaderAnimationGroupLoadingMode.Clean;
+        }
     }
 
     componentDidMount() {
@@ -93,6 +106,30 @@ export class ToolsTabComponent extends PaneComponent {
         this.setState({ tag: "Stop recording" });
     }
 
+    importAnimations(event: any) {
+
+        const scene = this.props.scene;
+
+        const overwriteAnimations = this.props.globalState.sceneImportDefaults["overwriteAnimations"];
+        const animationGroupLoadingMode = this.props.globalState.sceneImportDefaults["animationGroupLoadingMode"];
+
+        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 as any).SceneLoader.ImportAnimationsAsync("file:", sceneFile, scene, overwriteAnimations, animationGroupLoadingMode, null, onSuccess);
+            }
+        };
+        let filesInputAnimation = new FilesInput(scene.getEngine() as any, scene as any, () => { }, () => { }, () => { }, (remaining: number) => { }, () => { }, reload, () => { });
+
+        filesInputAnimation.loadFiles(event);
+    }
+
     shouldExport(node: Node): boolean {
 
         // No skybox
@@ -163,6 +200,15 @@ export class ToolsTabComponent extends PaneComponent {
             return null;
         }
 
+        const sceneImportDefaults = this.props.globalState.sceneImportDefaults;
+
+        var animationGroupLoadingModes = [
+            { label: "Clean", value: SceneLoaderAnimationGroupLoadingMode.Clean },
+            { label: "Stop", value: SceneLoaderAnimationGroupLoadingMode.Stop },
+            { label: "Sync", value: SceneLoaderAnimationGroupLoadingMode.Sync },
+            { label: "NoSync", value: SceneLoaderAnimationGroupLoadingMode.NoSync },
+        ];
+
         return (
             <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="CAPTURE">
@@ -192,6 +238,17 @@ 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)} />
+                    <CheckBoxLineComponent label="Overwrite animations" target={sceneImportDefaults} propertyName="overwriteAnimations" onSelect={value => {
+                        sceneImportDefaults["overwriteAnimations"] = value;
+                        this.forceUpdate();
+                    }} />
+                    {
+                        sceneImportDefaults["overwriteAnimations"] === false &&
+                        <OptionsLineComponent label="Animation merge mode" options={animationGroupLoadingModes} target={sceneImportDefaults} propertyName="animationGroupLoadingMode" />
+                    }
+                </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="SCENE EXPORT">
                     {
                         this._isExporting && 

+ 2 - 0
inspector/src/components/globalState.ts

@@ -18,6 +18,8 @@ export class GlobalState {
     public onTabChangedObservable = new Observable<number>();
     public onPluginActivatedObserver: Nullable<Observer<ISceneLoaderPlugin | ISceneLoaderPluginAsync>>;
 
+    public sceneImportDefaults: { [key: string]: any } = {};
+
     public validationResults: IGLTFValidationResults;
     public onValidationResultsUpdatedObservable = new Observable<IGLTFValidationResults>();
 

+ 23 - 9
sandbox/index.js

@@ -86,9 +86,7 @@ if (BABYLON.Engine.isSupported()) {
         engine.resize();
     });
 
-    var sceneLoaded = function(sceneFile, babylonScene) {
-        engine.clearInternalTexturesCache();
-
+    var anyLoaded = function(babylonScene, playFirstAnimationGroup) {
         // Clear dropdown that contains animation names
         dropdownContent.innerHTML = "";
         animationBar.style.display = "none";
@@ -100,8 +98,8 @@ if (BABYLON.Engine.isSupported()) {
                 var group = babylonScene.animationGroups[index];
                 createDropdownLink(group, index);
             }
-            currentGroup = babylonScene.animationGroups[0];
-            currentGroupIndex = 0;
+            currentGroupIndex = playFirstAnimationGroup ? 0 : babylonScene.animationGroups.length - 1;
+            currentGroup = babylonScene.animationGroups[currentGroupIndex];
             currentGroup.play(true);
         }
 
@@ -120,15 +118,31 @@ if (BABYLON.Engine.isSupported()) {
 
         // Clear the error
         errorZone.style.display = 'none';
+    }
 
-        btnInspector.classList.remove("hidden");
-        btnEnvironment.classList.remove("hidden");
+    var assetContainerLoaded = function (sceneFile, babylonScene) {
+        anyLoaded(babylonScene);
+    }
+
+    var sceneLoaded = function (sceneFile, babylonScene) {
+        engine.clearInternalTexturesCache();
+
+        anyLoaded(babylonScene, true);
 
-        currentScene = babylonScene;
-        document.title = "Babylon.js - " + sceneFile.name;
         // Fix for IE, otherwise it will change the default filter for files selection after first use
         htmlInput.value = "";
 
+        currentScene = babylonScene;
+
+        babylonScene.onAnimationFileImportedObservable.add((scene) => {
+            anyLoaded(scene, false);
+        });
+
+        document.title = "Babylon.js - " + sceneFile.name;
+
+        btnInspector.classList.remove("hidden");
+        btnEnvironment.classList.remove("hidden");
+
         // Attach camera to canvas inputs
         if (!currentScene.activeCamera || currentScene.lights.length === 0) {
             currentScene.createDefaultCamera(true);

+ 1 - 1
src/Animations/animationGroup.ts

@@ -575,7 +575,7 @@ export class AnimationGroup implements IDisposable {
             }
         }
 
-        if (parsedAnimationGroup.from !== null && parsedAnimationGroup.from !== null) {
+        if (parsedAnimationGroup.from !== null && parsedAnimationGroup.to !== null) {
             animationGroup.normalize(parsedAnimationGroup.from, parsedAnimationGroup.to);
         }
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 126 - 0
src/Loading/sceneLoader.ts


+ 13 - 0
src/abstractScene.ts

@@ -198,4 +198,17 @@ export abstract class AbstractScene {
      * Environment texture for the scene
      */
     public environmentTexture: Nullable<BaseTexture> = null;
+
+    /**
+     * @returns all meshes, lights, cameras, transformNodes and bones
+     */
+    public getNodes(): Array<Node> {
+        let nodes = new Array<Node>();
+        nodes = nodes.concat(this.meshes);
+        nodes = nodes.concat(this.lights);
+        nodes = nodes.concat(this.cameras);
+        nodes = nodes.concat(this.transformNodes); // dummies
+        this.skeletons.forEach((skeleton) => nodes = nodes.concat(skeleton.bones));
+        return nodes;
+    }
 }

+ 68 - 0
src/assetContainer.ts

@@ -4,9 +4,14 @@ import { Mesh } from "./Meshes/mesh";
 import { TransformNode } from './Meshes/transformNode';
 import { Skeleton } from './Bones/skeleton';
 import { AnimationGroup } from './Animations/animationGroup';
+import { Animatable } from './Animations/animatable';
 import { AbstractMesh } from './Meshes/abstractMesh';
 import { MultiMaterial } from './Materials/multiMaterial';
 import { Material } from './Materials/material';
+import { Logger } from './Misc/logger';
+import { EngineStore } from './Engines/engineStore';
+import { Nullable } from './types';
+import { Node } from './node';
 
 /**
  * Set of assets to keep when moving a scene into an asset container.
@@ -454,4 +459,67 @@ export class AssetContainer extends AbstractScene {
         this.meshes.unshift(rootMesh);
         return rootMesh;
     }
+
+    /**
+     * Merge animations from this asset container into a scene
+     * @param scene is the instance of BABYLON.Scene to append to (default: last created scene)
+     * @param animatables set of animatables to retarget to a node from the scene
+     * @param targetConverter defines a function used to convert animation targets from the asset container to the scene (default: search node by name)
+     */
+    public mergeAnimationsTo(scene: Nullable<Scene> = EngineStore.LastCreatedScene, animatables: Animatable[], targetConverter: Nullable<(target: any) => Nullable<Node>> = null): void {
+        if (!scene) {
+            Logger.Error("No scene available to merge animations to");
+            return;
+        }
+
+        let _targetConverter = targetConverter ? targetConverter : (target: any) => { return scene.getBoneByName(target.name) || scene.getNodeByName(target.name); };
+
+        // Copy new node animations
+        let nodesInAC = this.getNodes();
+        nodesInAC.forEach((nodeInAC) => {
+            let nodeInScene = _targetConverter(nodeInAC);
+            if (nodeInScene !== null) {
+                // Remove old animations with same target property as a new one
+                for (let animationInAC of nodeInAC.animations) {
+                    // Doing treatment on an array for safety measure
+                    let animationsWithSameProperty = nodeInScene.animations.filter((animationInScene) => {
+                        return animationInScene.targetProperty === animationInAC.targetProperty;
+                    });
+                    for (let animationWithSameProperty of animationsWithSameProperty) {
+                        const index = nodeInScene.animations.indexOf(animationWithSameProperty, 0);
+                        if (index > -1) {
+                            nodeInScene.animations.splice(index, 1);
+                        }
+                    }
+                }
+
+                // Append new animations
+                nodeInScene.animations = nodeInScene.animations.concat(nodeInAC.animations);
+            }
+        });
+
+        // Copy new animation groups
+        this.animationGroups.slice().forEach((animationGroupInAC) => {
+            // Clone the animation group and all its animatables
+            animationGroupInAC.clone(animationGroupInAC.name, _targetConverter);
+
+            // Remove animatables related to the asset container
+            animationGroupInAC.animatables.forEach((animatable) => {
+                animatable.stop();
+            });
+        });
+
+        // Retarget animatables
+        animatables.forEach((animatable) => {
+            let target = _targetConverter(animatable.target);
+
+            if (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 asset container
+                scene.stopAnimation(animatable.target);
+            }
+        });
+    }
 }

+ 5 - 0
src/scene.ts

@@ -627,6 +627,11 @@ export class Scene extends AbstractScene implements IAnimatable {
     public onMeshImportedObservable = new Observable<AbstractMesh>();
 
     /**
+     * This Observable will when an animation file has been imported into the scene.
+     */
+    public onAnimationFileImportedObservable = new Observable<Scene>();
+
+    /**
      * Gets or sets a user defined funtion to select LOD from a mesh and a camera.
      * By default this function is undefined and Babylon.js will select LOD based on distance to camera
      */