Initial commit, with a basic window and GLArea

This commit is contained in:
2026-08-18 13:49:46 -04:00
parent 661631d993
commit b394340223
26 changed files with 15427 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
/* application.vala
*
* Copyright 2026 Brody Brooks
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
public class Worldbuilder.Application : Adw.Application {
public Application () {
Object (
application_id: "com.brodybrooks.WorldBuilder",
flags: ApplicationFlags.DEFAULT_FLAGS,
resource_base_path: "/com/brodybrooks/WorldBuilder"
);
}
construct {
ActionEntry[] action_entries = {
{ "about", this.on_about_action },
{ "preferences", this.on_preferences_action },
{ "quit", this.quit }
};
this.add_action_entries (action_entries, this);
this.set_accels_for_action ("app.quit", {"<control>q"});
}
public override void activate () {
base.activate ();
var win = this.active_window ?? new Worldbuilder.Window (this);
win.present ();
}
private void on_about_action () {
string[] developers = { "Brody Brooks" };
var about = new Adw.AboutDialog () {
application_name = "WorldBuilder",
application_icon = "com.brodybrooks.WorldBuilder",
developer_name = "Brody Brooks",
translator_credits = _("translator-credits"),
version = "0.1.0",
developers = developers,
copyright = "© 2026 Brody Brooks",
};
about.present (this.active_window);
}
private void on_preferences_action () {
message ("app.preferences action activated");
}
}
+6
View File
@@ -0,0 +1,6 @@
[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
namespace Config {
public const string GETTEXT_PACKAGE;
public const string LOCALEDIR;
public const string PACKAGE_VERSION;
}
+191
View File
@@ -0,0 +1,191 @@
/**
* A 4-by-4 matrix useful in OpenGL matrix operations.
*/
public struct Ch3.Mat4x4 {
public float data[4*4];
/**
* Create a new matrix with a value along its diagonal.
*/
public Mat4x4 (float diagonal_value) {
for (int i = 0; i < 4; i++)
this[i, i] = diagonal_value;
}
public Mat4x4.identity () {
this (1.0f);
}
/**
* Construct a perspective transform.
*
* @param fov field of view, angle in radians
* @param aspect_ratio width / height
* @param near distance to near plane along the z axis
* @param far distance to far plane along the z axis
*/
public Mat4x4.perspective (float fov, float aspect_ratio, float near, float far) {
// see http://www.songho.ca/opengl/gl_projectionmatrix.html
float height_2 = Math.tanf (fov / 2) * near;
float width_2 = aspect_ratio * height_2;
this[0, 0] = near / width_2;
this[1, 1] = near / height_2;
this[2, 2] = -(far + near) / (far - near);
this[2, 3] = -2 * far * near / (far - near);
this[3, 2] = -1;
}
/**
* Constructs a transformation matrix for a camera looking at `target` from
* `position`.
*
* @param position the position of the camera
* @param target what the camera is looking at
* @param up a direction that represents "up"
*/
public Mat4x4.look_at (Vec3 position, Vec3 target, Vec3 up) {
// see https://learnopengl.com/Getting-started/Camera
up = up.normalized ();
var direction = position.subtract (target).normalized ();
var right = up.cross (direction).normalized ();
up = direction.cross (right).normalized ();
this[0, 0] = right.x;
this[0, 1] = right.y;
this[0, 2] = right.z;
this[0, 3] = right.x * (-position.x) + right.y + (-position.y) + right.z * (-position.z);
this[1, 0] = up.x;
this[1, 1] = up.y;
this[1, 2] = up.z;
this[1, 3] = up.x * (-position.x) + up.y * (-position.y) + up.z * (-position.z);
this[2, 0] = direction.x;
this[2, 1] = direction.y;
this[2, 2] = direction.z;
this[2, 3] = direction.x * (-position.x) + direction.y * (-position.y) + direction.z * (-position.z);
this[3, 3] = 1;
}
public void set (int row, int col, float value) {
data[col*4 + row] = value;
}
public float get (int row, int col) {
return data[col*4 + row];
}
public Mat4x4 write (int row, int col, float value) {
var copy = this;
copy[row, col] = value;
return copy;
}
public Mat4x4 rotateX (float radians) {
// see https://learnopengl.com/Getting-started/Transformations
var copy = this;
// rotate around x axis
float sin_x, cos_x;
Math.sincosf (radians, out sin_x, out cos_x);
for (int col = 0; col < 4; col++) {
// copy[0, col] = this[0, col];
copy[1, col] = cos_x * this[1, col] - sin_x * this[2, col];
copy[2, col] = sin_x * this[1, col] + cos_x * this[2, col];
// copy[3, col] = this[3, col];
}
return copy;
}
public Mat4x4 rotateY (float radians) {
var copy = this;
// rotate around y axis
float sin_y, cos_y;
Math.sincosf (radians, out sin_y, out cos_y);
for (int col = 0; col < 4; col++) {
copy[0, col] = cos_y * this[0, col] + sin_y * this[2, col];
// copy[1, col] = this[1, col];
copy[2, col] = -sin_y * this[0, col] + cos_y * this[2, col];
// copy[3, col] = this[3, col];
}
return copy;
}
public Mat4x4 rotateZ (float radians) {
var copy = this;
// rotate around z axis
float sin_z, cos_z;
Math.sincosf (radians, out sin_z, out cos_z);
for (int col = 0; col < 4; col++) {
copy[0, col] = cos_z * this[0, col] - sin_z * this[1, col];
copy[1, col] = sin_z * this[0, col] + cos_z * this[1, col];
// copy[2, col] = this[2, col];
// copy[3, col] = this[3, col];
}
return copy;
}
public Mat4x4 scale (float x, float y, float z) {
var copy = this;
copy[0, 0] *= x;
copy[1, 1] *= y;
copy[2, 2] *= z;
return copy;
}
public Mat4x4 translate (float x, float y, float z) {
var copy = this;
copy[0, 3] += x;
copy[1, 3] += y;
copy[2, 3] += z;
return copy;
}
public Mat4x4 multiply (Mat4x4 other) {
var result = Mat4x4 (0);
// this x other = result
for (int row = 0; row < 4; row++)
for (int col = 0; col < 4; col++)
for (int k = 0; k < 4; k++) // FIXME: codegen with result[row, col] += ... versus = ... + ...
result[row, col] = result[row, col] + this[row, k] * other[k, col];
return result;
}
public string to_string (string format = "%f") {
string vals[4*4];
int maxlen = 0;
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
vals[col*4 + row] = this[row, col].to_string (format);
var len = vals[col*4 + row].length;
if (len > maxlen)
maxlen = len;
}
}
var sb = new StringBuilder ();
for (int row = 0; row < 4; row++) {
sb.append ("| ");
for (int col = 0; col < 4; col++) {
var len = vals[col*4 + row].length;
sb.append (vals[col*4 + row]);
for (int i = 0; i < maxlen - len; i++)
sb.append_c (' ');
if (col < 3)
sb.append_c (' ');
}
sb.append (" |\n");
}
return sb.str;
}
}
+14
View File
@@ -0,0 +1,14 @@
public struct Worldbuilder.Vector2 {
public float x;
public float y;
public Vec2 (float x = 0, float y = 0) {
this.x = x;
this.y = y;
}
public string to_string () {
return @"<$x,$y>";
}
}
+44
View File
@@ -0,0 +1,44 @@
public struct Worldbuilder.Vector3 {
public float x;
public float y;
public float z;
public Vector3 (float x = 0, float y = 0, float z = 0)
{
this.x = x;
this.y = y;
this.z = z;
}
public Vector3 normalized ()
requires (length >= 1e-9)
{
return Vector3 (x/length, y/length, z/length);
}
public float length
{
get { return Math.sqrtf (x*x + y*y + z*z); }
}
public Vector3 add (Vector3 other)
{
return Vector3 (x + other.x, y + other.y, z + other.z);
}
public Vector3 subtract (Vector3 other)
{
return Vector3 (x - other.x, y - other.y, z - other.z);
}
public Vector3 cross (Vector3 other)
{
return Vector3 (y * other.z - z * other.y, -(x * other.z - z * other.x), x * other.y - y * other.x);
}
public string to_string ()
{
return @"<$x,$y,$z>";
}
}
+28
View File
@@ -0,0 +1,28 @@
/* main.vala
*
* Copyright 2026 Brody Brooks
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
int main (string[] args) {
Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
Intl.textdomain (Config.GETTEXT_PACKAGE);
var app = new Worldbuilder.Application ();
return app.run (args);
}
+24
View File
@@ -0,0 +1,24 @@
worldbuilder_sources = [
'main.vala',
'application.vala',
'window.vala',
]
worldbuilder_deps = [
config_dep,
dependency('gtk4', version: '>= 4.2'),
dependency('libadwaita-1', version: '>= 1.4'),
cc.find_library('epoxy'),
valac.find_library('gl', dirs: meson.source_root() / 'vapi'),
]
worldbuilder_sources += gnome.compile_resources('worldbuilder-resources',
'worldbuilder.gresource.xml',
c_name: 'worldbuilder'
)
executable('worldbuilder', worldbuilder_sources,
dependencies: worldbuilder_deps,
include_directories: config_inc,
install: true,
)
+5
View File
@@ -0,0 +1,5 @@
public class Worldbuilder.SceneRenderer : Object
{
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="AdwShortcutsDialog" id="shortcuts_dialog">
<child>
<object class="AdwShortcutsSection">
<property name="title" translatable="yes">Shortcuts</property>
<child>
<object class="AdwShortcutsItem">
<property name="title" translatable="yes" context="shortcut window">Show Shortcuts</property>
<property name="action-name">app.shortcuts</property>
</object>
</child>
<child>
<object class="AdwShortcutsItem">
<property name="title" translatable="yes" context="shortcut window">Quit</property>
<property name="action-name">app.quit</property>
</object>
</child>
</object>
</child>
</object>
</interface>
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="Adw" version="1.0"/>
<template class="WorldbuilderWindow" parent="AdwApplicationWindow">
<property name="title" translatable="yes">WorldBuilder</property>
<style>
<class name="devel"/>
</style>
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="content">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<child type="end">
<object class="GtkMenuButton">
<property name="primary">True</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="tooltip-text" translatable="yes">Main Menu</property>
<property name="menu-model">primary_menu</property>
</object>
</child>
</object>
</child>
<property name="content">
<object class="GtkGLArea" id="draw_area">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</property>
</object>
</property>
</template>
<menu id="primary_menu">
<section>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
<attribute name="action">app.shortcuts</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_About WorldBuilder</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
</interface>
+40
View File
@@ -0,0 +1,40 @@
/* window.vala
*
* Copyright 2026 Brody Brooks
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
[GtkTemplate (ui = "/com/brodybrooks/WorldBuilder/window.ui")]
public class Worldbuilder.Window : Adw.ApplicationWindow {
[GtkChild]
private unowned Gtk.GLArea draw_area;
public Window (Gtk.Application app) {
Object (application: app);
draw_area.render.connect(Render);
}
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;
}
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/brodybrooks/WorldBuilder">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">shortcuts-dialog.ui</file>
</gresource>
</gresources>