package me.eater.threedom.example import me.eater.threedom.dom.Document import me.eater.threedom.dom.IDocument import me.eater.threedom.dom.PlainNode import me.eater.threedom.dom.createNode import me.eater.threedom.example.shader.Plain import me.eater.threedom.example.vertex.Simple import me.eater.threedom.gl.GL import me.eater.threedom.gl.ShaderPreProcessor import me.eater.threedom.gl.dom.PerspectiveCamera import me.eater.threedom.gl.dom.TriMesh import me.eater.threedom.gl.texture.Texture import me.eater.threedom.gl.vertex.VertexArrayObject import me.eater.threedom.gl.vertex.VertexBuffer import me.eater.threedom.utils.joml.toFloat import me.eater.threedom.utils.joml.translation import me.eater.threedom.utils.joml.vec3 import org.joml.Vector2f import org.joml.Vector3f import org.lwjgl.glfw.GLFW.* import org.lwjgl.glfw.GLFWErrorCallback import org.lwjgl.opengl.GL.createCapabilities import org.lwjgl.opengl.GL11.* import org.lwjgl.system.MemoryStack.stackPush import org.lwjgl.system.MemoryUtil.NULL import java.time.Instant import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin fun main() { GLFWErrorCallback.createPrint(System.err).set() if (!glfwInit()) { println("Unable to initialize GLFW") return } glfwDefaultWindowHints() glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4) glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4) glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) val window = glfwCreateWindow(300, 300, "Hello world", NULL, NULL) if (window == NULL) { println("Failed creating window") return } val stack = stackPush() val pWidth = stack.mallocInt(1) val pHeight = stack.mallocInt(1) // Get the window size passed to glfwCreateWindow glfwGetWindowSize(window, pWidth, pHeight) // Get the resolution of the primary monitor val vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()) ?: return // Center the window glfwSetWindowPos( window, (vidmode.width() - pWidth.get(0)) / 2, (vidmode.height() - pHeight.get(0)) / 2 ) // Make the OpenGL context current glfwMakeContextCurrent(window) var width = pWidth.get(0) var height = pHeight.get(0) // Enable v-sync glfwSwapInterval(1) glfwShowWindow(window) createCapabilities() val doc: IDocument = Document() val triMesh = doc.createNode() val preProcessor = ShaderPreProcessor.createDefault() val mesh = listOf( -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f ) val buffer = mesh .chunked(6) .map { Simple().apply { position = vec3(it[0], it[1], it[2]) normal = vec3(it[3], it[4], it[5]) } } .let { VertexBuffer(it) } val VAO = VertexArrayObject { buffer(Simple::position) buffer(Simple::normal) } triMesh.mesh = VAO println("Loading mesh") println("Loading shader program") val plainShader = Plain.load(preProcessor) val lampShader = Plain.lamp(preProcessor) val texture = Texture() texture.load(Simple::class.java.getResourceAsStream("/textures/fujiwara.jpg").readAllBytes()) plainShader.apply { objectColor = vec3(1.0, 0.5, 0.0) lightColor = vec3(1.0, 1.0, 1.0) } triMesh.use(lampShader) val empty = doc.createNode() doc.addNode(empty) empty.addNode(triMesh) val around = mutableListOf() val amount = 3 val deg = (PI * 2) / amount for (i in 0 until amount) { val trimeshDup = triMesh.clone() trimeshDup.use(plainShader) around.add(trimeshDup) triMesh.addNode(trimeshDup) trimeshDup.model { translate(2 * sin(deg * i), 0.0, 2 * cos(deg * i)) } } val camera = doc.createNode() doc.addNode(camera) glfwSetFramebufferSizeCallback(window) { _, newWidth, newHeight -> glViewport(0, 0, newWidth, newHeight) camera.width = newWidth.toDouble() camera.height = newHeight.toDouble() } glfwSetKeyCallback(window) { _, key, _, action, _ -> if ((key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) && action == GLFW_RELEASE) glfwSetWindowShouldClose(window, true) var z = 0.0 var x = 0.0 val speed = 0.1 if (key == GLFW_KEY_UP && action != GLFW_RELEASE) { z += speed } if (key == GLFW_KEY_DOWN && action != GLFW_RELEASE) { z -= speed } if (key == GLFW_KEY_LEFT && action != GLFW_RELEASE) { x += speed } if (key == GLFW_KEY_RIGHT && action != GLFW_RELEASE) { x -= speed } if (x != 0.0 || z != 0.0) { camera.model { translateLocal(x, 0.0, z) } } } println("Starting render loop") glClearColor(0.3f, 0.2f, 0.2f, 1.0f) glEnable(GL_DEPTH_TEST) while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) empty.model { setTranslation(0.0, 0.0, sin(Instant.now().toEpochMilli().toDouble() / 1000) - 3) } triMesh.model { rotateY(0.005) } around.forEach { it.model { rotateY(-0.005) } } plainShader.lightPos = triMesh.absolute.translation.toFloat() plainShader.viewPos = camera.absolute.translation.toFloat() doc.render(GL, camera) glfwSwapBuffers(window) glfwPollEvents() } }