private void ExportMaterial(BabylonMaterial babylonMaterial, GLTF gltf)
        {
            var name = babylonMaterial.name;
            var id   = babylonMaterial.id;

            RaiseMessage("GLTFExporter.Material | Export material named: " + name, 1);

            if (babylonMaterial.GetType() == typeof(BabylonStandardMaterial))
            {
                var babylonStandardMaterial = babylonMaterial as BabylonStandardMaterial;


                // --- prints ---

                RaiseMessage("GLTFExporter.Material | babylonMaterial data", 2);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);

                // Ambient
                for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambient[" + i + "]=" + babylonStandardMaterial.ambient[i], 3);
                }

                // Diffuse
                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuse.Length=" + babylonStandardMaterial.diffuse.Length, 3);
                for (int i = 0; i < babylonStandardMaterial.diffuse.Length; i++)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuse[" + i + "]=" + babylonStandardMaterial.diffuse[i], 3);
                }
                if (babylonStandardMaterial.diffuseTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3);
                }

                // Normal / bump
                if (babylonStandardMaterial.bumpTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 3);
                }

                // Specular
                for (int i = 0; i < babylonStandardMaterial.specular.Length; i++)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3);
                }
                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);

                // Occlusion
                if (babylonStandardMaterial.ambientTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 3);
                }

                // Emissive
                for (int i = 0; i < babylonStandardMaterial.emissive.Length; i++)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissive[" + i + "]=" + babylonStandardMaterial.emissive[i], 3);
                }
                if (babylonStandardMaterial.emissiveTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 3);
                }


                // --------------------------------
                // --------- gltfMaterial ---------
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                var gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                // Alpha
                string alphaMode;
                float? alphaCutoff;
                getAlphaMode(babylonStandardMaterial, out alphaMode, out alphaCutoff);
                gltfMaterial.alphaMode   = alphaMode;
                gltfMaterial.alphaCutoff = alphaCutoff;

                // DoubleSided
                gltfMaterial.doubleSided = !babylonMaterial.backFaceCulling;

                // Normal
                gltfMaterial.normalTexture = ExportTexture(babylonStandardMaterial.bumpTexture, gltf);

                // Occulison
                gltfMaterial.occlusionTexture = ExportTexture(babylonStandardMaterial.ambientTexture, gltf);

                // Emissive
                gltfMaterial.emissiveFactor  = babylonStandardMaterial.emissive;
                gltfMaterial.emissiveTexture = ExportTexture(babylonStandardMaterial.emissiveTexture, gltf);


                // --------------------------------
                // --- gltfPbrMetallicRoughness ---
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;

                // --- Global ---

                SpecularGlossiness _specularGlossiness = new SpecularGlossiness
                {
                    diffuse    = new BabylonColor3(babylonStandardMaterial.diffuse),
                    opacity    = babylonMaterial.alpha,
                    specular   = new BabylonColor3(babylonStandardMaterial.specular),
                    glossiness = babylonStandardMaterial.specularPower / 256
                };

                MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true);

                // Base color
                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                {
                    _metallicRoughness.baseColor.r,
                    _metallicRoughness.baseColor.g,
                    _metallicRoughness.baseColor.b,
                    _metallicRoughness.opacity
                };

                // Metallic roughness
                gltfPbrMetallicRoughness.metallicFactor  = _metallicRoughness.metallic;
                gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness;


                // --- Textures ---

                if (babylonStandardMaterial.diffuseTexture != null)
                {
                    Func <string, Bitmap> loadTextureFromOutput = delegate(string textureName)
                    {
                        return(LoadTexture(Path.Combine(gltf.OutputFolder, textureName)));
                    };

                    Bitmap diffuseBitmap = loadTextureFromOutput(babylonStandardMaterial.diffuseTexture.name);

                    if (diffuseBitmap != null)
                    {
                        Bitmap specularBitmap = null;
                        if (babylonStandardMaterial.specularTexture != null)
                        {
                            specularBitmap = loadTextureFromOutput(babylonStandardMaterial.specularTexture.name);
                        }

                        Bitmap opacityBitmap = null;
                        if (babylonStandardMaterial.diffuseTexture.hasAlpha == false && babylonStandardMaterial.opacityTexture != null)
                        {
                            opacityBitmap = loadTextureFromOutput(babylonStandardMaterial.opacityTexture.name);
                        }

                        // Retreive dimensions
                        int width              = 0;
                        int height             = 0;
                        var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, diffuseBitmap, specularBitmap, opacityBitmap);
                        if (!haveSameDimensions)
                        {
                            RaiseWarning("Diffuse, specular and opacity maps should have same dimensions", 2);
                        }

                        // Create base color and metallic+roughness maps
                        Bitmap baseColorBitmap         = new Bitmap(width, height);
                        Bitmap metallicRoughnessBitmap = new Bitmap(width, height);
                        var    hasAlpha = false;
                        for (int x = 0; x < width; x++)
                        {
                            for (int y = 0; y < height; y++)
                            {
                                var diffuse = diffuseBitmap.GetPixel(x, y);
                                SpecularGlossiness specularGlossinessTexture = new SpecularGlossiness
                                {
                                    diffuse = new BabylonColor3(diffuse),
                                    opacity = babylonStandardMaterial.diffuseTexture.hasAlpha? diffuse.A / 255.0f :
                                              opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB ? opacityBitmap.GetPixel(x, y).R / 255.0f :
                                              opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB == false?opacityBitmap.GetPixel(x, y).A / 255.0f :
                                              1,
                                    specular = specularBitmap != null ? new BabylonColor3(specularBitmap.GetPixel(x, y)) :
                                               new BabylonColor3(),
                                    glossiness = babylonStandardMaterial.useGlossinessFromSpecularMapAlpha && specularBitmap != null?specularBitmap.GetPixel(x, y).A / 255.0f :
                                                 0
                                };

                                var displayPrints = x == width / 2 && y == height / 2;
                                MetallicRoughness metallicRoughnessTexture = ConvertToMetallicRoughness(specularGlossinessTexture, displayPrints);

                                Color colorBase = Color.FromArgb(
                                    (int)(metallicRoughnessTexture.opacity * 255),
                                    (int)(metallicRoughnessTexture.baseColor.r * 255),
                                    (int)(metallicRoughnessTexture.baseColor.g * 255),
                                    (int)(metallicRoughnessTexture.baseColor.b * 255)
                                    );
                                baseColorBitmap.SetPixel(x, y, colorBase);
                                if (metallicRoughnessTexture.opacity != 1)
                                {
                                    hasAlpha = true;
                                }

                                // The metalness values are sampled from the B channel.
                                // The roughness values are sampled from the G channel.
                                // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
                                Color colorMetallicRoughness = Color.FromArgb(
                                    0,
                                    (int)(metallicRoughnessTexture.roughness * 255),
                                    (int)(metallicRoughnessTexture.metallic * 255)
                                    );
                                metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
                            }
                        }

                        // Export maps and textures
                        var baseColorFileName = babylonMaterial.name + "_baseColor" + (hasAlpha ? ".png" : ".jpg");
                        gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, baseColorBitmap, baseColorFileName, gltf);
                        if (specularBitmap != null)
                        {
                            gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, metallicRoughnessBitmap, babylonMaterial.name + "_metallicRoughness" + ".jpg", gltf);
                        }
                    }
                }
            }
            else if (babylonMaterial.GetType() == typeof(BabylonPBRMetallicRoughnessMaterial))
            {
                var babylonPBRMetallicRoughnessMaterial = babylonMaterial as BabylonPBRMetallicRoughnessMaterial;


                // --- prints ---

                RaiseMessage("GLTFExporter.Material | babylonMaterial data", 2);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                RaiseMessage("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);

                // Global
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights=" + babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights, 3);
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.disableLighting=" + babylonPBRMetallicRoughnessMaterial.disableLighting, 3);
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.alphaCutOff=" + babylonPBRMetallicRoughnessMaterial.alphaCutOff, 3);
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.transparencyMode=" + babylonPBRMetallicRoughnessMaterial.transparencyMode, 3);
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.doubleSided=" + babylonPBRMetallicRoughnessMaterial.doubleSided, 3);

                // Base color
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor.Length=" + babylonPBRMetallicRoughnessMaterial.baseColor.Length, 3);
                for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.baseColor.Length; i++)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.baseColor[i], 3);
                }
                if (babylonPBRMetallicRoughnessMaterial.baseTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseTexture=null", 3);
                }

                // Metallic+roughness
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallic=" + babylonPBRMetallicRoughnessMaterial.metallic, 3);
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.roughness=" + babylonPBRMetallicRoughnessMaterial.roughness, 3);
                if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture=null", 3);
                }

                // Environment
                if (babylonPBRMetallicRoughnessMaterial.environmentTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.environmentTexture=null", 3);
                }

                // Normal / bump
                if (babylonPBRMetallicRoughnessMaterial.normalTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.normalTexture=null", 3);
                }
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapX=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapX, 3);
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapY=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapY, 3);

                // Emissive
                for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.emissiveColor.Length; i++)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.emissiveColor[i], 3);
                }
                if (babylonPBRMetallicRoughnessMaterial.emissiveTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveTexture=null", 3);
                }

                // Ambient occlusion
                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionStrength=" + babylonPBRMetallicRoughnessMaterial.occlusionStrength, 3);
                if (babylonPBRMetallicRoughnessMaterial.occlusionTexture == null)
                {
                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionTexture=null", 3);
                }


                // --------------------------------
                // --------- gltfMaterial ---------
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                var gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                // Alpha
                string alphaMode;
                float? alphaCutoff;
                getAlphaMode(babylonPBRMetallicRoughnessMaterial, out alphaMode, out alphaCutoff);
                gltfMaterial.alphaMode   = alphaMode;
                gltfMaterial.alphaCutoff = alphaCutoff;

                // DoubleSided
                gltfMaterial.doubleSided = babylonPBRMetallicRoughnessMaterial.doubleSided;

                // Normal
                gltfMaterial.normalTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.normalTexture, gltf);

                // Occulison
                gltfMaterial.occlusionTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.occlusionTexture, gltf);

                // Emissive
                gltfMaterial.emissiveFactor  = babylonPBRMetallicRoughnessMaterial.emissiveColor;
                gltfMaterial.emissiveTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.emissiveTexture, gltf);


                // --------------------------------
                // --- gltfPbrMetallicRoughness ---
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;

                // --- Global ---

                // Base color
                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                {
                    babylonPBRMetallicRoughnessMaterial.baseColor[0],
                    babylonPBRMetallicRoughnessMaterial.baseColor[1],
                    babylonPBRMetallicRoughnessMaterial.baseColor[2],
                    1.0f // TODO - alpha
                };
                gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.baseTexture, gltf);

                // Metallic roughness
                gltfPbrMetallicRoughness.metallicFactor           = babylonPBRMetallicRoughnessMaterial.metallic;
                gltfPbrMetallicRoughness.roughnessFactor          = babylonPBRMetallicRoughnessMaterial.roughness;
                gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture, gltf);
            }
            else
            {
                RaiseWarning("GLTFExporter.Material | Unsupported material type: " + babylonMaterial.GetType(), 2);
            }
        }
Example #2
0
        private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List <GLTFChannel> channelList, List <GLTFAnimationSampler> samplerList, int startFrame, int endFrame, BabylonScene babylonScene, bool offsetToStartAtFrameZero = true)
        {
            if (exportedMorphTargets.Contains(babylonMorphTargetManager) || !_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))
            {
                return(false);
            }

            var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager);
            var frames             = new List <float>(influencesPerFrame.Keys);

            var framesInRange = frames.Where(frame => frame >= startFrame && frame <= endFrame).ToList();

            framesInRange.Sort(); // Mandatory to sort otherwise gltf loader of babylon doesn't understand
            if (framesInRange.Count() <= 0)
            {
                return(false);
            }

            logger.RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2);

            // Target
            var gltfTarget = new GLTFChannelTarget
            {
                node = gltfNode.index
            };

            gltfTarget.path = "weights";

            // Buffer
            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

            // --- Input ---
            var accessorInput = GLTFBufferService.Instance.CreateAccessor(
                gltf,
                GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                "accessorAnimationInput",
                GLTFAccessor.ComponentType.FLOAT,
                GLTFAccessor.TypeEnum.SCALAR
                );

            // Populate accessor
            accessorInput.min = new float[] { float.MaxValue };
            accessorInput.max = new float[] { float.MinValue };

            int numKeys = 0;

            foreach (var frame in framesInRange)
            {
                numKeys++;
                float inputValue = frame;
                if (offsetToStartAtFrameZero)
                {
                    inputValue -= startFrame;
                }
                inputValue /= (float)babylonScene.TimelineFramesPerSecond;
                // Store values as bytes
                accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
                // Update min and max values
                GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
            }
            accessorInput.count = numKeys;

            if (accessorInput.count == 0)
            {
                logger.RaiseWarning(String.Format("GLTFExporter.Animation | No frames to export in morph target animation \"weight\" for mesh named \"{0}\". This will cause an error in the output gltf.", babylonMorphTargetManager.sourceMesh.name));
            }

            // --- Output ---
            GLTFAccessor accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                gltf,
                GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                "accessorAnimationWeights",
                GLTFAccessor.ComponentType.FLOAT,
                GLTFAccessor.TypeEnum.SCALAR
                );

            // Populate accessor
            foreach (var frame in framesInRange)
            {
                var outputValues = influencesPerFrame[frame];
                // Store values as bytes
                foreach (var outputValue in outputValues)
                {
                    accessorOutput.count++;
                    accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                }
            }

            // Animation sampler
            var gltfAnimationSampler = new GLTFAnimationSampler
            {
                input  = accessorInput.index,
                output = accessorOutput.index
            };

            gltfAnimationSampler.index = samplerList.Count;
            samplerList.Add(gltfAnimationSampler);

            // Channel
            var gltfChannel = new GLTFChannel
            {
                sampler = gltfAnimationSampler.index,
                target  = gltfTarget
            };

            channelList.Add(gltfChannel);

            // Mark this morph target as exported.
            exportedMorphTargets.Add(babylonMorphTargetManager);
            return(true);
        }
        private GLTFCamera ExportCamera(ref GLTFNode gltfNode, BabylonCamera babylonCamera, GLTF gltf, GLTFNode gltfParentNode)
        {
            RaiseMessage("GLTFExporter.Camera | Export camera named: " + babylonCamera.name, 2);

            // --- prints ---
            #region prints

            RaiseVerbose("GLTFExporter.Camera | babylonCamera data", 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.type=" + babylonCamera.type, 4);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.fov=" + babylonCamera.fov, 4);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.maxZ=" + babylonCamera.maxZ, 4);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.minZ=" + babylonCamera.minZ, 4);
            #endregion


            // --------------------------
            // ------- gltfCamera -------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | create gltfCamera", 3);

            // Camera
            var gltfCamera = new GLTFCamera {
                name = babylonCamera.name
            };
            gltfCamera.index = gltf.CamerasList.Count;
            gltf.CamerasList.Add(gltfCamera);
            gltfNode.camera     = gltfCamera.index;
            gltfCamera.gltfNode = gltfNode;

            // Camera type
            switch (babylonCamera.mode)
            {
            case (BabylonCamera.CameraMode.ORTHOGRAPHIC_CAMERA):
                var gltfCameraOrthographic = new GLTFCameraOrthographic();
                gltfCameraOrthographic.xmag  = 1;    // Do not bother about it - still mandatory
                gltfCameraOrthographic.ymag  = 1;    // Do not bother about it - still mandatory
                gltfCameraOrthographic.zfar  = babylonCamera.maxZ;
                gltfCameraOrthographic.znear = babylonCamera.minZ;

                gltfCamera.type         = GLTFCamera.CameraType.orthographic.ToString();
                gltfCamera.orthographic = gltfCameraOrthographic;
                break;

            case (BabylonCamera.CameraMode.PERSPECTIVE_CAMERA):
                var gltfCameraPerspective = new GLTFCameraPerspective();
                gltfCameraPerspective.aspectRatio = null;              // Do not bother about it - use default glTF value
                gltfCameraPerspective.yfov        = babylonCamera.fov; // Babylon camera fov mode is assumed to be vertical (FOVMODE_VERTICAL_FIXED)
                gltfCameraPerspective.zfar        = babylonCamera.maxZ;
                gltfCameraPerspective.znear       = babylonCamera.minZ;

                gltfCamera.type        = GLTFCamera.CameraType.perspective.ToString();
                gltfCamera.perspective = gltfCameraPerspective;
                break;

            default:
                RaiseError("GLTFExporter.Camera | camera mode not found");
                break;
            }

            return(gltfCamera);
        }
Example #4
0
        private GLTFNode ExportAbstractMesh(ref GLTFNode gltfNode, BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfParentNode, BabylonScene babylonScene)
        {
            logger.RaiseMessage("GLTFExporter.AbstractMesh | Export abstract mesh named: " + babylonAbstractMesh.name, 2);

            // Mesh
            var gltfMesh = gltf.MeshesList.Find(_gltfMesh => _gltfMesh.idGroupInstance == babylonAbstractMesh.idGroupInstance);

            if (gltfMesh != null)
            {
                gltfNode.mesh = gltfMesh.index;

                // Skin
                if (gltfMesh.idBabylonSkeleton.HasValue)
                {
                    var babylonSkeleton = babylonScene.skeletons[gltfMesh.idBabylonSkeleton.Value];
                    // Export a new skeleton if necessary and a new skin
                    var gltfSkin = ExportSkin(babylonSkeleton, gltf, gltfNode, gltfMesh);
                    gltfNode.skin = gltfSkin.index;
                }
            }

            return(gltfNode);
        }
Example #5
0
        private void ExportNodeAnimation(GLTFAnimation gltfAnimation, int startFrame, int endFrame, GLTF gltf, BabylonNode babylonNode, GLTFNode gltfNode, BabylonScene babylonScene, BabylonAnimationGroup animationGroup = null)
        {
            var channelList = gltfAnimation.ChannelList;
            var samplerList = gltfAnimation.SamplerList;

            bool exportNonAnimated = exportParameters.animgroupExportNonAnimated;

            // Combine babylon animations from .babylon file and cached ones
            var babylonAnimations = new List <BabylonAnimation>();

            if (animationGroup != null)
            {
                var targetedAnimations = animationGroup.targetedAnimations.Where(animation => animation.targetId == babylonNode.id);
                foreach (var targetedAnimation in targetedAnimations)
                {
                    babylonAnimations.Add(targetedAnimation.animation);
                }
            }

            // Do not include the node animations if a provided animation group already includes them.
            if (babylonAnimations.Count <= 0)
            {
                if (babylonNode.animations != null)
                {
                    babylonAnimations.AddRange(babylonNode.animations);
                }

                if (babylonNode.extraAnimations != null)
                {
                    babylonAnimations.AddRange(babylonNode.extraAnimations);
                }
            }

            // Filter animations to only keep TRS ones
            babylonAnimations = babylonAnimations.FindAll(babylonAnimation => _getTargetPath(babylonAnimation.property) != null);

            if (babylonAnimations.Count > 0 || exportNonAnimated)
            {
                if (babylonAnimations.Count > 0)
                {
                    logger.RaiseMessage("GLTFExporter.Animation | Export animations of node named: " + babylonNode.name, 2);
                }
                else if (exportNonAnimated)
                {
                    logger.RaiseMessage("GLTFExporter.Animation | Export dummy animation for node named: " + babylonNode.name, 2);
                    // Export a dummy animation
                    babylonAnimations.Add(GetDummyAnimation(gltfNode, startFrame, endFrame, babylonScene));
                }


                foreach (BabylonAnimation babylonAnimation in babylonAnimations)
                {
                    var babylonAnimationKeysInRange = babylonAnimation.keys.Where(key => key.frame >= startFrame && key.frame <= endFrame);
                    if (babylonAnimationKeysInRange.Count() <= 0)
                    {
                        continue;
                    }

                    // Target
                    var gltfTarget = new GLTFChannelTarget
                    {
                        node = gltfNode.index
                    };
                    gltfTarget.path = _getTargetPath(babylonAnimation.property);

                    // --- Input ---
                    var accessorInput = _createAndPopulateInput(gltf, babylonAnimation, startFrame, endFrame);
                    if (accessorInput == null)
                    {
                        continue;
                    }

                    // --- Output ---
                    GLTFAccessor accessorOutput = _createAccessorOfPath(gltfTarget.path, gltf);

                    // Populate accessor
                    int numKeys = 0;
                    foreach (var babylonAnimationKey in babylonAnimationKeysInRange)
                    {
                        numKeys++;

                        // copy data before changing it in case animation groups overlap
                        float[] outputValues = new float[babylonAnimationKey.values.Length];
                        babylonAnimationKey.values.CopyTo(outputValues, 0);

                        // Switch coordinate system at object level
                        if (babylonAnimation.property == "position")
                        {
                            outputValues[2] *= -1;
                        }
                        else if (babylonAnimation.property == "rotationQuaternion")
                        {
                            outputValues[0] *= -1;
                            outputValues[1] *= -1;
                        }

                        // Store values as bytes
                        foreach (var outputValue in outputValues)
                        {
                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                        }
                    }
                    ;
                    accessorOutput.count = numKeys;
                    if (accessorOutput.count == 0)
                    {
                        logger.RaiseWarning(String.Format("GLTFExporter.Animation | No frames to export in node animation \"{1}\" of node named \"{0}\". This will cause an error in the output gltf.", babylonNode.name, babylonAnimation.name));
                    }

                    // Animation sampler
                    var gltfAnimationSampler = new GLTFAnimationSampler
                    {
                        input  = accessorInput.index,
                        output = accessorOutput.index
                    };
                    gltfAnimationSampler.index = samplerList.Count;
                    samplerList.Add(gltfAnimationSampler);

                    // Channel
                    var gltfChannel = new GLTFChannel
                    {
                        sampler = gltfAnimationSampler.index,
                        target  = gltfTarget
                    };
                    channelList.Add(gltfChannel);
                }
            }

            ExportGLTFExtension(babylonNode, ref gltfAnimation, gltf);
        }
 private GLTFTextureInfo ExportBaseColorTexture(GLTF gltf, BabylonTexture babylonTexture)
 {
     return(babylonTexture == null ? null : babylonTexture.bitmap != null?ExportBitmapTexture(gltf, babylonTexture) : ExportTexture(babylonTexture, gltf));
 }
 private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf)
 {
     return(ExportTexture(babylonTexture, gltf, null));
 }
