torinos-yt / VL.Alembic

MIT License
12 stars 1 forks source link

How te get meshes properly? #3

Closed seghier closed 1 day ago

seghier commented 1 week ago

Hi Is there any example how to get meshes from alembic file? i tried many times but always get wrong vertices coordinates and wrong results if i try to output the vertices only i get duplicated results with different transformations

`protected override bool ReadFile(string filename, int index, RhinoDoc doc, Rhino.FileIO.FileReadOptions options) { double time = GetTimeFromUser();

try
{
    Rhino.RhinoApp.WriteLine($"Opening Alembic file: {filename}");
    var alembicScene = AlembicScene.Open(filename);

    if (alembicScene == null)
    {
        Rhino.RhinoApp.WriteLine("Failed to open Alembic file.");
        return false;
    }

    Rhino.RhinoApp.WriteLine($"Navigating to time {time}...");
    alembicScene.SetTime((float)time);

    IEnumerable<DataPointer> pointers;
    IEnumerable<VertexDeclaration> layouts;
    IEnumerable<Stride.Core.Mathematics.BoundingBox> bounds;
    IEnumerable<Stride.Core.Mathematics.Matrix> transforms;
    IEnumerable<PinnedSequence<Stride.Core.Mathematics.Vector3>> sequences;
    IEnumerable<Stride.Core.Mathematics.Matrix> pttransforms;

    int successfulImports = 0;

    alembicScene.GetMeshes(out pointers, out layouts, out bounds, out transforms);
    var names = alembicScene.Names;

    foreach (var name in names)
    {
        var topo = alembicScene.GetMeshTopology(name);
    }

    if (pointers == null || !pointers.Any())
    {
        Rhino.RhinoApp.WriteLine("No mesh data found in the scene.");
        return false;
    }

    var meshDataArray = pointers.Zip(transforms, (p, t) => new { Pointer = p, Transform = t }).ToArray();
    var pointCloud = new PointCloud();
    foreach (var meshData in meshDataArray)
    {
        try
        {
            if (meshData.Pointer.Pointer == IntPtr.Zero || meshData.Pointer.Size <= 0)
            {
                Rhino.RhinoApp.WriteLine("Invalid pointer detected. Skipping mesh.");
                continue;
            }

            var vertexData = GetVertexDataFromPointer(meshData.Pointer);
            if (vertexData == null || vertexData.Vertices.Count == 0)
            {
                Rhino.RhinoApp.WriteLine("No vertices found. Skipping.");
                continue;
            }

            // Add all vertices to point cloud with colors based on position
            foreach (var vertex in vertexData.Vertices)
            {
                // Map vertex position to RGB color for visualization
                var color = GetColorFromPosition(vertex);
                pointCloud.Add(new Point3d(vertex.X, -vertex.Y, vertex.Z), color);
            }

            Rhino.RhinoApp.WriteLine($"Added {pointCloud.Count} points to cloud");

            // Add point cloud to document
            var pointCloudObject = doc.Objects.AddPointCloud(pointCloud);
            /*/
            if (pointCloudObject != Guid.Empty)
            {
                successfulImports++;
            }
            /*/

        }
        catch (Exception ex)
        {
            Rhino.RhinoApp.WriteLine($"Error processing vertices: {ex.Message}");
        }
    }

    doc.Views.Redraw();
    return successfulImports > 0;
}
catch (Exception ex)
{
    Rhino.RhinoApp.WriteLine($"Error reading Alembic file: {ex.Message}");
    return false;
}

}

// Helper method to generate colors based on position private System.Drawing.Color GetColorFromPosition(Point3d point) { // Normalize coordinates to 0-1 range assuming typical model scale float scale = 10.0f; // Adjust this based on your model size float r = (float)(point.X / scale + 0.5); float g = (float)(point.Y / scale + 0.5); float b = (float)(point.Z / scale + 0.5);

// Clamp values to valid range
r = Math.Min(1.0f, Math.Max(0.0f, r));
g = Math.Min(1.0f, Math.Max(0.0f, g));
b = Math.Min(1.0f, Math.Max(0.0f, b));

return System.Drawing.Color.FromArgb(
    (int)(r * 255),
    (int)(g * 255),
    (int)(b * 255)
);

}

private Transform ConvertStrideToRhinoTransform(Stride.Core.Mathematics.Matrix strideMatrix) { // Create a transform that converts from Stride's coordinate system to Rhino's var transform = Transform.Identity;

// Convert from right-handed (Stride) to right-handed (Rhino) coordinate system
transform.M00 = strideMatrix.M11;
transform.M01 = strideMatrix.M12;
transform.M02 = strideMatrix.M13;
transform.M03 = strideMatrix.M14;

transform.M10 = strideMatrix.M21;
transform.M11 = strideMatrix.M22;
transform.M12 = strideMatrix.M23;
transform.M13 = strideMatrix.M24;

transform.M20 = strideMatrix.M31;
transform.M21 = strideMatrix.M32;
transform.M22 = strideMatrix.M33;
transform.M23 = strideMatrix.M34;

transform.M30 = strideMatrix.M41;
transform.M31 = strideMatrix.M42;
transform.M32 = strideMatrix.M43;
transform.M33 = strideMatrix.M44;

return transform;

}

private Mesh CreateRhinoMesh(DataPointer pointer) { try { var vertexData = GetVertexDataFromPointer(pointer); if (vertexData == null || vertexData.Vertices.Count == 0) { return null; }

    var mesh = new Mesh();

    // Add vertices with correct coordinate system conversion
    foreach (var vertex in vertexData.Vertices)
    {
        // Blender/Alembic uses right-handed Z-up system:
        // - X right
        // - Y forward (into screen)
        // - Z up

        // Rhino uses right-handed Y-forward system:
        // - X right
        // - Y forward
        // - Z up

        // Therefore the conversion is:
        // Rhino.X = Alembic.X
        // Rhino.Y = -Alembic.Y  (flip Y to convert from into-screen to forward)
        // Rhino.Z = Alembic.Z   (both use Z up)

        mesh.Vertices.Add(vertex.X, -vertex.Y, vertex.Z);
    }

    // Add faces - we need to flip the winding order to maintain correct face orientation
    foreach (var face in vertexData.Faces)
    {
        if (face.IsValid() &&
            face.A < mesh.Vertices.Count &&
            face.B < mesh.Vertices.Count &&
            face.C < mesh.Vertices.Count)
        {
            // Add face with corrected winding order
            mesh.Faces.AddFace(face.A, face.B, face.C);
        }
    }

    // Compute mesh properties
    mesh.Normals.ComputeNormals();
    mesh.Compact();

    return mesh;
}
catch (Exception ex)
{
    Rhino.RhinoApp.WriteLine($"Error creating mesh: {ex.Message}");
    return null;
}

}

private VertexData GetVertexDataFromPointer(DataPointer pointer) { var vertexData = new VertexData();

try
{
    var vertexBuffer = new byte[pointer.Size];
    Marshal.Copy(pointer.Pointer, vertexBuffer, 0, pointer.Size);

    // First try to find the vertex count marker (4 bytes)
    int vertexCount = BitConverter.ToInt32(vertexBuffer, 0);
    int currentOffset = 4;

    // Validate vertex count
    if (vertexCount <= 0 || vertexCount * 12 > pointer.Size)
    {
        // If invalid, assume data is packed without header
        vertexCount = pointer.Size / (3 * sizeof(float));
        currentOffset = 0;
    }

    // Read vertex data
    for (int i = 0; i < vertexCount && (currentOffset + 11) < pointer.Size; i++)
    {
        float x = BitConverter.ToSingle(vertexBuffer, currentOffset);
        float y = BitConverter.ToSingle(vertexBuffer, currentOffset + 4);
        float z = BitConverter.ToSingle(vertexBuffer, currentOffset + 8);

        if (!float.IsNaN(x) && !float.IsNaN(y) && !float.IsNaN(z) &&
            !float.IsInfinity(x) && !float.IsInfinity(y) && !float.IsInfinity(z))
        {
            // Scale vertices if needed
            const float scale = 1.0f; // Adjust this value if needed
            vertexData.Vertices.Add(new Point3d(x * scale, y * scale, z * scale));
        }

        currentOffset += 12;
    }

    // After vertices, try to read face indices
    if (currentOffset + 12 < pointer.Size)
    {
        int faceCount = (pointer.Size - currentOffset) / (3 * sizeof(int));

        for (int i = 0; i < faceCount && (currentOffset + 11) < pointer.Size; i++)
        {
            int a = BitConverter.ToInt32(vertexBuffer, currentOffset);
            int b = BitConverter.ToInt32(vertexBuffer, currentOffset + 4);
            int c = BitConverter.ToInt32(vertexBuffer, currentOffset + 8);

            if (a >= 0 && b >= 0 && c >= 0 &&
                a < vertexData.Vertices.Count &&
                b < vertexData.Vertices.Count &&
                c < vertexData.Vertices.Count)
            {
                vertexData.Faces.Add(new MeshFace(a, b, c));
            }

            currentOffset += 12;
        }
    }
    else
    {
        // If no face data found, create triangles from vertices
        for (int i = 0; i < vertexData.Vertices.Count - 2; i += 3)
        {
            vertexData.Faces.Add(new MeshFace(i, i + 1, i + 2));
        }
    }

    return vertexData;
}
catch (Exception ex)
{
    Rhino.RhinoApp.WriteLine($"Error extracting vertex data: {ex.Message}");
    return null;
}

}

private class VertexData { public List Vertices { get; set; } = new List(); public List Faces { get; set; } = new List(); }

// Example of a method to get the time from the user private double GetTimeFromUser() { // Default time to load double defaultTime = 0.0; string defaultTimeStr = defaultTime.ToString(); // Convert default time to string

// Prompt the user in the Rhino command line
Rhino.Commands.Result getResult = Rhino.Input.RhinoGet.GetString("Enter time to load (default 0.0):", true, ref defaultTimeStr);

// Check if the user cancelled or confirmed the input
if (getResult == Rhino.Commands.Result.Success && double.TryParse(defaultTimeStr, out double time) && time >= 0)
{
    return time; // Return parsed time
}

// If user cancels or input is invalid, return default
Rhino.RhinoApp.WriteLine("Invalid input or cancelled. Loading default time 0.0.");
return defaultTime;

}`

