Java tutorial
/* * Copyright LWJGL. All rights reserved. * License terms: https://www.lwjgl.org/license */ package org.lwjgl.demo.util.par; import org.lwjgl.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*; import org.lwjgl.util.par.*; import java.nio.*; import java.util.*; import static org.lwjgl.glfw.Callbacks.*; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL43.*; import static org.lwjgl.stb.STBEasyFont.*; import static org.lwjgl.system.MemoryStack.*; import static org.lwjgl.system.MemoryUtil.*; import static org.lwjgl.util.nfd.NativeFileDialog.*; import static org.lwjgl.util.par.ParShapes.*; public final class ParShapesDemo { private long window; private int width = 1024; private int height = 768; private ParShapesMesh mesh; private Callback debugCB; private int vbo; private int ibo; private boolean hasNormals; private int program; private int meshKey = 1; private int slices = 32, stacks = 32, seed = 1, subdivisions = 4; private boolean wireframe; private int hudVBO; private int hudVertexCount; private ParShapesDemo() { } private void init() { GLFWErrorCallback.createPrint().set(); if (!glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); } glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); window = glfwCreateWindow(width, height, "par_shapes demo", NULL, NULL); if (window == NULL) { throw new RuntimeException("Failed to create the GLFW window"); } glfwMakeContextCurrent(window); GL.createCapabilities(); debugCB = GLUtil.setupDebugMessageCallback(); if (debugCB != null && GL.getCapabilities().OpenGL43) { glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DEBUG_SEVERITY_NOTIFICATION, (IntBuffer) null, false); } glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> { if (action != GLFW_RELEASE) { return; } int scale; if ((mods & GLFW_MOD_CONTROL) != 0) { scale = 100; } else if ((mods & GLFW_MOD_SHIFT) != 0) { scale = 10; } else { scale = 1; } switch (key) { case GLFW_KEY_DOWN: if (slices > 3) { slices -= scale; if (slices < 3) { slices = 3; } updateMesh(); } break; case GLFW_KEY_UP: slices += scale; updateMesh(); break; case GLFW_KEY_LEFT: if (stacks > 1) { stacks -= scale; if (stacks < 1) { stacks = 1; } updateMesh(); } break; case GLFW_KEY_RIGHT: stacks += scale; updateMesh(); break; case GLFW_KEY_PAGE_DOWN: seed--; updateMesh(); break; case GLFW_KEY_PAGE_UP: seed++; updateMesh(); break; case GLFW_KEY_KP_SUBTRACT: if (subdivisions > 1) { subdivisions--; updateMesh(); } break; case GLFW_KEY_KP_ADD: subdivisions++; updateMesh(); break; case GLFW_KEY_1: case GLFW_KEY_2: case GLFW_KEY_3: case GLFW_KEY_4: case GLFW_KEY_5: case GLFW_KEY_6: case GLFW_KEY_7: case GLFW_KEY_8: updateMesh(key); break; case GLFW_KEY_E: if (mesh != null) { exportMesh(); } break; case GLFW_KEY_W: wireframe = !wireframe; updateHUD(); break; case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(window, true); break; } }); glfwSetFramebufferSizeCallback(window, (window, width, height) -> { this.width = width; this.height = height; updateViewport(width, height); }); // center window GLFWVidMode vidmode = Objects.requireNonNull(glfwGetVideoMode(glfwGetPrimaryMonitor())); glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2); glfwShowWindow(window); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glDisable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); updateViewport(width, height); glLoadIdentity(); glTranslatef(0.0f, 0.0f, -3.0f); glRotatef(45.0f, 1.0f, 0.0f, 0.0f); vbo = glGenBuffers(); ibo = glGenBuffers(); hudVBO = glGenBuffers(); updateMesh(GLFW_KEY_1); int vshader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vshader, "#version 110\n" + "\n" + "attribute vec3 position;\n" + "attribute vec3 normal;\n" + "\n" + "varying vec3 viewNormal;\n" + "\n" + "void main(void) {\n" + " viewNormal = gl_NormalMatrix * normal;\n" + " gl_Position = gl_ModelViewProjectionMatrix * vec4(position, 1.0);\n" + "}\n"); glCompileShader(vshader); if (glGetShaderi(vshader, GL_COMPILE_STATUS) == GL_FALSE) { throw new IllegalStateException(glGetShaderInfoLog(vshader)); } int fshader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fshader, "#version 110\n" + "\n" + "varying vec3 viewNormal;\n" + "\n" + "void main(void) {\n" + " gl_FragColor = vec4(normalize(viewNormal), 1.0);\n" + "}\n"); glCompileShader(fshader); if (glGetShaderi(fshader, GL_COMPILE_STATUS) == GL_FALSE) { throw new IllegalStateException(glGetShaderInfoLog(fshader)); } program = glCreateProgram(); glAttachShader(program, vshader); glAttachShader(program, fshader); glBindAttribLocation(program, 0, "position"); glBindAttribLocation(program, 1, "normal"); glLinkProgram(program); if (glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) { throw new IllegalStateException(glGetProgramInfoLog(program)); } glDeleteShader(fshader); glDeleteShader(vshader); } private static void updateViewport(int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); double fovY = 45.0; double zNear = 0.01; double zFar = 100.0; double aspect = (double) width / (double) height; double fH = Math.tan(fovY / 360 * Math.PI) * zNear; double fW = fH * aspect; glFrustum(-fW, fW, -fH, fH, zNear, zFar); glMatrixMode(GL_MODELVIEW); } private void updateMesh() { updateMesh(meshKey); } private void updateMesh(int key) { meshKey = key; if (mesh != null) { par_shapes_free_mesh(mesh); mesh = null; } switch (key) { case GLFW_KEY_1: mesh = par_shapes_create_parametric_sphere(slices, stacks); break; case GLFW_KEY_2: mesh = par_shapes_create_hemisphere(slices, stacks); break; case GLFW_KEY_3: mesh = par_shapes_create_cylinder(slices, stacks); break; case GLFW_KEY_4: mesh = par_shapes_create_torus(slices, stacks, 0.25f); break; case GLFW_KEY_5: mesh = par_shapes_create_trefoil_knot(slices, stacks, 1.0f); break; case GLFW_KEY_6: mesh = par_shapes_create_klein_bottle(slices, stacks); if (mesh != null) { par_shapes_scale(mesh, 0.1f, 0.1f, 0.1f); } break; case GLFW_KEY_7: String program = " sx 2 sy 2" + " ry 90 rx 90" + " shape tube rx 15 call rlimb rx -15" + " shape tube rx -15 call llimb rx 15" + " shape tube ry 15 call rlimb ry -15" + " shape tube ry 15 call llimb ry -15" + " rule rlimb" + " sx 0.925 sy 0.925 tz 1 rx 1.2" + " call rlimb2" + " rule rlimb2.1" + " shape connect" + " call rlimb" + " rule rlimb2.1" + " rx 15 shape tube call rlimb rx -15" + " rx -15 shape tube call llimb rx 15" + " rule rlimb.1" + " call llimb" + " rule llimb.1" + " call rlimb" + " rule llimb.10" + " sx 0.925 sy 0.925" + " tz 1" + " rx -1.2" + " shape connect" + " call llimb"; mesh = par_shapes_create_lsystem(program, slices, 60); if (mesh != null) { par_shapes_scale(mesh, 0.05f, 0.05f, 0.05f); par_shapes_translate(mesh, 0.0f, -1.0f, 0.0f); } break; case GLFW_KEY_8: mesh = par_shapes_create_rock(seed, subdivisions); break; } if (mesh != null) { int vc = mesh.npoints(); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vc * (3 + 3 + 2) * 4, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, vc * (0 + 0) * 4, mesh.points(vc * 3)); glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, vc * (0 + 0) * 4); FloatBuffer normals = mesh.normals(vc * 3); if (hasNormals = normals != null) { glBufferSubData(GL_ARRAY_BUFFER, vc * (3 + 0) * 4, normals); glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, vc * (3 + 0) * 4); } int tc = mesh.ntriangles(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.triangles(tc * 3), GL_STATIC_DRAW); } updateHUD(); } private void updateHUD() { ByteBuffer color = memAlloc(4); ByteBuffer buffer = memAlloc(1024 * 60); setColor(color, 255, 255, 255, 0); String[] meshes = { "Sphere", "Hemisphere", "Cylinder", "Torus", "Trefoil knot", "Klein bottle", "l-system", "Rock" }; for (int i = 0; i < meshes.length; i++) { if (i == meshKey - GLFW_KEY_1) { setColor(color, 255, 0, 255, 255); } else { setColor(color, 255, 255, 255, 255); } print(0, i * 10, "(" + (i + 1) + ") " + meshes[i], color, buffer); } if (mesh != null) { setColor(color, 255, 255, 255, 255); print(0, meshes.length * 10 + 20, "Triangles: " + mesh.ntriangles(), color, buffer); print(4, meshes.length * 10 + 10, "Vertices: " + mesh.npoints(), color, buffer); } String[] controls = { "(E) Export to .obj", "(W) Wireframe:", "(up/down) Slices:", "(left/right) Stacks:", "(page up/down) Seed:", "(-/+) Subdivisions:" }; int alignment = stb_easy_font_width(controls[4]); int y = height / 2 - controls.length * 10 - 4; setColor(color, 255, 255, 0, 255); y = print(alignment - stb_easy_font_width(controls[0]), y, controls[0], color, buffer); y = print(alignment - stb_easy_font_width(controls[1]), y, controls[1] + " " + (wireframe ? "ON" : "OFF"), color, buffer); if (meshKey == GLFW_KEY_8) { setColor(color, 64, 64, 0, 255); } y = print(alignment - stb_easy_font_width(controls[2]), y, controls[2] + " " + slices, color, buffer); if (meshKey == GLFW_KEY_7) { setColor(color, 64, 64, 0, 255); } y = print(alignment - stb_easy_font_width(controls[3]), y, controls[3] + " " + stacks, color, buffer); if (meshKey == GLFW_KEY_8) { setColor(color, 255, 255, 0, 255); } else { setColor(color, 64, 64, 0, 255); } y = print(alignment - stb_easy_font_width(controls[4]), y, controls[4] + " " + seed, color, buffer); print(alignment - stb_easy_font_width(controls[5]), y, controls[5] + " " + subdivisions, color, buffer); if (mesh == null) { String msg = "Error in mesh generation!"; setColor(color, 255, 0, 0, 255); print((width / 2 - stb_easy_font_width(msg)) / 2, (height / 2 - 5) / 2, msg, color, buffer); } buffer.flip(); hudVertexCount = buffer.limit() >> 4; glBindBuffer(GL_ARRAY_BUFFER, hudVBO); glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW); glVertexPointer(2, GL_FLOAT, 4 * 4, 0); glColorPointer(4, GL_UNSIGNED_BYTE, 4 * 4, 3 * 4); memFree(color); memFree(buffer); } private static void setColor(ByteBuffer color, int r, int g, int b, int a) { color.put(0, (byte) r).put(1, (byte) g).put(2, (byte) b).put(3, (byte) a); } private static int print(int x, int y, String text, ByteBuffer color, ByteBuffer buffer) { int quads = stb_easy_font_print(x, y, text, color, buffer); buffer.position(buffer.position() + quads * (4 * 4 * 4)); return y + 10; } private void cleanup() { if (mesh != null) { par_shapes_free_mesh(mesh); mesh = null; } glDeleteProgram(program); glDeleteBuffers(hudVBO); glDeleteBuffers(ibo); glDeleteBuffers(vbo); glfwFreeCallbacks(window); glfwTerminate(); Objects.requireNonNull(glfwSetErrorCallback(null)).free(); if (debugCB != null) { debugCB.free(); } } private void run() { init(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (mesh != null) { glUseProgram(program); glEnableVertexAttribArray(0); if (hasNormals) { glEnableVertexAttribArray(1); } else { glVertexAttrib3f(1, 1.0f, 1.0f, 1.0f); } glBindBuffer(GL_ARRAY_BUFFER, vbo); glDrawElements(GL_TRIANGLES, mesh.ntriangles() * 3, GL_UNSIGNED_INT, 0); if (hasNormals) { glDisableVertexAttribArray(1); } glUseProgram(0); if (wireframe) { glEnable(GL_POLYGON_OFFSET_LINE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glPolygonOffset(-1, -1); glColor3f(1.0f, 1.0f, 1.0f); glDrawElements(GL_TRIANGLES, mesh.ntriangles() * 3, GL_UNSIGNED_INT, 0); glPolygonOffset(0.0f, 0.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glDisable(GL_POLYGON_OFFSET_LINE); } glDisableVertexAttribArray(0); } // HUD glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.0, width, height, 0.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glTranslatef(4.0f, 4.0f, 0.0f); glScalef(2.0f, 2.0f, 1.0f); glBindBuffer(GL_ARRAY_BUFFER, hudVBO); glDrawArrays(GL_QUADS, 0, hudVertexCount); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glfwSwapBuffers(window); } cleanup(); } private void exportMesh() { try (MemoryStack stack = stackPush()) { PointerBuffer pp = stack.mallocPointer(1); int result = NFD_SaveDialog("obj", null, pp); switch (result) { case NFD_OKAY: long path = pp.get(0); npar_shapes_export(mesh.address(), path); nNFD_Free(path); break; case NFD_ERROR: System.err.format("Error: %s\n", NFD_GetError()); } } } public static void main(String[] args) { new ParShapesDemo().run(); } }