Example #8
0
        private GLTFTextureInfo ExportEmissiveTexture(BabylonStandardMaterial babylonMaterial, GLTF gltf, float[] defaultEmissive, float[] defaultDiffuse)
        {
            // Use one as a reference for UVs parameters
            var babylonTexture = babylonMaterial.emissiveTexture != null ? babylonMaterial.emissiveTexture : babylonMaterial.diffuseTexture;

            if (babylonTexture == null)
            {
                return(null);
            }
            if (babylonMaterial.emissiveTexture == null && defaultEmissive.IsAlmostEqualTo(new float[] { 0, 0, 0 }, 0))
            {
                return(null);
            }

            if (GetRegisteredEmissive(babylonMaterial, defaultDiffuse, defaultEmissive) != null)
            {
                return(GetRegisteredEmissive(babylonMaterial, defaultDiffuse, defaultEmissive));
            }

            Bitmap emissivePremultipliedBitmap = null;

            if (CopyTexturesToOutput)
            {
                // Emissive
                Bitmap emissiveBitmap = null;
                if (babylonMaterial.emissiveTexture != null)
                {
                    emissiveBitmap = LoadTexture(babylonMaterial.emissiveTexture.originalPath);
                }

                // Diffuse
                Bitmap diffuseBitmap = null;
                if (babylonMaterial.diffuseTexture != null)
                {
                    diffuseBitmap = LoadTexture(babylonMaterial.diffuseTexture.originalPath);
                }

                if (emissiveBitmap != null || diffuseBitmap != null)
                {
                    // Retreive dimensions
                    int width              = 0;
                    int height             = 0;
                    var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, emissiveBitmap, diffuseBitmap);
                    if (!haveSameDimensions)
                    {
                        RaiseError("Emissive and diffuse maps should have same dimensions", 2);
                    }

                    // Create pre-multiplied emissive map
                    emissivePremultipliedBitmap = new Bitmap(width, height);
                    for (int x = 0; x < width; x++)
                    {
                        for (int y = 0; y < height; y++)
                        {
                            var _emissive = emissiveBitmap != null?emissiveBitmap.GetPixel(x, y).toArrayRGB().Multiply(1f / 255.0f) : defaultEmissive;

                            var _diffuse = diffuseBitmap != null?diffuseBitmap.GetPixel(x, y).toArrayRGB().Multiply(1f / 255.0f) : defaultDiffuse;

                            var emissivePremultiplied = _emissive.Multiply(_diffuse);

                            Color colorEmissivePremultiplied = Color.FromArgb(
                                (int)(emissivePremultiplied[0] * 255),
                                (int)(emissivePremultiplied[1] * 255),
                                (int)(emissivePremultiplied[2] * 255)
                                );
                            emissivePremultipliedBitmap.SetPixel(x, y, colorEmissivePremultiplied);
                        }
                    }
                }
            }

            var name = babylonMaterial.name + "_emissive.jpg";

            var emissiveTextureInfo = ExportBitmapTexture(gltf, babylonTexture, emissivePremultipliedBitmap, name);

            RegisterEmissive(emissiveTextureInfo, babylonMaterial, defaultDiffuse, defaultEmissive);

            return(emissiveTextureInfo);
        }
Example #9
0
        private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf, string name, Func <string> writeImageFunc)
        {
            if (babylonTexture == null)
            {
                return(null);
            }

            if (name == null)
            {
                name = babylonTexture.name;
            }

            if (CheckIfImageIsRegistered(name))
            {
                var TextureComponent = GetRegisteredTexture(name);

                return(TextureComponent);
            }

            RaiseMessage("GLTFExporter.Texture | Export texture named: " + name, 2);

            string validImageFormat = writeImageFunc.Invoke();

            if (validImageFormat == null)
            {
                return(null);
            }

            name = Path.ChangeExtension(name, validImageFormat);

            // --------------------------
            // -------- Sampler ---------
            // --------------------------

            RaiseMessage("GLTFExporter.Texture | create sampler", 3);
            GLTFSampler gltfSampler = new GLTFSampler();

            gltfSampler.index = gltf.SamplersList.Count;
            gltf.SamplersList.Add(gltfSampler);

            // --- Retreive info from babylon texture ---
            // Mag and min filters
            GLTFSampler.TextureMagFilter?magFilter;
            GLTFSampler.TextureMinFilter?minFilter;
            getSamplingParameters(babylonTexture.samplingMode, out magFilter, out minFilter);
            gltfSampler.magFilter = magFilter;
            gltfSampler.minFilter = minFilter;
            // WrapS and wrapT
            gltfSampler.wrapS = getWrapMode(babylonTexture.wrapU);
            gltfSampler.wrapT = getWrapMode(babylonTexture.wrapV);


            // --------------------------
            // --------- Image ----------
            // --------------------------

            RaiseMessage("GLTFExporter.Texture | create image", 3);
            GLTFImage gltfImage = new GLTFImage
            {
                uri = name
            };

            gltfImage.index = gltf.ImagesList.Count;
            gltf.ImagesList.Add(gltfImage);
            switch (validImageFormat)
            {
            case "jpg":
                gltfImage.FileExtension = "jpeg";
                break;

            case "png":
                gltfImage.FileExtension = "png";
                break;
            }


            // --------------------------
            // -------- Texture ---------
            // --------------------------

            RaiseMessage("GLTFExporter.Texture | create texture", 3);
            var gltfTexture = new GLTFTexture
            {
                name    = name,
                sampler = gltfSampler.index,
                source  = gltfImage.index
            };

            gltfTexture.index = gltf.TexturesList.Count;
            gltf.TexturesList.Add(gltfTexture);


            // --------------------------
            // ------ TextureInfo -------
            // --------------------------
            var gltfTextureInfo = new GLTFTextureInfo
            {
                index    = gltfTexture.index,
                texCoord = babylonTexture.coordinatesIndex
            };

            RegisterTexture(gltfTextureInfo, name);

            return(gltfTextureInfo);
        }
        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary)
        {
            RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            ReportProgressChanged(0);
            var gltf = new GLTF(Path.GetDirectoryName(outputFile));

            // Asset
            gltf.asset = new GLTFAsset
            {
                version   = "2.0",
                generator = "Babylon2Gltf2017",
                copyright = "2017 (c) BabylonJS"
                            // no minVersion
            };

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene scene = new GLTFScene();

            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            ReportProgressChanged(10);
            var babylonMaterials = babylonScene.MaterialsList;

            babylonMaterials.ForEach((babylonMaterial) =>
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            });
            // TODO - Handle multimaterials
            RaiseMessage(string.Format("GLTFExporter | Total: {0}", gltf.MaterialsList.Count /*+ glTF.MultiMaterialsList.Count*/), Color.Gray, 1);

            // Nodes
            List <BabylonNode> babylonNodes = new List <BabylonNode>();

            babylonNodes.AddRange(babylonScene.meshes);
            babylonNodes.AddRange(babylonScene.lights);
            babylonNodes.AddRange(babylonScene.cameras);

            // Root nodes
            RaiseMessage("GLTFExporter | Exporting root nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
            var progressionStep = 80.0f / babylonRootNodes.Count;
            var progression     = 20.0f;

            ReportProgressChanged((int)progression);
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");
            // Cast lists to arrays
            gltf.Prepare();
            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings()); // Standard serializer, not the optimized one
            var sb             = new StringBuilder();
            var sw             = new StringWriter(sb, CultureInfo.InvariantCulture);

            using (var jsonWriter = new JsonTextWriter(sw))
            {
                jsonWriter.Formatting = Formatting.None;
                jsonSerializer.Serialize(jsonWriter, gltf);
            }
            string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");

            File.WriteAllText(outputGltfFile, sb.ToString());

            // Binary
            if (generateBinary)
            {
                // TODO - Export glTF data to binary format .glb
                RaiseError("GLTFExporter | TODO - Generating binary files");
            }

            ReportProgressChanged(100);
        }
        public void ExportGltf(BabylonScene babylonScene, string outputDirectory, string outputFileName, bool generateBinary)
        {
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            // Force output file extension to be gltf
            outputFileName = Path.ChangeExtension(outputFileName, "gltf");

            // Update path of output .gltf file to include subdirectory
            var outputFile = Path.Combine(outputDirectory, outputFileName);

            float progressionStep;
            var   progression = 0.0f;

            ReportProgressChanged((int)progression);

            // Initialization
            initBabylonNodes(babylonScene);
            babylonMaterialsToExport = new List <BabylonMaterial>();

            var gltf = new GLTF(outputFile);

            // Asset
            gltf.asset = new GLTFAsset
            {
                version   = "2.0",
                copyright = "2017 (c) BabylonJS"
                            // no minVersion
            };

            string maxVersion = null;

#if MAX2015
            maxVersion = "2015";
#elif MAX2017
            maxVersion = "2017";
#elif MAX2018
            maxVersion = "2018";
#endif
            gltf.asset.generator = $"babylon.js glTF exporter for 3ds max {maxVersion} v{exporterVersion}";

            // Extensions
            gltf.extensionsUsed     = new List <string>();
            gltf.extensionsRequired = new List <string>();

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene   scene  = new GLTFScene();
            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Meshes
            RaiseMessage("GLTFExporter | Exporting meshes");
            progression = 10.0f;
            ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            }

            // Root nodes
            RaiseMessage("GLTFExporter | Exporting nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
            progressionStep          = 40.0f / babylonRootNodes.Count;
            alreadyExportedSkeletons = new Dictionary <BabylonSkeleton, BabylonSkeletonExportData>();
            NbNodesByName            = new Dictionary <string, int>();
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            foreach (var babylonMaterial in babylonMaterialsToExport)
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            }
            ;
            RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);

            // Prepare buffers
            gltf.BuffersList.ForEach(buffer =>
            {
                buffer.BufferViews.ForEach(bufferView =>
                {
                    bufferView.Accessors.ForEach(accessor =>
                    {
                        // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements
                        accessor.bytesList = new List <byte>(padChunk(accessor.bytesList.ToArray(), 4, 0x00));

                        // Update byte properties
                        accessor.byteOffset    = bufferView.byteLength;
                        bufferView.byteLength += accessor.bytesList.Count;
                        // Merge bytes
                        bufferView.bytesList.AddRange(accessor.bytesList);
                    });
                    // Update byte properties
                    bufferView.byteOffset = buffer.byteLength;
                    buffer.byteLength    += bufferView.bytesList.Count;
                    // Merge bytes
                    buffer.bytesList.AddRange(bufferView.bytesList);
                });
            });

            // Cast lists to arrays
            gltf.Prepare();

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");
            if (!generateBinary)
            {
                // Write .gltf file
                string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                File.WriteAllText(outputGltfFile, gltfToJson(gltf));

                // Write .bin file
                string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
                {
                    gltf.BuffersList.ForEach(buffer =>
                    {
                        buffer.bytesList.ForEach(b => writer.Write(b));
                    });
                }
            }
            else
            {
                // Export glTF data to binary format .glb

                // Header
                UInt32 magic   = 0x46546C67; // ASCII code for glTF
                UInt32 version = 2;
                UInt32 length  = 12;         // Header length

                // --- JSON chunk ---
                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
                // Remove buffers uri
                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                {
                    gltfBuffer.uri = null;
                }
                // Switch images to binary
                var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
                imageBufferViews.ForEach(imageBufferView =>
                {
                    imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
                });
                gltf.Prepare();
                // Serialize gltf data to JSON string then convert it to bytes
                byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
                chunkDataJson = padChunk(chunkDataJson, 4, 0x20);
                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
                length += chunkLengthJson + 8; // 8 = JSON chunk header length

                // bin chunk
                UInt32 chunkTypeBin   = 0x004E4942; // ASCII code for BIN
                UInt32 chunkLengthBin = 0;
                if (gltf.BuffersList.Count > 0)
                {
                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                    {
                        chunkLengthBin += (uint)gltfBuffer.byteLength;
                    }
                    length += chunkLengthBin + 8; // 8 = bin chunk header length
                }


                // Write binary file
                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
                {
                    // Header
                    writer.Write(magic);
                    writer.Write(version);
                    writer.Write(length);

                    // JSON chunk
                    writer.Write(chunkLengthJson);
                    writer.Write(chunkTypeJson);
                    writer.Write(chunkDataJson);

                    // bin chunk
                    if (gltf.BuffersList.Count > 0)
                    {
                        writer.Write(chunkLengthBin);
                        writer.Write(chunkTypeBin);
                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
                    }
                };
            }

            ReportProgressChanged(100);
        }
        private void ExportMaterial(BabylonMaterial babylonMaterial, GLTF gltf)
        {
            var name = babylonMaterial.name;
            var id   = babylonMaterial.id;

            RaiseMessage("GLTFExporter.Material | Export material named: " + name, 1);

            if (babylonMaterial.GetType() == typeof(BabylonStandardMaterial))
            {
                var babylonStandardMaterial = babylonMaterial as BabylonStandardMaterial;


                // --- prints ---
                #region prints

                RaiseVerbose("GLTFExporter.Material | babylonMaterial data", 2);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);

                // Ambient
                for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambient[" + i + "]=" + babylonStandardMaterial.ambient[i], 3);
                }

                // Diffuse
                RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuse.Length=" + babylonStandardMaterial.diffuse.Length, 3);
                for (int i = 0; i < babylonStandardMaterial.diffuse.Length; i++)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuse[" + i + "]=" + babylonStandardMaterial.diffuse[i], 3);
                }
                if (babylonStandardMaterial.diffuseTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3);
                }
                else
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture.name=" + babylonStandardMaterial.diffuseTexture.name, 3);
                }

                // Normal / bump
                if (babylonStandardMaterial.bumpTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 3);
                }
                else
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.bumpTexture.name=" + babylonStandardMaterial.bumpTexture.name, 3);
                }

                // Opacity
                if (babylonStandardMaterial.opacityTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.opacityTexture=null", 3);
                }
                else
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.opacityTexture.name=" + babylonStandardMaterial.opacityTexture.name, 3);
                }

                // Specular
                for (int i = 0; i < babylonStandardMaterial.specular.Length; i++)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3);
                }
                RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
                if (babylonStandardMaterial.specularTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularTexture=null", 3);
                }
                else
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularTexture.name=" + babylonStandardMaterial.specularTexture.name, 3);
                }

                // Occlusion
                if (babylonStandardMaterial.ambientTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 3);
                }
                else
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambientTexture.name=" + babylonStandardMaterial.ambientTexture.name, 3);
                }

                // Emissive
                for (int i = 0; i < babylonStandardMaterial.emissive.Length; i++)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissive[" + i + "]=" + babylonStandardMaterial.emissive[i], 3);
                }
                if (babylonStandardMaterial.emissiveTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 3);
                }
                else
                {
                    RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture.name=" + babylonStandardMaterial.emissiveTexture.name, 3);
                }
                #endregion


                // --------------------------------
                // --------- gltfMaterial ---------
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                var gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                // Alpha
                string alphaMode;
                float? alphaCutoff;
                getAlphaMode(babylonStandardMaterial, out alphaMode, out alphaCutoff);
                gltfMaterial.alphaMode   = alphaMode;
                gltfMaterial.alphaCutoff = alphaCutoff;

                // DoubleSided
                gltfMaterial.doubleSided = !babylonMaterial.backFaceCulling;

                // Normal
                gltfMaterial.normalTexture = ExportTexture(babylonStandardMaterial.bumpTexture, gltf);

                // Occulison
                gltfMaterial.occlusionTexture = ExportTexture(babylonStandardMaterial.ambientTexture, gltf);

                // Emissive
                gltfMaterial.emissiveFactor  = babylonStandardMaterial.emissive.Multiply(babylonStandardMaterial.diffuse);
                gltfMaterial.emissiveTexture = ExportEmissiveTexture(babylonStandardMaterial, gltf, babylonStandardMaterial.emissive, babylonStandardMaterial.diffuse);

                // Constraints
                if (gltfMaterial.emissiveTexture != null)
                {
                    gltfMaterial.emissiveFactor = new[] { 1.0f, 1.0f, 1.0f };
                }

                // --------------------------------
                // --- gltfPbrMetallicRoughness ---
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;

                // --- Global ---

                SpecularGlossiness _specularGlossiness = new SpecularGlossiness
                {
                    diffuse    = new BabylonColor3(babylonStandardMaterial.diffuse),
                    opacity    = babylonMaterial.alpha,
                    specular   = new BabylonColor3(babylonStandardMaterial.specular),
                    glossiness = babylonStandardMaterial.specularPower / 256
                };

                MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true);

                // Base color
                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                {
                    _metallicRoughness.baseColor.r,
                    _metallicRoughness.baseColor.g,
                    _metallicRoughness.baseColor.b,
                    _metallicRoughness.opacity
                };

                // Metallic roughness
                gltfPbrMetallicRoughness.metallicFactor  = _metallicRoughness.metallic;
                gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness;


                // --- Textures ---
                var babylonTexture = babylonStandardMaterial.diffuseTexture != null ? babylonStandardMaterial.diffuseTexture :
                                     babylonStandardMaterial.specularTexture != null ? babylonStandardMaterial.specularTexture :
                                     babylonStandardMaterial.opacityTexture != null ? babylonStandardMaterial.opacityTexture :
                                     null;

                if (babylonTexture != null)
                {
                    bool isAlphaInTexture = (isTextureOk(babylonStandardMaterial.diffuseTexture) && babylonStandardMaterial.diffuseTexture.hasAlpha) ||
                                            isTextureOk(babylonStandardMaterial.opacityTexture);

                    Bitmap baseColorBitmap         = null;
                    Bitmap metallicRoughnessBitmap = null;

                    if (CopyTexturesToOutput)
                    {
                        // Diffuse
                        Bitmap diffuseBitmap = null;
                        if (babylonStandardMaterial.diffuseTexture != null)
                        {
                            diffuseBitmap = LoadTexture(babylonStandardMaterial.diffuseTexture.originalPath);
                        }

                        // Specular
                        Bitmap specularBitmap = null;
                        if (babylonStandardMaterial.specularTexture != null)
                        {
                            specularBitmap = LoadTexture(babylonStandardMaterial.specularTexture.originalPath);
                        }

                        // Opacity / Alpha / Transparency
                        Bitmap opacityBitmap = null;
                        if ((babylonStandardMaterial.diffuseTexture == null || babylonStandardMaterial.diffuseTexture.hasAlpha == false) && babylonStandardMaterial.opacityTexture != null)
                        {
                            opacityBitmap = LoadTexture(babylonStandardMaterial.opacityTexture.originalPath);
                        }

                        if (diffuseBitmap != null || specularBitmap != null || opacityBitmap != null)
                        {
                            // Retreive dimensions
                            int width              = 0;
                            int height             = 0;
                            var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, diffuseBitmap, specularBitmap, opacityBitmap);
                            if (!haveSameDimensions)
                            {
                                RaiseError("Diffuse, specular and opacity maps should have same dimensions", 2);
                            }

                            // Create baseColor+alpha and metallic+roughness maps
                            baseColorBitmap         = new Bitmap(width, height);
                            metallicRoughnessBitmap = new Bitmap(width, height);
                            for (int x = 0; x < width; x++)
                            {
                                for (int y = 0; y < height; y++)
                                {
                                    SpecularGlossiness specularGlossinessTexture = new SpecularGlossiness
                                    {
                                        diffuse = diffuseBitmap != null ? new BabylonColor3(diffuseBitmap.GetPixel(x, y)) :
                                                  _specularGlossiness.diffuse,
                                        opacity = diffuseBitmap != null && babylonStandardMaterial.diffuseTexture.hasAlpha ? diffuseBitmap.GetPixel(x, y).A / 255.0f :
                                                  opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB ? opacityBitmap.GetPixel(x, y).R / 255.0f :
                                                  opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB == false?opacityBitmap.GetPixel(x, y).A / 255.0f :
                                                  _specularGlossiness.opacity,
                                        specular = specularBitmap != null ? new BabylonColor3(specularBitmap.GetPixel(x, y)) :
                                                   _specularGlossiness.specular,
                                        glossiness = babylonStandardMaterial.useGlossinessFromSpecularMapAlpha && specularBitmap != null?specularBitmap.GetPixel(x, y).A / 255.0f :
                                                     _specularGlossiness.glossiness
                                    };

                                    var displayPrints = x == width / 2 && y == height / 2;
                                    MetallicRoughness metallicRoughnessTexture = ConvertToMetallicRoughness(specularGlossinessTexture, displayPrints);

                                    Color colorBase = Color.FromArgb(
                                        (int)(metallicRoughnessTexture.opacity * 255),
                                        (int)(metallicRoughnessTexture.baseColor.r * 255),
                                        (int)(metallicRoughnessTexture.baseColor.g * 255),
                                        (int)(metallicRoughnessTexture.baseColor.b * 255)
                                        );
                                    baseColorBitmap.SetPixel(x, y, colorBase);

                                    // The metalness values are sampled from the B channel.
                                    // The roughness values are sampled from the G channel.
                                    // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
                                    Color colorMetallicRoughness = Color.FromArgb(
                                        0,
                                        (int)(metallicRoughnessTexture.roughness * 255),
                                        (int)(metallicRoughnessTexture.metallic * 255)
                                        );
                                    metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
                                }
                            }
                        }
                    }

                    // Export maps and textures
                    var baseColorFileName = babylonMaterial.name + "_baseColor" + (isAlphaInTexture ? ".png" : ".jpg");
                    gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(gltf, babylonTexture, baseColorBitmap, baseColorFileName);
                    if (isTextureOk(babylonStandardMaterial.specularTexture))
                    {
                        gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(gltf, babylonTexture, metallicRoughnessBitmap, babylonMaterial.name + "_metallicRoughness" + ".jpg");
                    }

                    // Constraints
                    if (gltfPbrMetallicRoughness.baseColorTexture != null)
                    {
                        gltfPbrMetallicRoughness.baseColorFactor = new[] { 1.0f, 1.0f, 1.0f, 1.0f };
                    }

                    if (gltfPbrMetallicRoughness.metallicRoughnessTexture != null)
                    {
                        gltfPbrMetallicRoughness.metallicFactor  = 1.0f;
                        gltfPbrMetallicRoughness.roughnessFactor = 1.0f;
                    }
                }
            }
            else if (babylonMaterial.GetType() == typeof(BabylonPBRMetallicRoughnessMaterial))
            {
                var babylonPBRMetallicRoughnessMaterial = babylonMaterial as BabylonPBRMetallicRoughnessMaterial;


                // --- prints ---
                #region prints

                RaiseVerbose("GLTFExporter.Material | babylonMaterial data", 2);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                RaiseVerbose("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);

                // Global
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights=" + babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights, 3);
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.disableLighting=" + babylonPBRMetallicRoughnessMaterial.disableLighting, 3);
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.alphaCutOff=" + babylonPBRMetallicRoughnessMaterial.alphaCutOff, 3);
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.transparencyMode=" + babylonPBRMetallicRoughnessMaterial.transparencyMode, 3);
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.doubleSided=" + babylonPBRMetallicRoughnessMaterial.doubleSided, 3);

                // Base color
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor.Length=" + babylonPBRMetallicRoughnessMaterial.baseColor.Length, 3);
                for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.baseColor.Length; i++)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.baseColor[i], 3);
                }
                if (babylonPBRMetallicRoughnessMaterial.baseTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseTexture=null", 3);
                }

                // Metallic+roughness
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallic=" + babylonPBRMetallicRoughnessMaterial.metallic, 3);
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.roughness=" + babylonPBRMetallicRoughnessMaterial.roughness, 3);
                if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture=null", 3);
                }

                // Normal / bump
                if (babylonPBRMetallicRoughnessMaterial.normalTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.normalTexture=null", 3);
                }
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapX=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapX, 3);
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapY=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapY, 3);

                // Emissive
                for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.emissive.Length; i++)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.emissive[i], 3);
                }
                if (babylonPBRMetallicRoughnessMaterial.emissiveTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveTexture=null", 3);
                }

                // Ambient occlusion
                RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionStrength=" + babylonPBRMetallicRoughnessMaterial.occlusionStrength, 3);
                if (babylonPBRMetallicRoughnessMaterial.occlusionTexture == null)
                {
                    RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionTexture=null", 3);
                }
                #endregion


                // --------------------------------
                // --------- gltfMaterial ---------
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                var gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                // Alpha
                string alphaMode;
                float? alphaCutoff;
                getAlphaMode(babylonPBRMetallicRoughnessMaterial, out alphaMode, out alphaCutoff);
                gltfMaterial.alphaMode   = alphaMode;
                gltfMaterial.alphaCutoff = alphaCutoff;

                // DoubleSided
                gltfMaterial.doubleSided = babylonPBRMetallicRoughnessMaterial.doubleSided;

                // Normal
                gltfMaterial.normalTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.normalTexture, gltf);

                // Occlusion
                var occlusionText = ExportTexture(babylonPBRMetallicRoughnessMaterial.occlusionTexture, gltf);
                gltfMaterial.occlusionTexture = occlusionText;

                // Emissive
                gltfMaterial.emissiveFactor  = babylonPBRMetallicRoughnessMaterial.emissive;
                gltfMaterial.emissiveTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.emissiveTexture, gltf);


                // --------------------------------
                // --- gltfPbrMetallicRoughness ---
                // --------------------------------

                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;

                // --- Global ---

                // Base color
                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                {
                    babylonPBRMetallicRoughnessMaterial.baseColor[0],
                    babylonPBRMetallicRoughnessMaterial.baseColor[1],
                    babylonPBRMetallicRoughnessMaterial.baseColor[2],
                    babylonPBRMetallicRoughnessMaterial.alpha
                };
                if (babylonPBRMetallicRoughnessMaterial.baseTexture != null)
                {
                    if (babylonPBRMetallicRoughnessMaterial.baseTexture.bitmap != null)
                    {
                        // Base color & Alpha texture has been merged manually by the exporter
                        // Write bitmap file
                        gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(gltf, babylonPBRMetallicRoughnessMaterial.baseTexture);
                    }
                    else
                    {
                        // Base color & Alpha texture was already merged
                        // Copy file
                        gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.baseTexture, gltf);
                    }
                }

                // Metallic roughness
                gltfPbrMetallicRoughness.metallicFactor  = babylonPBRMetallicRoughnessMaterial.metallic;
                gltfPbrMetallicRoughness.roughnessFactor = babylonPBRMetallicRoughnessMaterial.roughness;
                if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture != null)
                {
                    if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture.bitmap != null)
                    {
                        // Metallic & roughness texture has been merged manually by the exporter
                        // Write bitmap file
                        gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(gltf, babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture);
                    }
                    else
                    {
                        // Metallic & roughness & occlusion texture was already merged
                        // Copy file
                        if (babylonPBRMetallicRoughnessMaterial.occlusionTexture != null)
                        {
                            if (babylonPBRMetallicRoughnessMaterial.occlusionTexture.originalPath == babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture.originalPath)
                            {
                                gltfPbrMetallicRoughness.metallicRoughnessTexture = occlusionText;
                            }
                            else
                            {
                                // Metallic & roughness texture was already merged
                                // Copy file
                                gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture, gltf);
                            }
                        }
                        else
                        {
                            // Metallic & roughness texture was already merged
                            // Copy file
                            gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture, gltf);
                        }
                    }
                }
            }
            else
            {
                RaiseWarning("GLTFExporter.Material | Unsupported material type: " + babylonMaterial.GetType(), 2);
            }
        }
