/* Copyright (c) 2016 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "gamemath/Matrix4x4f.h" #include "gamemath/Quaternionf.h" #include "gamemath/Vector3f.h" #include "gamemath/VectorConversions.h" #include "renderer/VertexTypes.h" #include "shadercollection/ShaderCollection.h" #include "shell/Shell.h" #include "shell/ShellKeyCodes.h" #include "testharness/Obj3DModelIO.h" #include "testharness/SharedEvents.h" #include "testharness/TestHarness_globals.h" #include "testharness/TrimeshViewerScreen.h" #include #include #include #include #define stemobject_implementation TrimeshViewerScreen stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_entry(activate); stemobject_vtable_entry(deactivate); stemobject_vtable_end(); TrimeshViewerScreen * TrimeshViewerScreen_create(Renderer * renderer) { stemobject_create_implementation(init, renderer) } bool TrimeshViewerScreen_init(TrimeshViewerScreen * self, Renderer * renderer) { call_super(init, self); self->renderer = renderer; self->renderLayer = RenderLayer_create(RENDER_LAYER_SORT_NONE, NULL, NULL); self->shaderConfiguration = ShaderConfiguration3DLit_create(ShaderCollection_get3DLitShader()); call_virtual(setTexture, self->shaderConfiguration, 0, g_blankTexture, false); self->renderPipelineConfiguration = default3DRenderPipelineConfiguration(RENDER_BLEND_NONE); self->triangleRenderable = Renderable_createWithVertexBuffer(PRIMITIVE_TRIANGLES, &self->renderPipelineConfiguration, self->shaderConfiguration, VertexBuffer_create(self->shaderConfiguration->shader->vertexFormat, NULL, 0, NULL, 0, BUFFER_USAGE_STREAM), true, NULL, NULL); self->normalRenderable = Renderable_createWithVertexBuffer(PRIMITIVE_LINES, &self->renderPipelineConfiguration, self->shaderConfiguration, VertexBuffer_create(self->shaderConfiguration->shader->vertexFormat, NULL, 0, NULL, 0, BUFFER_USAGE_STREAM), true, NULL, NULL); RenderLayer_addRenderable(self->renderLayer, self->triangleRenderable, 0, RECT4i_EMPTY); RenderLayer_addRenderable(self->renderLayer, self->normalRenderable, 0, RECT4i_EMPTY); return true; } void TrimeshViewerScreen_dispose(TrimeshViewerScreen * self) { if (self->trimesh != NULL) { CollisionStaticTrimesh_dispose(self->trimesh); self->trimesh = NULL; } Renderable_dispose(self->triangleRenderable); Renderable_dispose(self->normalRenderable); ShaderConfiguration3DLit_dispose(self->shaderConfiguration); RenderLayer_dispose(self->renderLayer); call_super(dispose, self); } static void writeTrimeshTriangleVertices(CollisionStaticTrimesh * trimesh, VertexIO * vertexIO) { struct vertex_p3f_t2f_n3f_c4f vertices[trimesh->triangleCount * 3], vertex = {.texCoords = {0.0f, 0.0f}, .color = {1.0f, 1.0f, 1.0f, 1.0f}}; uint32_t indexes[trimesh->triangleCount * 3]; Vector3f normal; Vector3f position; for (unsigned int triangleIndex = 0; triangleIndex < trimesh->triangleCount; triangleIndex++) { normal = Vector3x_toVector3f(trimesh->triangles[triangleIndex].normal); vertex.normal[0] = normal.x; vertex.normal[1] = normal.y; vertex.normal[2] = normal.z; position = Vector3x_toVector3f(trimesh->vertices[trimesh->triangles[triangleIndex].vertexIndexes[0]].position); vertex.position[0] = position.x; vertex.position[1] = position.y; vertex.position[2] = position.z; vertices[triangleIndex * 3 + 0] = vertex; position = Vector3x_toVector3f(trimesh->vertices[trimesh->triangles[triangleIndex].vertexIndexes[1]].position); vertex.position[0] = position.x; vertex.position[1] = position.y; vertex.position[2] = position.z; vertices[triangleIndex * 3 + 1] = vertex; position = Vector3x_toVector3f(trimesh->vertices[trimesh->triangles[triangleIndex].vertexIndexes[2]].position); vertex.position[0] = position.x; vertex.position[1] = position.y; vertex.position[2] = position.z; vertices[triangleIndex * 3 + 2] = vertex; indexes[triangleIndex * 3 + 0] = triangleIndex * 3 + 0; indexes[triangleIndex * 3 + 1] = triangleIndex * 3 + 1; indexes[triangleIndex * 3 + 2] = triangleIndex * 3 + 2; } VertexIO_writeIndexedVertices(vertexIO, sizeof(vertices) / sizeof(vertices[0]), vertices, sizeof(indexes) / sizeof(indexes[0]), indexes); } #define NORMAL_SCALE 0.25f static void writeTrimeshNormalVertices(CollisionStaticTrimesh * trimesh, VertexIO * vertexIO) { struct vertex_p3f_t2f_n3f_c4f vertices[trimesh->vertexCount * 2], vertex = {.texCoords = {0.0f, 0.0f}, .color = {1.0f, 1.0f, 1.0f, 1.0f}}; uint32_t indexes[trimesh->vertexCount * 2]; Vector3f normal; Vector3f position; for (unsigned int vertexIndex = 0; vertexIndex < trimesh->vertexCount; vertexIndex++) { normal = Vector3x_toVector3f(trimesh->vertices[vertexIndex].normal); vertex.normal[0] = normal.x; vertex.normal[1] = normal.y; vertex.normal[2] = normal.z; position = Vector3x_toVector3f(trimesh->vertices[vertexIndex].position); vertex.position[0] = position.x; vertex.position[1] = position.y; vertex.position[2] = position.z; vertices[vertexIndex * 2 + 0] = vertex; vertex.position[0] += normal.x * NORMAL_SCALE; vertex.position[1] += normal.y * NORMAL_SCALE; vertex.position[2] += normal.z * NORMAL_SCALE; vertices[vertexIndex * 2 + 1] = vertex; indexes[vertexIndex * 2 + 0] = vertexIndex * 2 + 0; indexes[vertexIndex * 2 + 1] = vertexIndex * 2 + 1; } VertexIO_writeIndexedVertices(vertexIO, sizeof(vertices) / sizeof(vertices[0]), vertices, sizeof(indexes) / sizeof(indexes[0]), indexes); } static void initRenderablesWithTrimesh(TrimeshViewerScreen * self, CollisionStaticTrimesh * trimesh) { VertexIO * vertexIO = VertexIO_create(self->shaderConfiguration->shader->vertexFormat, 0.0, 0.0); writeTrimeshTriangleVertices(trimesh, vertexIO); VertexBuffer_bufferData(self->triangleRenderable->vertexBuffer, vertexIO->vertices, vertexIO->vertexCount, vertexIO->indexes, vertexIO->indexCount, BUFFER_USAGE_STREAM); VertexIO_dispose(vertexIO); vertexIO = VertexIO_create(self->shaderConfiguration->shader->vertexFormat, 0.0, 0.0); writeTrimeshNormalVertices(trimesh, vertexIO); VertexBuffer_bufferData(self->normalRenderable->vertexBuffer, vertexIO->vertices, vertexIO->vertexCount, vertexIO->indexes, vertexIO->indexCount, BUFFER_USAGE_STREAM); } static void loadTrimeshPreset1(TrimeshViewerScreen * self) { CollisionStaticTrimesh * trimesh; Vector3x vertices[] = {{0x01000, 0x01000, 0x01000}, {0x20000, 0x00000, 0x00000}, {0x00000, 0x00000, 0x20000}, {0x01000, 0x01000, 0x01000}, {0x00000, 0x00000, 0x20000}, {0x00000, 0x20000, 0x00000}, {0x01000, 0x01000, 0x01000}, {0x00000, 0x20000, 0x00000}, {0x20000, 0x00000, 0x00000}, {0x00000, 0x20000, 0x00000}, {0x00000, 0x00000, 0x20000}, {0x20000, 0x00000, 0x00000}}; trimesh = CollisionStaticTrimesh_create(NULL, NULL, NULL, vertices, 12); initRenderablesWithTrimesh(self, trimesh); CollisionStaticTrimesh_dispose(trimesh); Shell_redisplay(); } static void loadTrimeshPreset2(TrimeshViewerScreen * self) { CollisionStaticTrimesh * trimesh; Vector3x vertices[] = {{0x00000, 0x00000, 0x00000}, {0x10000, 0x00000, 0x00000}, {0x00000, 0x10000, 0x00000}, {0x00000, 0x10000, 0x00000}, {0x10000, 0x00000, 0x00000}, {0x10000, 0x10000, -0x10000}, {0x10000, 0x00000, 0x00000}, {0x00000, 0x00000, 0x00000}, {0x10000, 0x10000, -0x10000}}; trimesh = CollisionStaticTrimesh_create(NULL, NULL, NULL, vertices, 9); initRenderablesWithTrimesh(self, trimesh); CollisionStaticTrimesh_dispose(trimesh); Shell_redisplay(); } static bool draw(Atom eventID, void * eventData, void * context) { TrimeshViewerScreen * self = context; Matrix4x4f matrix = MATRIX4x4f_IDENTITY; Matrix4x4f_translate(&matrix, 0.0f, 0.0f, -xtof(self->cameraDistance)); Matrix4x4f_multiply(&matrix, Quaternionf_toMatrix(Quaternionx_toQuaternionf(self->cameraDirection))); Matrix4x4f_translate(&matrix, xtof(self->cameraFocus.x), xtof(self->cameraFocus.y), xtof(self->cameraFocus.z)); call_virtual(setViewMatrix, self->shaderConfiguration, matrix); Renderer_clear(self->renderer, COLOR4f(0.0f, 0.0f, 0.0f, 0.0f)); Renderer_drawLayer(self->renderer, self->renderLayer, 0.0, 0.0); return true; } static bool keyDown(Atom eventID, void * eventData, void * context) { TrimeshViewerScreen * self = context; struct keyEvent * event = eventData; switch (event->keyCode) { case KEY_CODE_O: { const char * filePath; if (Shell_openFileDialog(NULL, NULL, NULL, &filePath, 0, NULL)) { CollisionStaticTrimesh * trimesh = loadObjStaticModelFile(filePath); if (trimesh != NULL) { initRenderablesWithTrimesh(self, trimesh); CollisionStaticTrimesh_dispose(trimesh); Shell_redisplay(); } } break; } case KEY_CODE_1: loadTrimeshPreset1(self); break; case KEY_CODE_2: loadTrimeshPreset2(self); break; } return false; } static bool mouseDown(Atom eventID, void * eventData, void * context) { Shell_setMouseDeltaMode(true); Shell_redisplay(); return true; } static bool mouseUp(Atom eventID, void * eventData, void * context) { Shell_setMouseDeltaMode(false); Shell_redisplay(); return true; } #define CAMERA_LOOK_SENSITIVITY 0x00100 #define CAMERA_ZOOM_SENSITIVITY 0x00060 #define CAMERA_DRAG_SENSITIVITY 0x00060 static bool mouseDragged(Atom eventID, void * eventData, void * context) { TrimeshViewerScreen * self = context; struct mouseEvent * event = eventData; Vector3x offset; if (event->modifiers & MODIFIER_SHIFT_BIT) { // Pan if (event->modifiers & MODIFIER_CONTROL_BIT) { offset.x = 0x00000; offset.y = 0x00000; offset.z = -xmul(ftox(event->delta.y), xmul(CAMERA_DRAG_SENSITIVITY, self->cameraDistance)); } else { offset.x = xmul(ftox(event->delta.x), xmul(CAMERA_DRAG_SENSITIVITY, self->cameraDistance)); offset.y = -xmul(ftox(event->delta.y), xmul(CAMERA_DRAG_SENSITIVITY, self->cameraDistance)); offset.z = 0x00000; } offset = Quaternionx_multiplyVector3x(Quaternionx_inverted(self->cameraDirection), offset); self->cameraFocus = Vector3x_add(self->cameraFocus, offset); Shell_redisplay(); } else if (event->modifiers & MODIFIER_CONTROL_BIT) { // Zoom self->cameraDistance += xmul(ftox(event->delta.y), xmul(CAMERA_ZOOM_SENSITIVITY, self->cameraDistance)); if (self->cameraDistance < 0x10000) { self->cameraDistance = 0x10000; } Shell_redisplay(); } else { // Rotate Quaternionx_rotate(&self->cameraDirection, VECTOR3x_UP, xmul(ftox(event->delta.x), CAMERA_LOOK_SENSITIVITY)); Quaternionx_rotate(&self->cameraDirection, Quaternionx_multiplyVector3x(Quaternionx_inverted(self->cameraDirection), VECTOR3x_RIGHT), xmul(ftox(event->delta.y), CAMERA_LOOK_SENSITIVITY)); Shell_redisplay(); } return true; } static bool resized(Atom eventID, void * eventData, void * context) { TrimeshViewerScreen * self = context; Shell_redisplay(); call_virtual(setProjectionMatrix, self->shaderConfiguration, Matrix4x4f_perspective(MATRIX4x4f_IDENTITY, 70.0f, g_viewRatio, 0.5f, 800.0f)); return true; } void TrimeshViewerScreen_activate(TrimeshViewerScreen * self, Screen * lastScreen, const char * transitionName) { loadTrimeshPreset1(self); self->trimesh = NULL; self->cameraFocus = VECTOR3x_ZERO; self->cameraDirection = QUATERNIONx_IDENTITY; self->cameraDistance = 0xF0000; EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_UP), mouseUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DRAGGED), mouseDragged, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_RESIZED), resized, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); call_virtual(setProjectionMatrix, self->shaderConfiguration, Matrix4x4f_perspective(MATRIX4x4f_IDENTITY, 70.0f, g_viewRatio, 0.5f, 800.0f)); call_virtual(setLights, self->shaderConfiguration, VECTOR3f(0.0f, 8.0f, 8.0f), COLOR3f(1.0f, 1.0f, 1.0f), VECTOR3f(-1.0f, -2.0f, -8.0f), COLOR3f(0.8f, 0.8f, 0.8f), COLOR3f(0.2f, 0.2f, 0.105f)); Shell_redisplay(); } void TrimeshViewerScreen_deactivate(TrimeshViewerScreen * self, Screen * nextScreen, const char * transitionName) { if (self->trimesh != NULL) { CollisionStaticTrimesh_dispose(self->trimesh); self->trimesh = NULL; } EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_UP), mouseUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DRAGGED), mouseDragged, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_RESIZED), resized, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); }