Bläddra i källkod

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

Popov72 4 år sedan
förälder
incheckning
8c99aec8a5
31 ändrade filer med 1349 tillägg och 368 borttagningar
  1. 6 6
      Playground/index-local.html
  2. 15 5
      Viewer/tests/validation/validation.js
  3. 53 1
      dist/preview release/glTF2Interface/babylon.glTF2Interface.d.ts
  4. 11 1
      dist/preview release/meshopt_decoder.module.js
  5. 83 20
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx
  6. 23 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx
  7. 416 111
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx
  8. 69 4
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx
  9. 71 11
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx
  10. 16 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx
  11. 71 20
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx
  12. 57 1
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx
  13. 26 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx
  14. 20 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx
  15. 3 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx
  16. 68 65
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/playhead.tsx
  17. 22 1
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx
  18. 2 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx
  19. 67 5
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx
  20. 14 2
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx
  21. 139 37
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx
  22. 29 27
      loaders/src/glTF/2.0/Extensions/EXT_meshopt_compression.ts
  23. 10 2
      loaders/src/glTF/2.0/glTFLoader.ts
  24. 1 1
      loaders/src/glTF/glTFValidation.ts
  25. 5 5
      localDev/index-views.html
  26. 5 5
      localDev/index.html
  27. 5 5
      sandbox/public/index-local.html
  28. 2 10
      src/Meshes/Compression/dracoCompression.ts
  29. BIN
      tests/validation/ReferenceImages/gltfBuggyMeshopt.png
  30. 25 18
      tests/validation/config.json
  31. 15 5
      tests/validation/validation.js

+ 6 - 6
Playground/index-local.html

@@ -62,19 +62,19 @@
             }
 
             // Load the scripts + map file to allow vscode debug.
-            BABYLONDEVTOOLS.Loader       
+            BABYLONDEVTOOLS.Loader
                 .require("index.js")
                 .load(() => {
                     BABYLON.DracoCompression.Configuration.decoder = {
-                        wasmUrl: "../dist/preview%20release/draco_wasm_wrapper_gltf.js",
-                        wasmBinaryUrl: "../dist/preview%20release/draco_decoder_gltf.wasm",
-                        fallbackUrl: "../dist/preview%20release/draco_decoder_gltf.js"
+                        wasmUrl: GetAbsoluteUrl("../dist/preview%20release/draco_wasm_wrapper_gltf.js"),
+                        wasmBinaryUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.wasm"),
+                        fallbackUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.js")
                     };
                     BABYLON.GLTFValidation.Configuration = {
-                        url: "../dist/preview%20release/gltf_validator.js"
+                        url: GetAbsoluteUrl("../dist/preview%20release/gltf_validator.js")
                     };
                     BABYLON.GLTF2.Loader.Extensions.EXT_meshopt_compression.DecoderPath =
-                        "../dist/preview%20release/meshopt_decoder.module.js";
+                        GetAbsoluteUrl("../dist/preview%20release/meshopt_decoder.js");
                     BABYLON.KhronosTextureContainer2.URLConfig = {
                         jsDecoderModule: GetAbsoluteUrl("../dist/preview%20release/babylon.ktx2Decoder.js"),
                         wasmUASTCToASTC: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),

+ 15 - 5
Viewer/tests/validation/validation.js

@@ -304,13 +304,23 @@ function init() {
     BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental = true;
 
     BABYLON.DracoCompression.Configuration.decoder = {
-        wasmUrl: "../../dist/preview%20release/draco_wasm_wrapper_gltf.js",
-        wasmBinaryUrl: "../../dist/preview%20release/draco_decoder_gltf.wasm",
-        fallbackUrl: "../../dist/preview%20release/draco_decoder_gltf.js"
+        wasmUrl: GetAbsoluteUrl("../../dist/preview%20release/draco_wasm_wrapper_gltf.js"),
+        wasmBinaryUrl: GetAbsoluteUrl("../../dist/preview%20release/draco_decoder_gltf.wasm"),
+        fallbackUrl: GetAbsoluteUrl("../../dist/preview%20release/draco_decoder_gltf.js")
     };
-
     BABYLON.GLTFValidation.Configuration = {
-        url: "../../dist/preview%20release/gltf_validator.js"
+        url: GetAbsoluteUrl("../../dist/preview%20release/gltf_validator.js")
+    };
+    BABYLON.GLTF2.Loader.Extensions.EXT_meshopt_compression.DecoderPath =
+        GetAbsoluteUrl("../../dist/preview%20release/meshopt_decoder.js");
+    BABYLON.KhronosTextureContainer2.URLConfig = {
+        jsDecoderModule: GetAbsoluteUrl("../../dist/preview%20release/babylon.ktx2Decoder.js"),
+        wasmUASTCToASTC: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),
+        wasmUASTCToBC7: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_bc7.wasm"),
+        wasmUASTCToRGBA_UNORM: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_unorm.wasm"),
+        wasmUASTCToRGBA_SRGB: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_srgb.wasm"),
+        jsMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.js"),
+        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
     };
 
     BABYLON.KhronosTextureContainer2.URLConfig = {

+ 53 - 1
dist/preview release/glTF2Interface/babylon.glTF2Interface.d.ts

@@ -34,6 +34,7 @@ declare module BABYLON.GLTF2 {
          */
         FLOAT = 5126,
     }
+
     /**
      * Specifies if the attirbute is a scalar, vector, or matrix
      */
@@ -67,6 +68,7 @@ declare module BABYLON.GLTF2 {
          */
         MAT4 = "MAT4",
     }
+
     /**
      * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates
      */
@@ -88,6 +90,7 @@ declare module BABYLON.GLTF2 {
          */
         WEIGHTS = "weights",
     }
+
     /**
      * Interpolation algorithm
      */
@@ -105,6 +108,7 @@ declare module BABYLON.GLTF2 {
          */
         CUBICSPLINE = "CUBICSPLINE",
     }
+
     /**
      * A camera's projection.  A node can reference a camera to apply a transform to place the camera in the scene
      */
@@ -118,6 +122,7 @@ declare module BABYLON.GLTF2 {
          */
         ORTHOGRAPHIC = "orthographic",
     }
+
     /**
      * The mime-type of the image
      */
@@ -131,6 +136,7 @@ declare module BABYLON.GLTF2 {
          */
         PNG = "image/png",
     }
+
     /**
      * The alpha rendering mode of the material
      */
@@ -148,6 +154,7 @@ declare module BABYLON.GLTF2 {
          */
         BLEND = "BLEND",
     }
+
     /**
      * The type of the primitives to render
      */
@@ -181,6 +188,7 @@ declare module BABYLON.GLTF2 {
          */
         TRIANGLE_FAN = 6,
     }
+
     /**
      * Magnification filter.  Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR)
      */
@@ -194,6 +202,7 @@ declare module BABYLON.GLTF2 {
          */
         LINEAR = 9729,
     }
+
     /**
      * Minification filter.  All valid values correspond to WebGL enums
      */
@@ -223,6 +232,7 @@ declare module BABYLON.GLTF2 {
          */
         LINEAR_MIPMAP_LINEAR = 9987,
     }
+
     /**
      * S (U) wrapping mode.  All valid values correspond to WebGL enums
      */
@@ -240,6 +250,7 @@ declare module BABYLON.GLTF2 {
          */
         REPEAT = 10497,
     }
+
     /**
      * glTF Property
      */
@@ -255,6 +266,7 @@ declare module BABYLON.GLTF2 {
          */
         extras?: any;
     }
+
     /**
      * glTF Child of Root Property
      */
@@ -264,6 +276,7 @@ declare module BABYLON.GLTF2 {
          */
         name?: string;
     }
+
     /**
      * Indices of those attributes that deviate from their initialization value
      */
@@ -281,6 +294,7 @@ declare module BABYLON.GLTF2 {
          */
         componentType: AccessorComponentType;
     }
+
     /**
      * Array of size accessor.sparse.count times number of components storing the displaced accessor attributes pointed by accessor.sparse.indices
      */
@@ -294,6 +308,7 @@ declare module BABYLON.GLTF2 {
          */
         byteOffset?: number;
     }
+
     /**
      * Sparse storage of attributes that deviate from their initialization value
      */
@@ -311,6 +326,7 @@ declare module BABYLON.GLTF2 {
          */
         values: IAccessorSparseValues;
     }
+
     /**
      * A typed view into a bufferView.  A bufferView contains raw binary data.  An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer
      */
@@ -352,6 +368,7 @@ declare module BABYLON.GLTF2 {
          */
         sparse?: IAccessorSparse;
     }
+
     /**
      * Targets an animation's sampler at a node's property
      */
@@ -365,6 +382,7 @@ declare module BABYLON.GLTF2 {
          */
         target: IAnimationChannelTarget;
     }
+
     /**
      * The index of the node and TRS property that an animation channel targets
      */
@@ -378,6 +396,7 @@ declare module BABYLON.GLTF2 {
          */
         path: AnimationChannelTargetPath;
     }
+
     /**
      * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target)
      */
@@ -395,6 +414,7 @@ declare module BABYLON.GLTF2 {
          */
         output: number;
     }
+
     /**
      * A keyframe animation
      */
@@ -408,6 +428,7 @@ declare module BABYLON.GLTF2 {
          */
         samplers: IAnimationSampler[];
     }
+
     /**
      * Metadata about the glTF asset
      */
@@ -429,6 +450,7 @@ declare module BABYLON.GLTF2 {
          */
         minVersion?: string;
     }
+
     /**
      * A buffer points to binary geometry, animation, or skins
      */
@@ -442,6 +464,7 @@ declare module BABYLON.GLTF2 {
          */
         byteLength: number;
     }
+
     /**
      * A view into a buffer generally representing a subset of the buffer
      */
@@ -463,6 +486,7 @@ declare module BABYLON.GLTF2 {
          */
         byteStride?: number;
     }
+
     /**
      * An orthographic camera containing properties to create an orthographic projection matrix
      */
@@ -484,6 +508,7 @@ declare module BABYLON.GLTF2 {
          */
         znear: number;
     }
+
     /**
      * A perspective camera containing properties to create a perspective projection matrix
      */
@@ -505,6 +530,7 @@ declare module BABYLON.GLTF2 {
          */
         znear: number;
     }
+
     /**
      * A camera's projection.  A node can reference a camera to apply a transform to place the camera in the scene
      */
@@ -522,6 +548,7 @@ declare module BABYLON.GLTF2 {
          */
         type: CameraType;
     }
+
     /**
      * Image data used to create a texture. Image can be referenced by URI or bufferView index. mimeType is required in the latter case
      */
@@ -539,6 +566,7 @@ declare module BABYLON.GLTF2 {
          */
         bufferView?: number;
     }
+
     /**
      * Material Normal Texture Info
      */
@@ -548,6 +576,7 @@ declare module BABYLON.GLTF2 {
          */
         scale?: number;
     }
+
     /**
      * Material Occlusion Texture Info
      */
@@ -557,6 +586,7 @@ declare module BABYLON.GLTF2 {
          */
         strength?: number;
     }
+
     /**
      * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology
      */
@@ -582,6 +612,7 @@ declare module BABYLON.GLTF2 {
          */
         metallicRoughnessTexture?: ITextureInfo;
     }
+
     /**
      * The material appearance of a primitive
      */
@@ -619,6 +650,7 @@ declare module BABYLON.GLTF2 {
          */
         doubleSided?: boolean;
     }
+
     /**
      * Geometry to be rendered with the given material
      */
@@ -648,6 +680,7 @@ declare module BABYLON.GLTF2 {
             [name: string]: number;
         }[];
     }
+
     /**
      * A set of primitives to be rendered.  A node can contain one mesh.  A node's transform places the mesh in the scene
      */
@@ -661,6 +694,7 @@ declare module BABYLON.GLTF2 {
          */
         weights?: number[];
     }
+
     /**
      * A node in the node hierarchy
      */
@@ -702,6 +736,7 @@ declare module BABYLON.GLTF2 {
          */
         weights?: number[];
     }
+
     /**
      * Texture sampler properties for filtering and wrapping modes
      */
@@ -723,6 +758,7 @@ declare module BABYLON.GLTF2 {
          */
         wrapT?: TextureWrapMode;
     }
+
     /**
      * The root nodes of a scene
      */
@@ -732,6 +768,7 @@ declare module BABYLON.GLTF2 {
          */
         nodes: number[];
     }
+
     /**
      * Joints and matrices defining a skin
      */
@@ -749,6 +786,7 @@ declare module BABYLON.GLTF2 {
          */
         joints: number[];
     }
+
     /**
      * A texture and its sampler
      */
@@ -762,6 +800,7 @@ declare module BABYLON.GLTF2 {
          */
         source: number;
     }
+
     /**
      * Reference to a texture
      */
@@ -775,6 +814,7 @@ declare module BABYLON.GLTF2 {
          */
         texCoord?: number;
     }
+
     /**
      * The root object for a glTF asset
      */
@@ -1213,6 +1253,18 @@ declare module BABYLON.GLTF2 {
         ids: number[];
     }
 
+    /**
+     * Interfaces from the EXT_meshopt_compression extension
+     */
 
-
+    /** @hidden */
+    interface IEXTMeshoptCompression {
+        buffer: number;
+        byteOffset?: number;
+        byteLength: number;
+        byteStride: number;
+        count: number;
+        mode: "ATTRIBUTES" | "TRIANGLES" | "INDICES";
+        filter?: "NONE" | "OCTAHEDRAL" | "QUATERNION" | "EXPONENTIAL";
+    }
 }

+ 11 - 1
dist/preview release/meshopt_decoder.module.js

@@ -110,4 +110,14 @@ var MeshoptDecoder = (function() {
 	};
 })();
 