Example #13
0
        public static void ExportToGLTF(this MiloObjectDir milo, string path, AppState appState)
        {
            var serializer    = appState.GetSerializer();
            var pathDirectory = Path.GetDirectoryName(path);

            var textures = milo.Entries
                           .Where(x => "Tex".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase))
                           .Select(y => serializer.ReadFromMiloObjectBytes <Tex>(y as MiloObjectBytes))
                           .ToList();

            var extTexs = textures.Where(x => x.UseExternal).ToList();
            var miloDir = appState.GetWorkingDirectory().FullPath;

            // Update textures
            foreach (var texture in textures.Where(x => x.UseExternal && x.Bitmap == null)) // TODO: Figure out what UseExternal actually means
            {
                var texPath = Path.Combine(miloDir, MakeGenPath(texture.ExternalPath, appState.SystemInfo.Platform));
                var bitmap  = serializer.ReadFromFile <HMXBitmap>(texPath);

                texture.Bitmap      = bitmap;
                texture.UseExternal = false;
            }

            var views = milo.Entries
                        .Where(x => "View".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase))
                        .Select(y => serializer.ReadFromMiloObjectBytes <View>(y as MiloObjectBytes))
                        .ToList();

            views.AddRange(milo.Entries
                           .Where(x => "Group".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase))
                           .Select(y => serializer.ReadFromMiloObjectBytes <Group>(y as MiloObjectBytes))
                           .ToList());

            var meshes = milo.Entries
                         .Where(x => "Mesh".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase))
                         .Select(y => serializer.ReadFromMiloObjectBytes <Mackiloha.Render.Mesh>(y as MiloObjectBytes))
                         .Where(z => !string.IsNullOrEmpty(z.Material)) // Don't care about bone meshes for now
                         .ToList();

            var trans = milo.Entries
                        .Where(x => "Trans".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase))
                        .Select(y => serializer.ReadFromMiloObjectBytes <Mackiloha.Render.TransStandalone>(y as MiloObjectBytes))
                        .ToList();

            var materials = milo.Entries
                            .Where(x => "Mat".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase))
                            .Select(y => serializer.ReadFromMiloObjectBytes <Mackiloha.Render.Mat>(y as MiloObjectBytes))
                            //.Where(z => z.TextureEntries.Count > 0 && z.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture))) // TODO: Idk?
                            .ToList();

            var cams = milo.Entries
                       .Where(x => "Cam".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase) && appState.SystemInfo.Version <= 10)
                       .Select(y => serializer.ReadFromMiloObjectBytes <Cam>(y as MiloObjectBytes))
                       .ToList();

            var environs = milo.Entries
                           .Where(x => "Environ".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase) && appState.SystemInfo.Version <= 10)
                           .Select(y => serializer.ReadFromMiloObjectBytes <Environ>(y as MiloObjectBytes))
                           .ToList();

            var miloEntries = textures
                              .Union <MiloObject>(views)
                              .Union(meshes)
                              .Union(trans)
                              .Union(materials)
                              .Union(cams)
                              .Union(environs)
                              .ToList();

            var transEntries = miloEntries
                               .Where(x => x is ITrans)
                               .ToList();

            var drawEntries = miloEntries
                              .Where(x => x is IDraw)
                              .ToList();

            /* var transforms = milo.Entries
             *  .Where(x => x.Type.Equals("Trans", StringComparison.CurrentCultureIgnoreCase))
             *  .Select(y =>
             *  {
             *      var trans = MiloOG.Trans.FromStream(new MemoryStream((y as MiloEntry).Data));
             *      trans.Name = y.Name;
             *      return trans;
             *  }).ToList(); */

            var scene = new GLTF()
            {
                Asset = new Asset()
                {
                    Generator = $"Mackiloha v{typeof(MiloExtensions).Assembly.GetName().Version}"
                },
                Images = textures.Select(x => new Image()
                {
                    Name = Path.GetFileNameWithoutExtension(x.Name) + ".png",
                    Uri  = Path.GetFileNameWithoutExtension(x.Name) + ".png"
                }).ToArray(),
                Samplers = new Sampler[]
                {
                    new Sampler()
                    {
                        MagFilter = MagFilter.Linear,
                        MinFilter = MinFilter.Nearest,
                        WrapS     = WrapMode.Repeat,
                        WrapT     = WrapMode.Repeat
                    }
                },
                Scene = 0
            };

            var currentOffset = 0;

            scene.Textures = textures.Select(x => new Texture()
            {
                Name    = x.Name,
                Sampler = 0,
                Source  = currentOffset++
            }).ToArray();

            var keyIdxPairs = Enumerable.Range(0, textures.Count).ToDictionary(x => textures[x].Name);

            scene.Materials = materials.Select(x => new Material()
            {
                Name = x.Name,
                PbrMetallicRoughness = new PbrMetallicRoughness()
                {
                    BaseColorTexture =
                        // Verify material has texture and exists in milo
                        (x.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture)) &&
                         keyIdxPairs.ContainsKey(x.TextureEntries.First(x => !string.IsNullOrEmpty(x.Texture)).Texture))
                            ? new BaseColorTexture()
                    {
                        // TODO: Figure out how to map multiple textures to single material
                        Index = keyIdxPairs[x.TextureEntries.First(y => !string.IsNullOrEmpty(y.Texture)).Texture]
                    }
                            : null,
                    BaseColorFactor = new Vector4 <double>(x.BaseColor.R, x.BaseColor.G, x.BaseColor.B, x.BaseColor.A),
                    MetallicFactor  = (x.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture)) && x.TextureEntries.First(y => !string.IsNullOrEmpty(y.Texture)).Unknown2 == 2) ? 1 : 0,
                    RoughnessFactor = (x.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture)) && x.TextureEntries.First(y => !string.IsNullOrEmpty(y.Texture)).Unknown2 == 2) ? 0 : 1
                },
                EmissiveFactor = new Vector3 <double>(),
                AlphaMode      = AlphaMode.Mask, // x.Blend == BlendFactor.One ? AlphaMode.Blend : AlphaMode.Opaque,
                DoubleSided    = true
            }).ToArray();

            if (!Directory.Exists(pathDirectory))
            {
                // Create directory
                Directory.CreateDirectory(pathDirectory);
            }

            // Saves textures
            for (int i = 0; i < textures.Count; i++)
            {
                textures[i].Bitmap.SaveAs(serializer.Info,
                                          Path.Combine(pathDirectory, scene.Images[i].Uri));
            }

            var accessors   = new List <Accessor>();
            var sceneMeshes = new List <GLTFTools.Mesh>();

            int bufferSize12 = meshes.Select(x => x.Vertices.Count * 12 * 2).Sum(); // Verts + norms
            int bufferSize8  = meshes.Select(x => x.Vertices.Count * 8).Sum();      // UV
            int bufferSize4  = meshes.Select(x => x.Faces.Count * 6).Sum();         // Faces

            if (bufferSize4 % 4 != 0)
            {
                bufferSize4 += 4 - (bufferSize4 % 4);
            }

            scene.Buffers = new GLTFTools.Buffer[]
            {
                new GLTFTools.Buffer()
                {
                    Name       = Path.GetFileNameWithoutExtension(path),
                    ByteLength = (bufferSize4 + bufferSize8 + bufferSize12),
                    Uri        = Path.GetFileNameWithoutExtension(path) + ".bin"
                }
            };

            scene.BufferViews = new BufferView[]
            {
                new BufferView()
                {
                    Name       = "vertsAndNorms",
                    ByteLength = bufferSize12,
                    ByteOffset = 0,
                    ByteStride = 12
                },
                new BufferView()
                {
                    Name       = "uvs",
                    ByteLength = bufferSize8,
                    ByteOffset = bufferSize12,
                    ByteStride = 8
                },
                new BufferView()
                {
                    Name       = "faces",
                    ByteLength = bufferSize4,
                    ByteOffset = bufferSize12 + bufferSize8,
                    ByteStride = null
                }
            };

            int buffer12Offset = scene.BufferViews[0].ByteOffset.Value;
            int buffer8Offset  = scene.BufferViews[1].ByteOffset.Value;
            int buffer4Offset  = scene.BufferViews[2].ByteOffset.Value;

            var bw = new BinaryWriter(new MemoryStream(new byte[bufferSize12 + bufferSize8 + bufferSize4]));
            Dictionary <string, int> meshIndex = new Dictionary <string, int>();

            currentOffset = 0;

            keyIdxPairs = Enumerable
                          .Range(0, materials.Count)
                          .ToDictionary(x => materials[x].Name);

            foreach (var mesh in meshes)
            {
                if (mesh.Vertices.Count <= 0 || mesh.Faces.Count <= 0)
                {
                    continue;
                }
                meshIndex.Add(mesh.Name, currentOffset++);

                // Finds related material + texture
                var mat = materials.First(x => ((string)x.Name).Equals(mesh.Material, StringComparison.CurrentCultureIgnoreCase));

                sceneMeshes.Add(new GLTFTools.Mesh()
                {
                    Name       = mesh.Name,
                    Primitives = new MeshPrimitive[]
                    {
                        new MeshPrimitive()
                        {
                            Attributes = new MeshPrimitiveAttributes()
                            {
                                Position           = accessors.Count,
                                Normal             = accessors.Count + 1,
                                TextureCoordinate0 = accessors.Count + 2
                            },
                            Indices  = accessors.Count + 3,
                            Material = keyIdxPairs[mesh.Material],
                            Mode     = RenderMode.Triangles
                        }
                    }
                });

                // Vertices
                accessors.Add(new Accessor()
                {
                    Name          = mesh.Name + "_positions",
                    ComponentType = ComponentType.Float,
                    Count         = mesh.Vertices.Count,
                    Min           = new double[]
                    {
                        mesh.Vertices.Select(x => x.X).Min(),
                        mesh.Vertices.Select(x => x.Y).Min(),
                        mesh.Vertices.Select(x => x.Z).Min()
                    },
                    Max = new double[]
                    {
                        mesh.Vertices.Select(x => x.X).Max(),
                        mesh.Vertices.Select(x => x.Y).Max(),
                        mesh.Vertices.Select(x => x.Z).Max()
                    },
                    Type       = GLType.Vector3,
                    BufferView = 0,
                    ByteOffset = buffer12Offset - scene.BufferViews[0].ByteOffset.Value
                });
                bw.BaseStream.Seek(buffer12Offset, SeekOrigin.Begin);
                foreach (var vert in mesh.Vertices)
                {
                    bw.Write(vert.X);
                    bw.Write(vert.Y);
                    bw.Write(vert.Z);
                }
                buffer12Offset = (int)bw.BaseStream.Position;

                // Normals
                accessors.Add(new Accessor()
                {
                    Name          = mesh.Name + "_normals",
                    ComponentType = ComponentType.Float,
                    Count         = mesh.Vertices.Count,
                    Min           = new double[]
                    {
                        mesh.Vertices.Select(x => x.NormalX).Min(),
                        mesh.Vertices.Select(x => x.NormalY).Min(),
                        mesh.Vertices.Select(x => x.NormalZ).Min()
                    },
                    Max = new double[]
                    {
                        mesh.Vertices.Select(x => x.NormalX).Max(),
                        mesh.Vertices.Select(x => x.NormalY).Max(),
                        mesh.Vertices.Select(x => x.NormalZ).Max()
                    },
                    Type       = GLType.Vector3,
                    BufferView = 0,
                    ByteOffset = buffer12Offset - scene.BufferViews[0].ByteOffset.Value
                });
                bw.BaseStream.Seek(buffer12Offset, SeekOrigin.Begin);
                foreach (var vert in mesh.Vertices)
                {
                    bw.Write(vert.NormalX);
                    bw.Write(vert.NormalY);
                    bw.Write(vert.NormalZ);
                }
                buffer12Offset = (int)bw.BaseStream.Position;

                // UV coordinates
                accessors.Add(new Accessor()
                {
                    Name          = mesh.Name + "_texcoords",
                    ComponentType = ComponentType.Float,
                    Count         = mesh.Vertices.Count,
                    Min           = new double[]
                    {
                        mesh.Vertices.Select(x => x.U).Min(),
                        mesh.Vertices.Select(x => x.V).Min()
                    },
                    Max = new double[]
                    {
                        mesh.Vertices.Select(x => x.U).Max(),
                        mesh.Vertices.Select(x => x.V).Max()
                    },
                    Type       = GLType.Vector2,
                    BufferView = 1,
                    ByteOffset = buffer8Offset - scene.BufferViews[1].ByteOffset.Value
                });
                bw.BaseStream.Seek(buffer8Offset, SeekOrigin.Begin);
                foreach (var vert in mesh.Vertices)
                {
                    bw.Write(vert.U);
                    bw.Write(vert.V);
                }
                buffer8Offset = (int)bw.BaseStream.Position;

                // Faces
                accessors.Add(new Accessor()
                {
                    Name          = mesh.Name + "_indicies",
                    ComponentType = ComponentType.UnsignedShort,
                    Count         = mesh.Faces.Count * 3,
                    Min           = new double[]
                    {
                        mesh.Faces.SelectMany(x => new [] { x.V1, x.V2, x.V3 }).Min()
                    },
                    Max = new double[]
                    {
                        mesh.Faces.SelectMany(x => new [] { x.V1, x.V2, x.V3 }).Max()
                    },
                    Type       = GLType.Scalar,
                    BufferView = 2,
                    ByteOffset = buffer4Offset - scene.BufferViews[2].ByteOffset.Value
                });
                bw.BaseStream.Seek(buffer4Offset, SeekOrigin.Begin);
                foreach (var face in mesh.Faces)
                {
                    bw.Write(face.V1);
                    bw.Write(face.V2);
                    bw.Write(face.V3);
                }
                buffer4Offset = (int)bw.BaseStream.Position;
            }

            scene.Accessors = accessors.ToArray();
            scene.Meshes    = sceneMeshes.ToArray();

            var nodes     = new List <Node>();
            var nodeIndex = new Dictionary <string, int>();

            /* // TODO: Make milo objects with transforms data
             * MiloOG.AbstractEntry GetAbstractEntry(string name)
             * {
             *  var entry = milo.Entries.FirstOrDefault(x => x.Name == name);
             *  if (entry == null) return null;
             *
             *  switch (entry.Type)
             *  {
             *      case "Mesh":
             *          return meshes.First(y => y.Name == entry.Name);
             *      case "Trans":
             *          return transforms.First(y => y.Name == entry.Name);
             *      case "View":
             *          return views.First(y => y.Name == entry.Name);
             *      default:
             *          return null;
             *  }
             * } */

            /* Matrix4<float>? GetTransform(string transform)
             * {
             *  var transEntry = milo.Entries.FirstOrDefault(y => y.Name == transform);
             *  if (transEntry == null) return null;
             *
             *  switch (transEntry.Type)
             *  {
             *      case "Mesh":
             *          var mesh = meshes.First(y => y.Name == transEntry.Name);
             *          return mesh.Mat2.ToGLMatrix();
             *      case "Trans":
             *          var trans = transforms.First(y => y.Name == transEntry.Name);
             *          return trans.Mat2.ToGLMatrix();
             *      case "View":
             *          var view2 = views.First(y => y.Name == transEntry.Name);
             *          return view2.Mat2.ToGLMatrix();
             *      default:
             *          return null;
             *  }
             * } */

            /* string GetTransformName(MiloOG.AbstractEntry entry)
             * {
             *  switch (entry.Type)
             *  {
             *      case "Mesh":
             *          var mesh = meshes.First(y => y.Name == entry.Name);
             *          return mesh.Transform;
             *      case "Trans":
             *          var trans = transforms.First(y => y.Name == entry.Name);
             *          return trans.Name;
             *      case "View":
             *          var view = views.First(y => y.Name == entry.Name);
             *          return view.Transform;
             *      default:
             *          return null;
             *  }
             * } */

            var shadowRegex = new System.Text.RegularExpressions.Regex("shadow[^.]*.mesh$", System.Text.RegularExpressions.RegexOptions.IgnoreCase);

            var children = drawEntries
                           .Where(w => w is ITrans) // Use trans collection?
                           .Select(x => new
            {
                Name  = (string)x.Name,
                Trans = (string)(x as ITrans).Transform
            })
                           .Where(y => !string.IsNullOrEmpty(y.Trans))
                           .GroupBy(z => z.Trans)
                           .ToDictionary(g => g.Key, g => g.Select(w => w.Name)
                                         .OrderBy(s => s)
                                         .Where(x => !shadowRegex.IsMatch(x)) // Removes shadow meshes for now
                                         .ToList());

            /* foreach (var entry in meshes.Union<MiloObject>(views)) // TODO: Union w/ trans
             * {
             *  var transName = (entry as Mackiloha.Render.Interfaces.ITrans)
             *      .Trans
             *      .Transform;
             *
             *  if (!children.ContainsKey(transName))
             *      children.Add(transName, new List<string>(new string[] { entry.Name }));
             *  else if (!children[transName].Contains(entry.Name))
             *      children[transName].Add(entry.Name);
             * } */

            var rootIndex = new List <int>();

            foreach (var key in children.Keys)
            {
                rootIndex.Add(nodes.Count);
                var transEntry = transEntries.FirstOrDefault(x => x.Name == key) as ITrans;

                var node = new Node()
                {
                    Name     = "Root_" + key,
                    Mesh     = meshIndex.ContainsKey(key) ? (int?)meshIndex[key] : null,
                    Matrix   = ToGLMatrix((transEntry != null) ? transEntry.Mat2 : Matrix4.Identity()),
                    Children = Enumerable.Range(nodes.Count + 1, children[key].Count).ToArray()
                };
                nodes.Add(node);

                foreach (var child in children[key])
                {
                    var subEntry = drawEntries.First(x => x.Name == child);

                    var subNode = new Node()
                    {
                        Name = subEntry.Name,
                        Mesh = meshIndex.ContainsKey(subEntry.Name) ? (int?)meshIndex[subEntry.Name] : null,
                        //Matrix = ToGLMatrix(subEntry.Mat1)
                    };
                    nodeIndex.Add(child, rootIndex.Last());
                    nodes.Add(subNode);
                }
            }

            int CreateNode(string name) // Returns index of node
            {
                if (nodeIndex.ContainsKey(name))
                {
                    return(nodeIndex[name]);
                }

                var           entry          = drawEntries.First(x => x.Name == name) as IDraw;
                var           transformEntry = drawEntries.First(x => x.Name == (entry as ITrans).Transform);
                List <string> subNodes       = entry.Drawables.Select(x => (string)x).ToList();

                var node = new Node()
                {
                    Name   = name,
                    Mesh   = meshIndex.ContainsKey(name) ? (int?)meshIndex[name] : null,
                    Matrix = ToGLMatrix((entry as ITrans).Mat1),
                    //Matrix = GetTransform(entry.Transform),
                    Children = (subNodes.Count > 0) ? subNodes.Select(x => CreateNode(x)).ToArray() : null
                };

                nodeIndex.Add(name, nodes.Count);
                nodes.Add(node);
                return(nodeIndex[name]);
            }

            // foreach (var n in meshes.Union<MiloOG.AbstractEntry>(views).Union<MiloOG.AbstractEntry>(transforms)) CreateNode(n.Name);
            //
            // scene.Scene = 0;
            // scene.Scenes = new Scene[] { new Scene() { Nodes = Enumerable.Range(0, nodes.Count).ToArray() } };


            // foreach (var view in views) CreateNode(view.Name);
            //
            // // Finds root node
            // var childrenNodes = nodes.SelectMany(x => x.Children ?? new int[0]).Distinct();
            // var parentNodes = Enumerable.Range(0, nodes.Count);
            // var rootIdx = parentNodes.Except(childrenNodes).Single();
            //
            // scene.Scene = 0;
            // scene.Scenes = new Scene[] { new Scene() { Nodes = new int[] { rootIdx } } };

            List <string> GetAllSubs(MiloObject entry)
            {
                List <string> subsEntriesNames = (entry as IDraw).Drawables
                                                 .Select(x => (string)x)
                                                 .ToList();

                var subEntries = subsEntriesNames
                                 .Select(x => drawEntries.FirstOrDefault(y => y.Name == x))
                                 .Where(y => y != null)
                                 .ToList();

                foreach (var subEntry in subEntries)
                {
                    subsEntriesNames.AddRange(GetAllSubs(subEntry));
                }

                return(subsEntriesNames);
            }

            scene.Scene = 0;
            //scene.Scenes = new Scene[] { new Scene() { Nodes = rootIndex.ToArray() } };

            scene.Scenes = views
                           .Select(x => new Scene()
            {
                Nodes = GetAllSubs(x)
                        .Select(y => nodeIndex.ContainsKey(y) ? nodeIndex[y] : -1)
                        .Where(z => z != -1)
                        .Distinct()
                        .ToArray()
            })
                           .OrderByDescending(x => x.Nodes.Length)
                           .ToArray();

            if (scene.Scenes.Length <= 0)
            {
                // Create scene from root notes
                scene.Scenes = new[]
Example #14
0
        private void _exportMorphTargets(BabylonMesh babylonMesh, BabylonSubMesh babylonSubMesh, BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFBuffer buffer, GLTFMeshPrimitive meshPrimitive)
        {
            var gltfMorphTargets = new List <GLTFMorphTarget>();

            foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
            {
                var gltfMorphTarget = new GLTFMorphTarget();

                // Positions
                if (babylonMorphTarget.positions != null)
                {
                    var accessorTargetPositions = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                        "accessorTargetPositions",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC3
                        );
                    gltfMorphTarget.Add(GLTFMorphTarget.Attribute.POSITION.ToString(), accessorTargetPositions.index);
                    // Populate accessor
                    int nbComponents = 3; // Vector3
                    int startIndex   = babylonSubMesh.verticesStart * nbComponents;
                    int endIndex     = startIndex + babylonSubMesh.verticesCount * nbComponents;
                    accessorTargetPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
                    accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                    for (int indexPosition = startIndex; indexPosition < endIndex; indexPosition += 3)
                    {
                        var positionTarget = ArrayExtension.SubArray(babylonMorphTarget.positions, indexPosition, 3);

                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
                        var positionMesh = ArrayExtension.SubArray(babylonMesh.positions, indexPosition, 3);
                        for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++)
                        {
                            positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate];
                        }

                        positionTarget[2] *= -1;

                        // Store values as bytes
                        foreach (var coordinate in positionTarget)
                        {
                            accessorTargetPositions.bytesList.AddRange(BitConverter.GetBytes(coordinate));
                        }
                        // Update min and max values
                        GLTFBufferService.UpdateMinMaxAccessor(accessorTargetPositions, positionTarget);
                    }
                    accessorTargetPositions.count = babylonSubMesh.verticesCount;
                }

                // Normals
                if (babylonMorphTarget.normals != null && exportParameters.exportMorphNormals)
                {
                    var accessorTargetNormals = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                        "accessorTargetNormals",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC3
                        );
                    gltfMorphTarget.Add(GLTFMorphTarget.Attribute.NORMAL.ToString(), accessorTargetNormals.index);
                    // Populate accessor
                    int nbComponents = 3; // Vector3
                    int startIndex   = babylonSubMesh.verticesStart * nbComponents;
                    int endIndex     = startIndex + babylonSubMesh.verticesCount * nbComponents;
                    for (int indexNormal = startIndex; indexNormal < endIndex; indexNormal += 3)
                    {
                        var normalTarget = ArrayExtension.SubArray(babylonMorphTarget.normals, indexNormal, 3);

                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
                        var normalMesh = ArrayExtension.SubArray(babylonMesh.normals, indexNormal, 3);
                        for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++)
                        {
                            normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate];
                        }

                        normalTarget[2] *= -1;

                        // Store values as bytes
                        foreach (var coordinate in normalTarget)
                        {
                            accessorTargetNormals.bytesList.AddRange(BitConverter.GetBytes(coordinate));
                        }
                    }
                    accessorTargetNormals.count = babylonSubMesh.verticesCount;
                }

                // Tangents
                if (babylonMorphTarget.tangents != null && exportParameters.exportTangents)
                {
                    var accessorTargetTangents = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                        "accessorTargetTangents",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC3
                        );
                    gltfMorphTarget.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTargetTangents.index);
                    // Populate accessor
                    // Note that the w component for handedness is omitted when targeting TANGENT data since handedness cannot be displaced.
                    int nbComponents = 4; // Vector4
                    int startIndex   = babylonSubMesh.verticesStart * nbComponents;
                    int endIndex     = startIndex + babylonSubMesh.verticesCount * nbComponents;
                    for (int indexTangent = startIndex; indexTangent < endIndex; indexTangent += 4)
                    {
                        var tangentTarget = ArrayExtension.SubArray(babylonMorphTarget.tangents, indexTangent, 3);

                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
                        var tangentMesh = ArrayExtension.SubArray(babylonMesh.tangents, indexTangent, 3);
                        for (int indexCoordinate = 0; indexCoordinate < tangentTarget.Length; indexCoordinate++)
                        {
                            tangentTarget[indexCoordinate] = tangentTarget[indexCoordinate] - tangentMesh[indexCoordinate];
                        }

                        tangentTarget[2] *= -1;

                        // Store values as bytes
                        foreach (var coordinate in tangentTarget)
                        {
                            accessorTargetTangents.bytesList.AddRange(BitConverter.GetBytes(coordinate));
                        }
                    }
                    accessorTargetTangents.count = babylonSubMesh.verticesCount;
                }

                gltfMorphTargets.Add(gltfMorphTarget);
            }
            if (gltfMorphTargets.Count > 0)
            {
                meshPrimitive.targets = gltfMorphTargets.ToArray();
            }
        }
        private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List <GLTFChannel> channelList, List <GLTFAnimationSampler> samplerList, int startFrame, int endFrame, BabylonScene babylonScene)
        {
            if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))
            {
                return(false);
            }

            logger.RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2);

            // Target
            var gltfTarget = new GLTFChannelTarget
            {
                node = gltfNode.index
            };

            gltfTarget.path = "weights";

            // Buffer
            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

            // --- Input ---
            var accessorInput = GLTFBufferService.Instance.CreateAccessor(
                gltf,
                GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                "accessorAnimationInput",
                GLTFAccessor.ComponentType.FLOAT,
                GLTFAccessor.TypeEnum.SCALAR
                );

            // Populate accessor
            accessorInput.min = new float[] { float.MaxValue };
            accessorInput.max = new float[] { float.MinValue };

            var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager);
            var frames             = new List <int>(influencesPerFrame.Keys);

            frames.Sort(); // Mandatory otherwise gltf loader of babylon doesn't understand

            int numKeys = 0;

            foreach (var frame in frames)
            {
                if (frame < startFrame)
                {
                    continue;
                }

                if (frame > endFrame)
                {
                    continue;
                }

                numKeys++;
                var inputValue = frame / (float)babylonScene.TimelineFramesPerSecond;
                // Store values as bytes
                accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
                // Update min and max values
                GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
            }
            accessorInput.count = numKeys;

            // bail out if we have no keys to export (?)
            // todo [KeyInterpolation]: bail out only when there are no keyframes at all (?) and otherwise add the appropriate (interpolated) keyframes
            if (numKeys == 0)
            {
                return(false);
            }

            // --- Output ---
            GLTFAccessor accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                gltf,
                GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                "accessorAnimationWeights",
                GLTFAccessor.ComponentType.FLOAT,
                GLTFAccessor.TypeEnum.SCALAR
                );

            // Populate accessor
            foreach (var frame in frames)
            {
                if (frame < startFrame)
                {
                    continue;
                }

                if (frame > endFrame)
                {
                    continue;
                }

                var outputValues = influencesPerFrame[frame];
                // Store values as bytes
                foreach (var outputValue in outputValues)
                {
                    accessorOutput.count++;
                    accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                }
            }

            // Animation sampler
            var gltfAnimationSampler = new GLTFAnimationSampler
            {
                input  = accessorInput.index,
                output = accessorOutput.index
            };

            gltfAnimationSampler.index = samplerList.Count;
            samplerList.Add(gltfAnimationSampler);

            // Channel
            var gltfChannel = new GLTFChannel
            {
                sampler = gltfAnimationSampler.index,
                target  = gltfTarget
            };

            channelList.Add(gltfChannel);

            return(true);
        }
        private GLTFCamera ExportCamera(BabylonCamera babylonCamera, GLTF gltf, GLTFNode gltfParentNode)
        {
            RaiseMessage("GLTFExporter.Camera | Export camera named: " + babylonCamera.name, 1);

            // --------------------------
            // ---------- Node ----------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | Node", 2);
            // Node
            var gltfNode = new GLTFNode();

            gltfNode.name  = babylonCamera.name;
            gltfNode.index = gltf.NodesList.Count;
            gltf.NodesList.Add(gltfNode);

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as child to " + gltfParentNode.name, 3);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as root node to scene", 3);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            gltfNode.translation = babylonCamera.position;
            if (babylonCamera.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonCamera.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonCamera.rotation[0],
                    Y = babylonCamera.rotation[1],
                    Z = babylonCamera.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
            }
            // No scaling defined for babylon camera. Use identity instead.
            gltfNode.scale = new float[3] {
                1, 1, 1
            };

            // Switch coordinate system at object level
            gltfNode.translation[2] *= -1;
            gltfNode.rotation[0]    *= -1;
            gltfNode.rotation[1]    *= -1;

            // Animations
            ExportNodeAnimation(babylonCamera, gltf, gltfNode);

            // --- prints ---
            #region prints

            RaiseVerbose("GLTFExporter.Camera | babylonCamera data", 2);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.type=" + babylonCamera.type, 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.fov=" + babylonCamera.fov, 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.maxZ=" + babylonCamera.maxZ, 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.minZ=" + babylonCamera.minZ, 3);
            #endregion


            // --------------------------
            // ------- gltfCamera -------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | create gltfCamera", 2);

            // Camera
            var gltfCamera = new GLTFCamera {
                name = babylonCamera.name
            };
            gltfCamera.index = gltf.CamerasList.Count;
            gltf.CamerasList.Add(gltfCamera);
            gltfNode.camera     = gltfCamera.index;
            gltfCamera.gltfNode = gltfNode;

            // Camera type
            switch (babylonCamera.mode)
            {
            case (BabylonCamera.CameraMode.ORTHOGRAPHIC_CAMERA):
                var gltfCameraOrthographic = new GLTFCameraOrthographic();
                gltfCameraOrthographic.xmag  = 1;    // Do not bother about it - still mandatory
                gltfCameraOrthographic.ymag  = 1;    // Do not bother about it - still mandatory
                gltfCameraOrthographic.zfar  = babylonCamera.maxZ;
                gltfCameraOrthographic.znear = babylonCamera.minZ;

                gltfCamera.type         = GLTFCamera.CameraType.orthographic.ToString();
                gltfCamera.orthographic = gltfCameraOrthographic;
                break;

            case (BabylonCamera.CameraMode.PERSPECTIVE_CAMERA):
                var gltfCameraPerspective = new GLTFCameraPerspective();
                gltfCameraPerspective.aspectRatio = null;              // Do not bother about it - use default glTF value
                gltfCameraPerspective.yfov        = babylonCamera.fov; // Babylon camera fov mode is assumed to be vertical (FOVMODE_VERTICAL_FIXED)
                gltfCameraPerspective.zfar        = babylonCamera.maxZ;
                gltfCameraPerspective.znear       = babylonCamera.minZ;

                gltfCamera.type        = GLTFCamera.CameraType.perspective.ToString();
                gltfCamera.perspective = gltfCameraPerspective;
                break;

            default:
                RaiseError("GLTFExporter.Camera | camera mode not found");
                break;
            }

            return(gltfCamera);
        }
        private void ExportNodeAnimation(GLTFAnimation gltfAnimation, int startFrame, int endFrame, GLTF gltf, BabylonNode babylonNode, GLTFNode gltfNode, BabylonScene babylonScene)
        {
            var channelList = gltfAnimation.ChannelList;
            var samplerList = gltfAnimation.SamplerList;

            bool exportNonAnimated = exportParameters.animgroupExportNonAnimated;

            // Combine babylon animations from .babylon file and cached ones
            var babylonAnimations = new List <BabylonAnimation>();

            if (babylonNode.animations != null)
            {
                babylonAnimations.AddRange(babylonNode.animations);
            }
            if (babylonNode.extraAnimations != null)
            {
                babylonAnimations.AddRange(babylonNode.extraAnimations);
            }

            // Filter animations to only keep TRS ones
            babylonAnimations = babylonAnimations.FindAll(babylonAnimation => _getTargetPath(babylonAnimation.property) != null);

            if (babylonAnimations.Count > 0 || exportNonAnimated)
            {
                if (babylonAnimations.Count > 0)
                {
                    logger.RaiseMessage("GLTFExporter.Animation | Export animations of node named: " + babylonNode.name, 2);
                }
                else if (exportNonAnimated)
                {
                    logger.RaiseMessage("GLTFExporter.Animation | Export dummy animation for node named: " + babylonNode.name, 2);
                    // Export a dummy animation
                    babylonAnimations.Add(GetDummyAnimation(gltfNode, startFrame, endFrame, babylonScene));
                }

                foreach (BabylonAnimation babylonAnimation in babylonAnimations)
                {
                    // Target
                    var gltfTarget = new GLTFChannelTarget
                    {
                        node = gltfNode.index
                    };
                    gltfTarget.path = _getTargetPath(babylonAnimation.property);

                    // --- Input ---
                    var accessorInput = _createAndPopulateInput(gltf, babylonAnimation, startFrame, endFrame);
                    if (accessorInput == null)
                    {
                        continue;
                    }

                    // --- Output ---
                    GLTFAccessor accessorOutput = _createAccessorOfPath(gltfTarget.path, gltf);

                    // Populate accessor
                    int numKeys = 0;
                    foreach (var babylonAnimationKey in babylonAnimation.keys)
                    {
                        if (babylonAnimationKey.frame < startFrame)
                        {
                            continue;
                        }

                        if (babylonAnimationKey.frame > endFrame)
                        {
                            continue;
                        }

                        numKeys++;

                        // copy data before changing it in case animation groups overlap
                        float[] outputValues = new float[babylonAnimationKey.values.Length];
                        babylonAnimationKey.values.CopyTo(outputValues, 0);

                        // Switch coordinate system at object level
                        if (babylonAnimation.property == "position")
                        {
                            outputValues[2] *= -1;
                            outputValues[0] *= exportParameters.scaleFactor;
                            outputValues[1] *= exportParameters.scaleFactor;
                            outputValues[2] *= exportParameters.scaleFactor;
                        }
                        else if (babylonAnimation.property == "rotationQuaternion")
                        {
                            outputValues[0] *= -1;
                            outputValues[1] *= -1;
                        }

                        // Store values as bytes
                        foreach (var outputValue in outputValues)
                        {
                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                        }
                    }
                    ;
                    accessorOutput.count = numKeys;

                    // bail out if no keyframes to export (?)
                    // todo [KeyInterpolation]: bail out only when there are no keyframes at all (?) and otherwise add the appropriate (interpolated) keyframes
                    if (numKeys == 0)
                    {
                        continue;
                    }

                    // Animation sampler
                    var gltfAnimationSampler = new GLTFAnimationSampler
                    {
                        input  = accessorInput.index,
                        output = accessorOutput.index
                    };
                    gltfAnimationSampler.index = samplerList.Count;
                    samplerList.Add(gltfAnimationSampler);

                    // Channel
                    var gltfChannel = new GLTFChannel
                    {
                        sampler = gltfAnimationSampler.index,
                        target  = gltfTarget
                    };
                    channelList.Add(gltfChannel);
                }
            }

            if (babylonNode.GetType() == typeof(BabylonMesh))
            {
                var babylonMesh = babylonNode as BabylonMesh;

                // Morph targets
                var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
                if (babylonMorphTargetManager != null)
                {
                    ExportMorphTargetWeightAnimation(babylonMorphTargetManager, gltf, gltfNode, channelList, samplerList, startFrame, endFrame, babylonScene);
                }
            }
        }
