Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve utils for mesh generation and processing #179

Closed
chrisdill opened this issue Aug 3, 2023 · 9 comments
Closed

Improve utils for mesh generation and processing #179

chrisdill opened this issue Aug 3, 2023 · 9 comments
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@chrisdill
Copy link
Owner

New issue continuing #35. I want it to be easier to build and process meshes as that is one of the few areas that is a pain to manage with unsafe code even if you are careful.

Initially this starts with adding span wrappers/checks to help change the existing mesh but I would like to extend that to make the initial mesh creation/building process easier.

@chrisdill chrisdill added the enhancement New feature or request label Aug 3, 2023
@chrisdill chrisdill self-assigned this Aug 3, 2023
@chrisdill chrisdill added the help wanted Extra attention is needed label Aug 3, 2023
@JupiterRider
Copy link
Contributor

Since .NET 6 we have the static class NativeMemory:
https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativememory?view=net-6.0

NativeMemory.AllocZeroed allocates zeroed memory on the heap (like calloc in C does).

Here is an example:

using System.Numerics;
using System.Runtime.InteropServices;
using Raylib_cs;

Raylib.InitWindow(800, 450, "Test");

var mesh = new Mesh() { triangleCount = 1, vertexCount = 3 };

Span<float> vertices, normals, texcoords;

unsafe
{
    mesh.vertices = (float*)NativeMemory.AllocZeroed(9, sizeof(float));
    mesh.normals = (float*)NativeMemory.AllocZeroed(9, sizeof(float));
    mesh.texcoords = (float*)NativeMemory.AllocZeroed(6, sizeof(float));

    vertices = new Span<float>(mesh.vertices, 9);
    normals = new Span<float>(mesh.normals, 9);
    texcoords = new Span<float>(mesh.texcoords, 6);
}

vertices[3] = 1;
vertices[5] = 2;
vertices[6] = 2;

normals[1] = 1;
normals[4] = 1;
normals[7] = 1;

texcoords[2] = .5f;
texcoords[3] = 1;
texcoords[4] = 1;

Raylib.UploadMesh(ref mesh, false);
var model = Raylib.LoadModelFromMesh(mesh);

var camera = new Camera3D() { position = new(5, 5, 5), up = new() { Y = 1 }, fovy = 45 };

while (!Raylib.WindowShouldClose())
{
    Raylib.UpdateCamera(ref camera, CameraMode.CAMERA_ORBITAL);

    Raylib.BeginDrawing();

    Raylib.ClearBackground(Color.WHITE);
    Raylib.BeginMode3D(camera);
    Raylib.DrawModel(model, Vector3.Zero, 1, Color.RED);
    Raylib.DrawGrid(10, 1);
    Raylib.EndMode3D();

    Raylib.EndDrawing();
}

Raylib.UnloadModel(model);
Raylib.CloseWindow();

Screenshot_20230804_185820

We don't need to call NativeMemory.Free in the end, because unload Raylib.UnloadModel frees the mesh as well.

@nickyMcDonald
Copy link
Contributor

nickyMcDonald commented Aug 6, 2023

Taking @JupiterRider's idea you could add a constructor that looks something like this:

/// <summary>Generate mesh</summary>
public Mesh(int triangleCount, int vertexCount)
{
    this.triangleCount = triangleCount;
    this.vertexCount = vertexCount;
    vertices = (float*)NativeMemory.AllocZeroed((nuint)(3 * this.vertexCount), sizeof(float));
}

Also add extention method's to utils:

/// <summary>Access mesh triangles</summary>
public static Span<T> TrianglesAs<T>(this Mesh mesh) where T : unmanaged
{
    return new(mesh.vertices, 3 * mesh.vertexCount * sizeof(float) / sizeof(T));
}

Utilizing these would look something like this:

using Raylib_cs;
using System.Numerics;

Raylib.InitWindow(1280, 960, "Triangle");
Raylib.SetTargetFPS(60);

Camera3D camera = new(Vector3.One, Vector3.Zero, Vector3.UnitY, 90f, CameraProjection.CAMERA_PERSPECTIVE);
Mesh mesh = new(1, 3);
Span<Vector3> vertices = mesh.TrianglesAs<Vector3>();
vertices[0] = Vector3.UnitZ;
vertices[1] = Vector3.UnitX;
vertices[2] = Vector3.Zero;
Raylib.UploadMesh(ref mesh, false);
Model model = Raylib.LoadModelFromMesh(mesh);

