Got a model to load and appear in the GLArea

This commit is contained in:
2026-08-19 13:34:55 -04:00
parent a67ec7ef24
commit bba7098437
18 changed files with 10141 additions and 9992 deletions
+22
View File
@@ -0,0 +1,22 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl Black_Piece
Ns 650.249991
Ka 0.275000 0.275000 0.275000
Kd 0.107621 0.105609 0.108037
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3
newmtl White_Piece
Ns 650.249991
Ka 0.650000 0.650000 0.650000
Kd 0.882937 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

+12
View File
@@ -0,0 +1,12 @@
# Blender 5.2.0 LTS MTL File: 'prop_sign_traffic01.blend'
# www.blender.org
newmtl Material
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
illum 2
map_Kd props_streetsigns.png
map_d props_streetsigns.png
+82
View File
@@ -0,0 +1,82 @@
# Blender 5.2.0 LTS
# www.blender.org
mtllib prop_sign_traffic01.mtl
o Pole
v 0.178493 1.997270 0.017707
v 0.000000 2.000000 0.000000
v 0.000000 0.000000 -0.020000
v 0.000000 2.000000 -0.020000
v 0.019021 0.000000 -0.006180
v 0.019021 2.000000 -0.006180
v 0.011756 0.000000 0.016180
v 0.011756 2.000000 0.016180
v -0.011756 0.000000 0.016180
v -0.011756 2.000000 0.016180
v -0.019021 0.000000 -0.006180
v -0.019021 2.000000 -0.006180
v -0.178493 1.997270 0.017707
v 0.178493 1.640285 0.017707
v -0.178493 1.640285 0.017707
v -0.178493 1.640285 0.014304
v 0.178493 1.640285 0.014304
v -0.178493 1.997270 0.014304
v 0.178493 1.997270 0.014304
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 0.9511 -0.0000 -0.3090
vn 0.5878 -0.0000 0.8090
vn -0.5878 -0.0000 0.8090
vn -0.9511 -0.0000 -0.3090
vn -0.0000 -0.0000 1.0000
vt 0.010888 0.998435
vt 0.007837 0.996283
vt 0.013939 0.996283
vt 0.061975 0.125000
vt 0.061975 1.000000
vt 0.040605 1.000000
vt 0.040605 0.125000
vt 0.008128 0.999870
vt 0.019236 1.000000
vt 0.019236 0.125000
vt 0.010888 0.999870
vt 0.062500 0.125000
vt 0.062500 1.000000
vt 0.042822 1.000000
vt 0.042822 0.125000
vt 0.013647 0.999870
vt 0.021453 1.000000
vt 0.021453 0.125000
vt 0.269104 0.368067
vt 0.159931 0.368067
vt 0.159931 0.249896
vt 0.269104 0.249896
vt 0.000084 1.000000
vt 0.000084 0.125000
vt 0.499907 0.496094
vt 0.390624 0.496094
vt 0.390624 0.378473
vt 0.499907 0.378473
s 0
usemtl Material
f 2/1/1 6/2/1 4/3/1
s 1
f 3/4/2 4/5/2 6/6/3 5/7/3
s 0
f 2/1/1 8/8/1 6/2/1
s 1
f 5/7/3 6/6/3 8/9/4 7/10/4
s 0
f 2/1/1 10/11/1 8/8/1
s 1
f 7/12/4 8/13/4 10/14/5 9/15/5
s 0
f 2/1/1 12/16/1 10/11/1
s 1
f 9/15/5 10/14/5 12/17/6 11/18/6
s 0
f 1/19/7 13/20/7 15/21/7 14/22/7
f 2/1/1 4/3/1 12/16/1
s 1
f 11/18/6 12/17/6 4/23/2 3/24/2
s 0
f 19/25/7 18/26/7 16/27/7 17/28/7
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+15 -182
View File
@@ -1,189 +1,22 @@
using GL; namespace Worldbuilder.Core
{
namespace Worldbuilder.Core { public class Camera : Object
{
/**
* The position of the camera
*/
public Vector3 position { get; set; }
/** /**
* Encapsulates a camera object. A camera contains a projection matrix and a view matrix. * The direction the camera is facing.
*
* The main purpose of this class is to provide equivalents of legacy OpenGL functions
* removed in modern OpenGL. Advanced features like quaternion-based rotations are beyond
* the scope of this project.
*/ */
public class Camera : Object { public Vector3 direction { get; set; }
private Matrix4x4 projection_matrix;
private Matrix4x4 view_matrix;
private Matrix4x4 result_matrix;
private Matrix4x4 total_matrix;
/** public Camera (Vector3 position = Vector3 (), Vector3 direction = Vector3 (0, 0, -1))
* Instantiates a camera object. {
* this.position = position;
* The projection and view matrices are set to the identity matrix. this.direction = direction;
*/
public Camera() {
reset();
}
private void update() {
result_matrix = projection_matrix;
result_matrix.multiply(view_matrix);
}
/**
* Resets both the projection matrix and the view matrix.
*/
public void reset() {
reset_projection();
reset_view();
}
/**
* Resets the projection matrix, setting it to the identity matrix.
*/
public void reset_projection() {
projection_matrix = Matrix4x4.identity();
update();
}
/**
* Resets the view matrix, setting it to the identity matrix.
*/
public void reset_view() {
view_matrix = Matrix4x4.identity();
update();
}
/**
* Applies the camera to the next model object that will be drawn, by
* multiplying the current projection and view matrices by the model matrix
* passed to the function (which specifies the offset and rotation of the model
* relative to the camera), and binds the matrix data to the specified
* shader uniform variable.
*
* @param uniform_id The shader uniform variable to bind to
* @param model_matrix The model matrix
*/
public void apply(int uniform_id, ref Matrix4x4 model_matrix) {
total_matrix = result_matrix;
total_matrix.multiply(model_matrix);
glUniformMatrix4fv(uniform_id, 1, (GL.GLboolean)false, total_matrix.data);
}
/**
* Sets up a perspective projection. The previous projection matrix is ignored and overwritten.
*
* Replicates the effects of the legacy ``glFrustum`` function within this camera.
* Refer to that function for the complete description of the projection parameters.
*
* @param left The coordinate of the left vertical clipping plane.
* @param right The coordinate of the right vertical clipping plane.
* @param bottom The coordinate of the bottom horizontal clipping plane.
* @param top The coordinate of the top horizontal clipping plane.
* @param near The coordinate of the near vertical clipping plane. Must be positive.
* @param far The coordinate of the far vertical clipping plane. Must be positive.
*/
public void set_frustum_projection(
float left,
float right,
float bottom,
float top,
float near, float far
) {
projection_matrix = Matrix4x4.from_data(
2f * near / (right - left), 0, (right + left) / (right - left), 0,
0, 2f * near / (top - bottom), (top + bottom) / (top - bottom), 0,
0, 0, -(far + near) / (far - near), -2f * far * near / (far - near),
0, 0, -1, 0
);
update();
}
/**
* Sets up a symmetrical perspective projection. The previous projection matrix is ignored and overwritten.
*
* Replicates the effects of the legacy ``gluPerspective`` function within this camera.
* Refer to that function for the complete description of the projection parameters.
*
* @param fovy_deg The field of view angle, in degrees, in the y direction.
* @param aspect The aspect ratio that determines the field of view in the x direction.
* The aspect ratio is the ratio of x (width) to y (height).
* @param near The coordinate of the near vertical clipping plane. Must be positive.
* @param far The coordinate of the far vertical clipping plane. Must be positive.
*/
public void set_perspective_projection(float fovy_deg, float aspect, float near, float far) {
var f = 1 / Math.tanf(MathUtils.DegToRad(fovy_deg / 2));
projection_matrix = Matrix4x4.from_data(
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, -(far + near) / (far - near), -2f * far * near / (far - near),
0, 0, -1, 0
);
update();
}
/**
* Sets up an orthogonal projection. The previous projection matrix is ignored and overwritten.
*
* Replicates the effects of the legacy ``glOrtho`` function within this camera.
* Refer to that function for the complete description of the projection parameters.
*
* @param left The coordinate of the left vertical clipping plane.
* @param right The coordinate of the right vertical clipping plane.
* @param bottom The coordinate of the bottom horizontal clipping plane.
* @param top The coordinate of the top horizontal clipping plane.
* @param near The coordinate of the near vertical clipping plane.
* @param far The coordinate of the far vertical clipping plane.
*/
public void set_ortho_projection(
float left,
float right,
float bottom,
float top,
float near, float far
) {
projection_matrix = Matrix4x4.from_data(
2f / (right - left), 0, 0, (right + left) / (right - left),
0, 2f / (top - bottom), 0, (top + bottom) / (top - bottom),
0, 0, -2f * far * near / (far - near), -(far + near) / (far - near),
0, 0, 0, 1
);
update();
}
/**
* Replicates the effects of the legacy ``gluLookAt`` function within this camera.
* Refer to that function for the complete description of how the camera is positioned.
*
* @param eye The 3D position of the camera
* @param center The 3D position of the point the camera is looking at
* @param up The up-direction
*/
public void look_at(ref Vector3 eye, ref Vector3 center, ref Vector3 up) {
Vector3 l = center;
l.subtract(eye);
l.normalized();
Vector3 uptemp = up;
uptemp.normalized();
Vector3 s = l.cross(uptemp);
Vector3 u = s.cross(l);
view_matrix = Matrix4x4.from_data(
s.x, s.y, s.z, 0,
u.x, u.y, u.z, 0,
-l.x, -l.y, -l.z, 0,
0, 0, 0, 1
);
Vector3 translation = Vector3();
translation.subtract(eye);
view_matrix = view_matrix.translate(translation.x, translation.y, translation.z);
update();
} }
} }
} }
+113
View File
@@ -0,0 +1,113 @@
namespace Worldbuilder.Core
{
public class Material : Object
{
public string id { get; construct; }
public Vector3 ambient_color { get; set; }
public Vector3 diffuse_color { get; set; }
public Vector3 specular_color { get; set; }
public float specular_exponent { get; set; }
public Texture2D? ambient_texture { get; set; }
public Texture2D? diffuse_texture { get; set; }
public Texture2D? specular_texture { get; set; }
public Material (string id) {
Object (id: id);
}
/**
* Parse a .mtl file and return all the materials found.
*/
public static Material[] load_library (string path) throws Error
{
Material[] mats = {};
var data = new Utf8InputStream (resources_open_stream (path, ResourceLookupFlags.NONE));
debug ("loading %s ...", path);
while (data.next_line ()) {
if (data.at_end_of_line || data.skip_char ('#')) { // empty line or comment
continue;
} else if (data.skip_symbol ("newmtl")) {
string mtlname = data.read_string ();
mats += new Material (mtlname);
} else {
if (mats.length == 0)
throw new IOError.INVALID_DATA ("line %u: material undefined here", data.lineno);
if (data.skip_symbol ("Ka")) {
float x = data.parse_float ();
float y = data.parse_float ();
float z = data.parse_float ();
mats[mats.length-1].ambient_color = Vector3 (x, y, z);
} else if (data.skip_symbol ("Kd")) {
float x = data.parse_float ();
float y = data.parse_float ();
float z = data.parse_float ();
mats[mats.length-1].diffuse_color = Vector3 (x, y, z);
} else if (data.skip_symbol ("Ks")) {
float x = data.parse_float ();
float y = data.parse_float ();
float z = data.parse_float ();
mats[mats.length-1].specular_color = Vector3 (x, y, z);
} else if (data.skip_symbol ("Ns")) {
mats[mats.length-1].specular_exponent = data.parse_float ();
} else if (data.skip_symbol ("map_Ka")) {
string filename = Path.get_basename (data.read_string ());
mats[mats.length-1].ambient_texture = new Texture2D (filename);
} else if (data.skip_symbol ("map_Kd")) {
string filename = Path.get_basename (data.read_string ());
mats[mats.length-1].diffuse_texture = new Texture2D (filename);
} else if (data.skip_symbol ("map_Ks")) {
string filename = Path.get_basename (data.read_string ());
mats[mats.length-1].specular_texture = new Texture2D (filename);
}
}
}
return mats;
}
public string to_string ()
{
var sb = new StringBuilder ();
sb.append ("Material ");
sb.append (id);
sb.append (" { ambient: ");
if (ambient_texture != null) {
sb.append ("( ");
sb.append (ambient_color.to_string ());
sb.append (", ");
sb.append (ambient_texture.path);
sb.append_c (')');
} else {
sb.append (ambient_color.to_string ());
}
sb.append (", diffuse: ");
if (diffuse_texture != null) {
sb.append ("( ");
sb.append (diffuse_color.to_string ());
sb.append (", ");
sb.append (diffuse_texture.path);
sb.append_c (')');
} else {
sb.append (diffuse_color.to_string ());
}
sb.append (", specular: ");
if (specular_texture != null) {
sb.append ("( ");
sb.append (specular_color.to_string ());
sb.append (", ");
sb.append (specular_texture.path);
sb.append_c (')');
} else {
sb.append (specular_color.to_string ());
}
sb.append (" }");
return sb.str;
}
}
}
+102
View File
@@ -0,0 +1,102 @@
namespace Worldbuilder.Core
{
public class Mesh : Object
{
public struct Point
{
public Vector3 v;
public Vector2 uv;
public Vector3 n;
public Point (Vector3 v = Vector3(), Vector2 uv = Vector2(), Vector3 n = Vector3())
{
this.v = v;
this.uv = uv;
this.n = n;
}
}
public struct Face
{
public Point p[3];
public string to_string ()
{
return @"($(p[0].v)/$(p[0].uv)/$(p[0].n), $(p[1].v)/$(p[1].uv)/$(p[1].n), $(p[2].v)/$(p[2].uv)/$(p[2].n))";
}
}
public string id { get; construct; }
public GL.uint vao { get; construct; }
public GL.uint vbo { get; construct; }
/**
* Number of vertices in this mesh. Pass this value to {@link GL.draw_arrays}
*/
public int size { get; construct; }
public Material? material { get; set; }
/**
* Construct GL buffers for all vertices, UVs, and normals.
*
* @param id a unique identifier for the mesh
* @param program the current GL program, used to recover attribute locations
* @param material the material for this mesh, or null
*/
public Mesh (string id, Face[] tris, GL.Program program, Material? material)
{
Object (id: id,
vao: GL.gen_vertex_array (),
vbo: GL.gen_buffer (),
size: tris.length * 3);
this.material = material;
// debug (@"mesh $id has $(tris.length) triangle(s):");
// for (var i = 0; i < tris.length; i++)
// debug (@" [$i] = $(tris[i])");
// create one contiguous view of the mesh data
unowned float[] data = (float[]) tris;
GL.bind_vertex_array (vao);
// upload the data
GL.bind_buffer (GL.ARRAY_BUFFER, vbo);
GL.buffer_floats (GL.ARRAY_BUFFER, data, GL.STATIC_DRAW);
// set up the vertex attribute locations for this data
GL.VertexAttribute position_attr = program.get_attrib_location ("position");
GL.VertexAttribute texcoord_attr = program.get_attrib_location ("texcoord");
GL.VertexAttribute normal_attr = program.get_attrib_location ("normal");
debug (@"shader attributes: (position: $position_attr, texcoord: $texcoord_attr, normal: $normal_attr)");
if (position_attr != GL.NULL_ATTRIBUTE) {
position_attr.pointer (3, GL.FLOAT, GL.FALSE, (GL.sizei) ((3 + 2 + 3) * sizeof(float)));
position_attr.enable_array ();
}
if (texcoord_attr != GL.NULL_ATTRIBUTE) {
texcoord_attr.pointer (2, GL.FLOAT, GL.FALSE, (GL.sizei) ((3 + 2 + 3) * sizeof(float)), 3 * sizeof(float));
texcoord_attr.enable_array ();
}
if (normal_attr != GL.NULL_ATTRIBUTE) {
normal_attr.pointer (3, GL.FLOAT, GL.FALSE, (GL.sizei) ((3 + 2 + 3) * sizeof(float)), (3 + 2) * sizeof(float));
normal_attr.enable_array ();
}
GL.bind_vertex_array (0); // unbind VAO
}
~Mesh ()
{
// TODO: GL.delete_vertex_array (vao);
// TODO: delete other buffers
}
public string to_string ()
{
return @"$id (vbo: $vbo)";
}
}
}
+149
View File
@@ -0,0 +1,149 @@
namespace Worldbuilder.Core
{
/**
* An object in the scene.
*/
public class Model : Object
{
public HashTable<string, Mesh> meshes { get; default = new HashTable<string, Mesh> (str_hash, str_equal); }
public HashTable<string, Material> materials { get; default = new HashTable<string, Material> (str_hash, str_equal); }
public Vector3 position { get; set; }
public Vector3 rotation { get; set; }
public Vector3 scale { get; set; default = Vector3 (1.0f, 1.0f, 1.0f); }
public GenericSet<Model> children { get; default = new GenericSet<Model> (null, null); }
public string name { get; construct; }
private static uint next_id = 0;
public uint id { get; construct; }
/**
* Construct a model from a Wavefront object.
*
* @param path GResource path
*/
public Model (string name, string path, GL.Program program) throws Error {
Object (name: name, id: ++next_id);
debug ("created Model \"%s\" with ID %u", name, id);
var data = new Utf8InputStream (resources_open_stream (path, ResourceLookupFlags.NONE));
Vector3[] vertices = {}; // all the vertices in the .obj
Vector2[] uvs = {}; // all the UVs in the .obj
Vector3[] normals = {}; // all the normals in the .obj
Mesh.Face[] faces = {}; // only the faces of the current mesh
string? mesh_id = null;
Material? material = null; // the current material
debug ("loading %s ...", path);
while (data.next_line ()) {
if (data.skip_char ('#')) { // comments
continue;
} else if (data.skip_symbol ("mtllib")) {
string mtllib = data.read_string ();
foreach (var mat in Material.load_library (@"/com/brodybrooks/WorldBuilder/models/$mtllib")) {
materials[mat.id] = mat;
}
} else if (data.skip_symbol ("usemtl")) {
string mat_id = data.read_string ();
if (!materials.contains (mat_id))
throw new IOError.INVALID_DATA ("%s:line %u:%s is not a valid material ID", path, data.lineno, mat_id);
// save a new mesh with the previous material first
if (faces.length > 0) {
var mesh = new Mesh (mesh_id ?? @"Mesh#$(meshes.length)", faces, program, material);
meshes[mesh.id] = mesh;
// reset faces
faces = {};
material = null;
mesh_id = null;
}
material = materials[mat_id];
} else if (data.skip_symbol ("o")) {
if (faces.length > 0) {
var mesh = new Mesh (mesh_id ?? @"Mesh#$(meshes.length)", faces, program, material);
meshes[mesh.id] = mesh;
// reset faces
faces = {};
material = null;
mesh_id = null;
}
mesh_id = data.read_string ();
if (mesh_id.length == 0) // object name is optional
mesh_id = null;
} else if (data.skip_symbol ("v")) {
float x = data.parse_float ();
float y = data.parse_float ();
float z = data.parse_float ();
try {
float w = data.parse_float (); // w
x /= w;
y /= w;
z /= w;
} catch (Error e) { /* ignore */ }
vertices += Vector3 (x, y, z);
} else if (data.skip_symbol ("vt")) { // texture coordinates
float u = data.parse_float ();
float v = data.parse_float ();
try {
data.parse_float (); // w
} catch (Error e) { /* ignore */ }
uvs += Vector2 (u, v);
} else if (data.skip_symbol ("vn")) { // normals
float x = data.parse_float ();
float y = data.parse_float ();
float z = data.parse_float ();
normals += Vector3 (x, y, z).normalized ();
} else if (data.skip_symbol ("f")) { // face
Mesh.Point points[3];
int npoints = 0;
while (!data.at_end_of_line) {
int vi = -1, vt = -1, vn = -1;
vi = data.parse_int ();
if (data.skip_char ('/')) {
if (data.peek_char () != '/')
vt = data.parse_int ();
}
if (data.skip_char ('/'))
vn = data.parse_int ();
if (vi - 1 < 0 || vi - 1 >= vertices.length)
throw new IOError.INVALID_DATA ("%s:line %u: invalid vertex ID %d", path, data.lineno, vi - 1);
points[npoints % 3].v = vertices[vi - 1];
if (vt != -1) {
if (vt - 1 < 0 || vt - 1 >= uvs.length)
throw new IOError.INVALID_DATA ("%s:line %u: invalid texture ID %d", path, data.lineno, vt - 1);
points[npoints % 3].uv = uvs[vt - 1];
}
if (vn != -1) {
if (vn - 1 < 0 || vn - 1 >= normals.length)
throw new IOError.INVALID_DATA ("%s:line %u: invalid normal ID %d", path, data.lineno, vn - 1);
points[npoints % 3].n = normals[vn - 1];
}
npoints++;
if (npoints >= 3) {
faces += Mesh.Face () { p = points };
points[1] = points[2];
npoints += 2;
}
}
}
}
if (faces.length > 0) {
var mesh = new Mesh (mesh_id ?? @"Mesh#$(meshes.length)", faces, program, material);
meshes[mesh.id] = mesh;
faces = {};
material = null;
mesh_id = null;
}
}
}
}
+123
View File
@@ -0,0 +1,123 @@
namespace Worldbuilder.Core
{
public class Utf8InputStream : Object
{
public DataInputStream base_stream { get; construct; }
private string _line = "";
private size_t _line_len = 0;
private size_t _line_offset = 0;
public Utf8InputStream (InputStream base_stream) throws IOError
{
Object (base_stream: new DataInputStream (base_stream));
}
public bool at_end_of_line { get { return _line_offset >= _line_len; } }
public uint lineno { get; private set; }
/**
* Advance to the next line in the data. On the first call, advances to the
* first line.
*/
public bool next_line () throws IOError
{
string? data = base_stream.read_line_utf8 ();
if (data == null)
return false;
_line = data.strip ();
_line_len = _line.length;
_line_offset = 0;
lineno += 1;
return true;
}
public char peek_char ()
{
return _line[(long)_line_offset];
}
public float parse_float () throws IOError
{
float value = 0;
char *p = (char *)_line + _line_offset;
unowned string end_p = (string)p;
while (*p != '\0' && p->isspace ())
p++;
float.try_parse ((string)p, out value, out end_p);
if ((char *)end_p - (char *)p == 0)
throw new IOError.INVALID_DATA ("unexpected character %c in float", end_p[0]);
_line_offset = (char *)end_p - (char *)_line;
return value;
}
public int parse_int () throws IOError
{
int value = 0;
char *p = (char *)_line + _line_offset;
unowned string end_p = (string)p;
while (*p != '\0' && p->isspace ())
p++;
int.try_parse ((string)p, out value, out end_p);
if ((char *)end_p - (char *)p == 0)
throw new IOError.INVALID_DATA ("unexpected character %c in int", end_p[0]);
_line_offset = (char *)end_p - (char *)_line;
return value;
}
/**
* Reads the rest of the current line after any spaces.
*/
public string read_string ()
{
char *p = (char *)_line + _line_offset;
while (*p != '\0' && p->isspace ())
p++;
char *old_p = p;
while (*p != '\0')
p++;
_line_offset = (char *)p - (char *)_line;
return (string)old_p;
}
public bool skip_symbol (string to_skip)
{
char *p = (char *)_line + _line_offset;
while (*p != '\0' && p->isspace ())
p++;
char *old_p = p;
while (*p != '\0' && to_skip[(long)(p - old_p)] == *p)
p++;
if (p - old_p != to_skip.length || *p != '\0' && !p->isspace ())
return false;
_line_offset = p - (char *)_line;
return true;
}
public bool skip_char (char to_skip)
{
char *p = (char *)_line + _line_offset;
if (*p != '\0' && *p == to_skip)
p++;
else
return false;
_line_offset = p - (char *)_line;
return true;
}
}
}
+8 -3
View File
@@ -2,13 +2,18 @@ worldbuilder_sources = [
'main.vala', 'main.vala',
'application.vala', 'application.vala',
'window.vala', 'window.vala',
'scene_renderer.vala',
'core/math.vala',
'core/vector3.vala', 'core/vector3.vala',
'core/matrix4x4.vala', 'core/matrix4x4.vala',
'core/vector2.vala', 'core/vector2.vala',
'core/camera.vala' 'core/camera.vala',
'core/mesh.vala',
'core/texture2d.vala',
'core/material.vala',
'core/model.vala',
'core/utf8inputstream.vala',
'scene_renderer.vala',
] ]
worldbuilder_deps = [ worldbuilder_deps = [
+194 -1
View File
@@ -1,5 +1,198 @@
using GL;
using Worldbuilder.Core;
public class Worldbuilder.SceneRenderer : Object namespace Worldbuilder
{ {
public class SceneRenderer : Object
{
public GL.Program shader { get; private set; }
public GenericSet<Model> scene { get; default = new GenericSet<Model> (null, null); }
public Vector3 ambient_light { get; default = Vector3 (0.6f, 0.6f, 0.6f); }
public Vector3 light_position { get; default = Vector3 (2.0f, 1.0f, 0.0f); }
public Camera camera { get; set; default = new Camera(Vector3(0f, 0.1f, 0.5f)); }
public SceneRenderer () {}
public void Setup(Gtk.Widget widget)
{
print("SceneRenderer / Setting up...\n");
var area = (Gtk.GLArea)widget;
area.make_current();
if (area.get_error() != null)
return;
shader = GL.Program();
var vshader = GL.Shader(GL.VERTEX_SHADER);
try
{
vshader.load("/com/brodybrooks/WorldBuilder/shaders/shader.vert");
vshader.compile();
var fshader = GL.Shader(GL.FRAGMENT_SHADER);
fshader.load("/com/brodybrooks/WorldBuilder/shaders/shader.frag");
fshader.compile();
shader.attach_shader(vshader);
shader.attach_shader(fshader);
shader.link();
}
catch (Error e)
{
printerr("ERROR: %s\n", e.message);
return;
}
GL.use_program(shader);
shader.set_int("AmbientTex", 0);
shader.set_int("DiffuseTex", 1);
// Load the model
try
{
const string model_dir = "/com/brodybrooks/WorldBuilder/models";
var sign = new Model("Sign", @"$model_dir/ChessPiecePawnWhite.obj", shader)
{
position = Vector3(0, 0, 0),
scale = Vector3(4, 4, 4)
};
scene.add(sign);
}
catch (Error e)
{
printerr ("ERROR: %s\n", e.message);
return;
}
area.add_tick_callback( (widget, frame_clock) => {
widget.queue_draw();
return Source.CONTINUE;
});
}
public bool Render(Gtk.GLArea area, Gdk.GLContext context)
{
Error? area_err;
if ((area_err = area.get_error ()) != null)
{
printerr ("GL ERROR: %s\nGL Area Error: %s\n",
GL.get_error ().to_string (),
area_err.message);
return Source.REMOVE;
}
// update transformation
float aspect_ratio = area.get_width () / (float)area.get_height();
var projection_matrix = Matrix4x4.perspective ((float)Math.PI_4, aspect_ratio, 0.1f, 200f);
// construct view matrix from camera
var view_matrix = Matrix4x4.look_at(camera.position, camera.position.add (camera.direction), Vector3 (0, 1, 0));
area.make_current ();
GL.use_program (shader);
// get view from camera position and rotation
shader.set_mat4x4 ("view", ref view_matrix);
shader.set_mat4x4 ("proj", ref projection_matrix);
// ambient color and light position can vary throughout the scene
shader.set_vec3 ("AmbientLight", ambient_light);
shader.set_vec3 ("LightPosition", light_position);
shader.set_vec3 ("CameraPosition", camera.position);
GL.clear_color(0.05f, 0.05f, 0.05f, 1.0f);
GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
scene.foreach (model => render_model (model, Matrix4x4.identity(), shader));
GL.flush();
GL.finish();
return Source.CONTINUE;
}
public void OnResize(Gtk.Widget widget, int width, int height)
{
print("SceneRenderer / Resizing...\n");
}
/**
* This is a recursive procedure because we want to render each child in a
* model hierarchy with a model transform that is relative to the parent
* model.
*/
void render_model (Model model, Matrix4x4 parent_model_matrix, GL.Program program) {
// construct a model matrix from the up-to-date transform
// attributes of the object
var model_matrix = Matrix4x4.identity ()
.scale (model.scale.x, model.scale.y, model.scale.z)
.rotateX (model.rotation.x/180 * (float)Math.PI)
.rotateY (model.rotation.y/180 * (float)Math.PI)
.rotateZ (model.rotation.z/180 * (float)Math.PI)
.translate (model.position.x, model.position.y, model.position.z);
model_matrix = parent_model_matrix.multiply (model_matrix);
program.set_mat4x4 ("model", ref model_matrix);
// render every mesh of the object
model.meshes.foreach ((_, mesh) => {
// update the material
if (mesh.material != null) {
// debug (@"mesh has $(mesh.material)");
program.set_vec3 ("AmbientColor", mesh.material.ambient_color);
if (mesh.material.ambient_texture != null) {
program.set_boolean ("HaveAmbientTex", GL.TRUE);
GL.active_texture (GL.TEXTURE0);
GL.bind_texture (GL.TEXTURE_2D, mesh.material.ambient_texture.id);
} else {
program.set_boolean ("HaveAmbientTex", GL.FALSE);
}
program.set_vec3 ("DiffuseColor", mesh.material.diffuse_color);
if (mesh.material.diffuse_texture != null) {
shader.set_boolean ("HaveDiffuseTex", GL.TRUE);
GL.active_texture (GL.TEXTURE1);
GL.bind_texture (GL.TEXTURE_2D, mesh.material.diffuse_texture.id);
} else {
program.set_boolean ("HaveDiffuseTex", GL.FALSE);
}
program.set_vec3 ("SpecularColor", mesh.material.specular_color);
program.set_float ("SpecularCoeff", mesh.material.specular_exponent);
if (mesh.material.specular_texture != null) {
program.set_boolean ("HaveSpecularTex", GL.TRUE);
GL.active_texture (GL.TEXTURE2);
GL.bind_texture (GL.TEXTURE_2D, mesh.material.specular_texture.id);
} else {
program.set_boolean ("HaveSpecularTex", GL.FALSE);
}
} else {
// reset the shader material
// GL.active_texture (GL.TEXTURE0);
// GL.bind_texture (GL.TEXTURE_2D, 0);
// GL.active_texture (GL.TEXTURE1);
// GL.bind_texture (GL.TEXTURE_2D, 0);
// GL.active_texture (GL.TEXTURE2);
// GL.bind_texture (GL.TEXTURE_2D, 0);
program.set_vec3 ("AmbientColor", Vector3 ());
program.set_vec3 ("DiffuseColor", Vector3 ());
program.set_vec3 ("SpecularColor", Vector3 ());
program.set_float ("SpecularCoeff", 0f);
program.set_boolean ("HaveAmbientTex", GL.FALSE);
program.set_boolean ("HaveDiffuseTex", GL.FALSE);
program.set_boolean ("HaveSpecularTex", GL.FALSE);
}
// render the mesh vertices
GL.bind_vertex_array (mesh.vao);
// GL.bind_buffer (GL.ARRAY_BUFFER, mesh.vbo);
GL.draw_arrays (GL.TRIANGLES, 0, mesh.size);
});
// now render all children
model.children.foreach (child => render_model (child, model_matrix, program));
}
}
} }
+67
View File
@@ -0,0 +1,67 @@
#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 TexCoord;
in vec3 Normal;
in vec3 FragPosition;
// the color of the ambient light
uniform vec3 AmbientLight;
// position of the global light (TODO: have multiple lights)
uniform vec3 LightPosition;
// position of the scene camera
uniform vec3 CameraPosition;
// the strengths of the various lighting types
uniform vec3 AmbientColor;
uniform vec3 DiffuseColor;
uniform vec3 SpecularColor;
uniform float SpecularCoeff;
// the textures
uniform bool HaveAmbientTex;
uniform sampler2D AmbientTex;
uniform bool HaveDiffuseTex;
uniform sampler2D DiffuseTex;
uniform bool HaveSpecularTex;
uniform sampler2D SpecularTex;
// other properties
uniform bool Selected;
void main() {
vec3 ambient = AmbientLight * AmbientColor;
// direction to light
vec3 Li = normalize(LightPosition - FragPosition);
// direction to camera
vec3 v = normalize(CameraPosition - FragPosition);
// half vector (bisects Li and V)
vec3 h = (v + Li) / length(v + Li);
// diffuse lighting
vec3 diffuse = max(dot(Normal, Li), 0.0) * DiffuseColor;
// specular lighting
vec3 specular = pow(max(dot(Normal, h), 0.0), SpecularCoeff) * SpecularColor;
// textures
if (HaveAmbientTex)
ambient *= vec3(texture(AmbientTex, TexCoord));
if (HaveDiffuseTex)
diffuse *= vec3(texture(DiffuseTex, TexCoord));
if (HaveSpecularTex)
specular *= vec3(texture(SpecularTex, TexCoord));
vec3 color = ambient + diffuse + specular;
if (Selected) // highlight the edges of the object if it's selected
color = mix(color, vec3(1.0, 0.0, 0.0), 1.0 - abs(dot(Normal, v)));
outColor = vec4(color, 1.0);
}
+21
View File
@@ -0,0 +1,21 @@
#version 300 es
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 texcoord;
layout(location = 2) in vec3 normal;
out vec2 TexCoord;
out vec3 Normal;
out vec3 FragPosition;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
gl_Position = proj * view * model * vec4(position, 1.0);
TexCoord = texcoord;
// XXX: calculate transpose-inverse of model on the CPU side
Normal = normalize(mat3(transpose(inverse(model))) * normal);
FragPosition = vec3(model * vec4(position, 1.0));
}
+10 -11
View File
@@ -18,23 +18,22 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
namespace Worldbuilder
{
[GtkTemplate (ui = "/com/brodybrooks/WorldBuilder/window.ui")] [GtkTemplate (ui = "/com/brodybrooks/WorldBuilder/window.ui")]
public class Worldbuilder.Window : Adw.ApplicationWindow { public class Window : Adw.ApplicationWindow {
[GtkChild] [GtkChild]
private unowned Gtk.GLArea draw_area; private unowned Gtk.GLArea draw_area;
private SceneRenderer renderer;
public Window (Gtk.Application app) { public Window (Gtk.Application app) {
Object (application: app); Object (application: app);
renderer = new SceneRenderer();
draw_area.render.connect(Render); draw_area.realize.connect(renderer.Setup);
} draw_area.render.connect(renderer.Render);
draw_area.resize.connect(renderer.OnResize);
private bool Render (Gtk.GLArea area, Gdk.GLContext context) }
{
// set the background color
GL.glClearColor (0.1f, 0.2f, 0.4f, 1.0f);
GL.glClear(GL.GL_COLOR_BUFFER_BIT);
return true;
} }
} }
+11
View File
@@ -4,4 +4,15 @@
<file preprocess="xml-stripblanks">window.ui</file> <file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">shortcuts-dialog.ui</file> <file preprocess="xml-stripblanks">shortcuts-dialog.ui</file>
</gresource> </gresource>
<gresource prefix="/com/brodybrooks/WorldBuilder/shaders">
<file alias="shader.vert">shaders/shader.vert</file>
<file alias="shader.frag">shaders/shader.frag</file>
</gresource>
<gresource prefix="/com/brodybrooks/WorldBuilder/models">
<file alias="ChessPiecePawnWhite.obj">../data/ChessPiecePawnWhite.obj</file>
<file alias="ChessPiece.mtl">../data/ChessPiece.mtl</file>
<file alias="Marble_COL_1K.jpg">../data/Marble_COL_1K.jpg</file>
</gresource>
</gresources> </gresources>
+5066 -9780
View File
File diff suppressed because it is too large Load Diff