add gl and example
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
eff722ea05
commit
7fc4b44571
@ -1,5 +0,0 @@
|
|||||||
<graph>
|
|
||||||
<box diameter="10" position="[33, 40, 3]">
|
|
||||||
|
|
||||||
</box>
|
|
||||||
</graph>
|
|
@ -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
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 384 KiB |
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
@ -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,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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
Binary file not shown.
Loading…
Reference in New Issue