Example #18
0
        private void _exportMorphTargets(BabylonMesh babylonMesh, BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFBuffer buffer, GLTFMeshPrimitive meshPrimitive, List <float> weights)
        {
            var gltfMorphTargets = new List <GLTFMorphTarget>();

            foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
            {
                var gltfMorphTarget = new GLTFMorphTarget();

                // Positions
                if (babylonMorphTarget.positions != null)
                {
                    var accessorTargetPositions = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                        "accessorTargetPositions",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC3
                        );
                    gltfMorphTarget.Add(GLTFMorphTarget.Attribute.POSITION.ToString(), accessorTargetPositions.index);
                    // Populate accessor
                    accessorTargetPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
                    accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                    for (int indexPosition = 0; indexPosition < babylonMorphTarget.positions.Length; indexPosition += 3)
                    {
                        var positionTarget = Tools.SubArray(babylonMorphTarget.positions, indexPosition, 3);

                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
                        var positionMesh = Tools.SubArray(babylonMesh.positions, indexPosition, 3);
                        for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++)
                        {
                            positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate];
                        }

                        // Store values as bytes
                        foreach (var coordinate in positionTarget)
                        {
                            accessorTargetPositions.bytesList.AddRange(BitConverter.GetBytes(coordinate));
                        }
                        // Update min and max values
                        GLTFBufferService.UpdateMinMaxAccessor(accessorTargetPositions, positionTarget);
                    }
                    accessorTargetPositions.count = babylonMorphTarget.positions.Length / 3;
                }

                // Normals
                if (babylonMorphTarget.normals != null)
                {
                    var accessorTargetNormals = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                        "accessorTargetNormals",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC3
                        );
                    gltfMorphTarget.Add(GLTFMorphTarget.Attribute.NORMAL.ToString(), accessorTargetNormals.index);
                    // Populate accessor
                    for (int indexNormal = 0; indexNormal < babylonMorphTarget.positions.Length; indexNormal += 3)
                    {
                        var normalTarget = Tools.SubArray(babylonMorphTarget.normals, indexNormal, 3);

                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
                        var normalMesh = Tools.SubArray(babylonMesh.normals, indexNormal, 3);
                        for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++)
                        {
                            normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate];
                        }

                        // Store values as bytes
                        foreach (var coordinate in normalTarget)
                        {
                            accessorTargetNormals.bytesList.AddRange(BitConverter.GetBytes(coordinate));
                        }
                    }
                    accessorTargetNormals.count = babylonMorphTarget.normals.Length / 3;
                }

                if (gltfMorphTarget.Count > 0)
                {
                    gltfMorphTargets.Add(gltfMorphTarget);
                    weights.Add(babylonMorphTarget.influence);
                }
            }
            if (gltfMorphTargets.Count > 0)
            {
                meshPrimitive.targets = gltfMorphTargets.ToArray();
            }
        }
        private GLTFTextureInfo ExportEmissiveTexture(BabylonStandardMaterial babylonMaterial, GLTF gltf, float[] defaultEmissive, float[] defaultDiffuse)
        {
            // Use one as a reference for UVs parameters
            var babylonTexture = babylonMaterial.emissiveTexture != null ? babylonMaterial.emissiveTexture : babylonMaterial.diffuseTexture;

            if (babylonTexture == null)
            {
                return(null);
            }

            // Anticipate if a black texture is going to be export
            if (babylonMaterial.emissiveTexture == null && defaultEmissive.IsAlmostEqualTo(new float[] { 0, 0, 0 }, 0))
            {
                return(null);
            }

            // Check if the texture has already been exported
            if (GetRegisteredEmissive(babylonMaterial, defaultDiffuse, defaultEmissive) != null)
            {
                return(GetRegisteredEmissive(babylonMaterial, defaultDiffuse, defaultEmissive));
            }

            Bitmap emissivePremultipliedBitmap = null;

            if (exportParameters.writeTextures)
            {
                // Emissive
                Bitmap emissiveBitmap = null;
                if (babylonMaterial.emissiveTexture != null)
                {
                    emissiveBitmap = TextureUtilities.LoadTexture(babylonMaterial.emissiveTexture.originalPath, logger);
                }

                // Diffuse
                Bitmap diffuseBitmap = null;
                if (babylonMaterial.diffuseTexture != null)
                {
                    diffuseBitmap = TextureUtilities.LoadTexture(babylonMaterial.diffuseTexture.originalPath, logger);
                }

                if (emissiveBitmap != null || diffuseBitmap != null)
                {
                    // Retreive dimensions
                    int width              = 0;
                    int height             = 0;
                    var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, emissiveBitmap, diffuseBitmap);
                    if (!haveSameDimensions)
                    {
                        logger.RaiseError("Emissive and diffuse maps should have same dimensions", 2);
                    }

                    // Create pre-multiplied emissive map
                    emissivePremultipliedBitmap = new Bitmap(width, height);
                    for (int x = 0; x < width; x++)
                    {
                        for (int y = 0; y < height; y++)
                        {
                            var _emissive = emissiveBitmap != null?emissiveBitmap.GetPixel(x, y).toArrayRGB().Multiply(1f / 255.0f) : defaultEmissive;

                            var _diffuse = diffuseBitmap != null?diffuseBitmap.GetPixel(x, y).toArrayRGB().Multiply(1f / 255.0f) : defaultDiffuse;

                            var emissivePremultiplied = _emissive.Multiply(_diffuse);

                            Color colorEmissivePremultiplied = Color.FromArgb(
                                (int)(emissivePremultiplied[0] * 255),
                                (int)(emissivePremultiplied[1] * 255),
                                (int)(emissivePremultiplied[2] * 255)
                                );
                            emissivePremultipliedBitmap.SetPixel(x, y, colorEmissivePremultiplied);
                        }
                    }
                }
            }

            var emissiveTextureInfo = ExportBitmapTexture(gltf, babylonTexture, emissivePremultipliedBitmap);

            // Register the texture for optimisation
            RegisterEmissive(emissiveTextureInfo, babylonMaterial, defaultDiffuse, defaultEmissive);

            return(emissiveTextureInfo);
        }
