diff --git a/ecere/src/gfx/3D/Mesh.ec b/ecere/src/gfx/3D/Mesh.ec index 3136e7d2de..77a2b049de 100644 --- a/ecere/src/gfx/3D/Mesh.ec +++ b/ecere/src/gfx/3D/Mesh.ec @@ -270,6 +270,19 @@ public struct MeshPart uint count; }; +public struct MeshMorph +{ + Mesh target; + float weight; + String name; + + void OnFree() + { + delete target; // For now, target is owned here + delete name; + } +}; + public class Mesh : struct { public: @@ -295,9 +308,106 @@ public: set { dupVerts = value; } get { return dupVerts; } }; + property Array morphs + { + set { if(morphs) morphs.Free(), delete morphs; morphs = value; } + get { return morphs; } + } + property Mesh unmorphedMesh + { + set { delete unmorphedMesh; unmorphedMesh = value; } + get { return unmorphedMesh; } + } #define GPU_SKIN + void ApplyMorphs() + { + Array morphs = this.morphs; + if(morphs && !unmorphedMesh) + unmorphedMesh = Copy(); + if(morphs) + { + int i; + int nMorphs = morphs.count, m; + int nVertices = Min(this.nVertices, unmorphedMesh.nVertices); + Vector3Df * vertices = this.vertices, * unmVertices = unmorphedMesh.vertices; + int dvCount = dupVerts ? dupVerts.count : 0; + // TODO: apply same computed delta approach to tangents; light vectors? + Vector3Df * unmNormals = unmorphedMesh.normals, * computedUnmorphedNormals = this.computedUnmorphedNormals; + if(!computedUnmorphedNormals) + { + Vector3Df * origUNMNormals = unmNormals; + + computedUnmorphedNormals = new Vector3Df[unmorphedMesh.nVertices]; + this.computedUnmorphedNormals = computedUnmorphedNormals; + unmorphedMesh.normals = computedUnmorphedNormals; + unmorphedMesh.ComputeNormals2(true, true); + unmorphedMesh.normals = origUNMNormals; + } + + memcpy(vertices, unmVertices, (nVertices - dvCount) * sizeof(Vector3Df)); + + for(m = 0; m < nMorphs; m++) + { + MeshMorph morph = morphs[m]; + __attribute__((unused)) const String n = morph.name; + float w = morph.weight; + Mesh target = morph.target; + if(w && target) + { + int nv = Min(nVertices, target.nVertices); + const Vector3Df * sv = unmVertices, * tv = target.vertices; + Vector3Df * v = vertices; + + for(i = 0; i < nv; i++, v++, sv++, tv++) + { + float dx = (tv->x - sv->x) * w; + float dy = (tv->y - sv->y) * w; + float dz = (tv->z - sv->z) * w; + v->x += dx, v->y += dy, v->z += dz; + } + } + } + + if(dupVerts) + { + Vector3Df * v = vertices + nVertices - dvCount; + + for(i = 0; i < dvCount; i++, v++) + { + int dv = dupVerts[i]; + *v = vertices[dv]; + } + } + + ComputeNormals2(true, true); + + // Re-orient original normals based on rotation of computed normals + for(i = 0; i < nVertices; i++) + { + Vector3D axis; + double len; + Vector3Df normal1 = computedUnmorphedNormals[i], normal0 = normals[i]; + + axis.CrossProduct( + { normal0.x, normal0.y, normal0.z }, + { normal1.x, normal1.y, normal1.z }); + + len = axis.length; + if(len) + { + double dot = normal0.DotProduct(normal1); + Degrees angle = atan2(len, dot); + Quaternion q; // q is rotation between computed normals + axis.Scale(axis, 1.0 / len); + q.RotationAxis(axis, angle); + normals[i].MultQuaternion(unmNormals[i], q); + } + } + } + } + void ApplySkin() { MeshSkin skin = this.skin; @@ -635,6 +745,10 @@ public: driver.FreeMesh(displaySystem, this); } delete parts; + + delete unmorphedMesh; + delete computedUnmorphedNormals; + if(morphs) morphs.Free(), delete morphs; } } @@ -2020,6 +2134,10 @@ private: Array matBones; SkinVert * boneData; // For uploading to GPU #endif + + Array morphs; + Mesh unmorphedMesh; + Vector3Df * computedUnmorphedNormals; // Normals as computed by ComputeNormals2() for unmorphed mesh }; void computeNormalWeights(int n, float * vertices, uint vStride, uint * indices, bool ix32Bit, int base, double * weights, Vector3D * edges, Vector3D * rEdges) diff --git a/ecere/src/gfx/3D/Object.ec b/ecere/src/gfx/3D/Object.ec index eb6030af59..7f10bd749a 100644 --- a/ecere/src/gfx/3D/Object.ec +++ b/ecere/src/gfx/3D/Object.ec @@ -1761,6 +1761,27 @@ public: SetMinMaxRadius(false); } + private bool _ApplyMorphs() + { + bool result = false; + Object o; + if(flags.mesh && mesh && mesh.morphs) + { + mesh.ApplyMorphs(); + // flags.morphApplied = true; + result = true; + } + for(o = children.first; o; o = o.next) + result |= o._ApplyMorphs(); + return result; + } + + public void ApplyMorphs() + { + _ApplyMorphs(); + SetMinMaxRadius(false); + } + void ResetPose() { Object o; diff --git a/ecere/src/gfx/3D/models/e3d/e3dDefs.ec b/ecere/src/gfx/3D/models/e3d/e3dDefs.ec index 525d7f6b40..9490a75424 100644 --- a/ecere/src/gfx/3D/models/e3d/e3dDefs.ec +++ b/ecere/src/gfx/3D/models/e3d/e3dDefs.ec @@ -15,7 +15,7 @@ enum E3DBlockType : uint16 meshID = 0x1020, meshBBox = 0x1021, meshDuplVerts = 0x1022, - attributes = 0x2000, + attributes = 0x2000, // uint count (limit of 65,536 vertices) attrVertices = 0x2010, // float x,y,z vertices attrVerticesDbl = 0x2011, // double x,y,z vertices attrQVertices = 0x2018, // quantized x,y,z 16-bit vertices (first, deltas) @@ -56,7 +56,12 @@ enum E3DBlockType : uint16 skinBoneWeights = 0x1054, // byte 0..7: set of bone weights to use for this skin parts = 0x1060, - + morphs = 0x1070, // integer Count + morph = 0x1071, + morphID = 0x1072, + morphName = 0x1073, + morphWeight = 0x1074, // TODO: Use morph weights uniforms for GPU morphs? + // [meshID (reference)] // TODO: Load as vertex attribute instead for GPU morphs? Keep as meshID for re-use? nodes = 0x3000, meshNode = 0x3010, nodeID = 0x3020, @@ -64,7 +69,7 @@ enum E3DBlockType : uint16 scaling = 0x3030, orientation = 0x3031, position = 0x3032, - skeleton = 0x3040, + skeleton = 0x3040, // Should we change this to have skeletonID, skeletonName blocks? // Can have sub-nodes! cameraNode = 0x3011, @@ -144,7 +149,7 @@ enum E3DBlockType : uint16 ftkLightFallOff = 0xA2A0, // Light fall off -- float light fall off (in degrees) per key ftkLightColor = 0xA2B0, // Light color -- 3 (r,g,b) float 0..1 light color per key ftkHide = 0xA2C0, // Hide node -- 1 boolean byte (0: displayed, 1: hidden) per key - ftkMorph = 0xA300 // Morph -- Reserved for morph definition (per key) + ftkMorph = 0xA300 // Morph -- int morph index, float weight (per key) }; struct E3DBlockHeader @@ -283,6 +288,7 @@ class E3DWriteContext : struct Array allAnimatedObjects { }; Map objectToNodeID { }; uint nodeID; + uint morphID; ~E3DWriteContext() { diff --git a/ecere/src/gfx/3D/models/e3d/e3dRead.ec b/ecere/src/gfx/3D/models/e3d/e3dRead.ec index 3674aba1a8..71c2a3ea90 100644 --- a/ecere/src/gfx/3D/models/e3d/e3dRead.ec +++ b/ecere/src/gfx/3D/models/e3d/e3dRead.ec @@ -592,6 +592,12 @@ static void readBlocks(E3DContext ctx, File f, DisplaySystem displaySystem, E3DB object.SetMinMaxRadius(false); } } + else if(containerType == morph) + { + MeshMorph * morph = &mesh.morphs[mesh.morphs.count]; + Mesh target = ctx.meshesByID[id]; + morph->target = target; + } else ctx.meshesByID[id] = data; break; @@ -898,6 +904,44 @@ static void readBlocks(E3DContext ctx, File f, DisplaySystem displaySystem, E3DB // PrintLn("Duplicate Vertices!"); break; } + case morphs: + { + int count = 0; + f.Read(&count, sizeof(int), 1); + pos += sizeof(int); + if(count && !mesh.morphs) + { + mesh.morphs = { minAllocSize = count }; + readSubBlocks = true; + } + break; + } + case morph: + { + readSubBlocks = true; + break; + } + case morphID: + { + // Assuming sequential ID starting from 0 for now + int id = 0; + f.Read(&id, sizeof(int), 1); + break; + } + case morphName: + { + MeshMorph * morph = &mesh.morphs[mesh.morphs.count]; + morph->name = readString(f); + break; + } + case morphWeight: + { + MeshMorph * morph = &mesh.morphs[mesh.morphs.count]; + float w = 0; + f.Read(&w, sizeof(float), 1); + morph->weight = w; + break; + } case skin: readSubBlocks = true; break; @@ -1007,6 +1051,8 @@ static void readBlocks(E3DContext ctx, File f, DisplaySystem displaySystem, E3DB } if(readSubBlocks) readBlocks(ctx, f, displaySystem, header.type, pos, bEnd, subData); + if(header.type == morph && mesh.morphs && mesh.morphs.minAllocSize >= mesh.morphs.count + 1) + mesh.morphs.count++; if(header.type == material) { Material mat = subData; diff --git a/ecere/src/gfx/3D/models/e3d/e3dWrite.ec b/ecere/src/gfx/3D/models/e3d/e3dWrite.ec index 134d2b4970..826727a6e6 100644 --- a/ecere/src/gfx/3D/models/e3d/e3dWrite.ec +++ b/ecere/src/gfx/3D/models/e3d/e3dWrite.ec @@ -161,10 +161,27 @@ static void writeInterleaved(E3DWriteContext ctx, File f, Mesh mesh) // TODO: A Array skinVerts = null; byte maxBones = 0; uint dupVertCount = mesh.dupVerts ? mesh.dupVerts.count : 0; + Vector3Df * tmpVertices = null; if(features.vertices) { - vertices = mesh.vertices; + int startDups = nVertices - dupVertCount; + Mesh unmorphedMesh = mesh.unmorphedMesh; + if(unmorphedMesh && unmorphedMesh.nVertices == nVertices) + vertices = unmorphedMesh.vertices; + else if(unmorphedMesh && unmorphedMesh.nVertices == startDups) + { + int i; + Vector3Df * v; + + vertices = tmpVertices = new Vector3Df[nVertices]; + memcpy(vertices, unmorphedMesh.vertices, startDups); + v = vertices + startDups; + for(i = 0; i < dupVertCount; i++, v++) + *v = unmorphedMesh.vertices[mesh.dupVerts[i]]; + } + else + vertices = mesh.vertices; type = attrVertices; f.Write(&type, sizeof(E3DBlockType), 1); f.Write(&vSize, sizeof(uint16), 1); @@ -262,6 +279,7 @@ static void writeInterleaved(E3DWriteContext ctx, File f, Mesh mesh) // TODO: A f.Write(skinVerts[j].weights, sizeof(byte), maxBones); } } + delete tmpVertices; } static int getFacesCount(PrimitiveGroup g) @@ -455,17 +473,13 @@ struct FacesMaterial int start, count, material; }; -static void writeFaceMaterials(E3DWriteContext ctx, File f, Mesh mesh) +static void writeFaceMaterials(E3DWriteContext ctx, File f, Array faceMaterials) { - Array faceMaterials = ctx.meshFaceMaterials[(uintptr)mesh]; - //PrimitiveGroup g; - //for(g = mesh.groups.first; g; g = g.next) f.Write(faceMaterials.array, sizeof(FacesMaterial), faceMaterials.count); } -static void writeParts(E3DWriteContext ctx, File f, Mesh mesh) +static void writeParts(E3DWriteContext ctx, File f, Array parts) { - Array parts = mesh.parts; f.Write(parts.array, sizeof(MeshPart), parts.count); } @@ -500,9 +514,8 @@ static void writeSkinBones(E3DWriteContext ctx, File f, Array bones) } } -static void writeSkin(E3DWriteContext ctx, File f, Mesh mesh) +static void writeSkin(E3DWriteContext ctx, File f, MeshSkin skin) { - MeshSkin skin = mesh.skin; byte boneWeights = 0; // writeE3DBlock(ctx, f, skinName, skin.name, writeString); @@ -511,12 +524,38 @@ static void writeSkin(E3DWriteContext ctx, File f, Mesh mesh) writeE3DBlock(ctx, f, skinBoneWeights, &boneWeights, writeByte); } +static void writeMorph(E3DWriteContext ctx, File f, MeshMorph morph) +{ + Mesh target = morph.target; + int targetID = ctx.meshToID[(uintptr)target]; + int id = ctx.morphID; + + writeE3DBlock(ctx, f, morphID, &id, writeInt); + writeE3DBlock(ctx, f, morphName, morph.name, writeString); + writeE3DBlock(ctx, f, morphWeight, &morph.weight, writeFloat); + writeE3DBlock(ctx, f, meshID, &targetID, writeInt); +} + +static void writeMorphs(E3DWriteContext ctx, File f, Array morphs) +{ + int nMorphs = morphs.count, i; + + f.Write(&nMorphs, sizeof(nMorphs), 1); + for(i = 0; i < morphs.count; i++) + { + ctx.morphID = i; + writeE3DBlock(ctx, f, morph, &morphs[i], writeMorph); + } +} + static void writeMesh(E3DWriteContext ctx, File f, Mesh mesh) { if(mesh) { int id = ctx.meshToID[(uintptr)mesh]; - writeE3DBlock(ctx, f, meshID, &id, writeInt); + Array faceMaterials = ctx.meshFaceMaterials[(uintptr)mesh]; + + writeE3DBlock(ctx, f, meshID, &id, writeInt); writeE3DBlock(ctx, f, attributes, mesh, writeAttributes); if(mesh.dupVerts && mesh.dupVerts.count) writeE3DBlock(ctx, f, meshDuplVerts, mesh, writeDuplVerts); @@ -524,11 +563,15 @@ static void writeMesh(E3DWriteContext ctx, File f, Mesh mesh) writeE3DBlock(ctx, f, triFaces32, mesh, writeTriFaces32); else writeE3DBlock(ctx, f, triFaces16, mesh, writeTriFaces16); - writeE3DBlock(ctx, f, facesMaterials, mesh, writeFaceMaterials); + + if(faceMaterials) + writeE3DBlock(ctx, f, facesMaterials, faceMaterials, writeFaceMaterials); if(mesh.skin) - writeE3DBlock(ctx, f, skin, mesh, writeSkin); + writeE3DBlock(ctx, f, skin, mesh.skin, writeSkin); if(mesh.parts) - writeE3DBlock(ctx, f, parts, mesh, writeParts); + writeE3DBlock(ctx, f, parts, mesh.parts, writeParts); + if(mesh.morphs) + writeE3DBlock(ctx, f, morphs, mesh.morphs, writeMorphs); } } @@ -542,6 +585,24 @@ void calculateMeshes(E3DWriteContext ctx, Object object) if(object.flags.mesh && object.mesh) { Mesh mesh = object.mesh; + + if(mesh.morphs) // Require morphs to be first so they can be looked up + { + int i; + for(i = 0; i < mesh.morphs.count; i++) + { + MeshMorph * morph = &mesh.morphs[i]; + Mesh target = morph->target; + if(target && !ctx.meshToID[(uintptr)target]) + { + ctx.meshToID[(uintptr)target] = ctx.allMeshes.count+1; + ctx.allMeshes.size++; + ctx.allMeshes[ctx.allMeshes.count-1] = target; + // computeFacesMaterials(ctx, target); + } + } + } + if(!ctx.meshToID[(uintptr)mesh]) { ctx.meshToID[(uintptr)mesh] = ctx.allMeshes.count+1; @@ -833,6 +894,11 @@ static void writeInt(E3DWriteContext ctx, File f, int * data) f.Write(data, sizeof(int), 1); } +static void writeFloat(E3DWriteContext ctx, File f, float * data) +{ + f.Write(data, sizeof(float), 1); +} + static void writeTextureID(E3DWriteContext ctx, File f, int * data) { writeE3DBlock(ctx, f, textureID, data, writeInt);