-export { MeshoptDecoder };
+// UMD-style export
+if (typeof exports === 'object' && typeof module === 'object')
+	module.exports = MeshoptDecoder;
+else if (typeof define === 'function' && define['amd'])
+	define([], function() {
+		return MeshoptDecoder;
+	});
+else if (typeof exports === 'object')
+	exports["MeshoptDecoder"] = MeshoptDecoder;
+else
+	(typeof self !== 'undefined' ? self : this).MeshoptDecoder = MeshoptDecoder;

+ 83 - 20
inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx

@@ -3,8 +3,8 @@ import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
 import { Observable } from "babylonjs/Misc/observable";
 import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
 import { Animation } from "babylonjs/Animations/animation";
-//import { Vector2, Vector3, Quaternion } from "babylonjs/Maths/math.vector";
-//import { Color3, Color4 } from "babylonjs/Maths/math.color";
+//import { Vector2, Vector3, Quaternion } from "babylonjs/Maths/math.vector"; // Remove comment lines when these imports are supported
+//import { Color3, Color4 } from "babylonjs/Maths/math.color"; // Remove comment lines when these imports are supported
 import { IAnimatable } from "babylonjs/Animations/animatable.interface";
 import { IAnimationKey } from "babylonjs/Animations/animationKey";
 
@@ -21,7 +21,16 @@ interface IAddAnimationProps {
 }
 
 /**
- * Controls the creation of a new animation
+ * Controls the creation of a new animation.
+ * @property {boolean} isOpen controls if the add animation pannel is open or closed in the editor controls;
+ * @property {()=>void} close sends the message to close this panel setting isOpen to false;
+ * @property {IAnimatable} entity is the animation object to add the animation to;
+ * @property {Observable<PropertyChangedEvent>} onPropertyChangedObservable is the registered observable
+ * @property {(message: string) => void} setNotificationMessage sends the message string to display errors on the message box
+ * @property {() => void} finishedUpdate tells the parent component the update on the animation collection has completed
+ * @property { (animation: Animation) => void} addedNewAnimation sends the animation to the editor to process how its is rendered
+ * @property {number} fps Frames per second of the animation.
+ * @property {Animation | undefined} selectedToUpdate the selected animation so we can update the renderer curve.
  */
 export class AddAnimation extends React.Component<
     IAddAnimationProps,
@@ -44,12 +53,19 @@ export class AddAnimation extends React.Component<
             animationName: editingAnimation ? editingAnimation.name : "",
             animationTargetPath: "",
             animationType: editingAnimation ? editingAnimation.dataType : Animation.ANIMATIONTYPE_FLOAT,
-            loopMode: editingAnimation ? editingAnimation.loopMode ?? Animation.ANIMATIONLOOPMODE_CYCLE : Animation.ANIMATIONLOOPMODE_CYCLE,
+            loopMode: editingAnimation
+                ? editingAnimation.loopMode ?? Animation.ANIMATIONLOOPMODE_CYCLE
+                : Animation.ANIMATIONLOOPMODE_CYCLE,
             animationTargetProperty: editingAnimation ? editingAnimation.targetProperty : "",
             isUpdating: editingAnimation ? true : false,
         };
     }
 
+    /**
+     * We decide wether the animation will be added or edited
+     * @param prevProps Previous props.
+     * @param prevState Previous state.
+     */
     componentDidUpdate(prevProps: IAddAnimationProps, prevState: any) {
         if (this.props.selectedToUpdate !== undefined && this.props.selectedToUpdate !== prevProps.selectedToUpdate) {
             this.setState(this.setInitialState(this.props.selectedToUpdate));
@@ -60,6 +76,13 @@ export class AddAnimation extends React.Component<
         }
     }
 
+    /**
+     * Updates the animation with the correct properties
+     * Updates its name, loopmode and targetProperty.
+     * This allows the edition of the animation in the curve editor.
+     * We can only update these props for the animation.
+     * Keyframes of course are updated on the curve editor.
+     */
     updateAnimation = () => {
         if (this.props.selectedToUpdate !== undefined) {
             const oldNameValue = this.props.selectedToUpdate.name;
@@ -78,6 +101,10 @@ export class AddAnimation extends React.Component<
         }
     };
 
+    /**
+     * Returns the animation type as string
+     * @param type Type of animation so we return a string.
+     */
     getTypeAsString(type: number) {
         switch (type) {
             case Animation.ANIMATIONTYPE_FLOAT:
@@ -99,6 +126,14 @@ export class AddAnimation extends React.Component<
         }
     }
 
+    /**
+     * Process the creation of a new animation.
+     * We verify is the property to animate is present on the object
+     * We verify if the property derives from a correct class *type of animation to add an animation
+     * At the end we create an empty animation named animation.
+     * The animation keys array is set to empty so we can add keyframes in the editor.
+     * We return the animation to the curve editor and update the entity animation collection
+     */
     addAnimation = () => {
         if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
             let matchTypeTargetProperty = this.state.animationTargetProperty.split(".");
@@ -131,7 +166,9 @@ export class AddAnimation extends React.Component<
                             break;
                     }
                 } else {
-                    this.props.setNotificationMessage(`The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`);
+                    this.props.setNotificationMessage(
+                        `The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`
+                    );
                 }
             } else if (matchTypeTargetProperty.length > 1) {
                 let matchProp = (this.props.entity as any)[matchTypeTargetProperty[0]];
@@ -144,18 +181,33 @@ export class AddAnimation extends React.Component<
             }
 
             if (matched) {
-                let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find((anim) => anim.targetProperty === this.state.animationTargetProperty, this);
+                let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find(
+                    (anim) => anim.targetProperty === this.state.animationTargetProperty,
+                    this
+                );
 
-                let alreadyAnimationName = (this.props.entity as IAnimatable).animations?.find((anim) => anim.name === this.state.animationName, this);
+                let alreadyAnimationName = (this.props.entity as IAnimatable).animations?.find(
+                    (anim) => anim.name === this.state.animationName,
+                    this
+                );
 
                 if (alreadyAnimatedProperty) {
-                    this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" already has an animation`);
+                    this.props.setNotificationMessage(
+                        `The property "${this.state.animationTargetProperty}" already has an animation`
+                    );
                 } else if (alreadyAnimationName) {
-                    this.props.setNotificationMessage(`There is already an animation with the name: "${this.state.animationName}"`);
+                    this.props.setNotificationMessage(
+                        `There is already an animation with the name: "${this.state.animationName}"`
+                    );
                 } else {
-                    let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, this.props.fps, animationDataType);
+                    let animation = new Animation(
+                        this.state.animationName,
+                        this.state.animationTargetProperty,
+                        this.props.fps,
+                        animationDataType
+                    );
 
-                    // Start with two keyframes
+                    // Start with empty keyframes
                     var keys: IAnimationKey[] = [];
 
                     animation.setKeys(keys);
@@ -166,7 +218,7 @@ export class AddAnimation extends React.Component<
                         this.raiseOnPropertyChanged(updatedCollection, store);
                         this.props.entity.animations = updatedCollection;
                         this.props.addedNewAnimation(animation);
-                        //Cleaning form fields
+                        //Here we clean the form fields
                         this.setState({
                             animationName: "",
                             animationTargetPath: "",
@@ -177,7 +229,11 @@ export class AddAnimation extends React.Component<
                     }
                 }
             } else {
-                this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" is not a "${this.getTypeAsString(this.state.animationType)}" type`);
+                this.props.setNotificationMessage(
+                    `The property "${this.state.animationTargetProperty}" is not a "${this.getTypeAsString(
+                        this.state.animationType
+                    )}" type`
+                );
             }
         } else {
             this.props.setNotificationMessage(`You need to provide a name and target property.`);
@@ -248,21 +304,26 @@ export class AddAnimation extends React.Component<
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Property</label>
-                            <input type="text" value={this.state.animationTargetProperty} onChange={this.handlePropertyChange}></input>
+                            <input
+                                type="text"
+                                value={this.state.animationTargetProperty}
+                                onChange={this.handlePropertyChange}
+                            ></input>
                         </div>
                     )}
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Type</label>
                             <select onChange={this.handleTypeChange} value={this.state.animationType}>
+                                {/** Uncomment the following lines when other animation types are available */}
                                 {/* <option value={Animation.ANIMATIONTYPE_COLOR3}>Color3</option>
-                <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option> */}
+                                    <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option> */}
                                 <option value={Animation.ANIMATIONTYPE_FLOAT}>Float</option>
                                 {/* <option value={Animation.ANIMATIONTYPE_VECTOR3}>Vector3</option>
-                <option value={Animation.ANIMATIONTYPE_VECTOR2}>Vector2</option>
-                <option value={Animation.ANIMATIONTYPE_QUATERNION}>
-                  Quaternion
-                </option> */}
+                                    <option value={Animation.ANIMATIONTYPE_VECTOR2}>Vector2</option>
+                                    <option value={Animation.ANIMATIONTYPE_QUATERNION}>
+                                        Quaternion
+                                    </option> */}
                             </select>
                         </div>
                     )}
@@ -276,7 +337,9 @@ export class AddAnimation extends React.Component<
                     </div>
                     <div className="confirm-buttons">
                         <ButtonLineComponent label={confirmLabel} onClick={confirmHandleOnClick} />
-                        {this.props.entity.animations?.length !== 0 ? <ButtonLineComponent label={"Cancel"} onClick={this.props.close} /> : null}
+                        {this.props.entity.animations?.length !== 0 ? (
+                            <ButtonLineComponent label={"Cancel"} onClick={this.props.close} />
+                        ) : null}
                     </div>
                 </div>
             </div>