while (!Raylib.WindowShouldClose())
{
    Raylib.UpdateCamera(ref camera, CameraMode.CAMERA_ORBITAL);
    Raylib.BeginDrawing();
    Raylib.ClearBackground(Color.BLACK);
    Raylib.BeginMode3D(camera);
    Raylib.DrawModel(model, Vector3.Zero, 1f, Color.PINK);
    Raylib.DrawGrid(2, 1);
    Raylib.EndMode3D();
    Raylib.EndDrawing();
}
Raylib.UnloadModel(model);
Raylib.CloseWindow();

@chrisdill
Copy link
Owner Author

chrisdill commented Aug 12, 2023

@JupiterRider Unsure if it is better to use MemAlloc, MemFree via Raylib instead. Raylib can be recompiled to change how allocation works internally so using the built in versions can make sure alloc/free calls work with each other correctly.

@n77y Interesting idea! How could this be applied to other mesh fields?

@JupiterRider
Copy link
Contributor

JupiterRider commented Aug 12, 2023

@chrisdill Doesn't matter. They both use calloc in stdlib.h anyways:
https://github.com/raysan5/raylib/blob/master/src/utils.c#L163
https://github.com/raysan5/raylib/blob/master/src/raylib.h#L124

https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Unix.cs,149
https://source.dot.net/#System.Private.CoreLib/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemAlloc.cs,20
https://github.com/dotnet/runtime/blob/main/src/native/libs/System.Native/pal_memory.c#L63

I don't think its a good Idea for maintainability compiling our own version of raylib or writing to many layers of abstraction.

Raylib-cs should better be a binding only. For everything beyond we could create an additional project called Raylib-cs.Extras or something.

@nickyMcDonald
Copy link
Contributor

@chrisdill, @JupiterRider, would it be possible and sensible to add an syntax suger allocator like this?

/// <summary>C++ style memory allocator</summary>
public static T* New<T>(int count) where T : unmanaged
{
    return (T*)MemAlloc(count * sizeof(T));
}

or this:

/// <summary>C++ style memory allocator</summary>
public static T* New<T>(int count) where T : unmanaged
{
    return (T*)NativeMemory.AllocZeroed((nuint)count, (nuint)sizeof(T));
}

The method name can be changed if its not disirable. This could help with changing:

mesh.vertices = (float*)NativeMemory.AllocZeroed((nuint)(3 * mesh.vertexCount), sizeof(float));

To instead look like this:

mesh.vertices = New<float>(3 * mesh.vertexCount);

@nickyMcDonald
Copy link
Contributor

nickyMcDonald commented Aug 13, 2023

We could simplify the constructor to:

public Mesh(int triangleCount, int vertexCount)
{
    this.triangleCount = triangleCount;
    this.vertexCount = vertexCount;
}

And have all of our allocations be in seperate extention methods (utils) so we can pick and choose which ones we are going to use when generating custom meshes:

/// <summary>Allocate mesh vertices</summary>
public static void AllocVertices(ref this Mesh mesh)
{
    mesh.vertices = New<float>(3 * mesh.vertexCount);
}

/// <summary>Allocate mesh texcoords</summary>
public static void AllocTexcoords(ref this Mesh mesh)
{
    mesh.texcoords = New<float>(2 * mesh.vertexCount);
}

/// <summary>Allocate mesh colors</summary>
public static void AllocColors(ref this Mesh mesh)
{
    mesh.colors = New<byte>(4 * mesh.vertexCount);
}

/// <summary>Allocate mesh indices</summary>
public static void AllocIndices(ref this Mesh mesh)
{
    mesh.indices = New<ushort>(3 * mesh.triangleCount);
}

All of the accessors can be more utils:

/// <summary>Access mesh vertices</summary>
public static Span<T> VerticesAs<T>(this Mesh mesh) where T : unmanaged
{
    return new(mesh.vertices, 3 * mesh.vertexCount * sizeof(float) / sizeof(T));
}

/// <summary>Access mesh texcoords</summary>
public static Span<T> TexcoordsAs<T>(this Mesh mesh) where T : unmanaged
{
    return new(mesh.texcoords, 2 * mesh.vertexCount * sizeof(float) / sizeof(T));
}

/// <summary>Access mesh colors</summary>
public static Span<T> ColorsAs<T>(this Mesh mesh) where T : unmanaged
{
    return new(mesh.colors, 4 * mesh.vertexCount * sizeof(byte) / sizeof(T));
}

/// <summary>Access mesh indices</summary>
public static Span<T> IndicesAs<T>(this Mesh mesh) where T : unmanaged
{
    return new(mesh.indices, 3 * mesh.triangleCount * sizeof(ushort) / sizeof(T));
}

Example of usage:

using Raylib_cs;
using System.Numerics;