Example #20
0
        private void ExportMaterial(BabylonMaterial babylonMaterial, GLTF gltf)
        {
            var name = babylonMaterial.name;
            var id   = babylonMaterial.id;

            logger.RaiseMessage("GLTFExporter.Material | Export material named: " + name, 1);

            GLTFMaterial          gltfMaterial           = null;
            IGLTFMaterialExporter customMaterialExporter = exportParameters.customGLTFMaterialExporter;

            if (customMaterialExporter != null && customMaterialExporter.GetGltfMaterial(babylonMaterial, gltf, logger, out gltfMaterial))
            {
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);
            }
            else if (babylonMaterial is BabylonStandardMaterial babylonStandardMaterial)
            {
                // --- prints ---
                #region prints

                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial data", 2);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);

                // Ambient
                for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambient[" + i + "]=" + babylonStandardMaterial.ambient[i], 3);
                }

                // Diffuse
                logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuse.Length=" + babylonStandardMaterial.diffuse.Length, 3);
                for (int i = 0; i < babylonStandardMaterial.diffuse.Length; i++)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuse[" + i + "]=" + babylonStandardMaterial.diffuse[i], 3);
                }
                if (babylonStandardMaterial.diffuseTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3);
                }
                else
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture.name=" + babylonStandardMaterial.diffuseTexture.name, 3);
                }

                // Normal / bump
                if (babylonStandardMaterial.bumpTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 3);
                }
                else
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.bumpTexture.name=" + babylonStandardMaterial.bumpTexture.name, 3);
                }

                // Opacity
                if (babylonStandardMaterial.opacityTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.opacityTexture=null", 3);
                }
                else
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.opacityTexture.name=" + babylonStandardMaterial.opacityTexture.name, 3);
                }

                // Specular
                for (int i = 0; i < babylonStandardMaterial.specular.Length; i++)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3);
                }
                logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
                if (babylonStandardMaterial.specularTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularTexture=null", 3);
                }
                else
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularTexture.name=" + babylonStandardMaterial.specularTexture.name, 3);
                }

                // Occlusion
                if (babylonStandardMaterial.ambientTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 3);
                }
                else
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambientTexture.name=" + babylonStandardMaterial.ambientTexture.name, 3);
                }

                // Emissive
                for (int i = 0; i < babylonStandardMaterial.emissive.Length; i++)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissive[" + i + "]=" + babylonStandardMaterial.emissive[i], 3);
                }
                if (babylonStandardMaterial.emissiveTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 3);
                }
                else
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture.name=" + babylonStandardMaterial.emissiveTexture.name, 3);
                }
                #endregion

                // --------------------------------
                // --------- gltfMaterial ---------
                // --------------------------------
                logger.RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                //Custom user properties
                if (babylonStandardMaterial.metadata != null && babylonStandardMaterial.metadata.Count != 0)
                {
                    gltfMaterial.extras = babylonStandardMaterial.metadata;
                }

                // Alpha
                GLTFMaterial.AlphaMode alphaMode;
                float?alphaCutoff;
                getAlphaMode(babylonStandardMaterial, out alphaMode, out alphaCutoff);
                gltfMaterial.alphaMode = alphaMode;
                if (alphaCutoff.HasValue && alphaCutoff.Value != 0.5f) // do not export glTF default value
                {
                    gltfMaterial.alphaCutoff = alphaCutoff;
                }

                // DoubleSided
                gltfMaterial.doubleSided = !babylonMaterial.backFaceCulling;

                // Normal
                gltfMaterial.normalTexture = ExportTexture(babylonStandardMaterial.bumpTexture, gltf);

                // Occlusion
                gltfMaterial.occlusionTexture = ExportTexture(babylonStandardMaterial.ambientTexture, gltf);

                // Emissive
                gltfMaterial.emissiveFactor = babylonStandardMaterial.emissive;
                // linkEmissiveWithDiffuse attribute doesn't have an equivalent in gltf format
                // When true, the emissive texture needs to be manually multiplied with diffuse texture
                // Otherwise, the emissive texture is assumed to be already pre-multiplied
                if (babylonStandardMaterial.linkEmissiveWithDiffuse)
                {
                    // Even when no emissive texture is provided, the self illumination value needs to be multiplied to the diffuse texture in order to get the pre-multiplied emissive (texture)
                    if (babylonStandardMaterial.emissiveTexture != null || babylonStandardMaterial.selfIllum > 0)
                    {
                        // Default emissive is the raw value of the self illumination
                        // It is not the babylon emissive value which is already pre-multiplied with diffuse color
                        float[] defaultEmissive = new float[] { 1, 1, 1 }.Multiply(babylonStandardMaterial.selfIllum);
                        gltfMaterial.emissiveTexture = ExportEmissiveTexture(babylonStandardMaterial, gltf, defaultEmissive, babylonStandardMaterial.diffuse);
                    }
                }
                else
                {
                    gltfMaterial.emissiveTexture = ExportTexture(babylonStandardMaterial.emissiveTexture, gltf);
                }

                // Constraints
                if (gltfMaterial.emissiveTexture != null)
                {
                    gltfMaterial.emissiveFactor = new[] { 1.0f, 1.0f, 1.0f };
                }

                // --------------------------------
                // --- gltfPbrMetallicRoughness ---
                // --------------------------------

                logger.RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;

                // --- Global ---

                SpecularGlossiness _specularGlossiness = new SpecularGlossiness
                {
                    diffuse    = new BabylonColor3(babylonStandardMaterial.diffuse),
                    opacity    = babylonMaterial.alpha,
                    specular   = new BabylonColor3(babylonStandardMaterial.specular),
                    glossiness = babylonStandardMaterial.specularPower / 256
                };

                MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true);

                // Base color
                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                {
                    _metallicRoughness.baseColor.r,
                    _metallicRoughness.baseColor.g,
                    _metallicRoughness.baseColor.b,
                    _metallicRoughness.opacity
                };

                // Metallic roughness
                gltfPbrMetallicRoughness.metallicFactor  = _metallicRoughness.metallic;
                gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness;


                // --- Textures ---
                var babylonTexture = babylonStandardMaterial.diffuseTexture != null ? babylonStandardMaterial.diffuseTexture :
                                     babylonStandardMaterial.specularTexture != null ? babylonStandardMaterial.specularTexture :
                                     babylonStandardMaterial.opacityTexture != null ? babylonStandardMaterial.opacityTexture :
                                     null;

                if (babylonTexture != null)
                {
                    //Check if the texture already exist
                    var _key = SetStandText(babylonStandardMaterial);

                    if (GetStandTextInfo(_key) != null)
                    {
                        var _pairBCMR = GetStandTextInfo(_key);
                        gltfPbrMetallicRoughness.baseColorTexture         = _pairBCMR.baseColor;
                        gltfPbrMetallicRoughness.metallicRoughnessTexture = _pairBCMR.metallicRoughness;
                    }
                    else
                    {
                        bool isAlphaInTexture = (isTextureOk(babylonStandardMaterial.diffuseTexture) && babylonStandardMaterial.diffuseTexture.hasAlpha) ||
                                                isTextureOk(babylonStandardMaterial.opacityTexture);

                        Bitmap baseColorBitmap             = null;
                        Bitmap metallicRoughnessBitmap     = null;
                        string baseColorBitmapName         = null;
                        string metallicRoughnessBitmapName = null;

                        GLTFTextureInfo textureInfoBC = null;
                        GLTFTextureInfo textureInfoMR = null;

                        if (exportParameters.writeTextures)
                        {
                            // Diffuse
                            Bitmap diffuseBitmap = null;
                            if (babylonStandardMaterial.diffuseTexture != null)
                            {
                                if (babylonStandardMaterial.diffuseTexture.bitmap != null)
                                {
                                    // Diffuse color map has been computed by the exporter
                                    diffuseBitmap = babylonStandardMaterial.diffuseTexture.bitmap;
                                }
                                else
                                {
                                    // Diffuse color map is straight input
                                    diffuseBitmap = TextureUtilities.LoadTexture(babylonStandardMaterial.diffuseTexture.originalPath, logger);
                                    babylonStandardMaterial.diffuseTexture.bitmap = diffuseBitmap;
                                }
                            }

                            // Specular
                            Bitmap specularBitmap = null;
                            if (babylonStandardMaterial.specularTexture != null)
                            {
                                if (babylonStandardMaterial.specularTexture.bitmap != null)
                                {
                                    // Specular color map has been computed by the exporter
                                    specularBitmap = babylonStandardMaterial.specularTexture.bitmap;
                                }
                                else
                                {
                                    // Specular color map is straight input
                                    specularBitmap = TextureUtilities.LoadTexture(babylonStandardMaterial.specularTexture.originalPath, logger);
                                }
                            }

                            // Opacity / Alpha / Transparency
                            Bitmap opacityBitmap = null;
                            if ((babylonStandardMaterial.diffuseTexture == null || babylonStandardMaterial.diffuseTexture.hasAlpha == false) && babylonStandardMaterial.opacityTexture != null)
                            {
                                opacityBitmap = TextureUtilities.LoadTexture(babylonStandardMaterial.opacityTexture.originalPath, logger);
                            }

                            if ((diffuseBitmap != null && (specularBitmap != null || opacityBitmap != null)) || specularBitmap != null || opacityBitmap != null)
                            {
                                // Retreive dimensions
                                int width              = 0;
                                int height             = 0;
                                var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, diffuseBitmap, specularBitmap, opacityBitmap);
                                if (!haveSameDimensions)
                                {
                                    logger.RaiseError("Diffuse, specular and opacity maps should have same dimensions", 2);
                                }
                                baseColorBitmapName = (babylonStandardMaterial.diffuseTexture != null ? Path.GetFileNameWithoutExtension(babylonStandardMaterial.diffuseTexture.name) : TextureUtilities.ColorToStringName(Color.FromArgb(255, (int)(babylonStandardMaterial.diffuse[0]), (int)(babylonStandardMaterial.diffuse[1]), (int)(babylonStandardMaterial.diffuse[2]))))
                                                      + (babylonStandardMaterial.opacityTexture != null ? "_alpha_" + Path.GetFileNameWithoutExtension(babylonStandardMaterial.opacityTexture.name) : "")
                                                      + ".png";

                                metallicRoughnessBitmapName = "MR_" + baseColorBitmapName
                                                              + "_spec_" + (babylonStandardMaterial.specularTexture != null ? Path.GetFileNameWithoutExtension(babylonStandardMaterial.specularTexture.name) : TextureUtilities.ColorToStringName(Color.FromArgb(255, (int)(babylonStandardMaterial.specular[0]), (int)(babylonStandardMaterial.specular[1]), (int)(babylonStandardMaterial.specular[2]))))
                                                              + "_gloss_" + babylonStandardMaterial.specularPower
                                                              + ".png";
                                // Create baseColor+alpha and metallic+roughness maps
                                baseColorBitmap         = new Bitmap(width, height);
                                metallicRoughnessBitmap = new Bitmap(width, height);
                                for (int x = 0; x < width; x++)
                                {
                                    for (int y = 0; y < height; y++)
                                    {
                                        SpecularGlossiness specularGlossinessTexture = new SpecularGlossiness
                                        {
                                            diffuse = diffuseBitmap != null ? new BabylonColor3(diffuseBitmap.GetPixel(x, y)) :
                                                      _specularGlossiness.diffuse,
                                            opacity = diffuseBitmap != null && babylonStandardMaterial.diffuseTexture.hasAlpha ? diffuseBitmap.GetPixel(x, y).A / 255.0f :
                                                      opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB ? opacityBitmap.GetPixel(x, y).R / 255.0f :
                                                      opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB == false?opacityBitmap.GetPixel(x, y).A / 255.0f :
                                                      _specularGlossiness.opacity,
                                            specular = specularBitmap != null ? new BabylonColor3(specularBitmap.GetPixel(x, y)) :
                                                       _specularGlossiness.specular,
                                            glossiness = babylonStandardMaterial.useGlossinessFromSpecularMapAlpha && specularBitmap != null?specularBitmap.GetPixel(x, y).A / 255.0f :
                                                         _specularGlossiness.glossiness
                                        };

                                        var displayPrints = x == width / 2 && y == height / 2;
                                        MetallicRoughness metallicRoughnessTexture = ConvertToMetallicRoughness(specularGlossinessTexture, displayPrints);

                                        Color colorBase = Color.FromArgb(
                                            (int)(metallicRoughnessTexture.opacity * 255),
                                            (int)(metallicRoughnessTexture.baseColor.r * 255),
                                            (int)(metallicRoughnessTexture.baseColor.g * 255),
                                            (int)(metallicRoughnessTexture.baseColor.b * 255)
                                            );
                                        baseColorBitmap.SetPixel(x, y, colorBase);

                                        // The metalness values are sampled from the B channel.
                                        // The roughness values are sampled from the G channel.
                                        // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
                                        Color colorMetallicRoughness = Color.FromArgb(
                                            0,
                                            (int)(metallicRoughnessTexture.roughness * 255),
                                            (int)(metallicRoughnessTexture.metallic * 255)
                                            );
                                        metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
                                    }
                                }
                            }
                        }

                        //export textures
                        if (baseColorBitmap != null || babylonTexture.bitmap != null)
                        {
                            textureInfoBC = ExportBitmapTexture(gltf, babylonTexture, baseColorBitmap, baseColorBitmapName);
                            gltfPbrMetallicRoughness.baseColorTexture = textureInfoBC;
                        }

                        if (isTextureOk(babylonStandardMaterial.specularTexture))
                        {
                            textureInfoMR = ExportBitmapTexture(gltf, babylonTexture, metallicRoughnessBitmap, metallicRoughnessBitmapName);
                            gltfPbrMetallicRoughness.metallicRoughnessTexture = textureInfoMR;
                        }

                        //register the texture
                        AddStandText(_key, textureInfoBC, textureInfoMR);
                    }

                    // Constraints
                    if (gltfPbrMetallicRoughness.baseColorTexture != null)
                    {
                        gltfPbrMetallicRoughness.baseColorFactor[3] = 1.0f;
                    }

                    if (gltfPbrMetallicRoughness.metallicRoughnessTexture != null)
                    {
                        gltfPbrMetallicRoughness.metallicFactor  = 1.0f;
                        gltfPbrMetallicRoughness.roughnessFactor = 1.0f;
                    }
                }
            }
            else if (typeof(BabylonPBRBaseSimpleMaterial).IsAssignableFrom(babylonMaterial.GetType()))
            {
                var babylonPBRBaseSimpleMaterial = babylonMaterial as BabylonPBRBaseSimpleMaterial;

                // --- prints ---
                #region prints

                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial data", 2);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);

                // Global
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.maxSimultaneousLights=" + babylonPBRBaseSimpleMaterial.maxSimultaneousLights, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.disableLighting=" + babylonPBRBaseSimpleMaterial.disableLighting, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.alphaCutOff=" + babylonPBRBaseSimpleMaterial.alphaCutOff, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.transparencyMode=" + babylonPBRBaseSimpleMaterial.transparencyMode, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.doubleSided=" + babylonPBRBaseSimpleMaterial.doubleSided, 3);

                // Base color
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.baseColor.Length=" + babylonPBRBaseSimpleMaterial.baseColor.Length, 3);
                for (int i = 0; i < babylonPBRBaseSimpleMaterial.baseColor.Length; i++)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.baseColor[" + i + "]=" + babylonPBRBaseSimpleMaterial.baseColor[i], 3);
                }
                if (babylonPBRBaseSimpleMaterial.baseTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.baseTexture=null", 3);
                }


                // Normal / bump
                if (babylonPBRBaseSimpleMaterial.normalTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.normalTexture=null", 3);
                }
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.invertNormalMapX=" + babylonPBRBaseSimpleMaterial.invertNormalMapX, 3);
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.invertNormalMapY=" + babylonPBRBaseSimpleMaterial.invertNormalMapY, 3);

                // Emissive
                for (int i = 0; i < babylonPBRBaseSimpleMaterial.emissive.Length; i++)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.emissiveColor[" + i + "]=" + babylonPBRBaseSimpleMaterial.emissive[i], 3);
                }
                if (babylonPBRBaseSimpleMaterial.emissiveTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.emissiveTexture=null", 3);
                }

                // Ambient occlusion
                logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.occlusionStrength=" + babylonPBRBaseSimpleMaterial.occlusionStrength, 3);
                if (babylonPBRBaseSimpleMaterial.occlusionTexture == null)
                {
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRBaseSimpleMaterial.occlusionTexture=null", 3);
                }

                if (babylonMaterial is BabylonPBRMetallicRoughnessMaterial pbrMRMatPrint)
                {
                    // Metallic+roughness
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallic=" + pbrMRMatPrint.metallic, 3);
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.roughness=" + pbrMRMatPrint.roughness, 3);
                    if (pbrMRMatPrint.metallicRoughnessTexture == null)
                    {
                        logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture=null", 3);
                    }
                }
                else if (babylonMaterial is BabylonPBRSpecularGlossinessMaterial pbrSGMatPrint)
                {
                    // Metallic+roughness
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRSpecularGlossinessMaterial.specular=" + pbrSGMatPrint.specularColor, 3);
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRSpecularGlossinessMaterial.glossiness=" + pbrSGMatPrint.glossiness, 3);
                    if (pbrSGMatPrint.specularGlossinessTexture == null)
                    {
                        logger.RaiseVerbose("GLTFExporter.Material | babylonPBRSpecularGlossinessMaterial.specularGlossinessTexture=null", 3);
                    }
                }

                #endregion

                // --------------------------------
                // --------- gltfMaterial ---------
                // --------------------------------

                logger.RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                //Custom user properties
                if (babylonMaterial.metadata != null && babylonMaterial.metadata.Count != 0)
                {
                    gltfMaterial.extras = babylonMaterial.metadata;
                }

                // Alpha
                GLTFMaterial.AlphaMode alphaMode;
                float?alphaCutoff;
                getAlphaMode(babylonPBRBaseSimpleMaterial, out alphaMode, out alphaCutoff);
                gltfMaterial.alphaMode = alphaMode;
                if (alphaCutoff.HasValue && alphaCutoff.Value != 0.5f) // do not export glTF default value
                {
                    gltfMaterial.alphaCutoff = alphaCutoff;
                }

                // DoubleSided
                gltfMaterial.doubleSided = babylonPBRBaseSimpleMaterial.doubleSided;

                // Normal
                gltfMaterial.normalTexture = ExportTexture(babylonPBRBaseSimpleMaterial.normalTexture, gltf);

                // Occlusion
                if (babylonPBRBaseSimpleMaterial.occlusionTexture != null)
                {
                    if (babylonPBRBaseSimpleMaterial.occlusionTexture.bitmap != null)
                    {
                        // ORM texture has been merged manually by the exporter
                        // Occlusion is defined as well as metallic and/or roughness
                        logger.RaiseVerbose("Occlusion is defined as well as metallic and/or roughness", 2);
                        gltfMaterial.occlusionTexture = ExportBitmapTexture(gltf, babylonPBRBaseSimpleMaterial.occlusionTexture);
                    }
                    else
                    {
                        // ORM texture was already merged or only occlusion is defined
                        logger.RaiseVerbose("ORM texture was already merged or only occlusion is defined", 2);
                        gltfMaterial.occlusionTexture = ExportTexture(babylonPBRBaseSimpleMaterial.occlusionTexture, gltf);
                    }
                }

                // Emissive
                gltfMaterial.emissiveFactor  = babylonPBRBaseSimpleMaterial.emissive;
                gltfMaterial.emissiveTexture = ExportTexture(babylonPBRBaseSimpleMaterial.emissiveTexture, gltf);

                // --------------------------------
                // --- gltfPbrMetallicRoughness ---
                // --------------------------------
                if (babylonMaterial is BabylonPBRMetallicRoughnessMaterial pbrMRMat)
                {
                    // Metallic+roughness
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallic=" + pbrMRMat.metallic, 3);
                    logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.roughness=" + pbrMRMat.roughness, 3);
                    if (pbrMRMat.metallicRoughnessTexture == null)
                    {
                        logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture=null", 3);
                    }

                    logger.RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                    var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                    gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;

                    // --- Global ---

                    // Base color
                    gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                    {
                        pbrMRMat.baseColor[0],
                        pbrMRMat.baseColor[1],
                        pbrMRMat.baseColor[2],
                        pbrMRMat.alpha
                    };
                    if (pbrMRMat.baseTexture != null)
                    {
                        gltfPbrMetallicRoughness.baseColorTexture = ExportBaseColorTexture(gltf, pbrMRMat.baseTexture);
                    }

                    // Metallic roughness
                    gltfPbrMetallicRoughness.metallicFactor  = pbrMRMat.metallic;
                    gltfPbrMetallicRoughness.roughnessFactor = pbrMRMat.roughness;
                    if (pbrMRMat.metallicRoughnessTexture != null)
                    {
                        if (pbrMRMat.metallicRoughnessTexture == pbrMRMat.occlusionTexture)
                        {
                            // Occlusion is defined as well as metallic and/or roughness
                            // Use same texture
                            logger.RaiseVerbose("Occlusion is defined as well as metallic and/or roughness", 2);
                            gltfPbrMetallicRoughness.metallicRoughnessTexture = gltfMaterial.occlusionTexture;
                        }
                        else
                        {
                            // Occlusion is not defined, only metallic and/or roughness
                            logger.RaiseVerbose("Occlusion is not defined, only metallic and/or roughness", 2);

                            if (pbrMRMat.metallicRoughnessTexture.bitmap != null)
                            {
                                // Metallic & roughness texture has been merged manually by the exporter
                                // Write bitmap file
                                logger.RaiseVerbose("Metallic & roughness texture has been merged manually by the exporter", 2);
                                gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(gltf, pbrMRMat.metallicRoughnessTexture);
                            }
                            else
                            {
                                // Metallic & roughness texture was already merged
                                // Copy file
                                logger.RaiseVerbose("Metallic & roughness texture was already merged", 2);
                                gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportTexture(pbrMRMat.metallicRoughnessTexture, gltf);
                            }
                        }
                    }
                }
                else if (babylonMaterial is BabylonPBRSpecularGlossinessMaterial pbrSGMat)
                {
                    var ext = new KHR_materials_pbrSpecularGlossiness()
                    {
                        specularFactor            = pbrSGMat.specularColor,
                        glossinessFactor          = pbrSGMat.glossiness,
                        diffuseTexture            = ExportTexture(pbrSGMat.diffuseTexture, gltf),
                        specularGlossinessTexture = ExportTexture(pbrSGMat.specularGlossinessTexture, gltf)
                    };

                    gltfMaterial.extensions = gltfMaterial.extensions ?? new GLTFExtensions(); // ensure extensions exist
                    gltfMaterial.extensions.AddExtension(gltf, "KHR_materials_pbrSpecularGlossiness", ext);
                }
            }
            else if (babylonMaterial.GetType() == typeof(BabylonFurMaterial))
            {
                // TODO - Implement proper handling of BabylonFurMaterial once gLTF spec has support
                var babylonPBRFurMaterial = babylonMaterial as BabylonFurMaterial;

                gltfMaterial = new GLTFMaterial
                {
                    name = name
                };
                gltfMaterial.id    = babylonMaterial.id;
                gltfMaterial.index = gltf.MaterialsList.Count;
                gltf.MaterialsList.Add(gltfMaterial);

                //Custom user properties
                if (babylonPBRFurMaterial.metadata != null && babylonPBRFurMaterial.metadata.Count != 0)
                {
                    gltfMaterial.extras = babylonPBRFurMaterial.metadata;
                }
            }
            else
            {
                logger.RaiseWarning("GLTFExporter.Material | Unsupported material type: " + babylonMaterial.GetType(), 2);
            }

            if (gltfMaterial != null && babylonMaterial.isUnlit)
            {
                // Add Unlit extension
                if (!exportParameters.enableKHRMaterialsUnlit)
                {
                    logger.RaiseWarning("GLTFExporter.Material | KHR_materials_unlit has not been enabled for export!", 2);
                }
                else
                {
                    if (gltfMaterial.extensions == null)
                    {
                        gltfMaterial.extensions = new GLTFExtensions();
                    }
                    if (gltf.extensionsUsed == null)
                    {
                        gltf.extensionsUsed = new System.Collections.Generic.List <string>();
                    }
                    if (!gltf.extensionsUsed.Contains("KHR_materials_unlit"))
                    {
                        gltf.extensionsUsed.Add("KHR_materials_unlit");
                    }
                    gltfMaterial.extensions["KHR_materials_unlit"] = new object();
                }
            }
        }
        private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf, string name)
        {
            if (babylonTexture == null)
            {
                return(null);
            }

            if (name == null)
            {
                name = babylonTexture.name;
            }

            logger.RaiseMessage("GLTFExporter.Texture | Export texture named: " + name, 2);

            if (glTFTextureInfoMap.ContainsKey(babylonTexture.Id))
            {
                return(glTFTextureInfoMap[babylonTexture.Id]);
            }
            else
            {
                var sourcePath = babylonTexture.originalPath;
                if (babylonTexture.bitmap != null)
                {
                    sourcePath = Path.Combine(gltf.OutputFolder, name);
                }

                if (sourcePath == null || sourcePath == "")
                {
                    logger.RaiseWarning("Texture path is missing.", 3);
                    return(null);
                }

                var validImageFormat = TextureUtilities.GetValidImageFormat(Path.GetExtension(sourcePath));

                if (validImageFormat == null)
                {
                    // Image format is not supported by the exporter
                    logger.RaiseWarning(string.Format("Format of texture {0} is not supported by the exporter. Consider using a standard image format like jpg or png.", Path.GetFileName(sourcePath)), 3);
                    return(null);
                }

                var destPath = Path.Combine(gltf.OutputFolder, name);
                destPath = Path.ChangeExtension(destPath, validImageFormat);

                name = Path.ChangeExtension(name, validImageFormat);

                // --------------------------
                // -------- Sampler ---------
                // --------------------------
                logger.RaiseMessage("GLTFExporter.Texture | create sampler", 3);
                GLTFSampler gltfSampler = new GLTFSampler();
                gltfSampler.index = gltf.SamplersList.Count;

                // --- Retrieve info from babylon texture ---
                // Mag and min filters
                GLTFSampler.TextureMagFilter?magFilter;
                GLTFSampler.TextureMinFilter?minFilter;
                getSamplingParameters(babylonTexture.samplingMode, out magFilter, out minFilter);
                gltfSampler.magFilter = magFilter;
                gltfSampler.minFilter = minFilter;
                // WrapS and wrapT
                gltfSampler.wrapS = getWrapMode(babylonTexture.wrapU);
                gltfSampler.wrapT = getWrapMode(babylonTexture.wrapV);

                var matchingSampler = gltf.SamplersList.FirstOrDefault(sampler => sampler.wrapS == gltfSampler.wrapS && sampler.wrapT == gltfSampler.wrapT && sampler.magFilter == gltfSampler.magFilter && sampler.minFilter == gltfSampler.minFilter);
                if (matchingSampler != null)
                {
                    gltfSampler = matchingSampler;
                }
                else
                {
                    gltf.SamplersList.Add(gltfSampler);
                }


                // --------------------------
                // --------- Image ----------
                // --------------------------

                logger.RaiseMessage("GLTFExporter.Texture | create image", 3);
                GLTFImage gltfImage = null;

                string ImageName = name;

                if (exportParameters.tryToReuseOpaqueAndBlendTexture)
                {
                    if (!string.IsNullOrEmpty(babylonTexture.baseColorPath))
                    {
                        if (string.IsNullOrEmpty(babylonTexture.alphaPath))
                        {
                            // lets search previous similar image
                            ImageName = BaseColorAlphaImageNameLookup(babylonTexture, ImageName);
                        }
                        else
                        {
                            // register Pair with ImageName
                            RegisterBaseColorAlphaImageName(babylonTexture, ImageName);
                        }
                    }
                }

                if (glTFImageMap.ContainsKey(ImageName))
                {
                    gltfImage = glTFImageMap[ImageName];
                }
                else
                {
                    string textureUri = ImageName;
                    if (!string.IsNullOrWhiteSpace(exportParameters.textureFolder))
                    {
                        textureUri = PathUtilities.GetRelativePath(exportParameters.outputPath, exportParameters.textureFolder);
                        textureUri = Path.Combine(textureUri, ImageName);
                    }
                    gltfImage = new GLTFImage
                    {
                        uri = textureUri
                    };
                    gltfImage.index = gltf.ImagesList.Count;
                    gltf.ImagesList.Add(gltfImage);
                    switch (validImageFormat)
                    {
                    case "jpg":
                        gltfImage.FileExtension = "jpeg";
                        break;

                    case "png":
                        gltfImage.FileExtension = "png";
                        break;
                    }
                    if (exportParameters.outputFormat == "glb")
                    {
                        var imageBufferView = WriteImageToGltfBuffer(gltf, gltfImage, sourcePath, babylonTexture.bitmap);
                        gltfImage.uri        = null;
                        gltfImage.bufferView = imageBufferView.index;
                        gltfImage.mimeType   = "image/" + gltfImage.FileExtension;
                    }
                    else
                    {
                        if (exportParameters.writeTextures)
                        {
                            if (babylonTexture.bitmap != null)
                            {
                                // We may have modified this texture image, copy the buffer contents to disk
                                var extension   = Path.GetExtension(ImageName).ToLower();
                                var imageFormat = extension == ".jpg" ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png;
                                logger.RaiseMessage($"GLTFExporter.Texture | write image '{ImageName}' to '{destPath}'", 3);
                                TextureUtilities.SaveBitmap(babylonTexture.bitmap, destPath, imageFormat, exportParameters.txtQuality, logger);
                            }
                            else
                            {
                                // Copy texture from source to output
                                TextureUtilities.CopyTexture(sourcePath, destPath, exportParameters.txtQuality, logger);
                            }
                        }
                    }
                    glTFImageMap.Add(ImageName, gltfImage);
                }

                // --------------------------
                // -------- Texture ---------
                // --------------------------

                logger.RaiseMessage("GLTFExporter.Texture | create texture", 3);
                var gltfTexture = new GLTFTexture
                {
                    name    = name,
                    sampler = gltfSampler.index,
                    source  = gltfImage.index
                };
                gltfTexture.index = gltf.TexturesList.Count;

                if (!CheckIfImageIsRegistered(name))
                {
                    gltf.TexturesList.Add(gltfTexture);
                }
                else
                {
                    gltfTexture = gltf.TexturesList[GetRegisteredTexture(gltfTexture.name).index];
                }

                // --------------------------
                // ------ TextureInfo -------
                // --------------------------
                var gltfTextureInfo = new GLTFTextureInfo
                {
                    index    = gltfTexture.index,
                    texCoord = babylonTexture.coordinatesIndex
                };

                if (babylonTexture.level != 1.0)
                {
                    gltfTextureInfo.scale = babylonTexture.level;
                }

                if (!(babylonTexture.uOffset == 0) || !(babylonTexture.vOffset == 0) || !(babylonTexture.uScale == 1) || !(babylonTexture.vScale == 1) || !(babylonTexture.wAng == 0))
                {
                    // Add texture extension if enabled in the export settings
                    if (exportParameters.enableKHRTextureTransform)
                    {
                        AddTextureTransformExtension(ref gltf, ref gltfTextureInfo, babylonTexture);
                    }
                    else
                    {
                        logger.RaiseWarning("GLTFExporter.Texture | KHR_texture_transform is not enabled, so the texture may look incorrect at runtime!", 3);
                        logger.RaiseWarning("GLTFExporter.Texture | KHR_texture_transform is not enabled, so the texture may look incorrect at runtime!", 3);
                    }
                }
                var textureID = name + TextureTransformID(gltfTextureInfo);
                // Check for texture optimization.  This is done here after the texture transform has been potentially applied to the texture extension
                if (CheckIfImageIsRegistered(textureID))
                {
                    var textureComponent = GetRegisteredTexture(textureID);

                    return(textureComponent);
                }

                // Add the texture in the dictionary
                RegisterTexture(gltfTextureInfo, textureID);
                glTFTextureInfoMap[babylonTexture.Id] = gltfTextureInfo;

                return(gltfTextureInfo);
            }
        }