+ 23 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -2,18 +2,36 @@ import * as React from "react";
 import { Vector2 } from "babylonjs/Maths/math.vector";
 
 interface IAnchorSvgPointProps {
+    // Keyframe point
     control: Vector2;
+    // Anchor point
     anchor: Vector2;
+    // Is currently active
     active: boolean;
+    // Type of control point (left/right)
     type: string;
+    // keyframe index
     index: string;
+    // Is selected
     selected: boolean;
+    // Event to set selected
     selectControlPoint: (id: string) => void;
+    // How many frames are currently in the canvas
     framesInCanvasView: { from: number; to: number };
 }
 
 /**
  * Renders the control point to a keyframe.
+ * Each keyframe has left and right control points to control de tangent of the curve
+ * This controls the inTangent and outTangent values for the keyframe in the animation.
+ * @property {Vector2} control is the control point to control de curve tangent
+ * @property {Vector2} anchor represents the Keyframe point which acts origin point.
+ * @property {boolen} active tells the component if the control point is currently active
+ * @property {string} type (left/right) if the control will be the left or right control point
+ * @property {boolean} selected if the control point is currently selected. If selected we can move the control point and will become active
+ * @property {(id: string) => void;} selectControlPoint sends the id of the control point to the parent component to tell if it is selected
+ * @property {{ from: number; to: number }} framesInCanvasView controls from/to which keyframe should the control point can expand and control de curve
+ * The frames in canvas tells us how many frames are currently visible in the canvas and therefore control the width of the line between the control and anchor point
  */
 export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, { visiblePoint: Vector2 }> {
     constructor(props: IAnchorSvgPointProps) {
@@ -31,6 +49,11 @@ export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, { visi
         this.props.selectControlPoint(this.props.type);
     };
 
+    /**
+     * Controls where should we render the visible point (representing the control point)
+     * The visible control point differs from the control point for UX reasons. The control point
+     * expands beyond the visible canvas.
+     */
     setVisiblePoint() {
         const quarterDistance = (this.props.framesInCanvasView.to - this.props.framesInCanvasView.from) / 10;
         const distanceOnFlat = Math.abs(this.props.anchor.x - this.props.control.x);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 416 - 111
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx


+ 69 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx

@@ -8,13 +8,21 @@ import { IconButtonLineComponent } from "../../../lines/iconButtonLineComponent"
 import { Nullable } from "babylonjs/types";
 
 interface IAnimationListTreeProps {
+    // If the animation is targeted animation or not
     isTargetedAnimation: boolean;
+    // The entity that is being targetd by the animations
     entity: IAnimatable | TargetedAnimation;
+    // The currently selected animations
     selected: Animation | null;
+    // The obeservable
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    // Event to send the selected animation and the coordinate to render the correct curve
     selectAnimation: (selected: Animation, coordinate?: SelectedCoordinate) => void;
+    // Event to empty the animation list
     empty: () => void;
+    // Event to edit the selected animation
     editAnimation: (selected: Animation) => void;
+    // Event to deselect the animation
     deselectAnimation: () => void;
 }
 
@@ -26,6 +34,7 @@ interface Item {
     open: boolean;
 }
 
+// Collection of coordinates available in different animated target property types.
 export enum SelectedCoordinate {
     x = 0,
     y = 1,
@@ -60,7 +69,10 @@ export class AnimationListTree extends React.Component<
     constructor(props: IAnimationListTreeProps) {
         super(props);
 
-        const animations = this.props.entity instanceof TargetedAnimation ? (this.props.entity as TargetedAnimation).animation : (this.props.entity as IAnimatable).animations;
+        const animations =
+            this.props.entity instanceof TargetedAnimation
+                ? (this.props.entity as TargetedAnimation).animation
+                : (this.props.entity as IAnimatable).animations;
 
         this.state = {
             selectedCoordinate: 0,
@@ -70,6 +82,10 @@ export class AnimationListTree extends React.Component<
         };
     }
 
+    /**
+     * Set the animation list if has changed properties
+     * @param prevProps previous properties
+     */
     componentDidUpdate(prevProps: IAnimationListTreeProps) {
         if (this.props.entity instanceof TargetedAnimation) {
             if ((this.props.entity as TargetedAnimation).animation !== (prevProps.entity as TargetedAnimation).animation) {
@@ -88,6 +104,9 @@ export class AnimationListTree extends React.Component<
         }
     }
 
+    /**
+     * Delete animation from list
+     */
     deleteAnimation = () => {
         let currentSelected = this.props.selected;
         if (this.props.entity instanceof TargetedAnimation) {
@@ -120,6 +139,11 @@ export class AnimationListTree extends React.Component<
         }
     };
 
+    /**
+     * Update the animations collection
+     * @param newValue new animation list
+     * @param previousValue previous animation list
+     */
     raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
         if (!this.props.onPropertyChangedObservable) {
             return;
@@ -133,6 +157,9 @@ export class AnimationListTree extends React.Component<
         });
     }
 
+    /**
+     * Renders the animation list
+     */
     generateList() {
         let animationList =
             (this.props.entity as IAnimatable).animations &&
@@ -151,6 +178,10 @@ export class AnimationListTree extends React.Component<
         return animationList ?? null;
     }
 
+    /**
+     * Open or closes the animation to show its coordinate animations
+     * @param index Animation index
+     */
     toggleProperty(index: number) {
         if (this.state.animationList) {
             const updated = this.state.animationList.map((a) => {
@@ -163,14 +194,30 @@ export class AnimationListTree extends React.Component<
         }
     }
 
+    /**
+     * Select the animation to render
+     * @param animation Selected animation
+     * @param coordinate Selected coordinate (x, y, z)
+     * @param index Index
+     */
     setSelectedCoordinate(animation: Animation, coordinate: SelectedCoordinate, index: number) {
         this.setState({ selectedCoordinate: coordinate, selectedAnimation: index });
         this.props.selectAnimation(animation, coordinate);
     }
 
+    /**
+     * Renders the coordinate belonging to an animation
+     * @param i Index
+     * @param animation Selected animation
+     * @param coordinate Coordinate name
+     * @param color Color identifier
+     * @param selectedCoordinate Selected coordinate (x, y, z)
+     */
     coordinateItem(i: number, animation: Animation, coordinate: string, color: string, selectedCoordinate: SelectedCoordinate) {
         const setSelectedCoordinate = () => this.setSelectedCoordinate(animation, selectedCoordinate, i);
-        const handleClass = `handle-indicator ${this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"}`;
+        const handleClass = `handle-indicator ${
+            this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"
+        }`;
         return (
             <li key={`${i}_${coordinate}`} id={`${i}_${coordinate}`} className="property" style={{ color: color }} onClick={setSelectedCoordinate}>
                 <div className={handleClass}></div>
@@ -179,13 +226,22 @@ export class AnimationListTree extends React.Component<
         );
     }
 
+    /**
+     * Render animation
+     * @param animation selected animations
+     * @param i index
+     * @param childrenElements its coordinate (x,y,z) animations
+     */
     typeAnimationItem(animation: Animation, i: number, childrenElements: ItemCoordinate[]) {
         const editAnimation = () => this.props.editAnimation(animation);
         const selectAnimation = () => this.props.selectAnimation(animation);
         const toggle = () => this.toggleProperty(i);
         return (
             <li className={this.props.selected && this.props.selected.name === animation.name ? "property sub active" : "property sub"} key={i}>
-                <div className={`animation-arrow ${this.state.animationList && this.state.animationList[i].open ? "" : "flip"}`} onClick={toggle}></div>
+                <div
+                    className={`animation-arrow ${this.state.animationList && this.state.animationList[i].open ? "" : "flip"}`}
+                    onClick={toggle}
+                ></div>
                 <p onClick={selectAnimation}>{animation.targetProperty}</p>
                 <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />
                 {!((this.props.entity as TargetedAnimation).getClassName() === "TargetedAnimation") ? (
@@ -202,13 +258,22 @@ export class AnimationListTree extends React.Component<
         );
     }
 
+    /**
+     * Render animation item
+     * @param animation Selected aniamtion
+     * @param i index
+     */
     setListItem(animation: Animation, i: number) {
         switch (animation.dataType) {
             case Animation.ANIMATIONTYPE_FLOAT:
                 const editAnimation = () => this.props.editAnimation(animation);
                 const selectAnimation = () => this.props.selectAnimation(animation, 0);
                 return (
-                    <li className={this.props.selected && this.props.selected.name === animation.name ? "property active" : "property"} key={i} onClick={selectAnimation}>
+                    <li
+                        className={this.props.selected && this.props.selected.name === animation.name ? "property active" : "property"}
+                        key={i}
+                        onClick={selectAnimation}
+                    >
                         <div className={`animation-bullet`}></div>
                         <p>{animation.targetProperty}</p>
                         <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />

+ 71 - 11
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx

@@ -94,7 +94,12 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
             this.props.scene.stopAnimation(this.props.animatable);
             this._mainAnimatable = null;
         } else {
-            this._mainAnimatable = this.props.scene.beginAnimation(this.props.animatable, this._animationControl.from, this._animationControl.to, this._animationControl.loop);
+            this._mainAnimatable = this.props.scene.beginAnimation(
+                this.props.animatable,
+                this._animationControl.from,
+                this._animationControl.to,
+                this._animationControl.loop
+            );
         }
         this.forceUpdate();
     }
@@ -184,20 +189,59 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                             <TextLineComponent label="Count" value={animations.length.toString()} />
                             {/* <ButtonLineComponent label="Edit" onClick={() => this.onOpenAnimationCurveEditor()} />
                             {animations.map((anim, i) => {
-                                return <TextLineComponent key={anim.targetProperty + i} label={"#" + i + " >"} value={anim.targetProperty} />;
+                                return (
+                                    <TextLineComponent
+                                        key={anim.targetProperty + i}
+                                        label={"#" + i + " >"}
+                                        value={anim.targetProperty}
+                                    />
+                                );
                             })}
 
                             {this._isCurveEditorOpen && (
-                                <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
-                                    <AnimationCurveEditorComponent scene={this.props.scene} entity={animatableAsAny} lockObject={this.props.lockObject} playOrPause={() => this.playOrPause()} globalState={this.props.globalState} />
+                                <PopupComponent
+                                    id="curve-editor"
+                                    title="Curve Animation Editor"
+                                    size={{ width: 1024, height: 512 }}
+                                    onOpen={(window: Window) => {}}
+                                    onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}
+                                >
+                                    <AnimationCurveEditorComponent
+                                        scene={this.props.scene}
+                                        entity={animatableAsAny}
+                                        lockObject={this.props.lockObject}
+                                        playOrPause={() => this.playOrPause()}
+                                        globalState={this.props.globalState}
+                                    />
                                 </PopupComponent>
                             )} */}
                         </LineContainerComponent>
                         {animations.length > 0 && (
-                            <LineContainerComponent globalState={this.props.globalState} title="ANIMATION GENERAL CONTROL">
-                                <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="From" target={this._animationControl} propertyName="from" onChange={() => this.onChangeFromOrTo()} />
-                                <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="To" target={this._animationControl} propertyName="to" onChange={() => this.onChangeFromOrTo()} />
-                                <CheckBoxLineComponent label="Loop" onSelect={(value) => (this._animationControl.loop = value)} isSelected={() => this._animationControl.loop} />
+                            <LineContainerComponent
+                                globalState={this.props.globalState}
+                                title="ANIMATION GENERAL CONTROL"
+                            >
+                                <FloatLineComponent
+                                    lockObject={this.props.lockObject}
+                                    isInteger={true}
+                                    label="From"
+                                    target={this._animationControl}
+                                    propertyName="from"
+                                    onChange={() => this.onChangeFromOrTo()}
+                                />
+                                <FloatLineComponent
+                                    lockObject={this.props.lockObject}
+                                    isInteger={true}
+                                    label="To"
+                                    target={this._animationControl}
+                                    propertyName="to"
+                                    onChange={() => this.onChangeFromOrTo()}
+                                />
+                                <CheckBoxLineComponent
+                                    label="Loop"
+                                    onSelect={(value) => (this._animationControl.loop = value)}
+                                    isSelected={() => this._animationControl.loop}
+                                />
                                 {this._isPlaying && (
                                     <SliderLineComponent
                                         ref={this.timelineRef}
@@ -209,7 +253,10 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                         onInput={(value) => this.onCurrentFrameChange(value)}
                                     />
                                 )}
-                                <ButtonLineComponent label={this._isPlaying ? "Stop" : "Play"} onClick={() => this.playOrPause()} />
+                                <ButtonLineComponent
+                                    label={this._isPlaying ? "Stop" : "Play"}
+                                    onClick={() => this.playOrPause()}
+                                />
                                 {(this._ranges.length > 0 || (this._animations && this._animations.length > 0)) && (
                                     <>
                                         <CheckBoxLineComponent
@@ -228,8 +275,21 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                         />
                                         {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} />
+                                                <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>
                                         )}
                                     </>

+ 16 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx

@@ -3,13 +3,21 @@ import { IAnimationKey } from "babylonjs/Animations/animationKey";
 import { IconButtonLineComponent } from "../../../lines/iconButtonLineComponent";
 
 interface IControlsProps {
+    // Keyframes to choose start or end of animation playback
     keyframes: IAnimationKey[] | null;
+    // The currently selected animation keyframe
     selected: IAnimationKey | null;
+    // The current frame number
     currentFrame: number;
+    // Event to change the current frame
     onCurrentFrameChange: (frame: number) => void;
+    // Event to communicate canvas repposition
     repositionCanvas: (keyframe: IAnimationKey) => void;
+    // Event to play, pause or play backwards the animation
     playPause: (direction: number) => void;
+    // If the animation is playing
     isPlaying: boolean;
+    // The reference to the scrollable dom object to set its position
     scrollable: React.RefObject<HTMLDivElement>;
 }
 
@@ -42,6 +50,9 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
         }
     };
 
+    /**
+     * Set animation to initial keyframe
+     */
     moveToAnimationStart = () => {
         const startKeyframe = this.props.keyframes && this.props.keyframes[0];
         if (startKeyframe !== null) {
@@ -51,6 +62,9 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
         }
     };
 
+    /**
+     * Set animation to the last keyframe
+     */
     moveToAnimationEnd = () => {
         const endKeyframe = this.props.keyframes && this.props.keyframes[this.props.keyframes.length - 1];
         if (endKeyframe !== null) {
@@ -60,6 +74,7 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
         }
     };
 
+    /** Move to next keyframe */
     nextKeyframe = () => {
         if (this.props.keyframes !== null) {
             let first = this.props.keyframes.find((kf) => kf.frame > this.props.currentFrame);
@@ -71,6 +86,7 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
         }
     };
 
+    /** Move to previous keyframe */
     previousKeyframe = () => {
         if (this.props.keyframes !== null) {
             let keyframes = [...this.props.keyframes];

+ 71 - 20
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -14,38 +14,59 @@ import { LockObject } from "../lockObject";
 import { GlobalState } from "../../../../globalState";
 
 interface IEditorControlsProps {
+    // if the entity has a targeted animation
     isTargetedAnimation: boolean;
+    // Entity if it is animatable or targeted animation type
     entity: IAnimatable | TargetedAnimation;
+    // The current selected animation
     selected: Animation | null;
+    // The global lock object
     lockObject: LockObject;
+    // The observable
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    // Event to send the message to the notification bar
     setNotificationMessage: (message: string) => void;
+    // Event to send the selected animation and the coordinate (x, y, z) axis to render a curve
     selectAnimation: (selected: Animation, axis?: SelectedCoordinate) => void;
+    // Event to set the frames per second of the animation
     setFps: (fps: number) => void;
+    // Event to set if the animation loops
     setIsLooping: () => void;
+    // The global state
     globalState: GlobalState;
+    // The snippet server address
     snippetServer: string;
+    // Event to deselect an animation
     deselectAnimation: () => void;
+    // The frames per second
     fps: number;
 }
 
+interface IEditorControlsState {
+    // If the add animation tab is currently visible in the editor
+    isAnimationTabOpen: boolean;
+    // If the edit animation tab is currently visible in the editor
+    isEditTabOpen: boolean;
+    // If the load animations tab is currently visible in the editor
+    isLoadTabOpen: boolean;
+    // If the save animations tab is currently visible in the editor
+    isSaveTabOpen: boolean;
+    // If the loop toggle is active or not.
+    isLoopActive: boolean;
+    // How many animations we have in the tree
+    animationsCount: number;
+    // Frames per second of the selected animation
+    framesPerSecond: number;
+    // The snippet ID the user needs to input to save/load (needs to switch tab)
+    snippetId: string;
+    // The currently selected animation
+    selected: Animation | undefined;
+}
+
 /**
  * Renders the Curve Editor controls to create, save, remove, load and edit animations
  */
-export class EditorControls extends React.Component<
-    IEditorControlsProps,
-    {
-        isAnimationTabOpen: boolean;
-        isEditTabOpen: boolean;
-        isLoadTabOpen: boolean;
-        isSaveTabOpen: boolean;
-        isLoopActive: boolean;
-        animationsCount: number;
-        framesPerSecond: number;
-        snippetId: string;
-        selected: Animation | undefined;
-    }
-> {
+export class EditorControls extends React.Component<IEditorControlsProps, IEditorControlsState> {
     constructor(props: IEditorControlsProps) {
         super(props);
         let count = this.props.isTargetedAnimation ? 1 : (this.props.entity as IAnimatable).animations?.length ?? 0;
@@ -62,12 +83,20 @@ export class EditorControls extends React.Component<
         };
     }
 
+    /**
+     * Makes sure the frames per second receive the updated prop
+     * @param prevProps previous properties
+     */
     componentDidUpdate(prevProps: IEditorControlsProps) {
         if (this.props.fps !== prevProps.fps) {
             this.setState({ framesPerSecond: this.props.fps });
         }
     }
 
+    /**
+     * Add the nimation, recounts the list and opens the correct tab
+     * @param animation The recently empty created animation
+     */
     onAnimationAdded = (animation: Animation) => {
         this.setState({
             animationsCount: this.recountAnimations(),
@@ -77,6 +106,9 @@ export class EditorControls extends React.Component<
         this.props.selectAnimation(animation, undefined);
     };
 
+    /**
+     * Set state when the animations list has finished updated
+     */
     finishedUpdate = () => {
         this.setState({
             isEditTabOpen: true,
@@ -85,10 +117,14 @@ export class EditorControls extends React.Component<
         });
     };
 
+    // Recount animations
     recountAnimations() {
         return (this.props.entity as IAnimatable).animations?.length ?? 0;
     }
 
+    /**
+     * Toggles loop
+     */
     changeLoopBehavior = () => {
         this.setState({
             isLoopActive: !this.state.isLoopActive,
@@ -96,6 +132,9 @@ export class EditorControls extends React.Component<
         this.props.setIsLooping();
     };
 
+    /**
+     * This avoids using lambda functions in JSX
+     */
     handleFirstTab = () => {
         this.handleTabs(0);
     };
@@ -109,6 +148,7 @@ export class EditorControls extends React.Component<
         this.handleTabs(3);
     };
 
+    // Opens/Closes the tabs
     handleTabs(tab: number) {
         let state = {
             isAnimationTabOpen: true,
@@ -155,6 +195,7 @@ export class EditorControls extends React.Component<
         this.setState(state);
     }
 
+    // Set state of FPS and sends the fps to parent
     handleChangeFps = (fps: number) => {
         this.props.setFps(fps);
         this.setState({ framesPerSecond: fps });
@@ -197,6 +238,10 @@ export class EditorControls extends React.Component<
         }
     };
 
+    /**
+     * The currently selected animation to edit it
+     * @param selected Selected animation
+     */
     editAnimation = (selected: Animation) => {
         this.setState({
             selected: selected,
@@ -207,11 +252,12 @@ export class EditorControls extends React.Component<
         });
     };
 
+    // Set state of the snippet id
     setSnippetId = (id: string) => {
         this.setState({ snippetId: id });
     };
 
-     /**
+    /**
      * Marks animation tab closed and hides the tab
      */
     onCloseAddAnimation = () => {
@@ -227,26 +273,30 @@ export class EditorControls extends React.Component<
                             active={this.state.isAnimationTabOpen}
                             tooltip="Add Animation"
                             icon="medium add-animation"
-                            onClick={this.handleFirstTab}></IconButtonLineComponent>
+                            onClick={this.handleFirstTab}
+                        ></IconButtonLineComponent>
                     )}
                     <IconButtonLineComponent
                         active={this.state.isLoadTabOpen}
                         tooltip="Load Animation"
                         icon="medium load"
-                        onClick={this.handleSecondTab}></IconButtonLineComponent>
+                        onClick={this.handleSecondTab}
+                    ></IconButtonLineComponent>
                     {this.state.animationsCount === 0 ? null : (
                         <IconButtonLineComponent
                             active={this.state.isSaveTabOpen}
                             tooltip="Save Animation"
                             icon="medium save"
-                            onClick={this.handleThirdTab}></IconButtonLineComponent>
+                            onClick={this.handleThirdTab}
+                        ></IconButtonLineComponent>
                     )}
                     {this.state.animationsCount === 0 ? null : (
                         <IconButtonLineComponent
                             active={this.state.isEditTabOpen}
                             tooltip="Edit Animations"
                             icon="medium animation-edit"
-                            onClick={this.handleFourthTab}></IconButtonLineComponent>
+                            onClick={this.handleFourthTab}
+                        ></IconButtonLineComponent>
                     )}
                     {this.state.isEditTabOpen ? (
                         <div className="input-fps">
@@ -263,7 +313,8 @@ export class EditorControls extends React.Component<
                         <IconButtonLineComponent
                             tooltip="Loop/Unloop"
                             icon={`medium ${this.state.isLoopActive ? "loop-active last" : "loop-inactive last"}`}
-                            onClick={this.changeLoopBehavior}></IconButtonLineComponent>
+                            onClick={this.changeLoopBehavior}
+                        ></IconButtonLineComponent>
                     ) : null}
                 </div>
                 {this.props.isTargetedAnimation ? null : (

+ 57 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx

@@ -3,20 +3,35 @@ import { IconButtonLineComponent } from "../../../lines/iconButtonLineComponent"
 import { IActionableKeyFrame } from "./animationCurveEditorComponent";
 
 interface IGraphActionsBarProps {
+    // Add a keyframe to animation
     addKeyframe: () => void;
+    // Remove keyframe to animation
     removeKeyframe: () => void;
+    // Shows the selected keyframes in the current visible canvas changing scale
     frameSelectedKeyframes: () => void;
+    // Handles the value change of the keyframe
     handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+    // Handles the frame change of the keyframe
     handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+    // Flats the selected control point relative to its keyframe
     flatTangent: () => void;
+    // Allows the user to change the tangent values of control points independently
     brokeTangents: () => void;
+    // Set a linear interpolation to the next keyframe on the selected control point
     setLerpToActiveControlPoint: () => void;
+    // If broken mode is active or not
     brokenMode: boolean;
+    // If the linear interpolation botton is active
     lerpMode: boolean;
+    // The currently selected keyframe to perform value or frame updates
     actionableKeyframe: IActionableKeyFrame;
+    // Name of the selected entity and animation
     title: string;
+    // If the graph controls are enabled or not
     enabled: boolean;
+    // Sets the keyframe value on the actionableKeyFrame
     setKeyframeValue: (actionableKeyframe: IActionableKeyFrame) => void;
+    // How many frames are between selected keyframes
     frameRange: { min: number | undefined; max: number | undefined };
 }
 
@@ -38,11 +53,19 @@ export class GraphActionsBar extends React.Component<
         this.state = { frame, value, min: this.props.frameRange.min, max: this.props.frameRange.max };
     }
 
+    /**
+     * Listen to keyup changes to handle if the input event has ended or change
+     */
     componentDidMount() {
         this._frameInput.current?.addEventListener("keyup", this.isEnterKeyUp.bind(this));
         this._valueInput.current?.addEventListener("keyup", this.isEnterKeyUp.bind(this));
     }
 
+    /**
+     * Set the changing state of frame, value and range of the actionablekeyframe
+     * @param prevProps previous props
+     * @param prevState previous state
+     */
     componentDidUpdate(prevProps: IGraphActionsBarProps, prevState: any) {
         if (prevProps.actionableKeyframe !== this.props.actionableKeyframe) {
             const { frame, value } = this.selectedKeyframeChanged(this.props.actionableKeyframe);
@@ -57,6 +80,10 @@ export class GraphActionsBar extends React.Component<
         }
     }
 
+    /**
+     * Returns the frame and value for the keyframe
+     * @param keyframe The keyframe to update
+     */
     selectedKeyframeChanged(keyframe: IActionableKeyFrame) {
         let frame = "";
         if (typeof keyframe.frame === "number") {
@@ -69,11 +96,18 @@ export class GraphActionsBar extends React.Component<
         return { frame, value };
     }
 
+    /**
+     * Remove listeners
+     */
     componentWillUnmount() {
         this._frameInput.current?.removeEventListener("keyup", this.isEnterKeyUp.bind(this));
         this._valueInput.current?.removeEventListener("keyup", this.isEnterKeyUp.bind(this));
     }
 
+    /**
+     * Trigger the change on the keyframe
+     * @param event Enter keyevent
+     */
     isEnterKeyUp(event: KeyboardEvent) {
         event.preventDefault();
 
@@ -83,6 +117,10 @@ export class GraphActionsBar extends React.Component<
         }
     }
 
+    /**
+     * Trigger the chnage on the keyframe on blur
+     * @param event Focus event
+     */
     onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
         event.preventDefault();
         if (event.target.value !== "") {
@@ -91,6 +129,9 @@ export class GraphActionsBar extends React.Component<
         }
     };
 
+    /**
+     * Gets the keyframe frame
+     */
     getFrame() {
         let frame;
         if (this.state.frame === "") {
@@ -102,6 +143,9 @@ export class GraphActionsBar extends React.Component<
         return frame;
     }
 
+    /**
+     * Gets the keyframe value
+     */
     getValue() {
         let value;
         if (this.state.value !== "") {
@@ -112,11 +156,19 @@ export class GraphActionsBar extends React.Component<
         return value;
     }
 
+    /**
+     * Set keyframe value state
+     * @param e Input event
+     */
     handleValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         e.preventDefault();
         this.setState({ value: e.target.value });
     };
 
+    /**
+     * Set the keyframe frame state
+     * @param e Input event
+     */
     handleFrameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         e.preventDefault();
         this.setState({ frame: e.target.value });
@@ -129,7 +181,11 @@ export class GraphActionsBar extends React.Component<
                     <div className="icon babylon-logo"></div>
                     <div className="title">{this.props.title}</div>
                 </div>
-                <div className={`buttons-container ${this.props.enabled ? "pointer-events-enabled" : "pointer-events-disabled"}`}>
+                <div
+                    className={`buttons-container ${
+                        this.props.enabled ? "pointer-events-enabled" : "pointer-events-disabled"
+                    }`}
+                >
                     <div className="action-input frame-input">
                         <input
                             ref={this._frameInput}

+ 26 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -6,13 +6,21 @@ const keyInactive = require("./assets/keyInactiveIcon.svg") as string;
 const keySelected = require("./assets/keySelectedIcon.svg") as string;
 
 export interface IKeyframeSvgPoint {
+    // Keyframe point to render in svg canvas
     keyframePoint: Vector2;
+    // Right control point (controls curve)
     rightControlPoint: Vector2 | null;
+    // Left control point (controls curve)
     leftControlPoint: Vector2 | null;
+    // svg keyframe id
     id: string;
+    // If the keyframe is selected on canvas
     selected: boolean;
+    // I the left control point is being dragged or active
     isLeftActive: boolean;
+    // I the right control point is being dragged or active
     isRightActive: boolean;
+    // The parent curve id
     curveId?: ICurveMetaData;
 }
 
@@ -23,15 +31,25 @@ export interface ICurveMetaData {
 }
 
 interface IKeyframeSvgPointProps {
+    // Keyframe point to render in svg canvas
     keyframePoint: Vector2;
+    // Left control point (controls curve)
     leftControlPoint: Vector2 | null;
+    // Right control point (controls curve)
     rightControlPoint: Vector2 | null;
+    // svg keyframe id
     id: string;
+    // If the keyframe is selected on canvas
     selected: boolean;
+    // Select keyframe and mode of selection
     selectKeyframe: (id: string, multiselect: boolean) => void;
+    // Returns the id of the selected keyframe and its animation property type
     selectedControlPoint: (type: string, id: string) => void;
+    // I the left control point is being dragged or active
     isLeftActive: boolean;
+    // I the right control point is being dragged or active
     isRightActive: boolean;
+    // Current visible frames in canvas
     framesInCanvasView: { from: number; to: number };
 }
 
@@ -44,6 +62,10 @@ export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         super(props);
     }
 
+    /**
+     * Select a keyframe. If the control key is pressed, multiselect keyframes
+     * @param e Mouse event
+     */
     select = (e: React.MouseEvent<SVGImageElement>) => {
         e.preventDefault();
         let multiSelect = false;
@@ -53,6 +75,10 @@ export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         this.props.selectKeyframe(this.props.id, multiSelect);
     };
 
+    /**
+     * Send the ID of the selected keyframe
+     * @param type Type of selected keyframe
+     */
     selectedControlPointId = (type: string) => {
         this.props.selectedControlPoint(type, this.props.id);
     };

+ 20 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx

@@ -13,14 +13,23 @@ import { IAnimatable } from "babylonjs/Animations/animatable.interface";
 import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
 
 interface ILoadSnippetProps {
+    // Animations to load
     animations: Animation[];
+    // Observable
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    // Global lock object
     lockObject: LockObject;
+    // Global state
     globalState: GlobalState;
+    // Snippet server address
     snippetServer: string;
+    // Function to set the id of the snippert
     setSnippetId: (id: string) => void;
+    // entity to reference the animations
     entity: IAnimatable | TargetedAnimation;
+    // sets the message for error or warning
     setNotificationMessage: (message: string) => void;
+    // tells if animation have loaded successfully
     animationsLoaded: (numberOfAnimations: number) => void;
 }
 
@@ -35,11 +44,19 @@ export class LoadSnippet extends React.Component<ILoadSnippetProps, { snippetId:
         this.state = { snippetId: "" };
     }
 
+    /**
+     * Set the animations snippet id
+     * @param value Snippet ID
+     */
     change = (value: string) => {
         this.setState({ snippetId: value });
         this.props.setSnippetId(value);
     };
 
+    /**
+     * Select a local file to load animations
+     * @param file File name
+     */
     loadFromFile = (file: File) => {
         Tools.ReadFile(
             file,
@@ -70,6 +87,9 @@ export class LoadSnippet extends React.Component<ILoadSnippetProps, { snippetId:
         );
     };
 
+    /**
+     * Load animations from server identified with a snippet id
+     */
     loadFromSnippet = () => {
         if (this.state.snippetId !== "") {
             //Notify observers

+ 3 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx

@@ -1,8 +1,11 @@
 import * as React from "react";
 
 interface IPlayheadProps {
+    // Message to display
     message: string;
+    // open or close state
     open: boolean;
+    // event to close the notification bar
     close: () => void;
 }
 

+ 68 - 65
inspector/src/components/actionTabs/tabs/propertyGrids/animations/playhead.tsx

@@ -1,80 +1,83 @@
-import * as React from 'react';
+import * as React from "react";
 
 interface IPlayheadProps {
-  frame: number;
-  offset: number;
-  onCurrentFrameChange: (frame: number) => void;
+    // Current frame
+    frame: number;
+    // SVG distane to render de playhead (margin)
+    offset: number;
+    // Event to change frame
+    onCurrentFrameChange: (frame: number) => void;
 }
 
 /**
  * Renders the Playhead
  */
 export class Playhead extends React.Component<IPlayheadProps> {
-  private _direction: number;
-  private _active: boolean;
-  constructor(props: IPlayheadProps) {
-    super(props);
-  }
+    private _direction: number;
+    private _active: boolean;
+    constructor(props: IPlayheadProps) {
+        super(props);
+    }
 
-  dragStart(e: React.TouchEvent<HTMLDivElement>): void;
-  dragStart(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-  dragStart(e: any) {
-    e.preventDefault();
-    this._direction = e.clientX;
-    this._active = true;
-  }
+    dragStart(e: React.TouchEvent<HTMLDivElement>): void;
+    dragStart(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
+    dragStart(e: any) {
+        e.preventDefault();
+        this._direction = e.clientX;
+        this._active = true;
+    }
 
-  drag(e: React.TouchEvent<HTMLDivElement>): void;
-  drag(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-  drag(e: any) {
-    e.preventDefault();
-    if (this._active) {
-      let moved = e.pageX - this._direction;
-      if (Math.sign(moved) === -1) {
-        this.props.onCurrentFrameChange(this.props.frame - 1);
-      } else {
-        this.props.onCurrentFrameChange(this.props.frame + 1);
-      }
+    drag(e: React.TouchEvent<HTMLDivElement>): void;
+    drag(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
+    drag(e: any) {
+        e.preventDefault();
+        if (this._active) {
+            let moved = e.pageX - this._direction;
+            if (Math.sign(moved) === -1) {
+                this.props.onCurrentFrameChange(this.props.frame - 1);
+            } else {
+                this.props.onCurrentFrameChange(this.props.frame + 1);
+            }
+        }
     }
-  }
 
-  dragEnd(e: React.TouchEvent<HTMLDivElement>): void;
-  dragEnd(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-  dragEnd(e: any) {
-    e.preventDefault();
-    this._direction = 0;
-    this._active = false;
-  }
+    dragEnd(e: React.TouchEvent<HTMLDivElement>): void;
+    dragEnd(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
+    dragEnd(e: any) {
+        e.preventDefault();
+        this._direction = 0;
+        this._active = false;
+    }
 
-  calculateMove() {
-    return `calc(${this.props.frame * this.props.offset}px + 20px)`;
-  }
+    calculateMove() {
+        return `calc(${this.props.frame * this.props.offset}px + 20px)`;
+    }
 
-  render() {
-    return (
-      <div
-        className='playhead-wrapper'
-        id='playhead'
-        style={{
-          left: this.calculateMove(),
-        }}
-      >
-        <div className='playhead-line'></div>
-        <div
-          className='playhead-handle'
-          onMouseMove={(e) => this.drag(e)}
-          onTouchMove={(e) => this.drag(e)}
-          onTouchStart={(e) => this.dragStart(e)}
-          onTouchEnd={(e) => this.dragEnd(e)}
-          onMouseDown={(e) => this.dragStart(e)}
-          onMouseUp={(e) => this.dragEnd(e)}
-          onMouseLeave={(e) => this.dragEnd(e)}
-          onDragStart={() => false}
-        >
-          <div className='playhead-circle'></div>
-          <div className='playhead'>{this.props.frame}</div>
-        </div>
-      </div>
-    );
-  }
+    render() {
+        return (
+            <div
+                className="playhead-wrapper"
+                id="playhead"
+                style={{
+                    left: this.calculateMove(),
+                }}
+            >
+                <div className="playhead-line"></div>
+                <div
+                    className="playhead-handle"
+                    onMouseMove={(e) => this.drag(e)}
+                    onTouchMove={(e) => this.drag(e)}
+                    onTouchStart={(e) => this.dragStart(e)}
+                    onTouchEnd={(e) => this.dragEnd(e)}
+                    onMouseDown={(e) => this.dragStart(e)}
+                    onMouseUp={(e) => this.dragEnd(e)}
+                    onMouseLeave={(e) => this.dragEnd(e)}
+                    onDragStart={() => false}
+                >
+                    <div className="playhead-circle"></div>
+                    <div className="playhead">{this.props.frame}</div>
+                </div>
+            </div>
+        );
+    }
 }

+ 22 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx

@@ -9,11 +9,17 @@ import { Nullable } from "babylonjs/types";
 import { GlobalState } from "../../../../globalState";
 
 interface ISaveSnippetProps {
+    // Animation to save
     animations: Nullable<Animation[]>;
+    // Observable
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    // Global lock object
     lockObject: LockObject;
+    // Global state
     globalState: GlobalState;
+    // Snippet server address
     snippetServer: string;
+    // Snippert id to save the snippet
     snippetId: string;
 }
 
@@ -45,6 +51,10 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
         this.state = { selectedAnimations: animList ?? [] };
     }
 
+    /**
+     * Set the selected animations to save
+     * @param e Input event
+     */
     handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         e.preventDefault();
 
@@ -60,6 +70,9 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
         this.setState({ selectedAnimations: updated });
     };
 
+    /**
+     * Stringify the selected animation
+     */
     stringifySelectedAnimations() {
         const content: string[] = [];
         this.state.selectedAnimations.forEach((animation) => {
@@ -73,11 +86,17 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
         return JSON.stringify(content);
     }
 
+    /**
+     * Save the selected animations to a local file
+     */
     saveToFile = () => {
         const content = this.stringifySelectedAnimations();
         Tools.Download(new Blob([content]), "animations.json");
     };
 
+    /**
+     * Save the selected animations if a snippet ID is set
+     */
     saveToSnippet = () => {
         if (this.props.snippetId !== "") {
             let serverId = this.props.snippetId;
@@ -161,7 +180,9 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
                     </ul>
                 </div>
                 <div className="save-buttons">
-                    {this.props.snippetId && <ButtonLineComponent label="Save to snippet server" onClick={this.saveToSnippet} />}
+                    {this.props.snippetId && (
+                        <ButtonLineComponent label="Save to snippet server" onClick={this.saveToSnippet} />
+                    )}
                     <ButtonLineComponent label="Save" onClick={this.saveToFile} />
                 </div>
                 <div className="save-server">

+ 2 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx

@@ -2,7 +2,9 @@ import * as React from "react";
 import { CurveScale } from "./animationCurveEditorComponent";
 
 interface ISwitchButtonProps {
+    /** Current scale */
     current: CurveScale;
+    /** On click change of scale */
     action?: (event: CurveScale) => void;
 }
 

+ 67 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -3,27 +3,42 @@ import { Vector2 } from "babylonjs/Maths/math.vector";
 import { IKeyframeSvgPoint } from "./keyframeSvgPoint";
 
 interface ISvgDraggableAreaProps {
+    // List of SVG Points in curve
     keyframeSvgPoints: IKeyframeSvgPoint[];
+    // Update the position of the selected SVG Point
     updatePosition: (updatedKeyframe: IKeyframeSvgPoint, id: string) => void;
+    // The scale of the curve respect to the visible canvas
     scale: number;
+    // The SVG canvas viewBox
     viewBoxScale: number;
+    // Deselects all keyframes
     deselectKeyframes: () => void;
+    // Remove the selected keyframes
     removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
+    // How much y panning the user has done to move the canvas (relative to canvas)
     panningY: (panningY: number) => void;
+    // How much x panning the user has done to move the canvas (relative to canvas)
     panningX: (panningX: number) => void;
+    // Moves the current selected frame back and forth depending on number of frames
     setCurrentFrame: (direction: number) => void;
+    // The anchor point of the canvas to center it
     positionCanvas?: Vector2;
+    // If the canvas is set to reposition
     repositionCanvas?: boolean;
+    // If the canvas reposition event has ended
     canvasPositionEnded: () => void;
+    // Resets the selected keyframe
     resetActionableKeyframe: () => void;
+    // How many frames should be visible in the current canvas scale
     framesInCanvasView: { from: number; to: number };
+    // How many frames has the canvas added or removed depending on window resize event
     framesResized: number;
 }
 
 /**
  * The SvgDraggableArea is a wrapper for SVG Canvas the interaction
  *
- * Here we control the drag and key behavior for the SVG components. 
+ * Here we control the drag and key behavior for the SVG components.
  */
 export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, { panX: number; panY: number }> {
     private _active: boolean;
@@ -58,6 +73,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         this.state = { panX: 0, panY: 0 };
     }
 
+    /**
+     * Listen to key events to be able to drag and set the correct canvas client width
+    */
     componentDidMount() {
         this._draggableArea.current?.addEventListener("keydown", this.keyDown.bind(this));
         this._draggableArea.current?.addEventListener("keyup", this.keyUp.bind(this));
@@ -66,6 +84,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }, 500);
     }
 
+    /**
+     * Makes sure the canvas has resposition correctly
+     * @param prevProps previous props
+     */
     componentDidUpdate(prevProps: ISvgDraggableAreaProps) {
         if (
             this.props.positionCanvas !== prevProps.positionCanvas &&
@@ -84,15 +106,22 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     }
 
+    /**
+     * Identify the type of target of the mouse event
+     * @param e SVG Mouse Event
+     */
     dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         if ((e.target as SVGSVGElement).classList.contains("draggable")) {
+            // Set dragging as active
             this._active = true;
+            // If we are trying to move a keyframe, set the current dragging point being moved
             const dataId = (e.target as SVGSVGElement).getAttribute("data-id");
             if (dataId !== null) {
                 this._currentPointId = dataId;
             }
 
+            // If we are trying to move a keyframe control point, set the dragging control point
             if ((e.target as SVGSVGElement).classList.contains("control-point")) {
                 const type = (e.target as SVGSVGElement).getAttribute("type");
                 if (type !== null) {
@@ -101,12 +130,14 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
             }
         }
 
+        // Controls if we are trying to drag the playhead
         if ((e.target as SVGSVGElement).classList.contains("svg-playhead")) {
             this._active = true;
             this._playheadSelected = true;
             this._playheadDrag = e.clientX - e.currentTarget.getBoundingClientRect().left;
         }
 
+        // Controls if we are trying to pan the content in the canvas
         if ((e.target as SVGSVGElement).classList.contains("pannable")) {
             if (this._isControlKeyPress) {
                 this._active = true;
@@ -118,6 +149,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     };
 
+    /**
+     * Handle dragging
+     * @param e SVG Mouse Event
+     */
     drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         if (this._active) {
             e.preventDefault();
@@ -182,6 +217,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     };
 
+    /**
+     *  Resets the dragging state
+     * @param e Drag event
+     */
     dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this._active = false;
@@ -195,6 +234,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         this._movedY = 0;
     };
 
+    /**
+     * Gets the current position of the mouse in a SVG Canvas
+     * @param e Mouse event
+     */
     getMousePosition = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined => {
         if (this._draggableArea.current) {
             var svg = this._draggableArea.current as SVGSVGElement;
@@ -210,9 +253,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
     };
 
     /**
-    * Handles the canvas panning direction and sets the X and Y values to move the
-    * SVG canvas
-    */
+     * Handles the canvas panning direction and sets the X and Y values to move the
+     * SVG canvas
+     */
     panDirection() {
         let directionX = 1;
         if (this._movedX < this._panStop.x) {
@@ -228,22 +271,26 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
             directionY = 1; //bottom
         }
 
+        // Establish the pan buffer we have to start moving the canvas
         const bufferX = this._movedX === 0 ? 1 : Math.abs(this._movedX - this._panStop.x);
         const bufferY = this._movedY === 0 ? 1 : Math.abs(this._movedY - this._panStop.y);
 
         let xMulti = 0;
+        // Evaluate if mouse move is too little to move the canvas
         if (bufferX >= this._dragBuffer) {
             xMulti = Math.round(Math.abs(bufferX - this._dragBuffer) / 2.5);
         }
-
+        // Evaluate if mouse move is too little to move the canvas
         let yMulti = 0;
         if (bufferY >= this._dragBuffer) {
             yMulti = Math.round(Math.abs(bufferY - this._dragBuffer) / 2.5);
         }
 
+        // Set last moved value
         this._movedX = this._panStop.x;
         this._movedY = this._panStop.y;
 
+        // Establish new pan value
         let newX = this.state.panX + directionX * xMulti;
         let newY = this.state.panY + directionY * yMulti;
 
@@ -256,6 +303,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         this.props.panningX(Math.round(newX));
     }
 
+    /**
+     * Allows dragging
+     * @param e Keyboard event
+     */
     keyDown(e: KeyboardEvent) {
         e.preventDefault();
         if (e.keyCode === 17 || e.keyCode === 32) {
@@ -264,6 +315,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     }
 
+    /**
+     * Allows multiple selection
+     * @param e Keyboard event
+     */
     keyUp(e: KeyboardEvent) {
         e.preventDefault();
         if (e.keyCode === 17 || e.keyCode === 32) {
@@ -282,6 +337,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     }
 
+    /**
+     * Focus on the SVG canvas
+     * @param e Mouse event
+     */
     focus = (e: React.MouseEvent<SVGSVGElement>) => {
         e.preventDefault();
         this._draggableArea.current?.focus();
@@ -295,6 +354,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     };
 
+    /**
+     * Is the control point active or not
+     */
     isNotControlPointActive() {
         const activeControlPoints = this.props.keyframeSvgPoints.filter((x) => x.isLeftActive || x.isRightActive);
         if (activeControlPoints.length !== 0) {

+ 14 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx

@@ -82,8 +82,20 @@ export class TargetedAnimationGridComponent extends React.Component<ITargetedAni
                     {targetedAnimation.target.name && <TextLineComponent label="Target" value={targetedAnimation.target.name} onLink={() => this.props.globalState.onSelectionChangedObservable.notifyObservers(targetedAnimation)} />}
                     {/* <ButtonLineComponent label="Edit animation" onClick={this.onOpenAnimationCurveEditor} />
                     {this._isCurveEditorOpen && (
-                        <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={this.onCloseAnimationCurveEditor}>
-                            <AnimationCurveEditorComponent scene={this.props.scene} entity={targetedAnimation as any} playOrPause={this.playOrPause} lockObject={this.props.lockObject} globalState={this.props.globalState} />
+                        <PopupComponent
+                            id="curve-editor"
+                            title="Curve Animation Editor"
+                            size={{ width: 1024, height: 512 }}
+                            onOpen={(window: Window) => {}}
+                            onClose={this.onCloseAnimationCurveEditor}
+                        >
+                            <AnimationCurveEditorComponent
+                                scene={this.props.scene}
+                                entity={targetedAnimation as any}
+                                playOrPause={this.playOrPause}
+                                lockObject={this.props.lockObject}
+                                globalState={this.props.globalState}
+                            />
                         </PopupComponent>
                     )} */}
                     <ButtonLineComponent label="Dispose" onClick={this.deleteAnimation} />

+ 139 - 37
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -3,38 +3,56 @@ import { IAnimationKey } from "babylonjs/Animations/animationKey";
 import { Controls } from "./controls";
 
 interface ITimelineProps {
+    // Keyframes list in the animation
     keyframes: IAnimationKey[] | null;
+    // The selected animation keyframe
     selected: IAnimationKey | null;
+    // The current frame of the selected animation keyframe
     currentFrame: number;
+    // Selects the frame of an animation keyframe
     onCurrentFrameChange: (frame: number) => void;
+    // Changes animation length limit (visible on canvas)
     onAnimationLimitChange: (limit: number) => void;
+    // Keyframe to drag by the user
     dragKeyframe: (frame: number, index: number) => void;
+    // Starts/stops the animation (0 stops, -1 plays backward, 1 normal)
     playPause: (direction: number) => void;
+    // If animation is playing
     isPlaying: boolean;
+    // The last visible frame on the canvas. Controls the length of the visible timeline
     animationLimit: number;
+    // Frames per second
     fps: number;
+    // Reposition the canvas and center it to the selected keyframe
     repositionCanvas: (keyframe: IAnimationKey) => void;
+    // Change proportion of the resized window
     resizeWindowProportion: number;
 }
 
+interface ITimelineState {
+    // Selected keyframe
+    selected: IAnimationKey;
+    // Active frame
+    activeKeyframe: number | null;
+    // Start of the timeline scrollbar
+    start: number;
+    // End of the timeline scrollbar
+    end: number;
+    // Current widht of the scrollbar
+    scrollWidth: number | undefined;
+    // The length of the visible frame
+    selectionLength: number[];
+    // Limit of the visible frames
+    limitValue: number;
+}
+
 /**
  * The Timeline for the curve editor
  *
- * Has a scrollbar that can be resized and move to left and right. 
+ * Has a scrollbar that can be resized and move to left and right.
  * The timeline does not affect the Canvas but only the frame container.
  */
-export class Timeline extends React.Component<
-    ITimelineProps,
-    {
-        selected: IAnimationKey;
-        activeKeyframe: number | null;
-        start: number;
-        end: number;
-        scrollWidth: number | undefined;
-        selectionLength: number[];
-        limitValue: number;
-    }
-> {
+export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
     // Div Elements to display the timeline
     private _scrollable: React.RefObject<HTMLDivElement>;
     private _scrollbarHandle: React.RefObject<HTMLDivElement>;
@@ -77,6 +95,7 @@ export class Timeline extends React.Component<
         }
     }
 
+    /** Listen to keyup events and set the initial lenght of the scrollbar */
     componentDidMount() {
         setTimeout(() => {
             this.setState({
@@ -87,6 +106,7 @@ export class Timeline extends React.Component<
         this._inputAnimationLimit.current?.addEventListener("keyup", this.isEnterKeyUp.bind(this));
     }
 
+    /** Recalculate the scrollwidth if a window resize happens */
     componentDidUpdate(prevProps: ITimelineProps) {
         if (prevProps.animationLimit !== this.props.animationLimit) {
             this.setState({ limitValue: this.props.animationLimit });
@@ -98,10 +118,15 @@ export class Timeline extends React.Component<
         }
     }
 
+    /** Remove key event listener */
     componentWillUnmount() {
         this._inputAnimationLimit.current?.removeEventListener("keyup", this.isEnterKeyUp.bind(this));
     }
 
+    /**
+     * Set component state if enter key is pressed
+     * @param event enter key event
+     */
     isEnterKeyUp(event: KeyboardEvent) {
         event.preventDefault();
         if (event.key === "Enter") {
@@ -109,11 +134,16 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+     * Detect blur event
+     * @param event Blur event
+     */
     onInputBlur(event: React.FocusEvent<HTMLInputElement>) {
         event.preventDefault();
         this.setControlState();
     }
 
+    /** Set component state (scrollbar width, position, and start and end) */
     setControlState() {
         this.props.onAnimationLimitChange(this.state.limitValue);
         const newEnd = Math.round(this.state.limitValue / 2);
@@ -137,9 +167,10 @@ export class Timeline extends React.Component<
     }
 
     /**
-    * @param {number} start Frame from which the scrollbar should begin.
-    * @param {number} end Last frame for the timeline.
-    */
+     * Set scrollwidth on the timeline
+     * @param {number} start Frame from which the scrollbar should begin.
+     * @param {number} end Last frame for the timeline.
+     */
     calculateScrollWidth(start: number, end: number) {
         if (this._scrollContainer.current && this.props.animationLimit !== 0) {
             const containerMarginLeftRight = this._marginScrollbar * 2;
@@ -157,20 +188,36 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+     * Play animation backwards
+     * @param event Mouse event
+     */
     playBackwards(event: React.MouseEvent<HTMLDivElement>) {
         this.props.playPause(-1);
     }
 
+    /**
+     * Play animation
+     * @param event Mouse event
+     */
     play(event: React.MouseEvent<HTMLDivElement>) {
         this.props.playPause(1);
     }
 
+    /**
+     * Pause the animation
+     * @param event Mouse event
+     */
     pause(event: React.MouseEvent<HTMLDivElement>) {
         if (this.props.isPlaying) {
             this.props.playPause(1);
         }
     }
 
+    /**
+     * Set the selected frame
+     * @param event Mouse event
+     */
     setCurrentFrame = (event: React.MouseEvent<HTMLDivElement>) => {
         event.preventDefault();
         if (this._scrollable.current) {
@@ -184,8 +231,8 @@ export class Timeline extends React.Component<
     };
 
     /**
-    * Handles the change of number of frames available in the timeline.
-    */
+     * Handles the change of number of frames available in the timeline.
+     */
     handleLimitChange(event: React.ChangeEvent<HTMLInputElement>) {
         event.preventDefault();
         let newLimit = parseInt(event.target.value);
@@ -197,12 +244,20 @@ export class Timeline extends React.Component<
         });
     }
 
+    /**
+     * Starts the scrollbar dragging
+     * @param e Mouse event on SVG Element
+     */
     dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this.setState({ activeKeyframe: parseInt((e.target as SVGSVGElement).id.replace("kf_", "")) });
         this._direction = e.clientX;
     };
 
+    /**
+     * Update the canvas visible frames while dragging
+     * @param e Mouse event
+     */
     drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         if (this.props.keyframes) {
@@ -226,8 +281,10 @@ export class Timeline extends React.Component<
     };
 
     /**
-    * Check if the frame is being used as a Keyframe by the animation
-    */
+     * Check if the frame is being used as a Keyframe by the animation
+     * @param frame number of frame
+     * @param direction frame increment or decrement
+     */
     isFrameBeingUsed(frame: number, direction: number) {
         let used = this.props.keyframes?.find((kf) => kf.frame === frame);
         if (used) {
@@ -238,12 +295,20 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+     * Reset drag state
+     * @param e Mouse event on SVG Element
+     */
     dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this._direction = 0;
         this.setState({ activeKeyframe: null });
     };
 
+    /**
+     * Change position of the scrollbar
+     * @param e Mouse event
+     */
     scrollDragStart = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
         this._scrollContainer.current && this._scrollContainer.current.focus();
@@ -267,6 +332,10 @@ export class Timeline extends React.Component<
         }
     };
 
+    /**
+     * Change size of scrollbar
+     * @param e Mouse event
+     */
     scrollDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
         if ((e.target as HTMLDivElement).className === "scrollbar") {
@@ -282,6 +351,10 @@ export class Timeline extends React.Component<
         }
     };
 
+    /**
+     * Reset scroll drag
+     * @param e Mouse event
+     */
     scrollDragEnd = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
         this._scrolling = false;
@@ -290,10 +363,10 @@ export class Timeline extends React.Component<
     };
 
     /**
-    * Sets the start, end and selection length of the scrollbar. This will control the width and
-    * height of the scrollbar as well as the number of frames available
-    * @param {number} pageX Controls the X axis of the scrollbar movement.
-    */
+     * Sets the start, end and selection length of the scrollbar. This will control the width and
+     * height of the scrollbar as well as the number of frames available
+     * @param {number} pageX Controls the X axis of the scrollbar movement.
+     */
     moveScrollbar(pageX: number) {
         if (this._scrolling && this._scrollbarHandle.current && this._scrollContainer.current) {
             const moved = pageX - this._shiftX;
@@ -321,8 +394,9 @@ export class Timeline extends React.Component<
     }
 
     /**
-    * Controls the resizing of the scrollbar from the right handle
-    */
+     * Controls the resizing of the scrollbar from the right handle
+     * @param {number} clientX how much mouse has moved
+     */
     resizeScrollbarRight(clientX: number) {
         if (this._scrollContainer.current && this._scrollbarHandle.current) {
             const moving = clientX - this._scrollContainer.current.getBoundingClientRect().left;
@@ -351,8 +425,9 @@ export class Timeline extends React.Component<
     }
 
     /**
-    * Controls the resizing of the scrollbar from the left handle
-    */
+     * Controls the resizing of the scrollbar from the left handle
+     *  @param {number} clientX how much mouse has moved
+     */
     resizeScrollbarLeft(clientX: number) {
         if (this._scrollContainer.current && this._scrollbarHandle.current) {
             const moving = clientX - this._scrollContainer.current.getBoundingClientRect().left;
@@ -371,7 +446,9 @@ export class Timeline extends React.Component<
 
             if (!(framesTo >= this.state.end - 20)) {
                 let toleft =
-                    framesTo * unit + this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar * 2;
+                    framesTo * unit +
+                    this._scrollContainer.current.getBoundingClientRect().left +
+                    this._marginScrollbar * 2;
                 if (this._scrollbarHandle.current) {
                     this._scrollbarHandle.current.style.left = toleft + "px";
                 }
@@ -385,12 +462,18 @@ export class Timeline extends React.Component<
     }
 
     /**
-    * Returns array with the expected length between two numbers
-    */
+     * Returns array with the expected length between two numbers
+     * @param start initial visible frame
+     * @param stop last visible frame
+     */
     range(start: number, end: number) {
         return Array.from({ length: end - start }, (_, i) => start + i * 1);
     }
 
+    /**
+     * Get the animation keyframe
+     * @param frame Frame
+     */
     getKeyframe(frame: number) {
         if (this.props.keyframes) {
             return this.props.keyframes.find((x) => x.frame === frame);
@@ -399,6 +482,10 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+     * Get the current animation keyframe
+     * @param frame Frame
+     */
     getCurrentFrame(frame: number) {
         if (this.props.currentFrame === frame) {
             return true;
@@ -407,12 +494,14 @@ export class Timeline extends React.Component<
         }
     }
 
+    /* Overrides default DOM drag */
     dragDomFalse = () => false;
 
     render() {
         return (
             <>
                 <div className="timeline">
+                    {/* Renders the play animation controls */}
                     <Controls
                         keyframes={this.props.keyframes}
                         selected={this.props.selected}
@@ -424,7 +513,13 @@ export class Timeline extends React.Component<
                         scrollable={this._scrollable}
                     />
                     <div className="timeline-wrapper">
-                        <div ref={this._scrollable} className="display-line" onClick={this.setCurrentFrame} tabIndex={50}>
+                        {/* Renders the timeline that displays the animation keyframes */}
+                        <div
+                            ref={this._scrollable}
+                            className="display-line"
+                            onClick={this.setCurrentFrame}
+                            tabIndex={50}
+                        >
                             <svg
                                 style={{
                                     width: "100%",
@@ -436,12 +531,14 @@ export class Timeline extends React.Component<
                                 onMouseUp={this.dragEnd}
                                 onMouseLeave={this.dragEnd}
                             >
+                                {/* Renders the visible frames */}
                                 {this.state.selectionLength.map((frame, i) => {
                                     return (
                                         <svg key={`tl_${frame}`}>
                                             {
                                                 <>
-                                                    {frame % Math.round(this.state.selectionLength.length / 20) === 0 ? (
+                                                    {frame % Math.round(this.state.selectionLength.length / 20) ===
+                                                    0 ? (
                                                         <>
                                                             <text
                                                                 x={(i * 100) / this.state.selectionLength.length + "%"}
@@ -504,7 +601,7 @@ export class Timeline extends React.Component<
                                 })}
                             </svg>
                         </div>
-
+                        {/* Timeline scrollbar that has drag events */}
                         <div
                             className="timeline-scroll-handle"
                             onMouseMove={this.scrollDrag}
@@ -514,7 +611,12 @@ export class Timeline extends React.Component<
                             onDragStart={this.dragDomFalse}
                         >
                             <div className="scroll-handle" ref={this._scrollContainer} tabIndex={60}>
-                                <div className="handle" ref={this._scrollbarHandle} style={{ width: this.state.scrollWidth }}>
+                                <div
+                                    className="handle"
+                                    ref={this._scrollbarHandle}
+                                    style={{ width: this.state.scrollWidth }}
+                                >
+                                    {/* Handle that resizes the scrollbar to the left */}
                                     <div className="left-grabber">
                                         <div className="left-draggable">
                                             <div className="grabber"></div>
@@ -524,7 +626,7 @@ export class Timeline extends React.Component<
                                         <div className="text">{this.state.start}</div>
                                     </div>
                                     <div className="scrollbar"></div>
-
+                                    {/* Handle that resizes the scrollbar to the right */}
                                     <div className="right-grabber">
                                         <div className="text">{this.state.end}</div>
                                         <div className="right-draggable">
@@ -536,7 +638,7 @@ export class Timeline extends React.Component<
                                 </div>
                             </div>
                         </div>
-
+                        {/* Handles the limit of number of frames in the selected animation */}
                         <div className="input-frame">
                             <input
                                 ref={this._inputAnimationLimit}

+ 29 - 27
loaders/src/glTF/2.0/Extensions/EXT_meshopt_compression.ts

@@ -1,11 +1,18 @@
 import { Nullable } from "babylonjs/types";
 import { Tools } from "babylonjs/Misc/tools";
 import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
-import { GLTFLoader } from "../glTFLoader";
+import { ArrayItem, GLTFLoader } from "../glTFLoader";
 import { IBufferView } from "../glTFLoaderInterfaces";
+import { IEXTMeshoptCompression } from "babylonjs-gltf2interface";
 
 const NAME = "EXT_meshopt_compression";
 
+declare var MeshoptDecoder: any;
+
+interface IBufferViewMeshopt extends IBufferView {
+    _meshOptData?: Promise<ArrayBufferView>;
+}
+
 /**
  * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression)
  *
@@ -23,12 +30,12 @@ export class EXT_meshopt_compression implements IGLTFLoaderExtension {
     public enabled: boolean;
 
     /**
-     * Path to decoder module; defaults to https://preview.babylonjs.com/meshopt_decoder.module.js
+     * Path to decoder module; defaults to https://preview.babylonjs.com/meshopt_decoder.js
      */
-    public static DecoderPath: string = "https://preview.babylonjs.com/meshopt_decoder.module.js";
+    public static DecoderPath: string = "https://preview.babylonjs.com/meshopt_decoder.js";
 
     private _loader: GLTFLoader;
-    private _decoder: Promise<any>;
+    private _decoderPromise?: Promise<any>;
 
     /** @hidden */
     constructor(loader: GLTFLoader) {
@@ -36,46 +43,41 @@ export class EXT_meshopt_compression implements IGLTFLoaderExtension {
         this._loader = loader;
 
         if (this.enabled) {
-            var url = Tools.GetAbsoluteUrl(EXT_meshopt_compression.DecoderPath);
-
-            this._decoder = import(/* webpackIgnore: true */ url).then(function (result) {
+            this._decoderPromise = Tools.LoadScriptAsync(EXT_meshopt_compression.DecoderPath).then(() => {
                 // Wait for WebAssembly compilation before resolving promise
-                var MeshoptDecoder = result.MeshoptDecoder;
-                return MeshoptDecoder.ready.then(() => MeshoptDecoder);
+                return MeshoptDecoder.ready;
             });
         }
     }
 
     /** @hidden */
     public dispose() {
+        (this._loader as any) = null;
+        delete this._decoderPromise;
     }
 
     /** @hidden */
     public loadBufferViewAsync(context: string, bufferView: IBufferView): Nullable<Promise<ArrayBufferView>> {
-        if (bufferView.extensions && bufferView.extensions[this.name]) {
-            var extensionDef = bufferView.extensions[this.name];
-            if (extensionDef._decoded) {
-                return extensionDef._decoded;
+        return GLTFLoader.LoadExtensionAsync<IEXTMeshoptCompression, ArrayBufferView>(context, bufferView, this.name, (extensionContext, extension) => {
+            const bufferViewMeshopt = bufferView as IBufferViewMeshopt;
+            if (bufferViewMeshopt._meshOptData) {
+                return bufferViewMeshopt._meshOptData;
             }
 
-            var view = this._loader.loadBufferViewAsync(context, extensionDef);
-
-            extensionDef._decoded = Promise.all([view, this._decoder]).then(function (res) {
-                var source = res[0] as Uint8Array;
-                var decoder = res[1];
-                var count = extensionDef.count;
-                var stride = extensionDef.byteStride;
-                var result = new Uint8Array(new ArrayBuffer(count * stride));
-
-                decoder.decodeGltfBuffer(result, count, stride, source, extensionDef.mode, extensionDef.filter);
+            const buffer = ArrayItem.Get(`${context}/buffer`, this._loader.gltf.buffers, extension.buffer);
+            const bufferPromise = this._loader.loadBufferAsync(`/buffers/${buffer.index}`, buffer, (extension.byteOffset || 0), extension.byteLength);
 
+            bufferViewMeshopt._meshOptData = Promise.all([bufferPromise, this._decoderPromise]).then((res) => {
+                const source = res[0] as Uint8Array;
+                const count = extension.count;
+                const stride = extension.byteStride;
+                const result = new Uint8Array(new ArrayBuffer(count * stride));
+                MeshoptDecoder.decodeGltfBuffer(result, count, stride, source, extension.mode, extension.filter);
                 return Promise.resolve(result);
             });
 
-            return extensionDef._decoded;
-        } else {
-            return null;
-        }
+            return bufferViewMeshopt._meshOptData;
+        });
     }
 }
 

+ 10 - 2
loaders/src/glTF/2.0/glTFLoader.ts

@@ -1479,7 +1479,15 @@ export class GLTFLoader implements IGLTFLoader {
         return sampler._data;
     }
 
-    private _loadBufferAsync(context: string, buffer: IBuffer, byteOffset: number, byteLength: number): Promise<ArrayBufferView> {
+    /**
+     * Loads a glTF buffer.
+     * @param context The context when loading the asset
+     * @param buffer The glTF buffer property
+     * @param byteOffset The byte offset to use
+     * @param byteLength The byte length to use
+     * @returns A promise that resolves with the loaded data when the load is complete
+     */
+    public loadBufferAsync(context: string, buffer: IBuffer, byteOffset: number, byteLength: number): Promise<ArrayBufferView> {
         const extensionPromise = this._extensionsLoadBufferAsync(context, buffer, byteOffset, byteLength);
         if (extensionPromise) {
             return extensionPromise;
@@ -1525,7 +1533,7 @@ export class GLTFLoader implements IGLTFLoader {
         }
 
         const buffer = ArrayItem.Get(`${context}/buffer`, this._gltf.buffers, bufferView.buffer);
-        bufferView._data = this._loadBufferAsync(`/buffers/${buffer.index}`, buffer, (bufferView.byteOffset || 0), bufferView.byteLength);
+        bufferView._data = this.loadBufferAsync(`/buffers/${buffer.index}`, buffer, (bufferView.byteOffset || 0), bufferView.byteLength);
 
         return bufferView._data;
     }

+ 1 - 1
loaders/src/glTF/glTFValidation.ts

@@ -131,7 +131,7 @@ export class GLTFValidation {
                 worker.addEventListener("error", onError);
                 worker.addEventListener("message", onMessage);
 
-                worker.postMessage({ id: "init", url: Tools.GetAbsoluteUrl(this.Configuration.url) });
+                worker.postMessage({ id: "init", url: this.Configuration.url });
                 worker.postMessage({ id: "validate", data: data, rootUrl: rootUrl, fileName: fileName });
             });
         }

+ 5 - 5
localDev/index-views.html

@@ -117,15 +117,15 @@
             .require(indexjs)
             .load(function() {
                 BABYLON.DracoCompression.Configuration.decoder = {
-                    wasmUrl: "../dist/preview%20release/draco_wasm_wrapper_gltf.js",
-                    wasmBinaryUrl: "../dist/preview%20release/draco_decoder_gltf.wasm",
-                    fallbackUrl: "../dist/preview%20release/draco_decoder_gltf.js"
+                    wasmUrl: GetAbsoluteUrl("../dist/preview%20release/draco_wasm_wrapper_gltf.js"),
+                    wasmBinaryUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.wasm"),
+                    fallbackUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.js")
                 };
                 BABYLON.GLTFValidation.Configuration = {
-                    url: "../dist/preview%20release/gltf_validator.js"
+                    url: GetAbsoluteUrl("../dist/preview%20release/gltf_validator.js")
                 };
                 BABYLON.GLTF2.Loader.Extensions.EXT_meshopt_compression.DecoderPath =
-                    "../dist/preview%20release/meshopt_decoder.module.js";
+                    GetAbsoluteUrl("../dist/preview%20release/meshopt_decoder.js");
                 BABYLON.KhronosTextureContainer2.URLConfig = {
                     jsDecoderModule: GetAbsoluteUrl("../dist/preview%20release/babylon.ktx2Decoder.js"),
                     wasmUASTCToASTC: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),

+ 5 - 5
localDev/index.html

@@ -86,15 +86,15 @@
             .require(indexjs)
             .load(function() {
                 BABYLON.DracoCompression.Configuration.decoder = {
-                    wasmUrl: "../dist/preview%20release/draco_wasm_wrapper_gltf.js",
-                    wasmBinaryUrl: "../dist/preview%20release/draco_decoder_gltf.wasm",
-                    fallbackUrl: "../dist/preview%20release/draco_decoder_gltf.js"
+                    wasmUrl: GetAbsoluteUrl("../dist/preview%20release/draco_wasm_wrapper_gltf.js"),
+                    wasmBinaryUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.wasm"),
+                    fallbackUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.js")
                 };
                 BABYLON.GLTFValidation.Configuration = {
-                    url: "../dist/preview%20release/gltf_validator.js"
+                    url: GetAbsoluteUrl("../dist/preview%20release/gltf_validator.js")
                 };
                 BABYLON.GLTF2.Loader.Extensions.EXT_meshopt_compression.DecoderPath =
-                    "../dist/preview%20release/meshopt_decoder.module.js";
+                    GetAbsoluteUrl("../dist/preview%20release/meshopt_decoder.js");
                 BABYLON.KhronosTextureContainer2.URLConfig = {
                     jsDecoderModule: GetAbsoluteUrl("../dist/preview%20release/babylon.ktx2Decoder.js"),
                     wasmUASTCToASTC: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),

+ 5 - 5
sandbox/public/index-local.html

@@ -42,15 +42,15 @@
             .require("index.js")
             .load(() => {
                 BABYLON.DracoCompression.Configuration.decoder = {
-                    wasmUrl: "../../dist/preview%20release/draco_wasm_wrapper_gltf.js",
-                    wasmBinaryUrl: "../../dist/preview%20release/draco_decoder_gltf.wasm",
-                    fallbackUrl: "../../dist/preview%20release/draco_decoder_gltf.js"
+                    wasmUrl: GetAbsoluteUrl("../dist/preview%20release/draco_wasm_wrapper_gltf.js"),
+                    wasmBinaryUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.wasm"),
+                    fallbackUrl: GetAbsoluteUrl("../dist/preview%20release/draco_decoder_gltf.js")
                 };
                 BABYLON.GLTFValidation.Configuration = {
-                    url: "../../dist/preview%20release/gltf_validator.js"
+                    url: GetAbsoluteUrl("../dist/preview%20release/gltf_validator.js")
                 };
                 BABYLON.GLTF2.Loader.Extensions.EXT_meshopt_compression.DecoderPath =
-                    "../../dist/preview%20release/meshopt_decoder.module.js";
+                    GetAbsoluteUrl("../dist/preview%20release/meshopt_decoder.js");
                 BABYLON.KhronosTextureContainer2.URLConfig = {
                     jsDecoderModule: GetAbsoluteUrl("../dist/preview%20release/babylon.ktx2Decoder.js"),
                     wasmUASTCToASTC: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),

+ 2 - 10
src/Meshes/Compression/dracoCompression.ts

@@ -164,14 +164,6 @@ function worker(): void {
     };
 }
 
-function getAbsoluteUrl<T>(url: T): T | string {
-    if (typeof document !== "object" || typeof url !== "string") {
-        return url;
-    }
-
-    return Tools.GetAbsoluteUrl(url);
-}
-
 /**
  * Configuration for Draco compression
  */
@@ -295,7 +287,7 @@ export class DracoCompression implements IDisposable {
         const decoderInfo: { url: string | undefined, wasmBinaryPromise: Promise<ArrayBuffer | string | undefined> } =
             (decoder.wasmUrl && decoder.wasmBinaryUrl && typeof WebAssembly === "object") ? {
                 url: decoder.wasmUrl,
-                wasmBinaryPromise: Tools.LoadFileAsync(getAbsoluteUrl(decoder.wasmBinaryUrl))
+                wasmBinaryPromise: Tools.LoadFileAsync(decoder.wasmBinaryUrl)
             } : {
                 url: decoder.fallbackUrl,
                 wasmBinaryPromise: Promise.resolve(undefined)
@@ -329,7 +321,7 @@ export class DracoCompression implements IDisposable {
                         worker.postMessage({
                             id: "init",
                             decoder: {
-                                url: getAbsoluteUrl(decoderInfo.url),
+                                url: decoderInfo.url,
                                 wasmBinary: decoderWasmBinary,
                             }
                         });

BIN
tests/validation/ReferenceImages/gltfBuggyMeshopt.png


+ 25 - 18
tests/validation/config.json

@@ -1,6 +1,6 @@
 {
     "root": "https://cdn.babylonjs.com",
-    "tests": [   
+    "tests": [
         {
             "title": "Sprites",
             "playgroundId": "#ZX8DJ3#1",
@@ -11,28 +11,28 @@
             "renderCount": 2,
             "playgroundId": "#8S19ZC#3",            
             "referenceImage": "procedural_nme.png"
-        },   
+        },
         {
             "title": "EXT_texture_webp",
-            "playgroundId": "#LSAUH2#2",            
+            "playgroundId": "#LSAUH2#2",
             "referenceImage": "webp.png"
-        }, 
+        },
         {
             "title": "CSGVertColor",
-            "playgroundId": "#R0H1IX#0",            
+            "playgroundId": "#R0H1IX#0",
             "referenceImage": "csgVertColor.png"
         },
         {
             "title": "Particle subemitters",
-            "playgroundId": "#1LK70I#20",            
+            "playgroundId": "#1LK70I#20",
             "renderCount": 50,
             "referenceImage": "subemitters.png"
-        },    
+        },
         {
             "title": "Black and White post-process",
             "playgroundId": "#N55Q2M#0",
             "referenceImage": "bwpp.png"
-        },           
+        },
         {
             "title": "Sprite maps",
             "playgroundId": "#ARLADE#23",
@@ -43,20 +43,20 @@
             "title": "Point light shadows",
             "playgroundId": "#U2F7P9#4",
             "referenceImage": "point-light-shadows.png"
-        },             
+        },
         {
             "title": "Node material 0",
             "playgroundId": "#M5VQE9#20",
             "referenceImage": "node-material0.png",
             "renderCount": 5,
             "excludeFromAutomaticTesting": true
-        },    
+        },
         {
             "title": "Node material 1",
             "playgroundId": "#QJ71C6#1",
             "referenceImage": "node-material1.png",
             "renderCount": 20
-        },     
+        },
         {
             "title": "Node material 2",
             "playgroundId": "#WYM31D#4",
@@ -66,28 +66,28 @@
             "title": "Node material 3",
             "playgroundId": "#LWGVT0#2",
             "referenceImage": "node-material3.png"
-        },     
+        },
         {
             "title": "Node material 4",
             "playgroundId": "#ZYGRII#1",
             "referenceImage": "node-material4.png"
-        },        
+        },
         {
             "title": "Node material 5",
             "playgroundId": "#V8VH7B#0",
             "referenceImage": "node-material5.png"
-        },                     
+        },
         {
             "title": "Node material 6",
             "playgroundId": "#2XY3Z4#1",
             "referenceImage": "node-material6.png"
-        },    
+        },
         {
             "title": "Node material PBR 1",
             "playgroundId": "#D8AK3Z#17",
             "renderCount": 5,
             "referenceImage": "node-material-pbr-1.png"
-        },    
+        },
         {
             "title": "Basis loader",
             "playgroundId": "#4RN0VF#64",
@@ -670,6 +670,11 @@
             "referenceImage": "gltfBuggyDraco.png"
         },
         {
+            "title": "GLTF Buggy with Meshopt Compression",
+            "playgroundId": "#CIYTF6#0",
+            "referenceImage": "gltfBuggyMeshopt.png"
+        },
+        {
             "title": "GLTF BoomBox with Unlit Material",
             "playgroundId": "#GYM97C#2",
             "referenceImage": "gltfUnlit.png"
@@ -782,7 +787,7 @@
             "playgroundId": "#QI7TL3#18",
             "referenceImage": "pbr_codecoverage3.png"
         },
-                {
+        {
             "title": "Texture cache",
             "playgroundId": "#20OAV9#237",
             "referenceImage": "texture cache.png"
@@ -985,7 +990,9 @@
             "title": "Shadows CSM and LODs",
             "playgroundId": "#24HWT9#0",
             "referenceImage": "shadowscsmandlod.png",
-            "excludedEngines": ["webgl1"]
+            "excludedEngines": [
+                "webgl1"
+            ]
         },
         {
             "title": "Glow layer and LODs",

+ 15 - 5
tests/validation/validation.js

@@ -424,13 +424,23 @@ function init(_engineName) {
     BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental = true;
 
     BABYLON.DracoCompression.Configuration.decoder = {
-        wasmUrl: "../../dist/preview%20release/draco_wasm_wrapper_gltf.js",
-        wasmBinaryUrl: "../../dist/preview%20release/draco_decoder_gltf.wasm",
-        fallbackUrl: "../../dist/preview%20release/draco_decoder_gltf.js"
+        wasmUrl: GetAbsoluteUrl("../../dist/preview%20release/draco_wasm_wrapper_gltf.js"),
+        wasmBinaryUrl: GetAbsoluteUrl("../../dist/preview%20release/draco_decoder_gltf.wasm"),
+        fallbackUrl: GetAbsoluteUrl("../../dist/preview%20release/draco_decoder_gltf.js")
     };
-
     BABYLON.GLTFValidation.Configuration = {
-        url: "../../dist/preview%20release/gltf_validator.js"
+        url: GetAbsoluteUrl("../../dist/preview%20release/gltf_validator.js")
+    };
+    BABYLON.GLTF2.Loader.Extensions.EXT_meshopt_compression.DecoderPath =
+        GetAbsoluteUrl("../../dist/preview%20release/meshopt_decoder.js");
+    BABYLON.KhronosTextureContainer2.URLConfig = {
+        jsDecoderModule: GetAbsoluteUrl("../../dist/preview%20release/babylon.ktx2Decoder.js"),
+        wasmUASTCToASTC: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),
+        wasmUASTCToBC7: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_bc7.wasm"),
+        wasmUASTCToRGBA_UNORM: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_unorm.wasm"),
+        wasmUASTCToRGBA_SRGB: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_srgb.wasm"),
+        jsMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.js"),
+        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
     };
 
     BABYLON.KhronosTextureContainer2.URLConfig = {