Archived
Got a model to load and appear in the GLArea
This commit is contained in:
@@ -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 |
@@ -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
|
||||||
@@ -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 |
+16
-183
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
+16
-17
@@ -18,23 +18,22 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[GtkTemplate (ui = "/com/brodybrooks/WorldBuilder/window.ui")]
|
namespace Worldbuilder
|
||||||
public class Worldbuilder.Window : Adw.ApplicationWindow {
|
{
|
||||||
[GtkChild]
|
[GtkTemplate (ui = "/com/brodybrooks/WorldBuilder/window.ui")]
|
||||||
private unowned Gtk.GLArea draw_area;
|
public class Window : Adw.ApplicationWindow {
|
||||||
|
[GtkChild]
|
||||||
|
private unowned Gtk.GLArea draw_area;
|
||||||
|
|
||||||
public Window (Gtk.Application app) {
|
private SceneRenderer renderer;
|
||||||
Object (application: app);
|
|
||||||
|
|
||||||
draw_area.render.connect(Render);
|
public Window (Gtk.Application app) {
|
||||||
|
Object (application: app);
|
||||||
|
renderer = new SceneRenderer();
|
||||||
|
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+5071
-9785
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user