Introduced scene maps and a model loader for external models

This commit is contained in:
zyb3rwolfi 2025-11-18 15:04:29 +00:00
parent cf8eb3f5fd
commit 1fd8aada02
9 changed files with 209 additions and 91 deletions

View File

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AssimpNet" Version="5.0.0-beta1" />
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="StbImageSharp" Version="2.30.15" />
</ItemGroup>

View File

@ -0,0 +1,77 @@
namespace TheLabs;
using System;
using System.Collections.Generic;
using System.IO;
using Assimp; // The library we just installed
using OpenTK.Mathematics;
public static class ModelLoader
{
public static Mesh LoadMesh(string path)
{
var importer = new AssimpContext();
var scene = importer.ImportFile(path, PostProcessSteps.Triangulate | PostProcessSteps.JoinIdenticalVertices);
if (scene == null || scene.SceneFlags.HasFlag(SceneFlags.Incomplete) || scene.RootNode == null)
{
throw new Exception("Error loading model: " + path);
}
// --- NEW LOGIC: Combine all meshes into one ---
List<float> allVertices = new List<float>();
List<uint> allIndices = new List<uint>();
// We need to track how many vertices we've added so far
// so the indices point to the correct new location
uint indexOffset = 0;
// Loop through EVERY mesh found in the file (Legs, Torso, Head, etc.)
foreach (var mesh in scene.Meshes)
{
// 1. Process Vertices for this specific part
for (int i = 0; i < mesh.VertexCount; i++)
{
// Position (x, y, z)
var pos = mesh.Vertices[i];
allVertices.Add(pos.X);
allVertices.Add(pos.Y);
allVertices.Add(pos.Z);
// Texture Coordinates (u, v)
if (mesh.HasTextureCoords(0))
{
var uv = mesh.TextureCoordinateChannels[0][i];
allVertices.Add(uv.X);
allVertices.Add(uv.Y);
}
else
{
allVertices.Add(0.0f);
allVertices.Add(0.0f);
}
}
// 2. Process Indices for this specific part
for (int i = 0; i < mesh.FaceCount; i++)
{
var face = mesh.Faces[i];
for (int j = 0; j < face.IndexCount; j++)
{
// CRITICAL: We add the offset to the index!
// If the leg had 100 vertices, the first vertex of the torso
// needs to be index 100, not 0.
allIndices.Add((uint)(face.Indices[j] + indexOffset));
}
}
// Increase the offset by the number of vertices we just added
indexOffset += (uint)mesh.VertexCount;
}
// Return one giant Mesh containing the whole character
return new Mesh(allVertices.ToArray(), allIndices.ToArray(), Mesh.VertexLayout.PosTex);
}
}

View File

@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL4;
using Assimp;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
@ -15,23 +16,21 @@ namespace TheLabs
// Negative X coordinates move to the left, positive X move to the right.
// Negative Y coordinates move to the bottom, positive Y move to the top.
// OpenGL only supports rendering in 3D, so to create a flat triangle, the Z coordinate will be kept as 0.
private SceneNode _rootNode;
private SceneNode _characterNode;
private SceneNode _buildingNode;
private Shader _shader;
private Camera _camera;
// - Resources that only need to be created once
private Mesh _cubeMesh;
private Mesh _circleMesh;
private Mesh _cylinderMesh;
private Texture _cubeTexture;
private Texture _cubeTexture2;
private float _rotation;
private Mesh _exampleObject;
private Mesh _buildingObject;
private Texture _texture;
private RenderObject _render;
private RenderObject _buildingRender;
float _rotation = 0.0f;
// -- Scene Objects
private RenderObject _cubeObject;
private RenderObject _cubeObject2;
private RenderObject _circleObject;
private RenderObject _cylinderObject;
private Bus _bus;
private Vector3 _cameraPosition = new Vector3(0.0f, 0.0f, -3.0f);
private Vector3 _cameraFront = new Vector3(0.0f, 0.0f, -1.0f);
@ -62,21 +61,32 @@ namespace TheLabs
//GL.Disable(EnableCap.CullFace);
//GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
// Set The background color to a nice blue.
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_camera = new Camera(_cameraPosition, _cameraFront, _cameraUp);
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_texture = new Texture("Textures/texture-a.png");
_rootNode = new SceneNode();
_exampleObject = ModelLoader.LoadMesh("Objects/cat.obj");
_buildingObject = ModelLoader.LoadMesh("Objects/building1.obj");
_render = new RenderObject(_exampleObject, _shader, _texture);
_buildingRender = new RenderObject(_buildingObject, _shader, _texture);
_characterNode = new SceneNode(_render);
_characterNode.Position = new Vector3(0.0f, -1.0f, 0.0f);
_rootNode.AddChild(_characterNode);
_buildingNode = new SceneNode(_buildingRender);
_buildingNode.Position = new Vector3(2.0f, -1.0f, 0.0f);
_rootNode.AddChild(_buildingNode);
CursorState = CursorState.Grabbed;
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_cubeTexture = new Texture("Textures/placeholder.png");
_cubeTexture2 = new Texture("Textures/placeholder.png");
_bus = new Bus(_cubeTexture, _shader);
}
@ -105,9 +115,8 @@ namespace TheLabs
0.1f,
100.0f
);
// --- Draw Scene Objects ---
_bus.DrawBus(view, projection);
_rootNode.Draw(view, projection, Matrix4.Identity);
SwapBuffers();
}