Example #22
0
        private GLTFNode _exportBone(BabylonBone babylonBone, GLTF gltf, BabylonSkeleton babylonSkeleton, List <BabylonBone> bones)
        {
            var nodeNodePair = nodeToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonBone.id));

            if (nodeNodePair.Key != null)
            {
                return(nodeNodePair.Value);
            }

            var boneNodePair = boneToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonBone.id));

            if (boneNodePair.Key != null)
            {
                return(boneNodePair.Value);
            }

            // Node
            var gltfNode = new GLTFNode
            {
                name  = babylonBone.name,
                index = gltf.NodesList.Count
            };

            gltf.NodesList.Add(gltfNode);

            boneToGltfNodeMap.Add(babylonBone, gltfNode);

            // Hierarchy
            if (babylonBone.parentBoneIndex >= 0)
            {
                var babylonParentBone = bones.Find(_babylonBone => _babylonBone.index == babylonBone.parentBoneIndex);
                var gltfParentNode    = _exportBone(babylonParentBone, gltf, babylonSkeleton, bones);
                RaiseMessage("GLTFExporter.Skin | Add " + babylonBone.name + " as child to " + gltfParentNode.name, 3);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Skin | Add " + babylonBone.name + " as root node to scene", 3);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            // Bones transform are exported through translation/rotation/scale (TRS) rather than matrix
            // Because gltf node animation can only target TRS properties, not the matrix one
            // Create matrix from array
            var babylonMatrix = new BabylonMatrix();

            babylonMatrix.m = babylonBone.matrix;
            // Decompose matrix into TRS
            var translationBabylon  = new BabylonVector3();
            var rotationQuatBabylon = new BabylonQuaternion();
            var scaleBabylon        = new BabylonVector3();

            babylonMatrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);
            // Store TRS values
            gltfNode.translation = translationBabylon.ToArray();
            gltfNode.rotation    = rotationQuatBabylon.ToArray();
            gltfNode.scale       = scaleBabylon.ToArray();

            // Animations
            //ExportBoneAnimation(babylonBone, gltf, gltfNode);

            return(gltfNode);
        }
Example #23
0
        private void ExportAnimationGroups(GLTF gltf, BabylonScene babylonScene)
        {
            // Retreive and parse animation group data
            var animationGroupList  = babylonScene.animationGroups;
            var animationGroupCount = animationGroupList == null ? 0 : animationGroupList.Count;

            gltf.AnimationsList.Clear();
            gltf.AnimationsList.Capacity = Math.Max(gltf.AnimationsList.Capacity, animationGroupCount);

            if (animationGroupCount <= 0)
            {
                logger.RaiseMessage("GLTFExporter.Animation | No AnimationGroups: exporting all animations together.", 1);
                GLTFAnimation gltfAnimation = new GLTFAnimation();
                gltfAnimation.name = "All Animations";

                int startFrame = babylonScene.TimelineStartFrame;
                int endFrame   = babylonScene.TimelineEndFrame;

                foreach (var pair in nodeToGltfNodeMap)
                {
                    BabylonNode node                             = pair.Key;
                    GLTFNode    gltfNode                         = pair.Value;
                    bool        nodeHasAnimations                = node.animations != null && node.animations.Length > 0 && node.animations[0] != null;
                    bool        nodeHasExtraAnimations           = node.extraAnimations != null && node.extraAnimations.Count > 0 && node.extraAnimations[0] != null;
                    BabylonMesh meshNode                         = node as BabylonMesh;
                    BabylonMorphTargetManager morphTargetManager = null;
                    bool nodeHasAnimatedMorphTargets             = false;
                    if (meshNode != null && meshNode.morphTargetManagerId != null)
                    {
                        morphTargetManager = GetBabylonMorphTargetManager(babylonScene, meshNode);
                        if (morphTargetManager != null)
                        {
                            nodeHasAnimatedMorphTargets = morphTargetManager.targets.Any(target => target.animations != null && target.animations.Length > 0 && target.animations[0] != null);
                        }
                    }

                    if (!nodeHasAnimations && !nodeHasExtraAnimations && !nodeHasAnimatedMorphTargets)
                    {
                        continue;
                    }
                    if (nodeHasAnimations && node.animations[0].property == "_matrix")
                    {
                        ExportBoneAnimation(gltfAnimation, startFrame, endFrame, gltf, node, pair.Value);
                    }
                    else
                    {
                        ExportNodeAnimation(gltfAnimation, startFrame, endFrame, gltf, node, gltfNode, babylonScene);
                    }

                    if (nodeHasAnimatedMorphTargets)
                    {
                        ExportMorphTargetWeightAnimation(morphTargetManager, gltf, gltfNode, gltfAnimation.ChannelList, gltfAnimation.SamplerList, startFrame, endFrame, babylonScene);
                    }
                }

                if (gltfAnimation.ChannelList.Count > 0)
                {
                    gltf.AnimationsList.Add(gltfAnimation);
                }
                else
                {
                    logger.RaiseMessage("GLTFExporter.Animation | No animation data for this animation, it is ignored.", 2);
                }
            }
            else
            {
                foreach (BabylonAnimationGroup animGroup in animationGroupList)
                {
                    logger.RaiseMessage("GLTFExporter.Animation | " + animGroup.name, 1);

                    GLTFAnimation gltfAnimation = new GLTFAnimation();
                    gltfAnimation.name = animGroup.name;

                    int startFrame = MathUtilities.RoundToInt(animGroup.from);
                    int endFrame   = MathUtilities.RoundToInt(animGroup.to);

                    var uniqueNodeIds = animGroup.targetedAnimations.Select(targetAnim => targetAnim.targetId).Distinct();
                    foreach (var id in uniqueNodeIds)
                    {
                        BabylonNode babylonNode = babylonNodes.Find(node => node.id.Equals(id));
                        GLTFNode    gltfNode    = null;
                        // search the babylon scene id map for the babylon node that matches this id
                        if (babylonNode != null)
                        {
                            // search our babylon->gltf node mapping to see if this node is included in the exported gltf scene
                            if (!nodeToGltfNodeMap.TryGetValue(babylonNode, out gltfNode))
                            {
                                continue;
                            }

                            bool nodeHasAnimations      = babylonNode.animations != null && babylonNode.animations.Length > 0 && babylonNode.animations[0] != null;
                            bool nodeHasExtraAnimations = babylonNode.extraAnimations != null && babylonNode.extraAnimations.Count > 0 && babylonNode.extraAnimations[0] != null;
                            if (!nodeHasAnimations && !nodeHasExtraAnimations)
                            {
                                continue;
                            }

                            if (nodeHasAnimations && babylonNode.animations[0].property == "_matrix") //TODO: Is this check accurate for deciphering between bones and nodes?
                            {
                                ExportBoneAnimation(gltfAnimation, startFrame, endFrame, gltf, babylonNode, gltfNode, animGroup);
                            }
                            else
                            {
                                ExportNodeAnimation(gltfAnimation, startFrame, endFrame, gltf, babylonNode, gltfNode, babylonScene, animGroup);
                            }
                        }
                        else
                        {
                            // if the node isn't found in the scene id map, check if it is the id for a morph target
                            BabylonMorphTargetManager morphTargetManager = babylonScene.morphTargetManagers.FirstOrDefault(mtm => mtm.targets.Any(target => target.animations != null && target.animations.Length > 0 && target.animations[0] != null));
                            if (morphTargetManager != null)
                            {
                                BabylonMesh mesh = morphTargetManager.sourceMesh;
                                if (mesh != null && nodeToGltfNodeMap.TryGetValue(mesh, out gltfNode))
                                {
                                    ExportMorphTargetWeightAnimation(morphTargetManager, gltf, gltfNode, gltfAnimation.ChannelList, gltfAnimation.SamplerList, startFrame, endFrame, babylonScene);
                                }
                            }
                        }
                    }

                    if (gltfAnimation.ChannelList.Count > 0)
                    {
                        gltf.AnimationsList.Add(gltfAnimation);
                    }
                    else
                    {
                        logger.RaiseMessage("No data exported for this animation, it is ignored.", 2);
                    }
                    // clear the exported morph target cache, since we are exporting a new animation group. //TODO: we should probably do this more elegantly.
                    exportedMorphTargets.Clear();
                }
            }
        }
Example #24
0
        private GLTFSkin ExportSkin(BabylonSkeleton babylonSkeleton, GLTF gltf, GLTFNode gltfNode)
        {
            RaiseMessage("GLTFExporter.Skin | Export skin of node '" + gltfNode.name + "' based on skeleton '" + babylonSkeleton.name + "'", 2);

            // Retreive gltf skeleton data if babylon skeleton has already been exported
            if (!alreadyExportedSkeletons.ContainsKey(babylonSkeleton))
            {
                alreadyExportedSkeletons.Add(babylonSkeleton, new BabylonSkeletonExportData());

                // Switch coordinate system at object level
                foreach (var babylonBone in babylonSkeleton.bones)
                {
                    var boneLocalMatrix = new BabylonMatrix();
                    boneLocalMatrix.m = babylonBone.matrix;

                    var translationBabylon  = new BabylonVector3();
                    var rotationQuatBabylon = new BabylonQuaternion();
                    var scale = new BabylonVector3();
                    boneLocalMatrix.decompose(scale, rotationQuatBabylon, translationBabylon);
                    translationBabylon    *= scaleFactor;
                    translationBabylon.Z  *= -1;
                    rotationQuatBabylon.X *= -1;
                    rotationQuatBabylon.Y *= -1;
                    boneLocalMatrix        = BabylonMatrix.Compose(scale, rotationQuatBabylon, translationBabylon);

                    babylonBone.matrix = boneLocalMatrix.m;
                }
            }
            var babylonSkeletonExportData = alreadyExportedSkeletons[babylonSkeleton];

            // Skin
            var      nameSuffix = babylonSkeletonExportData.nb != 0 ? "_" + babylonSkeletonExportData.nb : "";
            GLTFSkin gltfSkin   = new GLTFSkin
            {
                name = babylonSkeleton.name + nameSuffix
            };

            gltfSkin.index = gltf.SkinsList.Count;
            gltf.SkinsList.Add(gltfSkin);
            babylonSkeletonExportData.nb++;

            var bones = new List <BabylonBone>(babylonSkeleton.bones);

            // Compute and store world matrix of each bone
            var bonesWorldMatrices = new Dictionary <int, BabylonMatrix>();

            foreach (var babylonBone in babylonSkeleton.bones)
            {
                if (!bonesWorldMatrices.ContainsKey(babylonBone.index))
                {
                    BabylonMatrix boneWorldMatrix = _getBoneWorldMatrix(babylonBone, bones);
                    bonesWorldMatrices.Add(babylonBone.index, boneWorldMatrix);
                }
            }

            // Buffer
            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

            // Accessor - InverseBindMatrices
            var accessorInverseBindMatrices = GLTFBufferService.Instance.CreateAccessor(
                gltf,
                GLTFBufferService.Instance.GetBufferViewFloatMat4(gltf, buffer),
                "accessorInverseBindMatrices",
                GLTFAccessor.ComponentType.FLOAT,
                GLTFAccessor.TypeEnum.MAT4
                );

            gltfSkin.inverseBindMatrices = accessorInverseBindMatrices.index;

            // World matrix of the node
            var nodeWorldMatrix = _getNodeWorldMatrix(gltfNode);

            var gltfJoints = new List <int>();

            foreach (var babylonBone in babylonSkeleton.bones)
            {
                GLTFNode gltfBoneNode = null;
                if (!babylonSkeletonExportData.nodeByBone.ContainsKey(babylonBone))
                {
                    // Export bone as a new node
                    gltfBoneNode = _exportBone(babylonBone, gltf, babylonSkeleton, bones);
                    babylonSkeletonExportData.nodeByBone.Add(babylonBone, gltfBoneNode);
                }
                gltfBoneNode = babylonSkeletonExportData.nodeByBone[babylonBone];

                gltfJoints.Add(gltfBoneNode.index);

                // Set this bone as skeleton if it is a root
                // Meaning of 'skeleton' here is the top root bone
                if (babylonBone.parentBoneIndex == -1)
                {
                    gltfSkin.skeleton = gltfBoneNode.index;
                }

                // Compute inverseBindMatrice for this bone when attached to this node
                var boneLocalMatrix = new BabylonMatrix();
                boneLocalMatrix.m = babylonBone.matrix;
                //printMatrix("boneLocalMatrix[" + babylonBone.name + "]", boneLocalMatrix);

                BabylonMatrix boneWorldMatrix = null;
                if (babylonBone.parentBoneIndex == -1)
                {
                    boneWorldMatrix = boneLocalMatrix;
                }
                else
                {
                    var parentWorldMatrix = bonesWorldMatrices[babylonBone.parentBoneIndex];
                    // Remove scale of parent
                    // This actually enable to take into account the scale of the bones, except for the root one
                    parentWorldMatrix = _removeScale(parentWorldMatrix);

                    boneWorldMatrix = boneLocalMatrix * parentWorldMatrix;
                }
                //printMatrix("boneWorldMatrix[" + babylonBone.name + "]", boneWorldMatrix);

                var inverseBindMatrices = nodeWorldMatrix * BabylonMatrix.Invert(boneWorldMatrix);

                // Populate accessor
                List <float> matrix = new List <float>(inverseBindMatrices.m);
                matrix.ForEach(n => accessorInverseBindMatrices.bytesList.AddRange(BitConverter.GetBytes(n)));
                accessorInverseBindMatrices.count++;
            }
            gltfSkin.joints = gltfJoints.ToArray();

            return(gltfSkin);
        }
Example #25
0
        private void ExportBoneAnimation(GLTFAnimation gltfAnimation, int startFrame, int endFrame, GLTF gltf, BabylonNode babylonNode, GLTFNode gltfNode, BabylonAnimationGroup animationGroup = null)
        {
            var channelList = gltfAnimation.ChannelList;
            var samplerList = gltfAnimation.SamplerList;

            if (babylonNode.animations != null && babylonNode.animations[0].property == "_matrix")
            {
                logger.RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonNode.name, 2);

                BabylonAnimation babylonAnimation = null;
                if (animationGroup != null)
                {
                    var targetedAnimation = animationGroup.targetedAnimations.FirstOrDefault(animation => animation.targetId == babylonNode.id);
                    if (targetedAnimation != null)
                    {
                        babylonAnimation = targetedAnimation.animation;
                    }
                }

                // otherwise fall back to the full animation track on the node.
                if (babylonAnimation == null)
                {
                    babylonAnimation = babylonNode.animations[0];
                }

                var babylonAnimationKeysInRange = babylonAnimation.keys.Where(key => key.frame >= startFrame && key.frame <= endFrame);
                if (babylonAnimationKeysInRange.Count() <= 0)
                {
                    return;
                }

                // --- Input ---
                var accessorInput = _createAndPopulateInput(gltf, babylonAnimation, startFrame, endFrame);
                if (accessorInput == null)
                {
                    return;
                }

                // --- Output ---
                var paths = new string[] { "translation", "rotation", "scale" };
                var accessorOutputByPath = new Dictionary <string, GLTFAccessor>();

                foreach (string path in paths)
                {
                    GLTFAccessor accessorOutput = _createAccessorOfPath(path, gltf);
                    accessorOutputByPath.Add(path, accessorOutput);
                }

                // Populate accessors
                foreach (var babylonAnimationKey in babylonAnimationKeysInRange)
                {
                    var matrix = new BabylonMatrix();
                    matrix.m = babylonAnimationKey.values;

                    var translationBabylon  = new BabylonVector3();
                    var rotationQuatBabylon = new BabylonQuaternion();
                    var scaleBabylon        = new BabylonVector3();
                    matrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);

                    // Switch coordinate system at object level
                    translationBabylon.Z  *= -1;
                    rotationQuatBabylon.X *= -1;
                    rotationQuatBabylon.Y *= -1;

                    var outputValuesByPath = new Dictionary <string, float[]>();
                    outputValuesByPath.Add("translation", translationBabylon.ToArray());
                    outputValuesByPath.Add("rotation", rotationQuatBabylon.ToArray());
                    outputValuesByPath.Add("scale", scaleBabylon.ToArray());

                    // Store values as bytes
                    foreach (string path in paths)
                    {
                        var accessorOutput = accessorOutputByPath[path];
                        var outputValues   = outputValuesByPath[path];

                        foreach (var outputValue in outputValues)
                        {
                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                        }
                        accessorOutput.count++;
                    }
                }
                ;

                foreach (string path in paths)
                {
                    var accessorOutput = accessorOutputByPath[path];

                    // Animation sampler
                    var gltfAnimationSampler = new GLTFAnimationSampler
                    {
                        input  = accessorInput.index,
                        output = accessorOutput.index
                    };
                    gltfAnimationSampler.index = samplerList.Count;
                    samplerList.Add(gltfAnimationSampler);

                    // Target
                    var gltfTarget = new GLTFChannelTarget
                    {
                        node = gltfNode.index
                    };
                    gltfTarget.path = path;

                    // Channel
                    var gltfChannel = new GLTFChannel
                    {
                        sampler = gltfAnimationSampler.index,
                        target  = gltfTarget
                    };
                    channelList.Add(gltfChannel);
                }
            }

            ExportGLTFExtension(babylonNode, ref gltfAnimation, gltf);
        }
        private void ExportAnimationGroups(GLTF gltf, BabylonScene babylonScene)
        {
            // Retreive and parse animation group data
            var animationGroupList  = babylonScene.animationGroups;
            var animationGroupCount = animationGroupList == null ? 0 : animationGroupList.Count;

            gltf.AnimationsList.Clear();
            gltf.AnimationsList.Capacity = Math.Max(gltf.AnimationsList.Capacity, animationGroupCount);

            if (animationGroupCount <= 0)
            {
                logger.RaiseMessage("GLTFExporter.Animation | No AnimationGroups: exporting all animations together.", 1);
                GLTFAnimation gltfAnimation = new GLTFAnimation();
                gltfAnimation.name = "All Animations";

                int minFrame = babylonScene.TimelineStartFrame;
                int maxFrame = babylonScene.TimelineEndFrame;

                foreach (var pair in nodeToGltfNodeMap)
                {
                    ExportNodeAnimation(gltfAnimation, minFrame, maxFrame, gltf, pair.Key, pair.Value, babylonScene);
                }
                foreach (var pair in boneToGltfNodeMap)
                {
                    ExportBoneAnimation(gltfAnimation, minFrame, maxFrame, gltf, pair.Key, pair.Value);
                }

                if (gltfAnimation.ChannelList.Count > 0)
                {
                    gltf.AnimationsList.Add(gltfAnimation);
                }
                else
                {
                    logger.RaiseMessage("GLTFExporter.Animation | No animation data for this animation, it is ignored.", 2);
                }
            }
            else
            {
                foreach (BabylonAnimationGroup animGroup in animationGroupList)
                {
                    logger.RaiseMessage("GLTFExporter.Animation | " + animGroup.name, 1);

                    GLTFAnimation gltfAnimation = new GLTFAnimation();
                    gltfAnimation.name = animGroup.name;

                    int startFrame = MathUtilities.RoundToInt(animGroup.from);
                    int endFrame   = MathUtilities.RoundToInt(animGroup.to);

                    var uniqueNodeIds = animGroup.targetedAnimations.Select(targetAnim => targetAnim.targetId).Distinct();
                    foreach (var id in uniqueNodeIds)
                    {
                        BabylonNode babylonNode = babylonNodes.Find(node => node.id.Equals(id));

                        GLTFNode gltfNode;

                        if (babylonNode != null && nodeToGltfNodeMap.TryGetValue(babylonNode, out gltfNode))
                        {
                            ExportNodeAnimation(gltfAnimation, startFrame, endFrame, gltf, babylonNode, gltfNode, babylonScene);
                        }

                        // export all bones that match this id
                        foreach (KeyValuePair <BabylonBone, GLTFNode> pair in boneToGltfNodeMap)
                        {
                            if (pair.Key.id.Equals(id))
                            {
                                ExportBoneAnimation(gltfAnimation, startFrame, endFrame, gltf, pair.Key, pair.Value);
                            }
                        }
                    }

                    if (gltfAnimation.ChannelList.Count > 0)
                    {
                        gltf.AnimationsList.Add(gltfAnimation);
                    }
                    else
                    {
                        logger.RaiseMessage("No data exported for this animation, it is ignored.", 2);
                    }
                }
            }
        }