torinos-yt commented 5 days ago

In VL.Alembic, the data obtained by GetMeshes() is evaluates the mesh data, including at least position, normal and uv, and generates default values at runtime if they are missing. So, if you get mesh data, at least one vertex will contain 32 bytes(12(P)+12(N)+8(UV)) of data. also, this is simply interleaved with the position, normal and uv of each vertex and color if included, so there is no need to check the data at the beginning.

// First try to find the vertex count marker (4 bytes)
int vertexCount = BitConverter.ToInt32(vertexBuffer, 0);
int currentOffset = 4;

// Validate vertex count
if (vertexCount <= 0 || vertexCount * 12 > pointer.Size)
{
    // If invalid, assume data is packed without header
    vertexCount = pointer.Size / (3 * sizeof(float));
    currentOffset = 0;
}

If you want to know the exact number of vertices, for example

foreach (var (meshData, layout) in meshDataArray.Zip(layouts, (mesh, layout) => (mesh, layout)))
{
    int vertexCount = meshData.Pointer.Size / layout.VertexStride;
    ........
}

Also, Alembic basically uses the Y+ Up right-hand coordinate system, and most DCC exporters, including Blender, should convert to this coordinate system when exporting. Therefore, I think the coordinate transformation that needs to be done at the time of import is Alembic->Rhino, not Blender->Rhino.

VL.Alembic is intended to be used on vvvv gamma (a.k.a VL), which is a Visual Programming Language, and the Stride Game Engine, although I cannot verify that it is fully usable on Rhino or Glasshopper, I hope this reply helps you.

seghier commented 5 days ago

Thank you very much @torinos-yt That's help, and i fix the code

seghier commented 4 days ago

Hi @torinos-yt Is it possible to get particles from alembic file? Blender alembic importer support that image

torinos-yt commented 1 day ago

I am not familiar with Blender's alembic export, but if it is included as a Point Schema in the alembic archive, I believe it can be imported using AlembicScene.GetPoints() or similar. However, Point only supports getting Position(xyz). https://github.com/torinos-yt/VL.Alembic/blob/5beb5d0bf15f4959bd3ca818c7a5d1461a746a56/src/AlembicSceneSample.cs#L37

Close the issue once, but feel free to reopen it if not resolved.