View File

@ -28,7 +28,7 @@ public class RenderObject
Shader.Use();
Shader.SetInt("uTexture", 0);
// 2. Bind Texture
Texture.Use(); // Assumes you have a Texture class
Texture.Use();
// 3. Calculate Model Matrix
Matrix4 model = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position);
int modelLoc = GL.GetUniformLocation(Shader.Handle, "model");

View File

@ -0,0 +1,45 @@
using OpenTK.Mathematics;
using System.Collections.Generic;
namespace TheLabs;
public class SceneNode
{
public RenderObject? RenderObject;
public List<SceneNode> Children = new List<SceneNode>();
public Vector3 Position = Vector3.Zero;
public Quaternion Rotation = Quaternion.Identity;
public Vector3 Scale = Vector3.One;
public SceneNode(RenderObject? renderObject = null)
{
RenderObject = renderObject;
}
public void AddChild(SceneNode child)
{
Children.Add(child);
}
public void Draw(Matrix4 view, Matrix4 projection, Matrix4 parentTransform)
{
Matrix4 localTransform = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position);
Matrix4 globalTransform = localTransform * parentTransform;
if (RenderObject != null)
{
RenderObject.Position = Vector3.Zero;
RenderObject.Rotation = Quaternion.Identity;
RenderObject.Scale = Vector3.One;
RenderObject.Draw(view, projection);
}
foreach (var child in Children)
{
child.Draw(view, projection, globalTransform);
}
}
}

View File

