├── .gitignore ├── README.md ├── UNLICENSE ├── assets ├── Roboto-Bold.ttf ├── cube.obj ├── simple.glsl ├── teapot.obj ├── test.scene └── white.glsl ├── dep ├── include │ ├── GLFW │ │ ├── glfw3.h │ │ └── glfw3native.h │ ├── fontstash.h │ ├── nanovg.h │ ├── nanovg_gl.h │ ├── nanovg_gl_utils.h │ ├── stb_image.h │ └── stb_truetype.h ├── licenses │ ├── GLFW3.txt │ ├── NANOVG.txt │ └── STB.txt └── src │ └── nanovg.c ├── msvc ├── .gitignore ├── editor │ ├── editor.vcxproj │ ├── editor.vcxproj.filters │ ├── editor.vcxproj.user │ └── packages.config ├── engine │ ├── engine.vcxproj │ ├── engine.vcxproj.filters │ └── packages.config ├── lib-Win32 │ ├── glfw3.dll │ └── glfw3dll.lib ├── lib-x64 │ ├── glfw3.dll │ └── glfw3dll.lib ├── packages │ └── repositories.config └── vs2013.sln └── src ├── editor ├── editor.cpp ├── editor.h ├── gui.cpp ├── gui.h ├── main.cpp ├── scene.cpp ├── scene.h ├── widgets.cpp ├── widgets.h ├── window.cpp ├── window.h ├── xplat.cpp └── xplat.h └── engine ├── asset.cpp ├── asset.h ├── font.cpp ├── font.h ├── geometry.cpp ├── geometry.h ├── gl.cpp ├── gl.h ├── json.cpp ├── json.h ├── linalg.h ├── load.cpp ├── load.h ├── pack.h ├── transform.cpp ├── transform.h ├── utf8.cpp └── utf8.h /.gitignore: -------------------------------------------------------------------------------- 1 | /bin-* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | This project consists of some very early explorations into a 3D scene editor, of the sort that might be used to author video game levels, etc. 5 | 6 | Our example program shows a view into a 3D scene, with the ability to select objects either by clicking or by picking them from an object list, and with the ability to modify some properties either via a gizmo or through a property editor. 7 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /assets/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgorsten/editor/7749421e7046255fe9861509658a92385deb57b6/assets/Roboto-Bold.ttf -------------------------------------------------------------------------------- /assets/cube.obj: -------------------------------------------------------------------------------- 1 | v -1 -1 -1 2 | v -1 -1 1 3 | v -1 1 -1 4 | v -1 1 1 5 | v 1 -1 -1 6 | v 1 -1 1 7 | v 1 1 -1 8 | v 1 1 1 9 | 10 | vt 0 0 11 | vt 1 0 12 | vt 1 1 13 | vt 0 1 14 | 15 | vn -1 0 0 16 | vn 1 0 0 17 | vn 0 -1 0 18 | vn 0 1 0 19 | vn 0 0 -1 20 | vn 0 0 1 21 | 22 | f 1/1/1 2/2/1 4/3/1 3/4/1 23 | f 5/1/2 7/2/2 8/3/2 6/4/2 24 | f 1/1/3 5/2/3 6/3/3 2/4/3 25 | f 3/1/4 4/2/4 8/3/4 7/4/4 26 | f 1/1/5 3/2/5 7/3/5 5/4/5 27 | f 2/1/6 6/2/6 8/3/6 4/4/6 28 | -------------------------------------------------------------------------------- /assets/simple.glsl: -------------------------------------------------------------------------------- 1 | layout(binding = 0) uniform PerObject 2 | { 3 | mat4 u_model; 4 | mat4 u_modelIT; 5 | vec3 u_emissive; 6 | vec3 u_diffuse; 7 | }; 8 | 9 | #ifdef VERT_SHADER 10 | layout(location = 0) in vec3 v_position; 11 | layout(location = 1) in vec3 v_normal; 12 | out vec3 position; 13 | out vec3 normal; 14 | void main() 15 | { 16 | vec4 worldPos = u_model * vec4(v_position, 1); 17 | gl_Position = u_viewProj * worldPos; 18 | position = worldPos.xyz; 19 | normal = normalize((u_modelIT * vec4(v_normal,0)).xyz); 20 | } 21 | #endif 22 | 23 | #ifdef FRAG_SHADER 24 | in vec3 position; 25 | in vec3 normal; 26 | void main() 27 | { 28 | vec3 eyeDir = normalize(u_eye - position); 29 | vec3 light = u_emissive; 30 | for(int i=0; i<8; ++i) 31 | { 32 | vec3 lightDir = normalize(u_lights[i].position - position); 33 | light += u_lights[i].color * u_diffuse * max(dot(normal, lightDir), 0); 34 | 35 | vec3 halfDir = normalize(lightDir + eyeDir); 36 | light += u_lights[i].color * u_diffuse * pow(max(dot(normal, halfDir), 0), 128); 37 | } 38 | gl_FragColor = vec4(light,1); 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /assets/test.scene: -------------------------------------------------------------------------------- 1 | { 2 | "objects": [ 3 | { 4 | "name": "Ground", 5 | "pose": [ 6 | [0,0,-0.1], 7 | [0,0,0,1] 8 | ], 9 | "scale": [4,4,0.1], 10 | "diffuse": [0.4,0.4,0.4], 11 | "mesh": "cube", 12 | "prog": "simple" 13 | }, 14 | { 15 | "name": "Red Box", 16 | "pose": [ 17 | [-0.6,0,0.5], 18 | [0,0,0,1] 19 | ], 20 | "scale": [0.5,0.5,0.5], 21 | "diffuse": [1,0,0], 22 | "mesh": "cube", 23 | "prog": "simple" 24 | }, 25 | { 26 | "name": "Green Box", 27 | "pose": [ 28 | [0.6,0,0.5], 29 | [0,0,0,1] 30 | ], 31 | "scale": [0.5,0.5,0.5], 32 | "diffuse": [0,1,0], 33 | "mesh": "cube", 34 | "prog": "simple" 35 | }, 36 | { 37 | "name": "Teapot", 38 | "pose": [ 39 | [0,0,1], 40 | [0,0,0,1] 41 | ], 42 | "scale": [0.25,0.25,0.25], 43 | "diffuse": [1,1,0], 44 | "mesh": "teapot", 45 | "prog": "simple" 46 | }, 47 | { 48 | "name": "Light", 49 | "pose": [ 50 | [0,-1,3], 51 | [0,0,0,1] 52 | ], 53 | "scale": [0.1,0.1,0.1], 54 | "diffuse": [0,0,0], 55 | "mesh": "cube", 56 | "prog": "simple", 57 | "light": { 58 | "color": [1,1,1] 59 | } 60 | } 61 | ] 62 | } -------------------------------------------------------------------------------- /assets/white.glsl: -------------------------------------------------------------------------------- 1 | layout(binding = 0) uniform PerObject 2 | { 3 | mat4 u_model; 4 | }; 5 | 6 | #ifdef VERT_SHADER 7 | layout(location = 0) in vec3 v_position; 8 | void main() 9 | { 10 | gl_Position = u_viewProj * u_model * vec4(v_position, 1); 11 | } 12 | #endif 13 | 14 | #ifdef FRAG_SHADER 15 | void main() 16 | { 17 | gl_FragColor = vec4(1,1,1,1); 18 | } 19 | #endif 20 | -------------------------------------------------------------------------------- /dep/include/GLFW/glfw3native.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | * GLFW 3.1 - www.glfw.org 3 | * A library for OpenGL, window and input 4 | *------------------------------------------------------------------------ 5 | * Copyright (c) 2002-2006 Marcus Geelnard 6 | * Copyright (c) 2006-2010 Camilla Berglund 7 | * 8 | * This software is provided 'as-is', without any express or implied 9 | * warranty. In no event will the authors be held liable for any damages 10 | * arising from the use of this software. 11 | * 12 | * Permission is granted to anyone to use this software for any purpose, 13 | * including commercial applications, and to alter it and redistribute it 14 | * freely, subject to the following restrictions: 15 | * 16 | * 1. The origin of this software must not be misrepresented; you must not 17 | * claim that you wrote the original software. If you use this software 18 | * in a product, an acknowledgment in the product documentation would 19 | * be appreciated but is not required. 20 | * 21 | * 2. Altered source versions must be plainly marked as such, and must not 22 | * be misrepresented as being the original software. 23 | * 24 | * 3. This notice may not be removed or altered from any source 25 | * distribution. 26 | * 27 | *************************************************************************/ 28 | 29 | #ifndef _glfw3_native_h_ 30 | #define _glfw3_native_h_ 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | 37 | /************************************************************************* 38 | * Doxygen documentation 39 | *************************************************************************/ 40 | 41 | /*! @defgroup native Native access 42 | * 43 | * **By using the native API, you assert that you know what you're doing and 44 | * how to fix problems caused by using it. If you don't, you shouldn't be 45 | * using it.** 46 | * 47 | * Before the inclusion of @ref glfw3native.h, you must define exactly one 48 | * window system API macro and exactly one context creation API macro. Failure 49 | * to do this will cause a compile-time error. 50 | * 51 | * The available window API macros are: 52 | * * `GLFW_EXPOSE_NATIVE_WIN32` 53 | * * `GLFW_EXPOSE_NATIVE_COCOA` 54 | * * `GLFW_EXPOSE_NATIVE_X11` 55 | * 56 | * The available context API macros are: 57 | * * `GLFW_EXPOSE_NATIVE_WGL` 58 | * * `GLFW_EXPOSE_NATIVE_NSGL` 59 | * * `GLFW_EXPOSE_NATIVE_GLX` 60 | * * `GLFW_EXPOSE_NATIVE_EGL` 61 | * 62 | * These macros select which of the native access functions that are declared 63 | * and which platform-specific headers to include. It is then up your (by 64 | * definition platform-specific) code to handle which of these should be 65 | * defined. 66 | */ 67 | 68 | 69 | /************************************************************************* 70 | * System headers and types 71 | *************************************************************************/ 72 | 73 | #if defined(GLFW_EXPOSE_NATIVE_WIN32) 74 | // This is a workaround for the fact that glfw3.h needs to export APIENTRY (for 75 | // example to allow applications to correctly declare a GL_ARB_debug_output 76 | // callback) but windows.h assumes no one will define APIENTRY before it does 77 | #undef APIENTRY 78 | #include 79 | #elif defined(GLFW_EXPOSE_NATIVE_COCOA) 80 | #include 81 | #if defined(__OBJC__) 82 | #import 83 | #else 84 | typedef void* id; 85 | #endif 86 | #elif defined(GLFW_EXPOSE_NATIVE_X11) 87 | #include 88 | #include 89 | #else 90 | #error "No window API specified" 91 | #endif 92 | 93 | #if defined(GLFW_EXPOSE_NATIVE_WGL) 94 | /* WGL is declared by windows.h */ 95 | #elif defined(GLFW_EXPOSE_NATIVE_NSGL) 96 | /* NSGL is declared by Cocoa.h */ 97 | #elif defined(GLFW_EXPOSE_NATIVE_GLX) 98 | #include 99 | #elif defined(GLFW_EXPOSE_NATIVE_EGL) 100 | #include 101 | #else 102 | #error "No context API specified" 103 | #endif 104 | 105 | 106 | /************************************************************************* 107 | * Functions 108 | *************************************************************************/ 109 | 110 | #if defined(GLFW_EXPOSE_NATIVE_WIN32) 111 | /*! @brief Returns the adapter device name of the specified monitor. 112 | * 113 | * @return The UTF-8 encoded adapter device name (for example `\\.\DISPLAY1`) 114 | * of the specified monitor, or `NULL` if an [error](@ref error_handling) 115 | * occurred. 116 | * 117 | * @par Thread Safety 118 | * This function may be called from any thread. Access is not synchronized. 119 | * 120 | * @par History 121 | * Added in GLFW 3.1. 122 | * 123 | * @ingroup native 124 | */ 125 | GLFWAPI const char* glfwGetWin32Adapter(GLFWmonitor* monitor); 126 | 127 | /*! @brief Returns the display device name of the specified monitor. 128 | * 129 | * @return The UTF-8 encoded display device name (for example 130 | * `\\.\DISPLAY1\Monitor0`) of the specified monitor, or `NULL` if an 131 | * [error](@ref error_handling) occurred. 132 | * 133 | * @par Thread Safety 134 | * This function may be called from any thread. Access is not synchronized. 135 | * 136 | * @par History 137 | * Added in GLFW 3.1. 138 | * 139 | * @ingroup native 140 | */ 141 | GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* monitor); 142 | 143 | /*! @brief Returns the `HWND` of the specified window. 144 | * 145 | * @return The `HWND` of the specified window, or `NULL` if an 146 | * [error](@ref error_handling) occurred. 147 | * 148 | * @par Thread Safety 149 | * This function may be called from any thread. Access is not synchronized. 150 | * 151 | * @par History 152 | * Added in GLFW 3.0. 153 | * 154 | * @ingroup native 155 | */ 156 | GLFWAPI HWND glfwGetWin32Window(GLFWwindow* window); 157 | #endif 158 | 159 | #if defined(GLFW_EXPOSE_NATIVE_WGL) 160 | /*! @brief Returns the `HGLRC` of the specified window. 161 | * 162 | * @return The `HGLRC` of the specified window, or `NULL` if an 163 | * [error](@ref error_handling) occurred. 164 | * 165 | * @par Thread Safety 166 | * This function may be called from any thread. Access is not synchronized. 167 | * 168 | * @par History 169 | * Added in GLFW 3.0. 170 | * 171 | * @ingroup native 172 | */ 173 | GLFWAPI HGLRC glfwGetWGLContext(GLFWwindow* window); 174 | #endif 175 | 176 | #if defined(GLFW_EXPOSE_NATIVE_COCOA) 177 | /*! @brief Returns the `CGDirectDisplayID` of the specified monitor. 178 | * 179 | * @return The `CGDirectDisplayID` of the specified monitor, or 180 | * `kCGNullDirectDisplay` if an [error](@ref error_handling) occurred. 181 | * 182 | * @par Thread Safety 183 | * This function may be called from any thread. Access is not synchronized. 184 | * 185 | * @par History 186 | * Added in GLFW 3.1. 187 | * 188 | * @ingroup native 189 | */ 190 | GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* monitor); 191 | 192 | /*! @brief Returns the `NSWindow` of the specified window. 193 | * 194 | * @return The `NSWindow` of the specified window, or `nil` if an 195 | * [error](@ref error_handling) occurred. 196 | * 197 | * @par Thread Safety 198 | * This function may be called from any thread. Access is not synchronized. 199 | * 200 | * @par History 201 | * Added in GLFW 3.0. 202 | * 203 | * @ingroup native 204 | */ 205 | GLFWAPI id glfwGetCocoaWindow(GLFWwindow* window); 206 | #endif 207 | 208 | #if defined(GLFW_EXPOSE_NATIVE_NSGL) 209 | /*! @brief Returns the `NSOpenGLContext` of the specified window. 210 | * 211 | * @return The `NSOpenGLContext` of the specified window, or `nil` if an 212 | * [error](@ref error_handling) occurred. 213 | * 214 | * @par Thread Safety 215 | * This function may be called from any thread. Access is not synchronized. 216 | * 217 | * @par History 218 | * Added in GLFW 3.0. 219 | * 220 | * @ingroup native 221 | */ 222 | GLFWAPI id glfwGetNSGLContext(GLFWwindow* window); 223 | #endif 224 | 225 | #if defined(GLFW_EXPOSE_NATIVE_X11) 226 | /*! @brief Returns the `Display` used by GLFW. 227 | * 228 | * @return The `Display` used by GLFW, or `NULL` if an 229 | * [error](@ref error_handling) occurred. 230 | * 231 | * @par Thread Safety 232 | * This function may be called from any thread. Access is not synchronized. 233 | * 234 | * @par History 235 | * Added in GLFW 3.0. 236 | * 237 | * @ingroup native 238 | */ 239 | GLFWAPI Display* glfwGetX11Display(void); 240 | 241 | /*! @brief Returns the `RRCrtc` of the specified monitor. 242 | * 243 | * @return The `RRCrtc` of the specified monitor, or `None` if an 244 | * [error](@ref error_handling) occurred. 245 | * 246 | * @par Thread Safety 247 | * This function may be called from any thread. Access is not synchronized. 248 | * 249 | * @par History 250 | * Added in GLFW 3.1. 251 | * 252 | * @ingroup native 253 | */ 254 | GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor); 255 | 256 | /*! @brief Returns the `RROutput` of the specified monitor. 257 | * 258 | * @return The `RROutput` of the specified monitor, or `None` if an 259 | * [error](@ref error_handling) occurred. 260 | * 261 | * @par Thread Safety 262 | * This function may be called from any thread. Access is not synchronized. 263 | * 264 | * @par History 265 | * Added in GLFW 3.1. 266 | * 267 | * @ingroup native 268 | */ 269 | GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor); 270 | 271 | /*! @brief Returns the `Window` of the specified window. 272 | * 273 | * @return The `Window` of the specified window, or `None` if an 274 | * [error](@ref error_handling) occurred. 275 | * 276 | * @par Thread Safety 277 | * This function may be called from any thread. Access is not synchronized. 278 | * 279 | * @par History 280 | * Added in GLFW 3.0. 281 | * 282 | * @ingroup native 283 | */ 284 | GLFWAPI Window glfwGetX11Window(GLFWwindow* window); 285 | #endif 286 | 287 | #if defined(GLFW_EXPOSE_NATIVE_GLX) 288 | /*! @brief Returns the `GLXContext` of the specified window. 289 | * 290 | * @return The `GLXContext` of the specified window, or `NULL` if an 291 | * [error](@ref error_handling) occurred. 292 | * 293 | * @par Thread Safety 294 | * This function may be called from any thread. Access is not synchronized. 295 | * 296 | * @par History 297 | * Added in GLFW 3.0. 298 | * 299 | * @ingroup native 300 | */ 301 | GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* window); 302 | #endif 303 | 304 | #if defined(GLFW_EXPOSE_NATIVE_EGL) 305 | /*! @brief Returns the `EGLDisplay` used by GLFW. 306 | * 307 | * @return The `EGLDisplay` used by GLFW, or `EGL_NO_DISPLAY` if an 308 | * [error](@ref error_handling) occurred. 309 | * 310 | * @par Thread Safety 311 | * This function may be called from any thread. Access is not synchronized. 312 | * 313 | * @par History 314 | * Added in GLFW 3.0. 315 | * 316 | * @ingroup native 317 | */ 318 | GLFWAPI EGLDisplay glfwGetEGLDisplay(void); 319 | 320 | /*! @brief Returns the `EGLContext` of the specified window. 321 | * 322 | * @return The `EGLContext` of the specified window, or `EGL_NO_CONTEXT` if an 323 | * [error](@ref error_handling) occurred. 324 | * 325 | * @par Thread Safety 326 | * This function may be called from any thread. Access is not synchronized. 327 | * 328 | * @par History 329 | * Added in GLFW 3.0. 330 | * 331 | * @ingroup native 332 | */ 333 | GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* window); 334 | 335 | /*! @brief Returns the `EGLSurface` of the specified window. 336 | * 337 | * @return The `EGLSurface` of the specified window, or `EGL_NO_SURFACE` if an 338 | * [error](@ref error_handling) occurred. 339 | * 340 | * @par Thread Safety 341 | * This function may be called from any thread. Access is not synchronized. 342 | * 343 | * @par History 344 | * Added in GLFW 3.0. 345 | * 346 | * @ingroup native 347 | */ 348 | GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* window); 349 | #endif 350 | 351 | #ifdef __cplusplus 352 | } 353 | #endif 354 | 355 | #endif /* _glfw3_native_h_ */ 356 | 357 | -------------------------------------------------------------------------------- /dep/include/nanovg.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2013 Mikko Mononen memon@inside.org 3 | // 4 | // This software is provided 'as-is', without any express or implied 5 | // warranty. In no event will the authors be held liable for any damages 6 | // arising from the use of this software. 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 1. The origin of this software must not be misrepresented; you must not 11 | // claim that you wrote the original software. If you use this software 12 | // in a product, an acknowledgment in the product documentation would be 13 | // appreciated but is not required. 14 | // 2. Altered source versions must be plainly marked as such, and must not be 15 | // misrepresented as being the original software. 16 | // 3. This notice may not be removed or altered from any source distribution. 17 | // 18 | 19 | #ifndef NANOVG_H 20 | #define NANOVG_H 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #define NVG_PI 3.14159265358979323846264338327f 27 | 28 | #ifdef _MSC_VER 29 | #pragma warning(push) 30 | #pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union 31 | #endif 32 | 33 | typedef struct NVGcontext NVGcontext; 34 | 35 | struct NVGcolor { 36 | union { 37 | float rgba[4]; 38 | struct { 39 | float r,g,b,a; 40 | }; 41 | }; 42 | }; 43 | typedef struct NVGcolor NVGcolor; 44 | 45 | struct NVGpaint { 46 | float xform[6]; 47 | float extent[2]; 48 | float radius; 49 | float feather; 50 | NVGcolor innerColor; 51 | NVGcolor outerColor; 52 | int image; 53 | }; 54 | typedef struct NVGpaint NVGpaint; 55 | 56 | enum NVGwinding { 57 | NVG_CCW = 1, // Winding for solid shapes 58 | NVG_CW = 2, // Winding for holes 59 | }; 60 | 61 | enum NVGsolidity { 62 | NVG_SOLID = 1, // CCW 63 | NVG_HOLE = 2, // CW 64 | }; 65 | 66 | enum NVGlineCap { 67 | NVG_BUTT, 68 | NVG_ROUND, 69 | NVG_SQUARE, 70 | NVG_BEVEL, 71 | NVG_MITER, 72 | }; 73 | 74 | enum NVGalign { 75 | // Horizontal align 76 | NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left. 77 | NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center. 78 | NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right. 79 | // Vertical align 80 | NVG_ALIGN_TOP = 1<<3, // Align text vertically to top. 81 | NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle. 82 | NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. 83 | NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. 84 | }; 85 | 86 | struct NVGglyphPosition { 87 | const char* str; // Position of the glyph in the input string. 88 | float x; // The x-coordinate of the logical glyph position. 89 | float minx, maxx; // The bounds of the glyph shape. 90 | }; 91 | typedef struct NVGglyphPosition NVGglyphPosition; 92 | 93 | struct NVGtextRow { 94 | const char* start; // Pointer to the input text where the row starts. 95 | const char* end; // Pointer to the input text where the row ends (one past the last character). 96 | const char* next; // Pointer to the beginning of the next row. 97 | float width; // Logical width of the row. 98 | float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 99 | }; 100 | typedef struct NVGtextRow NVGtextRow; 101 | 102 | enum NVGimageFlags { 103 | NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image. 104 | NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction. 105 | NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction. 106 | NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered. 107 | NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha. 108 | }; 109 | 110 | // Begin drawing a new frame 111 | // Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() 112 | // nvgBeginFrame() defines the size of the window to render to in relation currently 113 | // set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 114 | // control the rendering on Hi-DPI devices. 115 | // For example, GLFW returns two dimension for an opened window: window size and 116 | // frame buffer size. In that case you would set windowWidth/Height to the window size 117 | // devicePixelRatio to: frameBufferWidth / windowWidth. 118 | void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio); 119 | 120 | // Cancels drawing the current frame. 121 | void nvgCancelFrame(NVGcontext* ctx); 122 | 123 | // Ends drawing flushing remaining render state. 124 | void nvgEndFrame(NVGcontext* ctx); 125 | 126 | // 127 | // Color utils 128 | // 129 | // Colors in NanoVG are stored as unsigned ints in ABGR format. 130 | 131 | // Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 132 | NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b); 133 | 134 | // Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 135 | NVGcolor nvgRGBf(float r, float g, float b); 136 | 137 | 138 | // Returns a color value from red, green, blue and alpha values. 139 | NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); 140 | 141 | // Returns a color value from red, green, blue and alpha values. 142 | NVGcolor nvgRGBAf(float r, float g, float b, float a); 143 | 144 | 145 | // Linearly interpoaltes from color c0 to c1, and returns resulting color value. 146 | NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u); 147 | 148 | // Sets transparency of a color value. 149 | NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a); 150 | 151 | // Sets transparency of a color value. 152 | NVGcolor nvgTransRGBAf(NVGcolor c0, float a); 153 | 154 | // Returns color value specified by hue, saturation and lightness. 155 | // HSL values are all in range [0..1], alpha will be set to 255. 156 | NVGcolor nvgHSL(float h, float s, float l); 157 | 158 | // Returns color value specified by hue, saturation and lightness and alpha. 159 | // HSL values are all in range [0..1], alpha in range [0..255] 160 | NVGcolor nvgHSLA(float h, float s, float l, unsigned char a); 161 | 162 | // 163 | // State Handling 164 | // 165 | // NanoVG contains state which represents how paths will be rendered. 166 | // The state contains transform, fill and stroke styles, text and font styles, 167 | // and scissor clipping. 168 | 169 | // Pushes and saves the current render state into a state stack. 170 | // A matching nvgRestore() must be used to restore the state. 171 | void nvgSave(NVGcontext* ctx); 172 | 173 | // Pops and restores current render state. 174 | void nvgRestore(NVGcontext* ctx); 175 | 176 | // Resets current render state to default values. Does not affect the render state stack. 177 | void nvgReset(NVGcontext* ctx); 178 | 179 | // 180 | // Render styles 181 | // 182 | // Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 183 | // Solid color is simply defined as a color value, different kinds of paints can be created 184 | // using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern(). 185 | // 186 | // Current render style can be saved and restored using nvgSave() and nvgRestore(). 187 | 188 | // Sets current stroke style to a solid color. 189 | void nvgStrokeColor(NVGcontext* ctx, NVGcolor color); 190 | 191 | // Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 192 | void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint); 193 | 194 | // Sets current fill cstyle to a solid color. 195 | void nvgFillColor(NVGcontext* ctx, NVGcolor color); 196 | 197 | // Sets current fill style to a paint, which can be a one of the gradients or a pattern. 198 | void nvgFillPaint(NVGcontext* ctx, NVGpaint paint); 199 | 200 | // Sets the miter limit of the stroke style. 201 | // Miter limit controls when a sharp corner is beveled. 202 | void nvgMiterLimit(NVGcontext* ctx, float limit); 203 | 204 | // Sets the stroke witdth of the stroke style. 205 | void nvgStrokeWidth(NVGcontext* ctx, float size); 206 | 207 | // Sets how the end of the line (cap) is drawn, 208 | // Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE. 209 | void nvgLineCap(NVGcontext* ctx, int cap); 210 | 211 | // Sets how sharp path corners are drawn. 212 | // Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL. 213 | void nvgLineJoin(NVGcontext* ctx, int join); 214 | 215 | // Sets the transparency applied to all rendered shapes. 216 | // Alreade transparent paths will get proportionally more transparent as well. 217 | void nvgGlobalAlpha(NVGcontext* ctx, float alpha); 218 | 219 | // 220 | // Transforms 221 | // 222 | // The paths, gradients, patterns and scissor region are transformed by an transformation 223 | // matrix at the time when they are passed to the API. 224 | // The current transformation matrix is a affine matrix: 225 | // [sx kx tx] 226 | // [ky sy ty] 227 | // [ 0 0 1] 228 | // Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation. 229 | // The last row is assumed to be 0,0,1 and is not stored. 230 | // 231 | // Apart from nvgResetTransform(), each transformation function first creates 232 | // specific transformation matrix and pre-multiplies the current transformation by it. 233 | // 234 | // Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). 235 | 236 | // Resets current transform to a identity matrix. 237 | void nvgResetTransform(NVGcontext* ctx); 238 | 239 | // Premultiplies current coordinate system by specified matrix. 240 | // The parameters are interpreted as matrix as follows: 241 | // [a c e] 242 | // [b d f] 243 | // [0 0 1] 244 | void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f); 245 | 246 | // Translates current coordinate system. 247 | void nvgTranslate(NVGcontext* ctx, float x, float y); 248 | 249 | // Rotates current coordinate system. Angle is specifid in radians. 250 | void nvgRotate(NVGcontext* ctx, float angle); 251 | 252 | // Skews the current coordinate system along X axis. Angle is specifid in radians. 253 | void nvgSkewX(NVGcontext* ctx, float angle); 254 | 255 | // Skews the current coordinate system along Y axis. Angle is specifid in radians. 256 | void nvgSkewY(NVGcontext* ctx, float angle); 257 | 258 | // Scales the current coordinat system. 259 | void nvgScale(NVGcontext* ctx, float x, float y); 260 | 261 | // Stores the top part (a-f) of the current transformation matrix in to the specified buffer. 262 | // [a c e] 263 | // [b d f] 264 | // [0 0 1] 265 | // There should be space for 6 floats in the return buffer for the values a-f. 266 | void nvgCurrentTransform(NVGcontext* ctx, float* xform); 267 | 268 | 269 | // The following functions can be used to make calculations on 2x3 transformation matrices. 270 | // A 2x3 matrix is representated as float[6]. 271 | 272 | // Sets the transform to identity matrix. 273 | void nvgTransformIdentity(float* dst); 274 | 275 | // Sets the transform to translation matrix matrix. 276 | void nvgTransformTranslate(float* dst, float tx, float ty); 277 | 278 | // Sets the transform to scale matrix. 279 | void nvgTransformScale(float* dst, float sx, float sy); 280 | 281 | // Sets the transform to rotate matrix. Angle is specifid in radians. 282 | void nvgTransformRotate(float* dst, float a); 283 | 284 | // Sets the transform to skew-x matrix. Angle is specifid in radians. 285 | void nvgTransformSkewX(float* dst, float a); 286 | 287 | // Sets the transform to skew-y matrix. Angle is specifid in radians. 288 | void nvgTransformSkewY(float* dst, float a); 289 | 290 | // Sets the transform to the result of multiplication of two transforms, of A = A*B. 291 | void nvgTransformMultiply(float* dst, const float* src); 292 | 293 | // Sets the transform to the result of multiplication of two transforms, of A = B*A. 294 | void nvgTransformPremultiply(float* dst, const float* src); 295 | 296 | // Sets the destination to inverse of specified transform. 297 | // Returns 1 if the inverse could be calculated, else 0. 298 | int nvgTransformInverse(float* dst, const float* src); 299 | 300 | // Transform a point by given transform. 301 | void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy); 302 | 303 | // Converts degress to radians and vice versa. 304 | float nvgDegToRad(float deg); 305 | float nvgRadToDeg(float rad); 306 | 307 | // 308 | // Images 309 | // 310 | // NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering. 311 | // In addition you can upload your own image. The image loading is provided by stb_image. 312 | // The parameter imageFlags is combination of flags defined in NVGimageFlags. 313 | 314 | // Creates image by loading it from the disk from specified file name. 315 | // Returns handle to the image. 316 | int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); 317 | 318 | // Creates image by loading it from the specified chunk of memory. 319 | // Returns handle to the image. 320 | int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); 321 | 322 | // Creates image from specified image data. 323 | // Returns handle to the image. 324 | int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data); 325 | 326 | // Updates image data specified by image handle. 327 | void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data); 328 | 329 | // Returns the domensions of a created image. 330 | void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h); 331 | 332 | // Deletes created image. 333 | void nvgDeleteImage(NVGcontext* ctx, int image); 334 | 335 | // 336 | // Paints 337 | // 338 | // NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 339 | // These can be used as paints for strokes and fills. 340 | 341 | // Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates 342 | // of the linear gradient, icol specifies the start color and ocol the end color. 343 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 344 | NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey, 345 | NVGcolor icol, NVGcolor ocol); 346 | 347 | // Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 348 | // drop shadows or hilights for boxes. Parameters (x,y) define the top-left corner of the rectangle, 349 | // (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 350 | // the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 351 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 352 | NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h, 353 | float r, float f, NVGcolor icol, NVGcolor ocol); 354 | 355 | // Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify 356 | // the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 357 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 358 | NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr, 359 | NVGcolor icol, NVGcolor ocol); 360 | 361 | // Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, 362 | // (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. 363 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 364 | NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey, 365 | float angle, int image, float alpha); 366 | 367 | // 368 | // Scissoring 369 | // 370 | // Scissoring allows you to clip the rendering into a rectangle. This is useful for varius 371 | // user interface cases like rendering a text edit or a timeline. 372 | 373 | // Sets the current scissor rectangle. 374 | // The scissor rectangle is transformed by the current transform. 375 | void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h); 376 | 377 | // Intersects current scissor rectangle with the specified rectangle. 378 | // The scissor rectangle is transformed by the current transform. 379 | // Note: in case the rotation of previous scissor rect differs from 380 | // the current one, the intersection will be done between the specified 381 | // rectangle and the previous scissor rectangle transformed in the current 382 | // transform space. The resulting shape is always rectangle. 383 | void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h); 384 | 385 | // Reset and disables scissoring. 386 | void nvgResetScissor(NVGcontext* ctx); 387 | 388 | // 389 | // Paths 390 | // 391 | // Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths. 392 | // Then you define one or more paths and sub-paths which describe the shape. The are functions 393 | // to draw common shapes like rectangles and circles, and lower level step-by-step functions, 394 | // which allow to define a path curve by curve. 395 | // 396 | // NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 397 | // winding and holes should have counter clockwise order. To specify winding of a path you can 398 | // call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW. 399 | // 400 | // Finally you can fill the path using current fill style by calling nvgFill(), and stroke it 401 | // with current stroke style by calling nvgStroke(). 402 | // 403 | // The curve segments and sub-paths are transformed by the current transform. 404 | 405 | // Clears the current path and sub-paths. 406 | void nvgBeginPath(NVGcontext* ctx); 407 | 408 | // Starts new sub-path with specified point as first point. 409 | void nvgMoveTo(NVGcontext* ctx, float x, float y); 410 | 411 | // Adds line segment from the last point in the path to the specified point. 412 | void nvgLineTo(NVGcontext* ctx, float x, float y); 413 | 414 | // Adds cubic bezier segment from last point in the path via two control points to the specified point. 415 | void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y); 416 | 417 | // Adds quadratic bezier segment from last point in the path via a control point to the specified point. 418 | void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y); 419 | 420 | // Adds an arc segment at the corner defined by the last path point, and two specified points. 421 | void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius); 422 | 423 | // Closes current sub-path with a line segment. 424 | void nvgClosePath(NVGcontext* ctx); 425 | 426 | // Sets the current sub-path winding, see NVGwinding and NVGsolidity. 427 | void nvgPathWinding(NVGcontext* ctx, int dir); 428 | 429 | // Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r, 430 | // and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW). 431 | // Angles are specified in radians. 432 | void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir); 433 | 434 | // Creates new rectangle shaped sub-path. 435 | void nvgRect(NVGcontext* ctx, float x, float y, float w, float h); 436 | 437 | // Creates new rounded rectangle shaped sub-path. 438 | void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r); 439 | 440 | // Creates new ellipse shaped sub-path. 441 | void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry); 442 | 443 | // Creates new circle shaped sub-path. 444 | void nvgCircle(NVGcontext* ctx, float cx, float cy, float r); 445 | 446 | // Fills the current path with current fill style. 447 | void nvgFill(NVGcontext* ctx); 448 | 449 | // Fills the current path with current stroke style. 450 | void nvgStroke(NVGcontext* ctx); 451 | 452 | 453 | // 454 | // Text 455 | // 456 | // NanoVG allows you to load .ttf files and use the font to render text. 457 | // 458 | // The appearance of the text can be defined by setting the current text style 459 | // and by specifying the fill color. Common text and font settings such as 460 | // font size, letter spacing and text align are supported. Font blur allows you 461 | // to create simple text effects such as drop shadows. 462 | // 463 | // At render time the font face can be set based on the font handles or name. 464 | // 465 | // Font measure functions return values in local space, the calculations are 466 | // carried in the same resolution as the final rendering. This is done because 467 | // the text glyph positions are snapped to the nearest pixels sharp rendering. 468 | // 469 | // The local space means that values are not rotated or scale as per the current 470 | // transformation. For example if you set font size to 12, which would mean that 471 | // line height is 16, then regardless of the current scaling and rotation, the 472 | // returned line height is always 16. Some measures may vary because of the scaling 473 | // since aforementioned pixel snapping. 474 | // 475 | // While this may sound a little odd, the setup allows you to always render the 476 | // same way regardless of scaling. I.e. following works regardless of scaling: 477 | // 478 | // const char* txt = "Text me up."; 479 | // nvgTextBounds(vg, x,y, txt, NULL, bounds); 480 | // nvgBeginPath(vg); 481 | // nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 482 | // nvgFill(vg); 483 | // 484 | // Note: currently only solid color fill is supported for text. 485 | 486 | // Creates font by loading it from the disk from specified file name. 487 | // Returns handle to the font. 488 | int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename); 489 | 490 | // Creates image by loading it from the specified memory chunk. 491 | // Returns handle to the font. 492 | int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData); 493 | 494 | // Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. 495 | int nvgFindFont(NVGcontext* ctx, const char* name); 496 | 497 | // Sets the font size of current text style. 498 | void nvgFontSize(NVGcontext* ctx, float size); 499 | 500 | // Sets the blur of current text style. 501 | void nvgFontBlur(NVGcontext* ctx, float blur); 502 | 503 | // Sets the letter spacing of current text style. 504 | void nvgTextLetterSpacing(NVGcontext* ctx, float spacing); 505 | 506 | // Sets the proportional line height of current text style. The line height is specified as multiple of font size. 507 | void nvgTextLineHeight(NVGcontext* ctx, float lineHeight); 508 | 509 | // Sets the text align of current text style, see NVGaling for options. 510 | void nvgTextAlign(NVGcontext* ctx, int align); 511 | 512 | // Sets the font face based on specified id of current text style. 513 | void nvgFontFaceId(NVGcontext* ctx, int font); 514 | 515 | // Sets the font face based on specified name of current text style. 516 | void nvgFontFace(NVGcontext* ctx, const char* font); 517 | 518 | // Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. 519 | float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end); 520 | 521 | // Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. 522 | // White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 523 | // Words longer than the max width are slit at nearest character (i.e. no hyphenation). 524 | void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end); 525 | 526 | // Measures the specified text string. Parameter bounds should be a pointer to float[4], 527 | // if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] 528 | // Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 529 | // Measured values are returned in local coordinate space. 530 | float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds); 531 | 532 | // Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], 533 | // if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] 534 | // Measured values are returned in local coordinate space. 535 | void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds); 536 | 537 | // Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. 538 | // Measured values are returned in local coordinate space. 539 | int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions); 540 | 541 | // Returns the vertical metrics based on the current text style. 542 | // Measured values are returned in local coordinate space. 543 | void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh); 544 | 545 | // Breaks the specified text into lines. If end is specified only the sub-string will be used. 546 | // White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 547 | // Words longer than the max width are slit at nearest character (i.e. no hyphenation). 548 | int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows); 549 | 550 | // 551 | // Internal Render API 552 | // 553 | enum NVGtexture { 554 | NVG_TEXTURE_ALPHA = 0x01, 555 | NVG_TEXTURE_RGBA = 0x02, 556 | }; 557 | 558 | struct NVGscissor { 559 | float xform[6]; 560 | float extent[2]; 561 | }; 562 | typedef struct NVGscissor NVGscissor; 563 | 564 | struct NVGvertex { 565 | float x,y,u,v; 566 | }; 567 | typedef struct NVGvertex NVGvertex; 568 | 569 | struct NVGpath { 570 | int first; 571 | int count; 572 | unsigned char closed; 573 | int nbevel; 574 | NVGvertex* fill; 575 | int nfill; 576 | NVGvertex* stroke; 577 | int nstroke; 578 | int winding; 579 | int convex; 580 | }; 581 | typedef struct NVGpath NVGpath; 582 | 583 | struct NVGparams { 584 | void* userPtr; 585 | int edgeAntiAlias; 586 | int (*renderCreate)(void* uptr); 587 | int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); 588 | int (*renderDeleteTexture)(void* uptr, int image); 589 | int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); 590 | int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); 591 | void (*renderViewport)(void* uptr, int width, int height); 592 | void (*renderCancel)(void* uptr); 593 | void (*renderFlush)(void* uptr); 594 | void (*renderFill)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); 595 | void (*renderStroke)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); 596 | void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGscissor* scissor, const NVGvertex* verts, int nverts); 597 | void (*renderDelete)(void* uptr); 598 | }; 599 | typedef struct NVGparams NVGparams; 600 | 601 | // Contructor and destructor, called by the render back-end. 602 | NVGcontext* nvgCreateInternal(NVGparams* params); 603 | void nvgDeleteInternal(NVGcontext* ctx); 604 | 605 | NVGparams* nvgInternalParams(NVGcontext* ctx); 606 | 607 | // Debug function to dump cached path data. 608 | void nvgDebugDumpPathCache(NVGcontext* ctx); 609 | 610 | #ifdef _MSC_VER 611 | #pragma warning(pop) 612 | #endif 613 | 614 | #define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; } 615 | 616 | #ifdef __cplusplus 617 | } 618 | #endif 619 | 620 | #endif // NANOVG_H 621 | -------------------------------------------------------------------------------- /dep/include/nanovg_gl_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2009-2013 Mikko Mononen memon@inside.org 3 | // 4 | // This software is provided 'as-is', without any express or implied 5 | // warranty. In no event will the authors be held liable for any damages 6 | // arising from the use of this software. 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 1. The origin of this software must not be misrepresented; you must not 11 | // claim that you wrote the original software. If you use this software 12 | // in a product, an acknowledgment in the product documentation would be 13 | // appreciated but is not required. 14 | // 2. Altered source versions must be plainly marked as such, and must not be 15 | // misrepresented as being the original software. 16 | // 3. This notice may not be removed or altered from any source distribution. 17 | // 18 | #ifndef NANOVG_GL_UTILS_H 19 | #define NANOVG_GL_UTILS_H 20 | 21 | struct NVGLUframebuffer { 22 | NVGcontext* ctx; 23 | GLuint fbo; 24 | GLuint rbo; 25 | GLuint texture; 26 | int image; 27 | }; 28 | typedef struct NVGLUframebuffer NVGLUframebuffer; 29 | 30 | // Helper function to create GL frame buffer to render to. 31 | void nvgluBindFramebuffer(NVGLUframebuffer* fb); 32 | NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags); 33 | void nvgluDeleteFramebuffer(NVGcontext* ctx, NVGLUframebuffer* fb); 34 | 35 | #endif // NANOVG_GL_UTILS_H 36 | 37 | #ifdef NANOVG_GL_IMPLEMENTATION 38 | 39 | #if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3) 40 | // FBO is core in OpenGL 3>. 41 | # define NANOVG_FBO_VALID 1 42 | #elif defined(NANOVG_GL2) 43 | // On OS X including glext defines FBO on GL2 too. 44 | # ifdef __APPLE__ 45 | # include 46 | # define NANOVG_FBO_VALID 1 47 | # endif 48 | #endif 49 | 50 | static GLint defaultFBO = -1; 51 | 52 | NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags) 53 | { 54 | #ifdef NANOVG_FBO_VALID 55 | GLint defaultFBO; 56 | GLint defaultRBO; 57 | NVGLUframebuffer* fb = NULL; 58 | 59 | glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); 60 | glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO); 61 | 62 | fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer)); 63 | if (fb == NULL) goto error; 64 | memset(fb, 0, sizeof(NVGLUframebuffer)); 65 | 66 | fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL); 67 | fb->texture = nvglImageHandle(ctx, fb->image); 68 | 69 | // frame buffer object 70 | glGenFramebuffers(1, &fb->fbo); 71 | glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo); 72 | 73 | // render buffer object 74 | glGenRenderbuffers(1, &fb->rbo); 75 | glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo); 76 | glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h); 77 | 78 | // combine all 79 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); 80 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); 81 | 82 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) goto error; 83 | 84 | glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); 85 | glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); 86 | return fb; 87 | error: 88 | glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); 89 | glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); 90 | nvgluDeleteFramebuffer(ctx, fb); 91 | return NULL; 92 | #else 93 | NVG_NOTUSED(ctx); 94 | NVG_NOTUSED(w); 95 | NVG_NOTUSED(h); 96 | NVG_NOTUSED(imageFlags); 97 | return NULL; 98 | #endif 99 | } 100 | 101 | void nvgluBindFramebuffer(NVGLUframebuffer* fb) 102 | { 103 | #ifdef NANOVG_FBO_VALID 104 | if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); 105 | glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO); 106 | #else 107 | NVG_NOTUSED(fb); 108 | #endif 109 | } 110 | 111 | void nvgluDeleteFramebuffer(NVGcontext* ctx, NVGLUframebuffer* fb) 112 | { 113 | #ifdef NANOVG_FBO_VALID 114 | if (fb == NULL) return; 115 | if (fb->fbo != 0) 116 | glDeleteFramebuffers(1, &fb->fbo); 117 | if (fb->rbo != 0) 118 | glDeleteRenderbuffers(1, &fb->rbo); 119 | if (fb->image >= 0) 120 | nvgDeleteImage(ctx, fb->image); 121 | fb->fbo = 0; 122 | fb->rbo = 0; 123 | fb->texture = 0; 124 | fb->image = -1; 125 | free(fb); 126 | #else 127 | NVG_NOTUSED(ctx); 128 | NVG_NOTUSED(fb); 129 | #endif 130 | } 131 | 132 | #endif // NANOVG_GL_IMPLEMENTATION 133 | -------------------------------------------------------------------------------- /dep/licenses/GLFW3.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002-2006 Marcus Geelnard 2 | Copyright (c) 2006-2010 Camilla Berglund 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, 9 | including commercial applications, and to alter it and redistribute it 10 | freely, subject to the following restrictions: 11 | 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would 15 | be appreciated but is not required. 16 | 17 | 2. Altered source versions must be plainly marked as such, and must not 18 | be misrepresented as being the original software. 19 | 20 | 3. This notice may not be removed or altered from any source 21 | distribution. 22 | 23 | -------------------------------------------------------------------------------- /dep/licenses/NANOVG.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Mikko Mononen memon@inside.org 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | 19 | -------------------------------------------------------------------------------- /dep/licenses/STB.txt: -------------------------------------------------------------------------------- 1 | These excellent public domain single-header libraries were authored by Sean T. Barrett. 2 | 3 | They are available at http://github.com/nothings/stb -------------------------------------------------------------------------------- /msvc/.gitignore: -------------------------------------------------------------------------------- 1 | /obj 2 | /ipch 3 | /packages 4 | /*.opensdf 5 | /*.sdf 6 | /*.suo 7 | -------------------------------------------------------------------------------- /msvc/editor/editor.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {B2B761F2-9090-4268-A83A-B25AA2D06452} 23 | Win32Proj 24 | Editor 25 | 26 | 27 | 28 | Application 29 | true 30 | v120 31 | Unicode 32 | 33 | 34 | Application 35 | true 36 | v120 37 | Unicode 38 | 39 | 40 | Application 41 | false 42 | v120 43 | true 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v120 50 | true 51 | Unicode 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | true 71 | $(SolutionDir)..\bin-$(Platform)\ 72 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 73 | $(ProjectName)-d 74 | 75 | 76 | true 77 | $(SolutionDir)..\bin-$(Platform)\ 78 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 79 | $(ProjectName)-d 80 | 81 | 82 | false 83 | $(SolutionDir)..\bin-$(Platform)\ 84 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 85 | 86 | 87 | false 88 | $(SolutionDir)..\bin-$(Platform)\ 89 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 90 | 91 | 92 | 93 | 94 | 95 | Level3 96 | Disabled 97 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 98 | $(SolutionDir)..\src;$(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 99 | 100 | 101 | Console 102 | true 103 | $(SolutionDir)lib-$(Platform);%(AdditionalLibraryDirectories) 104 | glfw3dll.lib;opengl32.lib;%(AdditionalDependencies) 105 | 106 | 107 | XCOPY /Y $(SolutionDir)lib-$(Platform)\*.dll $(OutDir) 108 | 109 | 110 | 111 | 112 | 113 | 114 | Level3 115 | Disabled 116 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 117 | $(SolutionDir)..\src;$(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 118 | 119 | 120 | Console 121 | true 122 | $(SolutionDir)lib-$(Platform);%(AdditionalLibraryDirectories) 123 | glfw3dll.lib;opengl32.lib;%(AdditionalDependencies) 124 | 125 | 126 | XCOPY /Y $(SolutionDir)lib-$(Platform)\*.dll $(OutDir) 127 | 128 | 129 | 130 | 131 | Level3 132 | 133 | 134 | MaxSpeed 135 | true 136 | true 137 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 138 | $(SolutionDir)..\src;$(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 139 | 140 | 141 | Console 142 | true 143 | true 144 | true 145 | $(SolutionDir)lib-$(Platform);%(AdditionalLibraryDirectories) 146 | glfw3dll.lib;opengl32.lib;%(AdditionalDependencies) 147 | 148 | 149 | XCOPY /Y $(SolutionDir)lib-$(Platform)\*.dll $(OutDir) 150 | 151 | 152 | 153 | 154 | Level3 155 | 156 | 157 | MaxSpeed 158 | true 159 | true 160 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 161 | $(SolutionDir)..\src;$(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 162 | 163 | 164 | Console 165 | true 166 | true 167 | true 168 | $(SolutionDir)lib-$(Platform);%(AdditionalLibraryDirectories) 169 | glfw3dll.lib;opengl32.lib;%(AdditionalDependencies) 170 | 171 | 172 | XCOPY /Y $(SolutionDir)lib-$(Platform)\*.dll $(OutDir) 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | {6b85ff49-f8b2-4703-8cfd-563500856cb8} 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /msvc/editor/editor.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | gui 8 | 9 | 10 | 11 | 12 | gui 13 | 14 | 15 | 16 | 17 | 18 | gui 19 | 20 | 21 | gui 22 | 23 | 24 | 25 | 26 | gui 27 | 28 | 29 | 30 | 31 | 32 | assets 33 | 34 | 35 | assets 36 | 37 | 38 | 39 | 40 | {7939bdf6-7f5c-43e5-bdef-5a43ee8582bd} 41 | 42 | 43 | {9b04b349-671e-4a37-88e6-63a4c226cc6b} 44 | 45 | 46 | -------------------------------------------------------------------------------- /msvc/editor/editor.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(OutDir) 5 | WindowsLocalDebugger 6 | 7 | 8 | $(OutDir) 9 | WindowsLocalDebugger 10 | 11 | 12 | $(OutDir) 13 | WindowsLocalDebugger 14 | 15 | 16 | $(OutDir) 17 | WindowsLocalDebugger 18 | 19 | -------------------------------------------------------------------------------- /msvc/editor/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /msvc/engine/engine.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {6B85FF49-F8B2-4703-8CFD-563500856CB8} 55 | Win32Proj 56 | engine 57 | 58 | 59 | 60 | StaticLibrary 61 | true 62 | v120 63 | Unicode 64 | 65 | 66 | StaticLibrary 67 | true 68 | v120 69 | Unicode 70 | 71 | 72 | StaticLibrary 73 | false 74 | v120 75 | true 76 | Unicode 77 | 78 | 79 | StaticLibrary 80 | false 81 | v120 82 | true 83 | Unicode 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 103 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 104 | 105 | 106 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 107 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 108 | 109 | 110 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 111 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 112 | 113 | 114 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 115 | $(SolutionDir)obj\$(ProjectName)\$(Configuration)-$(Platform)\ 116 | 117 | 118 | 119 | 120 | 121 | Level3 122 | Disabled 123 | _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 124 | $(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 125 | 126 | 127 | Windows 128 | true 129 | 130 | 131 | 132 | 133 | 134 | 135 | Level3 136 | Disabled 137 | _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 138 | $(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 139 | 140 | 141 | Windows 142 | true 143 | 144 | 145 | 146 | 147 | Level3 148 | 149 | 150 | MaxSpeed 151 | true 152 | true 153 | _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 154 | $(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 155 | 156 | 157 | Windows 158 | true 159 | true 160 | true 161 | 162 | 163 | 164 | 165 | Level3 166 | 167 | 168 | MaxSpeed 169 | true 170 | true 171 | _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 172 | $(SolutionDir)..\dep\include;%(AdditionalIncludeDirectories) 173 | 174 | 175 | Windows 176 | true 177 | true 178 | true 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /msvc/engine/engine.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | include 6 | 7 | 8 | include 9 | 10 | 11 | include 12 | 13 | 14 | include 15 | 16 | 17 | include 18 | 19 | 20 | include 21 | 22 | 23 | include 24 | 25 | 26 | include 27 | 28 | 29 | include 30 | 31 | 32 | include 33 | 34 | 35 | dep 36 | 37 | 38 | dep 39 | 40 | 41 | dep 42 | 43 | 44 | dep 45 | 46 | 47 | dep 48 | 49 | 50 | dep 51 | 52 | 53 | 54 | 55 | dep 56 | 57 | 58 | src 59 | 60 | 61 | src 62 | 63 | 64 | src 65 | 66 | 67 | src 68 | 69 | 70 | src 71 | 72 | 73 | src 74 | 75 | 76 | src 77 | 78 | 79 | src 80 | 81 | 82 | 83 | 84 | {b15a68d0-8a15-42d4-a73c-94aa8fb1687d} 85 | 86 | 87 | {8fe09733-e762-45ea-8873-fa07da7fb7cb} 88 | 89 | 90 | {bdd357ed-33aa-4e7a-ae32-c6f0cdbecd87} 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /msvc/engine/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /msvc/lib-Win32/glfw3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgorsten/editor/7749421e7046255fe9861509658a92385deb57b6/msvc/lib-Win32/glfw3.dll -------------------------------------------------------------------------------- /msvc/lib-Win32/glfw3dll.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgorsten/editor/7749421e7046255fe9861509658a92385deb57b6/msvc/lib-Win32/glfw3dll.lib -------------------------------------------------------------------------------- /msvc/lib-x64/glfw3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgorsten/editor/7749421e7046255fe9861509658a92385deb57b6/msvc/lib-x64/glfw3.dll -------------------------------------------------------------------------------- /msvc/lib-x64/glfw3dll.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgorsten/editor/7749421e7046255fe9861509658a92385deb57b6/msvc/lib-x64/glfw3dll.lib -------------------------------------------------------------------------------- /msvc/packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /msvc/vs2013.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "editor", "editor\editor.vcxproj", "{B2B761F2-9090-4268-A83A-B25AA2D06452}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "engine", "engine\engine.vcxproj", "{6B85FF49-F8B2-4703-8CFD-563500856CB8}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Win32 = Debug|Win32 13 | Debug|x64 = Debug|x64 14 | Release|Win32 = Release|Win32 15 | Release|x64 = Release|x64 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Debug|Win32.Build.0 = Debug|Win32 20 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Debug|x64.ActiveCfg = Debug|x64 21 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Debug|x64.Build.0 = Debug|x64 22 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Release|Win32.ActiveCfg = Release|Win32 23 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Release|Win32.Build.0 = Release|Win32 24 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Release|x64.ActiveCfg = Release|x64 25 | {B2B761F2-9090-4268-A83A-B25AA2D06452}.Release|x64.Build.0 = Release|x64 26 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Debug|Win32.ActiveCfg = Debug|Win32 27 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Debug|Win32.Build.0 = Debug|Win32 28 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Debug|x64.ActiveCfg = Debug|x64 29 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Debug|x64.Build.0 = Debug|x64 30 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Release|Win32.ActiveCfg = Release|Win32 31 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Release|Win32.Build.0 = Release|Win32 32 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Release|x64.ActiveCfg = Release|x64 33 | {6B85FF49-F8B2-4703-8CFD-563500856CB8}.Release|x64.Build.0 = Release|x64 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /src/editor/editor.cpp: -------------------------------------------------------------------------------- 1 | #include "editor.h" 2 | 3 | /////////////// 4 | // Selection // 5 | /////////////// 6 | 7 | Selection::Selection() {} 8 | 9 | void Selection::Deselect() 10 | { 11 | object.reset(); 12 | if(onSelectionChanged) onSelectionChanged(); 13 | } 14 | 15 | void Selection::SetSelection(std::shared_ptr object) 16 | { 17 | if(object != this->object.lock()) 18 | { 19 | this->object = object; 20 | if(onSelectionChanged) onSelectionChanged(); 21 | } 22 | } 23 | 24 | ////////////// 25 | // Draggers // 26 | ////////////// 27 | 28 | class Raycaster 29 | { 30 | gui::Rect rect; 31 | float4x4 invViewProj; 32 | public: 33 | Raycaster(const gui::Rect & rect, const float4x4 & proj, const Pose & viewpoint) : rect(rect), invViewProj(inv(mul(proj, LookAtMatrixRh(viewpoint.position, viewpoint.position + viewpoint.Ydir(), viewpoint.Zdir())))) {} 34 | 35 | Ray ComputeRay(const int2 & pixel) const 36 | { 37 | auto viewX = (pixel.x - rect.x0) * 2.0f / rect.GetWidth() - 1; 38 | auto viewY = 1 - (pixel.y - rect.y0) * 2.0f / rect.GetHeight(); 39 | return Ray::Between(TransformCoordinate(invViewProj, {viewX, viewY, -1}), TransformCoordinate(invViewProj, {viewX, viewY, 1})); 40 | } 41 | }; 42 | 43 | class LinearTranslationDragger : public gui::IDragger 44 | { 45 | Object & object; 46 | Raycaster caster; 47 | float3 direction, initialPosition; 48 | float initialS; 49 | 50 | float ComputeS(const int2 & mouse) const 51 | { 52 | const Ray ray1 = {initialPosition, direction}, ray2 = caster.ComputeRay(mouse); 53 | const auto r12 = ray2.start - ray1.start; 54 | const auto e1e2 = dot(ray1.direction, ray2.direction), denom = 1 - e1e2*e1e2; 55 | return (dot(r12,ray1.direction) - dot(r12,ray2.direction)*e1e2) / denom; 56 | } 57 | public: 58 | LinearTranslationDragger(Object & object, const Raycaster & caster, const float3 & direction, const int2 & click) : object(object), caster(caster), direction(qrot(object.pose.orientation, direction)), initialPosition(object.pose.position), initialS(ComputeS(click)) {} 59 | 60 | void OnDrag(int2 newMouse) override { object.pose.position = initialPosition + direction * (ComputeS(newMouse) - initialS); } 61 | void OnRelease() override {} 62 | void OnCancel() override { object.pose.position = initialPosition; } 63 | }; 64 | 65 | class AxisRotationDragger : public gui::IDragger 66 | { 67 | Object & object; 68 | Raycaster caster; 69 | float3 axis, edge1; 70 | float4 initialOrientation; 71 | 72 | float3 ComputeEdge(const int2 & mouse) const 73 | { 74 | auto ray = caster.ComputeRay(mouse); 75 | auto hit = IntersectRayPlane(ray, Plane(axis, object.pose.position)); 76 | return ray.GetPoint(hit.t) - object.pose.position; 77 | } 78 | public: 79 | AxisRotationDragger(Object & object, const Raycaster & caster, const float3 & axis, const int2 & click) : object(object), caster(caster), axis(qrot(object.pose.orientation, axis)), initialOrientation(object.pose.orientation), edge1(ComputeEdge(click)) {} 80 | 81 | void OnDrag(int2 newMouse) override { object.pose.orientation = qmul(RotationQuaternionFromToVec(edge1, ComputeEdge(newMouse)), initialOrientation); } 82 | void OnRelease() override {} 83 | void OnCancel() override { object.pose.orientation = initialOrientation; } 84 | }; 85 | 86 | class LinearScalingDragger : public gui::IDragger 87 | { 88 | Object & object; 89 | Raycaster caster; 90 | float3 scaleDirection, direction, initialScale; 91 | float initialS; 92 | 93 | float ComputeS(const int2 & mouse) const 94 | { 95 | const Ray ray1 = {object.pose.position, direction}, ray2 = caster.ComputeRay(mouse); 96 | const auto r12 = ray2.start - ray1.start; 97 | const auto e1e2 = dot(ray1.direction, ray2.direction), denom = 1 - e1e2*e1e2; 98 | return (dot(r12,ray1.direction) - dot(r12,ray2.direction)*e1e2) / denom; 99 | } 100 | public: 101 | LinearScalingDragger(Object & object, const Raycaster & caster, const float3 & direction, const int2 & click) : object(object), caster(caster), scaleDirection(direction), direction(qrot(object.pose.orientation, direction)), initialScale(object.localScale), initialS(ComputeS(click)) {} 102 | 103 | void OnDrag(int2 newMouse) override 104 | { 105 | float scale = ComputeS(newMouse) / initialS; 106 | object.localScale = initialScale + scaleDirection * ((scale - 1) * dot(initialScale, scaleDirection)); 107 | } 108 | void OnRelease() override {} 109 | void OnCancel() override { object.localScale = initialScale; } 110 | }; 111 | 112 | class MouselookDragger : public gui::IDragger 113 | { 114 | View & view; 115 | int2 lastMouse; 116 | public: 117 | MouselookDragger(View & view, const int2 & click) : view(view), lastMouse(click) {} 118 | 119 | void OnDrag(int2 newMouse) override { view.OnDrag(newMouse - lastMouse); lastMouse = newMouse; } 120 | bool OnKey(int key, int action, int mods) override 121 | { 122 | switch(key) 123 | { 124 | case GLFW_KEY_W: view.bf = action != GLFW_RELEASE; break; 125 | case GLFW_KEY_A: view.bl = action != GLFW_RELEASE; break; 126 | case GLFW_KEY_S: view.bb = action != GLFW_RELEASE; break; 127 | case GLFW_KEY_D: view.br = action != GLFW_RELEASE; break; 128 | } 129 | return true; 130 | } 131 | void OnRelease() override { view.bf = view.bl = view.bb = view.br = false; } 132 | void OnCancel() override {} 133 | }; 134 | 135 | ////////// 136 | // View // 137 | ////////// 138 | 139 | NVGcolor View::OnDrawBackground(const gui::DrawEvent & e) const 140 | { 141 | int winWidth, winHeight; 142 | glfwGetWindowSize(glfwGetCurrentContext(), &winWidth, &winHeight); 143 | 144 | glPushAttrib(GL_ALL_ATTRIB_BITS); 145 | glViewport(rect.x0, winHeight - rect.y1, rect.GetWidth(), rect.GetHeight()); 146 | glScissor(rect.x0, winHeight - rect.y1, rect.GetWidth(), rect.GetHeight()); 147 | glEnable(GL_SCISSOR_TEST); 148 | glClearColor(0,0,1,1); 149 | glClear(GL_COLOR_BUFFER_BIT); 150 | 151 | glEnable(GL_DEPTH_TEST); 152 | glEnable(GL_CULL_FACE); 153 | auto proj = PerspectiveMatrixRhGl(1, rect.GetAspect(), 0.25f, 32.0f); 154 | auto view = LookAtMatrixRh(viewpoint.position, viewpoint.position + viewpoint.Ydir(), viewpoint.Zdir()); 155 | auto viewProj = mul(proj, view); 156 | 157 | gl::Buffer buf; 158 | if(auto b = selection.arrowProg->GetNamedBlock("PerView")) 159 | { 160 | std::vector data(b->dataSize); 161 | b->SetUniform(data.data(), "u_eye", viewpoint.position); 162 | b->SetUniform(data.data(), "u_viewProj", viewProj); 163 | buf.SetData(GL_UNIFORM_BUFFER, data.size(), data.data(), GL_STREAM_DRAW); 164 | buf.BindBase(GL_UNIFORM_BUFFER, b->binding); 165 | } 166 | 167 | RenderContext ctx; 168 | scene.Draw(ctx); 169 | 170 | if(auto obj = selection.object.lock()) 171 | { 172 | glPushAttrib(GL_ALL_ATTRIB_BITS); 173 | glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 174 | glDepthFunc(GL_LEQUAL); 175 | glEnable(GL_POLYGON_OFFSET_LINE); 176 | glPolygonOffset(-1, -1); 177 | auto prog = obj->prog; 178 | obj->prog = selection.selectionProgram; 179 | obj->Draw(); 180 | obj->prog = prog; 181 | glPopAttrib(); 182 | 183 | glClear(GL_DEPTH_BUFFER_BIT); 184 | for(auto & axis : {float3(1,0,0), float3(0,1,0), float3(0,0,1)}) 185 | { 186 | auto model = (obj->pose * Pose({0,0,0}, RotationQuaternionFromToVec({0,0,1}, axis))).Matrix(); 187 | auto color = axis * 0.4f + 0.1f; 188 | 189 | gl::Buffer buf; 190 | if(auto b = selection.arrowProg->GetNamedBlock("PerObject")) 191 | { 192 | std::vector data(b->dataSize); 193 | b->SetUniform(data.data(), "u_model", model); 194 | b->SetUniform(data.data(), "u_modelIT", inv(transpose(model))); 195 | b->SetUniform(data.data(), "u_diffuse", color); 196 | b->SetUniform(data.data(), "u_emissive", color*0.5f); 197 | buf.SetData(GL_UNIFORM_BUFFER, data.size(), data.data(), GL_STREAM_DRAW); 198 | buf.BindBase(GL_UNIFORM_BUFFER, b->binding); 199 | } 200 | 201 | selection.arrowProg->Use(); 202 | GetGizmoMesh().Draw(); 203 | } 204 | } 205 | 206 | glPopAttrib(); 207 | 208 | if(e.hasFocus) 209 | { 210 | nvgBeginPath(e.vg); 211 | nvgRect(e.vg, rect.x0+1.5f, rect.y0+1.5f, rect.GetWidth()-3.0f, rect.GetHeight()-3.0f); 212 | nvgStrokeColor(e.vg, nvgRGBA(255,255,255,128)); 213 | nvgStrokeWidth(e.vg, 1); 214 | nvgStroke(e.vg); 215 | } 216 | 217 | return {}; 218 | } 219 | 220 | static gui::DraggerPtr CreateGizmoDragger(View::Mode mode, Object & obj, Raycaster caster, const float3 & axis, const int2 & cursor) 221 | { 222 | switch(mode) 223 | { 224 | default: case View::Translation: return std::make_shared(obj, caster, axis, cursor); 225 | case View::Rotation: return std::make_shared(obj, caster, axis, cursor); 226 | case View::Scaling: return std::make_shared(obj, caster, axis, cursor); 227 | } 228 | } 229 | 230 | gui::DraggerPtr View::OnClick(const gui::MouseEvent & e) 231 | { 232 | // Mouselook when the user drags the right mouse button 233 | if(e.button == GLFW_MOUSE_BUTTON_RIGHT) return std::make_shared(*this, e.cursor); 234 | 235 | // Respond to left mouse button clicks with a raycast 236 | if(e.button == GLFW_MOUSE_BUTTON_LEFT) 237 | { 238 | Raycaster caster(rect, PerspectiveMatrixRhGl(1, rect.GetAspect(), 0.25f, 32.0f), viewpoint); 239 | Ray ray = caster.ComputeRay(e.cursor); 240 | 241 | // If an object is selected, check if we have clicked on its gizmo 242 | if(auto obj = selection.object.lock()) 243 | { 244 | gui::DraggerPtr best; float bestT; 245 | for(auto & axis : {float3(1,0,0), float3(0,1,0), float3(0,0,1)}) 246 | { 247 | auto localRay = (obj->pose * Pose({0,0,0}, RotationQuaternionFromToVec({0,0,1}, axis))).Inverse() * ray; 248 | auto hit = GetGizmoMesh().Hit(localRay); 249 | if(hit.hit && (!best || hit.t < bestT)) 250 | { 251 | best = CreateGizmoDragger(mode, *obj, caster, axis, e.cursor); 252 | bestT = hit.t; 253 | } 254 | } 255 | if(best) return best; 256 | } 257 | 258 | // Otherwise see if we have selected a new object 259 | if(auto obj = scene.Hit(ray)) selection.SetSelection(obj); 260 | else selection.Deselect(); 261 | 262 | // If we did not click on an object directly, perhaps we should box select? 263 | } 264 | 265 | return nullptr; 266 | } 267 | 268 | const Mesh & View::GetGizmoMesh() const 269 | { 270 | switch(mode) 271 | { 272 | default: case Translation: return selection.arrowMesh; 273 | case Rotation: return selection.circleMesh; 274 | case Scaling: return selection.scaleMesh; 275 | } 276 | } 277 | 278 | View::View(Scene & scene, Selection & selection) : scene(scene), selection(selection) {} 279 | 280 | bool View::OnKey(GLFWwindow * window, int key, int action, int mods) 281 | { 282 | if(action != GLFW_PRESS) return false; 283 | switch(key) 284 | { 285 | case GLFW_KEY_W: mode = Translation; return true; 286 | case GLFW_KEY_E: mode = Rotation; return true; 287 | case GLFW_KEY_R: mode = Scaling; return true; 288 | default: return false; 289 | } 290 | } 291 | 292 | void View::OnUpdate(float timestep) 293 | { 294 | float3 dir; 295 | if(bf) dir.y += 1; 296 | if(bl) dir.x -= 1; 297 | if(bb) dir.y -= 1; 298 | if(br) dir.x += 1; 299 | if(mag2(dir) > 0) viewpoint.position = viewpoint.TransformCoord(norm(dir) * (timestep * 8)); 300 | } 301 | 302 | void View::OnDrag(const int2 & delta) 303 | { 304 | yaw -= delta.x * 0.01f; 305 | pitch -= delta.y * 0.01f; 306 | viewpoint.orientation = qmul(RotationQuaternionAxisAngle({0,0,1}, yaw), RotationQuaternionAxisAngle({1,0,0}, pitch)); 307 | } 308 | 309 | //////////// 310 | // Editor // 311 | //////////// 312 | 313 | static Mesh MakeBox(const float3 & halfDims) 314 | { 315 | Mesh mesh; 316 | mesh.AddBox(-halfDims, +halfDims); 317 | mesh.ComputeNormals(); 318 | mesh.Upload(); 319 | return mesh; 320 | } 321 | 322 | #include 323 | #include 324 | #include 325 | 326 | Editor::Editor() : window("Editor", 1280, 720), font(window.GetNanoVG(), "../assets/Roboto-Bold.ttf", 18, true, 0x500), factory(font, 2), quit() 327 | { 328 | assets.SetLoader([](const std::string & id) -> Mesh 329 | { 330 | return LoadMeshFromObj("../assets/"+id+".obj", true); 331 | }); 332 | 333 | assets.SetLoader([](const std::string & id) -> gl::Program 334 | { 335 | std::string shaderPrelude = R"(#version 420 336 | struct PointLight 337 | { 338 | vec3 position; 339 | vec3 color; 340 | }; 341 | layout(binding = 3) uniform PerScene 342 | { 343 | PointLight u_lights[8]; 344 | }; 345 | layout(binding = 2) uniform PerView 346 | { 347 | mat4 u_viewProj; 348 | vec3 u_eye; 349 | }; 350 | )"; 351 | 352 | auto source = LoadTextFile("../assets/" + id + ".glsl"); 353 | auto vs = shaderPrelude + "#define VERT_SHADER\n" + source; 354 | auto fs = shaderPrelude + "#define FRAG_SHADER\n" + source; 355 | return gl::Program(vs, fs); 356 | }); 357 | 358 | view = std::make_shared(scene, selection); 359 | 360 | auto prog = assets.GetAsset("simple"); 361 | selection.selectionProgram = assets.GetAsset("white"); 362 | 363 | selection.arrowMesh.AddCylinder({0,0,0}, 0.00f, {0,0,0}, 0.05f, {1,0,0}, {0,1,0}, 12); 364 | selection.arrowMesh.AddCylinder({0,0,0}, 0.05f, {0,0,1}, 0.05f, {1,0,0}, {0,1,0}, 12); 365 | selection.arrowMesh.AddCylinder({0,0,1}, 0.05f, {0,0,1}, 0.10f, {1,0,0}, {0,1,0}, 12); 366 | selection.arrowMesh.AddCylinder({0,0,1}, 0.10f, {0,0,1.2f}, 0.0f, {1,0,0}, {0,1,0}, 12); 367 | selection.arrowMesh.ComputeNormals(); 368 | selection.arrowMesh.Upload(); 369 | selection.arrowProg = prog; 370 | 371 | selection.circleMesh.AddCylinder({0,0,-0.02f}, 0.90f, {0,0,-0.02f}, 1.00f, {1,0,0}, {0,1,0}, 32); 372 | selection.circleMesh.AddCylinder({0,0,-0.02f}, 1.00f, {0,0,+0.02f}, 1.00f, {1,0,0}, {0,1,0}, 32); 373 | selection.circleMesh.AddCylinder({0,0,+0.02f}, 1.00f, {0,0,+0.02f}, 0.90f, {1,0,0}, {0,1,0}, 32); 374 | selection.circleMesh.AddCylinder({0,0,+0.02f}, 0.90f, {0,0,-0.02f}, 0.90f, {1,0,0}, {0,1,0}, 32); 375 | selection.circleMesh.ComputeNormals(); 376 | selection.circleMesh.Upload(); 377 | 378 | selection.scaleMesh.AddCylinder({0,0,0}, 0.00f, {0,0,0}, 0.05f, {1,0,0}, {0,1,0}, 12); 379 | selection.scaleMesh.AddCylinder({0,0,0}, 0.05f, {0,0,1}, 0.05f, {1,0,0}, {0,1,0}, 12); 380 | selection.scaleMesh.AddBox({-0.1f,-0.1f,1}, {+0.1f,+0.1f,1.2f}); 381 | selection.scaleMesh.ComputeNormals(); 382 | selection.scaleMesh.Upload(); 383 | 384 | view->viewpoint.position = {0,-4,1}; 385 | 386 | objectListPanel = std::make_shared(); 387 | propertyPanel = std::make_shared(); 388 | 389 | docker = std::make_shared(window, font); 390 | docker->SetPrimaryElement(view); 391 | docker->Dock(*view, "Property Viewer", propertyPanel, gui::Splitter::Right, 400); 392 | docker->Dock(*propertyPanel, "Object List", objectListPanel, gui::Splitter::Top, 200); 393 | 394 | selection.onSelectionChanged = [this]() 395 | { 396 | auto it = std::find(begin(scene.objects), end(scene.objects), selection.object.lock()); 397 | objectList->SetSelectedIndex(it != end(scene.objects) ? it - begin(scene.objects) : -1); 398 | RefreshPropertyPanel(); 399 | }; 400 | 401 | LoadScene("../assets/test.scene"); 402 | RefreshMenu(); 403 | } 404 | 405 | int Editor::Run() 406 | { 407 | auto t0 = glfwGetTime(); 408 | while(!quit && !window.ShouldClose()) 409 | { 410 | glfwPollEvents(); 411 | 412 | const auto t1 = glfwGetTime(); 413 | const auto timestep = static_cast(t1 - t0); 414 | t0 = t1; 415 | 416 | view->OnUpdate(timestep); 417 | window.Redraw(); 418 | docker->RedrawAll(); 419 | } 420 | return 0; 421 | } 422 | 423 | void Editor::LoadScene(const std::string & filepath) 424 | { 425 | scene = DeserializeFromJson(jsonFrom(LoadTextFile(filepath)), assets); 426 | RefreshObjectList(); 427 | } 428 | 429 | void Editor::RefreshMenu() 430 | { 431 | window.SetGuiRoot(docker, font, std::vector{ 432 | gui::MenuItem::Popup("File", { 433 | {"New", [](){}, GLFW_MOD_CONTROL, GLFW_KEY_N}, 434 | gui::MenuItem::Popup("Open", { 435 | {"Game", [](){}}, 436 | {"Level", [this](){ 437 | auto f = ChooseFile({{"Scene files","scene"}}, true); 438 | if(f.empty()) return; 439 | LoadScene(f); 440 | }} 441 | }), 442 | {"Save", [this](){ 443 | auto f = ChooseFile({{"Scene files","scene"}}, false); 444 | if(!f.empty()) std::ofstream(f, std::ofstream::binary) << tabbed(SerializeToJson(scene), 4); 445 | }, GLFW_MOD_CONTROL, GLFW_KEY_S}, 446 | {"Exit", [this]() { quit = true; }, GLFW_MOD_ALT, GLFW_KEY_F4} 447 | }), 448 | gui::MenuItem::Popup("Edit", { 449 | {"Cut", [](){}, GLFW_MOD_CONTROL, GLFW_KEY_X}, 450 | {"Copy", [](){}, GLFW_MOD_CONTROL, GLFW_KEY_C}, 451 | {"Paste", [](){}, GLFW_MOD_CONTROL, GLFW_KEY_V} 452 | }), 453 | gui::MenuItem::Popup("Object", { 454 | {"New", [this]() { 455 | scene.CreateObject("New Object", {0,0,0}, {0.5f,0.5f,0.5f}, assets.GetAsset("cube"), assets.GetAsset("diffuse"), {1,1,1}); 456 | RefreshObjectList(); 457 | }}, 458 | {"Duplicate", [this]() { 459 | auto obj = scene.DuplicateObject(*selection.object.lock()); 460 | RefreshObjectList(); 461 | selection.SetSelection(obj); 462 | }, GLFW_MOD_CONTROL, GLFW_KEY_D}, 463 | {"Delete", [this]() { 464 | scene.DeleteObject(selection.object.lock()); 465 | RefreshObjectList(); 466 | }, 0, GLFW_KEY_DELETE}, 467 | gui::MenuItem::Popup("Components", { 468 | {"Add Light", [this]() { 469 | if(auto obj = selection.object.lock()) 470 | { 471 | obj->light = std::make_unique(); 472 | } 473 | }} 474 | }) 475 | }) 476 | }); 477 | } 478 | 479 | void Editor::RefreshObjectList() 480 | { 481 | // TODO: Back up and restore selection 482 | objectList = std::make_shared(font, 2); 483 | for(auto & obj : scene.objects) objectList->AddItem(obj->name); 484 | auto it = std::find(begin(scene.objects), end(scene.objects), selection.object.lock()); 485 | objectList->SetSelectedIndex(it != end(scene.objects) ? it - begin(scene.objects) : -1); 486 | objectListPanel->children = {{{{0,0},{0,0},{1,0},{1,0}}, objectList}}; 487 | objectList->onSelectionChanged = [this]() { if(objectList->GetSelectedIndex() >= 0) selection.SetSelection(scene.objects[objectList->GetSelectedIndex()]); else selection.Deselect(); }; 488 | RefreshPropertyPanel(); 489 | } 490 | 491 | void Editor::RefreshPropertyPanel() 492 | { 493 | propertyPanel->children.clear(); 494 | 495 | if(auto obj = selection.object.lock()) 496 | { 497 | std::vector> props; 498 | props.push_back({"Name", factory.MakeEdit(obj->name, [this](const std::string & text) 499 | { 500 | int selectedIndex = objectList->GetSelectedIndex(); 501 | scene.objects[selectedIndex]->name = text; 502 | objectList->SetItemText(selectedIndex, text); 503 | })}); 504 | props.push_back({"Position", factory.MakeVectorEdit(obj->pose.position)}); 505 | props.push_back({"Orientation", factory.MakeVectorEdit(obj->pose.orientation)}); 506 | props.push_back({"Scale", factory.MakeVectorEdit(obj->localScale)}); 507 | props.push_back({"Mesh", factory.MakeAssetHandleEdit(assets, obj->mesh)}); 508 | props.push_back({"Program", factory.MakeAssetHandleEdit(assets, obj->prog)}); 509 | props.push_back({"Diffuse Color", factory.MakeVectorEdit(obj->color)}); 510 | auto pmap = factory.MakePropertyMap(props); 511 | float y0 = pmap->GetMinimumSize().y; 512 | propertyPanel->AddChild({{0,0},{0,0},{1,0},{0,y0}}, pmap); 513 | 514 | if(obj->light) 515 | { 516 | props.clear(); 517 | props.push_back({"Emissive Color", factory.MakeVectorEdit(obj->light->color)}); 518 | pmap = gui::Border::CreateBigBorder(factory.MakePropertyMap(props)); 519 | 520 | y0 += 8; 521 | auto y1 = y0+font.GetLineHeight(); 522 | propertyPanel->AddChild({{0,0},{0,y0},{1,0},{0,y1}}, factory.MakeLabel("Light Component:")); 523 | y0 = y1; 524 | y1 += pmap->GetMinimumSize().y; 525 | propertyPanel->AddChild({{0,0},{0,y0},{1,0},{0,y1}}, pmap); 526 | } 527 | } 528 | 529 | window.RefreshLayout(); 530 | } -------------------------------------------------------------------------------- /src/editor/editor.h: -------------------------------------------------------------------------------- 1 | #include "window.h" 2 | #include "widgets.h" 3 | #include "xplat.h" 4 | #include "scene.h" 5 | 6 | struct Selection 7 | { 8 | std::weak_ptr object; 9 | ProgramHandle selectionProgram; 10 | 11 | Mesh arrowMesh, circleMesh, scaleMesh; 12 | ProgramHandle arrowProg; 13 | std::function onSelectionChanged; 14 | 15 | Selection(); 16 | 17 | void Deselect(); 18 | void SetSelection(std::shared_ptr object); 19 | }; 20 | 21 | struct View : public gui::Element 22 | { 23 | enum Mode { Translation, Rotation, Scaling }; 24 | 25 | Scene & scene; 26 | Selection & selection; 27 | Pose viewpoint; 28 | float yaw=0,pitch=0; 29 | bool bf=0,bl=0,bb=0,br=0; 30 | Mode mode=Translation; 31 | 32 | const Mesh & GetGizmoMesh() const; 33 | 34 | View(Scene & scene, Selection & selection); 35 | 36 | bool OnKey(GLFWwindow * window, int key, int action, int mods) override; 37 | void OnUpdate(float timestep); 38 | void OnDrag(const int2 & delta); 39 | 40 | NVGcolor OnDrawBackground(const gui::DrawEvent & e) const override; 41 | gui::DraggerPtr OnClick(const gui::MouseEvent & e) override; 42 | }; 43 | 44 | class Editor 45 | { 46 | Window window; 47 | AssetLibrary assets; 48 | Font font; 49 | GuiFactory factory; 50 | std::shared_ptr objectList; 51 | std::shared_ptr docker; 52 | gui::ElementPtr objectListPanel; 53 | gui::ElementPtr propertyPanel; 54 | 55 | Scene scene; 56 | Selection selection; 57 | std::shared_ptr view; 58 | 59 | bool quit; 60 | 61 | void LoadScene(const std::string & filepath); 62 | void RefreshMenu(); 63 | void RefreshObjectList(); 64 | void RefreshPropertyPanel(); 65 | public: 66 | Editor(); 67 | 68 | int Run(); 69 | }; -------------------------------------------------------------------------------- /src/editor/gui.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_GUI_H 2 | #define EDITOR_GUI_H 3 | 4 | #include "engine/linalg.h" 5 | #include "engine/gl.h" 6 | #include "nanovg.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class Font; 15 | 16 | namespace gl { class Texture; } 17 | 18 | struct GLFWwindow; 19 | struct NVGcontext; 20 | 21 | class Window; 22 | 23 | namespace gui 24 | { 25 | struct IDragger 26 | { 27 | virtual void OnDrag(int2 newMouse) = 0; 28 | virtual bool OnKey(int key, int action, int mods) { return false; } 29 | virtual void OnRelease() = 0; 30 | virtual void OnCancel() = 0; 31 | }; 32 | typedef std::shared_ptr DraggerPtr; 33 | 34 | enum class Cursor { Arrow, IBeam, SizeNS, SizeWE }; 35 | 36 | struct Rect 37 | { 38 | int x0,y0,x1,y1; 39 | float GetCenterX() const { return (x0+x1)*0.5f; } 40 | float GetCenterY() const { return (y0+y1)*0.5f; } 41 | int GetWidth() const { return x1-x0; } 42 | int GetHeight() const { return y1-y0; } 43 | float GetAspect() const { return (float)GetWidth()/GetHeight(); } 44 | }; 45 | 46 | struct UCoord 47 | { 48 | float a,b; 49 | int resolve(int lo, int hi) const { return lo + static_cast(round(a*(hi-lo) + b)); } 50 | }; 51 | 52 | struct URect 53 | { 54 | UCoord x0,y0,x1,y1; 55 | Rect resolve(Rect r) const { return {x0.resolve(r.x0,r.x1), y0.resolve(r.y0,r.y1), x1.resolve(r.x0,r.x1), y1.resolve(r.y0,r.y1)}; } 56 | }; 57 | 58 | struct Child; 59 | 60 | struct MouseEvent 61 | { 62 | int2 cursor; 63 | int button; 64 | int mods; 65 | 66 | bool IsShiftHeld() const { return !!(mods & GLFW_MOD_SHIFT); } 67 | bool IsControlHeld() const { return !!(mods & GLFW_MOD_CONTROL); } 68 | bool IsAltHeld() const { return !!(mods & GLFW_MOD_ALT); } 69 | }; 70 | 71 | struct DrawEvent 72 | { 73 | NVGcontext * vg; // Current NanoVG context, can be used for drawing 74 | NVGcolor parent; // NanoVG color of parent element, can be used to draw masks/fades over top of current element 75 | bool hasFocus; // True if this was the last element the user clicked on 76 | bool isPressed; // True if the user clicked on this element and has not yet released the mouse 77 | bool isMouseOver; // True if the mouse is presently over this element 78 | }; 79 | 80 | typedef std::shared_ptr ElementPtr; 81 | 82 | struct Element 83 | { 84 | struct Child 85 | { 86 | URect placement; 87 | ElementPtr element; 88 | }; 89 | 90 | Rect rect; 91 | std::vector children; 92 | bool isVisible; 93 | bool isTransparent; // If true, this element will ignore mouse events 94 | int2 minimumSize; // Minimum size that this element should occupy, independent of children 95 | 96 | Element(); 97 | 98 | int2 GetMinimumSize() const; // Compute minimum size for this element, inclusive of children 99 | 100 | void AddChild(const URect & placement, ElementPtr child); 101 | void SetRect(const Rect & rect); 102 | 103 | virtual bool IsTabStop() const { return false; } 104 | virtual Cursor GetCursor() const { return Cursor::Arrow; } 105 | virtual NVGcolor OnDrawBackground(const DrawEvent & e) const { return e.parent; } // Draw contents before children 106 | virtual void OnDrawForeground(const DrawEvent & e) const {} // Draw contents after children 107 | 108 | virtual void OnChar(uint32_t codepoint) {} 109 | virtual bool OnKey(GLFWwindow * window, int key, int action, int mods) { return false; } 110 | virtual DraggerPtr OnClick(const MouseEvent & e) { return nullptr; } // If a dragger is returned, it will take focus until user releases mouse or hits "escape" 111 | virtual void OnTab() {} 112 | }; 113 | 114 | // Element which fills its client area with a solid color 115 | struct Fill : public gui::Element 116 | { 117 | NVGcolor color; 118 | Fill(NVGcolor color) : color(color) {} 119 | NVGcolor OnDrawBackground(const gui::DrawEvent & e) const override; 120 | }; 121 | 122 | // Element providing selectable, editable text 123 | class Text : public gui::Element 124 | { 125 | const Font & font; 126 | size_t cursor, mark; 127 | bool isSelecting; 128 | 129 | const char * GetFocusText() const { return text.data(); } 130 | size_t GetFocusTextSize() const { return text.size(); } 131 | public: 132 | std::string text; 133 | bool isEditable; 134 | NVGcolor color; 135 | 136 | Text(const Font & font) : font(font), cursor(), mark(), isSelecting(), isEditable(), color(nvgRGBA(255,255,255,255)) {} 137 | 138 | size_t GetSelectionLeftIndex() const { return std::min(cursor, mark); } 139 | size_t GetSelectionRightIndex() const { return std::max(cursor, mark); } 140 | std::string GetSelectionText() const { return std::string(text.c_str() + GetSelectionLeftIndex(), text.c_str() + GetSelectionRightIndex()); } 141 | 142 | void SelectAll(); 143 | void MoveSelectionCursor(int newCursor, bool holdingShift); 144 | void RemoveSelection(); 145 | void Insert(const char * string); 146 | 147 | bool IsTabStop() const override { return isEditable; } 148 | gui::Cursor GetCursor() const override { return isEditable ? gui::Cursor::IBeam : gui::Cursor::Arrow; } 149 | 150 | void OnChar(uint32_t codepoint) override; 151 | bool OnKey(GLFWwindow * window, int key, int action, int mods) override; 152 | gui::DraggerPtr OnClick(const gui::MouseEvent & e) override; 153 | void OnTab() override { SelectAll(); } 154 | NVGcolor OnDrawBackground(const gui::DrawEvent & e) const override; 155 | 156 | std::function onEdit; 157 | }; 158 | 159 | // User-draggable boundary between two child regions 160 | class Splitter : public gui::Element 161 | { 162 | public: 163 | enum Side { Left, Top, Right, Bottom }; 164 | 165 | Splitter(gui::ElementPtr panelA, gui::ElementPtr panelB, Side sideB, int pixelsB); 166 | }; 167 | 168 | // Decoration around a client area 169 | class Border : public gui::Element 170 | { 171 | int size; // Size of border region in pixels 172 | float offset, width, radius; // Placement of stroked border 173 | NVGcolor border, background; // Color of border, and internal fill 174 | public: 175 | Border(int size, float offset, float width, float radius, NVGcolor border, NVGcolor background, gui::ElementPtr inner); 176 | 177 | NVGcolor OnDrawBackground(const gui::DrawEvent & e) const override; 178 | void OnDrawForeground(const gui::DrawEvent & e) const override; 179 | 180 | static std::shared_ptr CreateBigBorder(gui::ElementPtr inner); 181 | static std::shared_ptr CreateEditBorder(gui::ElementPtr inner); 182 | }; 183 | 184 | // List box, allowing for user selection 185 | class ListBox : public gui::Element 186 | { 187 | const Font & font; 188 | int spacing, selectedIndex; 189 | public: 190 | ListBox(const Font & font, int spacing) : font(font), spacing(spacing), selectedIndex(-1) {} 191 | 192 | int GetSelectedIndex() const { return selectedIndex; } 193 | 194 | void SetSelectedIndex(int index); 195 | void SetItemText(int index, const std::string & text); 196 | void AddItem(const std::string & text); 197 | 198 | std::function onSelectionChanged; 199 | }; 200 | 201 | struct MenuItem 202 | { 203 | // TODO: Icon, hotkeys, etc 204 | bool isEnabled; // If true, this menu item can be clicked on 205 | std::string label; // The string that should be displayed for this item 206 | std::vector children; // If nonempty, clicking on this item will open a popup menu 207 | std::function onClick; // If bound, clicking on this item will invoke this function 208 | int hotKeyMods, hotKey; 209 | 210 | MenuItem() : isEnabled(true), hotKeyMods(), hotKey() {} 211 | MenuItem(const std::string & label, std::function onClick, int hotKeyMods=0, int hotKey=0) : isEnabled(true), label(label), onClick(onClick), hotKeyMods(hotKeyMods), hotKey(hotKey) {} 212 | 213 | static MenuItem Popup(std::string label, std::vector children) { auto r = MenuItem(); r.label = move(label); r.children = move(children); return r; } 214 | }; 215 | 216 | // Supports a menu bar with drop-down menus 217 | class Menu : public gui::Element 218 | { 219 | class Barrier; 220 | std::shared_ptr barrier; 221 | std::weak_ptr MakePopup(size_t level, const Font & font, const std::vector & items, float x, float y); 222 | public: 223 | Menu(gui::ElementPtr inner, const Font & font, const std::vector & items); 224 | }; 225 | 226 | // Allow for named panels to be "docked", and rearranged by the user 227 | class DockingContainer : public gui::Element 228 | { 229 | struct PanelState 230 | { 231 | ElementPtr panel, content; 232 | std::shared_ptr window; // If empty, panel is currently docked 233 | }; 234 | 235 | Window & mainWindow; 236 | const Font & font; 237 | std::vector panels; 238 | 239 | /*std::vector> tornWindows; 240 | std::vector dockedPanels;*/ 241 | 242 | bool DockElement(ElementPtr & candidate, Element & parent, const std::string & panelTitle, ElementPtr element, Splitter::Side side, int pixels); 243 | public: 244 | DockingContainer(Window & mainWindow, const Font & font) : mainWindow(mainWindow), font(font) {} 245 | 246 | void PreviewDockAtScreenCoords(const int2 & point); 247 | void PreviewDockAtWindowCoords(const int2 & point); 248 | void CancelPreview(); 249 | 250 | void RedrawAll(); 251 | 252 | void SetPrimaryElement(ElementPtr element); 253 | void Dock(Element & parent, const std::string & panelTitle, ElementPtr element, Splitter::Side side, int pixels); 254 | 255 | void DockAtScreenCoords(const std::string & title, ElementPtr element, const int2 & coords); 256 | void DockAtWindowCoords(const std::string & title, ElementPtr element, const int2 & coords); 257 | 258 | std::shared_ptr Tear(Element & element); 259 | }; 260 | } 261 | 262 | #endif -------------------------------------------------------------------------------- /src/editor/main.cpp: -------------------------------------------------------------------------------- 1 | #include "editor.h" 2 | 3 | #include 4 | 5 | int main(int argc, char * argv[]) 6 | { 7 | glfwInit(); 8 | int result = -1; 9 | try { result = Editor().Run(); } 10 | catch(const std::exception & e) { std::cerr << "Unhandled exception caught: " << e.what() << std::endl; } 11 | catch(...) { std::cerr << "Unknown unhandled exception caught." << std::endl; } 12 | glfwTerminate(); 13 | return result; 14 | } -------------------------------------------------------------------------------- /src/editor/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.h" 2 | 3 | #include 4 | 5 | void LightEnvironment::Bind(gl::Buffer & buffer, const gl::BlockDesc & perScene) const 6 | { 7 | std::vector dataBuffer(perScene.dataSize); 8 | for(size_t i=0; iGetNamedBlock("PerObject")) 40 | { 41 | std::vector data(b->dataSize); 42 | b->SetUniform(data.data(), "u_model", model); 43 | b->SetUniform(data.data(), "u_modelIT", inv(transpose(model))); 44 | b->SetUniform(data.data(), "u_diffuse", color); 45 | if(light) b->SetUniform(data.data(), "u_emissive", light->color); 46 | buf.SetData(GL_UNIFORM_BUFFER, data.size(), data.data(), GL_STREAM_DRAW); 47 | buf.BindBase(GL_UNIFORM_BUFFER, b->binding); 48 | } 49 | 50 | prog->Use(); 51 | mesh->Draw(); 52 | } 53 | 54 | std::shared_ptr Scene::Hit(const Ray & ray) 55 | { 56 | std::shared_ptr best = nullptr; 57 | float bestT = 0; 58 | for(auto & obj : objects) 59 | { 60 | auto hit = obj->Hit(ray); 61 | if(hit.hit && (!best || hit.t < bestT)) 62 | { 63 | best = obj; 64 | bestT = hit.t; 65 | } 66 | } 67 | return best; 68 | } 69 | 70 | void Scene::Draw(RenderContext & ctx) 71 | { 72 | LightEnvironment lights; 73 | for(auto & obj : objects) if(obj->light) lights.lights.push_back({obj->pose.position, obj->light->color}); 74 | if(!objects.empty()) lights.Bind(ctx.perScene, *objects[0]->prog->GetNamedBlock("PerScene")); 75 | 76 | for(auto & obj : objects) obj->Draw(); 77 | } -------------------------------------------------------------------------------- /src/editor/scene.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_SCENE_H 2 | #define EDITOR_SCENE_H 3 | 4 | #include "engine/asset.h" 5 | #include "engine/load.h" 6 | #include "engine/pack.h" 7 | 8 | typedef AssetLibrary::Handle MeshHandle; 9 | typedef AssetLibrary::Handle ProgramHandle; 10 | 11 | struct PointLight { float3 position, color; }; 12 | struct LightEnvironment 13 | { 14 | std::vector lights; 15 | void Bind(gl::Buffer & buffer, const gl::BlockDesc & perScene) const; 16 | }; 17 | 18 | struct LightComponent { float3 color; }; 19 | template void VisitFields(LightComponent & o, F f) { f("color", o.color); } 20 | 21 | struct Object 22 | { 23 | std::string name; 24 | Pose pose; 25 | float3 localScale = float3(1,1,1); 26 | 27 | float3 color; 28 | MeshHandle mesh; 29 | ProgramHandle prog; 30 | 31 | std::unique_ptr light; 32 | 33 | Object() {} 34 | Object(const Object & r) : name(r.name), pose(r.pose), localScale(r.localScale), color(r.color), mesh(r.mesh), prog(r.prog), light(r.light ? std::make_unique(*r.light) : nullptr) {} 35 | 36 | RayMeshHit Hit(const Ray & ray) const 37 | { 38 | if(!mesh) return RayMeshHit({false}, 0); 39 | auto localRay = pose.Inverse() * ray; 40 | localRay.start /= localScale; 41 | localRay.direction /= localScale; 42 | return mesh->Hit(localRay); 43 | } 44 | 45 | void Draw(); 46 | }; 47 | template void VisitFields(Object & o, F f) { f("name", o.name); f("pose", o.pose); f("scale", o.localScale); f("diffuse", o.color); f("mesh", o.mesh); f("prog", o.prog); f("light", o.light); } 48 | 49 | struct RenderContext 50 | { 51 | gl::Buffer perScene; 52 | }; 53 | 54 | struct Scene 55 | { 56 | std::vector> objects; 57 | 58 | std::shared_ptr Hit(const Ray & ray); 59 | 60 | void Draw(RenderContext & ctx); 61 | 62 | std::shared_ptr CreateObject(std::string name, const float3 & position, const float3 & scale, MeshHandle mesh, ProgramHandle prog, const float3 & diffuseColor) 63 | { 64 | auto obj = std::make_shared(); 65 | obj->name = name; 66 | obj->pose.position = position; 67 | obj->pose.orientation = {0,0,0,1}; 68 | obj->localScale = scale; 69 | obj->mesh = mesh; 70 | obj->prog = prog; 71 | obj->color = diffuseColor; 72 | objects.push_back(obj); 73 | return obj; 74 | } 75 | 76 | std::shared_ptr DuplicateObject(const Object & original) 77 | { 78 | auto obj = std::make_shared(original); 79 | objects.push_back(obj); 80 | return obj; 81 | } 82 | 83 | void DeleteObject(std::shared_ptr object) 84 | { 85 | auto it = std::find(begin(objects), end(objects), object); 86 | if(it != end(objects)) objects.erase(it); 87 | } 88 | }; 89 | template void VisitFields(Scene & o, F f) { f("objects", o.objects); } 90 | 91 | #endif -------------------------------------------------------------------------------- /src/editor/widgets.cpp: -------------------------------------------------------------------------------- 1 | #include "widgets.h" 2 | 3 | #include 4 | 5 | -------------------------------------------------------------------------------- /src/editor/widgets.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_WIDGETS_H 2 | #define EDITOR_WIDGETS_H 3 | 4 | #include "gui.h" 5 | #include "engine/font.h" 6 | #include "engine/asset.h" 7 | #include 8 | 9 | class GuiFactory 10 | { 11 | const Font & font; 12 | int spacing, editBorder; 13 | public: 14 | GuiFactory(const Font & font, int spacing) : font(font), spacing(spacing), editBorder(2) {} 15 | 16 | gui::ElementPtr MakeLabel(const std::string & text) const 17 | { 18 | auto elem = std::make_shared(font); 19 | elem->text = text; 20 | elem->minimumSize = {font.GetStringWidth(text), font.GetLineHeight()}; 21 | return elem; 22 | } 23 | 24 | gui::ElementPtr MakePropertyMap(std::vector> properties) const 25 | { 26 | float y0 = 0; 27 | auto labelPanel = std::make_shared(), valuePanel = std::make_shared(); 28 | valuePanel->minimumSize.x = 128; 29 | for(auto & pair : properties) 30 | { 31 | float y1 = y0 + editBorder, y2 = y1 + font.GetLineHeight(), y3 = y2 + editBorder; 32 | labelPanel->children.push_back({{{0,0},{0,y1},{1,0},{0,y2}}, MakeLabel(pair.first)}); 33 | valuePanel->children.push_back({{{0,0},{0,y0},{1,0},{0,y3}}, pair.second}); 34 | y0 = y3 + spacing; 35 | } 36 | return std::make_shared(valuePanel, labelPanel, gui::Splitter::Left, labelPanel->GetMinimumSize().x); 37 | } 38 | 39 | gui::ElementPtr MakeEdit(const std::string & text, std::function onEdit={}) const 40 | { 41 | auto elem = std::make_shared(font); 42 | elem->text = text; 43 | elem->isEditable = true; 44 | elem->onEdit = onEdit; 45 | return gui::Border::CreateEditBorder(elem); 46 | } 47 | gui::ElementPtr MakeStringEdit(std::string & value) const 48 | { 49 | std::ostringstream ss; 50 | ss << value; 51 | return MakeEdit(ss.str(), [&value](const std::string & text) { value = text; }); 52 | } 53 | gui::ElementPtr MakeFloatEdit(float & value) const 54 | { 55 | std::ostringstream ss; 56 | ss << value; 57 | return MakeEdit(ss.str(), [&value](const std::string & text) { std::istringstream(text) >> value; }); 58 | } 59 | gui::ElementPtr MakeVectorEdit(float3 & value) const 60 | { 61 | auto panel = std::make_shared(); 62 | panel->children.push_back({{{0.0f/3, 0},{0,0},{1.0f/3,-spacing*2.0f/3},{1,0}},MakeFloatEdit(value.x)}); 63 | panel->children.push_back({{{1.0f/3,+spacing*1.0f/3},{0,0},{2.0f/3,-spacing*1.0f/3},{1,0}},MakeFloatEdit(value.y)}); 64 | panel->children.push_back({{{2.0f/3,+spacing*2.0f/3},{0,0},{3.0f/3, 0},{1,0}},MakeFloatEdit(value.z)}); 65 | return panel; 66 | } 67 | gui::ElementPtr MakeVectorEdit(float4 & value) const 68 | { 69 | auto panel = std::make_shared(); 70 | panel->children.push_back({{{0.0f/4, 0},{0,0},{1.0f/4,-spacing*3.0f/4},{1,0}},MakeFloatEdit(value.x)}); 71 | panel->children.push_back({{{1.0f/4,+spacing*1.0f/4},{0,0},{2.0f/4,-spacing*2.0f/4},{1,0}},MakeFloatEdit(value.y)}); 72 | panel->children.push_back({{{2.0f/4,+spacing*2.0f/4},{0,0},{3.0f/4,-spacing*1.0f/4},{1,0}},MakeFloatEdit(value.z)}); 73 | panel->children.push_back({{{3.0f/4,+spacing*3.0f/4},{0,0},{4.0f/4, 0},{1,0}},MakeFloatEdit(value.w)}); 74 | return panel; 75 | } 76 | template gui::ElementPtr MakeAssetHandleEdit(AssetLibrary & assets, AssetLibrary::Handle & value) const 77 | { 78 | return MakeEdit(value ? value.GetId() : "{None}", [&assets, &value](const std::string & text) 79 | { 80 | value = assets.GetAsset(text); 81 | }); 82 | } 83 | }; 84 | 85 | #endif -------------------------------------------------------------------------------- /src/editor/window.cpp: -------------------------------------------------------------------------------- 1 | #include "window.h" 2 | #include "widgets.h" 3 | #include "engine/gl.h" 4 | 5 | #include "nanovg.h" 6 | #define NANOVG_GL3_IMPLEMENTATION 7 | #include "nanovg_gl.h" 8 | 9 | gui::ElementPtr GetElement(const gui::ElementPtr & element, int x, int y) 10 | { 11 | if(!element->isVisible || element->isTransparent) return nullptr; 12 | 13 | for(auto it = element->children.rbegin(), end = element->children.rend(); it != end; ++it) 14 | { 15 | if(auto elem = GetElement(it->element, x, y)) 16 | { 17 | return elem; 18 | } 19 | } 20 | 21 | if(x >= element->rect.x0 && y >= element->rect.y0 && x < element->rect.x1 && y < element->rect.y1) 22 | { 23 | return element; 24 | } 25 | 26 | return nullptr; 27 | } 28 | 29 | void Window::CancelDrag() 30 | { 31 | if(dragger) 32 | { 33 | dragger->OnCancel(); 34 | dragger = nullptr; 35 | } 36 | } 37 | 38 | void Window::TabTo(gui::ElementPtr element) 39 | { 40 | CancelDrag(); 41 | 42 | focus = element; 43 | if(focus) focus->OnTab(); 44 | } 45 | 46 | Window::Window(const char * title, int width, int height, const Window * parent, int2 pos) : window(), width(width), height(height), focus() 47 | { 48 | window = glfwCreateWindow(width, height, title, nullptr, parent ? parent->context->mainWindow : nullptr); 49 | glfwSetWindowUserPointer(window, this); 50 | 51 | int2 newPos; 52 | glfwGetWindowPos(window, &newPos.x, &newPos.y); 53 | if(pos.x >= 0) newPos.x = pos.x; 54 | if(pos.y >= 0) newPos.y = pos.y; 55 | glfwSetWindowPos(window, newPos.x, newPos.y); 56 | 57 | context = parent ? parent->context : std::make_shared(window); 58 | 59 | glfwSetWindowSizeCallback(window, [](GLFWwindow * window, int width, int height) 60 | { 61 | reinterpret_cast(glfwGetWindowUserPointer(window))->RefreshLayout(); 62 | }); 63 | 64 | glfwSetWindowRefreshCallback(window, [](GLFWwindow * window) 65 | { 66 | reinterpret_cast(glfwGetWindowUserPointer(window))->Redraw(); 67 | }); 68 | 69 | glfwSetCharCallback(window, [](GLFWwindow * window, unsigned int codepoint) 70 | { 71 | auto w = reinterpret_cast(glfwGetWindowUserPointer(window)); 72 | if(w->focus) w->focus->OnChar(codepoint); 73 | }); 74 | 75 | glfwSetMouseButtonCallback(window, [](GLFWwindow * window, int button, int action, int mods) 76 | { 77 | double xpos, ypos; 78 | glfwGetCursorPos(window, &xpos, &ypos); 79 | 80 | auto w = reinterpret_cast(glfwGetWindowUserPointer(window)); 81 | if(action == GLFW_PRESS) 82 | { 83 | w->CancelDrag(); 84 | 85 | w->focus = w->mouseover; 86 | if(w->focus) w->dragger = w->focus->OnClick({{(int)xpos, (int)ypos}, button, mods}); 87 | } 88 | if(action == GLFW_RELEASE) 89 | { 90 | if(w->dragger) 91 | { 92 | w->dragger->OnRelease(); 93 | w->dragger.reset(); 94 | } 95 | 96 | w->mouseover = GetElement(w->root, (int)xpos, (int)ypos); 97 | glfwSetCursor(window, w->context->cursors[(int)(w->mouseover ? w->mouseover->GetCursor() : gui::Cursor::Arrow)]); 98 | } 99 | }); 100 | 101 | glfwSetCursorPosCallback(window, [](GLFWwindow * window, double cx, double cy) 102 | { 103 | int x = static_cast(cx), y = static_cast(cy); 104 | auto w = reinterpret_cast(glfwGetWindowUserPointer(window)); 105 | if(w->dragger) w->dragger->OnDrag(int2(cx,cy)); 106 | else 107 | { 108 | w->mouseover = GetElement(w->root, x, y); 109 | glfwSetCursor(window, w->context->cursors[(int)(w->mouseover ? w->mouseover->GetCursor() : gui::Cursor::Arrow)]); 110 | } 111 | w->lastX = x; 112 | w->lastY = y; 113 | }); 114 | 115 | glfwSetKeyCallback(window, [](GLFWwindow * window, int key, int scancode, int action, int mods) 116 | { 117 | auto w = reinterpret_cast(glfwGetWindowUserPointer(window)); 118 | 119 | // Handle certain global UI keys 120 | if(action != GLFW_RELEASE) switch(key) 121 | { 122 | case GLFW_KEY_ESCAPE: // Escape cancels the current dragger 123 | w->CancelDrag(); 124 | return; 125 | case GLFW_KEY_TAB: // Tab iterates through editable controls 126 | if(w->focus) 127 | { 128 | auto focusIt = std::find(begin(w->tabStops), end(w->tabStops), w->focus); 129 | if(focusIt == end(w->tabStops) || ++focusIt == end(w->tabStops)) w->TabTo(nullptr); 130 | else w->TabTo(*focusIt); 131 | } 132 | if(!w->focus && !w->tabStops.empty()) w->TabTo(w->tabStops.front()); 133 | return; 134 | } 135 | 136 | if(w->dragger && w->dragger->OnKey(key, action, mods)) return; // If one is present, a dragger can consume keystrokes 137 | if(w->focus && w->focus->OnKey(window, key, action, mods)) return; // If an element is focused, it can consume keystrokes 138 | 139 | // Remaining keys can be consumed by global shortcuts 140 | if(action == GLFW_PRESS) 141 | { 142 | for(auto & shortcut : w->shortcuts) 143 | { 144 | if(key == shortcut.key && mods == shortcut.mods) 145 | { 146 | shortcut.onInvoke(); 147 | return; 148 | } 149 | } 150 | } 151 | }); 152 | } 153 | 154 | Context::Context() { memset(this, 0, sizeof(this)); } 155 | 156 | Context::Context(GLFWwindow * mainWindow) : Context() 157 | { 158 | this->mainWindow = mainWindow; 159 | 160 | struct Color { uint8_t r,g,b,a; } W{255,255,255,255}, B{0,0,0,255}, _{0,0,0,0}; 161 | Color arrow[] = { 162 | B,_,_,_,_,_,_,_,_,_,_,_, 163 | B,B,_,_,_,_,_,_,_,_,_,_, 164 | B,W,B,_,_,_,_,_,_,_,_,_, 165 | B,W,W,B,_,_,_,_,_,_,_,_, 166 | B,W,W,W,B,_,_,_,_,_,_,_, 167 | B,W,W,W,W,B,_,_,_,_,_,_, 168 | B,W,W,W,W,W,B,_,_,_,_,_, 169 | B,W,W,W,W,W,W,B,_,_,_,_, 170 | B,W,W,W,W,W,W,W,B,_,_,_, 171 | B,W,W,W,W,W,W,W,W,B,_,_, 172 | B,W,W,W,W,W,W,W,W,W,B,_, 173 | B,W,W,W,W,W,W,W,W,W,W,B, 174 | B,W,W,W,W,W,W,B,B,B,B,B, 175 | B,W,W,W,B,W,W,B,_,_,_,_, 176 | B,W,W,B,_,B,W,W,B,_,_,_, 177 | B,W,B,_,_,B,W,W,B,_,_,_, 178 | B,B,_,_,_,_,B,W,W,B,_,_, 179 | _,_,_,_,_,_,B,W,W,B,_,_, 180 | _,_,_,_,_,_,_,B,B,_,_,_, 181 | }, ibeam[] = { 182 | B,B,B,_,B,B,B, 183 | _,_,_,B,_,_,_, 184 | _,_,_,B,_,_,_, 185 | _,_,_,B,_,_,_, 186 | _,_,_,B,_,_,_, 187 | _,_,_,B,_,_,_, 188 | _,_,_,B,_,_,_, 189 | _,_,_,B,_,_,_, 190 | _,_,_,B,_,_,_, 191 | _,_,_,B,_,_,_, 192 | _,_,_,B,_,_,_, 193 | _,_,_,B,_,_,_, 194 | _,_,_,B,_,_,_, 195 | _,_,_,B,_,_,_, 196 | _,_,_,B,_,_,_, 197 | B,B,B,_,B,B,B, 198 | }, sizens[] = { 199 | _,_,_,_,B,_,_,_,_, 200 | _,_,_,B,W,B,_,_,_, 201 | _,_,B,W,W,W,B,_,_, 202 | _,B,W,W,W,W,W,B,_, 203 | B,W,W,W,W,W,W,W,B, 204 | B,B,B,B,W,B,B,B,B, 205 | _,_,_,B,W,B,_,_,_, 206 | _,_,_,B,W,B,_,_,_, 207 | _,_,_,B,W,B,_,_,_, 208 | _,_,_,B,W,B,_,_,_, 209 | _,_,_,B,W,B,_,_,_, 210 | _,_,_,B,W,B,_,_,_, 211 | _,_,_,B,W,B,_,_,_, 212 | _,_,_,B,W,B,_,_,_, 213 | _,_,_,B,W,B,_,_,_, 214 | _,_,_,B,W,B,_,_,_, 215 | _,_,_,B,W,B,_,_,_, 216 | B,B,B,B,W,B,B,B,B, 217 | B,W,W,W,W,W,W,W,B, 218 | _,B,W,W,W,W,W,B,_, 219 | _,_,B,W,W,W,B,_,_, 220 | _,_,_,B,W,B,_,_,_, 221 | _,_,_,_,B,_,_,_,_, 222 | }, sizewe[] = { 223 | _,_,_,_,B,B,_,_,_,_,_,_,_,_,_,_,_,B,B,_,_,_,_, 224 | _,_,_,B,W,B,_,_,_,_,_,_,_,_,_,_,_,B,W,B,_,_,_, 225 | _,_,B,W,W,B,_,_,_,_,_,_,_,_,_,_,_,B,W,W,B,_,_, 226 | _,B,W,W,W,B,B,B,B,B,B,B,B,B,B,B,B,B,W,W,W,B,_, 227 | B,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,W,B, 228 | _,B,W,W,W,B,B,B,B,B,B,B,B,B,B,B,B,B,W,W,W,B,_, 229 | _,_,B,W,W,B,_,_,_,_,_,_,_,_,_,_,_,B,W,W,B,_,_, 230 | _,_,_,B,W,B,_,_,_,_,_,_,_,_,_,_,_,B,W,B,_,_,_, 231 | _,_,_,_,B,B,_,_,_,_,_,_,_,_,_,_,_,B,B,_,_,_,_, 232 | }; 233 | GLFWimage image = {12,19,&arrow[0].r}; 234 | cursors[(int)gui::Cursor::Arrow] = glfwCreateCursor(&image, 0, 0); 235 | image = {7,16,&ibeam[0].r}; 236 | cursors[(int)gui::Cursor::IBeam] = glfwCreateCursor(&image, 3, 8); 237 | image = {9,23,&sizens[0].r}; 238 | cursors[(int)gui::Cursor::SizeNS] = glfwCreateCursor(&image, 4, 11); 239 | image = {23,9,&sizewe[0].r}; 240 | cursors[(int)gui::Cursor::SizeWE] = glfwCreateCursor(&image, 11, 4); 241 | 242 | glfwMakeContextCurrent(mainWindow); 243 | glewExperimental = GL_TRUE; 244 | if(glewInit() != GLEW_OK) throw std::runtime_error("Could not init glew"); 245 | 246 | vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); 247 | if (vg == NULL) throw std::runtime_error("Could not init nanovg"); 248 | } 249 | 250 | Context::~Context() 251 | { 252 | if(vg) nvgDeleteGL3(vg); 253 | for(auto cursor : cursors) if(cursor) glfwDestroyCursor(cursor); 254 | if(mainWindow) glfwDestroyWindow(mainWindow); 255 | } 256 | 257 | Window::~Window() 258 | { 259 | // If we are not the main window, destroy our GLFW window. 260 | if(window && window != context->mainWindow) 261 | { 262 | glfwDestroyWindow(window); 263 | } 264 | } 265 | 266 | static void CollectTabStops(std::vector & tabStops, const gui::ElementPtr & elem) 267 | { 268 | if(elem->IsTabStop()) tabStops.push_back(elem); 269 | for(auto & child : elem->children) CollectTabStops(tabStops, child.element); 270 | } 271 | 272 | void Window::RefreshLayout() 273 | { 274 | tabStops.clear(); 275 | glfwGetWindowSize(window, &width, &height); 276 | if(!root) return; 277 | root->SetRect({0,0,width,height}); 278 | CollectTabStops(tabStops, root); 279 | } 280 | 281 | void Window::GatherShortcuts(const gui::MenuItem & item) 282 | { 283 | if(item.hotKey) shortcuts.push_back({item.hotKeyMods, item.hotKey, item.onClick}); 284 | for(auto & child : item.children) GatherShortcuts(child); 285 | } 286 | 287 | void Window::SetGuiRoot(gui::ElementPtr element, const Font & menuFont, const std::vector & menuItems) 288 | { 289 | shortcuts.clear(); 290 | for(auto & item : menuItems) GatherShortcuts(item); 291 | root = menuItems.empty() ? element : std::make_shared(element, menuFont, menuItems); 292 | RefreshLayout(); 293 | } 294 | 295 | static void DrawElement(NVGcontext * vg, const gui::Element & elem, const gui::Element * mouseover, const gui::Element * focus, NVGcolor parentBackground) 296 | { 297 | if(!elem.isVisible) return; 298 | gui::DrawEvent e = {vg,parentBackground}; 299 | e.hasFocus = &elem == focus; 300 | e.isMouseOver = &elem == mouseover; 301 | 302 | nvgSave(vg); 303 | nvgScissor(vg, elem.rect.x0, elem.rect.y0, elem.rect.GetWidth(), elem.rect.GetHeight()); 304 | NVGcolor background = elem.OnDrawBackground(e); 305 | for(const auto & child : elem.children) DrawElement(vg, *child.element, mouseover, focus, background); 306 | elem.OnDrawForeground(e); 307 | nvgRestore(vg); 308 | } 309 | 310 | static void DrawElementBounds(NVGcontext * vg, const gui::Element & elem, const gui::Element * mouseover, const gui::Element * focus) 311 | { 312 | for(const auto & child : elem.children) DrawElementBounds(vg, *child.element, mouseover, focus); 313 | 314 | nvgBeginPath(vg); 315 | nvgRect(vg, elem.rect.x0+0.5f, elem.rect.y0+0.5f, elem.rect.GetWidth()-1.0f, elem.rect.GetHeight()-1.0f); 316 | if(&elem == focus) nvgStrokeColor(vg, nvgRGBAf(1,0,0,1)); 317 | else if(&elem == mouseover) nvgStrokeColor(vg, nvgRGBAf(1,1,0,1)); 318 | else nvgStrokeColor(vg, nvgRGBAf(1,1,1,1)); 319 | nvgStrokeWidth(vg, 1); 320 | nvgStroke(vg); 321 | } 322 | 323 | void Window::Redraw() 324 | { 325 | glfwMakeContextCurrent(window); 326 | glPushAttrib(GL_ALL_ATTRIB_BITS); 327 | glViewport(0, 0, width, height); 328 | glClearColor(0.125f,0.125f,0.125f,1); 329 | glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 330 | 331 | nvgBeginFrame(context->vg, width, height, 1.0f); 332 | 333 | 334 | DrawElement(context->vg, *root, mouseover.get(), focus.get(), nvgRGBA(0,0,0,0)); 335 | //DrawElementBounds(context->vg, *root, mouseover.get(), focus.get()); 336 | nvgEndFrame(context->vg); 337 | 338 | glPopAttrib(); 339 | 340 | glUseProgram(0); 341 | glActiveTexture(GL_TEXTURE0); 342 | glBindTexture(GL_TEXTURE_2D, 0); 343 | glBindBuffer(GL_UNIFORM_BUFFER, 0); 344 | glBindVertexArray(0); 345 | glBindBuffer(GL_ARRAY_BUFFER, 0); 346 | 347 | glfwSwapBuffers(window); 348 | } -------------------------------------------------------------------------------- /src/editor/window.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_WINDOW_H 2 | #define EDITOR_WINDOW_H 3 | 4 | #include "gui.h" 5 | #include "engine/font.h" 6 | #include "engine/utf8.h" 7 | 8 | #include 9 | #include 10 | 11 | struct NVGcontext; 12 | namespace gui { struct MenuItem; } 13 | 14 | struct Context 15 | { 16 | GLFWwindow * mainWindow; 17 | GLFWcursor * cursors[4]; 18 | NVGcontext * vg; 19 | 20 | Context(); 21 | Context(GLFWwindow * mainWindow); 22 | ~Context(); 23 | }; 24 | 25 | class Window 26 | { 27 | struct Shortcut { int mods, key; std::function onInvoke; }; 28 | 29 | // Core window state 30 | std::shared_ptr context; 31 | GLFWwindow * window; 32 | gui::ElementPtr root; 33 | std::vector shortcuts; 34 | 35 | // Layout, needs to be regenerated whenever window is resized or gui changes 36 | int width, height; 37 | std::vector tabStops; 38 | 39 | // Text manipulation logic 40 | gui::ElementPtr mouseover, focus; 41 | gui::DraggerPtr dragger; 42 | int lastX, lastY; 43 | 44 | void CancelDrag(); 45 | void TabTo(gui::ElementPtr element); 46 | void GatherShortcuts(const gui::MenuItem & item); 47 | public: 48 | Window(const char * title, int width, int height, const Window * parent = nullptr, int2 pos = {-1,-1}); 49 | ~Window(); 50 | 51 | int2 GetPos() const { int2 pos; glfwGetWindowPos(window, &pos.x, &pos.y); return pos; } 52 | void SetPos(const int2 & pos) { glfwSetWindowPos(window, pos.x, pos.y); } 53 | 54 | bool IsMainWindow() const { return window == context->mainWindow; } 55 | NVGcontext * GetNanoVG() const { return context->vg; } 56 | 57 | void Close() { glfwSetWindowShouldClose(window, 1); } 58 | bool ShouldClose() const { return !!glfwWindowShouldClose(window); } 59 | 60 | void RefreshLayout(); 61 | void SetGuiRoot(gui::ElementPtr element, const Font & menuFont, const std::vector & menuItems); 62 | void Redraw(); 63 | }; 64 | 65 | #endif -------------------------------------------------------------------------------- /src/editor/xplat.cpp: -------------------------------------------------------------------------------- 1 | #include "xplat.h" 2 | #include 3 | #include 4 | 5 | #ifdef WIN32 6 | 7 | #define WIN32_LEAN_AND_MEAN 8 | #include 9 | #include 10 | 11 | std::wstring UtfToWin(const std::string & utf) 12 | { 13 | if(utf.empty()) return {}; 14 | int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf.data(), utf.size(), nullptr, 0); 15 | if(len == 0) throw std::runtime_error("Invalid UTF-8 encoded string: " + utf); 16 | std::wstring win(len, ' '); 17 | MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf.data(), utf.size(), &win[0], win.size()); 18 | return win; 19 | } 20 | 21 | std::string ChooseFile(const std::vector & types, bool mustExist) 22 | { 23 | std::ostringstream ss; 24 | for(auto & type : types) { ss << type.type << " (*." << type.extension << ")" << '\0' << "*." << type.extension << '\0'; } 25 | auto filter = UtfToWin(ss.str()); 26 | 27 | wchar_t buffer[MAX_PATH] = {}; 28 | 29 | OPENFILENAME ofn; 30 | ZeroMemory(&ofn, sizeof(ofn)); 31 | ofn.lStructSize = sizeof(ofn); 32 | ofn.hwndOwner = NULL; 33 | ofn.lpstrFile = buffer; 34 | ofn.nMaxFile = MAX_PATH; 35 | ofn.lpstrFilter = filter.c_str(); 36 | ofn.nFilterIndex = 1; 37 | ofn.lpstrFileTitle = NULL; 38 | ofn.nMaxFileTitle = 0; 39 | ofn.lpstrInitialDir = NULL; 40 | ofn.Flags = OFN_PATHMUSTEXIST; // OFN_ALLOWMULTISELECT 41 | if(mustExist) ofn.Flags |= OFN_FILEMUSTEXIST; 42 | 43 | if ((mustExist ? GetOpenFileName : GetSaveFileName)(&ofn) == TRUE) 44 | { 45 | size_t len = wcstombs(nullptr, buffer, 0); 46 | if(len == size_t(-1)) throw std::runtime_error("Invalid path."); 47 | std::string result(len,' '); 48 | len = wcstombs(&result[0], buffer, result.size()); 49 | assert(len == result.size()); 50 | return result; 51 | } 52 | else return {}; 53 | } 54 | 55 | #endif -------------------------------------------------------------------------------- /src/editor/xplat.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_XPLAT_H 2 | #define EDITOR_XPLAT_H 3 | 4 | #include 5 | #include 6 | 7 | struct FileType { std::string type, extension; }; 8 | std::string ChooseFile(const std::vector & types, bool mustExist); 9 | 10 | #endif -------------------------------------------------------------------------------- /src/engine/asset.cpp: -------------------------------------------------------------------------------- 1 | #include "asset.h" 2 | 3 | #include 4 | #include 5 | 6 | struct AssetLibrary::List 7 | { 8 | std::function(const std::string & id)> loader; 9 | std::vector> records; 10 | 11 | std::shared_ptr GetRecord(const std::string & id) 12 | { 13 | for(auto & record : records) if(record->id == id) return record; 14 | 15 | if(loader) 16 | { 17 | try 18 | { 19 | auto a = loader(id); 20 | auto r = std::make_shared(); 21 | r->id = id; 22 | r->asset = move(a); 23 | records.push_back(r); 24 | return r; 25 | } 26 | catch(const std::exception & e) 27 | { 28 | std::cerr << "Unable to load asset \'" << id << "\': " << e.what() << std::endl; 29 | // TODO: Make some sort of record, so we only attempt to load an asset once 30 | } 31 | } 32 | 33 | return {}; 34 | } 35 | }; 36 | 37 | AssetLibrary::AssetLibrary() {} 38 | AssetLibrary::~AssetLibrary() {} 39 | 40 | void AssetLibrary::SetLoader(const std::type_info & type, std::function(const std::string & id)> loader) 41 | { 42 | lists[type].loader = loader; 43 | } 44 | 45 | std::shared_ptr AssetLibrary::AddAsset(const std::type_info & type, const std::string & id, std::shared_ptr asset) 46 | { 47 | auto r = std::make_shared(); 48 | r->id = id; 49 | r->asset = asset; 50 | lists[type].records.push_back(r); 51 | return r; 52 | } 53 | 54 | std::shared_ptr AssetLibrary::GetAsset(const std::type_info & type, const std::string & id) 55 | { 56 | auto it = lists.find(type); 57 | if(it == end(lists)) return {}; 58 | return it->second.GetRecord(id); 59 | } 60 | -------------------------------------------------------------------------------- /src/engine/asset.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_ASSET_H 2 | #define EDITOR_ASSET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class AssetLibrary 11 | { 12 | struct Record 13 | { 14 | std::string id; 15 | std::shared_ptr asset; 16 | }; 17 | 18 | struct List; 19 | std::map lists; 20 | 21 | void SetLoader(const std::type_info & type, std::function(const std::string & id)> loader); 22 | std::shared_ptr AddAsset(const std::type_info & type, const std::string & id, std::shared_ptr asset); 23 | std::shared_ptr GetAsset(const std::type_info & type, const std::string & id); 24 | public: 25 | AssetLibrary(); 26 | ~AssetLibrary(); 27 | 28 | template class Handle 29 | { 30 | std::shared_ptr record; 31 | public: 32 | Handle() {} 33 | Handle(const std::shared_ptr & record) : record(record) {} 34 | 35 | operator bool () const { return record && record->asset; } 36 | bool operator == (const Handle & r) const { return record == r.record; } 37 | bool operator != (const Handle & r) const { return record != r.record; } 38 | 39 | bool operator ! () const { return !record || !record->asset; } 40 | const T & operator * () const { return *reinterpret_cast(record->asset.get()); } 41 | const T * operator -> () const { return reinterpret_cast(record->asset.get()); } 42 | 43 | const std::string & GetId() const { static const std::string empty; return record ? record->id : empty; } 44 | }; 45 | 46 | template void SetLoader(F load) { SetLoader(typeid(T), [load](const std::string & id) { return std::make_shared(load(id)); }); } 47 | template Handle AddAsset(const std::string & id, T && asset) { return AddAsset(typeid(T), id, std::make_shared(std::move(asset))); } 48 | template Handle GetAsset(const std::string & id) { return GetAsset(typeid(T), id); } 49 | }; 50 | 51 | #endif -------------------------------------------------------------------------------- /src/engine/font.cpp: -------------------------------------------------------------------------------- 1 | #include "font.h" 2 | #include "utf8.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "nanovg.h" 8 | 9 | Font::Font(NVGcontext * vg, const char * filename, int pixelHeight, bool dropShadow, uint32_t maxCodepoint) : vg(vg), name(filename), pixelHeight(pixelHeight), dropShadow(dropShadow) 10 | { 11 | int font = nvgCreateFont(vg, filename, filename); 12 | if (font == -1) throw std::runtime_error("Could not add font " + name); 13 | } 14 | 15 | int Font::GetLineHeight() const 16 | { 17 | float a, d, h; 18 | nvgFontSize(vg, pixelHeight); 19 | nvgFontFace(vg, name.c_str()); 20 | nvgTextMetrics(vg, &a, &d, &h); 21 | return static_cast(h); 22 | } 23 | 24 | int Font::GetBaselineOffset() const 25 | { 26 | float a, d, h; 27 | nvgFontSize(vg, pixelHeight); 28 | nvgFontFace(vg, name.c_str()); 29 | nvgTextMetrics(vg, &a, &d, &h); 30 | return static_cast(a); 31 | } 32 | 33 | int Font::GetStringWidth(const std::string & string) const 34 | { 35 | nvgFontSize(vg, pixelHeight); 36 | nvgFontFace(vg, name.c_str()); 37 | return static_cast(nvgTextBounds(vg, 0,0, string.c_str(), nullptr, nullptr)); 38 | } 39 | 40 | size_t Font::GetUnitIndex(const std::string & string, int x) const 41 | { 42 | NVGglyphPosition positions[1024]; 43 | nvgFontSize(vg, pixelHeight); 44 | nvgFontFace(vg, name.c_str()); 45 | int n = nvgTextGlyphPositions(vg, 0, 0, string.c_str(), nullptr, positions, 1024); 46 | for(int i=0; i(i); 47 | return n; 48 | } 49 | 50 | void Font::DrawString(int x, int y, NVGcolor color, const std::string & string) const 51 | { 52 | nvgFontSize(vg, pixelHeight); 53 | nvgFontFace(vg, name.c_str()); 54 | nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); 55 | if(dropShadow) 56 | { 57 | 58 | nvgFillColor(vg, nvgRGBA(0,0,0,color.a)); 59 | nvgText(vg, x+1, y+1, string.c_str(), nullptr); 60 | } 61 | nvgFillColor(vg, color); 62 | nvgText(vg, x, y, string.c_str(), nullptr); 63 | } -------------------------------------------------------------------------------- /src/engine/font.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_FONT_H 2 | #define ENGINE_FONT_H 3 | 4 | #include "gl.h" 5 | #include "nanovg.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | struct NVGcontext; 12 | 13 | class Font 14 | { 15 | NVGcontext * vg; 16 | std::string name; 17 | int pixelHeight; 18 | bool dropShadow; 19 | public: 20 | Font(NVGcontext * vg, const char * filename, int pixelHeight, bool dropShadow = false, uint32_t maxCodepoint = 127); 21 | 22 | int GetLineHeight() const; // Number of pixels from top to bottom of a line 23 | int GetBaselineOffset() const; // Number of pixels from top of a line to the "baseline" of the font 24 | int GetStringWidth(const std::string & s) const; 25 | size_t GetUnitIndex(const std::string & s, int x) const; 26 | 27 | void DrawString(int x, int y, NVGcolor color, const std::string & s) const; 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /src/engine/geometry.cpp: -------------------------------------------------------------------------------- 1 | #include "geometry.h" 2 | 3 | RayPlaneHit IntersectRayPlane(const Ray & ray, const Plane & plane) 4 | { 5 | auto denom = dot(ray.direction, plane.GetNormal()); 6 | if(std::abs(denom) < 0.0001f) return {false}; 7 | return {true, -(dot(ray.start, plane.GetNormal()) + plane.coeff.w) / denom}; 8 | } 9 | 10 | RayTriHit IntersectRayTriangle(const Ray & ray, const float3 & v0, const float3 & v1, const float3 & v2) 11 | { 12 | auto e1 = v1 - v0, e2 = v2 - v0, h = cross(ray.direction, e2); 13 | auto a = dot(e1, h); 14 | if (a > -0.0001f && a < 0.0001f) return {false}; 15 | 16 | float f = 1 / a; 17 | auto s = ray.start - v0; 18 | auto u = f * dot(s, h); 19 | if (u < 0 || u > 1) return {false}; 20 | 21 | auto q = cross(s, e1); 22 | auto v = f * dot(ray.direction, q); 23 | if (v < 0 || u + v > 1) return {false}; 24 | 25 | auto t = f * dot(e2, q); 26 | if(t < 0) return {false}; 27 | 28 | return {true,t,u,v}; 29 | } -------------------------------------------------------------------------------- /src/engine/geometry.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_GEOMETRY_H 2 | #define ENGINE_GEOMETRY_H 3 | 4 | #include "linalg.h" 5 | #include "transform.h" 6 | 7 | struct Plane 8 | { 9 | float4 coeff; // Coefficients of plane equation, in ax * by * cz + d form 10 | Plane(const float3 & axis, const float3 & point) : coeff(axis, -dot(axis,point)) {} 11 | const float3 & GetNormal() const { return coeff.xyz(); } 12 | }; 13 | 14 | struct Ray 15 | { 16 | float3 start, direction; 17 | float3 GetPoint(float t) const { return start + direction * t; } 18 | static Ray Between(const float3 & start, const float3 & end) { return {start,norm(end-start)}; } 19 | friend Ray operator * (const Pose & pose, const Ray & ray) { return {pose.TransformCoord(ray.start), pose.TransformVector(ray.direction)}; } 20 | }; 21 | 22 | struct RayPlaneHit { bool hit; float t; }; 23 | RayPlaneHit IntersectRayPlane(const Ray & ray, const Plane & plane); 24 | 25 | struct RayTriHit { bool hit; float t,u,v; }; 26 | RayTriHit IntersectRayTriangle(const Ray & ray, const float3 & vertex0, const float3 & vertex1, const float3 & vertex2); 27 | 28 | struct RayMeshHit : RayTriHit { size_t triangle; RayMeshHit(const RayTriHit & hit, size_t triangle) : RayTriHit(hit), triangle(triangle) {} }; 29 | template RayMeshHit IntersectRayMesh(const Ray & ray, const VERTEX * verts, float3 (VERTEX::*position), const uint3 * tris, size_t numTris) 30 | { 31 | RayMeshHit best = {{false},0}; 32 | for(size_t i=0; i 3 | #include 4 | #pragma comment(lib, "glfw3dll.lib") 5 | 6 | template T & BltSwap(T * lhs, T & rhs) 7 | { 8 | char temp[sizeof(T)]; 9 | memcpy(temp, lhs, sizeof(T)); 10 | memcpy(lhs, &rhs, sizeof(T)); 11 | memcpy(&rhs, temp, sizeof(T)); 12 | return *lhs; 13 | } 14 | 15 | using namespace gl; 16 | 17 | Mesh::Mesh() : vertexArray(), arrayBuffer(), elementBuffer(), vertexCount(), indexCount(), mode(GL_TRIANGLES), indexType() {} 18 | Mesh::Mesh(Mesh && r) : Mesh() { BltSwap(this,r); } 19 | Mesh & Mesh::operator = (Mesh && r) { return BltSwap(this,r); } 20 | Mesh::~Mesh() 21 | { 22 | if(elementBuffer) glDeleteBuffers(1,&elementBuffer); 23 | if(arrayBuffer) glDeleteBuffers(1,&arrayBuffer); 24 | if(vertexArray) glDeleteVertexArrays(1,&vertexArray); 25 | } 26 | 27 | void Mesh::Draw() const 28 | { 29 | glBindVertexArray(vertexArray); 30 | if(elementBuffer) glDrawElements(mode, indexCount, indexType, nullptr); 31 | else glDrawArrays(mode, 0, vertexCount); 32 | } 33 | 34 | void Mesh::SetVertexData(const void * vertices, size_t vertexSize, size_t vertexCount) 35 | { 36 | if(!arrayBuffer) glGenBuffers(1,&arrayBuffer); 37 | glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer); 38 | glBufferData(GL_ARRAY_BUFFER, vertexSize*vertexCount, vertices, GL_STATIC_DRAW); 39 | this->vertexCount = vertexCount; 40 | } 41 | 42 | void Mesh::SetAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) 43 | { 44 | if(!vertexArray) glGenVertexArrays(1, &vertexArray); 45 | glBindVertexArray(vertexArray); 46 | if(!arrayBuffer) glGenBuffers(1,&arrayBuffer); 47 | glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer); 48 | glVertexAttribPointer(index, size, type, normalized, stride, pointer); 49 | glEnableVertexAttribArray(index); 50 | } 51 | 52 | void Mesh::SetIndexData(const void * indices, GLenum type, size_t indexCount, GLenum mode) 53 | { 54 | if(!vertexArray) glGenVertexArrays(1, &vertexArray); 55 | glBindVertexArray(vertexArray); 56 | if(!elementBuffer) glGenBuffers(1,&elementBuffer); 57 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer); 58 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, (type == GL_UNSIGNED_INT ? 4 : type == GL_UNSIGNED_SHORT ? 2 : type == GL_UNSIGNED_BYTE ? 1 : 0)*indexCount, indices, GL_STATIC_DRAW); 59 | this->indexCount = indexCount; 60 | this->indexType = type; 61 | this->mode = mode; 62 | } 63 | 64 | void Texture::Load(const char * filename) 65 | { 66 | int x, y, n; 67 | if(auto pixels = stbi_load(filename, &x, &y, &n, 0)) 68 | { 69 | GLenum format[] = {0, GL_LUMINANCE, 0, GL_RGB, GL_RGBA}; 70 | 71 | if(!tex) glGenTextures(1, &tex); 72 | glBindTexture(GL_TEXTURE_2D, tex); 73 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, format[n], GL_UNSIGNED_BYTE, pixels); 74 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 75 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 76 | stbi_image_free(pixels); 77 | 78 | width = x; 79 | height = y; 80 | } 81 | } 82 | 83 | static void AttachShader(GLuint program, GLenum type, const char * source) 84 | { 85 | GLuint shader = glCreateShader(type); 86 | glShaderSource(shader, 1, &source, nullptr); 87 | glCompileShader(shader); 88 | 89 | GLint status, length; 90 | glGetShaderiv(shader, GL_COMPILE_STATUS, &status); 91 | if(status == GL_FALSE) 92 | { 93 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); 94 | std::vector buffer(length); 95 | glGetShaderInfoLog(shader, length, nullptr, buffer.data()); 96 | glDeleteShader(shader); 97 | throw std::runtime_error(std::string("glCompileShader(...) failed with log:\n") + buffer.data()); 98 | } 99 | 100 | glAttachShader(program, shader); 101 | glDeleteShader(shader); 102 | } 103 | 104 | gl::Program::Program(const std::string & vertShader, const std::string & fragShader) : Program() 105 | { 106 | object = glCreateProgram(); 107 | AttachShader(object, GL_VERTEX_SHADER, vertShader.c_str()); 108 | AttachShader(object, GL_FRAGMENT_SHADER, fragShader.c_str()); 109 | glLinkProgram(object); 110 | 111 | GLint status, length; 112 | glGetProgramiv(object, GL_LINK_STATUS, &status); 113 | if(status == GL_FALSE) 114 | { 115 | glGetProgramiv(object, GL_INFO_LOG_LENGTH, &length); 116 | std::vector buffer(length); 117 | glGetProgramInfoLog(object, length, nullptr, buffer.data()); 118 | throw std::runtime_error(std::string("glLinkProgram(...) failed with log:\n") + buffer.data()); 119 | } 120 | 121 | // Enumerate active program uniforms 122 | std::map blocks; 123 | GLint activeUniforms, activeUniformMaxLength; 124 | glGetProgramiv(object, GL_ACTIVE_UNIFORMS, &activeUniforms); 125 | glGetProgramiv(object, GL_ACTIVE_UNIFORM_MAX_LENGTH, &activeUniformMaxLength); 126 | std::vector nameBuffer(activeUniformMaxLength); 127 | for(GLuint index = 0; index < activeUniforms; ++index) 128 | { 129 | GLint blockIndex; 130 | glGetActiveUniformsiv(object, 1, &index, GL_UNIFORM_BLOCK_INDEX, &blockIndex); 131 | if(blockIndex == -1) continue; 132 | 133 | UniformDesc uniform; 134 | glGetActiveUniform(object, index, nameBuffer.size(), nullptr, &uniform.size, &uniform.type, nameBuffer.data()); 135 | glGetActiveUniformsiv(object, 1, &index, GL_UNIFORM_OFFSET, &uniform.offset); 136 | glGetActiveUniformsiv(object, 1, &index, GL_UNIFORM_ARRAY_STRIDE, &uniform.arrayStride); 137 | glGetActiveUniformsiv(object, 1, &index, GL_UNIFORM_MATRIX_STRIDE, &uniform.matrixStride); 138 | uniform.name = nameBuffer.data(); 139 | blocks[blockIndex].uniforms.push_back(uniform); 140 | } 141 | 142 | // Enumerate active program uniform blocks 143 | for(auto & pair : blocks) 144 | { 145 | GLint nameLength; 146 | glGetActiveUniformBlockiv(object, pair.first, GL_UNIFORM_BLOCK_BINDING, &pair.second.binding); 147 | glGetActiveUniformBlockiv(object, pair.first, GL_UNIFORM_BLOCK_DATA_SIZE, &pair.second.dataSize); 148 | glGetActiveUniformBlockiv(object, pair.first, GL_UNIFORM_BLOCK_NAME_LENGTH, &nameLength); 149 | nameBuffer.resize(nameLength); 150 | glGetActiveUniformBlockName(object, pair.first, nameBuffer.size(), nullptr, nameBuffer.data()); 151 | pair.second.name = nameBuffer.data(); 152 | this->blocks.push_back(std::move(pair.second)); 153 | } 154 | } 155 | 156 | gl::Program::~Program() 157 | { 158 | if(object) glDeleteProgram(object); 159 | } -------------------------------------------------------------------------------- /src/engine/gl.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_GL_H 2 | #define ENGINE_GL_H 3 | 4 | #include "linalg.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace gl 12 | { 13 | inline GLenum GetType(uint8_t *) { return GL_UNSIGNED_BYTE; } 14 | inline GLenum GetType(uint16_t *) { return GL_UNSIGNED_SHORT; } 15 | inline GLenum GetType(uint32_t *) { return GL_UNSIGNED_INT; } 16 | inline GLenum GetType(float *) { return GL_FLOAT; } 17 | 18 | class Buffer 19 | { 20 | GLuint object; 21 | public: 22 | Buffer() : object() {} 23 | Buffer(Buffer && r) : Buffer() { *this = std::move(r); } 24 | Buffer(const Buffer & r) = delete; 25 | ~Buffer() { if(object) glDeleteBuffers(1, &object); } 26 | 27 | void BindBase(GLenum target, GLuint index) const { glBindBufferBase(target, index, object); } 28 | 29 | Buffer & operator = (Buffer && r) { std::swap(object, r.object); return *this; } 30 | Buffer & operator = (const Buffer & r) = delete; 31 | 32 | void SetData(GLenum target, GLsizeiptr size, GLvoid * data, GLenum usage) 33 | { 34 | if(!object) glGenBuffers(1, &object); 35 | glBindBuffer(target, object); 36 | glBufferData(target, size, data, usage); 37 | } 38 | }; 39 | 40 | class Mesh 41 | { 42 | GLuint vertexArray, arrayBuffer, elementBuffer; 43 | GLsizei vertexCount, indexCount; 44 | GLenum mode, indexType; 45 | public: 46 | Mesh(); 47 | Mesh(Mesh && r); 48 | Mesh(const Mesh & r) = delete; 49 | Mesh & operator = (Mesh && r); 50 | Mesh & operator = (const Mesh & r) = delete; 51 | ~Mesh(); 52 | 53 | void Draw() const; 54 | 55 | void SetVertexData(const void * vertices, size_t vertexSize, size_t vertexCount); 56 | void SetAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer); 57 | void SetIndexData(const void * indices, GLenum type, size_t indexCount, GLenum mode); 58 | 59 | template void SetVertices(const std::vector & vertices) { SetVertexData(vertices.data(), sizeof(V), vertices.size()); } 60 | template void SetAttribute(GLuint index, vec V::*attribute, bool normalized = false) { SetAttribPointer(index, N, GetType((T*)0), normalized ? GL_TRUE : GL_FALSE, sizeof(V), &(reinterpret_cast(nullptr)->*attribute)); } 61 | template void SetElements(const std::vector> & elements) { const GLenum modes[] = {0,GL_POINTS,GL_LINES,GL_TRIANGLES,GL_QUADS}; SetIndexData(elements.data(), GetType((T*)0), elements.size()*N, modes[N]); } 62 | }; 63 | 64 | class Texture 65 | { 66 | GLuint tex; 67 | int width, height; 68 | public: 69 | Texture() : tex() {} 70 | ~Texture() { if(tex) glDeleteTextures(1,&tex); } 71 | 72 | int GetWidth() const { return width; } 73 | int GetHeight() const { return height; } 74 | void Bind() const { glBindTexture(GL_TEXTURE_2D, tex); } 75 | 76 | void Load(const char * filename); 77 | }; 78 | 79 | struct UniformDesc 80 | { 81 | std::string name; 82 | GLint offset; 83 | GLint size, arrayStride, matrixStride; 84 | GLenum type; 85 | 86 | void SetValue(uint8_t * data, const float3 & value) const { if(type == GL_FLOAT_VEC3) reinterpret_cast(data[offset]) = value; } 87 | void SetValue(uint8_t * data, const float4x4 & value) const { if(type == GL_FLOAT_MAT4) reinterpret_cast(data[offset]) = value; } 88 | }; 89 | 90 | struct BlockDesc 91 | { 92 | std::string name; 93 | GLint binding, dataSize; 94 | std::vector uniforms; 95 | 96 | const UniformDesc * GetNamedUniform(const std::string & name) const { for(auto & uniform : uniforms) if(uniform.name == name) return &uniform; return nullptr; } 97 | template void SetUniform(uint8_t * data, const std::string & name, const T & value) const { if(auto u = GetNamedUniform(name)) u->SetValue(data, value); } 98 | }; 99 | 100 | class Program 101 | { 102 | GLuint object; 103 | std::vector blocks; 104 | public: 105 | Program() : object() {} 106 | Program(const std::string & vertShader, const std::string & fragShader); 107 | Program(Program && r) : Program() { *this = std::move(r); } 108 | Program(const Program & r) = delete; 109 | ~Program(); 110 | 111 | Program & operator = (Program && r) { std::swap(object, r.object); blocks.swap(r.blocks); return *this; } 112 | Program & operator = (const Program & r) = delete; 113 | 114 | const std::vector & GetBlocks() const { return blocks; } 115 | const BlockDesc * GetDefaultBlock(const std::string & name) const { for(auto & block : blocks) if(block.binding == -1) return █ return nullptr; } 116 | const BlockDesc * GetNamedBlock(const std::string & name) const { for(auto & block : blocks) if(block.name == name) return █ return nullptr; } 117 | void Use() const { glUseProgram(object); } 118 | }; 119 | } 120 | 121 | #endif -------------------------------------------------------------------------------- /src/engine/json.cpp: -------------------------------------------------------------------------------- 1 | #include "json.h" 2 | 3 | #include 4 | #include 5 | 6 | std::ostream & printEscaped(std::ostream & out, const std::string & str) 7 | { 8 | // Escape sequences for ", \, and control characters, 0 indicates no escaping needed 9 | static const char * escapes[256] = { 10 | "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", 11 | "\\b", "\\t", "\\n", "\\u000B", "\\f", "\\r", "\\u000E", "\\u000F", 12 | "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", 13 | "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F", 14 | 0, 0, "\\\"", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "\\\\", 0, 0, 0, 16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "\\u007F" 17 | }; 18 | out << '"'; 19 | for (uint8_t ch : str) 20 | { 21 | if (escapes[ch]) out << escapes[ch]; 22 | else out << ch; 23 | } 24 | return out << '"'; 25 | } 26 | 27 | std::ostream & operator << (std::ostream & out, const JsonArray & arr) 28 | { 29 | int i = 0; 30 | out << '['; 31 | for (auto & val : arr) out << (i++ ? "," : "") << val; 32 | return out << ']'; 33 | } 34 | 35 | std::ostream & operator << (std::ostream & out, const JsonObject & obj) 36 | { 37 | int i = 0; 38 | out << '{'; 39 | for (auto & kvp : obj) 40 | { 41 | printEscaped(out << (i++ ? "," : ""), kvp.first) << ':' << kvp.second; 42 | } 43 | return out << '}'; 44 | } 45 | 46 | std::ostream & operator << (std::ostream & out, const JsonValue & val) 47 | { 48 | if (val.isNull()) return out << "null"; 49 | else if (val.isFalse()) return out << "false"; 50 | else if (val.isTrue()) return out << "true"; 51 | else if (val.isString()) return printEscaped(out, val.contents()); 52 | else if (val.isNumber()) return out << val.contents(); 53 | else if (val.isArray()) return out << val.array(); 54 | else return out << val.object(); 55 | } 56 | 57 | static std::ostream & indent(std::ostream & out, int space, int n = 0) 58 | { 59 | if (n) out << ','; 60 | out << '\n'; 61 | for (int i = 0; i < space; ++i) out << ' '; 62 | return out; 63 | } 64 | 65 | std::ostream & operator << (std::ostream & out, tabbed_ref arr) 66 | { 67 | if (std::none_of(begin(arr.value), end(arr.value), [](const JsonValue & val) { return val.isArray() || val.isObject(); })) return out << arr.value; 68 | else 69 | { 70 | int space = arr.indent + arr.tabWidth, i = 0; 71 | out << '['; 72 | for (auto & val : arr.value) indent(out, space, i++) << tabbed(val, arr.tabWidth, space); 73 | return indent(out, arr.indent) << ']'; 74 | } 75 | } 76 | 77 | std::ostream & operator << (std::ostream & out, tabbed_ref obj) 78 | { 79 | if (obj.value.empty()) return out << "{}"; 80 | else 81 | { 82 | int space = obj.indent + obj.tabWidth, i = 0; 83 | out << '{'; 84 | for (auto & kvp : obj.value) 85 | { 86 | printEscaped(indent(out, space, i++), kvp.first) << ": " << tabbed(kvp.second, obj.tabWidth, space); 87 | } 88 | return indent(out, obj.indent) << '}'; 89 | } 90 | } 91 | 92 | std::ostream & operator << (std::ostream & out, tabbed_ref val) 93 | { 94 | if (val.value.isArray()) return out << tabbed(val.value.array(), val.tabWidth, val.indent); 95 | else if (val.value.isObject()) return out << tabbed(val.value.object(), val.tabWidth, val.indent); 96 | else return out << val.value; 97 | } 98 | 99 | bool isJsonNumber(const std::string & num) 100 | { 101 | static const std::regex regex(R"(-?(0|([1-9][0-9]*))((\.[0-9]+)?)(((e|E)((\+|-)?)[0-9]+)?))"); 102 | return std::regex_match(begin(num), end(num), regex); 103 | } 104 | 105 | static uint16_t decode_hex(char ch) { 106 | if (ch >= '0' && ch <= '9') return ch - '0'; 107 | if (ch >= 'A' && ch <= 'F') return 10 + ch - 'A'; 108 | if (ch >= 'a' && ch <= 'f') return 10 + ch - 'a'; 109 | throw JsonParseError(std::string("invalid hex digit: ") + ch); 110 | } 111 | 112 | static std::string decode_string(std::string::const_iterator first, std::string::const_iterator last) 113 | { 114 | if (std::any_of(first, last, iscntrl)) throw JsonParseError("control character found in string literal"); 115 | if (std::find(first, last, '\\') == last) return std::string(first, last); // No escape characters, use the string directly 116 | std::string s; s.reserve(last - first); // Reserve enough memory to hold the entire string 117 | for (; first < last; ++first) 118 | { 119 | if (*first != '\\') s.push_back(*first); 120 | else switch (*(++first)) 121 | { 122 | case '"': s.push_back('"'); break; 123 | case '\\': s.push_back('\\'); break; 124 | case '/': s.push_back('/'); break; 125 | case 'b': s.push_back('\b'); break; 126 | case 'f': s.push_back('\f'); break; 127 | case 'n': s.push_back('\n'); break; 128 | case 'r': s.push_back('\r'); break; 129 | case 't': s.push_back('\t'); break; 130 | case 'u': 131 | if (first + 5 > last) throw JsonParseError("incomplete escape sequence: " + std::string(first - 1, last)); 132 | else 133 | { 134 | uint16_t val = (decode_hex(first[1]) << 12) | (decode_hex(first[2]) << 8) | (decode_hex(first[3]) << 4) | decode_hex(first[4]); 135 | if (val < 0x80) s.push_back(static_cast(val)); // ASCII codepoint, no translation needed 136 | else if (val < 0x800) // 2-byte UTF-8 encoding 137 | { 138 | s.push_back(0xC0 | ((val >> 6) & 0x1F)); // Leading byte: 5 content bits 139 | s.push_back(0x80 | ((val >> 0) & 0x3F)); // Continuation byte: 6 content bits 140 | } 141 | else // 3-byte UTF-8 encoding (16 content bits, sufficient to store all \uXXXX patterns) 142 | { 143 | s.push_back(0xE0 | ((val >> 12) & 0x0F)); // Leading byte: 4 content bits 144 | s.push_back(0x80 | ((val >> 6) & 0x3F)); // Continuation byte: 6 content bits 145 | s.push_back(0x80 | ((val >> 0) & 0x3F)); // Continuation byte: 6 content bits 146 | } 147 | first += 4; 148 | } 149 | break; 150 | default: throw JsonParseError("invalid escape sequence"); 151 | } 152 | } 153 | return s; 154 | } 155 | 156 | struct JsonToken { char type; std::string value; JsonToken(char type, std::string value = std::string()) : type(type), value(move(value)) {} }; 157 | 158 | struct JsonParseState 159 | { 160 | std::vector::iterator it, last; 161 | 162 | bool matchAndDiscard(char type) { if (it->type != type) return false; ++it; return true; } 163 | void discardExpected(char type, const char * what) { if (!matchAndDiscard(type)) throw JsonParseError(std::string("Syntax error: Expected ") + what); } 164 | 165 | JsonValue parseValue() 166 | { 167 | auto token = it++; 168 | switch (token->type) 169 | { 170 | case 'n': return nullptr; 171 | case 'f': return false; 172 | case 't': return true; 173 | case '"': return token->value; 174 | case '#': return JsonValue::fromNumber(token->value); 175 | case '[': 176 | if (matchAndDiscard(']')) return JsonArray{}; 177 | else 178 | { 179 | JsonArray arr; 180 | while (true) 181 | { 182 | arr.push_back(parseValue()); 183 | if (matchAndDiscard(']')) return arr; 184 | discardExpected(',', ", or ]"); 185 | } 186 | } 187 | case '{': 188 | if (matchAndDiscard('}')) return JsonObject{}; 189 | else 190 | { 191 | JsonObject obj; 192 | while (true) 193 | { 194 | auto name = move(it->value); 195 | discardExpected('"', "string"); 196 | discardExpected(':', ":"); 197 | obj.emplace_back(move(name), parseValue()); 198 | if (matchAndDiscard('}')) { return obj; } 199 | discardExpected(',', ", or }"); 200 | } 201 | } 202 | default: throw JsonParseError("Expected value"); 203 | } 204 | } 205 | }; 206 | 207 | std::vector jsonTokensFrom(const std::string & text) 208 | { 209 | std::vector tokens; 210 | auto it = begin(text); 211 | while (true) 212 | { 213 | it = std::find_if_not(it, end(text), isspace); // Skip whitespace 214 | if (it == end(text)) 215 | { 216 | tokens.emplace_back('$'); 217 | return tokens; 218 | } 219 | switch (*it) 220 | { 221 | case '[': case ']': case ',': 222 | case '{': case '}': case ':': 223 | tokens.push_back({ *it++ }); 224 | break; 225 | case '"': 226 | { 227 | auto it2 = ++it; 228 | for (; it2 < end(text); ++it2) 229 | { 230 | if (*it2 == '"') break; 231 | if (*it2 == '\\') ++it2; 232 | } 233 | if (it2 < end(text)) 234 | { 235 | tokens.emplace_back('"', decode_string(it, it2)); 236 | it = it2 + 1; 237 | } 238 | else throw JsonParseError("String missing closing quote"); 239 | } 240 | break; 241 | case '-': case '0': case '1': case '2': 242 | case '3': case '4': case '5': case '6': 243 | case '7': case '8': case '9': 244 | { 245 | auto it2 = std::find_if_not(it, end(text), [](char ch) { return isalnum(ch) || ch == '+' || ch == '-' || ch == '.'; }); 246 | auto num = std::string(it, it2); 247 | if (!isJsonNumber(num)) throw JsonParseError("Invalid number: " + num); 248 | tokens.emplace_back('#', move(num)); 249 | it = it2; 250 | } 251 | break; 252 | default: 253 | if (isalpha(*it)) 254 | { 255 | auto it2 = std::find_if_not(it, end(text), isalpha); 256 | if (std::equal(it, it2, "true")) tokens.emplace_back('t'); 257 | else if (std::equal(it, it2, "false")) tokens.emplace_back('f'); 258 | else if (std::equal(it, it2, "null")) tokens.emplace_back('n'); 259 | else throw JsonParseError("Invalid token: " + std::string(it, it2)); 260 | it = it2; 261 | } 262 | else throw JsonParseError("Invalid character: \'" + std::string(1, *it) + '"'); 263 | } 264 | } 265 | } 266 | 267 | JsonValue jsonFrom(const std::string & text) 268 | { 269 | auto tokens = jsonTokensFrom(text); 270 | JsonParseState p = { begin(tokens), end(tokens) }; 271 | auto val = p.parseValue(); 272 | p.discardExpected('$', "end-of-stream"); 273 | return val; 274 | } 275 | -------------------------------------------------------------------------------- /src/engine/json.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_JSON_H 2 | #define ENGINE_JSON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class JsonValue; 10 | typedef std::vector JsonArray; 11 | typedef std::vector> JsonObject; 12 | struct JsonParseError : std::runtime_error { JsonParseError(const std::string & what) : runtime_error("json parse error - " + what) {} }; 13 | 14 | JsonValue jsonFrom(const std::string & text); // throws JsonParseError 15 | bool isJsonNumber(const std::string & num); 16 | 17 | class JsonValue 18 | { 19 | template static std::string to_str(const T & val) { std::ostringstream ss; ss << val; return ss.str(); } 20 | 21 | enum Kind { Null, False, True, String, Number, Array, Object }; 22 | Kind kind; // What kind of value is this? 23 | std::string str; // Contents of String or Number value 24 | JsonObject obj; // Fields of Object value 25 | JsonArray arr; // Elements of Array value 26 | 27 | JsonValue(Kind kind, std::string str) : kind(kind), str(move(str)) {} 28 | public: 29 | JsonValue() : kind(Null) {} // Default construct null 30 | JsonValue(std::nullptr_t) : kind(Null) {} // Construct null from nullptr 31 | JsonValue(bool b) : kind(b ? True : False) {} // Construct true or false from boolean 32 | JsonValue(const char * s) : JsonValue(String, s) {} // Construct String from C-string 33 | JsonValue(std::string s) : JsonValue(String, move(s)) {} // Construct String from std::string 34 | JsonValue(int32_t n) : JsonValue(Number, to_str(n)) {} // Construct Number from integer 35 | JsonValue(uint32_t n) : JsonValue(Number, to_str(n)) {} // Construct Number from integer 36 | JsonValue(int64_t n) : JsonValue(Number, to_str(n)) {} // Construct Number from integer 37 | JsonValue(uint64_t n) : JsonValue(Number, to_str(n)) {} // Construct Number from integer 38 | JsonValue(float n) : JsonValue(Number, to_str(n)) {} // Construct Number from float 39 | JsonValue(double n) : JsonValue(Number, to_str(n)) {} // Construct Number from double 40 | JsonValue(JsonObject o) : kind(Object), obj(move(o)) {} // Construct Object from vector> (TODO: Assert no duplicate keys) 41 | JsonValue(JsonArray a) : kind(Array), arr(move(a)) {} // Construct Array from vector 42 | 43 | bool operator == (const JsonValue & r) const { return kind == r.kind && str == r.str && obj == r.obj && arr == r.arr; } 44 | bool operator != (const JsonValue & r) const { return !(*this == r); } 45 | 46 | const JsonValue & operator[](size_t index) const { const static JsonValue null; return index < arr.size() ? arr[index] : null; } 47 | const JsonValue & operator[](int index) const { const static JsonValue null; return index < 0 ? null : (*this)[static_cast(index)]; } 48 | const JsonValue & operator[](const char * key) const { for (auto & kvp : obj) if (kvp.first == key) return kvp.second; const static JsonValue null; return null; } 49 | const JsonValue & operator[](const std::string & key) const { return (*this)[key.c_str()]; } 50 | 51 | bool isString() const { return kind == String; } 52 | bool isNumber() const { return kind == Number; } 53 | bool isObject() const { return kind == Object; } 54 | bool isArray() const { return kind == Array; } 55 | bool isTrue() const { return kind == True; } 56 | bool isFalse() const { return kind == False; } 57 | bool isNull() const { return kind == Null; } 58 | 59 | bool boolOrDefault(bool def) const { return isTrue() ? true : isFalse() ? false : def; } 60 | std::string stringOrDefault(const char * def) const { return kind == String ? str : def; } 61 | template T numberOrDefault(T def) const { if (!isNumber()) return def; T val = def; std::istringstream(str) >> val; return val; } 62 | 63 | std::string string() const { return stringOrDefault(""); } // Value, if a String, empty otherwise 64 | template T number() const { return numberOrDefault(T()); } // Value, if a Number, empty otherwise 65 | const JsonObject & object() const { return obj; } // Name/value pairs, if an Object, empty otherwise 66 | const JsonArray & array() const { return arr; } // Values, if an Array, empty otherwise 67 | 68 | const std::string & contents() const { return str; } // Contents, if a String, JSON format number, if a Number, empty otherwise 69 | 70 | static JsonValue fromNumber(std::string num) { assert(::isJsonNumber(num)); return JsonValue(Number, move(num)); } 71 | }; 72 | 73 | std::ostream & operator << (std::ostream & out, const JsonValue & val); 74 | std::ostream & operator << (std::ostream & out, const JsonArray & arr); 75 | std::ostream & operator << (std::ostream & out, const JsonObject & obj); 76 | 77 | template struct tabbed_ref { const T & value; int tabWidth, indent; }; 78 | template tabbed_ref tabbed(const T & value, int tabWidth, int indent = 0) { return{ value, tabWidth, indent }; } 79 | std::ostream & operator << (std::ostream & out, tabbed_ref val); 80 | std::ostream & operator << (std::ostream & out, tabbed_ref arr); 81 | std::ostream & operator << (std::ostream & out, tabbed_ref obj); 82 | 83 | #endif -------------------------------------------------------------------------------- /src/engine/linalg.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_LINALG_H 2 | #define ENGINE_LINALG_H 3 | 4 | #include 5 | #include 6 | 7 | template struct vec; 8 | template struct vec 9 | { 10 | T x, y; 11 | vec() : x(), y() {} 12 | vec(T x, T y) : x(x), y(y) {} 13 | T & operator [] (int i) { return (&x)[i]; } // v[i] retrieves the i'th row 14 | const T & operator [] (int i) const { return (&x)[i]; } // v[i] retrieves the i'th row 15 | bool operator == (const vec & r) const { return x == r.x && y == r.y; } 16 | template vec apply(const vec & r, F f) const { return {f(x,r.x), f(y,r.y)}; } 17 | template vec apply(T r, F f) const { return {f(x,r), f(y,r)}; } 18 | }; 19 | 20 | template struct vec 21 | { 22 | T x, y, z; 23 | vec() : x(), y(), z() {} 24 | vec(T x, T y, T z) : x(x), y(y), z(z) {} 25 | vec(const vec & xy, T z) : vec(xy.x, xy.y, z) {} 26 | T & operator [] (int i) { return (&x)[i]; } // v[i] retrieves the i'th row 27 | const T & operator [] (int i) const { return (&x)[i]; } // v[i] retrieves the i'th row 28 | bool operator == (const vec & r) const { return x == r.x && y == r.y && z == r.z; } 29 | const vec & xy() const { return reinterpret_cast &>(x); } 30 | template vec apply(const vec & r, F f) const { return {f(x,r.x), f(y,r.y), f(z,r.z)}; } 31 | template vec apply(T r, F f) const { return {f(x,r), f(y,r), f(z,r)}; } 32 | }; 33 | 34 | template struct vec 35 | { 36 | T x, y, z, w; 37 | vec() : x(), y(), z(), w() {} 38 | vec(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {} 39 | vec(const vec & xy, T z, T w) : vec(xy.x, xy.y, z, w) {} 40 | vec(const vec & xyz, T w) : vec(xyz.x, xyz.y, xyz.z, w) {} 41 | T & operator [] (int i) { return (&x)[i]; } // v[i] retrieves the i'th row 42 | const T & operator [] (int i) const { return (&x)[i]; } // v[i] retrieves the i'th row 43 | bool operator == (const vec & r) const { return x == r.x && y == r.y && z == r.z && w == r.w; } 44 | const vec & xy() const { return reinterpret_cast &>(x); } 45 | const vec & xyz() const { return reinterpret_cast &>(x); } 46 | template vec apply(const vec & r, F f) const { return {f(x,r.x), f(y,r.y), f(z,r.z), f(w,r.w)}; } 47 | template vec apply(T r, F f) const { return {f(x,r), f(y,r), f(z,r), f(w,r)}; } 48 | }; 49 | 50 | template vec operator + (const vec & a) { return a.apply(T(), [](T x, T) { return +x; }); } 51 | template vec operator - (const vec & a) { return a.apply(T(), [](T x, T) { return -x; }); } 52 | template vec operator + (const vec & a, const vec & b) { return a.apply(b, [](T a, T b) { return a+b; }); } 53 | template vec operator - (const vec & a, const vec & b) { return a.apply(b, [](T a, T b) { return a-b; }); } 54 | template vec operator * (const vec & a, const vec & b) { return a.apply(b, [](T a, T b) { return a*b; }); } 55 | template vec operator / (const vec & a, const vec & b) { return a.apply(b, [](T a, T b) { return a/b; }); } 56 | template vec operator + (const vec & a, T b) { return a.apply(b, [](T a, T b) { return a+b; }); } 57 | template vec operator - (const vec & a, T b) { return a.apply(b, [](T a, T b) { return a-b; }); } 58 | template vec operator * (const vec & a, T b) { return a.apply(b, [](T a, T b) { return a*b; }); } 59 | template vec operator / (const vec & a, T b) { return a.apply(b, [](T a, T b) { return a/b; }); } 60 | template vec operator += (vec & a, const vec & b) { return a=a+b; } 61 | template vec & operator -= (vec & a, const vec & b) { return a=a-b; } 62 | template vec & operator *= (vec & a, const vec & b) { return a=a*b; } 63 | template vec & operator /= (vec & a, const vec & b) { return a=a/b; } 64 | template vec & operator += (vec & a, T b) { return a=a+b; } 65 | template vec & operator -= (vec & a, T b) { return a=a-b; } 66 | template vec & operator *= (vec & a, T b) { return a=a*b; } 67 | template vec & operator /= (vec & a, T b) { return a=a/b; } 68 | 69 | template T cross (const vec & a, const vec & b) { return a.x*b.y - a.y*b.x; } 70 | template vec cross (const vec & a, const vec & b) { return {a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x}; } 71 | template T dot (const vec & a, const vec & b) { return a.x*b.x + a.y*b.y; } 72 | template T dot (const vec & a, const vec & b) { return a.x*b.x + a.y*b.y + a.z*b.z; } 73 | template T dot (const vec & a, const vec & b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; } 74 | template vec lerp (const vec & a, const vec & b, T t) { return a*(1-t) + b*t; } 75 | template T mag (const vec & a) { return sqrt(mag2(a)); } 76 | template T mag2 (const vec & a) { return dot(a,a); } 77 | template vec max (const vec & a, const vec & b) { return a.apply(b, std::max); } 78 | template vec min (const vec & a, const vec & b) { return a.apply(b, std::min); } 79 | template vec norm (const vec & a) { return a/mag(a); } 80 | template vec normz (const vec & a) { auto m = mag(a); return m ? a/m : vec(); } 81 | 82 | template vec qconj (const vec & q) { return {-q.x,-q.y,-q.z,q.w}; } 83 | template vec qinv (const vec & q) { return qconj(q)/mag2(q); } 84 | template vec qmul (const vec & a, const vec & b) { return {a.x*b.w+a.w*b.x+a.y*b.z-a.z*b.y, a.y*b.w+a.w*b.y+a.z*b.x-a.x*b.z, a.z*b.w+a.w*b.z+a.x*b.y-a.y*b.x, a.w*b.w-a.x*b.x-a.y*b.y-a.z*b.z}; } 85 | template vec qrot (const vec & q, const vec & v) { return qxdir(q)*v.x + qydir(q)*v.y + qzdir(q)*v.z; } // qvq* 86 | template vec qxdir (const vec & q) { return {q.w*q.w+q.x*q.x-q.y*q.y-q.z*q.z, (q.x*q.y+q.z*q.w)*2, (q.z*q.x-q.y*q.w)*2}; } // q{1,0,0,0}q* 87 | template vec qydir (const vec & q) { return {(q.x*q.y-q.z*q.w)*2, q.w*q.w-q.x*q.x+q.y*q.y-q.z*q.z, (q.y*q.z+q.x*q.w)*2}; } // q{0,1,0,0}q* 88 | template vec qzdir (const vec & q) { return {(q.z*q.x+q.y*q.w)*2, (q.y*q.z-q.x*q.w)*2, q.w*q.w-q.x*q.x-q.y*q.y+q.z*q.z}; } // q{0,0,1,0}q* 89 | 90 | template struct mat; 91 | template struct mat 92 | { 93 | typedef vec U; 94 | U x, y; 95 | mat() : x(), y() {} 96 | mat(U x, U y) : x(x), y(y) {} 97 | U & operator [] (int j) { return (&x)[j]; } // M[j] retrieves the j'th column 98 | const U & operator [] (int j) const { return (&x)[j]; } // M[j] retrieves the j'th column 99 | T & operator () (int i, int j) { return (*this)[j][i]; } // M(i,j) retrieves the i'th row of the j'th column 100 | const T & operator () (int i, int j) const { return (*this)[j][i]; } // M(i,j) retrieves the i'th row of the j'th column 101 | vec row(int i) const { return {x[i],y[i]}; } // row(i) retrieves the i'th row 102 | template mat apply(const mat & r, F f) const { return {x.apply(r.x,f), y.apply(r.y,f)}; } 103 | template mat apply(T r, F f) const { return {x.apply(r,f), y.apply(r,f)}; } 104 | }; 105 | 106 | template struct mat 107 | { 108 | typedef vec U; 109 | U x, y, z; 110 | mat() : x(), y(), z() {} 111 | mat(U x, U y, U z) : x(x), y(y), z(z) {} 112 | U & operator [] (int j) { return (&x)[j]; } // M[j] retrieves the j'th column 113 | const U & operator [] (int j) const { return (&x)[j]; } // M[j] retrieves the j'th column 114 | T & operator () (int i, int j) { return (*this)[j][i]; } // M(i,j) retrieves the i'th row of the j'th column 115 | const T & operator () (int i, int j) const { return (*this)[j][i]; } // M(i,j) retrieves the i'th row of the j'th column 116 | vec row(int i) const { return {x[i],y[i],z[i]}; } // row(i) retrieves the i'th row 117 | template mat apply(const mat & r, F f) const { return {x.apply(r.x,f), y.apply(r.y,f), z.apply(r.z,f)}; } 118 | template mat apply(T r, F f) const { return {x.apply(r,f), y.apply(r,f), z.apply(r,f)}; } 119 | }; 120 | 121 | template struct mat 122 | { 123 | typedef vec U; 124 | U x, y, z, w; 125 | mat() : x(), y(), z(), w() {} 126 | mat(U x, U y, U z, U w) : x(x), y(y), z(z), w(w) {} 127 | U & operator [] (int j) { return (&x)[j]; } // M[j] retrieves the j'th column 128 | const U & operator [] (int j) const { return (&x)[j]; } // M[j] retrieves the j'th column 129 | T & operator () (int i, int j) { return (*this)[j][i]; } // M(i,j) retrieves the i'th row of the j'th column 130 | const T & operator () (int i, int j) const { return (*this)[j][i]; } // M(i,j) retrieves the i'th row of the j'th column 131 | vec row(int i) const { return {x[i],y[i],z[i],w[i]}; } // row(i) retrieves the i'th row 132 | template mat apply(const mat & r, F f) const { return {x.apply(r.x,f), y.apply(r.y,f), z.apply(r.z,f), w.apply(r.w,f)}; } 133 | template mat apply(T r, F f) const { return {x.apply(r,f), y.apply(r,f), z.apply(r,f), w.apply(r,f)}; } 134 | }; 135 | 136 | template mat operator - (const mat & a) { return a.apply(T(), [](T x, T) { return -x; }); } 137 | template mat operator + (const mat & a, const mat & b) { return a.apply(b, [](T a, T b) { return a+b; }); } 138 | template mat operator - (const mat & a, const mat & b) { return a.apply(b, [](T a, T b) { return a-b; }); } 139 | template mat operator * (const mat & a, T b) { return a.apply(b, [](T a, T b) { return a*b; }); } 140 | template mat operator / (const mat & a, T b) { return a.apply(b, [](T a, T b) { return a/b; }); } 141 | template mat & operator += (mat & a, const mat & b) { return a=a+b; } 142 | template mat & operator -= (mat & a, const mat & b) { return a=a-b; } 143 | template mat & operator *= (mat & a, T b) { return a=a*b; } 144 | template mat & operator /= (mat & a, T b) { return a=a/b; } 145 | 146 | template mat adj (const mat & a) { return {{a.y.y, -a.x.y}, {-a.y.x, a.x.x}}; } 147 | template mat adj (const mat & a); // Definition deferred due to size 148 | template mat adj (const mat & a); // Definition deferred due to size 149 | template T det (const mat & a) { return a.x.x*a.y.y - a.x.y*a.y.x; } 150 | template T det (const mat & a) { return a.x.x*(a.y.y*a.z.z - a.z.y*a.y.z) + a.x.y*(a.y.z*a.z.x - a.z.z*a.y.x) + a.x.z*(a.y.x*a.z.y - a.z.x*a.y.y); } 151 | template T det (const mat & a); // Definition deferred due to size 152 | template mat inv (const mat & a) { return adj(a)/det(a); } 153 | template vec mul (const mat & a, const vec & b) { return a.x*b.x + a.y*b.y; } 154 | template vec mul (const mat & a, const vec & b) { return a.x*b.x + a.y*b.y + a.z*b.z; } 155 | template vec mul (const mat & a, const vec & b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; } 156 | template mat mul (const mat & a, const mat & b) { return {mul(a,b.x), mul(a,b.y)}; } 157 | template mat mul (const mat & a, const mat & b) { return {mul(a,b.x), mul(a,b.y), mul(a,b.z)}; } 158 | template mat mul (const mat & a, const mat & b) { return {mul(a,b.x), mul(a,b.y), mul(a,b.z), mul(a,b.w)}; } 159 | template mat transpose(const mat & a) { return {a.row(0), a.row(1)}; } 160 | template mat transpose(const mat & a) { return {a.row(0), a.row(1), a.row(2)}; } 161 | template mat transpose(const mat & a) { return {a.row(0), a.row(1), a.row(2), a.row(3)}; } 162 | 163 | typedef vec byte2; typedef vec ubyte2; typedef vec short2; typedef vec ushort2; 164 | typedef vec byte3; typedef vec ubyte3; typedef vec short3; typedef vec ushort3; 165 | typedef vec byte4; typedef vec ubyte4; typedef vec short4; typedef vec ushort4; 166 | typedef vec int2; typedef vec uint2; typedef vec long2; typedef vec ulong2; 167 | typedef vec int3; typedef vec uint3; typedef vec long3; typedef vec ulong3; 168 | typedef vec int4; typedef vec uint4; typedef vec long4; typedef vec ulong4; 169 | typedef vec float2; typedef mat float2x2; typedef mat float2x3; typedef mat float2x4; 170 | typedef vec float3; typedef mat float3x2; typedef mat float3x3; typedef mat float3x4; 171 | typedef vec float4; typedef mat float4x2; typedef mat float4x3; typedef mat float4x4; 172 | typedef vec double2; typedef mat double2x2; typedef mat double2x3; typedef mat double2x4; 173 | typedef vec double3; typedef mat double3x2; typedef mat double3x3; typedef mat double3x4; 174 | typedef vec double4; typedef mat double4x2; typedef mat double4x3; typedef mat double4x4; 175 | 176 | // Definitions of functions which do not fit on a single line 177 | template mat adj(const mat & a) { return { 178 | {a.y.y*a.z.z - a.z.y*a.y.z, a.z.y*a.x.z - a.x.y*a.z.z, a.x.y*a.y.z - a.y.y*a.x.z}, 179 | {a.y.z*a.z.x - a.z.z*a.y.x, a.z.z*a.x.x - a.x.z*a.z.x, a.x.z*a.y.x - a.y.z*a.x.x}, 180 | {a.y.x*a.z.y - a.z.x*a.y.y, a.z.x*a.x.y - a.x.x*a.z.y, a.x.x*a.y.y - a.y.x*a.x.y}}; 181 | } 182 | template mat adj(const mat & a) { return { 183 | {a.y.y*a.z.z*a.w.w + a.w.y*a.y.z*a.z.w + a.z.y*a.w.z*a.y.w - a.y.y*a.w.z*a.z.w - a.z.y*a.y.z*a.w.w - a.w.y*a.z.z*a.y.w, 184 | a.x.y*a.w.z*a.z.w + a.z.y*a.x.z*a.w.w + a.w.y*a.z.z*a.x.w - a.w.y*a.x.z*a.z.w - a.z.y*a.w.z*a.x.w - a.x.y*a.z.z*a.w.w, 185 | a.x.y*a.y.z*a.w.w + a.w.y*a.x.z*a.y.w + a.y.y*a.w.z*a.x.w - a.x.y*a.w.z*a.y.w - a.y.y*a.x.z*a.w.w - a.w.y*a.y.z*a.x.w, 186 | a.x.y*a.z.z*a.y.w + a.y.y*a.x.z*a.z.w + a.z.y*a.y.z*a.x.w - a.x.y*a.y.z*a.z.w - a.z.y*a.x.z*a.y.w - a.y.y*a.z.z*a.x.w}, 187 | {a.y.z*a.w.w*a.z.x + a.z.z*a.y.w*a.w.x + a.w.z*a.z.w*a.y.x - a.y.z*a.z.w*a.w.x - a.w.z*a.y.w*a.z.x - a.z.z*a.w.w*a.y.x, 188 | a.x.z*a.z.w*a.w.x + a.w.z*a.x.w*a.z.x + a.z.z*a.w.w*a.x.x - a.x.z*a.w.w*a.z.x - a.z.z*a.x.w*a.w.x - a.w.z*a.z.w*a.x.x, 189 | a.x.z*a.w.w*a.y.x + a.y.z*a.x.w*a.w.x + a.w.z*a.y.w*a.x.x - a.x.z*a.y.w*a.w.x - a.w.z*a.x.w*a.y.x - a.y.z*a.w.w*a.x.x, 190 | a.x.z*a.y.w*a.z.x + a.z.z*a.x.w*a.y.x + a.y.z*a.z.w*a.x.x - a.x.z*a.z.w*a.y.x - a.y.z*a.x.w*a.z.x - a.z.z*a.y.w*a.x.x}, 191 | {a.y.w*a.z.x*a.w.y + a.w.w*a.y.x*a.z.y + a.z.w*a.w.x*a.y.y - a.y.w*a.w.x*a.z.y - a.z.w*a.y.x*a.w.y - a.w.w*a.z.x*a.y.y, 192 | a.x.w*a.w.x*a.z.y + a.z.w*a.x.x*a.w.y + a.w.w*a.z.x*a.x.y - a.x.w*a.z.x*a.w.y - a.w.w*a.x.x*a.z.y - a.z.w*a.w.x*a.x.y, 193 | a.x.w*a.y.x*a.w.y + a.w.w*a.x.x*a.y.y + a.y.w*a.w.x*a.x.y - a.x.w*a.w.x*a.y.y - a.y.w*a.x.x*a.w.y - a.w.w*a.y.x*a.x.y, 194 | a.x.w*a.z.x*a.y.y + a.y.w*a.x.x*a.z.y + a.z.w*a.y.x*a.x.y - a.x.w*a.y.x*a.z.y - a.z.w*a.x.x*a.y.y - a.y.w*a.z.x*a.x.y}, 195 | {a.y.x*a.w.y*a.z.z + a.z.x*a.y.y*a.w.z + a.w.x*a.z.y*a.y.z - a.y.x*a.z.y*a.w.z - a.w.x*a.y.y*a.z.z - a.z.x*a.w.y*a.y.z, 196 | a.x.x*a.z.y*a.w.z + a.w.x*a.x.y*a.z.z + a.z.x*a.w.y*a.x.z - a.x.x*a.w.y*a.z.z - a.z.x*a.x.y*a.w.z - a.w.x*a.z.y*a.x.z, 197 | a.x.x*a.w.y*a.y.z + a.y.x*a.x.y*a.w.z + a.w.x*a.y.y*a.x.z - a.x.x*a.y.y*a.w.z - a.w.x*a.x.y*a.y.z - a.y.x*a.w.y*a.x.z, 198 | a.x.x*a.y.y*a.z.z + a.z.x*a.x.y*a.y.z + a.y.x*a.z.y*a.x.z - a.x.x*a.z.y*a.y.z - a.y.x*a.x.y*a.z.z - a.z.x*a.y.y*a.x.z}}; 199 | } 200 | template T det(const mat & a) { return 201 | a.x.x*(a.y.y*a.z.z*a.w.w + a.w.y*a.y.z*a.z.w + a.z.y*a.w.z*a.y.w - a.y.y*a.w.z*a.z.w - a.z.y*a.y.z*a.w.w - a.w.y*a.z.z*a.y.w) + 202 | a.x.y*(a.y.z*a.w.w*a.z.x + a.z.z*a.y.w*a.w.x + a.w.z*a.z.w*a.y.x - a.y.z*a.z.w*a.w.x - a.w.z*a.y.w*a.z.x - a.z.z*a.w.w*a.y.x) + 203 | a.x.z*(a.y.w*a.z.x*a.w.y + a.w.w*a.y.x*a.z.y + a.z.w*a.w.x*a.y.y - a.y.w*a.w.x*a.z.y - a.z.w*a.y.x*a.w.y - a.w.w*a.z.x*a.y.y) + 204 | a.x.w*(a.y.x*a.w.y*a.z.z + a.z.x*a.y.y*a.w.z + a.w.x*a.z.y*a.y.z - a.y.x*a.z.y*a.w.z - a.w.x*a.y.y*a.z.z - a.z.x*a.w.y*a.y.z); 205 | } 206 | 207 | #endif -------------------------------------------------------------------------------- /src/engine/load.cpp: -------------------------------------------------------------------------------- 1 | #include "load.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | Mesh LoadMeshFromObj(const std::string & filepath, bool swapYZ) 8 | { 9 | 10 | std::ifstream in(filepath); 11 | if(!in) throw std::runtime_error("File not found: " + filepath); 12 | 13 | std::vector vertices; 14 | std::map indices; 15 | std::vector triangles; 16 | 17 | std::vector positions; 18 | std::vector texCoords, normals; 19 | std::string line; 20 | while(in) 21 | { 22 | std::getline(in, line); 23 | if(!in) break; 24 | 25 | std::istringstream inLine(line); 26 | std::string token; 27 | inLine >> token; 28 | if(token == "v") 29 | { 30 | float4 position = {0,0,0,1}; 31 | inLine >> position.x >> position.y >> position.z >> position.w; 32 | positions.push_back(position); 33 | } 34 | if(token == "vt") 35 | { 36 | float3 texCoord = {0,0,0}; 37 | inLine >> texCoord.x >> texCoord.y >> texCoord.z; 38 | texCoords.push_back(texCoord); 39 | } 40 | if(token == "vn") 41 | { 42 | float3 normal = {0,0,0}; 43 | inLine >> normal.x >> normal.y >> normal.z; 44 | normals.push_back(norm(normal)); 45 | } 46 | 47 | if(token == "f") 48 | { 49 | std::vector faceIndices; 50 | while(inLine) 51 | { 52 | inLine >> token; 53 | if(!inLine) break; 54 | auto it = indices.find(token); 55 | if(it != end(indices)) 56 | { 57 | faceIndices.push_back(it->second); 58 | continue; 59 | } 60 | indices[token] = vertices.size(); 61 | faceIndices.push_back(vertices.size()); 62 | 63 | Vertex vertex; 64 | bool skipTexCoords = token.find("//") != std::string::npos; 65 | for(auto & ch : token) if(ch == '/') ch = ' '; 66 | int i0=0, i1=0, i2=0; 67 | std::istringstream(token) >> i0 >> i1 >> i2; 68 | if(skipTexCoords) std::swap(i1, i2); 69 | if(i0) vertex.position = positions[i0-1].xyz(); 70 | if(i1) vertex.texCoord = texCoords[i1-1].xy(); 71 | if(i2) vertex.normal = normals[i2-1]; 72 | vertices.push_back(vertex); 73 | } 74 | 75 | for(size_t i=2; i vertices; 12 | std::vector triangles; 13 | gl::Mesh glMesh; 14 | 15 | Mesh() {} 16 | Mesh(Mesh && m) : Mesh() { *this = std::move(m); } 17 | Mesh & operator = (Mesh && m) { vertices=move(m.vertices); triangles=move(m.triangles); glMesh=std::move(m.glMesh); return *this; } 18 | 19 | RayMeshHit Hit(const Ray & ray) const { return IntersectRayMesh(ray, vertices.data(), &Vertex::position, triangles.data(), triangles.size()); } 20 | 21 | void Upload(); 22 | void Draw() const; 23 | 24 | void ComputeNormals() 25 | { 26 | for(auto & vert : vertices) vert.normal = float3(0,0,0); 27 | for(auto & tri : triangles) 28 | { 29 | auto & v0 = vertices[tri.x], & v1 = vertices[tri.y], & v2 = vertices[tri.z]; 30 | auto n = cross(v1.position - v0.position, v2.position - v0.position); 31 | v0.normal += n; v1.normal += n; v2.normal += n; 32 | } 33 | for(auto & vert : vertices) vert.normal = norm(vert.normal); 34 | } 35 | 36 | void AddCylinder(const float3 & center0, float radius0, const float3 & center1, float radius1, const float3 & axisA, const float3 & axisB, int segments) 37 | { 38 | uint32_t base = vertices.size(); 39 | for(uint32_t i=0, n=segments; i void operator() (const char * name, const U & field) { auto val = j.Save(field); if(!val.isNull()) value.push_back({name, val}); } }; 12 | public: 13 | JsonSerializer() {} 14 | 15 | JsonValue Save(const bool & object) { return object; } 16 | JsonValue Save(const std::string & object) { return object; } 17 | template std::enable_if_t::value, JsonValue> Save(const T & object) { return object; } 18 | template JsonValue Save(const vec & object) { return JsonArray{object.x, object.y}; } 19 | template JsonValue Save(const vec & object) { return JsonArray{object.x, object.y, object.z}; } 20 | template JsonValue Save(const vec & object) { return JsonArray{object.x, object.y, object.z, object.w}; } 21 | JsonValue Save(const Pose & object) { return JsonArray{Save(object.position), Save(object.orientation)}; } 22 | template JsonValue Save(const std::unique_ptr & object) { return object ? Save(*object) : nullptr; } 23 | template JsonValue Save(const std::shared_ptr & object) { return object ? Save(*object) : nullptr; } 24 | template JsonValue Save(const std::vector & object) { JsonArray a; for(auto & elem : object) a.push_back(Save(elem)); return a; } 25 | template JsonValue Save(const AssetLibrary::Handle & object) { return object ? object.GetId() : nullptr; } 26 | template std::enable_if_t::value, JsonValue> Save(const T & object) { JsonObject o; VisitFields((T&)object, Visitor{*this,o}); return o; } 27 | }; 28 | 29 | class JsonDeserializer 30 | { 31 | struct Visitor { JsonDeserializer & j; const JsonValue & value; template void operator() (const char * name, U & field) { j.Load(field, value[name]); } }; 32 | AssetLibrary & assets; 33 | public: 34 | JsonDeserializer(AssetLibrary & assets) : assets(assets) {} 35 | 36 | void Load(bool & object, const JsonValue & value) { object = value.isTrue(); } 37 | void Load(std::string & object, const JsonValue & value) { object = value.string(); } 38 | template std::enable_if_t::value, void> Load(T & object, const JsonValue & value) { object = value.number(); } 39 | template void Load(vec & object, const JsonValue & value) { Load(object.x, value[0]); Load(object.y, value[1]); } 40 | template void Load(vec & object, const JsonValue & value) { Load(object.x, value[0]); Load(object.y, value[1]); Load(object.z, value[2]); } 41 | template void Load(vec & object, const JsonValue & value) { Load(object.x, value[0]); Load(object.y, value[1]); Load(object.z, value[2]); Load(object.w, value[3]); } 42 | void Load(Pose & object, const JsonValue & value) { Load(object.position, value[0]); Load(object.orientation, value[1]); } 43 | template void Load(std::unique_ptr & object, const JsonValue & value) { if(value.isObject()) { object = std::make_unique(); Load(*object, value); } else object.reset(); } 44 | template void Load(std::shared_ptr & object, const JsonValue & value) { if(value.isObject()) { object = std::make_shared(); Load(*object, value); } else object.reset(); } 45 | template void Load(std::vector & object, const JsonValue & value) { object.clear(); object.resize(value.array().size()); for(size_t i=0; i void Load(AssetLibrary::Handle & object, const JsonValue & value) { object = assets.GetAsset(value.string()); } 47 | template std::enable_if_t::value, void> Load(T & object, const JsonValue & value) { VisitFields(object, Visitor{*this,value}); } 48 | }; 49 | 50 | template JsonValue SerializeToJson(const T & object) 51 | { 52 | return JsonSerializer().Save(object); 53 | } 54 | 55 | template T DeserializeFromJson(const JsonValue & value, AssetLibrary & assets) 56 | { 57 | T object; 58 | JsonDeserializer(assets).Load(object, value); 59 | return object; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/engine/transform.cpp: -------------------------------------------------------------------------------- 1 | #include "transform.h" 2 | 3 | float4 RotationQuaternionAxisAngle(const float3 & axisOfRotation, float angleInRadians) 4 | { 5 | return {axisOfRotation * std::sin(angleInRadians/2), std::cos(angleInRadians/2)}; 6 | } 7 | 8 | float4 RotationQuaternionFromToVec(const float3 & fromVector, const float3 & toVector) 9 | { 10 | auto a = norm(fromVector), b = norm(toVector); 11 | return RotationQuaternionAxisAngle(normz(cross(a,b)), std::acos(dot(a,b))); 12 | } 13 | 14 | float4x4 TranslationMatrix(const float3 & translation) 15 | { 16 | return {{1,0,0,0},{0,1,0,0},{0,0,1,0},{translation,1}}; 17 | } 18 | 19 | float4x4 RigidTransformationMatrix(const float4 & rot, const float3 & vec) 20 | { 21 | return {{qxdir(rot),0},{qydir(rot),0},{qzdir(rot),0},{vec,1}}; 22 | } 23 | 24 | float4x4 ScaledTransformationMatrix(const float3 & scale, const float4 & rot, const float3 & vec) 25 | { 26 | return {{qxdir(rot)*scale.x,0},{qydir(rot)*scale.y,0},{qzdir(rot)*scale.z,0},{vec,1}}; 27 | } 28 | 29 | float4x4 PerspectiveMatrixRhGl(float verticalFieldOfViewInRadians, float aspectRatioWidthOverHeight, float nearClipDistance, float farClipDistance) 30 | { 31 | const auto yf = 1/std::tan(verticalFieldOfViewInRadians/2), xf = yf/aspectRatioWidthOverHeight, dz = nearClipDistance-farClipDistance; 32 | return {{xf,0,0,0}, {0,yf,0,0}, {0,0,(nearClipDistance+farClipDistance)/dz,-1}, {0,0,2*nearClipDistance*farClipDistance/dz,0}}; 33 | } 34 | 35 | float4x4 LookAtMatrixRh(const float3 & eye, const float3 & center, const float3 & up) 36 | { 37 | auto f = norm(center - eye), s = norm(cross(f, up)), u = norm(cross(s, f)); 38 | return mul(transpose(float4x4({s,0},{u,0},{-f,0},{0,0,0,1})), TranslationMatrix(-eye)); 39 | } -------------------------------------------------------------------------------- /src/engine/transform.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_TRANSFORM_H 2 | #define ENGINE_TRANSFORM_H 3 | 4 | #include "linalg.h" 5 | 6 | inline float3 TransformDirection(const float4x4 & transformMatrix, const float3 & direction) { return mul(transformMatrix, float4(direction,0)).xyz(); } // Works for rigid transforms only 7 | inline float3 TransformCoordinate(const float4x4 & transformMatrix, const float3 & coordinate) { auto r = mul(transformMatrix, float4(coordinate,1)); return r.xyz() / r.w; } 8 | 9 | float4 RotationQuaternionAxisAngle(const float3 & axisOfRotation, float angleInRadians); 10 | float4 RotationQuaternionFromToVec(const float3 & fromVector, const float3 & toVector); 11 | 12 | float4x4 TranslationMatrix(const float3 & translationVec); 13 | float4x4 RigidTransformationMatrix(const float4 & rotationQuat, const float3 & translationVec); 14 | float4x4 ScaledTransformationMatrix(const float3 & scalingFactors, const float4 & rotationQuat, const float3 & translationVec); 15 | float4x4 PerspectiveMatrixRhGl(float verticalFieldOfViewInRadians, float aspectRatioWidthOverHeight, float nearClipDistance, float farClipDistance); 16 | float4x4 LookAtMatrixRh(const float3 & eye, const float3 & center, const float3 & up); 17 | 18 | struct Pose 19 | { 20 | float3 position; 21 | float4 orientation; 22 | 23 | Pose(const float3 & pos, const float4 & ori) : position(pos), orientation(ori) {} 24 | Pose() : Pose({0,0,0}, {0,0,0,1}) {} 25 | Pose(const float3 & position) : Pose(position, {0,0,0,1}) {} 26 | Pose(const float4 & orientation) : Pose({0,0,0}, orientation) {} 27 | 28 | float3 TransformVector(const float3 & vec) const { return qrot(orientation, vec); } 29 | float3 TransformCoord(const float3 & coord) const { return position + TransformVector(coord); } 30 | 31 | Pose operator * (const Pose & pose) const { return {TransformCoord(pose.position), qmul(orientation,pose.orientation)}; } 32 | float3 operator * (const float3 & coord) const { return TransformCoord(coord); } 33 | 34 | Pose Inverse() const { auto invOri = qinv(orientation); return {qrot(invOri, -position), invOri}; } 35 | float4x4 Matrix() const { return RigidTransformationMatrix(orientation, position); } 36 | float3 Xdir() const { return qxdir(orientation); } // Equivalent to TransformVector({1,0,0}) 37 | float3 Ydir() const { return qydir(orientation); } // Equivalent to TransformVector({0,1,0}) 38 | float3 Zdir() const { return qzdir(orientation); } // Equivalent to TransformVector({0,0,1}) 39 | }; 40 | 41 | #endif -------------------------------------------------------------------------------- /src/engine/utf8.cpp: -------------------------------------------------------------------------------- 1 | #include "utf8.h" 2 | 3 | namespace utf8 4 | { 5 | int get_code_length(uint8_t byte) 6 | { 7 | if(byte < 0x80) return 1; 8 | if(byte < 0xC0) return 0; 9 | if(byte < 0xE0) return 2; 10 | if(byte < 0xF0) return 3; 11 | if(byte < 0xF8) return 4; 12 | return 0; 13 | } 14 | 15 | bool is_continuation_byte(uint8_t byte) 16 | { 17 | return byte >= 0x80 && byte < 0xC0; 18 | } 19 | 20 | const char * prev(const char * units) 21 | { 22 | do { --units; } while(is_continuation_byte(*units)); 23 | return units; 24 | } 25 | 26 | const char * next(const char * units) 27 | { 28 | return units + get_code_length(*units); 29 | } 30 | 31 | uint32_t code(const char * units) 32 | { 33 | static const uint8_t masks[] = {0, 0x7F, 0x1F, 0x0F, 0x07}; 34 | auto length = get_code_length(*units); 35 | uint32_t codepoint = units[0] & masks[length]; 36 | for(int i=1; i units(uint32_t code) 44 | { 45 | if(code < 0x80) return {{code}}; 46 | if(code < 0x800) return {{0xC0|((code>>6)&0x1F), 0x80|(code&0x3F)}}; 47 | if(code < 0x10000) return {{0xE0|((code>>12)&0x0F), 0x80|((code>>6)&0x3F), 0x80|(code&0x3F)}}; 48 | return {{0xF0|((code>>18)&0x07), 0x80|((code>>12)&0x3F), 0x80|((code>>6)&0x3F), 0x80|(code&0x3F)}}; 49 | } 50 | 51 | bool is_valid(const char * units, size_t count) 52 | { 53 | auto end = units + count; 54 | while(units != end) 55 | { 56 | auto length = get_code_length(*units++); 57 | if(length == 0) return false; 58 | for(int i=1; i 5 | #include 6 | 7 | namespace utf8 8 | { 9 | const char * prev(const char * units); // Assumes units points just past the end of a valid utf-8 sequence of code units 10 | const char * next(const char * units); // Assumes units points to the start of a valid utf-8 sequence of code units 11 | uint32_t code(const char * units); // Assumes units points to the start of a valid utf-8 sequence of code units 12 | std::array units(uint32_t code); // Assumes code < 0x110000 13 | bool is_valid(const char * units, size_t count); // Return true if the given sequence of code units is valid utf-8 14 | } 15 | 16 | #endif --------------------------------------------------------------------------------