├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ └── codeStyleConfig.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── voxl.iml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── conanfile.txt ├── include └── voxl.h ├── models └── bunny_low.obj ├── samples ├── benchmark.cpp └── objviewer.cpp └── src ├── internal.h ├── polyconv.cpp ├── tribox.cpp ├── tribox.h └── voxl.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake 2 | /cmake-build-* 3 | CMakeUserPresets.json -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/voxl.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(voxl CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | find_package(GLFW3 REQUIRED) 7 | find_package(PNG REQUIRED) 8 | find_package(HIGHWAY REQUIRED) 9 | 10 | include_directories(include) 11 | 12 | add_library(voxl OBJECT src/voxl.cpp) 13 | target_link_libraries(voxl highway::hwy) 14 | 15 | add_library(polyconv OBJECT src/tribox.cpp src/polyconv.cpp) 16 | target_link_libraries(polyconv PNG::PNG) 17 | 18 | add_executable(objviewer samples/objviewer.cpp) 19 | target_link_libraries(objviewer voxl polyconv glfw) 20 | 21 | add_executable(benchmark samples/benchmark.cpp) 22 | target_link_libraries(benchmark voxl polyconv glfw) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voxl 2 | 3 | voxl is a real time sparse voxel octree renderer. It is mainly made as an experiment inspired by [Euclideon](https://www.youtube.com/watch?v=00gAbgBu8R4). Due to the shortage of high resolution voxel models it supports loading of polygon models in the .obj format. 4 | 5 | ## Screenshots 6 | 7 | ![voxl screenshot of dragon](https://raw.githubusercontent.com/rools/voxl/assets/voxl1.png) 8 | ![voxl screenshot of bunnies](https://raw.githubusercontent.com/rools/voxl/assets/voxl2.png) 9 | 10 | ## License 11 | 12 | Copyright 2012 Robert Olsson 13 | 14 | Licensed under the Apache License, Version 2.0 (the "License"); 15 | you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | glfw/3.3.8 3 | libpng/1.6.40 4 | highway/1.0.5 5 | 6 | [generators] 7 | CMakeDeps 8 | CMakeToolchain 9 | -------------------------------------------------------------------------------- /include/voxl.h: -------------------------------------------------------------------------------- 1 | #ifndef VOXL_H 2 | #define VOXL_H 3 | 4 | #include 5 | #include 6 | 7 | #define MODEL_LIMIT 16 8 | 9 | #ifndef M_PI 10 | #define M_PI 3.14159265358979323846264338327950288 11 | #endif 12 | 13 | #ifndef M_PI_2 14 | #define M_PI_2 1.57079632679489661923132169163975144 15 | #endif 16 | 17 | /** 18 | * Error codes 19 | */ 20 | typedef enum vx_error { 21 | VX_ERROR_OK = 0, // No error 22 | VX_ERROR_INVALID_DETAIL_LEVEL = 1, // Unvalid detail level specifier 23 | VX_ERROR_TEXTURE_NOT_FOUND = 2, // Referenced texture not found 24 | VX_ERROR_INVALID_OCTREE = 3, // The octree was invalid 25 | VX_ERROR_CANT_OPEN_FILE = 4, // The specified file can't be opened 26 | VX_ERROR_POLYGON_FILE_ERROR = 5, // The polygon file isn't valid 27 | VX_ERROR_INVALID_MODEL = 6, // The specified model is invalid 28 | VX_ERROR_INVALID_IMAGE = 7, // The specified image is invalid 29 | VX_ERROR_UNSUPPORTED_IMAGE = 8 // The specified image is unsupported 30 | } vx_error; 31 | 32 | /** 33 | * An octree 34 | */ 35 | struct vx_octree { 36 | struct vx_node *root; 37 | uint32_t size; 38 | uint32_t allocated_size; 39 | 40 | // Pointers to loaded models in the octree. 41 | uint32_t models[MODEL_LIMIT]; 42 | 43 | int model_count; 44 | }; 45 | 46 | /** 47 | * Camera 48 | */ 49 | struct vx_camera { 50 | float position[3]; 51 | float direction[3]; 52 | float rotation_vector[3]; 53 | float rotation; 54 | 55 | float tan_fov; 56 | 57 | int resolution[2]; 58 | }; 59 | 60 | /** 61 | * A render context associated with a specific octree and camera. 62 | */ 63 | 64 | // Initialize an empty octree. 65 | void vx_init_octree(struct vx_octree *octree); 66 | 67 | // Generate a frame and save it to buffer. 68 | void vx_render_frame(struct vx_octree *octree, struct vx_camera *camera, char *buffer); 69 | 70 | // Rasterize the polygon model with the specified level of detail and save it to octree. 71 | vx_error vx_poly_to_octree(const char *file_name, struct vx_octree *octree, int detail); 72 | 73 | // Write the octree to the specified file. 74 | vx_error vx_write_octree(FILE *file, struct vx_octree octree); 75 | 76 | // Load an octree as a model to another octree and return model id. 77 | int vx_load_model(struct vx_octree *octree, struct vx_octree *model); 78 | 79 | // Place a loaded model in an octree. 80 | vx_error vx_place_model(struct vx_octree *octree, int model, float x, float y, float z, int detail); 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /samples/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "voxl.h" 10 | 11 | const int WINDOW_WIDTH = 800; 12 | const int WINDOW_HEIGHT = 600; 13 | 14 | const float MOVEMENT_SPEED = 0.01f; 15 | 16 | const int FRAME_COUNT = 100; 17 | 18 | double benchmark(GLFWwindow *window, struct vx_octree *octree, int draw); 19 | 20 | void print_help(FILE *stream, const char *name) { 21 | fprintf(stream, "usage: %s [--draw] obj_file\n", name); 22 | } 23 | 24 | double get_time() { 25 | struct timeval t; 26 | gettimeofday(&t, NULL); 27 | return t.tv_sec + 1.0e-6 * t.tv_usec; 28 | } 29 | 30 | void glfw_error_callback(int error, const char *description) { 31 | fputs(description, stderr); 32 | } 33 | 34 | // A simple benchmark routine to make quick performance tests. 35 | double benchmark(GLFWwindow *window, struct vx_octree *octree, int draw) { 36 | char *buffer = (char *) malloc(WINDOW_WIDTH * WINDOW_HEIGHT * sizeof(uint32_t)); 37 | struct vx_camera camera; 38 | double start_time, end_time; 39 | GLuint texture; 40 | 41 | camera.resolution[0] = WINDOW_WIDTH; 42 | camera.resolution[1] = WINDOW_HEIGHT; 43 | camera.position[1] = 0.51f; 44 | camera.direction[0] = 1.0f; 45 | camera.direction[1] = 0.0f; 46 | camera.direction[2] = 0.0f; 47 | camera.rotation_vector[0] = 0.0f; 48 | camera.rotation_vector[1] = 1.0f; 49 | camera.rotation_vector[2] = 0.0f; 50 | camera.rotation = 0.0f; 51 | camera.tan_fov = tanf(M_PI * 0.25); 52 | 53 | if (draw) { 54 | glGenTextures(1, &texture); 55 | glBindTexture(GL_TEXTURE_2D, texture); 56 | 57 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 58 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 59 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 60 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 61 | } 62 | 63 | start_time = get_time(); 64 | 65 | for (int i = 0; i < FRAME_COUNT; ++i) { 66 | float progress = (float) i / FRAME_COUNT; 67 | 68 | // Rotate the camera around the center, always facing the center. 69 | camera.position[0] = 0.7f * cos(progress * M_PI * 2) + 0.5f; 70 | camera.position[2] = 0.7f * sin(progress * M_PI * 2) + 0.5f; 71 | camera.rotation = -progress * M_PI * 2 - 0.5 * M_PI; 72 | 73 | vx_render_frame(octree, &camera, buffer); 74 | 75 | if (draw) { 76 | glClear(GL_COLOR_BUFFER_BIT); 77 | 78 | glLoadIdentity(); 79 | 80 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WINDOW_WIDTH, WINDOW_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 81 | glColor3f(1, 1, 1); 82 | glBegin(GL_QUADS); 83 | glTexCoord2f(0.0, 0.0); 84 | glVertex2f(-1, -1); 85 | glTexCoord2f(0.0, 1.0); 86 | glVertex2f(-1, 1); 87 | glTexCoord2f(1.0, 1.0); 88 | glVertex2f(1, 1); 89 | glTexCoord2f(1.0, 0.0); 90 | glVertex2f(1, -1); 91 | glEnd(); 92 | 93 | glfwSwapBuffers(window); 94 | glfwPollEvents(); 95 | } 96 | } 97 | 98 | end_time = get_time(); 99 | 100 | if (draw) 101 | glDeleteTextures(1, &texture); 102 | 103 | return end_time - start_time; 104 | } 105 | 106 | int main(int argc, const char *argv[]) { 107 | int draw = 0; 108 | struct vx_octree octree; 109 | 110 | // Read command line options. 111 | { 112 | int arg = 1; 113 | 114 | while (arg < (argc - 1)) { 115 | if (strcmp(argv[arg], "--draw") == 0) { 116 | draw = 1; 117 | arg++; 118 | } else { 119 | print_help(stderr, argv[0]); 120 | exit(1); 121 | } 122 | } 123 | 124 | if (arg == argc) { 125 | print_help(stderr, argv[0]); 126 | exit(1); 127 | } 128 | } 129 | 130 | { 131 | vx_error e = vx_poly_to_octree(argv[argc - 1], &octree, 8); 132 | 133 | if (e == VX_ERROR_CANT_OPEN_FILE) { 134 | fprintf(stderr, "%s: %s: Could not open file\n", argv[0], argv[argc - 1]); 135 | exit(1); 136 | } 137 | } 138 | 139 | GLFWwindow *window = NULL; 140 | 141 | if (draw) { 142 | glfwSetErrorCallback(glfw_error_callback); 143 | 144 | if (!glfwInit()) { 145 | fprintf(stderr, "Could not initialize GLFW\n"); 146 | exit(1); 147 | } 148 | 149 | window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "voxl benchmark", NULL, NULL); 150 | if (!window) { 151 | fprintf(stderr, "Could not open GLFW window\n"); 152 | glfwTerminate(); 153 | exit(1); 154 | } 155 | 156 | glfwMakeContextCurrent(window); 157 | 158 | int framebuffer_width, framebuffer_height; 159 | glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); 160 | glViewport(0, 0, framebuffer_width, framebuffer_height); 161 | 162 | glEnable(GL_TEXTURE_2D); 163 | 164 | glMatrixMode(GL_PROJECTION); 165 | glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f); 166 | glMatrixMode(GL_MODELVIEW); 167 | 168 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 169 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 170 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 171 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 172 | } 173 | 174 | { 175 | double time = benchmark(window, &octree, draw); 176 | printf("benchmark time: %f s (%f fps)\n", time, FRAME_COUNT / time); 177 | } 178 | 179 | if (draw) { 180 | glfwDestroyWindow(window); 181 | glfwTerminate(); 182 | } 183 | 184 | return 0; 185 | } 186 | -------------------------------------------------------------------------------- /samples/objviewer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "voxl.h" 9 | 10 | const float MOVEMENT_SPEED = 0.1f; 11 | 12 | const int DEFAULT_WINDOW_WIDTH = 800; 13 | const int DEFAULT_WINDOW_HEIGHT = 600; 14 | const int DEFAULT_DETAIL_LEVEL = 8; 15 | const float DEFAULT_FOV = 90; 16 | const int FORMATION_SIZE = 4; 17 | 18 | const int FORMATION_LINE = 1; 19 | const int FORMATION_PLANE = 2; 20 | const int FORMATION_PYRAMID = 3; 21 | 22 | void print_help(FILE *stream, const char *name) { 23 | fprintf(stream, "usage: %s [OPTION] obj_file\n", name); 24 | fprintf(stream, " --detail detail level (default %i)\n", DEFAULT_DETAIL_LEVEL); 25 | fprintf(stream, " --width window width (default %i)\n", DEFAULT_WINDOW_WIDTH); 26 | fprintf(stream, " --height window height (default %i)\n", DEFAULT_WINDOW_HEIGHT); 27 | fprintf(stream, " --fov camera field of view (default %.1f)\n", DEFAULT_FOV); 28 | fprintf(stream, " --formation line, plane, pyramid\n"); 29 | fprintf(stream, " -h, --help display this help and exit\n"); 30 | } 31 | 32 | void glfw_error_callback(int error, const char *description) { 33 | fputs(description, stderr); 34 | } 35 | 36 | int main(int argc, const char *argv[]) { 37 | int running = GL_TRUE; 38 | struct vx_camera camera; 39 | struct vx_octree octree; 40 | 41 | int detail_level = DEFAULT_DETAIL_LEVEL; 42 | int window_width = DEFAULT_WINDOW_WIDTH; 43 | int window_height = DEFAULT_WINDOW_HEIGHT; 44 | float tan_fov = tan((DEFAULT_FOV * 0.5f * M_PI) / 180.0f); 45 | int formation = 0; 46 | 47 | // Read command line options. 48 | { 49 | int arg = 1; 50 | 51 | while (arg < (argc - 1)) { 52 | if (strcmp(argv[arg], "--detail") == 0) { 53 | detail_level = atoi(argv[arg + 1]); 54 | if (detail_level < 1 || detail_level > 20) { 55 | fprintf(stderr, "Invalid detail level\n"); 56 | exit(1); 57 | } 58 | arg += 2; 59 | } else if (strcmp(argv[arg], "--width") == 0) { 60 | window_width = atoi(argv[arg + 1]); 61 | if (window_width < 1) { 62 | fprintf(stderr, "Invalid window width\n"); 63 | exit(1); 64 | } 65 | arg += 2; 66 | } else if (strcmp(argv[arg], "--height") == 0) { 67 | window_height = atoi(argv[arg + 1]); 68 | if (window_height < 1) { 69 | fprintf(stderr, "Invalid field of view\n"); 70 | exit(1); 71 | } 72 | arg += 2; 73 | } else if (strcmp(argv[arg], "--fov") == 0) { 74 | float fov = atof(argv[arg + 1]); 75 | if (fov <= 0 || fov >= 180) { 76 | fprintf(stderr, "Invalid field of view\n"); 77 | exit(1); 78 | } 79 | tan_fov = tan((fov * 0.5f * M_PI) / 180.0f); 80 | arg += 2; 81 | } else if (strcmp(argv[arg], "--formation") == 0) { 82 | ++arg; 83 | if (strcmp(argv[arg], "line") == 0) 84 | formation = FORMATION_LINE; 85 | else if (strcmp(argv[arg], "plane") == 0) 86 | formation = FORMATION_PLANE; 87 | else if (strcmp(argv[arg], "pyramid") == 0) 88 | formation = FORMATION_PYRAMID; 89 | else { 90 | fprintf(stderr, "Invalid formation\n"); 91 | exit(1); 92 | } 93 | ++arg; 94 | } else if (strcmp(argv[arg], "--help") == 0 || strcmp(argv[arg], "-h") == 0) { 95 | print_help(stdout, argv[0]); 96 | exit(0); 97 | ++arg; 98 | } else { 99 | print_help(stderr, argv[0]); 100 | exit(1); 101 | } 102 | } 103 | 104 | if (arg == argc) { 105 | print_help(stderr, argv[0]); 106 | exit(1); 107 | } 108 | } 109 | 110 | camera.resolution[0] = window_width; 111 | camera.resolution[1] = window_height; 112 | camera.direction[0] = 1.0f; 113 | camera.direction[1] = 0.0f; 114 | camera.direction[2] = 0.0f; 115 | camera.rotation_vector[0] = 0.0f; 116 | camera.rotation_vector[1] = 1.0f; 117 | camera.rotation_vector[2] = 0.0f; 118 | camera.rotation = M_PI * 0.75f; 119 | camera.tan_fov = tan_fov; 120 | 121 | vx_init_octree(&octree); 122 | 123 | int model; 124 | 125 | { 126 | struct vx_octree m; 127 | vx_error e = vx_poly_to_octree(argv[argc - 1], &m, detail_level); 128 | 129 | if (e == VX_ERROR_CANT_OPEN_FILE) { 130 | fprintf(stderr, "%s: %s: Could not open file\n", argv[0], argv[argc - 1]); 131 | exit(1); 132 | } 133 | 134 | model = vx_load_model(&octree, &m); 135 | } 136 | 137 | float step = 1.0f / (1 << FORMATION_SIZE); 138 | 139 | if (formation == FORMATION_LINE) { 140 | camera.position[0] = 0.05f; 141 | camera.position[1] = 0.50f; 142 | camera.position[2] = 0.6f; 143 | 144 | for (float x = 0.0f; x <= 1.0f; x += step) { 145 | vx_place_model(&octree, model, x, 0.5f, 0.5f, FORMATION_SIZE); 146 | } 147 | } else if (formation == FORMATION_PLANE) { 148 | camera.position[0] = 0.05f; 149 | camera.position[1] = 0.55f; 150 | camera.position[2] = 0.95f; 151 | 152 | for (float x = 0.0f; x <= 1.0f; x += step) { 153 | for (float z = 0.0f; z <= 1.0f; z += step) { 154 | vx_place_model(&octree, model, x, 0.5f, z, FORMATION_SIZE); 155 | } 156 | } 157 | } else if (formation == FORMATION_PYRAMID) { 158 | camera.position[0] = 0.05f; 159 | camera.position[1] = 0.2f; 160 | camera.position[2] = 0.95f; 161 | 162 | for (float y = 0.0f; y <= 1.0f; y += step) { 163 | for (float x = y; x <= (1.0f - y); x += step) { 164 | for (float z = y; z <= (1.0f - y); z += step) { 165 | vx_place_model(&octree, model, x, y, z, FORMATION_SIZE); 166 | } 167 | } 168 | } 169 | } else { 170 | camera.position[0] = 0.40f; 171 | camera.position[1] = 0.48f; 172 | camera.position[2] = 0.55f; 173 | 174 | vx_place_model(&octree, model, 0.5f, 0.5f, 0.5f, FORMATION_SIZE); 175 | } 176 | 177 | glfwSetErrorCallback(glfw_error_callback); 178 | 179 | if (!glfwInit()) { 180 | fprintf(stderr, "Could not initialize GLFW\n"); 181 | exit(1); 182 | } 183 | 184 | GLFWwindow *window = glfwCreateWindow(window_width, window_height, "voxl obj viewer", NULL, NULL); 185 | if (!window) { 186 | fprintf(stderr, "Could not open GLFW window\n"); 187 | glfwTerminate(); 188 | exit(1); 189 | } 190 | 191 | glfwMakeContextCurrent(window); 192 | 193 | int framebuffer_width, framebuffer_height; 194 | glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); 195 | glViewport(0, 0, framebuffer_width, framebuffer_height); 196 | 197 | glEnable(GL_TEXTURE_2D); 198 | 199 | glMatrixMode(GL_PROJECTION); 200 | glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f); 201 | glMatrixMode(GL_MODELVIEW); 202 | 203 | GLuint texture; 204 | char *buffer = (char *) malloc(window_width * window_height * sizeof(uint32_t)); 205 | 206 | glGenTextures(1, &texture); 207 | glBindTexture(GL_TEXTURE_2D, texture); 208 | 209 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 210 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 211 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 212 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 213 | 214 | double lastTime = glfwGetTime(); 215 | 216 | float totFramerate = 0; 217 | int frameCount = 0; 218 | 219 | while (running) { 220 | glClear(GL_COLOR_BUFFER_BIT); 221 | 222 | glLoadIdentity(); 223 | 224 | vx_render_frame(&octree, &camera, buffer); 225 | 226 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 227 | glColor3f(1, 1, 1); 228 | glBegin(GL_QUADS); 229 | glTexCoord2f(0.0, 0.0); 230 | glVertex2f(-1, -1); 231 | glTexCoord2f(0.0, 1.0); 232 | glVertex2f(-1, 1); 233 | glTexCoord2f(1.0, 1.0); 234 | glVertex2f(1, 1); 235 | glTexCoord2f(1.0, 0.0); 236 | glVertex2f(1, -1); 237 | glEnd(); 238 | 239 | glfwSwapBuffers(window); 240 | glfwPollEvents(); 241 | 242 | double time = glfwGetTime(); 243 | float dtime = time - lastTime; 244 | float framerate = 1 / dtime; 245 | if (frameCount % 30 == 0) 246 | printf("%f fps\n", framerate); 247 | lastTime = time; 248 | 249 | totFramerate += framerate; 250 | ++frameCount; 251 | 252 | running = !glfwGetKey(window, GLFW_KEY_ESCAPE) && !glfwWindowShouldClose(window); 253 | 254 | if (glfwGetKey(window, 'W')) { 255 | camera.position[0] += dtime * MOVEMENT_SPEED * sinf(camera.rotation); 256 | camera.position[2] += dtime * MOVEMENT_SPEED * cosf(camera.rotation); 257 | } else if (glfwGetKey(window, 'S')) { 258 | camera.position[0] -= dtime * MOVEMENT_SPEED * sinf(camera.rotation); 259 | camera.position[2] -= dtime * MOVEMENT_SPEED * cosf(camera.rotation); 260 | } 261 | 262 | if (glfwGetKey(window, 'A')) { 263 | camera.position[0] -= dtime * MOVEMENT_SPEED * sinf(camera.rotation + M_PI_2); 264 | camera.position[2] -= dtime * MOVEMENT_SPEED * cosf(camera.rotation + M_PI_2); 265 | } else if (glfwGetKey(window, 'D')) { 266 | camera.position[0] += dtime * MOVEMENT_SPEED * sinf(camera.rotation + M_PI_2); 267 | camera.position[2] += dtime * MOVEMENT_SPEED * cosf(camera.rotation + M_PI_2); 268 | } 269 | 270 | if (camera.position[0] < 0.0f) 271 | camera.position[0] = 0.0f; 272 | else if (camera.position[0] > 1.0f) 273 | camera.position[0] = 1.0f; 274 | 275 | if (camera.position[2] < 0.0f) 276 | camera.position[2] = 0.0f; 277 | else if (camera.position[2] > 1.0f) 278 | camera.position[2] = 1.0f; 279 | 280 | if (glfwGetKey(window, GLFW_KEY_SPACE)) { 281 | camera.position[1] += dtime * MOVEMENT_SPEED; 282 | } else if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL)) { 283 | camera.position[1] -= dtime * MOVEMENT_SPEED; 284 | } 285 | 286 | if (camera.position[1] < 0.0f) 287 | camera.position[1] = 0.0f; 288 | else if (camera.position[1] > 1.0f) 289 | camera.position[1] = 1.0f; 290 | 291 | double mouse_x, mouse_y; 292 | glfwGetCursorPos(window, &mouse_x, &mouse_y); 293 | 294 | camera.rotation = mouse_x / 90.0f; 295 | } 296 | 297 | printf("avgerage framerate: %f\n", totFramerate / frameCount); 298 | 299 | glfwDestroyWindow(window); 300 | glfwTerminate(); 301 | 302 | return 0; 303 | } 304 | -------------------------------------------------------------------------------- /src/internal.h: -------------------------------------------------------------------------------- 1 | #ifndef VOXL_INTERNAL_H 2 | #define VOXL_INTERNAL_H 3 | 4 | #include 5 | 6 | #include "voxl.h" 7 | 8 | // An octree node. 9 | struct vx_node { 10 | uint32_t child_pointers[8]; 11 | uint8_t valid_masks; 12 | uint8_t leaf_mask; 13 | 14 | uint16_t padding; 15 | }; 16 | 17 | // An octree leaf (a voxel). 18 | struct vx_voxel { 19 | int8_t normals[3]; 20 | 21 | uint8_t colors[4]; 22 | 23 | // TODO: This padding causes a lot of wasted memory. 24 | int8_t padding; 25 | uint32_t padding2[7]; 26 | }; 27 | 28 | struct vx_render_context { 29 | struct vx_octree *octree; 30 | struct vx_camera *camera; 31 | 32 | uint32_t *buffer; 33 | 34 | int current_row; 35 | }; 36 | 37 | struct vx_image { 38 | int width; 39 | int height; 40 | 41 | // 8 bit per channel RGBA pixel information. 42 | unsigned char *data; 43 | }; 44 | 45 | // Return the largest argument. 46 | float vx_max(float a, float b, float c); 47 | 48 | // Return the smallest argument. 49 | float vx_min(float a, float b, float c); 50 | 51 | // Render the row in the context. This function is thread safe. 52 | void *vx_render_row(void *render_context); 53 | 54 | // Append nodes and/or voxels to the octree and return pointer to it. 55 | uint32_t vx_append_to_octree(struct vx_octree *octree, void *data, int size); 56 | 57 | // Returns the specified voxel, and creates it if it doesn't exist. 58 | struct vx_voxel *vx_get_octree_voxel(struct vx_octree *octree, float x, float y, float z, int detail); 59 | 60 | // Get the parent of the node or voxel that is or would have been at the specified position. 61 | void vx_get_parent( 62 | struct vx_octree *octree, struct vx_node **parent, int *index, float x, float y, float z, int detail); 63 | 64 | // Load an image. Only supports PNG RGBA images with a bit depth of 8 at the moment. 65 | vx_error vx_load_image(char *file_name, struct vx_image *image); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/polyconv.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "internal.h" 7 | #include "voxl.h" 8 | #include "tribox.h" 9 | 10 | #include 11 | 12 | vx_error vx_load_image(char *file_name, struct vx_image *image) { 13 | png_byte header[8]; 14 | png_structp png_ptr; 15 | png_infop info_ptr; 16 | 17 | // Open file and check if it is a PNG file. 18 | FILE *fp = fopen(file_name, "rb"); 19 | if (!fp) 20 | return VX_ERROR_CANT_OPEN_FILE; 21 | if (fread(header, 1, 8, fp) != 8) 22 | return VX_ERROR_INVALID_IMAGE; 23 | if (png_sig_cmp(header, 0, 8)) 24 | return VX_ERROR_UNSUPPORTED_IMAGE; 25 | 26 | // Initialize PNG structures. 27 | png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 28 | if (!png_ptr) 29 | return VX_ERROR_INVALID_IMAGE; 30 | info_ptr = png_create_info_struct(png_ptr); 31 | if (!info_ptr) 32 | return VX_ERROR_INVALID_IMAGE; 33 | if (setjmp(png_jmpbuf(png_ptr))) 34 | return VX_ERROR_INVALID_IMAGE; 35 | png_init_io(png_ptr, fp); 36 | png_set_sig_bytes(png_ptr, 8); 37 | png_read_info(png_ptr, info_ptr); 38 | 39 | // Only accept RGBA images with bit depth of 8 for simplicity. 40 | if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_RGB_ALPHA || png_get_bit_depth(png_ptr, info_ptr) != 8) 41 | return VX_ERROR_INVALID_IMAGE; 42 | 43 | image->width = png_get_image_width(png_ptr, info_ptr); 44 | image->height = png_get_image_height(png_ptr, info_ptr); 45 | 46 | if (setjmp(png_jmpbuf(png_ptr))) 47 | return VX_ERROR_INVALID_IMAGE; 48 | 49 | // Read the image data. 50 | unsigned char **row_pointers = (unsigned char **) malloc(sizeof(png_bytep) * image->height); 51 | image->data = (unsigned char *) malloc(png_get_rowbytes(png_ptr, info_ptr) * image->height); 52 | for (int y = 0; y < image->height; y++) 53 | row_pointers[y] = image->data + y * png_get_rowbytes(png_ptr, info_ptr); 54 | png_read_image(png_ptr, (png_bytepp) row_pointers); 55 | 56 | free(row_pointers); 57 | 58 | fclose(fp); 59 | 60 | return VX_ERROR_OK; 61 | } 62 | 63 | struct vx_voxel *vx_get_octree_voxel(struct vx_octree *octree, float x, float y, float z, int detail) { 64 | uint32_t node = 0; 65 | 66 | float mid_point[3] = {0.5f, 0.5f, 0.5f}; 67 | float node_size = 0.5f; 68 | 69 | struct vx_voxel *voxel = NULL; 70 | 71 | for (int i = detail - 1; i >= 0; --i) { 72 | int octant = x > mid_point[0] ? 1 : 0; 73 | octant |= y > mid_point[1] ? 2 : 0; 74 | octant |= z > mid_point[2] ? 4 : 0; 75 | 76 | node_size *= 0.5f; 77 | 78 | mid_point[0] += x > mid_point[0] ? node_size : -node_size; 79 | mid_point[1] += y > mid_point[1] ? node_size : -node_size; 80 | mid_point[2] += z > mid_point[2] ? node_size : -node_size; 81 | 82 | if (i == 0) { 83 | // The final level contains a voxel. 84 | if (octree->root[node].valid_masks & (1 << octant) && octree->root[node].leaf_mask & (1 << octant)) { 85 | voxel = (struct vx_voxel *) &octree->root[octree->root[node].child_pointers[octant]]; 86 | } else { 87 | struct vx_voxel new_voxel; 88 | int pointer = vx_append_to_octree(octree, &new_voxel, 1); 89 | octree->root[node].valid_masks |= (1 << octant); 90 | octree->root[node].leaf_mask |= (1 << octant); 91 | octree->root[node].child_pointers[octant] = pointer; 92 | voxel = (struct vx_voxel *) &octree->root[pointer]; 93 | } 94 | } else { 95 | // This level contains a node. 96 | if (octree->root[node].valid_masks & (1 << octant)) { 97 | node = octree->root[node].child_pointers[octant]; 98 | } else { 99 | struct vx_node new_node; 100 | new_node.leaf_mask = 0; 101 | new_node.valid_masks = 0; 102 | 103 | int pointer = vx_append_to_octree(octree, &new_node, 1); 104 | octree->root[node].child_pointers[octant] = pointer; 105 | octree->root[node].valid_masks |= (1 << octant); 106 | node = pointer; 107 | } 108 | } 109 | } 110 | 111 | return voxel; 112 | } 113 | 114 | vx_error vx_poly_to_octree(const char *file_name, struct vx_octree *octree, int detail) { 115 | float offset[3]; 116 | float scale; 117 | 118 | int vertex_count = 0; 119 | int face_count = 0; 120 | int uv_count = 0; 121 | int normal_count = 0; 122 | 123 | float **vertices; 124 | float **uvs; 125 | int **faces; 126 | float **normals; 127 | 128 | FILE *file = fopen(file_name, "r"); 129 | 130 | if (file == NULL) 131 | return VX_ERROR_CANT_OPEN_FILE; 132 | 133 | // Start with checking the bounds of the model and size needed for arrays. 134 | { 135 | float min[3] = {FLT_MAX, FLT_MAX, FLT_MAX}; 136 | float max[3] = {FLT_MIN, FLT_MIN, FLT_MIN}; 137 | 138 | while (!feof(file)) { 139 | char line[1024]; 140 | if (!fgets(line, 1024, file) && ferror(file)) 141 | return VX_ERROR_CANT_OPEN_FILE; 142 | 143 | if (!strncmp(line, "v ", 2)) { 144 | float x, y, z; 145 | 146 | sscanf(line, "v %f %f %f\n", &x, &y, &z); 147 | 148 | min[0] = x < min[0] ? x : min[0]; 149 | min[1] = y < min[1] ? y : min[1]; 150 | min[2] = z < min[2] ? z : min[2]; 151 | max[0] = x > max[0] ? x : max[0]; 152 | max[1] = y > max[1] ? y : max[1]; 153 | max[2] = z > max[2] ? z : max[2]; 154 | 155 | ++vertex_count; 156 | } else if (!strncmp(line, "vt ", 3)) { 157 | ++uv_count; 158 | } else if (!strncmp(line, "vn ", 3)) { 159 | ++normal_count; 160 | } else if (!strncmp(line, "f ", 2)) { 161 | ++face_count; 162 | } 163 | } 164 | 165 | offset[0] = -min[0]; 166 | offset[1] = -min[1]; 167 | offset[2] = -min[2]; 168 | 169 | // Scale factor so that the model is kept within the octree. 170 | scale = 1.0f / vx_max(max[0] - min[0], max[1] - min[1], max[2] - min[2]); 171 | } 172 | 173 | // TODO: Free these allocations when done. 174 | vertices = (float **) malloc(vertex_count * sizeof(float *)); 175 | uvs = (float **) malloc(uv_count * sizeof(float *)); 176 | faces = (int **) malloc(face_count * sizeof(int *)); 177 | normals = (float **) malloc(normal_count * sizeof(float *)); 178 | 179 | vertex_count = 0; 180 | face_count = 0; 181 | uv_count = 0; 182 | normal_count = 0; 183 | 184 | rewind(file); 185 | 186 | // Load the actual vertices, texture normals and faces from file. 187 | while (!feof(file)) { 188 | char line[1024]; 189 | if (!fgets(line, 1024, file) && ferror(file)) 190 | return VX_ERROR_CANT_OPEN_FILE; 191 | 192 | if (!strncmp(line, "v ", 2)) { 193 | float *vertex = (float *) malloc(3 * sizeof(float)); 194 | sscanf(line, "v %f %f %f\n", vertex, vertex + 1, vertex + 2); 195 | 196 | vertex[0] = (vertex[0] + offset[0]) * scale; 197 | vertex[1] = (vertex[1] + offset[1]) * scale; 198 | vertex[2] = (vertex[2] + offset[2]) * scale; 199 | 200 | vertices[vertex_count++] = vertex; 201 | } else if (!strncmp(line, "vt ", 3)) { 202 | float *uv = (float *) malloc(2 * sizeof(float)); 203 | 204 | sscanf(line, "vt %f %f\n", uv, uv + 1); 205 | 206 | uvs[uv_count++] = uv; 207 | } else if (!strncmp(line, "vn ", 3)) { 208 | float *normal = (float *) malloc(3 * sizeof(float)); 209 | 210 | sscanf(line, "vn %f %f %f\n", normal, normal + 1, normal + 2); 211 | 212 | normals[normal_count++] = normal; 213 | } else if (!strncmp(line, "f ", 2)) { 214 | int *face = (int *) malloc(9 * sizeof(int)); 215 | 216 | sscanf(line, 217 | "f %i/%i/%i %i/%i/%i %i/%i/%i\n", 218 | face, face + 3, face + 6, face + 1, face + 4, face + 7, face + 2, face + 5, face + 8); 219 | 220 | for (int i = 0; i < 9; ++i) 221 | face[i] -= 1; 222 | 223 | faces[face_count++] = face; 224 | } 225 | } 226 | 227 | //if (vertex_count == 0 || face_count == 0 || uv_count == 0 || normal_count == 0) 228 | // return VX_ERROR_POLYGON_FILE_ERROR; 229 | 230 | // Build the octree from the faces. 231 | octree->allocated_size = 4096; 232 | octree->root = (struct vx_node *) malloc(octree->allocated_size * sizeof(struct vx_node)); 233 | octree->size = 1; 234 | 235 | octree->root->leaf_mask = 0; 236 | octree->root->valid_masks = 0; 237 | 238 | const float step = 1.0f / (1 << detail); 239 | 240 | // TODO: Optimize rasterization process, this is a pretty slow way of doing it. 241 | for (int i = 0; i < face_count; ++i) { 242 | int *face = faces[i]; 243 | 244 | float *normal = normals[face[6]]; 245 | float *triangle_vertices[3] = {vertices[face[0]], vertices[face[1]], vertices[face[2]]}; 246 | 247 | // The bounding box of the polygon. 248 | float min[3], max[3]; 249 | min[0] = vx_min(triangle_vertices[0][0], triangle_vertices[1][0], triangle_vertices[2][0]) - step; 250 | min[1] = vx_min(triangle_vertices[0][1], triangle_vertices[1][1], triangle_vertices[2][1]) - step; 251 | min[2] = vx_min(triangle_vertices[0][2], triangle_vertices[1][2], triangle_vertices[2][2]) - step; 252 | max[0] = vx_max(triangle_vertices[0][0], triangle_vertices[1][0], triangle_vertices[2][0]) + step; 253 | max[1] = vx_max(triangle_vertices[0][1], triangle_vertices[1][1], triangle_vertices[2][1]) + step; 254 | max[2] = vx_max(triangle_vertices[0][2], triangle_vertices[1][2], triangle_vertices[2][2]) + step; 255 | 256 | for (float x = min[0]; x <= max[0]; x += step) { 257 | for (float y = min[1]; y <= max[1]; y += step) { 258 | for (float z = min[2]; z <= max[2]; z += step) { 259 | float box_center[3] = {x, y, z}; 260 | float box_half_size[3] = {0.5f * step, 0.5f * step, 0.5f * step}; 261 | 262 | if (triBoxOverlap(box_center, box_half_size, triangle_vertices)) { 263 | struct vx_voxel *voxel = vx_get_octree_voxel(octree, x, y, z, detail); 264 | 265 | voxel->colors[0] = 255; 266 | voxel->colors[1] = 255; 267 | voxel->colors[2] = 255; 268 | 269 | voxel->normals[0] = normal[0] * 128; 270 | voxel->normals[1] = normal[1] * 128; 271 | voxel->normals[2] = normal[2] * 128; 272 | } 273 | } 274 | } 275 | } 276 | } 277 | 278 | return VX_ERROR_OK; 279 | } 280 | -------------------------------------------------------------------------------- /src/tribox.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************/ 2 | /* AABB-triangle overlap test code */ 3 | /* by Tomas Akenine-Möller */ 4 | /* Function: int triBoxOverlap(float boxcenter[3], */ 5 | /* float boxhalfsize[3],float triverts[3][3]); */ 6 | /* History: */ 7 | /* 2001-03-05: released the code in its first version */ 8 | /* 2001-06-18: changed the order of the tests, faster */ 9 | /* */ 10 | /* Acknowledgement: Many thanks to Pierre Terdiman for */ 11 | /* suggestions and discussions on how to optimize code. */ 12 | /* Thanks to David Hunt for finding a ">="-bug! */ 13 | /********************************************************/ 14 | #include 15 | #include 16 | 17 | #include "tribox.h" 18 | 19 | #define X 0 20 | #define Y 1 21 | #define Z 2 22 | 23 | #define CROSS(dest,v1,v2) \ 24 | dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \ 25 | dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \ 26 | dest[2]=v1[0]*v2[1]-v1[1]*v2[0]; 27 | 28 | #define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]) 29 | 30 | #define SUB(dest,v1,v2) \ 31 | dest[0]=v1[0]-v2[0]; \ 32 | dest[1]=v1[1]-v2[1]; \ 33 | dest[2]=v1[2]-v2[2]; 34 | 35 | #define FINDMINMAX(x0,x1,x2,min,max) \ 36 | min = max = x0; \ 37 | if(x1max) max=x1;\ 39 | if(x2max) max=x2; 41 | 42 | int planeBoxOverlap(float normal[3],float d, float maxbox[3]) 43 | { 44 | int q; 45 | float vmin[3],vmax[3]; 46 | for(q=X;q<=Z;q++) 47 | { 48 | if(normal[q]>0.0f) 49 | { 50 | vmin[q]=-maxbox[q]; 51 | vmax[q]=maxbox[q]; 52 | } 53 | else 54 | { 55 | vmin[q]=maxbox[q]; 56 | vmax[q]=-maxbox[q]; 57 | } 58 | } 59 | if(DOT(normal,vmin)+d>0.0f) return 0; 60 | if(DOT(normal,vmax)+d>=0.0f) return 1; 61 | 62 | return 0; 63 | } 64 | 65 | 66 | /*======================== X-tests ========================*/ 67 | #define AXISTEST_X01(a, b, fa, fb) \ 68 | p0 = a*v0[Y] - b*v0[Z]; \ 69 | p2 = a*v2[Y] - b*v2[Z]; \ 70 | if(p0rad || max<-rad) return 0; 73 | 74 | #define AXISTEST_X2(a, b, fa, fb) \ 75 | p0 = a*v0[Y] - b*v0[Z]; \ 76 | p1 = a*v1[Y] - b*v1[Z]; \ 77 | if(p0rad || max<-rad) return 0; 80 | 81 | /*======================== Y-tests ========================*/ 82 | #define AXISTEST_Y02(a, b, fa, fb) \ 83 | p0 = -a*v0[X] + b*v0[Z]; \ 84 | p2 = -a*v2[X] + b*v2[Z]; \ 85 | if(p0rad || max<-rad) return 0; 88 | 89 | #define AXISTEST_Y1(a, b, fa, fb) \ 90 | p0 = -a*v0[X] + b*v0[Z]; \ 91 | p1 = -a*v1[X] + b*v1[Z]; \ 92 | if(p0rad || max<-rad) return 0; 95 | 96 | /*======================== Z-tests ========================*/ 97 | 98 | #define AXISTEST_Z12(a, b, fa, fb) \ 99 | p1 = a*v1[X] - b*v1[Y]; \ 100 | p2 = a*v2[X] - b*v2[Y]; \ 101 | if(p2rad || max<-rad) return 0; 104 | 105 | #define AXISTEST_Z0(a, b, fa, fb) \ 106 | p0 = a*v0[X] - b*v0[Y]; \ 107 | p1 = a*v1[X] - b*v1[Y]; \ 108 | if(p0rad || max<-rad) return 0; 111 | 112 | int triBoxOverlap(float boxcenter[3],float boxhalfsize[3],float *triverts[3]) 113 | { 114 | 115 | /* use separating axis theorem to test overlap between triangle and box */ 116 | /* need to test for overlap in these directions: */ 117 | /* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */ 118 | /* we do not even need to test these) */ 119 | /* 2) normal of the triangle */ 120 | /* 3) crossproduct(edge from tri, {x,y,z}-directin) */ 121 | /* this gives 3x3=9 more tests */ 122 | float v0[3],v1[3],v2[3]; 123 | float min,max,d,p0,p1,p2,rad,fex,fey,fez; 124 | float normal[3],e0[3],e1[3],e2[3]; 125 | 126 | /* This is the fastest branch on Sun */ 127 | /* move everything so that the boxcenter is in (0,0,0) */ 128 | SUB(v0,triverts[0],boxcenter); 129 | SUB(v1,triverts[1],boxcenter); 130 | SUB(v2,triverts[2],boxcenter); 131 | 132 | /* compute triangle edges */ 133 | SUB(e0,v1,v0); /* tri edge 0 */ 134 | SUB(e1,v2,v1); /* tri edge 1 */ 135 | SUB(e2,v0,v2); /* tri edge 2 */ 136 | 137 | /* Bullet 3: */ 138 | /* test the 9 tests first (this was faster) */ 139 | fex = fabs(e0[X]); 140 | fey = fabs(e0[Y]); 141 | fez = fabs(e0[Z]); 142 | AXISTEST_X01(e0[Z], e0[Y], fez, fey); 143 | AXISTEST_Y02(e0[Z], e0[X], fez, fex); 144 | AXISTEST_Z12(e0[Y], e0[X], fey, fex); 145 | 146 | fex = fabs(e1[X]); 147 | fey = fabs(e1[Y]); 148 | fez = fabs(e1[Z]); 149 | AXISTEST_X01(e1[Z], e1[Y], fez, fey); 150 | AXISTEST_Y02(e1[Z], e1[X], fez, fex); 151 | AXISTEST_Z0(e1[Y], e1[X], fey, fex); 152 | 153 | fex = fabs(e2[X]); 154 | fey = fabs(e2[Y]); 155 | fez = fabs(e2[Z]); 156 | AXISTEST_X2(e2[Z], e2[Y], fez, fey); 157 | AXISTEST_Y1(e2[Z], e2[X], fez, fex); 158 | AXISTEST_Z12(e2[Y], e2[X], fey, fex); 159 | 160 | /* Bullet 1: */ 161 | /* first test overlap in the {x,y,z}-directions */ 162 | /* find min, max of the triangle each direction, and test for overlap in */ 163 | /* that direction -- this is equivalent to testing a minimal AABB around */ 164 | /* the triangle against the AABB */ 165 | 166 | /* test in X-direction */ 167 | FINDMINMAX(v0[X],v1[X],v2[X],min,max); 168 | if(min>boxhalfsize[X] || max<-boxhalfsize[X]) return 0; 169 | 170 | /* test in Y-direction */ 171 | FINDMINMAX(v0[Y],v1[Y],v2[Y],min,max); 172 | if(min>boxhalfsize[Y] || max<-boxhalfsize[Y]) return 0; 173 | 174 | /* test in Z-direction */ 175 | FINDMINMAX(v0[Z],v1[Z],v2[Z],min,max); 176 | if(min>boxhalfsize[Z] || max<-boxhalfsize[Z]) return 0; 177 | 178 | /* Bullet 2: */ 179 | /* test if the box intersects the plane of the triangle */ 180 | /* compute plane equation of triangle: normal*x+d=0 */ 181 | CROSS(normal,e0,e1); 182 | d=-DOT(normal,v0); /* plane eq: normal.x+d=0 */ 183 | if(!planeBoxOverlap(normal,d,boxhalfsize)) return 0; 184 | 185 | return 1; /* box and triangle overlaps */ 186 | } 187 | -------------------------------------------------------------------------------- /src/tribox.h: -------------------------------------------------------------------------------- 1 | #ifndef TRIBOX_H 2 | #define TRIBOX_H 3 | 4 | int planeBoxOverlap(float normal[3], float d, float maxbox[3]); 5 | int triBoxOverlap(float boxcenter[3], float boxhalfsize[3], float *triverts[3]); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/voxl.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "internal.h" 11 | #include "voxl.h" 12 | 13 | HWY_BEFORE_NAMESPACE(); 14 | 15 | hwy::HWY_NAMESPACE::ScalableTag df; 16 | using VecF = hwy::HWY_NAMESPACE::Vec; 17 | using MaskF = hwy::HWY_NAMESPACE::Mask; 18 | 19 | // How many threads to use when rendering a frame. 20 | const int RENDERING_THREADS = 8; 21 | 22 | // The maximum level of detail level for any octree. 23 | const int MAX_DETAIL_LEVEL = 20; 24 | 25 | float vx_max(float a, float b, float c) { 26 | return a > b ? (a > c ? a : c) : (b > c ? b : c); 27 | } 28 | 29 | float vx_min(float a, float b, float c) { 30 | return a < b ? (a < c ? a : c) : (b < c ? b : c); 31 | } 32 | 33 | void vx_init_octree(struct vx_octree *octree) { 34 | octree->allocated_size = 128; 35 | octree->root = (vx_node *) malloc(octree->allocated_size * sizeof(struct vx_node)); 36 | octree->size = 1; 37 | octree->model_count = 0; 38 | octree->root->leaf_mask = 0; 39 | octree->root->valid_masks = 0; 40 | } 41 | 42 | uint32_t vx_ray_cast(struct vx_octree *octree, VecF ray_origin, VecF ray_direction) { 43 | VecF box = Zero(df); 44 | MaskF ray_sign = ray_direction >= Zero(df); 45 | VecF half_size = Set(df, 0.5f); 46 | MaskF moctant = ray_origin > (box + half_size); 47 | 48 | // The potential X, Y and Z-planes that the ray will collide with next. 49 | VecF planes = IfThenElseZero(moctant, half_size) + IfThenElseZero(ray_sign, half_size) + box; 50 | 51 | // Stacks to keep track of nodes during traversal of the octree. 52 | struct vx_node *node_stack[MAX_DETAIL_LEVEL]; 53 | uint64_t octant_stack[MAX_DETAIL_LEVEL]; 54 | int stack_pos = 0; 55 | 56 | node_stack[0] = octree->root; 57 | 58 | // What octant are we in? 59 | StoreMaskBits(df, moctant, (uint8_t *) octant_stack); 60 | 61 | MaskF mask[3]; 62 | { 63 | uint64_t tmp; 64 | mask[0] = LoadMaskBits(df, (uint8_t *) &(tmp = 1)); 65 | mask[1] = LoadMaskBits(df, (uint8_t *) &(tmp = 2)); 66 | mask[2] = LoadMaskBits(df, (uint8_t *) &(tmp = 4)); 67 | } 68 | 69 | while (stack_pos >= 0) { 70 | // Have we reached a child? 71 | if (node_stack[stack_pos]->valid_masks & (1 << octant_stack[stack_pos])) { 72 | // Is this child a voxel? 73 | if (node_stack[stack_pos]->leaf_mask & (1 << octant_stack[stack_pos])) { 74 | struct vx_voxel *voxel = (struct vx_voxel *) &octree->root[node_stack[stack_pos]->child_pointers[octant_stack[stack_pos]]]; 75 | 76 | // The direction of the directional light source. 77 | float light[3] = {-0.039030f, -0.866792f, 0.497139f}; 78 | 79 | float brightness = acosf( 80 | (voxel->normals[0] / 128.0f) * light[0] + 81 | (voxel->normals[1] / 128.0f) * light[1] + 82 | (voxel->normals[2] / 128.0f) * light[2]) / M_PI; 83 | 84 | return ((unsigned int) (voxel->colors[1] * brightness) << 16) | 85 | ((unsigned int) (voxel->colors[1] * brightness) << 8) | 86 | (unsigned int) (voxel->colors[0] * brightness); 87 | } 88 | 89 | // This was a node, push it to the stack. 90 | node_stack[stack_pos + 1] = &octree->root[node_stack[stack_pos]->child_pointers[octant_stack[stack_pos]]]; 91 | 92 | box = box + IfThenElseZero(moctant, half_size); 93 | 94 | half_size = half_size * Set(df, 0.5f); 95 | 96 | ++stack_pos; 97 | 98 | moctant = ray_origin > box + half_size; 99 | StoreMaskBits(df, moctant, (uint8_t *) &octant_stack[stack_pos]); 100 | 101 | planes = IfThenElseZero(moctant, half_size) + IfThenElseZero(ray_sign, half_size) + box; 102 | 103 | continue; 104 | } 105 | 106 | // Calculate intersection t values. 107 | float t[4]; 108 | Store((planes - ray_origin) / ray_direction, df, t); 109 | 110 | // What plane will the ray intersect with first? 111 | const int intersection_plane = t[0] < t[1] ? (t[0] < t[2] ? 0 : 2) : (t[1] < t[2] ? 1 : 2); 112 | uint64_t dir_sign = 0; 113 | StoreMaskBits(df, ray_sign, (uint8_t *) &dir_sign); 114 | dir_sign = (dir_sign >> intersection_plane) & 1; 115 | 116 | ray_origin = ray_origin + Set(df, t[intersection_plane]) * ray_direction; 117 | 118 | // Check if the ray left the node. 119 | if ((octant_stack[stack_pos] & (1 << intersection_plane)) >> intersection_plane == dir_sign) { 120 | // Pop all nodes that the ray left. 121 | do { 122 | --stack_pos; 123 | 124 | half_size = half_size * Set(df, 2.0f); 125 | 126 | moctant = LoadMaskBits(df, (uint8_t *) &octant_stack[stack_pos]); 127 | 128 | box = box - IfThenElseZero(moctant, half_size); 129 | } while (stack_pos >= 0 && (((octant_stack[stack_pos] >> intersection_plane) & 1) == dir_sign)); 130 | 131 | // Update octant. 132 | octant_stack[stack_pos] &= ~(1 << intersection_plane); 133 | octant_stack[stack_pos] |= dir_sign << intersection_plane; 134 | 135 | moctant = LoadMaskBits(df, (uint8_t *) &octant_stack[stack_pos]); 136 | 137 | planes = IfThenElseZero(moctant, half_size) + IfThenElseZero(ray_sign, half_size) + box; 138 | 139 | continue; 140 | } 141 | 142 | // Update octant. 143 | octant_stack[stack_pos] &= ~(1 << intersection_plane); 144 | octant_stack[stack_pos] |= dir_sign << intersection_plane; 145 | 146 | moctant = LoadMaskBits(df, (uint8_t *) &octant_stack[stack_pos]); 147 | 148 | // Move the plane that the ray hit. 149 | planes = dir_sign ? planes + IfThenElseZero(mask[intersection_plane], half_size) 150 | : planes - IfThenElseZero(mask[intersection_plane], half_size); 151 | } 152 | 153 | // The ray didn't hit any voxel. 154 | return 0xFF000000; 155 | } 156 | 157 | void vx_render_frame(struct vx_octree *octree, struct vx_camera *camera, char *buffer) { 158 | pthread_t threads[RENDERING_THREADS]; 159 | struct vx_render_context context; 160 | 161 | context.octree = octree; 162 | context.camera = camera; 163 | context.buffer = (uint32_t *) buffer; 164 | context.current_row = 0; 165 | 166 | // Start rendering threads. 167 | for (int t = 0; t < RENDERING_THREADS; ++t) 168 | pthread_create(&threads[t], NULL, vx_render_row, &context); 169 | 170 | // Wait for all threads to complete. 171 | for (int t = 0; t < RENDERING_THREADS; ++t) 172 | pthread_join(threads[t], NULL); 173 | } 174 | 175 | void *vx_render_row(void *render_context) { 176 | struct vx_render_context *rc = (struct vx_render_context *) render_context; 177 | 178 | float cosa = cosf(rc->camera->rotation); 179 | float sina = sinf(rc->camera->rotation); 180 | 181 | float *rv = rc->camera->rotation_vector; 182 | 183 | // x and y factors for ray vector to get correct field of view. 184 | float x_factor = rc->camera->tan_fov / (rc->camera->resolution[0] / 2); 185 | float y_factor = (rc->camera->tan_fov / (rc->camera->resolution[1] / 2)) * 186 | ((float) rc->camera->resolution[1] / rc->camera->resolution[0]); 187 | 188 | float ray_origin[4]; 189 | ray_origin[0] = rc->camera->position[0]; // - sina * 0.4f; 190 | ray_origin[1] = rc->camera->position[1]; 191 | ray_origin[2] = rc->camera->position[2]; // - cosa * 0.4f; 192 | ray_origin[3] = 0; 193 | 194 | for (int32_t y = (rc->current_row)++; y < rc->camera->resolution[1]; y = (rc->current_row)++) { 195 | for (int x = 0; x < rc->camera->resolution[0]; ++x) { 196 | float ray_direction[4]; 197 | 198 | { 199 | float rx = ((float) (x - rc->camera->resolution[0] / 2)) * x_factor; 200 | float ry = ((float) (y - rc->camera->resolution[1] / 2)) * y_factor; 201 | float rz = 1.0f; // This gives a rectilinear perspective. 202 | // float rz = 2.0f * cosf(0.5f * sqrtf(rx * rx + ry * ry)); // This gives a fisheye perspective. 203 | 204 | // Rotate the ray around the rotation vector. 205 | ray_direction[0] = rv[0] * (rv[0] * rx + rv[1] * ry + rv[2] * rz) * (1 - cosa) + rx * cosa + 206 | (-rv[2] * ry + rv[1] * rz) * sina; 207 | ray_direction[1] = rv[1] * (rv[0] * rx + rv[1] * ry + rv[2] * rz) * (1 - cosa) + ry * cosa + 208 | (rv[2] * rx + rv[0] * rz) * sina; 209 | ray_direction[2] = rv[2] * (rv[0] * rx + rv[1] * ry + rv[2] * rz) * (1 - cosa) + rz * cosa + 210 | (-rv[1] * rx + rv[0] * ry) * sina; 211 | } 212 | 213 | uint32_t color = vx_ray_cast(rc->octree, Load(df, ray_origin), Load(df, ray_direction)); 214 | 215 | rc->buffer[y * rc->camera->resolution[0] + x] = color; 216 | } 217 | } 218 | 219 | return NULL; 220 | } 221 | 222 | uint32_t vx_append_to_octree(struct vx_octree *octree, void *data, int size) { 223 | // Check if we need to allocate more memory to fit the new data. 224 | if ((octree->size + size) > octree->allocated_size) { 225 | // Allocate at least double the memory. (Is this really desirable when the octree grows very large?) 226 | uint32_t new_allocated_size = octree->size + size; 227 | if (2 * octree->allocated_size > new_allocated_size) 228 | new_allocated_size = 2 * octree->allocated_size; 229 | octree->root = (vx_node *) realloc(octree->root, new_allocated_size * sizeof(struct vx_node)); 230 | octree->allocated_size = new_allocated_size; 231 | } 232 | 233 | uint32_t pointer = octree->size; 234 | 235 | memcpy(&octree->root[octree->size], data, sizeof(struct vx_node) * size); 236 | octree->size += size; 237 | 238 | return pointer; 239 | } 240 | 241 | void vx_get_parent( 242 | struct vx_octree *octree, struct vx_node **parent, int *index, float x, float y, float z, int detail) { 243 | uint32_t node = 0; 244 | 245 | float mid_point[3] = {0.5f, 0.5f, 0.5f}; 246 | float node_size = 0.5f; 247 | 248 | for (int i = detail - 1; i >= 0; --i) { 249 | int octant = x > mid_point[0] ? 1 : 0; 250 | octant |= y > mid_point[1] ? 2 : 0; 251 | octant |= z > mid_point[2] ? 4 : 0; 252 | 253 | node_size *= 0.5f; 254 | 255 | mid_point[0] += x > mid_point[0] ? node_size : -node_size; 256 | mid_point[1] += y > mid_point[1] ? node_size : -node_size; 257 | mid_point[2] += z > mid_point[2] ? node_size : -node_size; 258 | 259 | if (i == 0) { 260 | // We found the parent of the node or voxel we wanted. 261 | *parent = &octree->root[node]; 262 | *index = octant; 263 | } else { 264 | // This level contains a node. 265 | if (octree->root[node].valid_masks & (1 << octant)) { 266 | node = octree->root[node].child_pointers[octant]; 267 | } else { 268 | struct vx_node new_node; 269 | new_node.leaf_mask = 0; 270 | new_node.valid_masks = 0; 271 | 272 | int pointer = vx_append_to_octree(octree, &new_node, 1); 273 | octree->root[node].child_pointers[octant] = pointer; 274 | octree->root[node].valid_masks |= (1 << octant); 275 | node = pointer; 276 | } 277 | } 278 | } 279 | } 280 | 281 | // TODO: Fix this temporary solution (eg. by switching to relative pointers). 282 | void add_pointer(struct vx_octree *octree, uint32_t node, int amount) { 283 | for (int i = 0; i < 8; ++i) { 284 | if (octree->root[node].valid_masks & (1 << i)) { 285 | octree->root[node].child_pointers[i] += amount; 286 | if ((octree->root[node].leaf_mask & (1 << i)) == 0) 287 | add_pointer(octree, octree->root[node].child_pointers[i] - amount, amount); 288 | } 289 | } 290 | } 291 | 292 | int vx_load_model(struct vx_octree *octree, struct vx_octree *model) { 293 | if (octree->model_count == MODEL_LIMIT) 294 | return -1; 295 | 296 | int model_index = octree->model_count; 297 | 298 | // TODO: Fix better solution... This destroys the model octree. 299 | add_pointer(model, 0, octree->size); 300 | 301 | octree->models[octree->model_count++] = vx_append_to_octree(octree, model->root, model->size); 302 | 303 | return model_index; 304 | } 305 | 306 | vx_error vx_place_model(struct vx_octree *octree, int model, float x, float y, float z, int detail) { 307 | struct vx_node *parent; 308 | int index; 309 | 310 | if (model < 0 || model >= octree->model_count) 311 | return VX_ERROR_INVALID_MODEL; 312 | 313 | vx_get_parent(octree, &parent, &index, x, y, z, detail); 314 | 315 | parent->child_pointers[index] = octree->models[model]; 316 | parent->valid_masks |= (1 << index); 317 | 318 | return VX_ERROR_OK; 319 | } 320 | 321 | HWY_AFTER_NAMESPACE(); --------------------------------------------------------------------------------