├── .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 |
4 |
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 | 
8 | 
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();
--------------------------------------------------------------------------------