@ -5,85 +5,70 @@ namespace TheLabs.Shapes;
public class Bus
{
public SceneNode MainNode;
private Mesh _wheelMesh;
private RenderObject _wheelObject;
private Mesh _bodyMesh;
private Mesh _roofMesh;
private Texture _wheelTexture;
private Shader _wheelShader;
private Texture _bodyTexture;
public Bus(Texture texture, Shader shader)
public Bus(Texture wheelTexture,Texture bodyTexture, Shader shader)
{
_wheelTexture = new Texture("Textures/rubber.jpg");
_wheelShader = shader;
_bodyTexture = new Texture("Textures/rust.png");
}
public void DrawWheel(Matrix4 view, Matrix4 projection, Vector3 position)
{
Matrix4 localTransform = Matrix4.Identity;
//Matrix4 comboTransform = localTransform * _wheelObject.Transform;
_wheelMesh = ShapeFactory.CreateTexturedCylinder();
_wheelObject = new RenderObject(_wheelMesh, _wheelShader, _wheelTexture);
_bodyMesh = ShapeFactory.CreateTexturedCube();
_roofMesh = ShapeFactory.CreateTexturedCylinder();
// Front Left Wheel
_wheelObject.Position = new Vector3(position);
_wheelObject.Scale = new Vector3(0.3f, 0.1f, 0.2f);
_wheelObject.Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(90.0f));
MainNode = new SceneNode();
_wheelObject.Draw(view, projection);
// Building the body
var bodyObj = new RenderObject(_bodyMesh, shader, bodyTexture);
var bodyNode = new SceneNode(bodyObj);
bodyNode.Scale = new Vector3(1.5f, 0.5f, 0.5f);
bodyNode.Scale = new Vector3(0.0f, 0.0f, 0.0f);
MainNode.AddChild(bodyNode);
}
public void drawBody(Matrix4 view, Matrix4 projection)
{
Mesh bodyMesh = ShapeFactory.CreateTexturedCube();
RenderObject bodyObject = new RenderObject(bodyMesh, _wheelShader, _bodyTexture);
// Building the body
var roofObj = new RenderObject(_roofMesh, shader, bodyTexture);
var roofNode = new SceneNode(roofObj);
roofNode.Position = new Vector3(0.0f, 0.25f, 0.0f);
roofNode.Scale = new Vector3(0.5f, 1.5f, .25f);
roofNode.Rotation = Quaternion.FromAxisAngle(Vector3.UnitY, MathHelper.DegreesToRadians(90.0f));
roofNode.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(90.0f));
MainNode.AddChild(roofNode);
bodyObject.Position = new Vector3(0.0f, 0.0f, 0.0f);
bodyObject.Scale = new Vector3(1.5f, 0.5f, 0.5f);
// Building the wheels
bodyObject.Draw(view, projection);
}
private void drawRoof(Matrix4 view, Matrix4 projection)
{
Mesh roofMesh = ShapeFactory.CreateTexturedCylinder();
RenderObject roofObject = new RenderObject(roofMesh, _wheelShader, _bodyTexture);
roofObject.Position = new Vector3(0.0f, 0.25f, 0.0f);
roofObject.Scale = new Vector3(0.5f, 1.5f, .25f);
roofObject.Rotation = Quaternion.FromAxisAngle(Vector3.UnitY, MathHelper.DegreesToRadians(90.0f));
roofObject.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(90.0f));
roofObject.Draw(view, projection);
}
public void DrawBus(Matrix4 view, Matrix4 projection)
{
// Drawing 4 wheels
float xOffset = 0.4f; // Reduced to bring wheels closer to the body
float zOffset = 0.28f; // Reduced to bring front and back wheels closer
float yOffset = -0.3f; // Adjusted to ensure wheels touch the body
// Drawing 4 wheels
for (int i = 0; i < 4; i++)
{
float xPosition = (i % 2 == 0) ? -xOffset : xOffset; // Left or Right
float zPosition = (i < 2) ? zOffset : -zOffset; // Front or Back
DrawWheel(view, projection, new Vector3(xPosition, yOffset, zPosition));
}
Vector3[] wheelPositions =
{
new Vector3(-xOffset, yOffset, zOffset), // Front Left
new Vector3(xOffset, yOffset, zOffset), // Front Right
new Vector3(-xOffset, yOffset, -zOffset), // Back Left
new Vector3(xOffset, yOffset, -zOffset) // Back Right
};
// Drawing the body
drawBody(view, projection);
// Drawing the roof
drawRoof(view, projection);
foreach (var pos in wheelPositions)
{
var wheelObj = new RenderObject(_wheelMesh, shader, wheelTexture);
var wheelNode = new SceneNode(wheelObj);
wheelNode.Position = pos; // Relative to the MainNode!
wheelNode.Scale = new Vector3(0.3f, 0.1f, 0.2f);
// Rotate cylinder to act like a wheel
wheelNode.Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(90.0f));
MainNode.AddChild(wheelNode);
}
}
public void Draw(Matrix4 view, Matrix4 projection)
{
MainNode.Draw(view, projection, Matrix4.Identity);
}

View File

@ -18,7 +18,6 @@ public class Texture : IDisposable
GL.BindTexture(TextureTarget.Texture2D, Handle);
// --- Set texture parameters ---
// Repeat the texture if UVs go outside [0, 1]
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

View File

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AssimpNet" Version="5.0.0-beta1" />
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="StbImageSharp" Version="2.30.15" />
</ItemGroup>

View File

@ -10,6 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AssimpNet" Version="5.0.0-beta1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />