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); } }
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); }
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); }
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)); }
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); }
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); } }
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[]
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); } } }
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); }
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); } }
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); }
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(); } } }
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); }
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); } } } }
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); } } } }