This commit is contained in:
parent
eff722ea05
commit
7fc4b44571
44 changed files with 1508 additions and 77 deletions
|
@ -1,5 +0,0 @@
|
||||||
<graph>
|
|
||||||
<box diameter="10" position="[33, 40, 3]">
|
|
||||||
|
|
||||||
</box>
|
|
||||||
</graph>
|
|
|
@ -2,4 +2,5 @@ rootProject.name = 'threedom'
|
||||||
|
|
||||||
include 'threedom-kapt'
|
include 'threedom-kapt'
|
||||||
include 'threedom'
|
include 'threedom'
|
||||||
|
include 'threedom-gl'
|
||||||
|
include 'threedom-example'
|
||||||
|
|
67
threedom-example/build.gradle
Normal file
67
threedom-example/build.gradle
Normal file
|
@ -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")
|
||||||
|
}
|
|
@ -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<TriMesh>()
|
||||||
|
|
||||||
|
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<PlainNode>()
|
||||||
|
|
||||||
|
doc.addNode(empty)
|
||||||
|
empty.addNode(triMesh)
|
||||||
|
|
||||||
|
val around = mutableListOf<TriMesh>()
|
||||||
|
|
||||||
|
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<PerspectiveCamera>()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
FragColor = vec4(1.0);
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
BIN
threedom-example/src/main/resources/textures/fujiwara.jpg
Normal file
BIN
threedom-example/src/main/resources/textures/fujiwara.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 384 KiB |
37
threedom-gl/build.gradle
Normal file
37
threedom-gl/build.gradle
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
|
11
threedom-gl/src/main/kotlin/me/eater/threedom/gl/GL.kt
Normal file
11
threedom-gl/src/main/kotlin/me/eater/threedom/gl/GL.kt
Normal file
|
@ -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<ICamera<*>> {
|
||||||
|
override val type = "GL"
|
||||||
|
val stack: MemoryStack = stackPush()
|
||||||
|
}
|
32
threedom-gl/src/main/kotlin/me/eater/threedom/gl/Shader.kt
Normal file
32
threedom-gl/src/main/kotlin/me/eater/threedom/gl/Shader.kt
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package me.eater.threedom.gl
|
||||||
|
|
||||||
|
class ShaderPreProcessor {
|
||||||
|
private val regex = Regex("(?<module>[^:]+)::(?<call>[^:(]+)\\((?<args>.+)\\)")
|
||||||
|
private val calls: MutableMap<Pair<String, String>, (List<String>) -> 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>) -> 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;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Int> = mutableMapOf()
|
||||||
|
val queuedUniformValues: MutableMap<Int, () -> Unit> = mutableMapOf()
|
||||||
|
private val textures: MutableMap<Int, Int> = 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<ShaderProgram, Texture?> {
|
||||||
|
var setUniform = false
|
||||||
|
|
||||||
|
return object : ReadWriteProperty<ShaderProgram, Texture?> {
|
||||||
|
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 <T> uniform(
|
||||||
|
name: String? = null,
|
||||||
|
setter: (location: Int, value: T) -> Unit,
|
||||||
|
getter: (programId: Int, location: Int) -> T?
|
||||||
|
) = UniformProperty(name, setter, getter)
|
||||||
|
|
||||||
|
open class UniformProperty<T>(
|
||||||
|
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<Float> =
|
||||||
|
uniform(name, ::glUniform1f, ::glGetUniformf)
|
||||||
|
|
||||||
|
protected fun int(name: String? = null): UniformProperty<Int> =
|
||||||
|
uniform(name, ::glUniform1i, ::glGetUniformi)
|
||||||
|
|
||||||
|
protected fun vec2(name: String? = null): UniformProperty<Vector2fc> =
|
||||||
|
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<Vector3fc> =
|
||||||
|
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<Vector4fc> =
|
||||||
|
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<Matrix3fc> =
|
||||||
|
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<Boolean> =
|
||||||
|
uniform(name, { loc, value ->
|
||||||
|
glUniform1i(loc, if (value) 1 else 0)
|
||||||
|
}, { programId, location ->
|
||||||
|
glGetUniformi(programId, location) == 1
|
||||||
|
})
|
||||||
|
|
||||||
|
protected fun mat4(name: String? = null): UniformProperty<Matrix4fc> =
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T : IRenderNode<T, GL, ICamera<*>>> :
|
||||||
|
IRenderNode<T, GL, ICamera<*>> {
|
||||||
|
override val target: GL
|
||||||
|
get() = GL
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package me.eater.threedom.gl.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
import org.joml.Matrix4dc
|
||||||
|
|
||||||
|
interface ICamera<T : INode<T>> : INode<T> {
|
||||||
|
var width: Double
|
||||||
|
var height: Double
|
||||||
|
|
||||||
|
val projection: Matrix4dc
|
||||||
|
val view: Matrix4dc
|
||||||
|
}
|
|
@ -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<PerspectiveCamera>(document), ICamera<PerspectiveCamera> {
|
||||||
|
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
|
||||||
|
}
|
|
@ -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<TriMesh>(document), GLRenderNode<TriMesh> {
|
||||||
|
var mesh: VertexArrayObject? = null
|
||||||
|
var program: ShaderProgram? = null
|
||||||
|
private set
|
||||||
|
var programConfig: (ShaderProgram.(TriMesh) -> Unit)? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun <T : ShaderProgram> use(program: T, block: T.(TriMesh) -> Unit) {
|
||||||
|
this.program = program
|
||||||
|
@kotlin.Suppress("UNCHECKED_CAST")
|
||||||
|
this.programConfig = block as ShaderProgram.(TriMesh) -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : ShaderProgram> 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package me.eater.threedom.gl.exception
|
||||||
|
|
||||||
|
data class ShaderCompilationException(val infoLog: String) : RuntimeException("Shader failed compiling: $infoLog")
|
|
@ -0,0 +1,4 @@
|
||||||
|
package me.eater.threedom.gl.exception
|
||||||
|
|
||||||
|
data class ShaderProgramLinkingException(val infoLog: String) :
|
||||||
|
RuntimeException("Failed linking ShaderProgram: $infoLog")
|
|
@ -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) {
|
||||||
|
}
|
|
@ -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<Int, WeakReference<Texture>>()
|
||||||
|
|
||||||
|
fun getTexture(id: Int): Texture? = textures[id]?.get()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <T : VertexData> VertexBuffer<T>.invoke(prop: KProperty1<T, *>) =
|
||||||
|
bind(this, prop)
|
||||||
|
|
||||||
|
fun <T : VertexData> bind(vertexBuffer: VertexBuffer<T>, prop: KProperty1<T, *>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T : VertexData>(
|
||||||
|
items: List<T> = 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<T> = 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package me.eater.threedom.gl.vertex
|
||||||
|
|
||||||
|
class VertexBufferMutable<T: VertexData>(override val items: MutableList<T> = mutableListOf()) : VertexBuffer<T>()
|
|
@ -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<String, Pair<Int, VertexDataPoint<*>>>()
|
||||||
|
private var offset = 0
|
||||||
|
|
||||||
|
val length
|
||||||
|
get() = items.map { (_, v) -> v.component2().length }.sum()
|
||||||
|
|
||||||
|
class VertexDataRegister<T>(private val default: T, val provider: (default: T) -> VertexDataPoint<T>) {
|
||||||
|
operator fun provideDelegate(thisRef: VertexData, prop: KProperty<*>): VertexDataPoint<T> {
|
||||||
|
val delegate = provider(default)
|
||||||
|
thisRef.items[prop.name] = thisRef.offset to delegate
|
||||||
|
thisRef.offset += delegate.length
|
||||||
|
return delegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun int(value: Int = 0): VertexDataRegister<Int> = VertexDataRegister(value, ::SingleInt)
|
||||||
|
fun double(value: Double = 0.0): VertexDataRegister<Double> = VertexDataRegister(value, ::SingleDouble)
|
||||||
|
fun float(value: Float = 0f): VertexDataRegister<Float> = VertexDataRegister(value, ::SingleFloat)
|
||||||
|
fun bool(value: Boolean = false): VertexDataRegister<Boolean> = VertexDataRegister(value, ::SingleBoolean)
|
||||||
|
fun vec2(value: Vector2fc = Vector2f()): VertexDataRegister<Vector2fc> = VertexDataRegister(value, ::Vec2)
|
||||||
|
fun vec3(value: Vector3fc = Vector3f()): VertexDataRegister<Vector3fc> = VertexDataRegister(value, ::Vec3)
|
||||||
|
fun vec4(value: Vector4fc = Vector4f()): VertexDataRegister<Vector4fc> = VertexDataRegister(value, ::Vec4)
|
||||||
|
fun mat3(value: Matrix3fc = Matrix3f()): VertexDataRegister<Matrix3fc> = VertexDataRegister(value, ::Mat3)
|
||||||
|
fun mat4(value: Matrix4fc = Matrix4f()): VertexDataRegister<Matrix4fc> = VertexDataRegister(value, ::Mat4)
|
||||||
|
|
||||||
|
fun getDataPoint(prop: KProperty1<out VertexData, *>): VertexDataPoint<*>? = items[prop.name]?.component2()
|
||||||
|
|
||||||
|
fun getOffset(prop: KProperty1<out VertexData, *>): 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T>(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<Int>(GL_INT, value, 4) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
buffer.putInt(position, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleFloat(value: Float) : VertexDataPoint<Float>(GL_FLOAT, value, 4) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
buffer.putFloat(position, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleDouble(value: Double) : VertexDataPoint<Double>(GL_DOUBLE, value, 8) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
buffer.putDouble(position, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleBoolean(value: Boolean) : VertexDataPoint<Boolean>(GL_BOOL, value, 1) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
buffer.put(position, if (value) 1 else 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vec2(value: Vector2fc) : VertexDataPoint<Vector2fc>(GL_FLOAT, value, 8, 2) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
value.get(position, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vec3(value: Vector3fc) : VertexDataPoint<Vector3fc>(GL_FLOAT, value, 12, 3) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
value.get(position, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vec4(value: Vector4fc) : VertexDataPoint<Vector4fc>(GL_FLOAT, value, 16, 4) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
value.get(position, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mat3(value: Matrix3fc) : VertexDataPoint<Matrix3fc>(GL_FLOAT, value, 3 * 3 * 4, 9) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
value.get(position, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mat4(value: Matrix4fc) : VertexDataPoint<Matrix4fc>(GL_FLOAT, value, 4 * 4 * 4, 16) {
|
||||||
|
override fun write(position: Int, buffer: ByteBuffer) {
|
||||||
|
value.get(position, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
||||||
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'me.eater.threedom'
|
group 'me.eater.threedom'
|
||||||
|
@ -11,8 +12,13 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
kotlinOptions.jvmTarget = "12"
|
||||||
}
|
}
|
||||||
compileTestKotlin {
|
compileTestKotlin {
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
kotlinOptions.jvmTarget = "12"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_12
|
||||||
|
targetCompatibility = JavaVersion.VERSION_12
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
||||||
id "org.jetbrains.kotlin.kapt" version "1.3.72"
|
id "org.jetbrains.kotlin.kapt" version "1.3.72"
|
||||||
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'me.eater.threedom'
|
group 'me.eater.threedom'
|
||||||
|
@ -30,8 +31,13 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
kotlinOptions.jvmTarget = "12"
|
||||||
}
|
}
|
||||||
compileTestKotlin {
|
compileTestKotlin {
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
kotlinOptions.jvmTarget = "12"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_12
|
||||||
|
targetCompatibility = JavaVersion.VERSION_12
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package me.eater.threedom.dom
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
import me.eater.threedom.dom.event.DOMTreeUpdate
|
import me.eater.threedom.dom.event.*
|
||||||
import me.eater.threedom.dom.event.NodeClassListUpdate
|
import me.eater.threedom.dom.render.IRenderNode
|
||||||
import me.eater.threedom.dom.event.NodeIDUpdate
|
import me.eater.threedom.dom.render.IRenderTarget
|
||||||
import me.eater.threedom.dom.event.NodeModelUpdate
|
|
||||||
import me.eater.threedom.event.Event
|
import me.eater.threedom.event.Event
|
||||||
import me.eater.threedom.generated.EventNames
|
import me.eater.threedom.generated.EventNames
|
||||||
import me.eater.threedom.utils.KDTree
|
import me.eater.threedom.utils.KDTree
|
||||||
|
@ -12,6 +11,7 @@ import kotlin.reflect.KClass
|
||||||
|
|
||||||
class Document : IDocument {
|
class Document : IDocument {
|
||||||
val root: PlainNode = PlainNode(this)
|
val root: PlainNode = PlainNode(this)
|
||||||
|
|
||||||
private val eventTree = EventTree {
|
private val eventTree = EventTree {
|
||||||
on<DOMTreeUpdate.Remove> { (event) ->
|
on<DOMTreeUpdate.Remove> { (event) ->
|
||||||
removeNodeFromSearch(event.child)
|
removeNodeFromSearch(event.child)
|
||||||
|
@ -22,7 +22,9 @@ class Document : IDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
on<DOMTreeUpdate.Move> { (event) ->
|
on<DOMTreeUpdate.Move> { (event) ->
|
||||||
updateKDTreeForNode(event.child)
|
if (event.child.shouldIndexLocation) {
|
||||||
|
updateKDTreeForNode(event.child)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on<NodeIDUpdate> { (event) ->
|
on<NodeIDUpdate> { (event) ->
|
||||||
|
@ -36,19 +38,19 @@ class Document : IDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
on<NodeClassListUpdate.Added> { (event) ->
|
on<NodeClassListUpdate.Added> { (event) ->
|
||||||
for (className in event.classNames) {
|
for (tagName in event.tagNames) {
|
||||||
byClass.getOrPut(className, ::mutableSetOf).add(event.node.nodeId)
|
byTag.getOrPut(tagName, ::mutableSetOf).add(event.node.nodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on<NodeClassListUpdate.Removed> { (event) ->
|
on<NodeClassListUpdate.Removed> { (event) ->
|
||||||
for (className in event.classNames) {
|
for (tagName in event.tagNames) {
|
||||||
val set = byClass[className] ?: continue
|
val set = byTag[tagName] ?: continue
|
||||||
|
|
||||||
set.remove(event.node.nodeId)
|
set.remove(event.node.nodeId)
|
||||||
|
|
||||||
if (set.size == 0) {
|
if (set.size == 0) {
|
||||||
byClass.remove(className)
|
byTag.remove(tagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,20 +58,34 @@ class Document : IDocument {
|
||||||
on<NodeModelUpdate> { (event) ->
|
on<NodeModelUpdate> { (event) ->
|
||||||
updateKDTreeForNode(event.node)
|
updateKDTreeForNode(event.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on<NodeLocationIndexStateChange> { (event) ->
|
||||||
|
if (event.shouldIndex) {
|
||||||
|
kdTree.add(event.node)
|
||||||
|
} else {
|
||||||
|
kdTree.remove(event.node)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private val kdTree = KDTree(this)
|
private val kdTree = KDTree(this)
|
||||||
|
|
||||||
private val allNodes: MutableMap<Long, INode<*>> = mutableMapOf()
|
private val allNodes: MutableMap<Long, INode<*>> = mutableMapOf()
|
||||||
private val byId: MutableMap<String, Long> = mutableMapOf()
|
private val byId: MutableMap<String, Long> = mutableMapOf()
|
||||||
private val byClass: MutableMap<String, MutableSet<Long>> = mutableMapOf()
|
private val byTag: MutableMap<String, MutableSet<Long>> = mutableMapOf()
|
||||||
|
private val byType: MutableMap<String, MutableSet<Long>> = mutableMapOf()
|
||||||
|
private val byRenderTarget: MutableMap<String, MutableSet<Long>> = mutableMapOf()
|
||||||
|
|
||||||
private fun updateKDTreeForNode(node: INode<*>) {
|
private fun updateKDTreeForNode(node: INode<*>) {
|
||||||
node.updateAbsolute()
|
node.updateAbsolute()
|
||||||
kdTree.update(node)
|
if (node.shouldIndexLocation) {
|
||||||
|
kdTree.update(node)
|
||||||
|
}
|
||||||
|
|
||||||
for (child in node.recursiveIterator()) {
|
for (child in node.recursiveIterator()) {
|
||||||
child.updateAbsolute()
|
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 getNodeById(id: String): INode<*>? = byId[id]?.let(allNodes::get)
|
||||||
|
|
||||||
override fun getNodesByClassName(className: String): Sequence<INode<*>> =
|
override fun getNodesByTag(tagName: String): Sequence<INode<*>> =
|
||||||
byClass[className]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence()
|
byTag[tagName]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence()
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : INode<T>> getNodesByClass(className: String): Sequence<T> =
|
||||||
|
byType[className]?.asSequence()?.mapNotNull { nodeId ->
|
||||||
|
allNodes[nodeId]?.let {
|
||||||
|
(it.className == className) as? T
|
||||||
|
}
|
||||||
|
} ?: emptySequence()
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : IRenderTarget<C>, C> getNodesByRenderTarget(targetType: String): Sequence<IRenderNode<*, T, C>> =
|
||||||
|
byRenderTarget[targetType]?.asSequence()?.mapNotNull(allNodes::get) as? Sequence<IRenderNode<*, T, C>>
|
||||||
|
?: emptySequence()
|
||||||
|
|
||||||
|
|
||||||
private fun addNodeToSearch(node: INode<*>) {
|
private fun addNodeToSearch(node: INode<*>) {
|
||||||
|
@ -116,11 +145,19 @@ class Document : IDocument {
|
||||||
|
|
||||||
node.id?.let { byId.putIfAbsent(it, node.nodeId) }
|
node.id?.let { byId.putIfAbsent(it, node.nodeId) }
|
||||||
|
|
||||||
for (className in node.classList) {
|
for (className in node.tagList) {
|
||||||
byClass.getOrPut(className, ::mutableSetOf).add(node.nodeId)
|
byTag.getOrPut(className, ::mutableSetOf).add(node.nodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
kdTree.add(node)
|
byType.getOrPut(node.javaClass.name, ::mutableSetOf).add(node.nodeId)
|
||||||
|
|
||||||
|
if (node is IRenderNode<*, *, *>) {
|
||||||
|
byRenderTarget.getOrPut(node.target.type, ::mutableSetOf).add(node.nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.shouldIndexLocation) {
|
||||||
|
kdTree.add(node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeNodeFromSearch(node: INode<*>) {
|
private fun removeNodeFromSearch(node: INode<*>) {
|
||||||
|
@ -128,15 +165,36 @@ class Document : IDocument {
|
||||||
|
|
||||||
node.id?.let { byId.remove(it, node.nodeId) }
|
node.id?.let { byId.remove(it, node.nodeId) }
|
||||||
|
|
||||||
for (className in node.classList) {
|
for (className in node.tagList) {
|
||||||
val set = byClass.get(className) ?: continue
|
val set = byTag.get(className) ?: continue
|
||||||
set.remove(node.nodeId)
|
set.remove(node.nodeId)
|
||||||
if (set.size == 0) {
|
if (set.size == 0) {
|
||||||
byClass.remove(className)
|
byTag.remove(className)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kdTree.remove(node)
|
|
||||||
|
byType[node.javaClass.name]?.let {
|
||||||
|
it.remove(node.nodeId)
|
||||||
|
|
||||||
|
if (it.size == 0) {
|
||||||
|
byType.remove(node.javaClass.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <reified T> on(topLevel: Boolean = false, noinline block: (Event<T>) -> Unit) = if (topLevel)
|
inline fun <reified T> on(topLevel: Boolean = false, noinline block: (Event<T>) -> Unit) = if (topLevel)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package me.eater.threedom.dom
|
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.Event
|
||||||
import me.eater.threedom.event.EventDispatcher
|
import me.eater.threedom.event.EventDispatcher
|
||||||
import me.eater.threedom.utils.joml.Vector3d
|
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(x: Number, y: Number, z: Number) = findAt(Vector3d(x, y, z))
|
||||||
fun findAt(vec: Vector3dc): Collection<INode<*>>
|
fun findAt(vec: Vector3dc): Collection<INode<*>>
|
||||||
fun rebalance()
|
fun rebalance()
|
||||||
|
|
||||||
|
fun <C, T : IRenderTarget<C>, N : IRenderNode<*, T, C>> render(target: T, renderer: C) {
|
||||||
|
for (node in getNodesByRenderTarget<T, C>(target.type)) {
|
||||||
|
node.render(renderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : INode<T>> IDocument.createNode() = createNode(T::class)
|
inline fun <reified T : INode<T>> IDocument.createNode() = createNode(T::class)
|
||||||
|
|
|
@ -6,6 +6,9 @@ import org.joml.Matrix4dc
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
||||||
|
val className: String
|
||||||
|
get() = this::class.java.name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of this node
|
* The ID of this node
|
||||||
*/
|
*/
|
||||||
|
@ -14,7 +17,7 @@ interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
||||||
/**
|
/**
|
||||||
* Set with all class names assigned to this node
|
* Set with all class names assigned to this node
|
||||||
*/
|
*/
|
||||||
val classList: MutableSet<String>
|
val tagList: MutableSet<String>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal ID of this node, should be unique inside document
|
* Internal ID of this node, should be unique inside document
|
||||||
|
@ -31,6 +34,12 @@ interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
||||||
*/
|
*/
|
||||||
val document: IDocument?
|
val document: IDocument?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the location should be indexed
|
||||||
|
*/
|
||||||
|
val shouldIndexLocation: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Absolute matrix relative to document
|
* Absolute matrix relative to document
|
||||||
*/
|
*/
|
||||||
|
@ -47,7 +56,7 @@ interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
||||||
*
|
*
|
||||||
* @param deep also clone all child nodes
|
* @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.
|
* Update the parent of this node, for consistency purposes.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package me.eater.threedom.dom
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.render.IRenderNode
|
||||||
|
import me.eater.threedom.dom.render.IRenderTarget
|
||||||
import org.joml.Vector3dc
|
import org.joml.Vector3dc
|
||||||
|
|
||||||
interface INodeQueryCapable {
|
interface INodeQueryCapable {
|
||||||
|
@ -9,9 +11,19 @@ interface INodeQueryCapable {
|
||||||
fun getNodeById(id: String): INode<*>?
|
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<INode<*>>
|
fun getNodesByTag(tagName: String): Sequence<INode<*>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all nodes inside this node with the Java class [className]
|
||||||
|
*/
|
||||||
|
fun <T : INode<T>> getNodesByClass(className: String): Sequence<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all nodes inside this node with the render target with type of [targetType]s
|
||||||
|
*/
|
||||||
|
fun <T : IRenderTarget<C>, C> getNodesByRenderTarget(targetType: String): Sequence<IRenderNode<*, T, C>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* find all nodes inside this node in region between [pointA] and [pointB]
|
* find all nodes inside this node in region between [pointA] and [pointB]
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package me.eater.threedom.dom
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
import me.eater.threedom.dom.event.DOMTreeUpdate
|
import me.eater.threedom.dom.event.*
|
||||||
import me.eater.threedom.dom.event.NodeClassListUpdate
|
import me.eater.threedom.dom.render.IRenderNode
|
||||||
import me.eater.threedom.dom.event.NodeIDUpdate
|
import me.eater.threedom.dom.render.IRenderTarget
|
||||||
import me.eater.threedom.dom.event.NodeModelUpdate
|
|
||||||
import me.eater.threedom.event.Event
|
import me.eater.threedom.event.Event
|
||||||
import me.eater.threedom.event.EventListener
|
import me.eater.threedom.event.EventListener
|
||||||
import me.eater.threedom.event.trigger
|
import me.eater.threedom.event.trigger
|
||||||
|
@ -15,6 +14,8 @@ import org.joml.Matrix4dc
|
||||||
import org.joml.Vector3dc
|
import org.joml.Vector3dc
|
||||||
|
|
||||||
abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListener {
|
abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListener {
|
||||||
|
override val className: String = this::class.java.name
|
||||||
|
|
||||||
override var document: IDocument? = document
|
override var document: IDocument? = document
|
||||||
protected set
|
protected set
|
||||||
override val nodeId = INode.getNextNodeId()
|
override val nodeId = INode.getNextNodeId()
|
||||||
|
@ -23,12 +24,18 @@ abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListene
|
||||||
override var parentNode: INode<*>? = null
|
override var parentNode: INode<*>? = null
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
private var _id: String? = null
|
override var shouldIndexLocation: Boolean = false
|
||||||
override var id: String?
|
|
||||||
get() = _id
|
|
||||||
set(value) {
|
set(value) {
|
||||||
val old = _id
|
if (field == value) return
|
||||||
_id = value
|
|
||||||
|
field = value
|
||||||
|
trigger(NodeLocationIndexStateChange(field, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
override var id: String? = null
|
||||||
|
set(value) {
|
||||||
|
val old = field
|
||||||
|
field = value
|
||||||
|
|
||||||
trigger(NodeIDUpdate(this, old, value))
|
trigger(NodeIDUpdate(this, old, value))
|
||||||
}
|
}
|
||||||
|
@ -36,7 +43,7 @@ abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListene
|
||||||
override var absolute: Matrix4dc = Matrix4d()
|
override var absolute: Matrix4dc = Matrix4d()
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
override val classList: MutableSet<String> = ObservableSet {
|
override val tagList: MutableSet<String> = ObservableSet {
|
||||||
when (it.action) {
|
when (it.action) {
|
||||||
ObservableSet.Action.Removed -> trigger(NodeClassListUpdate.Removed(it.elements, this))
|
ObservableSet.Action.Removed -> trigger(NodeClassListUpdate.Removed(it.elements, this))
|
||||||
ObservableSet.Action.Added -> trigger(NodeClassListUpdate.Added(it.elements, this))
|
ObservableSet.Action.Added -> trigger(NodeClassListUpdate.Added(it.elements, this))
|
||||||
|
@ -49,17 +56,15 @@ abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var _model: Matrix4d = Matrix4d()
|
override var model: Matrix4dc = Matrix4d()
|
||||||
override var model: Matrix4dc
|
|
||||||
get() = _model
|
|
||||||
set(value) {
|
set(value) {
|
||||||
val old = Matrix4d(_model)
|
val old = Matrix4d(field)
|
||||||
_model = value.mutable()
|
field = value.mutable()
|
||||||
trigger(NodeModelUpdate(this, old))
|
trigger(NodeModelUpdate(this, old))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun model(block: Matrix4d.() -> Matrix4dc) {
|
fun model(block: Matrix4d.() -> Matrix4dc) {
|
||||||
this.model = block(this._model)
|
this.model = block(this.model.mutable())
|
||||||
}
|
}
|
||||||
|
|
||||||
var children: List<INode<*>> = nodes.toList()
|
var children: List<INode<*>> = nodes.toList()
|
||||||
|
@ -190,10 +195,16 @@ abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListene
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNodeById(id: String): INode<*>? = document?.getNodeById(id)?.takeIf { it.hasParent(this) }
|
override fun getNodeById(id: String): INode<*>? = document?.getNodeById(id)?.takeIf { it.hasParent(this) }
|
||||||
override fun getNodesByClassName(className: String): Sequence<INode<*>> = if (nodes.isEmpty())
|
override fun getNodesByTag(tagName: String): Sequence<INode<*>> = if (nodes.isEmpty())
|
||||||
emptySequence()
|
emptySequence()
|
||||||
else
|
else
|
||||||
document?.getNodesByClassName(className)?.filter { it.hasParent(this) } ?: emptySequence()
|
document?.getNodesByTag(tagName)?.filter { it.hasParent(this) } ?: emptySequence()
|
||||||
|
|
||||||
|
override fun <T : INode<T>> getNodesByClass(className: String): Sequence<T> =
|
||||||
|
document?.getNodesByClass<T>(className)?.filter { it.hasParent(this) } ?: emptySequence()
|
||||||
|
|
||||||
|
override fun <T : IRenderTarget<C>, C> getNodesByRenderTarget(targetType: String): Sequence<IRenderNode<*, T, C>> =
|
||||||
|
document?.getNodesByRenderTarget<T, C>(targetType)?.filter { it.hasParent(this) } ?: emptySequence()
|
||||||
|
|
||||||
override fun updateAbsolute() {
|
override fun updateAbsolute() {
|
||||||
this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model
|
this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package me.eater.threedom.dom
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
class PlainNode(document: IDocument?) : Node<PlainNode>(document) {
|
class PlainNode(document: IDocument?) : Node<PlainNode>(document) {
|
||||||
override fun cloneSelf(): PlainNode {
|
override fun cloneSelf(): PlainNode = PlainNode(document)
|
||||||
return PlainNode(document)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ sealed class NodeClassListUpdate {
|
||||||
abstract val node: INode<*>
|
abstract val node: INode<*>
|
||||||
|
|
||||||
@EventName("NodeClassesRemoved")
|
@EventName("NodeClassesRemoved")
|
||||||
class Removed(val classNames: Set<String>, override val node: INode<*>) : NodeClassListUpdate()
|
class Removed(val tagNames: Set<String>, override val node: INode<*>) : NodeClassListUpdate()
|
||||||
|
|
||||||
@EventName("NodeClassesAdded")
|
@EventName("NodeClassesAdded")
|
||||||
class Added(val classNames: Set<String>, override val node: INode<*>) : NodeClassListUpdate()
|
class Added(val tagNames: Set<String>, override val node: INode<*>) : NodeClassListUpdate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<*>)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package me.eater.threedom.dom.render
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
|
||||||
|
interface IRenderNode<N : IRenderNode<N, T, C>, T : IRenderTarget<C>, C> :
|
||||||
|
INode<N> {
|
||||||
|
val target: T
|
||||||
|
|
||||||
|
fun render(renderer: C)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package me.eater.threedom.dom.render
|
||||||
|
|
||||||
|
interface IRenderTarget<C> {
|
||||||
|
val type: String
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package me.eater.threedom.utils.joml
|
package me.eater.threedom.utils.joml
|
||||||
|
|
||||||
import org.joml.Matrix4d
|
import org.joml.*
|
||||||
import org.joml.Matrix4dc
|
import org.joml.Vector2d
|
||||||
|
import org.joml.Vector2f
|
||||||
import org.joml.Vector3d
|
import org.joml.Vector3d
|
||||||
import org.joml.Vector3dc
|
import org.joml.Vector3f
|
||||||
|
import org.joml.Vector4d
|
||||||
|
import org.joml.Vector4f
|
||||||
|
|
||||||
fun <T : Number> Matrix4dc.setTranslation(x: T, y: T, z: T): Matrix4d =
|
fun <T : Number> Matrix4dc.setTranslation(x: T, y: T, z: T): Matrix4d =
|
||||||
Matrix4d(this).setTranslation(x.toDouble(), y.toDouble(), z.toDouble())
|
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 Matrix4dc.times(rhs: Matrix4dc) = mul(rhs, Matrix4d())
|
||||||
operator fun Matrix4d.times(rhs: Matrix4dc) = mul(rhs)
|
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")
|
@Suppress("FunctionName")
|
||||||
fun Vector3d(x: Number, y: Number, z: Number) = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
|
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 {
|
operator fun Vector3dc.compareTo(rhs: Vector3dc): Int {
|
||||||
for (i in 0 until 3) {
|
for (i in 0 until 3) {
|
||||||
val c = this[i].compareTo(rhs[i])
|
val c = this[i].compareTo(rhs[i])
|
||||||
|
|
|
@ -109,26 +109,26 @@ class DocumentTest : StringSpec({
|
||||||
triggered++
|
triggered++
|
||||||
}
|
}
|
||||||
|
|
||||||
newNode.classList.add(":3")
|
newNode.tagList.add(":3")
|
||||||
doc.addNode(secondNewNode)
|
doc.addNode(secondNewNode)
|
||||||
doc.addNode(thirdNewNode)
|
doc.addNode(thirdNewNode)
|
||||||
|
|
||||||
doc.getNodesByClassName(":3") shouldHaveCount 0
|
doc.getNodesByTag(":3") shouldHaveCount 0
|
||||||
thirdNewNode.addNode(newNode)
|
thirdNewNode.addNode(newNode)
|
||||||
doc.getNodesByClassName(":3") shouldHaveSingleElement newNode
|
doc.getNodesByTag(":3") shouldHaveSingleElement newNode
|
||||||
secondNewNode.getNodesByClassName(":3") shouldHaveCount 0
|
secondNewNode.getNodesByTag(":3") shouldHaveCount 0
|
||||||
thirdNewNode.getNodesByClassName(":3") shouldHaveSingleElement newNode
|
thirdNewNode.getNodesByTag(":3") shouldHaveSingleElement newNode
|
||||||
thirdNewNode.removeAll()
|
thirdNewNode.removeAll()
|
||||||
thirdNewNode.getNodesByClassName(":3") shouldHaveCount 0
|
thirdNewNode.getNodesByTag(":3") shouldHaveCount 0
|
||||||
doc.getNodesByClassName(":3") shouldHaveCount 0
|
doc.getNodesByTag(":3") shouldHaveCount 0
|
||||||
newNode.classList.clear()
|
newNode.tagList.clear()
|
||||||
newNode.classList.add(":/")
|
newNode.tagList.add(":/")
|
||||||
doc.addNode(newNode)
|
doc.addNode(newNode)
|
||||||
doc.getNodesByClassName(":3") shouldHaveCount 0
|
doc.getNodesByTag(":3") shouldHaveCount 0
|
||||||
doc.getNodesByClassName(":/") shouldHaveSingleElement newNode
|
doc.getNodesByTag(":/") shouldHaveSingleElement newNode
|
||||||
newNode.classList.clear()
|
newNode.tagList.clear()
|
||||||
newNode.classList.add(":3")
|
newNode.tagList.add(":3")
|
||||||
doc.getNodesByClassName(":/") shouldHaveCount 0
|
doc.getNodesByTag(":/") shouldHaveCount 0
|
||||||
doc.getNodesByClassName(":3") shouldHaveSingleElement newNode
|
doc.getNodesByTag(":3") shouldHaveSingleElement newNode
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,9 +18,11 @@ class PositionTest : StringSpec({
|
||||||
val doc: IDocument = Document()
|
val doc: IDocument = Document()
|
||||||
val node = doc.createNode<PlainNode>()
|
val node = doc.createNode<PlainNode>()
|
||||||
doc.addNode(node)
|
doc.addNode(node)
|
||||||
|
node.shouldIndexLocation = true
|
||||||
node.model { setTranslation(10, 0, 10) }
|
node.model { setTranslation(10, 0, 10) }
|
||||||
node.absolute.translation shouldBe Vector3d(10, 0, 10)
|
node.absolute.translation shouldBe Vector3d(10, 0, 10)
|
||||||
val nodeTwo = doc.createNode<PlainNode>()
|
val nodeTwo = doc.createNode<PlainNode>()
|
||||||
|
nodeTwo.shouldIndexLocation = true
|
||||||
node.addNode(nodeTwo)
|
node.addNode(nodeTwo)
|
||||||
nodeTwo.model { setTranslation(-10, 20, 0) }
|
nodeTwo.model { setTranslation(-10, 20, 0) }
|
||||||
nodeTwo.absolute.translation shouldBe Vector3d(0, 20, 10)
|
nodeTwo.absolute.translation shouldBe Vector3d(0, 20, 10)
|
||||||
|
@ -40,6 +42,8 @@ class PositionTest : StringSpec({
|
||||||
nodeTwo.model { setTranslation(-10, 20, 0) }
|
nodeTwo.model { setTranslation(-10, 20, 0) }
|
||||||
nodeThree.model { setTranslation(0, 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(10, 0, 10) shouldHaveSingleElement node
|
||||||
doc.findAt(0, 20, 10) shouldHaveSingleElement nodeTwo
|
doc.findAt(0, 20, 10) shouldHaveSingleElement nodeTwo
|
||||||
doc.findAt(0, 40, 10) shouldHaveSingleElement nodeThree
|
doc.findAt(0, 40, 10) shouldHaveSingleElement nodeThree
|
||||||
|
@ -64,6 +68,8 @@ class PositionTest : StringSpec({
|
||||||
nodeTwo.model { setTranslation(-10, 20, 0) }
|
nodeTwo.model { setTranslation(-10, 20, 0) }
|
||||||
nodeThree.model { setTranslation(0, 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()
|
val result = doc.findInRegion(Vector3d(0, 0, 0), Vector3d(0, 20, 20)).toList()
|
||||||
result shouldHaveSize 1
|
result shouldHaveSize 1
|
||||||
result shouldHaveSingleElement nodeTwo
|
result shouldHaveSingleElement nodeTwo
|
||||||
|
@ -78,6 +84,9 @@ class PositionTest : StringSpec({
|
||||||
val node = doc.createNode<PlainNode>()
|
val node = doc.createNode<PlainNode>()
|
||||||
val nodeTwo = doc.createNode<PlainNode>()
|
val nodeTwo = doc.createNode<PlainNode>()
|
||||||
val nodeThree = doc.createNode<PlainNode>()
|
val nodeThree = doc.createNode<PlainNode>()
|
||||||
|
|
||||||
|
listOf(node, nodeTwo, nodeThree).forEach { it.shouldIndexLocation = true }
|
||||||
|
|
||||||
doc.addNode(node)
|
doc.addNode(node)
|
||||||
node.addNode(nodeTwo)
|
node.addNode(nodeTwo)
|
||||||
nodeTwo.addNode(nodeThree)
|
nodeTwo.addNode(nodeThree)
|
||||||
|
|
BIN
tools/lwjglx-debug-1.0.0.jar
Normal file
BIN
tools/lwjglx-debug-1.0.0.jar
Normal file
Binary file not shown.
Loading…
Reference in a new issue