Example #27
0
        public static GLTFAccessor _createAccessorOfProperty(string path, GLTF gltf)
        {
            var          buffer         = GLTFBufferService.Instance.GetBuffer(gltf);
            GLTFAccessor accessorOutput = null;

            switch (path.ToUpperInvariant())
            {
            case "WIPERANIMSTATE1":
            case "WIPERANIMSTATE2":
            case "WIPERANIMSTATE3":
            case "WIPERANIMSTATE4":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    $"accessorAnimation{path}",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;

            case "BASECOLOR":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
                    "accessorAnimationBaseColor",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC4
                    );
                break;

            case "EMISSIVE":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
                    "accessorAnimationEmissiveColor",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                break;

            case "ROUGHNESS":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    "accessorAnimationRoughnessFactor",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;

            case "METALLIC":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    "accessorAnimationMetallicFactor",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;

            case "FOV":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    "accessorAnimationFOV",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;

            case "UVOFFSETU":
            case "UVOFFSETV":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    "accessorAnimationUVOffset",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;

            case "UVTILINGU":
            case "UVTILINGV":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    "accessorAnimationUVTiling",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;

            case "UVROTATION":
                accessorOutput = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
                    "accessorAnimationUVRotation",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                break;
            }

            return(accessorOutput);
        }
        private void ExportBoneAnimation(GLTFAnimation gltfAnimation, int startFrame, int endFrame, GLTF gltf, BabylonBone babylonBone, GLTFNode gltfNode)
        {
            var channelList = gltfAnimation.ChannelList;
            var samplerList = gltfAnimation.SamplerList;

            if (babylonBone.animation != null && babylonBone.animation.property == "_matrix")
            {
                logger.RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2);

                var babylonAnimation = babylonBone.animation;

                // --- Input ---
                var accessorInput = _createAndPopulateInput(gltf, babylonAnimation, startFrame, endFrame);
                if (accessorInput == null)
                {
                    return;
                }

                // --- Output ---
                var paths = new string[] { "translation", "rotation", "scale" };
                var accessorOutputByPath = new Dictionary <string, GLTFAccessor>();

                foreach (string path in paths)
                {
                    GLTFAccessor accessorOutput = _createAccessorOfPath(path, gltf);
                    accessorOutputByPath.Add(path, accessorOutput);
                }

                // Populate accessors
                foreach (var babylonAnimationKey in babylonAnimation.keys)
                {
                    if (babylonAnimationKey.frame < startFrame)
                    {
                        continue;
                    }

                    if (babylonAnimationKey.frame > endFrame)
                    {
                        continue;
                    }

                    var matrix = new BabylonMatrix();
                    matrix.m = babylonAnimationKey.values;

                    var translationBabylon  = new BabylonVector3();
                    var rotationQuatBabylon = new BabylonQuaternion();
                    var scaleBabylon        = new BabylonVector3();
                    matrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);

                    // Switch coordinate system at object level
                    translationBabylon.Z  *= -1;
                    translationBabylon    *= exportParameters.scaleFactor;
                    rotationQuatBabylon.X *= -1;
                    rotationQuatBabylon.Y *= -1;

                    var outputValuesByPath = new Dictionary <string, float[]>();
                    outputValuesByPath.Add("translation", translationBabylon.ToArray());
                    outputValuesByPath.Add("rotation", rotationQuatBabylon.ToArray());
                    outputValuesByPath.Add("scale", scaleBabylon.ToArray());

                    // Store values as bytes
                    foreach (string path in paths)
                    {
                        var accessorOutput = accessorOutputByPath[path];
                        var outputValues   = outputValuesByPath[path];

                        foreach (var outputValue in outputValues)
                        {
                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                        }
                        accessorOutput.count++;
                    }
                }
                ;

                foreach (string path in paths)
                {
                    var accessorOutput = accessorOutputByPath[path];

                    // Animation sampler
                    var gltfAnimationSampler = new GLTFAnimationSampler
                    {
                        input  = accessorInput.index,
                        output = accessorOutput.index
                    };
                    gltfAnimationSampler.index = samplerList.Count;
                    samplerList.Add(gltfAnimationSampler);

                    // Target
                    var gltfTarget = new GLTFChannelTarget
                    {
                        node = gltfNode.index
                    };
                    gltfTarget.path = path;

                    // Channel
                    var gltfChannel = new GLTFChannel
                    {
                        sampler = gltfAnimationSampler.index,
                        target  = gltfTarget
                    };
                    channelList.Add(gltfChannel);
                }
            }
        }
        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
        {
            RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);

            // --------------------------
            // --- Mesh from babylon ----
            // --------------------------

            if (babylonMesh.positions == null || babylonMesh.positions.Length == 0)
            {
                RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2);
                return(null);
            }

            RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
            // Retreive general data from babylon mesh
            int  nbVertices    = babylonMesh.positions.Length / 3;
            bool hasUV         = babylonMesh.uvs != null && babylonMesh.uvs.Length > 0;
            bool hasUV2        = babylonMesh.uvs2 != null && babylonMesh.uvs2.Length > 0;
            bool hasColor      = babylonMesh.colors != null && babylonMesh.colors.Length > 0;
            bool hasBones      = babylonMesh.matricesIndices != null && babylonMesh.matricesIndices.Length > 0;
            bool hasBonesExtra = babylonMesh.matricesIndicesExtra != null && babylonMesh.matricesIndicesExtra.Length > 0;
            bool hasTangents   = babylonMesh.tangents != null && babylonMesh.tangents.Length > 0;

            RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3);
            RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3);
            RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3);
            RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3);
            RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3);
            RaiseMessage("GLTFExporter.Mesh | hasBonesExtra=" + hasBonesExtra, 3);

            // Retreive vertices data from babylon mesh
            List <GLTFGlobalVertex> globalVertices = new List <GLTFGlobalVertex>();

            for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++)
            {
                GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
                globalVertex.Position = BabylonVector3.FromArray(babylonMesh.positions, indexVertex);
                globalVertex.Normal   = BabylonVector3.FromArray(babylonMesh.normals, indexVertex);

                if (hasTangents)
                {
                    globalVertex.Tangent = BabylonQuaternion.FromArray(babylonMesh.tangents, indexVertex);

                    // Switch coordinate system at object level
                    globalVertex.Tangent.Z *= -1;

                    // Invert W to switch to right handed system
                    globalVertex.Tangent.W *= -1;
                }

                // Switch coordinate system at object level
                globalVertex.Position.Z *= -1;
                globalVertex.Normal.Z   *= -1;

                globalVertex.Position *= scaleFactor;

                if (hasUV)
                {
                    globalVertex.UV = BabylonVector2.FromArray(babylonMesh.uvs, indexVertex);
                    // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
                    // While for Babylon, it corresponds to the lower left corner of a texture image
                    globalVertex.UV.Y = 1 - globalVertex.UV.Y;
                }
                if (hasUV2)
                {
                    globalVertex.UV2 = BabylonVector2.FromArray(babylonMesh.uvs2, indexVertex);
                    // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
                    // While for Babylon, it corresponds to the lower left corner of a texture image
                    globalVertex.UV2.Y = 1 - globalVertex.UV2.Y;
                }
                if (hasColor)
                {
                    globalVertex.Color = Tools.SubArrayFromEntity(babylonMesh.colors, indexVertex, 4);
                }
                if (hasBones)
                {
                    // In babylon, the 4 bones indices are stored in a single int
                    // Each bone index is 8-bit offset from the next
                    int bonesIndicesMerged = babylonMesh.matricesIndices[indexVertex];
                    int bone3 = bonesIndicesMerged >> 24;
                    bonesIndicesMerged -= bone3 << 24;
                    int bone2 = bonesIndicesMerged >> 16;
                    bonesIndicesMerged -= bone2 << 16;
                    int bone1 = bonesIndicesMerged >> 8;
                    bonesIndicesMerged -= bone1 << 8;
                    int bone0 = bonesIndicesMerged >> 0;
                    bonesIndicesMerged -= bone0 << 0;
                    var bonesIndicesArray = new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 };
                    globalVertex.BonesIndices = bonesIndicesArray;
                    globalVertex.BonesWeights = Tools.SubArrayFromEntity(babylonMesh.matricesWeights, indexVertex, 4);
                }

                globalVertices.Add(globalVertex);
            }

            var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);

            // Retreive indices from babylon mesh
            List <int> babylonIndices = babylonMesh.indices.ToList();

            // --------------------------
            // ------- Init glTF --------
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | Init glTF", 2);
            // Mesh
            var gltfMesh = new GLTFMesh {
                name = babylonMesh.name
            };

            gltfMesh.index = gltf.MeshesList.Count;
            gltf.MeshesList.Add(gltfMesh);
            gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
            if (hasBones)
            {
                gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId;
            }

            // --------------------------
            // ---- glTF primitives -----
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
            var meshPrimitives = new List <GLTFMeshPrimitive>();

            foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
            {
                // --------------------------
                // ------ SubMesh data ------
                // --------------------------

                List <GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount);

                var gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
                // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
                // Thus, the gltf indices list is a concatenation of sub lists all 0-based
                // Example for 2 triangles, each being a submesh:
                //      babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
                var minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart
                for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++)
                {
                    gltfIndices[indexIndice] -= minIndiceValue;
                }

                // --------------------------
                // ----- Mesh primitive -----
                // --------------------------

                // MeshPrimitive
                var meshPrimitive = new GLTFMeshPrimitive
                {
                    attributes = new Dictionary <string, int>()
                };
                meshPrimitives.Add(meshPrimitive);

                // Material
                if (babylonMesh.materialId != null)
                {
                    RaiseMessage("GLTFExporter.Mesh | Material", 3);
                    // Retreive the babylon material
                    BabylonMaterial babylonMaterial;
                    var             babylonMaterialId = babylonMesh.materialId;
                    // From multi materials first, if any
                    // Loop recursively even though it shouldn't be a real use case
                    var babylonMultiMaterials = new List <BabylonMultiMaterial>(babylonScene.multiMaterials);
                    BabylonMultiMaterial babylonMultiMaterial;
                    do
                    {
                        babylonMultiMaterial = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMaterialId);
                        if (babylonMultiMaterial != null)
                        {
                            babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex];
                        }
                    }while (babylonMultiMaterial != null);
                    // Then from materials
                    var babylonMaterials = new List <BabylonMaterial>(babylonScene.materials);
                    babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);

                    // If babylon material was exported successfully
                    if (babylonMaterial != null)
                    {
                        // Update primitive material index
                        var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial);
                        if (indexMaterial == -1)
                        {
                            // Store material for exportation
                            indexMaterial = babylonMaterialsToExport.Count;
                            babylonMaterialsToExport.Add(babylonMaterial);
                        }
                        meshPrimitive.material = indexMaterial;
                    }

                    // TODO - Add and retreive info from babylon material
                    meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES;
                }

                // --------------------------
                // ------- Accessors --------
                // --------------------------

                RaiseMessage("GLTFExporter.Mesh | Geometry", 3);

                // Buffer
                var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

                // --- Indices ---
                var componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT;
                if (nbVertices >= 65536)
                {
                    componentType = GLTFAccessor.ComponentType.UNSIGNED_INT;
                }
                var accessorIndices = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer),
                    "accessorIndices",
                    componentType,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                meshPrimitive.indices = accessorIndices.index;
                // Populate accessor
                if (componentType == GLTFAccessor.ComponentType.UNSIGNED_INT)
                {
                    gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                }
                else
                {
                    var gltfIndicesShort = gltfIndices.ConvertAll(new Converter <int, ushort>(n => (ushort)n));
                    gltfIndicesShort.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                }
                accessorIndices.count = gltfIndices.Count;

                // --- Positions ---
                var accessorPositions = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                    "accessorPositions",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
                // Populate accessor
                accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
                accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                globalVerticesSubMesh.ForEach((globalVertex) =>
                {
                    var positions = globalVertex.Position.ToArray();
                    // Store values as bytes
                    foreach (var position in positions)
                    {
                        accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position));
                    }
                    // Update min and max values
                    GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions);
                });
                accessorPositions.count = globalVerticesSubMesh.Count;

                // --- Tangents ---
                if (hasTangents)
                {
                    var accessorTangents = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorTangents",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTangents.index);
                    // Populate accessor
                    List <float> tangents = globalVerticesSubMesh.SelectMany(v => v.Tangent.ToArray()).ToList();
                    tangents.ForEach(n => accessorTangents.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorTangents.count = globalVerticesSubMesh.Count;
                }

                // --- Normals ---
                var accessorNormals = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                    "accessorNormals",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
                // Populate accessor
                List <float> normals = globalVerticesSubMesh.SelectMany(v => v.Normal.ToArray()).ToList();
                normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
                accessorNormals.count = globalVerticesSubMesh.Count;

                // --- Colors ---
                if (hasColor)
                {
                    var accessorColors = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorColors",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
                    // Populate accessor
                    List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
                    colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorColors.count = globalVerticesSubMesh.Count;
                }

                // --- UV ---
                if (hasUV)
                {
                    var accessorUVs = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                        "accessorUVs",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC2
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
                    // Populate accessor
                    List <float> uvs = globalVerticesSubMesh.SelectMany(v => v.UV.ToArray()).ToList();
                    uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorUVs.count = globalVerticesSubMesh.Count;
                }

                // --- UV2 ---
                if (hasUV2)
                {
                    var accessorUV2s = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                        "accessorUV2s",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC2
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
                    // Populate accessor
                    List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => v.UV2.ToArray()).ToList();
                    uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorUV2s.count = globalVerticesSubMesh.Count;
                }

                // --- Bones ---
                if (hasBones)
                {
                    RaiseMessage("GLTFExporter.Mesh | Bones", 3);
                    // --- Joints ---
                    var accessorJoints = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer),
                        "accessorJoints",
                        GLTFAccessor.ComponentType.UNSIGNED_SHORT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index);
                    // Populate accessor
                    List <ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList();
                    joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorJoints.count = globalVerticesSubMesh.Count;

                    // --- Weights ---
                    var accessorWeights = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorWeights",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index);
                    // Populate accessor
                    List <float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList();
                    weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorWeights.count = globalVerticesSubMesh.Count;
                }

                // Morph targets positions and normals
                if (babylonMorphTargetManager != null)
                {
                    RaiseMessage("GLTFExporter.Mesh | Morph targets", 3);
                    _exportMorphTargets(babylonMesh, babylonSubMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive);
                }
            }
            gltfMesh.primitives = meshPrimitives.ToArray();

            // Morph targets weights
            if (babylonMorphTargetManager != null)
            {
                var weights = new List <float>();
                foreach (BabylonMorphTarget babylonMorphTarget in babylonMorphTargetManager.targets)
                {
                    weights.Add(babylonMorphTarget.influence);
                }
                gltfMesh.weights = weights.ToArray();
            }

            return(gltfMesh);
        }
        private void ExportAnimationGroups(GLTF gltf, BabylonScene babylonScene)
        {
            // Retreive and parse animation group data
            AnimationGroupList animationList = InitAnimationGroups();

            gltf.AnimationsList.Clear();
            gltf.AnimationsList.Capacity = Math.Max(gltf.AnimationsList.Capacity, animationList.Count);

            if (animationList.Count <= 0)
            {
                RaiseMessage("GLTFExporter.Animation | No AnimationGroups: exporting all animations together.", 1);
                GLTFAnimation gltfAnimation = new GLTFAnimation();
                gltfAnimation.name = "All Animations";

                int minFrame = Loader.Core.AnimRange.Start / Loader.Global.TicksPerFrame;
                int maxFrame = Loader.Core.AnimRange.End / Loader.Global.TicksPerFrame;

                foreach (var pair in nodeToGltfNodeMap)
                {
                    ExportNodeAnimation(gltfAnimation, minFrame, maxFrame, gltf, pair.Key, pair.Value, babylonScene);
                }
                foreach (var pair in boneToGltfNodeMap)
                {
                    ExportBoneAnimation(gltfAnimation, minFrame, maxFrame, gltf, pair.Key, pair.Value);
                }

                if (gltfAnimation.ChannelList.Count > 0)
                {
                    gltf.AnimationsList.Add(gltfAnimation);
                }
                else
                {
                    RaiseMessage("GLTFExporter.Animation | No animation data for this animation, it is ignored.", 2);
                }
            }
            else
            {
                foreach (AnimationGroup animGroup in animationList)
                {
                    RaiseMessage("GLTFExporter.Animation | " + animGroup.Name, 1);

                    GLTFAnimation gltfAnimation = new GLTFAnimation();
                    gltfAnimation.name = animGroup.Name;

                    int startFrame = animGroup.FrameStart;
                    int endFrame   = animGroup.FrameEnd;

                    foreach (uint nodeHandle in animGroup.NodeHandles)
                    {
                        // todo: make something a little more efficient..
                        IINode maxNode = Loader.Core.RootNode.FindChildNode(nodeHandle);

                        // node could have been deleted, silently ignore it
                        if (maxNode == null)
                        {
                            continue;
                        }

                        string      id          = maxNode.GetGuid().ToString();
                        BabylonNode babylonNode = babylonNodes.Find(node => node.id.Equals(id));

                        GLTFNode gltfNode;

                        if (babylonNode != null && nodeToGltfNodeMap.TryGetValue(babylonNode, out gltfNode))
                        {
                            ExportNodeAnimation(gltfAnimation, startFrame, endFrame, gltf, babylonNode, gltfNode, babylonScene);
                        }

                        // export all bones that match this id
                        foreach (KeyValuePair <BabylonBone, GLTFNode> pair in boneToGltfNodeMap)
                        {
                            if (pair.Key.id.Equals(id))
                            {
                                ExportBoneAnimation(gltfAnimation, startFrame, endFrame, gltf, pair.Key, pair.Value);
                            }
                        }
                    }

                    if (gltfAnimation.ChannelList.Count > 0)
                    {
                        gltf.AnimationsList.Add(gltfAnimation);
                    }
                    else
                    {
                        RaiseMessage("No data exported for this animation, it is ignored.", 2);
                    }
                }
            }
        }