public static int AddMeshObjectToScene( [MarshalAs(UnmanagedType.LPStr)] string name, InteropMatrix4x4 transform, [MarshalAs(UnmanagedType.LPStr)] string material ) { InteropLogger.Debug($"Adding mesh <name={name}, material={material}>"); try { var obj = new SceneObject(name, SceneObjectType.Mesh); obj.Transform = transform; obj.Material = material; Bridge.AddObject(obj); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
void RebuildTriangles() { triangles.Resize(loopTris.Length * 3); InteropLogger.Debug($"RebuildTriangles name={Name}, Length={triangles.Length}"); for (int t = 0; t < loopTris.Length; t++) { var loopTri = loopTris[t]; // Triangles are flipped due to coordinate space conversions // that happen from Blender to Unity int tri0 = (int)loopTri.tri_2; int tri1 = (int)loopTri.tri_1; int tri2 = (int)loopTri.tri_0; // If the triangle vert has been mapped to a split vertex, // use that instead of the original vertex triangles[t * 3 + 0] = splitVertices.GetValueOrDefault(tri0, (int)loops[tri0].v); triangles[t * 3 + 1] = splitVertices.GetValueOrDefault(tri1, (int)loops[tri1].v); triangles[t * 3 + 2] = splitVertices.GetValueOrDefault(tri2, (int)loops[tri2].v); } }
public bool ReplaceOrQueue <T>(RpcRequest type, string target, ref T data) where T : struct { var header = new InteropMessageHeader { type = type, index = 0, length = 0, count = 0 }; var payload = FastStructure.ToBytes(ref data); // If it's already queued, replace the payload /*var queued = FindQueuedMessage(target, ref header); * if (queued != null) * { * queued.payload = payload; * return true; * }*/ // Remove the old one to then queue up one at the end // This ensures messages that are queued up together // remain in their queued order. RemoveQueuedMessage(target, ref header); InteropLogger.Debug($" ROQ-> {target}:{header.type:F}"); outboundQueue.Enqueue(new InteropMessage { target = target, header = header, payload = payload }); return(false); }
void UpdateMesh() { // If vertex length changes - Unity will throw a fit since we can't // just fill buffers without it trying to second guess us each step. // // This is especially common with metaballs. // So we assume vertex length changes = everything will be dirtied. // Not a great assumption though so.. TODO! :) if (vertices.Length != mesh.vertices.Length) { mesh.Clear(); } // Channels that were dirtied from last time get loaded. if (vertices.IsDirty) { InteropLogger.Debug($"Dirty vertices={vertices.Length}"); mesh.vertices = vertices.Read(); } if (normals.IsDirty) { InteropLogger.Debug($"Dirty normals={normals.Length}"); mesh.normals = normals.Read(); } if (triangles.IsDirty) { InteropLogger.Debug($"Dirty triangles={triangles.Length}"); // Change index format for large Blender meshes - when needed if (triangles.Length > short.MaxValue) { mesh.indexFormat = IndexFormat.UInt32; } else { mesh.indexFormat = IndexFormat.UInt16; } mesh.triangles = triangles.Read(); } if (colors.IsDirty) { mesh.colors32 = colors.Read(); } if (uv.IsDirty) { mesh.uv = uv.Read(); } if (uv2.IsDirty) { mesh.uv2 = uv2.Read(); } if (uv3.IsDirty) { mesh.uv3 = uv3.Read(); } if (uv4.IsDirty) { mesh.uv4 = uv4.Read(); } // TODO: Additional channels // mesh.boneWeights = // mesh.bindposes = // mesh.tangents = // mesh.MarkModified(); // In editor mode - if the editor doesn't have focus then the mesh won't be updated. // So we force upload to the GPU once we have everything ready. We also maintain // local copies of mesh data to be updated by Blender whenever new data comes in. // (so we don't free up, say, UVs, and then not have that buffer when re-applying from Blender) mesh.UploadMeshData(false); // onUpdateVertices.Invoke(mesh); }
/// <summary> /// Generic implementation of mapping an array of Blender structs to interop structs. /// /// <para> /// This handles target buffer (re)allocation and vertex splitting /// if the values we're reading from <paramref name="source"/> deviates /// from the value already written to the same index. /// </para> /// </summary> /// <typeparam name="TSource"></typeparam> /// <typeparam name="TTarget"></typeparam> /// <param name="source">Array of loop structs from Blender. Must be aligned to <see cref="loops"/></param> /// <param name="target">Array of interop structs for Unity. Must be aligned to <see cref="vertices"/></param> /// <returns></returns> int RebuildBuffer <TSource, TTarget>( NativeArray <TSource> source, ArrayBuffer <TTarget> target ) where TSource : struct, IInteropConvertible <TTarget> where TTarget : struct { // No data in this buffer - clear target. if (source.Length < 1) { target.Clear(); return(0); } // Match the target channel to the vertex length target.Resize(vertices.Length); InteropLogger.Debug( $"RebuildBuffer name={Name}, Length={target.Length}, " + $"TSource={typeof(TSource)}, TTarget={typeof(TTarget)}" ); int splitCount = 0; var written = new bool[target.Length]; // Determine which vertices need to split and add entries for (int i = 0; i < source.Length; i++) { var val = source[i]; int vertIndex = (int)loops[i].v; if (splitVertices.ContainsKey(i)) { // Already split - copy to the split index vertIndex = splitVertices[i]; } // vertIndex will always be < written.Length // because any indices outside of that range will // be found in the splitVertices map else if (written[vertIndex]) { // If we already wrote to this vertex once - // determine if we should split from the original. // We exclude any indices outside the initial size // that were added by Split() here. var prevVal = target[vertIndex]; if (!val.Equals(prevVal)) { vertIndex = Split(i); splitCount++; } } else { written[vertIndex] = true; } target[vertIndex] = val.ToInterop(); } InteropLogger.Debug($"RebuildBuffer name={Name} - Split {splitCount} vertices"); return(splitCount); }
internal void CopyMeshDataNative( NativeArray <MVert> verts, NativeArray <MLoop> loops, NativeArray <MLoopTri> loopTris, NativeArray <MLoopCol> loopCols, NativeArray <MLoopUV> loopUVs ) { // A change of unique vertex count or a change to the primary // loop mapping (loop index -> vertex index) requires a full rebuild. // This will hit frequently for things like Metaballs that change often if (verts.Length != this.verts.Length || !this.loops.Equals(loops)) { InteropLogger.Debug($"CopyMeshData - rebuild all"); this.loops.CopyFrom(loops); this.verts.CopyFrom(verts); this.loopCols.CopyFrom(loopCols); this.loopUVs.CopyFrom(loopUVs); // ... and so on this.loopTris.CopyFrom(loopTris); RebuildAll(); return; } int prevVertexCount = vertices.Length; // Trigger rebuild of buffers based on whether the Blender data changed. if (!this.verts.Equals(verts)) { InteropLogger.Debug("CopyMeshData - !verts.Equals"); this.verts.CopyFrom(verts); RebuildVertices(); RebuildNormals(); } if (!this.loopCols.Equals(loopCols)) { InteropLogger.Debug("CopyMeshData - !colors.Equals"); this.loopCols.CopyFrom(loopCols); RebuildBuffer(this.loopCols, colors); } if (!this.loopUVs.Equals(loopUVs)) { InteropLogger.Debug("CopyMeshData - !uvs.Equals"); this.loopUVs.CopyFrom(loopUVs); RebuildBuffer(this.loopUVs, uvs); } // ... and so on InteropLogger.Debug("CopyMeshData - Check triangles"); // If any of the channels created new split vertices // we need to rebuild the full triangle buffer to re-map // old loop triangle indices to new split vertex indices if (prevVertexCount != vertices.Length || !this.loopTris.Equals(loopTris)) { InteropLogger.Debug($"CopyMeshData - !loopTris.Equals or prev {prevVertexCount} != {vertices.Length}"); this.loopTris.CopyFrom(loopTris); RebuildTriangles(); } }
/// <summary> /// Copy all mesh data from Blender in one go and optimize down as much as possible. /// </summary> /// <remarks> /// Reference: LuxCoreRender/LuxCore::Scene_DefineBlenderMesh /// for the logic dealing with split normals / UVs / etc. /// </remarks> /// <param name="verts"></param> /// <param name="loops"></param> /// <param name="loopTris"></param> /// <param name="loopUVs"></param> /// <param name="loopCols"></param> internal void CopyMeshData_V1( MVert[] verts, MLoop[] loops, MLoopTri[] loopTris, MLoopCol[] loopCols, List <MLoopUV[]> loopUVs ) { #if LEGACY // In the case of split vertices - this'll resize DOWN // and then resize UP again for split vertices. Reallocate(verts.Length, loopTris.Length, loopUVs.Count, loopCols != null); var normalScale = 1f / 32767f; // Copy in vertex coordinates and normals for (int i = 0; i < verts.Length; i++) { var co = verts[i].co; var no = verts[i].no; _vertices[i] = new InteropVector3(co[0], co[1], co[2]); // Normals need to be cast from short -> float from Blender _normals[i] = new InteropVector3( no[0] * normalScale, no[1] * normalScale, no[2] * normalScale ); } // Copy UV layers for (int layer = 0; layer < loopUVs.Count; layer++) { var uvLayer = _uvs[layer]; for (uint i = 0; i < loopUVs[layer].Length; i++) { var vertIndex = loops[i].v; // This will overwrite itself for shared vertices - that's fine. // We'll be handling split UVs when reading in triangle data. uvLayer[vertIndex] = new InteropVector2( loopUVs[layer][i].uv ); } } // Copy vertex colors if we got 'em if (loopCols != null) { for (uint i = 0; i < loopCols.Length; i++) { var vertIndex = loops[i].v; var col = loopCols[i]; _colors[vertIndex] = new InteropColor32( col.r, col.g, col.b, col.a ); } } // Track what triangle vertices need to be split. // This maps an index in `triangles` to an index in `loops` var splitTris = new Dictionary <uint, uint>(); // Generate triangle list while identifying any vertices that will need // to be split - due to having split UVs, normals, etc in the loop data. for (uint t = 0; t < loopTris.Length; t++) { for (uint i = 0; i < 3; i++) { var loopIndex = loopTris[t].tri[i]; var vertIndex = loops[loopIndex].v; var split = false; // Assumes .v is already < verts.Length // TODO: Test differing normals - not applicable // here as normals are only read in from MVert // Determine if we should make a new vertex for split UVs for (int layer = 0; layer < loopUVs.Count && !split; layer++) { var loopUV = loopUVs[layer][loopIndex].uv; var vertUV = _uvs[layer][vertIndex]; // TODO: Handle floating point errors? if (loopUV[0] != vertUV.x || loopUV[1] != vertUV.y) { split = true; } } // If we have vertex colors, check for split colors if (loopCols != null) { var col = loopCols[loopIndex]; var vertCol = _colors[vertIndex]; if (col.r != vertCol.r || col.g != vertCol.g || col.b != vertCol.b || col.a != vertCol.a ) { split = true; } } _triangles[(t * 3) + i] = vertIndex; // Track if we need to split the vertex in the triangle // to a new one once we've iterated through everything if (split) { splitTris.Add((t * 3) + i, loopIndex); } } } // 7958 + 32245 = 40203 // LOOPS are 31488 // 7958 * 3 = 23874 // 15744 loop triangles // 31488 vertex color indices // If we have triangle verts to split - apply all at once so there's // only a single re-allocation to our arrays. var totalNewVertices = splitTris.Count; if (totalNewVertices > 0) { InteropLogger.Debug($"Splitting {totalNewVertices} vertices"); var newVertIndex = (uint)verts.Length; // Reallocate everything to fit the new set of vertices Reallocate(verts.Length + totalNewVertices, loopTris.Length, loopUVs.Count, loopCols != null); // Generate new vertices with any split data (normals, UVs, colors, etc) foreach (var tri in splitTris.Keys) { var prevVertIndex = _triangles[tri]; // MVert index var loopIndex = splitTris[tri]; // MLoop index // Same coordinates as the original vertex _vertices[newVertIndex] = _vertices[prevVertIndex]; // TODO: If there were split normals, that'd be handled here. _normals[newVertIndex] = _normals[prevVertIndex]; // Read UVs from loops again to handle any split UVs for (int layer = 0; layer < loopUVs.Count; layer++) { var uv = loopUVs[layer][loopIndex].uv; _uvs[layer][newVertIndex] = new InteropVector2(uv); } // Same deal for vertex colors - copy from the loop if (loopCols != null) { var col = loopCols[loopIndex]; _colors[newVertIndex] = new InteropColor32( col.r, col.g, col.b, col.a ); } // And finally update the triangle to point to the new vertex _triangles[tri] = newVertIndex; newVertIndex++; } } #endif }
/// <summary> /// Copy an <see cref="MVert"/> array into <see cref="Vertices"/> and <see cref="Normals"/>. /// </summary> /// <param name="verts"></param> internal void CopyFromMVerts(MVert[] verts) { var count = verts.Length; if (vertices == null) { vertices = new InteropVector3[count]; } if (normals == null) { normals = new InteropVector3[count]; } Array.Resize(ref vertices, count); Array.Resize(ref normals, count); // We're copying the data instead of sending directly into shared memory // because we cannot guarantee that: // 1. shared memory will be available for write when this is called // 2. the source MVert array hasn't been reallocated once shared // memory *is* available for write // TODO: "Smart" dirtying. // If verts don't change, don't flag for updates. // Or if verts change, only update a region of the verts. // Could do a BeginChangeCheck() ... EndChangeCheck() with // a bool retval if changes happened. // The idea is to do as much work as possible locally within // Blender to transmit the smallest deltas to Unity. // On a resize - this is just everything. int rangeStart = count; int rangeEnd = 0; int changedVertexCount = 0; for (int i = 0; i < count; i++) { var co = verts[i].co; var no = verts[i].no; var newVert = new InteropVector3(co[0], co[1], co[2]); // Normals need to be cast from short -> float var newNorm = new InteropVector3(no[0] / 32767f, no[1] / 32767f, no[2] / 32767f); if (!newVert.Approx(vertices[i]) || !newNorm.Approx(normals[i])) { rangeStart = Math.Min(rangeStart, i); rangeEnd = Math.Max(rangeEnd, i); changedVertexCount++; } vertices[i] = newVert; normals[i] = newNorm; // TECHNICALLY I can collect an index list of what changed and send just // that to Unity - but is it really worth the extra effort? We'll see! // Console.WriteLine($" - v={vertices[i]}, n={normals[i]}"); // Other issue is that changes may affect index 0, 1, and 1000, 1001 for a quad. // Or a change has shifted vertices around in the array due to some sort of // esoteric Blender operator. So to play it safe, we just update the whole array // until it can be guaranteed that we can identify an accurate slice of updates. } InteropLogger.Debug( $"** Changed {changedVertexCount} vertices within index range " + $"[{rangeStart}, {rangeEnd}] covering {rangeEnd - rangeStart} vertices" ); }