diff --git a/examples/graph-definition.xml b/examples/graph-definition.xml
deleted file mode 100644
index 0ca9d9b..0000000
--- a/examples/graph-definition.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/settings.gradle b/settings.gradle
index 07223db..e58984f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,4 +2,5 @@ rootProject.name = 'threedom'
include 'threedom-kapt'
include 'threedom'
-
+include 'threedom-gl'
+include 'threedom-example'
diff --git a/threedom-example/build.gradle b/threedom-example/build.gradle
new file mode 100644
index 0000000..6ddd520
--- /dev/null
+++ b/threedom-example/build.gradle
@@ -0,0 +1,67 @@
+import org.gradle.internal.os.OperatingSystem
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm' version '1.3.72'
+ id 'java'
+ id 'application'
+}
+
+group 'me.eater.threedom'
+version '1.0-SNAPSHOT'
+
+switch (OperatingSystem.current()) {
+ case OperatingSystem.LINUX:
+ project.ext.lwjglNatives = "natives-linux"
+ break
+ case OperatingSystem.MAC_OS:
+ project.ext.lwjglNatives = "natives-macos"
+ break
+ case OperatingSystem.WINDOWS:
+ project.ext.lwjglNatives = "natives-windows"
+ break
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ implementation platform("org.lwjgl:lwjgl-bom:3.2.3")
+
+ implementation project(":threedom")
+ implementation project(":threedom-gl")
+
+ implementation "org.lwjgl:lwjgl"
+ implementation "org.lwjgl:lwjgl-opengl"
+ implementation "org.lwjgl:lwjgl-glfw"
+
+ implementation 'org.joml:joml:1.9.24'
+
+ runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
+ runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
+ runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
+ runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
+}
+
+application {
+ mainClassName = 'me.eater.threedom.example.MainKt'
+}
+
+compileKotlin {
+ kotlinOptions.jvmTarget = "12"
+}
+compileTestKotlin {
+ kotlinOptions.jvmTarget = "12"
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_12
+ targetCompatibility = JavaVersion.VERSION_12
+}
+
+
+run {
+
+ jvmArgs("-javaagent:$projectDir/../tools/lwjglx-debug-1.0.0.jar")
+}
diff --git a/threedom-example/src/main/kotlin/me/eater/threedom/example/main.kt b/threedom-example/src/main/kotlin/me/eater/threedom/example/main.kt
new file mode 100644
index 0000000..0e04d61
--- /dev/null
+++ b/threedom-example/src/main/kotlin/me/eater/threedom/example/main.kt
@@ -0,0 +1,251 @@
+package me.eater.threedom.example
+
+import me.eater.threedom.dom.Document
+import me.eater.threedom.dom.IDocument
+import me.eater.threedom.dom.PlainNode
+import me.eater.threedom.dom.createNode
+import me.eater.threedom.example.shader.Plain
+import me.eater.threedom.example.vertex.Simple
+import me.eater.threedom.gl.GL
+import me.eater.threedom.gl.ShaderPreProcessor
+import me.eater.threedom.gl.dom.PerspectiveCamera
+import me.eater.threedom.gl.dom.TriMesh
+import me.eater.threedom.gl.texture.Texture
+import me.eater.threedom.gl.vertex.VertexArrayObject
+import me.eater.threedom.gl.vertex.VertexBuffer
+import me.eater.threedom.utils.joml.toFloat
+import me.eater.threedom.utils.joml.translation
+import me.eater.threedom.utils.joml.vec3
+import org.joml.Vector2f
+import org.joml.Vector3f
+import org.lwjgl.glfw.GLFW.*
+import org.lwjgl.glfw.GLFWErrorCallback
+import org.lwjgl.opengl.GL.createCapabilities
+import org.lwjgl.opengl.GL11.*
+import org.lwjgl.system.MemoryStack.stackPush
+import org.lwjgl.system.MemoryUtil.NULL
+import java.time.Instant
+import kotlin.math.PI
+import kotlin.math.cos
+import kotlin.math.sin
+
+fun main() {
+ GLFWErrorCallback.createPrint(System.err).set()
+
+ if (!glfwInit()) {
+ println("Unable to initialize GLFW")
+ return
+ }
+
+ glfwDefaultWindowHints()
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4)
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4)
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
+
+ val window = glfwCreateWindow(300, 300, "Hello world", NULL, NULL)
+ if (window == NULL) {
+ println("Failed creating window")
+ return
+ }
+
+ val stack = stackPush()
+ val pWidth = stack.mallocInt(1)
+ val pHeight = stack.mallocInt(1)
+
+ // Get the window size passed to glfwCreateWindow
+ glfwGetWindowSize(window, pWidth, pHeight)
+
+ // Get the resolution of the primary monitor
+ val vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()) ?: return
+
+ // Center the window
+ glfwSetWindowPos(
+ window,
+ (vidmode.width() - pWidth.get(0)) / 2,
+ (vidmode.height() - pHeight.get(0)) / 2
+ )
+
+
+ // Make the OpenGL context current
+ glfwMakeContextCurrent(window)
+
+ var width = pWidth.get(0)
+ var height = pHeight.get(0)
+
+ // Enable v-sync
+ glfwSwapInterval(1)
+ glfwShowWindow(window)
+ createCapabilities()
+
+ val doc: IDocument = Document()
+
+ val triMesh = doc.createNode()
+
+ val preProcessor = ShaderPreProcessor.createDefault()
+
+ val mesh = listOf(
+ -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
+ 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
+ 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
+ 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
+ -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
+ -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
+
+ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
+ 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
+ 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
+ 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
+ -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
+ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
+
+ -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
+ -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
+ -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
+
+ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
+ 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
+ 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
+ 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
+ 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
+
+ -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
+ 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
+ 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
+ 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
+ -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
+ -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
+
+ -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
+ -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
+ -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
+ )
+
+ val buffer = mesh
+ .chunked(6)
+ .map {
+ Simple().apply {
+ position = vec3(it[0], it[1], it[2])
+ normal = vec3(it[3], it[4], it[5])
+ }
+ }
+ .let {
+ VertexBuffer(it)
+ }
+
+ val VAO = VertexArrayObject {
+ buffer(Simple::position)
+ buffer(Simple::normal)
+ }
+
+ triMesh.mesh = VAO
+ println("Loading mesh")
+ println("Loading shader program")
+ val plainShader = Plain.load(preProcessor)
+ val lampShader = Plain.lamp(preProcessor)
+
+ val texture = Texture()
+ texture.load(Simple::class.java.getResourceAsStream("/textures/fujiwara.jpg").readAllBytes())
+ plainShader.apply {
+ objectColor = vec3(1.0, 0.5, 0.0)
+ lightColor = vec3(1.0, 1.0, 1.0)
+ }
+
+ triMesh.use(lampShader)
+
+ val empty = doc.createNode()
+
+ doc.addNode(empty)
+ empty.addNode(triMesh)
+
+ val around = mutableListOf()
+
+ val amount = 3
+ val deg = (PI * 2) / amount
+ for (i in 0 until amount) {
+ val trimeshDup = triMesh.clone()
+ trimeshDup.use(plainShader)
+ around.add(trimeshDup)
+ triMesh.addNode(trimeshDup)
+
+ trimeshDup.model {
+ translate(2 * sin(deg * i), 0.0, 2 * cos(deg * i))
+ }
+ }
+
+ val camera = doc.createNode()
+ doc.addNode(camera)
+
+ glfwSetFramebufferSizeCallback(window) { _, newWidth, newHeight ->
+ glViewport(0, 0, newWidth, newHeight)
+
+ camera.width = newWidth.toDouble()
+ camera.height = newHeight.toDouble()
+ }
+
+ glfwSetKeyCallback(window) { _, key, _, action, _ ->
+ if ((key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) && action == GLFW_RELEASE)
+ glfwSetWindowShouldClose(window, true)
+
+
+ var z = 0.0
+ var x = 0.0
+ val speed = 0.1
+ if (key == GLFW_KEY_UP && action != GLFW_RELEASE) {
+ z += speed
+ }
+
+ if (key == GLFW_KEY_DOWN && action != GLFW_RELEASE) {
+ z -= speed
+ }
+
+ if (key == GLFW_KEY_LEFT && action != GLFW_RELEASE) {
+ x += speed
+ }
+
+ if (key == GLFW_KEY_RIGHT && action != GLFW_RELEASE) {
+ x -= speed
+ }
+
+ if (x != 0.0 || z != 0.0) {
+ camera.model {
+ translateLocal(x, 0.0, z)
+ }
+ }
+ }
+
+ println("Starting render loop")
+ glClearColor(0.3f, 0.2f, 0.2f, 1.0f)
+ glEnable(GL_DEPTH_TEST)
+ while (!glfwWindowShouldClose(window)) {
+ glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
+
+ empty.model {
+ setTranslation(0.0, 0.0, sin(Instant.now().toEpochMilli().toDouble() / 1000) - 3)
+ }
+
+ triMesh.model {
+ rotateY(0.005)
+ }
+
+ around.forEach {
+ it.model {
+ rotateY(-0.005)
+ }
+ }
+
+ plainShader.lightPos = triMesh.absolute.translation.toFloat()
+ plainShader.viewPos = camera.absolute.translation.toFloat()
+ doc.render(GL, camera)
+
+ glfwSwapBuffers(window)
+ glfwPollEvents()
+ }
+}
+
diff --git a/threedom-example/src/main/kotlin/me/eater/threedom/example/shader/Plain.kt b/threedom-example/src/main/kotlin/me/eater/threedom/example/shader/Plain.kt
new file mode 100644
index 0000000..ad11ebe
--- /dev/null
+++ b/threedom-example/src/main/kotlin/me/eater/threedom/example/shader/Plain.kt
@@ -0,0 +1,60 @@
+package me.eater.threedom.example.shader
+
+import me.eater.threedom.gl.Shader
+import me.eater.threedom.gl.ShaderPreProcessor
+import me.eater.threedom.gl.ShaderProgram
+
+class Plain private constructor(vertexShader: Shader, fragmentShader: Shader) :
+ ShaderProgram(vertexShader, fragmentShader) {
+
+ var objectColor by vec3()
+ var lightColor by vec3()
+ var lightPos by vec3()
+ var viewPos by vec3()
+
+ companion object {
+ fun load(preProcessor: ShaderPreProcessor): Plain {
+ val vertex = preProcessor.compile(
+ this::class.java.getResourceAsStream("/shaders/plain/vertex.glsl").readAllBytes()
+ .toString(Charsets.UTF_8),
+ Shader.ShaderType.Vertex
+ )
+
+ val fragment = preProcessor.compile(
+ this::class.java.getResourceAsStream("/shaders/plain/fragment.glsl").readAllBytes()
+ .toString(Charsets.UTF_8),
+ Shader.ShaderType.Fragment
+
+ )
+ return Plain(
+ vertex,
+ fragment
+ ).also {
+ vertex.delete()
+ fragment.delete()
+ }
+ }
+
+ fun lamp(preProcessor: ShaderPreProcessor): Plain {
+ val vertex = preProcessor.compile(
+ this::class.java.getResourceAsStream("/shaders/plain/vertex.glsl").readAllBytes()
+ .toString(Charsets.UTF_8),
+ Shader.ShaderType.Vertex
+ )
+
+ val fragment = preProcessor.compile(
+ this::class.java.getResourceAsStream("/shaders/plain/fragment_lamp.glsl").readAllBytes()
+ .toString(Charsets.UTF_8),
+ Shader.ShaderType.Fragment
+
+ )
+ return Plain(
+ vertex,
+ fragment
+ ).also {
+ vertex.delete()
+ fragment.delete()
+ }
+ }
+ }
+}
diff --git a/threedom-example/src/main/kotlin/me/eater/threedom/example/vertex/Simple.kt b/threedom-example/src/main/kotlin/me/eater/threedom/example/vertex/Simple.kt
new file mode 100644
index 0000000..ef1105f
--- /dev/null
+++ b/threedom-example/src/main/kotlin/me/eater/threedom/example/vertex/Simple.kt
@@ -0,0 +1,8 @@
+package me.eater.threedom.example.vertex
+
+import me.eater.threedom.gl.vertex.VertexData
+
+class Simple : VertexData() {
+ var position by vec3()
+ var normal by vec3()
+}
diff --git a/threedom-example/src/main/resources/shaders/plain/fragment.glsl b/threedom-example/src/main/resources/shaders/plain/fragment.glsl
new file mode 100644
index 0000000..55730ba
--- /dev/null
+++ b/threedom-example/src/main/resources/shaders/plain/fragment.glsl
@@ -0,0 +1,32 @@
+#version 330 core
+out vec4 FragColor;
+
+in vec3 FragPos;
+in vec3 Normal;
+in vec3 LightPos; // extra in variable, since we need the light position in view space we calculate this in the vertex shader
+
+uniform vec3 lightColor;
+uniform vec3 objectColor;
+
+void main()
+{
+ // ambient
+ float ambientStrength = 0.1;
+ vec3 ambient = ambientStrength * lightColor;
+
+ // diffuse
+ vec3 norm = normalize(Normal);
+ vec3 lightDir = normalize(LightPos - FragPos);
+ float diff = max(dot(norm, lightDir), 0.0);
+ vec3 diffuse = diff * lightColor;
+
+ // specular
+ float specularStrength = 0.5;
+ vec3 viewDir = normalize(-FragPos); // the viewer is always at (0,0,0) in view-space, so viewDir is (0,0,0) - Position => -Position
+ vec3 reflectDir = reflect(-lightDir, norm);
+ float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
+ vec3 specular = specularStrength * spec * lightColor;
+
+ vec3 result = (ambient + diffuse + specular) * objectColor;
+ FragColor = vec4(result, 1.0);
+}
diff --git a/threedom-example/src/main/resources/shaders/plain/fragment_lamp.glsl b/threedom-example/src/main/resources/shaders/plain/fragment_lamp.glsl
new file mode 100644
index 0000000..fbbe624
--- /dev/null
+++ b/threedom-example/src/main/resources/shaders/plain/fragment_lamp.glsl
@@ -0,0 +1,7 @@
+#version 330 core
+
+out vec4 FragColor;
+
+void main() {
+ FragColor = vec4(1.0);
+}
diff --git a/threedom-example/src/main/resources/shaders/plain/vertex.glsl b/threedom-example/src/main/resources/shaders/plain/vertex.glsl
new file mode 100644
index 0000000..e9c60ad
--- /dev/null
+++ b/threedom-example/src/main/resources/shaders/plain/vertex.glsl
@@ -0,0 +1,21 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec3 aNormal;
+
+out vec3 FragPos;
+out vec3 Normal;
+out vec3 LightPos;
+
+uniform vec3 lightPos; // we now define the uniform in the vertex shader and pass the 'view space' lightpos to the fragment shader. lightPos is currently in world space.
+
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+
+void main()
+{
+ gl_Position = projection * view * model * vec4(aPos, 1.0);
+ FragPos = vec3(view * model * vec4(aPos, 1.0));
+ Normal = mat3(transpose(inverse(view * model))) * aNormal;
+ LightPos = vec3(view * vec4(lightPos, 1.0)); // Transform world-space light position to view-space light position
+}
diff --git a/threedom-example/src/main/resources/textures/fujiwara.jpg b/threedom-example/src/main/resources/textures/fujiwara.jpg
new file mode 100644
index 0000000..0891122
Binary files /dev/null and b/threedom-example/src/main/resources/textures/fujiwara.jpg differ
diff --git a/threedom-gl/build.gradle b/threedom-gl/build.gradle
new file mode 100644
index 0000000..08f16e6
--- /dev/null
+++ b/threedom-gl/build.gradle
@@ -0,0 +1,37 @@
+plugins {
+ id 'org.jetbrains.kotlin.jvm' version '1.3.72'
+ id 'java'
+}
+
+group 'me.eater.threedom'
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation platform("org.lwjgl:lwjgl-bom:3.2.3")
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ implementation "org.jetbrains.kotlin:kotlin-reflect"
+ implementation project(":threedom")
+
+ implementation 'org.joml:joml:1.9.24'
+ implementation "org.lwjgl:lwjgl"
+ implementation "org.lwjgl:lwjgl-opengl"
+ implementation "org.lwjgl:lwjgl-stb"
+}
+
+compileKotlin {
+ kotlinOptions.jvmTarget = "12"
+}
+compileTestKotlin {
+ kotlinOptions.jvmTarget = "12"
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_12
+ targetCompatibility = JavaVersion.VERSION_12
+}
+
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/GL.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/GL.kt
new file mode 100644
index 0000000..35b7b7b
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/GL.kt
@@ -0,0 +1,11 @@
+package me.eater.threedom.gl
+
+import me.eater.threedom.dom.render.IRenderTarget
+import me.eater.threedom.gl.dom.ICamera
+import org.lwjgl.system.MemoryStack
+import org.lwjgl.system.MemoryStack.stackPush
+
+object GL : IRenderTarget> {
+ override val type = "GL"
+ val stack: MemoryStack = stackPush()
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/Shader.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/Shader.kt
new file mode 100644
index 0000000..46f4902
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/Shader.kt
@@ -0,0 +1,32 @@
+package me.eater.threedom.gl
+
+import me.eater.threedom.gl.GL.stack
+import me.eater.threedom.gl.exception.ShaderCompilationException
+import org.lwjgl.opengl.GL20.*
+import org.lwjgl.system.MemoryUtil
+
+data class Shader(val shaderId: Int, val shaderType: ShaderType) {
+ enum class ShaderType(val glId: Int) {
+ Fragment(GL_FRAGMENT_SHADER),
+ Vertex(GL_VERTEX_SHADER);
+ }
+
+ fun delete() {
+ glDeleteShader(shaderId)
+ }
+
+ companion object {
+ fun create(source: String, shaderType: ShaderType): Shader {
+ val shaderId = glCreateShader(shaderType.glId)
+ glShaderSource(shaderId, source)
+ glCompileShader(shaderId)
+ val success = stack.mallocInt(1)
+ glGetShaderiv(shaderId, GL_COMPILE_STATUS, success)
+ if (success.get(0) == 0) {
+ throw ShaderCompilationException(glGetShaderInfoLog(shaderId))
+ }
+
+ return Shader(shaderId, shaderType)
+ }
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/ShaderPreProcessor.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/ShaderPreProcessor.kt
new file mode 100644
index 0000000..e80f406
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/ShaderPreProcessor.kt
@@ -0,0 +1,46 @@
+package me.eater.threedom.gl
+
+class ShaderPreProcessor {
+ private val regex = Regex("(?[^:]+)::(?[^:(]+)\\((?.+)\\)")
+ private val calls: MutableMap, (List) -> String> = mutableMapOf()
+
+ fun process(input: String): String =
+ input.split("\n").joinToString("\n") {
+ if (it.startsWith("#[") && it.endsWith("]")) {
+ val macro = it.substring(2 until it.length - 1)
+ val match = regex.find(macro) ?: return@joinToString it
+ val module = match.groups["module"]!!.value
+ val call = match.groups["call"]!!.value
+ val args = match.groups["args"]!!.value.split(",").map { arg -> arg.trim() }.toList()
+
+ calls[module to call]?.invoke(args) ?: throw RuntimeException("Can't find macro $module::$call")
+ } else {
+ it
+ }
+ }
+
+ fun register(module: String, call: String, block: (List) -> String) {
+ calls[module to call] = block
+ }
+
+ fun compile(source: String, type: Shader.ShaderType): Shader {
+ return Shader.create(process(source), type)
+ }
+
+ companion object {
+ fun createDefault() = ShaderPreProcessor().apply {
+ register("3dom", "import") { it ->
+ val knownUniforms = mapOf(
+ "model" to "mat4",
+ "view" to "mat4",
+ "projection" to "mat4",
+ "normalModel" to "mat3"
+ )
+
+ it.joinToString("\n") { uni ->
+ "uniform ${knownUniforms[uni]} $uni;"
+ }
+ }
+ }
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/ShaderProgram.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/ShaderProgram.kt
new file mode 100644
index 0000000..7ade9a3
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/ShaderProgram.kt
@@ -0,0 +1,220 @@
+package me.eater.threedom.gl
+
+import me.eater.threedom.gl.GL.stack
+import me.eater.threedom.gl.dom.ICamera
+import me.eater.threedom.gl.exception.ShaderProgramLinkingException
+import me.eater.threedom.gl.texture.Texture
+import me.eater.threedom.utils.joml.toFloat
+import org.joml.*
+import org.lwjgl.opengl.GL20.*
+import org.lwjgl.system.MemoryUtil
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+open class ShaderProgram(private val programId: Int = glCreateProgram()) {
+ private val uniforms: MutableMap = mutableMapOf()
+ val queuedUniformValues: MutableMap Unit> = mutableMapOf()
+ private val textures: MutableMap = mutableMapOf()
+ private var textureIndex: Int = 0
+
+ var model by mat4()
+ var normalModel by mat3()
+ var view by mat4()
+ var projection by mat4()
+
+ private fun getUniformLocation(name: String) = uniforms.getOrPut(name) {
+ glGetUniformLocation(programId, name)
+ }
+
+ protected fun sampler2D(
+ index: Int = textureIndex++,
+ name: String? = null
+ ): ReadWriteProperty {
+ var setUniform = false
+
+ return object : ReadWriteProperty {
+ override fun getValue(thisRef: ShaderProgram, property: KProperty<*>): Texture? {
+ return Texture.getTexture(textures[index] ?: return null)
+ }
+
+ override fun setValue(thisRef: ShaderProgram, property: KProperty<*>, value: Texture?) {
+ textures[index] = value?.id ?: 0
+
+ if (setUniform) {
+ return
+ }
+
+ val loc = getUniformLocation(name ?: property.name)
+ if (loc == -1) {
+ return
+ }
+
+ if (glGetInteger(GL_CURRENT_PROGRAM) == thisRef.programId) {
+ glUniform1i(loc, index)
+ } else {
+ queuedUniformValues[loc] = {
+ glUniform1i(loc, index)
+ }
+ }
+
+ setUniform = true
+ }
+ }
+ }
+
+ protected fun uniform(
+ name: String? = null,
+ setter: (location: Int, value: T) -> Unit,
+ getter: (programId: Int, location: Int) -> T?
+ ) = UniformProperty(name, setter, getter)
+
+ open class UniformProperty(
+ val name: String? = null,
+ val setter: (location: Int, value: T) -> Unit,
+ val getter: (programId: Int, location: Int) -> T?
+ ) {
+ operator fun getValue(thisRef: ShaderProgram, property: KProperty<*>): T? {
+ val loc = thisRef.getUniformLocation(name ?: property.name)
+ if (loc == -1) {
+ return null
+ }
+
+ return getter(thisRef.programId, loc)
+ }
+
+ operator fun setValue(thisRef: ShaderProgram, property: KProperty<*>, value: T?) {
+ val valueNonNull = value ?: return
+
+ val loc = thisRef.getUniformLocation(name ?: property.name)
+ if (loc == -1) {
+ return
+ }
+
+
+ if (glGetInteger(GL_CURRENT_PROGRAM) == thisRef.programId) {
+ setter(loc, valueNonNull)
+ } else {
+ thisRef.queuedUniformValues[loc] = {
+ setter(loc, valueNonNull)
+ }
+ }
+ }
+ }
+
+ protected fun float(name: String? = null): UniformProperty =
+ uniform(name, ::glUniform1f, ::glGetUniformf)
+
+ protected fun int(name: String? = null): UniformProperty =
+ uniform(name, ::glUniform1i, ::glGetUniformi)
+
+ protected fun vec2(name: String? = null): UniformProperty =
+ uniform(name, { loc, value ->
+ val floatArray = MemoryUtil.memAllocFloat(2)
+ value.get(floatArray)
+ glUniform2fv(loc, floatArray)
+ MemoryUtil.memFree(floatArray)
+ }, { programId, location ->
+ val buffer = FloatArray(2)
+ glGetUniformfv(programId, location, buffer)
+ Vector2f().set(buffer)
+ })
+
+ protected fun vec3(name: String? = null): UniformProperty =
+ uniform(name, { loc, value ->
+ val floatArray = MemoryUtil.memAllocFloat(3)
+ value.get(floatArray)
+ glUniform3fv(loc, floatArray)
+ MemoryUtil.memFree(floatArray)
+ }, { programId, location ->
+ val buffer = FloatArray(3)
+ glGetUniformfv(programId, location, buffer)
+ Vector3f().set(buffer)
+ })
+
+ protected fun vec4(name: String? = null): UniformProperty =
+ uniform(name, { loc, value ->
+ val floatArray = MemoryUtil.memAllocFloat(4)
+ value.get(floatArray)
+ glUniform4fv(loc, floatArray)
+ MemoryUtil.memFree(floatArray)
+ }, { programId, location ->
+ val buffer = FloatArray(4)
+ glGetUniformfv(programId, location, buffer)
+ Vector4f().set(buffer)
+ })
+
+ protected fun mat3(name: String? = null): UniformProperty =
+ uniform(name, { loc, value ->
+ val floatArray = FloatArray(3 * 3)
+ value.get(floatArray)
+ glUniformMatrix3fv(loc, false, floatArray)
+ }, { programId, location ->
+ val buffer = FloatArray(3 * 3)
+ glGetUniformfv(programId, location, buffer)
+ Matrix3f().set(buffer)
+ })
+
+ protected fun bool(name: String? = null): UniformProperty =
+ uniform(name, { loc, value ->
+ glUniform1i(loc, if (value) 1 else 0)
+ }, { programId, location ->
+ glGetUniformi(programId, location) == 1
+ })
+
+ protected fun mat4(name: String? = null): UniformProperty =
+ uniform(name, { loc, value ->
+ val floatArray = FloatArray(4 * 4)
+ value.get(floatArray)
+ glUniformMatrix4fv(loc, false, floatArray)
+ }, { programId, location ->
+ val buffer = FloatArray(4 * 4)
+ glGetUniformfv(programId, location, buffer)
+ Matrix4f().set(buffer)
+ })
+
+ fun enable() {
+ glUseProgram(programId)
+
+ for ((_, queued) in queuedUniformValues) {
+ queued()
+ }
+
+ for ((index, texture) in textures) {
+ glActiveTexture(GL_TEXTURE0 + index)
+ glBindTexture(GL_TEXTURE_2D, texture)
+ }
+
+ queuedUniformValues.clear()
+ }
+
+ fun delete() {
+ glDeleteProgram(programId)
+ }
+
+ fun disable() {
+ glUseProgram(0)
+ }
+
+ fun uses(name: String): Boolean {
+ return getUniformLocation(name) != -1
+ }
+
+
+
+ fun camera(renderer: ICamera<*>) {
+ this.projection = renderer.projection.toFloat()
+ this.view = renderer.view.toFloat()
+ }
+
+ constructor(vertexShader: Shader, fragmentShader: Shader) : this() {
+ glAttachShader(programId, vertexShader.shaderId)
+ glAttachShader(programId, fragmentShader.shaderId)
+
+ glLinkProgram(programId)
+ val success = stack.mallocInt(1)
+ glGetProgramiv(programId, GL_LINK_STATUS, success)
+ if (success.get(0) == 0) {
+ throw ShaderProgramLinkingException(glGetProgramInfoLog(programId))
+ }
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/GLRenderNode.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/GLRenderNode.kt
new file mode 100644
index 0000000..0de2a39
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/GLRenderNode.kt
@@ -0,0 +1,10 @@
+package me.eater.threedom.gl.dom
+
+import me.eater.threedom.dom.render.IRenderNode
+import me.eater.threedom.gl.GL
+
+interface GLRenderNode>> :
+ IRenderNode> {
+ override val target: GL
+ get() = GL
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/ICamera.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/ICamera.kt
new file mode 100644
index 0000000..6c00e16
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/ICamera.kt
@@ -0,0 +1,12 @@
+package me.eater.threedom.gl.dom
+
+import me.eater.threedom.dom.INode
+import org.joml.Matrix4dc
+
+interface ICamera> : INode {
+ var width: Double
+ var height: Double
+
+ val projection: Matrix4dc
+ val view: Matrix4dc
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/PerspectiveCamera.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/PerspectiveCamera.kt
new file mode 100644
index 0000000..e5b2edd
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/PerspectiveCamera.kt
@@ -0,0 +1,53 @@
+package me.eater.threedom.gl.dom
+
+import me.eater.threedom.dom.IDocument
+import me.eater.threedom.dom.Node
+import org.joml.Matrix4d
+import org.joml.Matrix4dc
+
+class PerspectiveCamera(document: IDocument?) : Node(document), ICamera {
+ override fun cloneSelf(): PerspectiveCamera =
+ PerspectiveCamera(document)
+
+ var fieldOfView: Double = 45.0
+ set(value) {
+ field = value
+ updateProjection()
+ }
+
+ var far: Double = 100.0
+ set(value) {
+ field = value
+ updateProjection()
+ }
+
+ var near: Double = 0.1
+ set(value) {
+ field = value
+ updateProjection()
+ }
+
+ override var width: Double = 800.0
+ set(value) {
+ field = value
+ updateProjection()
+ }
+
+ override var height: Double = 600.0
+ set(value) {
+ field = value
+ updateProjection()
+ }
+
+ private fun updateProjection() {
+ projection = makeProjection()
+ }
+
+ private fun makeProjection(): Matrix4dc = Matrix4d().perspective(fieldOfView, width / height, near, far)
+
+ override var projection: Matrix4dc = makeProjection()
+ private set
+
+ override val view: Matrix4dc
+ get() = absolute
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/TriMesh.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/TriMesh.kt
new file mode 100644
index 0000000..ae99605
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/dom/TriMesh.kt
@@ -0,0 +1,49 @@
+package me.eater.threedom.gl.dom
+
+import me.eater.threedom.dom.IDocument
+import me.eater.threedom.dom.Node
+import me.eater.threedom.gl.ShaderProgram
+import me.eater.threedom.gl.vertex.VertexArrayObject
+import me.eater.threedom.utils.joml.toFloat
+import org.joml.Matrix3d
+import org.joml.Matrix4d
+import org.lwjgl.opengl.GL30.*
+
+open class TriMesh(document: IDocument?) : Node(document), GLRenderNode {
+ var mesh: VertexArrayObject? = null
+ var program: ShaderProgram? = null
+ private set
+ var programConfig: (ShaderProgram.(TriMesh) -> Unit)? = null
+ private set
+
+ fun use(program: T, block: T.(TriMesh) -> Unit) {
+ this.program = program
+ @kotlin.Suppress("UNCHECKED_CAST")
+ this.programConfig = block as ShaderProgram.(TriMesh) -> Unit
+ }
+
+ fun use(program: T) {
+ this.program = program
+ this.programConfig = null
+ }
+
+ override fun cloneSelf(): TriMesh {
+ return TriMesh(document).also {
+ it.mesh = mesh
+ it.program = program
+ it.programConfig = programConfig
+ }
+ }
+
+ override fun render(renderer: ICamera<*>) {
+ val mesh = mesh ?: return
+ val program = program ?: return
+ program.enable()
+ programConfig?.let { program.it(this) }
+ program.camera(renderer)
+ program.model = absolute.toFloat()
+ program.normalModel = model.invert(Matrix4d()).transpose().get3x3(Matrix3d()).toFloat()
+ glBindVertexArray(mesh.id)
+ glDrawArrays(GL_TRIANGLES, 0, mesh.size)
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/exception/ShaderCompilationException.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/exception/ShaderCompilationException.kt
new file mode 100644
index 0000000..37b22d1
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/exception/ShaderCompilationException.kt
@@ -0,0 +1,3 @@
+package me.eater.threedom.gl.exception
+
+data class ShaderCompilationException(val infoLog: String) : RuntimeException("Shader failed compiling: $infoLog")
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/exception/ShaderProgramLinkingException.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/exception/ShaderProgramLinkingException.kt
new file mode 100644
index 0000000..2838ede
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/exception/ShaderProgramLinkingException.kt
@@ -0,0 +1,4 @@
+package me.eater.threedom.gl.exception
+
+data class ShaderProgramLinkingException(val infoLog: String) :
+ RuntimeException("Failed linking ShaderProgram: $infoLog")
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/geometry/Plane.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/geometry/Plane.kt
new file mode 100644
index 0000000..6e4cc27
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/geometry/Plane.kt
@@ -0,0 +1,8 @@
+package me.eater.threedom.gl.geometry
+
+import me.eater.threedom.dom.IDocument
+import me.eater.threedom.dom.INode
+import me.eater.threedom.gl.dom.TriMesh
+
+class Plane(document: IDocument?) : TriMesh(document) {
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/texture/Texture.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/texture/Texture.kt
new file mode 100644
index 0000000..2a03363
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/texture/Texture.kt
@@ -0,0 +1,55 @@
+package me.eater.threedom.gl.texture
+
+import me.eater.threedom.gl.GL.stack
+import org.lwjgl.opengl.GL30.*
+import org.lwjgl.stb.STBImage.stbi_load_from_memory
+import org.lwjgl.stb.STBImage.stbi_set_flip_vertically_on_load
+import org.lwjgl.system.MemoryUtil
+import java.lang.ref.WeakReference
+import java.nio.ByteBuffer
+
+class Texture {
+ var id: Int = run {
+ val pTexture = stack.mallocInt(1)
+ glGenTextures(pTexture)
+ pTexture.get(0).also {
+ textures[it] = WeakReference(this)
+ }
+ }
+
+ fun load(image: ByteArray, flipped: Boolean = true, mipmapLevel: Int = 0) {
+ val mem = MemoryUtil.memAlloc(image.size)
+ mem.put(image)
+ mem.rewind()
+ load(mem, flipped, mipmapLevel)
+ MemoryUtil.memFree(mem)
+ }
+
+ fun load(image: ByteBuffer, flipped: Boolean = true, mipmapLevel: Int = 0) {
+ stbi_set_flip_vertically_on_load(flipped)
+ val pWidth = stack.mallocInt(1)
+ val pHeight = stack.mallocInt(1)
+ val pChannels = stack.mallocInt(1)
+ val imagePixelData = stbi_load_from_memory(image, pWidth, pHeight, pChannels, 4)
+ glBindTexture(GL_TEXTURE_2D, id)
+ glTexImage2D(
+ GL_TEXTURE_2D,
+ mipmapLevel,
+ GL_RGBA8,
+ pWidth.get(0),
+ pHeight.get(0),
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ imagePixelData
+ )
+ glGenerateMipmap(GL_TEXTURE_2D)
+ glBindTexture(GL_TEXTURE_2D, 0)
+ }
+
+ companion object {
+ private val textures = mutableMapOf>()
+
+ fun getTexture(id: Int): Texture? = textures[id]?.get()
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexArrayObject.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexArrayObject.kt
new file mode 100644
index 0000000..3f8c3cf
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexArrayObject.kt
@@ -0,0 +1,78 @@
+package me.eater.threedom.gl.vertex
+
+import me.eater.threedom.gl.GL.stack
+import org.lwjgl.opengl.GL30.*
+import kotlin.math.min
+import kotlin.reflect.KProperty1
+
+class VertexArrayObject() {
+ var size: Int = 0
+
+ val id = run {
+ val pId = stack.mallocInt(1)
+ glGenVertexArrays(pId)
+ pId.get(0)
+ }
+
+ constructor(block: VertexArrayObject.() -> Unit) : this() {
+ block(this)
+ }
+
+ var pointerIndex: Int = 0
+
+ operator fun VertexBuffer.invoke(prop: KProperty1) =
+ bind(this, prop)
+
+ fun bind(vertexBuffer: VertexBuffer, prop: KProperty1) {
+ if (vertexBuffer.items.isEmpty()) {
+ return
+ }
+
+ size = if (pointerIndex == 0) {
+ vertexBuffer.items.size
+ } else {
+ min(size, vertexBuffer.items.size)
+ }
+
+ val first = vertexBuffer.items.first()
+ val item = first.getDataPoint(prop) ?: return
+ var size = item.slots
+ var length = item.length
+ var repeat = 1
+
+ var i = 4
+ while (size > 4) {
+ if ((size % i) == 0) {
+ repeat = size / i
+ size = i
+ length /= i
+ break
+ }
+
+ i--
+ }
+
+ val stride = if (vertexBuffer.interleaved) {
+ vertexBuffer.stride
+ } else {
+ item.length
+ }
+
+ var offset = first.getOffset(prop)!!
+
+ if (!vertexBuffer.interleaved) {
+ offset *= vertexBuffer.items.size
+ }
+
+ glBindVertexArray(id)
+ glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer.id)
+ for (x in 0 until repeat) {
+ val pointer = pointerIndex++
+ glVertexAttribPointer(pointer, size, item.type, false, stride, offset + (x * length).toLong())
+ glEnableVertexAttribArray(pointer)
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
+ glBindVertexArray(0)
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexBuffer.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexBuffer.kt
new file mode 100644
index 0000000..b129803
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexBuffer.kt
@@ -0,0 +1,45 @@
+package me.eater.threedom.gl.vertex
+
+import me.eater.threedom.gl.GL.stack
+import org.lwjgl.opengl.GL30.*
+import org.lwjgl.system.MemoryUtil
+import java.nio.ByteBuffer
+
+open class VertexBuffer(
+ items: List = listOf(),
+ val interleaved: Boolean = false,
+ val usage: Int = GL_STATIC_DRAW
+) {
+ val id: Int = run {
+ val pId = stack.mallocInt(1)
+ glGenBuffers(pId)
+ pId.get(0)
+ }
+
+ @Suppress("CanBePrimaryConstructorProperty")
+ open val items: List = items
+ var stride: Int
+ private set
+
+ init {
+ val size = items.size * (items.firstOrNull()?.length ?: 0).also { stride = it }
+ val byteBuffer = MemoryUtil.memAlloc(size)
+
+ var offset = 0
+ for (i in items.indices) {
+ val item = items[i]
+
+ if (interleaved) {
+ item.write(offset, byteBuffer)
+ offset += item.length
+ } else {
+ item.writeNonInterleaved(i, items.size, byteBuffer)
+ }
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, id)
+ glBufferData(GL_ARRAY_BUFFER, byteBuffer, usage)
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
+ MemoryUtil.memFree(byteBuffer)
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexBufferMutable.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexBufferMutable.kt
new file mode 100644
index 0000000..a4cc90f
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexBufferMutable.kt
@@ -0,0 +1,3 @@
+package me.eater.threedom.gl.vertex
+
+class VertexBufferMutable(override val items: MutableList = mutableListOf()) : VertexBuffer()
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexData.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexData.kt
new file mode 100644
index 0000000..14c00b3
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexData.kt
@@ -0,0 +1,56 @@
+package me.eater.threedom.gl.vertex
+
+import me.eater.threedom.gl.vertex.VertexDataPoint.*
+import org.joml.*
+import java.nio.ByteBuffer
+import kotlin.reflect.KProperty
+import kotlin.reflect.KProperty1
+
+abstract class VertexData() {
+ private val items = linkedMapOf>>()
+ private var offset = 0
+
+ val length
+ get() = items.map { (_, v) -> v.component2().length }.sum()
+
+ class VertexDataRegister(private val default: T, val provider: (default: T) -> VertexDataPoint) {
+ operator fun provideDelegate(thisRef: VertexData, prop: KProperty<*>): VertexDataPoint {
+ val delegate = provider(default)
+ thisRef.items[prop.name] = thisRef.offset to delegate
+ thisRef.offset += delegate.length
+ return delegate
+ }
+ }
+
+ fun int(value: Int = 0): VertexDataRegister = VertexDataRegister(value, ::SingleInt)
+ fun double(value: Double = 0.0): VertexDataRegister = VertexDataRegister(value, ::SingleDouble)
+ fun float(value: Float = 0f): VertexDataRegister = VertexDataRegister(value, ::SingleFloat)
+ fun bool(value: Boolean = false): VertexDataRegister = VertexDataRegister(value, ::SingleBoolean)
+ fun vec2(value: Vector2fc = Vector2f()): VertexDataRegister = VertexDataRegister(value, ::Vec2)
+ fun vec3(value: Vector3fc = Vector3f()): VertexDataRegister = VertexDataRegister(value, ::Vec3)
+ fun vec4(value: Vector4fc = Vector4f()): VertexDataRegister = VertexDataRegister(value, ::Vec4)
+ fun mat3(value: Matrix3fc = Matrix3f()): VertexDataRegister = VertexDataRegister(value, ::Mat3)
+ fun mat4(value: Matrix4fc = Matrix4f()): VertexDataRegister = VertexDataRegister(value, ::Mat4)
+
+ fun getDataPoint(prop: KProperty1): VertexDataPoint<*>? = items[prop.name]?.component2()
+
+ fun getOffset(prop: KProperty1): Int? = items[prop.name]?.component1()
+
+ fun writeNonInterleaved(position: Int, size: Int, byteBuffer: ByteBuffer) {
+ var offset = 0;
+ for ((_, pair) in items) {
+ val (_, item) = pair
+ item.write(offset + (position * item.length), byteBuffer)
+ offset += item.length * size
+ }
+ }
+
+ fun write(position: Int, byteBuffer: ByteBuffer) {
+ var pos = position
+ for ((_, pair) in items) {
+ val (_, item) = pair
+ item.write(pos, byteBuffer)
+ pos += item.length
+ }
+ }
+}
diff --git a/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexDataPoint.kt b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexDataPoint.kt
new file mode 100644
index 0000000..140fd1e
--- /dev/null
+++ b/threedom-gl/src/main/kotlin/me/eater/threedom/gl/vertex/VertexDataPoint.kt
@@ -0,0 +1,73 @@
+package me.eater.threedom.gl.vertex
+
+import org.joml.*
+import org.lwjgl.opengl.GL30.*
+import java.nio.ByteBuffer
+import kotlin.reflect.KProperty
+
+abstract class VertexDataPoint(val type: Int, var value: T, val length: Int, val slots: Int = 1) {
+ abstract fun write(position: Int, buffer: ByteBuffer)
+
+ operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return value
+ }
+
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ this.value = value
+ }
+
+ class SingleInt(value: Int) : VertexDataPoint(GL_INT, value, 4) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ buffer.putInt(position, value)
+ }
+
+ }
+
+ class SingleFloat(value: Float) : VertexDataPoint(GL_FLOAT, value, 4) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ buffer.putFloat(position, value)
+ }
+ }
+
+ class SingleDouble(value: Double) : VertexDataPoint(GL_DOUBLE, value, 8) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ buffer.putDouble(position, value)
+ }
+ }
+
+ class SingleBoolean(value: Boolean) : VertexDataPoint(GL_BOOL, value, 1) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ buffer.put(position, if (value) 1 else 2)
+ }
+ }
+
+ class Vec2(value: Vector2fc) : VertexDataPoint(GL_FLOAT, value, 8, 2) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ value.get(position, buffer)
+ }
+ }
+
+ class Vec3(value: Vector3fc) : VertexDataPoint(GL_FLOAT, value, 12, 3) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ value.get(position, buffer)
+ }
+ }
+
+ class Vec4(value: Vector4fc) : VertexDataPoint(GL_FLOAT, value, 16, 4) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ value.get(position, buffer)
+ }
+ }
+
+ class Mat3(value: Matrix3fc) : VertexDataPoint(GL_FLOAT, value, 3 * 3 * 4, 9) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ value.get(position, buffer)
+ }
+ }
+
+ class Mat4(value: Matrix4fc) : VertexDataPoint(GL_FLOAT, value, 4 * 4 * 4, 16) {
+ override fun write(position: Int, buffer: ByteBuffer) {
+ value.get(position, buffer)
+ }
+ }
+}
diff --git a/threedom-kapt/build.gradle b/threedom-kapt/build.gradle
index e930f3e..dbbb5b9 100644
--- a/threedom-kapt/build.gradle
+++ b/threedom-kapt/build.gradle
@@ -1,5 +1,6 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
+ id 'java'
}
group 'me.eater.threedom'
@@ -11,8 +12,13 @@ dependencies {
}
compileKotlin {
- kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.jvmTarget = "12"
}
compileTestKotlin {
- kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.jvmTarget = "12"
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_12
+ targetCompatibility = JavaVersion.VERSION_12
}
diff --git a/threedom/build.gradle b/threedom/build.gradle
index 261bfd0..6999a06 100644
--- a/threedom/build.gradle
+++ b/threedom/build.gradle
@@ -1,6 +1,7 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
id "org.jetbrains.kotlin.kapt" version "1.3.72"
+ id 'java'
}
group 'me.eater.threedom'
@@ -30,8 +31,13 @@ dependencies {
}
compileKotlin {
- kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.jvmTarget = "12"
}
compileTestKotlin {
- kotlinOptions.jvmTarget = "1.8"
+ kotlinOptions.jvmTarget = "12"
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_12
+ targetCompatibility = JavaVersion.VERSION_12
}
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt
index eb1528a..8170586 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt
@@ -1,9 +1,8 @@
package me.eater.threedom.dom
-import me.eater.threedom.dom.event.DOMTreeUpdate
-import me.eater.threedom.dom.event.NodeClassListUpdate
-import me.eater.threedom.dom.event.NodeIDUpdate
-import me.eater.threedom.dom.event.NodeModelUpdate
+import me.eater.threedom.dom.event.*
+import me.eater.threedom.dom.render.IRenderNode
+import me.eater.threedom.dom.render.IRenderTarget
import me.eater.threedom.event.Event
import me.eater.threedom.generated.EventNames
import me.eater.threedom.utils.KDTree
@@ -12,6 +11,7 @@ import kotlin.reflect.KClass
class Document : IDocument {
val root: PlainNode = PlainNode(this)
+
private val eventTree = EventTree {
on { (event) ->
removeNodeFromSearch(event.child)
@@ -22,7 +22,9 @@ class Document : IDocument {
}
on { (event) ->
- updateKDTreeForNode(event.child)
+ if (event.child.shouldIndexLocation) {
+ updateKDTreeForNode(event.child)
+ }
}
on { (event) ->
@@ -36,19 +38,19 @@ class Document : IDocument {
}
on { (event) ->
- for (className in event.classNames) {
- byClass.getOrPut(className, ::mutableSetOf).add(event.node.nodeId)
+ for (tagName in event.tagNames) {
+ byTag.getOrPut(tagName, ::mutableSetOf).add(event.node.nodeId)
}
}
on { (event) ->
- for (className in event.classNames) {
- val set = byClass[className] ?: continue
+ for (tagName in event.tagNames) {
+ val set = byTag[tagName] ?: continue
set.remove(event.node.nodeId)
if (set.size == 0) {
- byClass.remove(className)
+ byTag.remove(tagName)
}
}
}
@@ -56,20 +58,34 @@ class Document : IDocument {
on { (event) ->
updateKDTreeForNode(event.node)
}
+
+ on { (event) ->
+ if (event.shouldIndex) {
+ kdTree.add(event.node)
+ } else {
+ kdTree.remove(event.node)
+ }
+ }
}
private val kdTree = KDTree(this)
private val allNodes: MutableMap> = mutableMapOf()
private val byId: MutableMap = mutableMapOf()
- private val byClass: MutableMap> = mutableMapOf()
+ private val byTag: MutableMap> = mutableMapOf()
+ private val byType: MutableMap> = mutableMapOf()
+ private val byRenderTarget: MutableMap> = mutableMapOf()
private fun updateKDTreeForNode(node: INode<*>) {
node.updateAbsolute()
- kdTree.update(node)
+ if (node.shouldIndexLocation) {
+ kdTree.update(node)
+ }
for (child in node.recursiveIterator()) {
child.updateAbsolute()
- kdTree.update(child)
+ if (node.shouldIndexLocation) {
+ kdTree.update(child)
+ }
}
}
@@ -107,8 +123,21 @@ class Document : IDocument {
override fun getNodeById(id: String): INode<*>? = byId[id]?.let(allNodes::get)
- override fun getNodesByClassName(className: String): Sequence> =
- byClass[className]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence()
+ override fun getNodesByTag(tagName: String): Sequence> =
+ byTag[tagName]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence()
+
+ @Suppress("UNCHECKED_CAST")
+ override fun > getNodesByClass(className: String): Sequence =
+ byType[className]?.asSequence()?.mapNotNull { nodeId ->
+ allNodes[nodeId]?.let {
+ (it.className == className) as? T
+ }
+ } ?: emptySequence()
+
+ @Suppress("UNCHECKED_CAST")
+ override fun , C> getNodesByRenderTarget(targetType: String): Sequence> =
+ byRenderTarget[targetType]?.asSequence()?.mapNotNull(allNodes::get) as? Sequence>
+ ?: emptySequence()
private fun addNodeToSearch(node: INode<*>) {
@@ -116,11 +145,19 @@ class Document : IDocument {
node.id?.let { byId.putIfAbsent(it, node.nodeId) }
- for (className in node.classList) {
- byClass.getOrPut(className, ::mutableSetOf).add(node.nodeId)
+ for (className in node.tagList) {
+ byTag.getOrPut(className, ::mutableSetOf).add(node.nodeId)
+ }
+
+ byType.getOrPut(node.javaClass.name, ::mutableSetOf).add(node.nodeId)
+
+ if (node is IRenderNode<*, *, *>) {
+ byRenderTarget.getOrPut(node.target.type, ::mutableSetOf).add(node.nodeId)
}
- kdTree.add(node)
+ if (node.shouldIndexLocation) {
+ kdTree.add(node)
+ }
}
private fun removeNodeFromSearch(node: INode<*>) {
@@ -128,15 +165,36 @@ class Document : IDocument {
node.id?.let { byId.remove(it, node.nodeId) }
- for (className in node.classList) {
- val set = byClass.get(className) ?: continue
+ for (className in node.tagList) {
+ val set = byTag.get(className) ?: continue
set.remove(node.nodeId)
if (set.size == 0) {
- byClass.remove(className)
+ byTag.remove(className)
+ }
+ }
+
+
+ byType[node.javaClass.name]?.let {
+ it.remove(node.nodeId)
+
+ if (it.size == 0) {
+ byType.remove(node.javaClass.name)
}
}
- kdTree.remove(node)
+ if (node is IRenderNode<*, *, *>) {
+ byRenderTarget[node.target.type]?.let {
+ it.remove(node.nodeId)
+
+ if (it.size == 0) {
+ byRenderTarget.remove(node.target.type)
+ }
+ }
+ }
+
+ if (node.shouldIndexLocation) {
+ kdTree.remove(node)
+ }
}
inline fun on(topLevel: Boolean = false, noinline block: (Event) -> Unit) = if (topLevel)
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt
index 1f8c0b0..fa9ade2 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt
@@ -1,5 +1,7 @@
package me.eater.threedom.dom
+import me.eater.threedom.dom.render.IRenderNode
+import me.eater.threedom.dom.render.IRenderTarget
import me.eater.threedom.event.Event
import me.eater.threedom.event.EventDispatcher
import me.eater.threedom.utils.joml.Vector3d
@@ -14,6 +16,12 @@ interface IDocument : EventDispatcher, INodeContainer {
fun findAt(x: Number, y: Number, z: Number) = findAt(Vector3d(x, y, z))
fun findAt(vec: Vector3dc): Collection>
fun rebalance()
+
+ fun , N : IRenderNode<*, T, C>> render(target: T, renderer: C) {
+ for (node in getNodesByRenderTarget(target.type)) {
+ node.render(renderer)
+ }
+ }
}
inline fun > IDocument.createNode() = createNode(T::class)
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt
index 5580435..4b97ef9 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt
@@ -6,6 +6,9 @@ import org.joml.Matrix4dc
import java.util.concurrent.atomic.AtomicLong
interface INode> : Comparable>, INodeContainer {
+ val className: String
+ get() = this::class.java.name
+
/**
* The ID of this node
*/
@@ -14,7 +17,7 @@ interface INode> : Comparable>, INodeContainer {
/**
* Set with all class names assigned to this node
*/
- val classList: MutableSet
+ val tagList: MutableSet
/**
* Internal ID of this node, should be unique inside document
@@ -31,6 +34,12 @@ interface INode> : Comparable>, INodeContainer {
*/
val document: IDocument?
+ /**
+ * If the location should be indexed
+ */
+ val shouldIndexLocation: Boolean
+ get() = false
+
/**
* Absolute matrix relative to document
*/
@@ -47,7 +56,7 @@ interface INode> : Comparable>, INodeContainer {
*
* @param deep also clone all child nodes
*/
- fun clone(deep: Boolean): T
+ fun clone(deep: Boolean = false): T
/**
* Update the parent of this node, for consistency purposes.
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt
index 22a0420..8a13484 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt
@@ -1,5 +1,7 @@
package me.eater.threedom.dom
+import me.eater.threedom.dom.render.IRenderNode
+import me.eater.threedom.dom.render.IRenderTarget
import org.joml.Vector3dc
interface INodeQueryCapable {
@@ -9,9 +11,19 @@ interface INodeQueryCapable {
fun getNodeById(id: String): INode<*>?
/**
- * Get all nodes inside this node with the class [className]
+ * Get all nodes inside this node with the class [tagName]
*/
- fun getNodesByClassName(className: String): Sequence>
+ fun getNodesByTag(tagName: String): Sequence>
+
+ /**
+ * Get all nodes inside this node with the Java class [className]
+ */
+ fun > getNodesByClass(className: String): Sequence
+
+ /**
+ * Get all nodes inside this node with the render target with type of [targetType]s
+ */
+ fun , C> getNodesByRenderTarget(targetType: String): Sequence>
/**
* find all nodes inside this node in region between [pointA] and [pointB]
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt
index 5cad9c0..3ae58bd 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt
@@ -1,9 +1,8 @@
package me.eater.threedom.dom
-import me.eater.threedom.dom.event.DOMTreeUpdate
-import me.eater.threedom.dom.event.NodeClassListUpdate
-import me.eater.threedom.dom.event.NodeIDUpdate
-import me.eater.threedom.dom.event.NodeModelUpdate
+import me.eater.threedom.dom.event.*
+import me.eater.threedom.dom.render.IRenderNode
+import me.eater.threedom.dom.render.IRenderTarget
import me.eater.threedom.event.Event
import me.eater.threedom.event.EventListener
import me.eater.threedom.event.trigger
@@ -15,6 +14,8 @@ import org.joml.Matrix4dc
import org.joml.Vector3dc
abstract class Node>(document: IDocument?) : INode, EventListener {
+ override val className: String = this::class.java.name
+
override var document: IDocument? = document
protected set
override val nodeId = INode.getNextNodeId()
@@ -23,12 +24,18 @@ abstract class Node>(document: IDocument?) : INode, EventListene
override var parentNode: INode<*>? = null
protected set
- private var _id: String? = null
- override var id: String?
- get() = _id
+ override var shouldIndexLocation: Boolean = false
set(value) {
- val old = _id
- _id = value
+ if (field == value) return
+
+ field = value
+ trigger(NodeLocationIndexStateChange(field, this))
+ }
+
+ override var id: String? = null
+ set(value) {
+ val old = field
+ field = value
trigger(NodeIDUpdate(this, old, value))
}
@@ -36,7 +43,7 @@ abstract class Node>(document: IDocument?) : INode, EventListene
override var absolute: Matrix4dc = Matrix4d()
protected set
- override val classList: MutableSet = ObservableSet {
+ override val tagList: MutableSet = ObservableSet {
when (it.action) {
ObservableSet.Action.Removed -> trigger(NodeClassListUpdate.Removed(it.elements, this))
ObservableSet.Action.Added -> trigger(NodeClassListUpdate.Added(it.elements, this))
@@ -49,17 +56,15 @@ abstract class Node>(document: IDocument?) : INode, EventListene
}
}
- private var _model: Matrix4d = Matrix4d()
- override var model: Matrix4dc
- get() = _model
+ override var model: Matrix4dc = Matrix4d()
set(value) {
- val old = Matrix4d(_model)
- _model = value.mutable()
+ val old = Matrix4d(field)
+ field = value.mutable()
trigger(NodeModelUpdate(this, old))
}
fun model(block: Matrix4d.() -> Matrix4dc) {
- this.model = block(this._model)
+ this.model = block(this.model.mutable())
}
var children: List> = nodes.toList()
@@ -190,10 +195,16 @@ abstract class Node>(document: IDocument?) : INode, EventListene
}
override fun getNodeById(id: String): INode<*>? = document?.getNodeById(id)?.takeIf { it.hasParent(this) }
- override fun getNodesByClassName(className: String): Sequence> = if (nodes.isEmpty())
+ override fun getNodesByTag(tagName: String): Sequence> = if (nodes.isEmpty())
emptySequence()
else
- document?.getNodesByClassName(className)?.filter { it.hasParent(this) } ?: emptySequence()
+ document?.getNodesByTag(tagName)?.filter { it.hasParent(this) } ?: emptySequence()
+
+ override fun > getNodesByClass(className: String): Sequence =
+ document?.getNodesByClass(className)?.filter { it.hasParent(this) } ?: emptySequence()
+
+ override fun , C> getNodesByRenderTarget(targetType: String): Sequence> =
+ document?.getNodesByRenderTarget(targetType)?.filter { it.hasParent(this) } ?: emptySequence()
override fun updateAbsolute() {
this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/PlainNode.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/PlainNode.kt
index 940ef80..d4cc4d2 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/PlainNode.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/PlainNode.kt
@@ -1,7 +1,5 @@
package me.eater.threedom.dom
class PlainNode(document: IDocument?) : Node(document) {
- override fun cloneSelf(): PlainNode {
- return PlainNode(document)
- }
+ override fun cloneSelf(): PlainNode = PlainNode(document)
}
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeClassListUpdate.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeClassListUpdate.kt
index ead11c6..1bee03d 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeClassListUpdate.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeClassListUpdate.kt
@@ -7,8 +7,8 @@ sealed class NodeClassListUpdate {
abstract val node: INode<*>
@EventName("NodeClassesRemoved")
- class Removed(val classNames: Set, override val node: INode<*>) : NodeClassListUpdate()
+ class Removed(val tagNames: Set, override val node: INode<*>) : NodeClassListUpdate()
@EventName("NodeClassesAdded")
- class Added(val classNames: Set, override val node: INode<*>) : NodeClassListUpdate()
+ class Added(val tagNames: Set, override val node: INode<*>) : NodeClassListUpdate()
}
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeLocationIndexStateChange.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeLocationIndexStateChange.kt
new file mode 100644
index 0000000..761af5a
--- /dev/null
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/event/NodeLocationIndexStateChange.kt
@@ -0,0 +1,7 @@
+package me.eater.threedom.dom.event
+
+import me.eater.threedom.dom.INode
+import me.eater.threedom.kapt.EventName
+
+@EventName("NodeLocationIndexStateChange")
+class NodeLocationIndexStateChange(val shouldIndex: Boolean, val node: INode<*>)
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/render/IRenderNode.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/render/IRenderNode.kt
new file mode 100644
index 0000000..a0d88d6
--- /dev/null
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/render/IRenderNode.kt
@@ -0,0 +1,10 @@
+package me.eater.threedom.dom.render
+
+import me.eater.threedom.dom.INode
+
+interface IRenderNode, T : IRenderTarget, C> :
+ INode {
+ val target: T
+
+ fun render(renderer: C)
+}
diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/render/IRenderTarget.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/render/IRenderTarget.kt
new file mode 100644
index 0000000..a9e4aba
--- /dev/null
+++ b/threedom/src/main/kotlin/me/eater/threedom/dom/render/IRenderTarget.kt
@@ -0,0 +1,5 @@
+package me.eater.threedom.dom.render
+
+interface IRenderTarget {
+ val type: String
+}
diff --git a/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt b/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt
index b08c35c..4ae544b 100644
--- a/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt
+++ b/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt
@@ -1,9 +1,12 @@
package me.eater.threedom.utils.joml
-import org.joml.Matrix4d
-import org.joml.Matrix4dc
+import org.joml.*
+import org.joml.Vector2d
+import org.joml.Vector2f
import org.joml.Vector3d
-import org.joml.Vector3dc
+import org.joml.Vector3f
+import org.joml.Vector4d
+import org.joml.Vector4f
fun Matrix4dc.setTranslation(x: T, y: T, z: T): Matrix4d =
Matrix4d(this).setTranslation(x.toDouble(), y.toDouble(), z.toDouble())
@@ -16,9 +19,61 @@ fun Matrix4dc.mutable(): Matrix4d = if (this is Matrix4d) this else Matrix4d(thi
operator fun Matrix4dc.times(rhs: Matrix4dc) = mul(rhs, Matrix4d())
operator fun Matrix4d.times(rhs: Matrix4dc) = mul(rhs)
+@Suppress("FunctionName")
+fun Vector2f(x: Number, y: Number) = Vector2f(x.toFloat(), y.toFloat())
+
+@Suppress("FunctionName")
+fun Vector2d(x: Number, y: Number) = Vector2d(x.toDouble(), y.toDouble())
+
+fun vec2(x: Number, y: Number) = Vector2f(x, y)
+fun vec2d(x: Number, y: Number) = Vector2d(x, y)
+
+@Suppress("FunctionName")
+fun Vector3f(x: Number, y: Number, z: Number) = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
+
@Suppress("FunctionName")
fun Vector3d(x: Number, y: Number, z: Number) = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
+fun vec3(x: Number, y: Number, z: Number) = Vector3f(x, y, z)
+fun vec3d(x: Number, y: Number, z: Number) = Vector3d(x, y, z)
+
+@Suppress("FunctionName")
+fun Vector4f(x: Number, y: Number, z: Number, w: Number) =
+ Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+
+@Suppress("FunctionName")
+fun Vector4d(x: Number, y: Number, z: Number, w: Number) =
+ Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+
+fun vec4(x: Number, y: Number, z: Number, w: Number) = Vector4f(x, y, z, w)
+fun vec4d(x: Number, y: Number, z: Number, w: Number) = Vector4d(x, y, z, w)
+
+fun Vector2d.toFloat() = vec2(x, y)
+fun Vector3d.toFloat() = vec3(x, y, z)
+fun Vector4d.toFloat() = vec4(x, y, z, w)
+
+fun Vector2f.toDouble() = vec2d(x, y)
+fun Vector3f.toDouble() = vec3d(x, y, z)
+fun Vector4f.toDouble() = vec4d(x, y, z, w)
+
+fun Matrix4dc.toFloat() = Matrix4f(this)
+fun Matrix3dc.toFloat() =
+ Matrix3f(
+ m00().toFloat(),
+ m01().toFloat(),
+ m02().toFloat(),
+ m10().toFloat(),
+ m11().toFloat(),
+ m12().toFloat(),
+ m20().toFloat(),
+ m21().toFloat(),
+ m22().toFloat()
+ )
+
+
+fun Matrix2dc.toFloat() =
+ Matrix2f(this.m00().toFloat(), this.m01().toFloat(), this.m10().toFloat(), this.m11().toFloat())
+
operator fun Vector3dc.compareTo(rhs: Vector3dc): Int {
for (i in 0 until 3) {
val c = this[i].compareTo(rhs[i])
diff --git a/threedom/src/test/kotlin/me/eater/test/threedom/dom/DocumentTest.kt b/threedom/src/test/kotlin/me/eater/test/threedom/dom/DocumentTest.kt
index ebda536..12ab980 100644
--- a/threedom/src/test/kotlin/me/eater/test/threedom/dom/DocumentTest.kt
+++ b/threedom/src/test/kotlin/me/eater/test/threedom/dom/DocumentTest.kt
@@ -109,26 +109,26 @@ class DocumentTest : StringSpec({
triggered++
}
- newNode.classList.add(":3")
+ newNode.tagList.add(":3")
doc.addNode(secondNewNode)
doc.addNode(thirdNewNode)
- doc.getNodesByClassName(":3") shouldHaveCount 0
+ doc.getNodesByTag(":3") shouldHaveCount 0
thirdNewNode.addNode(newNode)
- doc.getNodesByClassName(":3") shouldHaveSingleElement newNode
- secondNewNode.getNodesByClassName(":3") shouldHaveCount 0
- thirdNewNode.getNodesByClassName(":3") shouldHaveSingleElement newNode
+ doc.getNodesByTag(":3") shouldHaveSingleElement newNode
+ secondNewNode.getNodesByTag(":3") shouldHaveCount 0
+ thirdNewNode.getNodesByTag(":3") shouldHaveSingleElement newNode
thirdNewNode.removeAll()
- thirdNewNode.getNodesByClassName(":3") shouldHaveCount 0
- doc.getNodesByClassName(":3") shouldHaveCount 0
- newNode.classList.clear()
- newNode.classList.add(":/")
+ thirdNewNode.getNodesByTag(":3") shouldHaveCount 0
+ doc.getNodesByTag(":3") shouldHaveCount 0
+ newNode.tagList.clear()
+ newNode.tagList.add(":/")
doc.addNode(newNode)
- doc.getNodesByClassName(":3") shouldHaveCount 0
- doc.getNodesByClassName(":/") shouldHaveSingleElement newNode
- newNode.classList.clear()
- newNode.classList.add(":3")
- doc.getNodesByClassName(":/") shouldHaveCount 0
- doc.getNodesByClassName(":3") shouldHaveSingleElement newNode
+ doc.getNodesByTag(":3") shouldHaveCount 0
+ doc.getNodesByTag(":/") shouldHaveSingleElement newNode
+ newNode.tagList.clear()
+ newNode.tagList.add(":3")
+ doc.getNodesByTag(":/") shouldHaveCount 0
+ doc.getNodesByTag(":3") shouldHaveSingleElement newNode
}
})
diff --git a/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt b/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt
index 6f7b48d..3f32c4a 100644
--- a/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt
+++ b/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt
@@ -18,9 +18,11 @@ class PositionTest : StringSpec({
val doc: IDocument = Document()
val node = doc.createNode()
doc.addNode(node)
+ node.shouldIndexLocation = true
node.model { setTranslation(10, 0, 10) }
node.absolute.translation shouldBe Vector3d(10, 0, 10)
val nodeTwo = doc.createNode()
+ nodeTwo.shouldIndexLocation = true
node.addNode(nodeTwo)
nodeTwo.model { setTranslation(-10, 20, 0) }
nodeTwo.absolute.translation shouldBe Vector3d(0, 20, 10)
@@ -40,6 +42,8 @@ class PositionTest : StringSpec({
nodeTwo.model { setTranslation(-10, 20, 0) }
nodeThree.model { setTranslation(0, 20, 0) }
+ listOf(node, nodeTwo, nodeThree).forEach { it.shouldIndexLocation = true }
+
doc.findAt(10, 0, 10) shouldHaveSingleElement node
doc.findAt(0, 20, 10) shouldHaveSingleElement nodeTwo
doc.findAt(0, 40, 10) shouldHaveSingleElement nodeThree
@@ -64,6 +68,8 @@ class PositionTest : StringSpec({
nodeTwo.model { setTranslation(-10, 20, 0) }
nodeThree.model { setTranslation(0, 20, 0) }
+ listOf(node, nodeTwo, nodeThree).forEach { it.shouldIndexLocation = true }
+
val result = doc.findInRegion(Vector3d(0, 0, 0), Vector3d(0, 20, 20)).toList()
result shouldHaveSize 1
result shouldHaveSingleElement nodeTwo
@@ -78,6 +84,9 @@ class PositionTest : StringSpec({
val node = doc.createNode()
val nodeTwo = doc.createNode()
val nodeThree = doc.createNode()
+
+ listOf(node, nodeTwo, nodeThree).forEach { it.shouldIndexLocation = true }
+
doc.addNode(node)
node.addNode(nodeTwo)
nodeTwo.addNode(nodeThree)
diff --git a/tools/lwjglx-debug-1.0.0.jar b/tools/lwjglx-debug-1.0.0.jar
new file mode 100644
index 0000000..3b1760d
Binary files /dev/null and b/tools/lwjglx-debug-1.0.0.jar differ