Raylib.InitWindow(1280, 960, "Hello World!");
Raylib.SetTargetFPS(60);

Camera3D camera = new(Vector3.One * 1.5f, Vector3.Zero, Vector3.UnitY, 60f, CameraProjection.CAMERA_PERSPECTIVE);
Mesh tetrahedron = new(4, 4);
tetrahedron.AllocVertices();
tetrahedron.AllocTexcoords();
tetrahedron.AllocColors();
tetrahedron.AllocIndices();
Span<Vector3> vertices = tetrahedron.VerticesAs<Vector3>();
Span<Vector2> texcoords = tetrahedron.TexcoordsAs<Vector2>();
Span<Color> colors = tetrahedron.ColorsAs<Color>();
Span<ushort> indices = tetrahedron.IndicesAs<ushort>();

// Coordinates for a regular tetrahedron (wikipedia)
vertices[0] = new(float.Sqrt(8f / 9f), 0f, -1f / 3f);
vertices[1] = new(-float.Sqrt(2f / 9f), float.Sqrt(2f / 3f), -1f / 3f);
vertices[2] = new(-float.Sqrt(2f / 9f), -float.Sqrt(2f / 3f), -1f / 3f);
vertices[3] = Vector3.UnitZ;

texcoords[0] = Vector2.Zero;
texcoords[1] = Vector2.UnitX;
texcoords[2] = Vector2.UnitY;
texcoords[3] = Vector2.One;

colors[0] = Color.PINK;
colors[1] = Color.LIME;
colors[2] = Color.SKYBLUE;
colors[3] = Color.VIOLET;

indices[0] = 2;
indices[1] = 1;
indices[2] = 0;

indices[3] = 1;
indices[4] = 3;
indices[5] = 0;

indices[6] = 2;
indices[7] = 3;
indices[8] = 1;

indices[9] = 0;
indices[10] = 3;
indices[11] = 2;

float rotationAngle = 0f;
Raylib.UploadMesh(ref tetrahedron, false);
Model model = Raylib.LoadModelFromMesh(tetrahedron);

Image image = Raylib.GenImagePerlinNoise(16, 16, 0, 0, 1000f);
Raylib.ImageBlurGaussian(ref image, 2);
Raylib.ImageColorBrightness(ref image, 100);
Raylib.ImageDither(ref image, 4, 4, 4, 4);
Texture2D texture = Raylib.LoadTextureFromImage(image);
Raylib.UnloadImage(image);

Raylib.SetMaterialTexture(ref model, 0, MaterialMapIndex.MATERIAL_MAP_DIFFUSE, ref texture);

while (!Raylib.WindowShouldClose())
{
    Raylib.UpdateCamera(ref camera, CameraMode.CAMERA_ORBITAL);
    rotationAngle = Raymath.Wrap(rotationAngle += 1f, 0f, 360f);

    Raylib.BeginDrawing();
    Raylib.ClearBackground(Color.BLACK);
    Raylib.BeginMode3D(camera);
    Raylib.DrawModelEx(model, Vector3.Zero, Vector3.UnitX, rotationAngle, Vector3.One, Color.WHITE);
    Raylib.EndMode3D();
    Raylib.EndDrawing();
}
Raylib.UnloadModel(model);
Raylib.UnloadTexture(texture);
Raylib.CloseWindow();

Hello World!

@JupiterRider
Copy link
Contributor

JupiterRider commented Aug 16, 2023

Nice work @n77y!
Now make a pull request for that and the pear is peeled :D

U can also apply an example for that here:
https://github.com/ChrisDill/Raylib-cs-Examples

Side notice:
NativeMemory requires at least .Net 6.

nickyMcDonald added a commit to nickyMcDonald/Raylib-cs that referenced this issue Aug 16, 2023
@chrisdill chrisdill pinned this issue Aug 25, 2023
@chrisdill
Copy link
Owner Author

@n77y Trying out the idea further using your example and I changed my mind on it needing to be separate from the Mesh struct. Updated the pr with a few more suggestions. Once done I will merge it in and add your example for it.

@n77y @JupiterRider Also note that examples have been merged back into the main repo so further development on examples will be done here.

chrisdill pushed a commit that referenced this issue Sep 9, 2023
* Add memory allocation to Raylib.Utils for memory allocation (#179)
* Add utils to Mesh for allocating and accessing mesh attributes (#179)
@chrisdill
Copy link
Owner Author

@JupiterRider @n77y The MeshDemo example has now been added and the utils seem to be working as expected. Plan to experiment more with this and maybe apply the same approach to other resource types in the future.

Thanks for all the help on this issue!

@chrisdill chrisdill unpinned this issue Oct 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants