public void GeneratePositionAnimation(IIGameNode gameNode, List <BabylonAnimation> animations) { if (isPositionAnimated(gameNode)) { ExportVector3Animation("position", animations, key => { var localMatrix = gameNode.GetLocalTM(key); if (float.IsNaN(localMatrix.Determinant)) { RaiseError($"Determinant of {gameNode.Name} of position animation at {key} localMatrix is NaN "); } var tm_babylon = new BabylonMatrix(); tm_babylon.m = localMatrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); // Apply unit conversion factor to meter t_babylon *= scaleFactorToMeters; return(new[] { t_babylon.X, t_babylon.Y, t_babylon.Z }); }); } }
private void exportTransform(BabylonAbstractMesh babylonAbstractMesh, IIGameNode maxGameNode) { // Position / rotation / scaling var localTM = maxGameNode.GetLocalTM(0); // use babylon decomposition, as 3ds max built-in values are no correct var tm_babylon = new BabylonMatrix(); tm_babylon.m = localTM.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); if (ExportQuaternionsInsteadOfEulers) { babylonAbstractMesh.rotationQuaternion = q_babylon.ToArray(); } else { babylonAbstractMesh.rotation = q_babylon.toEulerAngles().ToArray(); } // normalize quaternion var q = q_babylon; float q_length = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W); babylonAbstractMesh.rotationQuaternion = new[] { q_babylon.X / q_length, q_babylon.Y / q_length, q_babylon.Z / q_length, q_babylon.W / q_length }; babylonAbstractMesh.scaling = new[] { s_babylon.X, s_babylon.Y, s_babylon.Z }; babylonAbstractMesh.position = new[] { t_babylon.X, t_babylon.Y, t_babylon.Z }; }
public void GenerateRotationAnimation(IIGameNode gameNode, List <BabylonAnimation> animations, bool force = false) { if (gameNode.IGameControl.IsAnimated(IGameControlType.Rot) || gameNode.IGameControl.IsAnimated(IGameControlType.EulerX) || gameNode.IGameControl.IsAnimated(IGameControlType.EulerY) || gameNode.IGameControl.IsAnimated(IGameControlType.EulerZ) || force) { ExportQuaternionAnimation("rotationQuaternion", animations, key => { var localMatrix = gameNode.GetLocalTM(key); var tm_babylon = new BabylonMatrix(); tm_babylon.m = localMatrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); // normalize var q = q_babylon; float q_length = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W); return(new[] { q_babylon.X / q_length, q_babylon.Y / q_length, q_babylon.Z / q_length, q_babylon.W / q_length }); }); } }
public void GenerateScalingAnimation(IIGameNode gameNode, List <BabylonAnimation> animations) { if (gameNode.IGameControl.IsAnimated(IGameControlType.Scale)) { ExportVector3Animation("scaling", animations, key => { var localMatrix = gameNode.GetLocalTM(key); if (float.IsNaN(localMatrix.Determinant)) { RaiseError($"Determinant of {gameNode.Name} of scale animation at {key} localMatrix is NaN "); } var tm_babylon = new BabylonMatrix(); tm_babylon.m = localMatrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); return(new[] { s_babylon.X, s_babylon.Y, s_babylon.Z }); }); } }
private void exportTransform(BabylonAbstractMesh babylonAbstractMesh, IIGameNode maxGameNode) { // Position / rotation / scaling var localTM = GetLocalTM(maxGameNode, 0); var meshTrans = localTM.Translation; var meshRotation = localTM.Rotation; var meshScale = localTM.Scaling; babylonAbstractMesh.position = new[] { meshTrans.X, meshTrans.Y, meshTrans.Z }; var rotationQuaternion = new BabylonQuaternion { X = meshRotation.X, Y = meshRotation.Y, Z = meshRotation.Z, W = -meshRotation.W }; if (ExportQuaternionsInsteadOfEulers) { babylonAbstractMesh.rotationQuaternion = rotationQuaternion.ToArray(); } else { babylonAbstractMesh.rotation = rotationQuaternion.toEulerAngles().ToArray(); } babylonAbstractMesh.scaling = new[] { meshScale.X, meshScale.Y, meshScale.Z }; }
private BabylonNode BoneToNode(BabylonBone babylonBone) { BabylonNode babylonNode = new BabylonNode(); babylonNode.id = babylonBone.id; babylonNode.parentId = babylonBone.parentNodeId; babylonNode.name = babylonBone.name; babylonNode.animations = new[] { babylonBone.animation }; var tm_babylon = new BabylonMatrix(); tm_babylon.m = babylonBone.matrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); babylonNode.position = t_babylon.ToArray(); babylonNode.rotationQuaternion = q_babylon.ToArray(); babylonNode.scaling = s_babylon.ToArray(); return(babylonNode); }
public void GenerateRotationAnimation(IIGameNode gameNode, List <BabylonAnimation> animations, bool force = false) { if (gameNode.IGameControl.IsAnimated(IGameControlType.Rot) || gameNode.IGameControl.IsAnimated(IGameControlType.EulerX) || gameNode.IGameControl.IsAnimated(IGameControlType.EulerY) || gameNode.IGameControl.IsAnimated(IGameControlType.EulerZ) || (gameNode.IGameObject.IGameType == Autodesk.Max.IGameObject.ObjectTypes.Light && gameNode.IGameObject.AsGameLight().LightTarget != null) || // Light with target are indirectly animated by their target force) { ExportQuaternionAnimation("rotationQuaternion", animations, key => { var localMatrix = gameNode.GetLocalTM(key); var tm_babylon = new BabylonMatrix(); tm_babylon.m = localMatrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); // normalize var q = q_babylon; float q_length = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W); return(new[] { q_babylon.X / q_length, q_babylon.Y / q_length, q_babylon.Z / q_length, q_babylon.W / q_length }); }); } }
public void GenerateRotationAnimation(IIGameNode gameNode, List <BabylonAnimation> animations, bool force = false) { if (isRotationAnimated(gameNode) || force) { ExportQuaternionAnimation("rotationQuaternion", animations, key => { var localMatrix = gameNode.GetLocalTM(key); if (float.IsNaN(localMatrix.Determinant)) { RaiseError($"Determinant of {gameNode.Name} of rotation animation at {key} localMatrix is NaN "); } var tm_babylon = new BabylonMatrix(); tm_babylon.m = localMatrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); // normalize var q = q_babylon; float q_length = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W); return(new[] { q_babylon.X / q_length, q_babylon.Y / q_length, q_babylon.Z / q_length, q_babylon.W / q_length }); }); } }
private void exportTransform(BabylonAbstractMesh babylonAbstractMesh, IIGameNode maxGameNode) { // Position / rotation / scaling var localTM = maxGameNode.GetObjectTM(0); if (maxGameNode.NodeParent != null) { var parentWorld = maxGameNode.NodeParent.GetObjectTM(0); localTM.MultiplyBy(parentWorld.Inverse); } var meshTrans = localTM.Translation; var meshRotation = localTM.Rotation; var meshScale = localTM.Scaling; babylonAbstractMesh.position = new[] { meshTrans.X, meshTrans.Y, meshTrans.Z }; var rotationQuaternion = new BabylonQuaternion { X = meshRotation.X, Y = meshRotation.Y, Z = meshRotation.Z, W = -meshRotation.W }; if (ExportQuaternionsInsteadOfEulers) { babylonAbstractMesh.rotationQuaternion = rotationQuaternion.ToArray(); } else { babylonAbstractMesh.rotation = rotationQuaternion.toEulerAngles().ToArray(); } babylonAbstractMesh.scaling = new[] { meshScale.X, meshScale.Y, meshScale.Z }; }
private float[] FixChildQuaternion(float[] q, double angle) { BabylonQuaternion qFix = new BabylonQuaternion((float)Math.Sin(angle / 2), 0, 0, (float)Math.Cos(angle / 2)); BabylonQuaternion quaternion = new BabylonQuaternion(q[0], q[1], q[2], q[3]); BabylonQuaternion rotationQuaternion = qFix.MultiplyWith(quaternion); return(rotationQuaternion.ToArray()); }
private BabylonQuaternion FixChildQuaternion(BabylonNode node, double angle) { BabylonQuaternion qFix = new BabylonQuaternion((float)Math.Sin(angle / 2), 0, 0, (float)Math.Cos(angle / 2)); BabylonQuaternion quaternion = new BabylonQuaternion(node.rotationQuaternion[0], node.rotationQuaternion[1], node.rotationQuaternion[2], node.rotationQuaternion[3]); BabylonQuaternion rotationQuaternion = qFix.MultiplyWith(quaternion); return(rotationQuaternion); }
private BabylonMatrix _getNodeLocalMatrix(GLTFNode gltfNode) { return(BabylonMatrix.Compose( BabylonVector3.FromArray(gltfNode.scale), BabylonQuaternion.FromArray(gltfNode.rotation), BabylonVector3.FromArray(gltfNode.translation) )); }
private GLTFNode _exportBone(BabylonBone babylonBone, GLTF gltf, BabylonSkeleton babylonSkeleton, List <BabylonBone> bones) { if (alreadyExportedBones.ContainsKey(babylonBone)) { return(alreadyExportedBones[babylonBone]); } // Node var gltfNode = new GLTFNode { name = babylonBone.name }; gltfNode.index = gltf.NodesList.Count; gltf.NodesList.Add(gltfNode); alreadyExportedBones.Add(babylonBone, 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); }
static float dQuat2(BabylonQuaternion q1, BabylonQuaternion q2) { float dX = q2.X - q1.X; float dY = q2.Y - q1.Y; float dZ = q2.Z - q1.Z; float dW = q2.W - q1.W; return(dX * dX + dY * dY + dZ * dZ + dW * dW); }
/// <summary> /// Default space is transform /// Default rotation order is YXZ /// </summary> /// <param name="mTransformationMatrix"></param> /// <returns></returns> public static float[] getRotation(this MTransformationMatrix mTransformationMatrix) { double x = 0, y = 0, z = 0, w = 0; mTransformationMatrix.getRotationQuaternion(ref x, ref y, ref z, ref w); // Maya conversion algorithm is bugged when reaching limits (angle like (-90,89,90)) // Convert quaternion to vector3 using Babylon conversion algorithm BabylonQuaternion babylonQuaternion = new BabylonQuaternion((float)x, (float)y, (float)z, (float)w); return(babylonQuaternion.toEulerAngles().ToArray()); }
private BabylonMatrix _removeScale(BabylonMatrix boneWorldMatrix) { var translation = new BabylonVector3(); var rotation = new BabylonQuaternion(); var scale = new BabylonVector3(); boneWorldMatrix.decompose(scale, rotation, translation); scale.X = 1; scale.Y = 1; scale.Z = 1; return(BabylonMatrix.Compose(scale, rotation, translation)); }
/** * Computes a texture transform matrix with a pre-transformation */ public static BabylonMatrix ComputeTextureTransformMatrix(BabylonVector3 pivotCenter, BabylonVector3 offset, BabylonQuaternion rotation, BabylonVector3 scale) { var dOffset = new BabylonVector3(); var dRotation = new BabylonQuaternion(); var dScale = new BabylonVector3(); offset.X *= scale.X; offset.Y *= scale.Y; offset.Z *= 0; var transformMatrix = BabylonMatrix.Translation(new BabylonVector3(-pivotCenter.X, -pivotCenter.Y, 0)).multiply(BabylonMatrix.Compose(scale, rotation, offset)).multiply(BabylonMatrix.Translation(pivotCenter)); return(transformMatrix); }
private void printMatrix(string name, BabylonMatrix matrix) { // Decompose matrix into TRS var translation = new BabylonVector3(); var rotationQuat = new BabylonQuaternion(); var scale = new BabylonVector3(); matrix.decompose(scale, rotationQuat, translation); var rotation = rotationQuat.toEulerAngles(); rotation *= (float)(180 / Math.PI); var lvl = 3; RaiseWarning(name + ".translation=[" + translation.X + ", " + translation.Y + ", " + translation.Z + "]", lvl); RaiseWarning(name + ".rotation=[" + rotation.X + ", " + rotation.Y + ", " + rotation.Z + "]", lvl); RaiseWarning(name + ".scale=[" + scale.X + ", " + scale.Y + ", " + scale.Z + "]", lvl); }
public void GenerateScalingAnimation(IIGameNode gameNode, List <BabylonAnimation> animations) { if (gameNode.IGameControl.IsAnimated(IGameControlType.Scale)) { ExportVector3Animation("scaling", animations, key => { var localMatrix = gameNode.GetLocalTM(key); var tm_babylon = new BabylonMatrix(); tm_babylon.m = localMatrix.ToArray(); var s_babylon = new BabylonVector3(); var q_babylon = new BabylonQuaternion(); var t_babylon = new BabylonVector3(); tm_babylon.decompose(s_babylon, q_babylon, t_babylon); return(new[] { s_babylon.X, s_babylon.Y, s_babylon.Z }); }); } }
private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene) { logger.RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1); // -------------------------- // --- Mesh from babylon ---- // -------------------------- // get direct access to mesh data or use geometryId to do so IBabylonMeshData meshData = babylonMesh.geometryId == null ? babylonMesh : (IBabylonMeshData)babylonScene.geometries.Get(babylonMesh.geometryId) ?? babylonMesh; if (meshData.positions == null || meshData.positions.Length == 0) { logger.RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2); return(null); } logger.RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2); // Retreive general data from babylon mesh int nbVertices = meshData.positions.Length / 3; bool hasUV = meshData.uvs != null && meshData.uvs.Length > 0; bool hasUV2 = meshData.uvs2 != null && meshData.uvs2.Length > 0; bool hasColor = meshData.colors != null && meshData.colors.Length > 0; bool hasBones = meshData.matricesIndices != null && meshData.matricesIndices.Length > 0; bool hasTangents = meshData.tangents != null && meshData.tangents.Length > 0; bool hasNormals = meshData.normals != null && meshData.normals.Length > 0; bool hasBonesExtra = babylonMesh.matricesIndicesExtra != null && babylonMesh.matricesIndicesExtra.Length > 0; bool hasMetadata = babylonMesh.metadata != null && babylonMesh.metadata.Count > 0; logger.RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasBonesExtra=" + hasBonesExtra, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasMetadata=" + hasMetadata, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasNormals=" + hasNormals, 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(meshData.positions, indexVertex); // Switch coordinate system at object level globalVertex.Position.Z *= -1; globalVertex.Position *= exportParameters.scaleFactor; if (hasNormals) { globalVertex.Normal = BabylonVector3.FromArray(meshData.normals, indexVertex); globalVertex.Normal.Z *= -1; } if (hasTangents) { globalVertex.Tangent = BabylonQuaternion.FromArray(meshData.tangents, indexVertex); // Switch coordinate system at object level globalVertex.Tangent.Z *= -1; // Invert W to switch to right handed system globalVertex.Tangent.W *= -1; } if (hasUV) { globalVertex.UV = BabylonVector2.FromArray(meshData.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(meshData.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 = ArrayExtension.SubArrayFromEntity(meshData.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 ushort[] unpackBabylonBonesToArray(uint babylonBoneIndices) { uint bone3 = babylonBoneIndices >> 24; uint bone2 = (babylonBoneIndices << 8) >> 24; uint bone1 = (babylonBoneIndices << 16) >> 24; uint bone0 = (babylonBoneIndices << 24) >> 24; babylonBoneIndices -= bone0 << 0; return(new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 }); } uint bonesIndicesMerged = (uint)meshData.matricesIndices[indexVertex]; globalVertex.BonesIndices = unpackBabylonBonesToArray(bonesIndicesMerged); globalVertex.BonesWeights = ArrayExtension.SubArrayFromEntity(meshData.matricesWeights, indexVertex, 4); void clearBoneUnusedIndices(ushort[] indices, float[] weights) { for (int i = 0; i < indices.Length; ++i) { // Zero out indices of unused joint weights to avoid ACCESSOR_JOINTS_USED_ZERO_WEIGHT. if (MathUtilities.IsAlmostEqualTo(weights[i], 0, float.Epsilon)) { indices[i] = 0; } } } clearBoneUnusedIndices(globalVertex.BonesIndices, globalVertex.BonesWeights); if (hasBonesExtra) { uint bonesIndicesExtraMerged = (uint)meshData.matricesIndicesExtra[indexVertex]; globalVertex.BonesIndicesExtra = unpackBabylonBonesToArray(bonesIndicesExtraMerged); globalVertex.BonesWeightsExtra = ArrayExtension.SubArrayFromEntity(meshData.matricesWeightsExtra, indexVertex, 4); clearBoneUnusedIndices(globalVertex.BonesIndicesExtra, globalVertex.BonesWeightsExtra); } } globalVertices.Add(globalVertex); } var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh); // Retreive indices from babylon mesh List <int> babylonIndices = meshData.indices.ToList(); // -------------------------- // ------- Init glTF -------- // -------------------------- logger.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 ----- // -------------------------- logger.RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2); List <GLTFMeshPrimitive> meshPrimitives = new List <GLTFMeshPrimitive>(); if (meshData != babylonMesh && primitivesCache.TryGetValue(babylonMesh.geometryId, out List <GLTFMeshPrimitive> result)) { // this is a clone, indexing the same geometry // so we just need to retreive the primitive object and copy attributes and indices meshPrimitives.AddRange(result.Select((p, i) => { var meshPrimitive = new GLTFMeshPrimitive() { indices = p.indices, attributes = p.attributes }; // Material if (babylonMesh.materialId != null) { logger.RaiseMessage("GLTFExporter.Mesh | Material", 3); // according we have a clone, then submeshes are of the same count and order. SetBabylonMaterial(babylonMesh, babylonMesh.subMeshes[i], meshPrimitive); } return(meshPrimitive); })); } else { 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 if (minIndiceValue != 0) { 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) { logger.RaiseMessage("GLTFExporter.Mesh | Material", 3); SetBabylonMaterial(babylonMesh, babylonSubMesh, meshPrimitive); } // -------------------------- // ------- Accessors -------- // -------------------------- logger.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 --- if (hasNormals) { 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) { logger.RaiseMessage("GLTFExporter.Mesh | Bones", 3); // if we've already exported this mesh's skeleton, check if the skins match, // if so then export this mesh primitive to share joint and weight accessors. var matchingSkinnedMesh = alreadyExportedSkinnedMeshes.FirstOrDefault(skinnedMesh => skinnedMesh.skeletonId == babylonMesh.skeletonId); if (matchingSkinnedMesh != null && BabylonMesh.MeshesShareSkin(matchingSkinnedMesh, babylonMesh)) { var tmpGltfMesh = gltf.MeshesList.FirstOrDefault(mesh => matchingSkinnedMesh.name == mesh.name); var tmpGltfMeshPrimitive = tmpGltfMesh.primitives.First(); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.JOINTS_0.ToString()]); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString()]); if (hasBonesExtra) { meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_1.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.JOINTS_1.ToString()]); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString()]); } sharedSkinnedMeshesByOriginal[tmpGltfMesh].Add(gltfMesh); } else { // Create new joint and weight accessors for this mesh's skinning. // --- Joints --- sharedSkinnedMeshesByOriginal[gltfMesh] = new List <GLTFMesh>(); 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; if (hasBonesExtra) { // --- Joints Extra --- var accessorJointsExtra = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer), "accessorJointsExtra", GLTFAccessor.ComponentType.UNSIGNED_SHORT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_1.ToString(), accessorJointsExtra.index); // Populate accessor List <ushort> jointsExtra = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndicesExtra[0], v.BonesIndicesExtra[1], v.BonesIndicesExtra[2], v.BonesIndicesExtra[3] }).ToList(); jointsExtra.ForEach(n => accessorJointsExtra.bytesList.AddRange(BitConverter.GetBytes(n))); accessorJointsExtra.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; if (hasBonesExtra) { // --- Weights Extra --- var accessorWeightsExtra = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorWeightsExtra", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString(), accessorWeightsExtra.index); // Populate accessor List <float> weightBonesExtra = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeightsExtra[0], v.BonesWeightsExtra[1], v.BonesWeightsExtra[2], v.BonesWeightsExtra[3] }).ToList(); weightBonesExtra.ForEach(n => accessorWeightsExtra.bytesList.AddRange(BitConverter.GetBytes(n))); accessorWeightsExtra.count = globalVerticesSubMesh.Count; } } } // Morph targets positions and normals if (babylonMorphTargetManager != null) { logger.RaiseMessage("GLTFExporter.Mesh | Morph targets", 3); _exportMorphTargets(babylonMesh, babylonSubMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive); } } // this is a "master" mesh so save the primitives into dictionary if (babylonMesh.geometryId != null && meshPrimitives != null) { primitivesCache.Add(babylonMesh.geometryId, meshPrimitives); } } gltfMesh.primitives = meshPrimitives.ToArray(); // Morph targets weights and names if (babylonMorphTargetManager != null && babylonMorphTargetManager.targets != null) { var weights = new List <float>(); var targetNames = new List <String>(); foreach (BabylonMorphTarget babylonMorphTarget in babylonMorphTargetManager.targets) { weights.Add(babylonMorphTarget.influence); targetNames.Add(babylonMorphTarget.name); } gltfMesh.weights = weights.ToArray(); if (gltfMesh.extras == null) { gltfMesh.extras = new Dictionary <string, object>(); } gltfMesh.extras["targetNames"] = targetNames.ToArray(); } if (hasBones) { alreadyExportedSkinnedMeshes.Add(babylonMesh); } ExportGLTFExtension(babylonMesh, ref gltfMesh, gltf); return(gltfMesh); }
private GLTFSkin ExportSkin(BabylonSkeleton babylonSkeleton, GLTF gltf, GLTFNode gltfNode, GLTFMesh gltfMesh) { logger.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 *= exportParameters.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 // if this mesh is sharing a skin with another mesh, use the already exported skin var sharedSkinnedMeshesByOriginalPair = sharedSkinnedMeshesByOriginal.Where(skinSharingMeshPair => skinSharingMeshPair.Value.Contains(gltfMesh)).Select(kvp => (KeyValuePair <GLTFMesh, List <GLTFMesh> >?)kvp).FirstOrDefault(); if (sharedSkinnedMeshesByOriginalPair != null) { logger.RaiseMessage("GLTFExporter.Skin | Sharing skinning information from mesh '" + sharedSkinnedMeshesByOriginalPair.Value.Key.name + "'", 3); var skeletonExportData = alreadyExportedSkeletons[babylonSkeleton]; gltfNode.skin = skeletonExportData.skinIndex; return(gltf.SkinsList[(int)gltfNode.skin]); } // otherwise create a new GLTFSkin 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++; babylonSkeletonExportData.skinIndex = gltfSkin.index; 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)) { var nodePair = nodeToGltfNodeMap.First(pair => pair.Key.id.Equals(babylonBone.id)); BabylonMatrix boneWorldMatrix = _getNodeWorldMatrix(nodePair.Value); 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 = nodeToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonBone.id)).Value;//_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(); ExportGLTFExtension(babylonSkeleton, ref gltfNode, gltf); return(gltfSkin); }
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 hasTangents = babylonMesh.tangents != null && babylonMesh.tangents.Length > 0; 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; 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; 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); // Retrieve 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 export 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; // --- 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; // --- 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; } // --- 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; } if (hasBonesExtra) { RaiseWarning("Too many bones influences per vertex. glTF only support up to 4 bones influences per vertex. The result may not be as expected.", 3); } // 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 IStdUVGen _exportUV(IStdUVGen uvGen, BabylonTexture babylonTexture) { switch (uvGen.GetCoordMapping(0)) { case 1: //MAP_SPHERICAL babylonTexture.coordinatesMode = BabylonTexture.CoordinatesMode.SPHERICAL_MODE; break; case 2: //MAP_PLANAR babylonTexture.coordinatesMode = BabylonTexture.CoordinatesMode.PLANAR_MODE; break; default: babylonTexture.coordinatesMode = BabylonTexture.CoordinatesMode.EXPLICIT_MODE; break; } babylonTexture.coordinatesIndex = uvGen.MapChannel - 1; if (uvGen.MapChannel > 2) { RaiseWarning(string.Format("Unsupported map channel, Only channel 1 and 2 are supported."), 3); } babylonTexture.uOffset = uvGen.GetUOffs(0); babylonTexture.vOffset = -uvGen.GetVOffs(0); babylonTexture.uScale = uvGen.GetUScl(0); babylonTexture.vScale = uvGen.GetVScl(0); var offset = new BabylonVector3(babylonTexture.uOffset, -babylonTexture.vOffset, 0); var scale = new BabylonVector3(babylonTexture.uScale, babylonTexture.vScale, 1); var rotationEuler = new BabylonVector3(uvGen.GetUAng(0), uvGen.GetVAng(0), uvGen.GetWAng(0)); var rotation = BabylonQuaternion.FromEulerAngles(rotationEuler.X, rotationEuler.Y, rotationEuler.Z); var pivotCenter = new BabylonVector3(-0.5f, -0.5f, 0); var transformMatrix = MathUtilities.ComputeTextureTransformMatrix(pivotCenter, offset, rotation, scale); transformMatrix.decompose(scale, rotation, offset); var texTransformRotationEuler = rotation.toEulerAngles(); babylonTexture.uOffset = -offset.X; babylonTexture.vOffset = -offset.Y; babylonTexture.uScale = scale.X; babylonTexture.vScale = -scale.Y; babylonTexture.uRotationCenter = 0.0f; babylonTexture.vRotationCenter = 0.0f; babylonTexture.invertY = false; babylonTexture.uAng = texTransformRotationEuler.X; babylonTexture.vAng = texTransformRotationEuler.Y; babylonTexture.wAng = texTransformRotationEuler.Z; if (Path.GetExtension(babylonTexture.name).ToLower() == ".dds") { babylonTexture.vScale *= -1; // Need to invert Y-axis for DDS texture } if (babylonTexture.wAng != 0f && (babylonTexture.uScale != 1f || babylonTexture.vScale != 1f) && (Math.Abs(babylonTexture.uScale) - Math.Abs(babylonTexture.vScale)) > float.Epsilon) { RaiseWarning("Rotation and non-uniform tiling (scale) on a texture is not supported as it will cause texture shearing. You can use the map UV of the mesh for those transformations.", 3); } babylonTexture.wrapU = BabylonTexture.AddressMode.CLAMP_ADDRESSMODE; // CLAMP if ((uvGen.TextureTiling & 1) != 0) // WRAP { babylonTexture.wrapU = BabylonTexture.AddressMode.WRAP_ADDRESSMODE; } else if ((uvGen.TextureTiling & 4) != 0) // MIRROR { babylonTexture.wrapU = BabylonTexture.AddressMode.MIRROR_ADDRESSMODE; } babylonTexture.wrapV = BabylonTexture.AddressMode.CLAMP_ADDRESSMODE; // CLAMP if ((uvGen.TextureTiling & 2) != 0) // WRAP { babylonTexture.wrapV = BabylonTexture.AddressMode.WRAP_ADDRESSMODE; } else if ((uvGen.TextureTiling & 8) != 0) // MIRROR { babylonTexture.wrapV = BabylonTexture.AddressMode.MIRROR_ADDRESSMODE; } return(uvGen); }
private BabylonNode ExportLight(IIGameScene scene, IIGameNode lightNode, BabylonScene babylonScene) { if (IsLightExportable(lightNode) == false) { return(null); } var gameLight = lightNode.IGameObject.AsGameLight(); var initialized = gameLight.InitializeData; var babylonLight = new BabylonLight(); RaiseMessage(lightNode.Name, 1); babylonLight.name = lightNode.Name; // Export the custom attributes of this light babylonLight.metadata = ExportExtraAttributes(lightNode, babylonScene); // To preserve the position/rotation and the hierarchy, we create a dummy that will contains as direct children the light and the light children // The light will have no children. The dummy will contains the position and rotation animations. bool createDummy = lightNode.ChildCount > 0; BabylonNode dummy = null; if (createDummy) { dummy = ExportDummy(scene, lightNode, babylonScene); dummy.name = "_" + dummy.name + "_"; babylonLight.id = Guid.NewGuid().ToString(); babylonLight.parentId = dummy.id; babylonLight.hasDummy = true; } else { babylonLight.id = lightNode.MaxNode.GetGuid().ToString(); if (lightNode.NodeParent != null) { babylonLight.parentId = lightNode.NodeParent.MaxNode.GetGuid().ToString(); } } // Type var maxLight = (lightNode.MaxNode.ObjectRef as ILightObject); var lightState = Loader.Global.LightState.Create(); maxLight.EvalLightState(0, Tools.Forever, lightState); switch (lightState.Type) { case LightType.OmniLgt: babylonLight.type = 0; break; case LightType.SpotLgt: babylonLight.type = 2; babylonLight.angle = (float)(maxLight.GetFallsize(0, Tools.Forever) * Math.PI / 180.0f); babylonLight.exponent = 1; break; case LightType.DirectLgt: babylonLight.type = 1; break; case LightType.AmbientLgt: babylonLight.type = 3; babylonLight.groundColor = new float[] { 0, 0, 0 }; break; } // Shadows if (maxLight.ShadowMethod == 1) { if (lightState.Type == LightType.DirectLgt || lightState.Type == LightType.SpotLgt || lightState.Type == LightType.OmniLgt) { ExportShadowGenerator(lightNode.MaxNode, babylonScene, babylonLight); } else { RaiseWarning("Shadows maps are only supported for point, directional and spot lights", 2); } } // Position / rotation / scaling if (createDummy) { // The position is stored by the dummy parent and the default direction is downward and it is updated by the rotation of the parent dummy babylonLight.position = new[] { 0f, 0f, 0f }; babylonLight.direction = new[] { 0f, -1f, 0f }; } else { exportTransform(babylonLight, lightNode); // Position var localMatrix = lightNode.GetLocalTM(0); var position = localMatrix.Translation; // Direction var target = gameLight.LightTarget; if (target != null) { var targetWm = target.GetObjectTM(0); var targetPosition = targetWm.Translation; var direction = targetPosition.Subtract(position).Normalize; babylonLight.direction = new[] { direction.X, direction.Y, direction.Z }; } else { var vDir = Loader.Global.Point3.Create(0, -1, 0); vDir = localMatrix.ExtractMatrix3().VectorTransform(vDir).Normalize; babylonLight.direction = new[] { vDir.X, vDir.Y, vDir.Z }; } } // The HemisphericLight simulates the ambient environment light, so the passed direction is the light reflection direction, not the incoming direction. // So we need the opposite direction if (babylonLight.type == 3) { var worldRotation = lightNode.GetWorldTM(0).Rotation; BabylonQuaternion quaternion = new BabylonQuaternion(worldRotation.X, worldRotation.Y, worldRotation.Z, worldRotation.W); babylonLight.direction = quaternion.Rotate(new BabylonVector3(0f, 1f, 0f)).ToArray(); } var maxScene = Loader.Core.RootNode; // Exclusion try { var inclusion = maxLight.ExclList.TestFlag(1); //NT_INCLUDE var checkExclusionList = maxLight.ExclList.TestFlag(2); //NT_AFFECT_ILLUM if (checkExclusionList) { var excllist = new List <string>(); var incllist = new List <string>(); foreach (var meshNode in maxScene.NodesListBySuperClass(SClass_ID.Geomobject)) { #if MAX2017 || MAX2018 || MAX2019 || MAX2020 if (meshNode.CastShadows) #else if (meshNode.CastShadows == 1) #endif { var inList = maxLight.ExclList.FindNode(meshNode) != -1; if (inList) { if (inclusion) { incllist.Add(meshNode.GetGuid().ToString()); } else { excllist.Add(meshNode.GetGuid().ToString()); } } } } babylonLight.includedOnlyMeshesIds = incllist.ToArray(); babylonLight.excludedMeshesIds = excllist.ToArray(); } } catch (Exception e) { RaiseMessage("Light exclusion not supported", 2); } // Other fields babylonLight.intensity = maxLight.GetIntensity(0, Tools.Forever); babylonLight.diffuse = lightState.AffectDiffuse ? maxLight.GetRGBColor(0, Tools.Forever).ToArray() : new float[] { 0, 0, 0 }; babylonLight.specular = lightState.AffectDiffuse ? maxLight.GetRGBColor(0, Tools.Forever).ToArray() : new float[] { 0, 0, 0 }; if (maxLight.UseAtten) { babylonLight.range = maxLight.GetAtten(0, 3, Tools.Forever); } if (exportParameters.exportAnimations) { // Animations var animations = new List <BabylonAnimation>(); if (createDummy) { // Position and rotation animations are stored by the parent (the dummy). The direction result from the parent rotation except for the HemisphericLight. if (babylonLight.type == 3) { BabylonVector3 direction = new BabylonVector3(0, 1, 0); ExportVector3Animation("direction", animations, key => { var worldRotation = lightNode.GetWorldTM(key).Rotation; BabylonQuaternion quaternion = new BabylonQuaternion(worldRotation.X, worldRotation.Y, worldRotation.Z, worldRotation.W); return(quaternion.Rotate(direction).ToArray()); }); } } else { GeneratePositionAnimation(lightNode, animations); ExportVector3Animation("direction", animations, key => { var localMatrixAnimDir = lightNode.GetLocalTM(key); var positionLight = localMatrixAnimDir.Translation; var lightTarget = gameLight.LightTarget; if (lightTarget != null) { var targetWm = lightTarget.GetObjectTM(key); var targetPosition = targetWm.Translation; var direction = targetPosition.Subtract(positionLight).Normalize; return(new[] { direction.X, direction.Y, direction.Z }); } else { var vDir = Loader.Global.Point3.Create(0, -1, 0); vDir = localMatrixAnimDir.ExtractMatrix3().VectorTransform(vDir).Normalize; // The HemisphericLight (type == 3) simulates the ambient environment light, so the passed direction is the light reflection direction, not the incoming direction. // So we need the opposite direction return(babylonLight.type != 3 ? new[] { vDir.X, vDir.Y, vDir.Z } : new[] { -vDir.X, -vDir.Y, -vDir.Z }); } }); // Animation temporary stored for gltf but not exported for babylon // TODO - Will cause an issue when externalizing the glTF export process var extraAnimations = new List <BabylonAnimation>(); // Do not check if node rotation properties are animated GenerateRotationAnimation(lightNode, extraAnimations, true); babylonLight.extraAnimations = extraAnimations; } ExportFloatAnimation("intensity", animations, key => new[] { maxLight.GetIntensity(key, Tools.Forever) }); ExportColor3Animation("diffuse", animations, key => { return(lightState.AffectDiffuse? maxLight.GetRGBColor(key, Tools.Forever).ToArray() : new float[] { 0, 0, 0 }); }); babylonLight.animations = animations.ToArray(); if (lightNode.MaxNode.GetBoolProperty("babylonjs_autoanimate")) { babylonLight.autoAnimate = true; babylonLight.autoAnimateFrom = (int)lightNode.MaxNode.GetFloatProperty("babylonjs_autoanimate_from"); babylonLight.autoAnimateTo = (int)lightNode.MaxNode.GetFloatProperty("babylonjs_autoanimate_to"); babylonLight.autoAnimateLoop = lightNode.MaxNode.GetBoolProperty("babylonjs_autoanimateloop"); } } babylonScene.LightsList.Add(babylonLight); return(createDummy ? dummy : babylonLight); }
/// <summary> /// In 3DS Max default element can look in different direction than the same default element in Babylon or in glTF. /// This function correct the node rotation. /// </summary> /// <param name="node"></param> /// <param name="babylonScene"></param> /// <param name="angle"></param> private void FixNodeRotation(ref BabylonNode node, ref BabylonScene babylonScene, double angle) { string id = node.id; IList <BabylonMesh> meshes = babylonScene.MeshesList.FindAll(mesh => mesh.parentId == null ? false : mesh.parentId.Equals(id)); RaiseMessage($"{node.name}", 2); // fix the vue // Rotation around the axis X of PI / 2 in the indirect direction for camera // double angle = Math.PI / 2; // for camera // double angle = -Math.PI / 2; // for light if (node.rotation != null) { node.rotation[0] += (float)angle; } if (node.rotationQuaternion != null) { BabylonQuaternion rotationQuaternion = FixCameraQuaternion(node, angle); node.rotationQuaternion = rotationQuaternion.ToArray(); node.rotation = rotationQuaternion.toEulerAngles().ToArray(); } // animation List <BabylonAnimation> animations = new List <BabylonAnimation>(node.animations); BabylonAnimation animationRotationQuaternion = animations.Find(animation => animation.property.Equals("rotationQuaternion")); if (animationRotationQuaternion != null) { foreach (BabylonAnimationKey key in animationRotationQuaternion.keys) { key.values = FixCameraQuaternion(key.values, angle); } } else // if the camera has a lockedTargetId, it is the extraAnimations that stores the rotation animation { if (node.extraAnimations != null) { List <BabylonAnimation> extraAnimations = new List <BabylonAnimation>(node.extraAnimations); animationRotationQuaternion = extraAnimations.Find(animation => animation.property.Equals("rotationQuaternion")); if (animationRotationQuaternion != null) { foreach (BabylonAnimationKey key in animationRotationQuaternion.keys) { key.values = FixCameraQuaternion(key.values, angle); } } } } // fix direct children // Rotation around the axis X of -PI / 2 in the direct direction for camera children angle = -angle; foreach (var mesh in meshes) { RaiseVerbose($"{mesh.name}", 3); mesh.position = new float[] { mesh.position[0], mesh.position[2], -mesh.position[1] }; // Add a rotation of PI/2 axis X in direct direction if (mesh.rotationQuaternion != null) { // Rotation around the axis X of -PI / 2 in the direct direction BabylonQuaternion quaternion = FixChildQuaternion(mesh, angle); mesh.rotationQuaternion = quaternion.ToArray(); } if (mesh.rotation != null) { mesh.rotation[0] += (float)angle; } // Animations animations = new List <BabylonAnimation>(mesh.animations); // Position BabylonAnimation animationPosition = animations.Find(animation => animation.property.Equals("position")); if (animationPosition != null) { foreach (BabylonAnimationKey key in animationPosition.keys) { key.values = new float[] { key.values[0], key.values[2], -key.values[1] }; } } // Rotation animationRotationQuaternion = animations.Find(animation => animation.property.Equals("rotationQuaternion")); if (animationRotationQuaternion != null) { foreach (BabylonAnimationKey key in animationRotationQuaternion.keys) { key.values = FixChildQuaternion(key.values, angle); } } } }
/// <summary> /// Get TRS and visiblity animations of the transform /// </summary> /// <param name="transform">Transform above mesh/camera/light</param> /// <returns></returns> private List <BabylonAnimation> GetAnimation(MFnTransform transform) { // Animations MPlugArray connections = new MPlugArray(); MStringArray animCurvList = new MStringArray(); MIntArray keysTime = new MIntArray(); MDoubleArray keysValue = new MDoubleArray(); MFloatArray translateValues = new MFloatArray(); MFloatArray rotateValues = new MFloatArray(); MFloatArray scaleValues = new MFloatArray(); MFloatArray visibilityValues = new MFloatArray(); MFloatArray keyTimes = new MFloatArray(); List <BabylonAnimationKey> keys = new List <BabylonAnimationKey>(); List <BabylonAnimation> animationsObject = new List <BabylonAnimation>(); //Get the animCurve MGlobal.executeCommand("listConnections -type \"animCurve\" " + transform.fullPathName + ";", animCurvList); List <AnimCurvData> animCurvesData = new List <AnimCurvData>(); foreach (String animCurv in animCurvList) { AnimCurvData animCurvData = new AnimCurvData(); animCurvesData.Add(animCurvData); animCurvData.animCurv = animCurv; //Get the key time for each curves MGlobal.executeCommand("keyframe -q " + animCurv + ";", keysTime); //Get the value for each curves MGlobal.executeCommand("keyframe - q -vc -absolute " + animCurv + ";", keysValue); if (animCurv.EndsWith("translateZ") || animCurv.EndsWith("rotateX") || animCurv.EndsWith("rotateY")) { for (int index = 0; index < keysTime.Count; index++) { // Switch coordinate system at object level animCurvData.valuePerFrame.Add(keysTime[index], (float)keysValue[index] * -1.0f); } } else { for (int index = 0; index < keysTime.Count; index++) { animCurvData.valuePerFrame.Add(keysTime[index], (float)keysValue[index]); } } } string[] mayaAnimationProperties = new string[] { "translate", "rotate", "scale" }; string[] babylonAnimationProperties = new string[] { "position", "rotationQuaternion", "scaling" }; string[] axis = new string[] { "X", "Y", "Z" }; // Init TRS default values Dictionary <string, float> defaultValues = new Dictionary <string, float>(); float[] position = null; float[] rotationQuaternion = null; float[] rotation = null; float[] scaling = null; GetTransform(transform, ref position, ref rotationQuaternion, ref rotation, ref scaling); // coordinate system already switched defaultValues.Add("translateX", position[0]); defaultValues.Add("translateY", position[1]); defaultValues.Add("translateZ", position[2]); defaultValues.Add("rotateX", rotation[0]); defaultValues.Add("rotateY", rotation[1]); defaultValues.Add("rotateZ", rotation[2]); defaultValues.Add("scaleX", scaling[0]); defaultValues.Add("scaleY", scaling[1]); defaultValues.Add("scaleZ", scaling[2]); for (int indexAnimationProperty = 0; indexAnimationProperty < mayaAnimationProperties.Length; indexAnimationProperty++) { string mayaAnimationProperty = mayaAnimationProperties[indexAnimationProperty]; // Retreive animation curves data for current animation property // Ex: all "translate" data are "translateX", "translateY", "translateZ" List <AnimCurvData> animDataProperty = animCurvesData.Where(data => data.animCurv.Contains(mayaAnimationProperty)).ToList(); if (animDataProperty.Count == 0) { // Property is not animated continue; } // Get all frames for this property List <int> framesProperty = new List <int>(); foreach (var animData in animDataProperty) { framesProperty.AddRange(animData.valuePerFrame.Keys); } framesProperty = framesProperty.Distinct().ToList(); framesProperty.Sort(); // Get default values for this property BabylonAnimationKey lastBabylonAnimationKey = new BabylonAnimationKey(); lastBabylonAnimationKey.frame = 0; lastBabylonAnimationKey.values = new float[] { defaultValues[mayaAnimationProperty + "X"], defaultValues[mayaAnimationProperty + "Y"], defaultValues[mayaAnimationProperty + "Z"] }; // Compute all values for this property List <BabylonAnimationKey> babylonAnimationKeys = new List <BabylonAnimationKey>(); foreach (var frameProperty in framesProperty) { BabylonAnimationKey babylonAnimationKey = new BabylonAnimationKey(); babylonAnimationKeys.Add(babylonAnimationKey); // Frame babylonAnimationKey.frame = frameProperty; // Values float[] valuesProperty = new float[3]; for (int indexAxis = 0; indexAxis < axis.Length; indexAxis++) { AnimCurvData animCurvDataAxis = animDataProperty.Find(data => data.animCurv.EndsWith(axis[indexAxis])); float value; if (animCurvDataAxis != null && animCurvDataAxis.valuePerFrame.ContainsKey(frameProperty)) { value = animCurvDataAxis.valuePerFrame[frameProperty]; } else { value = lastBabylonAnimationKey.values[indexAxis]; } valuesProperty[indexAxis] = value; } babylonAnimationKey.values = valuesProperty.ToArray(); // Update last known values lastBabylonAnimationKey = babylonAnimationKey; } // Convert euler to quaternion angles if (indexAnimationProperty == 1) // Rotation { foreach (var babylonAnimationKey in babylonAnimationKeys) { BabylonVector3 eulerAngles = BabylonVector3.FromArray(babylonAnimationKey.values); BabylonQuaternion quaternionAngles = eulerAngles.toQuaternion(); babylonAnimationKey.values = quaternionAngles.ToArray(); } } var keysFull = new List <BabylonAnimationKey>(babylonAnimationKeys); // Optimization OptimizeAnimations(babylonAnimationKeys, true); // Ensure animation has at least 2 frames if (IsAnimationKeysRelevant(keys)) { // Create BabylonAnimation string babylonAnimationProperty = babylonAnimationProperties[indexAnimationProperty]; animationsObject.Add(new BabylonAnimation() { dataType = indexAnimationProperty == 1 ? (int)BabylonAnimation.DataType.Quaternion : (int)BabylonAnimation.DataType.Vector3, name = babylonAnimationProperty + " animation", framePerSecond = 30, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, property = babylonAnimationProperty, keys = babylonAnimationKeys.ToArray(), keysFull = keysFull }); } } return(animationsObject); }
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") { RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2); var babylonAnimation = babylonBone.animation; // Optimize animation var optimizeAnimations = !Loader.Core.RootNode.GetBoolProperty("babylonjs_donotoptimizeanimations"); // reverse negation for clarity if (optimizeAnimations) { // Filter animation keys to only keep frames between start and end List <BabylonAnimationKey> keysInRangeFull = babylonAnimation.keysFull.FindAll(babylonAnimationKey => babylonAnimationKey.frame >= startFrame && babylonAnimationKey.frame <= endFrame); // Optimization process always keeps first and last frames OptimizeAnimations(keysInRangeFull, false); if (IsAnimationKeysRelevant(keysInRangeFull)) { // From now, use optimized animation instead // Override animation keys babylonAnimation.keys = keysInRangeFull.ToArray(); } } // --- 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 QuatCorrection quatCorr = new QuatCorrection(); // restart correction for each curve 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); translationBabylon.Z *= -1; if (exportParameters.kuesaContQuats) { quatCorr.Quat = rotationQuatBabylon; rotationQuatBabylon = quatCorr.Quat; } 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 GLTFAnimation ExportBoneAnimation(BabylonBone babylonBone, GLTF gltf, GLTFNode gltfNode) { GLTFAnimation gltfAnimation = null; if (gltf.AnimationsList.Count > 0) { gltfAnimation = gltf.AnimationsList[0]; } else { gltfAnimation = new GLTFAnimation(); gltf.AnimationsList.Add(gltfAnimation); } var channelList = gltfAnimation.ChannelList; var samplerList = gltfAnimation.SamplerList; if (babylonBone.animation != null && babylonBone.animation.property == "_matrix") { RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2); var babylonAnimation = babylonBone.animation; // --- Input --- var accessorInput = _createAndPopulateInput(gltf, babylonAnimation); // --- 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) { 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); translationBabylon.Z *= -1; BabylonVector3 rotationVector3 = rotationQuatBabylon.toEulerAngles(); rotationVector3.X *= -1; rotationVector3.Y *= -1; rotationQuatBabylon = rotationVector3.toQuaternion(); 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); } } return(gltfAnimation); }
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 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.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); }