소스 검색

Associated with #5638

David Catuhe 6 년 전
부모
커밋
c00ab39ccb

+ 32 - 11
inspector/src/components/actionTabs/actionTabs.scss

@@ -1,3 +1,5 @@
+$line-padding-left: 2px;
+
 #inspector-host {
     position: absolute;
     right: 0px;
@@ -184,7 +186,7 @@
                 }
 
                 .iconMessageLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 30px 1fr;
@@ -204,7 +206,7 @@
                 }
 
                 .textLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr auto;
@@ -255,7 +257,7 @@
                 }
 
                 .textInputLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr 120px;
@@ -331,7 +333,7 @@
                 }
 
                 .radioLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr 24px;
@@ -393,7 +395,7 @@
                 }
 
                 .vector3Line {
-                    padding-left: 5px;                    
+                    padding-left:$line-padding-left;                    
                     display: grid;
 
                     .firstLine {
@@ -454,7 +456,7 @@
                 }
 
                 .checkBoxLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr auto;
@@ -608,7 +610,7 @@
                 }
 
                 .floatLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr 120px;
@@ -688,7 +690,7 @@
                 }       
                 
                 .color3Line {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     display: grid;
 
                     .firstLine {
@@ -774,7 +776,7 @@
                 }     
                 
                 .listLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr auto;
@@ -805,6 +807,24 @@
                     grid-template-rows: 100%;
                     grid-template-columns: 100%;
                     
+                    .paneList {
+                        border-left: 3px solid transparent;
+                    }
+
+                    &:hover {  
+                        .paneList {                      
+                            border-left: 3px solid rgba(51, 122, 183, 0.8);
+                        }
+
+                        .paneContainer-content {
+                            .header {
+                                .title {   
+                                    border-left: 3px solid rgb(51, 122, 183);
+                                }
+                            }
+                        }
+                    }
+                    
                     .paneContainer-highlight-border {
                         grid-row: 1;
                         grid-column: 1;
@@ -830,8 +850,9 @@
                             padding-right: 5px;                        
                             cursor: pointer;
                             
-                            .title {
-                                margin-left: 5px;
+                            .title {                                
+                                border-left: 3px solid transparent;
+                                padding-left: 5px;
                                 grid-column: 1;
                                 display: flex;
                                 align-items: center;

+ 1 - 1
inspector/src/components/actionTabs/lines/textureLineComponent.tsx

@@ -48,7 +48,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         var size = texture.getSize();
         var ratio = size.width / size.height;
         var width = this.props.width;
-        var height = (width / ratio) | 0;
+        var height = (width / ratio) | 1;
 
         let passPostProcess: PostProcess;
 

+ 0 - 47
inspector/src/components/actionTabs/tabs/debugTabComponent.tsx

@@ -4,14 +4,11 @@ import { LineContainerComponent } from "../lineContainerComponent";
 import { CheckBoxLineComponent } from "../lines/checkBoxLineComponent";
 import { RenderGridPropertyGridComponent } from "./propertyGrids/renderGridPropertyGridComponent";
 
-import { SkeletonViewer } from "babylonjs/Debug/skeletonViewer";
 import { PhysicsViewer } from "babylonjs/Debug/physicsViewer";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 
 export class DebugTabComponent extends PaneComponent {
-    private _skeletonViewersEnabled = false;
     private _physicsViewersEnabled = false;
-    private _skeletonViewers = new Array<SkeletonViewer>();
 
     constructor(props: IPaneComponentProps) {
         super(props);
@@ -28,55 +25,12 @@ export class DebugTabComponent extends PaneComponent {
             scene.reservedDataStore = {};
         }
 
-        for (var mesh of scene.meshes) {
-            if (mesh.skeleton && mesh.reservedDataStore && mesh.reservedDataStore.skeletonViewer) {
-                this._skeletonViewers.push(mesh.reservedDataStore.skeletonViewer);
-            }
-        }
-
-        this._skeletonViewersEnabled = (this._skeletonViewers.length > 0);
         this._physicsViewersEnabled = scene.reservedDataStore.physicsViewer != null;
     }
 
     componentWillUnmount() {
     }
 
-    switchSkeletonViewers() {
-        this._skeletonViewersEnabled = !this._skeletonViewersEnabled;
-        const scene = this.props.scene;
-
-        if (this._skeletonViewersEnabled) {
-            for (var mesh of scene.meshes) {
-                if (mesh.skeleton) {
-                    var found = false;
-                    for (var sIndex = 0; sIndex < this._skeletonViewers.length; sIndex++) {
-                        if (this._skeletonViewers[sIndex].skeleton === mesh.skeleton) {
-                            found = true;
-                            break;
-                        }
-                    }
-                    if (found) {
-                        continue;
-                    }
-                    var viewer = new SkeletonViewer(mesh.skeleton, mesh, scene, true, 0);
-                    viewer.isEnabled = true;
-                    this._skeletonViewers.push(viewer);
-                    if (!mesh.reservedDataStore) {
-                        mesh.reservedDataStore = {};
-                    }
-                    mesh.reservedDataStore.skeletonViewer = viewer;
-                }
-            }
-        } else {
-            for (var index = 0; index < this._skeletonViewers.length; index++) {
-                this._skeletonViewers[index].mesh.reservedDataStore.skeletonViewer = null;
-                this._skeletonViewers[index].dispose();
-            }
-            this._skeletonViewers = [];
-
-        }
-    }
-
     switchPhysicsViewers() {
         this._physicsViewersEnabled = !this._physicsViewersEnabled;
         const scene = this.props.scene;
@@ -112,7 +66,6 @@ export class DebugTabComponent extends PaneComponent {
             <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="HELPERS">
                     <RenderGridPropertyGridComponent globalState={this.props.globalState} scene={scene} />
-                    <CheckBoxLineComponent label="Bones" isSelected={() => this._skeletonViewersEnabled} onSelect={() => this.switchSkeletonViewers()} />
                     <CheckBoxLineComponent label="Physics" isSelected={() => this._physicsViewersEnabled} onSelect={() => this.switchPhysicsViewers()} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="TEXTURE CHANNELS">

+ 20 - 0
inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx

@@ -75,6 +75,10 @@ import { SSAORenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pi
 import { SSAORenderingPipelinePropertyGridComponent } from './propertyGrids/postProcesses/ssaoRenderingPipelinePropertyGridComponent';
 import { SSAO2RenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline';
 import { SSAO2RenderingPipelinePropertyGridComponent } from './propertyGrids/postProcesses/ssao2RenderingPipelinePropertyGridComponent';
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+import { SkeletonPropertyGridComponent } from './propertyGrids/meshes/skeletonPropertyGridComponent';
+import { Bone } from 'babylonjs/Bones/bone';
+import { BonePropertyGridComponent } from './propertyGrids/meshes/bonePropertyGridComponent';
 
 export class PropertyGridTabComponent extends PaneComponent {
     private _timerIntervalId: number;
@@ -278,6 +282,22 @@ export class PropertyGridTabComponent extends PaneComponent {
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
             }
 
+            if (className.indexOf("Skeleton") !== -1) {
+                const skeleton = entity as Skeleton;
+                return (<SkeletonPropertyGridComponent skeleton={skeleton}
+                    globalState={this.props.globalState}
+                    lockObject={this._lockObject}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("Bone") !== -1) {
+                const bone = entity as Bone;
+                return (<BonePropertyGridComponent bone={bone}
+                    globalState={this.props.globalState}
+                    lockObject={this._lockObject}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
             if (className === "TextBlock") {
                 const textBlock = entity as TextBlock;
                 return (<TextBlockPropertyGridComponent textBlock={textBlock}

+ 192 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx

@@ -0,0 +1,192 @@
+import * as React from "react";
+
+import { Observable, Observer } from "babylonjs/Misc/observable";
+import { Scene } from "babylonjs/scene";
+
+import { PropertyChangedEvent } from "../../../propertyChangedEvent";
+import { ButtonLineComponent } from "../../lines/buttonLineComponent";
+import { LineContainerComponent } from "../../lineContainerComponent";
+import { SliderLineComponent } from "../../lines/sliderLineComponent";
+import { LockObject } from "./lockObject";
+import { GlobalState } from '../../../globalState';
+import { IAnimatable } from 'babylonjs/Misc/tools';
+import { Animation } from 'babylonjs/Animations/animation';
+import { Animatable } from 'babylonjs/Animations/animatable';
+import { AnimationPropertiesOverride } from 'babylonjs/Animations/animationPropertiesOverride';
+import { AnimationRange } from 'babylonjs/Animations/animationRange';
+import { CheckBoxLineComponent } from '../../lines/checkBoxLineComponent';
+import { Nullable } from 'babylonjs/types';
+import { FloatLineComponent } from '../../lines/floatLineComponent';
+import { TextLineComponent } from '../../lines/textLineComponent';
+
+interface IAnimationGridComponentProps {
+    globalState: GlobalState;
+    animatable: IAnimatable,
+    scene: Scene,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class AnimationGridComponent extends React.Component<IAnimationGridComponentProps, { currentFrame: number }> {
+    private _animations: Nullable<Animation[]> = null;
+    private _ranges: AnimationRange[];
+    private _animationControl = {
+        from: 0,
+        to: 0,
+        loop: false
+    }
+    private _runningAnimatable: Nullable<Animatable>;
+    private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
+    private _isPlaying = false;
+
+    constructor(props: IAnimationGridComponentProps) {
+        super(props);
+
+        this.state = { currentFrame: 0 };
+
+        const animatableAsAny = this.props.animatable as any;
+
+        this._ranges = animatableAsAny.getAnimationRanges ? animatableAsAny.getAnimationRanges() : [];
+        if (animatableAsAny.getAnimatables) {
+            const animatables = animatableAsAny.getAnimatables();
+            this._animations = new Array<Animation>();
+
+            animatables.forEach((animatable: IAnimatable) => {
+                this._animations!.push(...animatable.animations);
+            });
+
+            // Extract from and to
+            if (this._animations && this._animations.length) {
+                this._animations.forEach(animation => {
+                    let keys = animation.getKeys();
+
+                    if (keys && keys.length > 0) {
+                        if (keys[0].frame < this._animationControl.from) {
+                            this._animationControl.from = keys[0].frame;
+                        }
+                        const lastKeyIndex = keys.length - 1;
+                        if (keys[lastKeyIndex].frame > this._animationControl.to) {
+                            this._animationControl.to = keys[lastKeyIndex].frame;
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    playOrPause() {
+        const animatable = this.props.animatable;
+        this._isPlaying = this.props.scene.getAllAnimatablesByTarget(animatable).length > 0;
+
+        if (this._isPlaying) {
+            this.props.scene.stopAnimation(this.props.animatable);
+            this._runningAnimatable = null;
+        } else {
+            this._runningAnimatable = this.props.scene.beginAnimation(this.props.animatable, this._animationControl.from, this._animationControl.to, this._animationControl.loop);
+        }
+        this.forceUpdate();
+    }
+
+    componentWillMount() {
+        this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(() => {
+            if (!this._isPlaying || !this._runningAnimatable) {
+                return;
+            }
+            this.setState({ currentFrame: this._runningAnimatable.masterFrame });
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onBeforeRenderObserver) {
+            this.props.scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);
+            this._onBeforeRenderObserver = null;
+        }
+    }
+
+    onCurrentFrameChange(value: number) {
+        if (!this._runningAnimatable) {
+            return;
+        }
+
+        this._runningAnimatable.goToFrame(value);
+        this.setState({ currentFrame: value });
+    }
+
+    render() {
+        const animatable = this.props.animatable;
+        const animatableAsAny = this.props.animatable as any;
+
+        let animatablesForTarget = this.props.scene.getAllAnimatablesByTarget(animatable);
+        this._isPlaying = animatablesForTarget.length > 0;
+
+        if (this._isPlaying && !this._runningAnimatable) {
+            this._runningAnimatable = animatablesForTarget[0];
+        }
+
+        if (this._runningAnimatable) {
+            this._animationControl.from = this._runningAnimatable.fromFrame;
+            this._animationControl.to = this._runningAnimatable.toFrame;
+            this._animationControl.loop = this._runningAnimatable.loopAnimation;
+        }
+
+        return (
+            <div>
+                {
+                    (this._ranges.length > 0 || this._animations && this._animations.length > 0) &&
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATION OVERRIDE">
+                        <CheckBoxLineComponent label="Enable override" onSelect={value => {
+                            if (value) {
+                                animatableAsAny.animationPropertiesOverride = new AnimationPropertiesOverride();
+                                animatableAsAny.animationPropertiesOverride.blendingSpeed = 0.05;
+                            } else {
+                                animatableAsAny.animationPropertiesOverride = null;
+                            }
+                            this.forceUpdate();
+                        }} isSelected={() => animatableAsAny.animationPropertiesOverride != null} />
+                        {
+                            animatableAsAny.animationPropertiesOverride != null &&
+                            <div>
+                                <CheckBoxLineComponent label="Enable blending" target={animatableAsAny.animationPropertiesOverride} propertyName="enableBlending" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                                <SliderLineComponent label="Blending speed" target={animatableAsAny.animationPropertiesOverride} propertyName="blendingSpeed" minimum={0} maximum={0.1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            </div>
+                        }
+                    </LineContainerComponent>
+                }
+                {
+                    this._ranges.length > 0 &&
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATION RANGES">
+                        {
+                            this._ranges.map(range => {
+                                return (
+                                    <ButtonLineComponent key={range.name} label={range.name}
+                                        onClick={() => {
+                                            this._runningAnimatable = null;
+                                            this.props.scene.beginAnimation(animatable, range.from, range.to, true)
+                                        }} />
+                                );
+                            })
+                        }
+                    </LineContainerComponent>
+                }
+                {
+                    this._animations && this._animations.length > 0 &&
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATIONS">
+                        <TextLineComponent label="Count" value={this._animations.length.toString()} />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="From" target={this._animationControl} propertyName="from" />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="To" target={this._animationControl} propertyName="to" />
+                        <CheckBoxLineComponent label="Loop" onSelect={value => this._animationControl.loop = value} isSelected={() => this._animationControl.loop} />
+                        <ButtonLineComponent label={this._isPlaying ? "Stop" : "Play"} onClick={() => this.playOrPause()} />
+                        {
+                            this._isPlaying &&
+                            <SliderLineComponent ref="timeline" label="Current frame" minimum={this._animationControl.from} maximum={this._animationControl.to}
+                                step={(this._animationControl.to - this._animationControl.from) / 1000.0} directValue={this.state.currentFrame}
+                                onInput={value => this.onCurrentFrameChange(value)}
+                            />
+                        }
+                    </LineContainerComponent>
+                }
+            </div>
+        );
+    }
+
+}

+ 50 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent.tsx

@@ -0,0 +1,50 @@
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from '../../../../globalState';
+import { Bone } from 'babylonjs/Bones/bone';
+import { Vector3LineComponent } from '../../../lines/vector3LineComponent';
+import { QuaternionLineComponent } from '../../../lines/quaternionLineComponent';
+
+interface IBonePropertyGridComponentProps {
+    globalState: GlobalState;
+    bone: Bone,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class BonePropertyGridComponent extends React.Component<IBonePropertyGridComponentProps> {
+    constructor(props: IBonePropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const bone = this.props.bone;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="ID" value={bone.id} />
+                    <TextLineComponent label="Unique ID" value={bone.uniqueId.toString()} />
+                </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="TRANSFORMATIONS">
+                    <Vector3LineComponent label="Position" target={bone} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        !bone.rotationQuaternion &&
+                        <Vector3LineComponent label="Rotation" target={bone} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    {
+                        bone.rotationQuaternion &&
+                        <QuaternionLineComponent label="Rotation" target={bone} propertyName="rotationQuaternion" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    <Vector3LineComponent label="Scaling" target={bone} propertyName="scaling" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 101 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/skeletonPropertyGridComponent.tsx

@@ -0,0 +1,101 @@
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from '../../../../globalState';
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+import { AnimationGridComponent } from '../animationPropertyGridComponent';
+import { SkeletonViewer } from 'babylonjs/Debug/skeletonViewer';
+
+interface ISkeletonPropertyGridComponentProps {
+    globalState: GlobalState;
+    skeleton: Skeleton,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class SkeletonPropertyGridComponent extends React.Component<ISkeletonPropertyGridComponentProps> {
+    private _skeletonViewersEnabled = false;
+    private _skeletonViewers = new Array<SkeletonViewer>();
+
+    constructor(props: ISkeletonPropertyGridComponentProps) {
+        super(props);
+    }
+
+    switchSkeletonViewers() {
+        this._skeletonViewersEnabled = !this._skeletonViewersEnabled;
+        const scene = this.props.skeleton.getScene();
+
+        if (this._skeletonViewersEnabled) {
+            for (var mesh of scene.meshes) {
+                if (mesh.skeleton === this.props.skeleton) {
+                    var found = false;
+                    for (var sIndex = 0; sIndex < this._skeletonViewers.length; sIndex++) {
+                        if (this._skeletonViewers[sIndex].skeleton === mesh.skeleton) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (found) {
+                        continue;
+                    }
+                    var viewer = new SkeletonViewer(mesh.skeleton, mesh, scene, true, 0);
+                    viewer.isEnabled = true;
+                    this._skeletonViewers.push(viewer);
+                    if (!mesh.reservedDataStore) {
+                        mesh.reservedDataStore = {};
+                    }
+                    mesh.reservedDataStore.skeletonViewer = viewer;
+                }
+            }
+        } else {
+            for (var index = 0; index < this._skeletonViewers.length; index++) {
+                this._skeletonViewers[index].mesh.reservedDataStore.skeletonViewer = null;
+                this._skeletonViewers[index].dispose();
+            }
+            this._skeletonViewers = [];
+
+        }
+    }
+
+    componentWillMount() {
+        const scene = this.props.skeleton.getScene();
+
+        if (!scene) {
+            return;
+        }
+
+        if (!scene.reservedDataStore) {
+            scene.reservedDataStore = {};
+        }
+
+        for (var mesh of scene.meshes) {
+            if (mesh.skeleton === this.props.skeleton && mesh.reservedDataStore && mesh.reservedDataStore.skeletonViewer) {
+                this._skeletonViewers.push(mesh.reservedDataStore.skeletonViewer);
+            }
+        }
+
+        this._skeletonViewersEnabled = (this._skeletonViewers.length > 0);
+    }
+
+    render() {
+        const skeleton = this.props.skeleton;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="ID" value={skeleton.id} />
+                    <TextLineComponent label="Bone count" value={skeleton.bones.length.toString()} />
+                    <CheckBoxLineComponent label="Use texture to store matrices" target={skeleton} propertyName="useTextureToStoreBoneMatrices" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Debug mode" isSelected={() => this._skeletonViewersEnabled} onSelect={() => this.switchSkeletonViewers()} />
+                </LineContainerComponent>
+                <AnimationGridComponent globalState={this.props.globalState} animatable={skeleton} scene={skeleton.getScene()} lockObject={this.props.lockObject} />
+            </div>
+        );
+    }
+}

+ 31 - 0
inspector/src/components/sceneExplorer/entities/boneTreeItemComponent.tsx

@@ -0,0 +1,31 @@
+import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+import { faBone } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+import { Bone } from 'babylonjs/Bones/bone';
+
+interface IBoneTreeItemComponenttProps {
+    bone: Bone,
+    extensibilityGroups?: IExplorerExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class BoneTreeItemComponent extends React.Component<IBoneTreeItemComponenttProps> {
+    constructor(props: IBoneTreeItemComponenttProps) {
+        super(props);
+    }
+
+
+    render() {
+        const bone = this.props.bone;
+        return (
+            <div className="skeletonTools">
+                <TreeItemLabelComponent label={bone.name || "no name"} onClick={() => this.props.onClick()} icon={faBone} color="lightgray" />
+                {
+                    <ExtensionsComponent target={bone} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 31 - 0
inspector/src/components/sceneExplorer/entities/skeletonTreeItemComponent.tsx

@@ -0,0 +1,31 @@
+import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+import { faSkull } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+
+interface ISkeletonTreeItemComponentProps {
+    skeleton: Skeleton,
+    extensibilityGroups?: IExplorerExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class SkeletonTreeItemComponent extends React.Component<ISkeletonTreeItemComponentProps> {
+    constructor(props: ISkeletonTreeItemComponentProps) {
+        super(props);
+    }
+
+
+    render() {
+        const skeleton = this.props.skeleton;
+        return (
+            <div className="skeletonTools">
+                <TreeItemLabelComponent label={skeleton.name || "no name"} onClick={() => this.props.onClick()} icon={faSkull} color="gray" />
+                {
+                    <ExtensionsComponent target={skeleton} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 12 - 0
inspector/src/components/sceneExplorer/sceneExplorer.scss

@@ -500,6 +500,18 @@
             }
         }    
 
+        .skeletonTools {
+            grid-column: 2;
+            display: grid;
+            grid-template-columns: 1fr auto 5px;
+            align-items: center;
+
+            .extensions {
+                width: 20px;
+                grid-column: 2;
+            }
+        }  
+
         .title {
             grid-column: 1;
             background: transparent;

+ 4 - 0
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -226,6 +226,10 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 <SceneExplorerFilterComponent onFilter={(filter) => this.filterContent(filter)} />
                 <SceneTreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} scene={scene} onRefresh={() => this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.rootNodes} label="Nodes" offset={1} filter={this.state.filter} />
+                {
+                    scene.skeletons.length > 0 &&
+                    <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.skeletons} label="Skeletons" offset={1} filter={this.state.filter} />
+                }
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.materials} label="Materials" offset={1} filter={this.state.filter} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={textures} label="Textures" offset={1} filter={this.state.filter} />
                 {

+ 12 - 0
inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx

@@ -26,6 +26,10 @@ import { GlobalState } from "../globalState";
 import { PostProcessItemComponent } from './entities/postProcessTreeItemComponent';
 import { RenderingPipelineItemComponent } from './entities/renderingPipelineTreeItemComponent';
 import { PostProcessRenderPipeline } from 'babylonjs/PostProcesses/RenderPipeline/postProcessRenderPipeline';
+import { SkeletonTreeItemComponent } from './entities/skeletonTreeItemComponent';
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+import { BoneTreeItemComponent } from './entities/boneTreeItemComponent';
+import { Bone } from 'babylonjs/Bones/bone';
 
 
 interface ITreeItemSpecializedComponentProps {
@@ -64,6 +68,14 @@ export class TreeItemSpecializedComponent extends React.Component<ITreeItemSpeci
                 }
             }
 
+            if (className.indexOf("Skeleton") !== -1) {
+                return (<SkeletonTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} skeleton={entity as Skeleton} onClick={() => this.onClick()} />);
+            }
+
+            if (className.indexOf("Bone") !== -1) {
+                return (<BoneTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} bone={entity as Bone} onClick={() => this.onClick()} />);
+            }
+
             if (className.indexOf("TransformNode") !== -1) {
                 return (<TransformNodeItemComponent extensibilityGroups={this.props.extensibilityGroups} transformNode={entity as TransformNode} onClick={() => this.onClick()} />);
             }

+ 8 - 0
src/Bones/bone.ts

@@ -103,6 +103,14 @@ export class Bone extends Node {
         }
     }
 
+    /**
+     * Gets the current object class name.
+     * @return the class name
+     */
+    public getClassName(): string {
+        return "Bone";
+    }
+
     // Members
 
     /**

+ 17 - 3
src/Bones/skeleton.ts

@@ -126,6 +126,22 @@ export class Skeleton implements IAnimatable {
         this._canUseTextureForBones = engineCaps.textureFloat && engineCaps.maxVertexTextureImageUnits > 0;
     }
 
+    /**
+     * Gets the current object class name.
+     * @return the class name
+     */
+    public getClassName(): string {
+        return "Skeleton";
+    }
+
+    /**
+     * Returns an array containing the root bones    
+     * @returns an array containing the root bones
+     */
+    public getChildren(): Array<Bone> {
+        return this.bones.filter(b => !b.parent);
+    }
+
     // Members
     /**
      * Gets the list of transform matrices to send to shaders (one matrix per bone)
@@ -247,10 +263,8 @@ export class Skeleton implements IAnimatable {
     public getAnimationRanges(): Nullable<AnimationRange>[] {
         var animationRanges: Nullable<AnimationRange>[] = [];
         var name: string;
-        var i: number = 0;
         for (name in this._ranges) {
-            animationRanges[i] = this._ranges[name];
-            i++;
+            animationRanges.push(this._ranges[name]);
         }
         return animationRanges;
     }

+ 13 - 0
src/node.ts

@@ -656,6 +656,19 @@ export class Node implements IBehaviorAware<Node> {
     }
 
     /**
+     * Gets the list of all animation ranges defined on this node
+     * @returns an array
+     */
+    public getAnimationRanges(): Nullable<AnimationRange>[] {
+        var animationRanges: Nullable<AnimationRange>[] = [];
+        var name: string;
+        for (name in this._ranges) {
+            animationRanges.push(this._ranges[name]);
+        }
+        return animationRanges;
+    }
+
+    /**
      * Will start the animation sequence
      * @param name defines the range frames for animation sequence
      * @param loop defines if the animation should loop (false by default)