├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── rlobj.c ├── rlobj.h └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-debug 3 | assets 4 | 5 | **/.DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "raylib"] 2 | path = raylib 3 | url = https://github.com/raysan5/raylib 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | project(rlobj C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 7 | add_subdirectory(raylib) 8 | endif () 9 | 10 | add_library(rlobj rlobj.c rlobj.h) 11 | target_include_directories(rlobj PUBLIC .) 12 | target_link_libraries(rlobj raylib) 13 | 14 | add_executable(rlobj-test test.c) 15 | target_link_libraries(rlobj-test rlobj) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rlobj 2 | 3 | rlobj is a drop-in replacement for raylib's obj loader, which uses an outdated version of tinyobj-loader-c. 4 | 5 | ## Why a new loader? 6 | 7 | As mentioned above the old OBJ loader is outdated, because some design decisions in its newer versions 8 | make integration into raylib complicated. The old, currently in-use version has some bugs regarding 9 | material assignment and multiple objects within one OBJ file, which are fixed in this loader. Additionally, 10 | this loader speeds loading up by around 2.7 times, likely because it doesn't bother reading 11 | anything raylib can't use, instead of being as compliant to the standard as possible. 12 | 13 | # Licensing 14 | 15 | This software is subject to terms of the Mozilla Public License, v. 2.0, with exemptions for 16 | [Ramon Santamaria and the raylib contributors](https://github.com/raysan5/raylib/graphs/contributors) 17 | who may, at their discretion, instead license any of the Covered Software under the [zlib license](https://en.wikipedia.org/wiki/Zlib_License). 18 | -------------------------------------------------------------------------------- /rlobj.c: -------------------------------------------------------------------------------- 1 | // rlobj (c) Nikolas Wipper 2021 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0, with exemptions for Ramon Santamaria and the 5 | * raylib contributors who may, at their discretion, instead license 6 | * any of the Covered Software under the zlib license. If a copy of 7 | * the MPL was not distributed with this file, You can obtain one 8 | * at https://mozilla.org/MPL/2.0/. */ 9 | 10 | #include "rlobj.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | typedef struct Edge { int vertex; int texcoord; int normal; } Edge; 19 | typedef struct Face { Edge edges[3]; } Face; 20 | 21 | typedef struct OBJMesh { 22 | Mesh mesh; 23 | unsigned long mat_hash; 24 | } OBJMesh; 25 | 26 | typedef struct OBJMat { 27 | char *base; 28 | 29 | Vector3 ambient, diffuse, specular; 30 | float opacity; 31 | char *ambient_map, *diffuse_map, *specular_map, *highlight_map, *alpha_map, *bump_map, *displacement_map, *decal_map, *reflection_map; 32 | unsigned long name_hash; 33 | } OBJMat; 34 | 35 | typedef struct ValidFloat { float f; bool valid; } ValidFloat; 36 | typedef struct ValidInt { int i; bool valid; } ValidInt; 37 | typedef struct ValidEdge { Edge e; bool valid; } ValidEdge; 38 | typedef struct ValidVec3 { Vector3 v; bool valid; } ValidVec3; 39 | 40 | typedef struct GenericFile { 41 | char *data; 42 | } GenericFile; 43 | 44 | typedef struct OBJFile { 45 | char *base; 46 | 47 | GenericFile data; 48 | Vector3 *vertices, *normals; 49 | Vector2 *texcoords; 50 | Face *faces; 51 | 52 | int vertex_count; 53 | int texcoord_count; 54 | int normal_count; 55 | int face_count; 56 | 57 | bool triangulation_warning; 58 | 59 | OBJMat *mats; 60 | int mat_count; 61 | } OBJFile; 62 | 63 | unsigned long hash(unsigned char *str) { 64 | unsigned long hash = 5381; 65 | int c; 66 | 67 | while ((c = *str++)) 68 | hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ 69 | 70 | return hash; 71 | } 72 | 73 | char *PutStringOnHeap(const char *string) { 74 | size_t string_len = strlen(string); 75 | if (string_len == 0) return NULL; 76 | char *res = RL_CALLOC(strlen(string) + 1, sizeof(string)); 77 | strcpy(res, string); 78 | return res; 79 | } 80 | 81 | char *AddBase(char *path, const char *base) { 82 | if (path[0] == '/') return path; 83 | 84 | size_t path_len = strlen(path), base_len = strlen(base); 85 | path = RL_REALLOC(path, sizeof(char) * (path_len + base_len + 2)); 86 | memmove(path + base_len + 1, path, path_len + 1); 87 | memcpy(path, base, base_len); 88 | path[base_len] = '/'; 89 | return path; 90 | } 91 | 92 | // Generic reader functions 93 | 94 | void IgnoreLine(GenericFile *file) { 95 | for (; *file->data != '\n' && *file->data != '\0'; file->data++); 96 | file->data++; 97 | } 98 | 99 | // Clears all whitespaces 100 | // Returns false if the line ended 101 | // This helps prevent reading two lines as one statement 102 | bool ClearWhitespace(GenericFile *file) { 103 | for (; *file->data == '\n' || isspace(*file->data);) { 104 | if (*file->data == '\n') { // Don't read over unexpected line break, this prevents invalidation of the next line 105 | return false; 106 | } 107 | file->data++; 108 | } 109 | return true; 110 | } 111 | 112 | char *ReadName(GenericFile *file) { 113 | ClearWhitespace(file); 114 | 115 | char *data_cpy = file->data; 116 | for (; *data_cpy != '\n' && *data_cpy != '\0'; data_cpy++); 117 | char *name = RL_CALLOC(data_cpy - file->data + 1, sizeof(char)); 118 | 119 | int i; 120 | for (i = 0; !isspace(file->data[i]) && file->data[i] != '\0'; i++) { 121 | name[i] = file->data[i]; 122 | } 123 | file->data += i; 124 | return name; 125 | } 126 | 127 | float ReadFloat(GenericFile *file) { 128 | float res = 0.f, fact = 1.f; 129 | bool point_seen = false; 130 | 131 | if (*file->data == '-') { 132 | file->data++; 133 | fact = -1; 134 | } 135 | 136 | for (; *file->data; file->data++) { 137 | if (*file->data == '.') { 138 | point_seen = true; 139 | continue; 140 | } 141 | 142 | unsigned char c = *file->data - '0'; 143 | if (c > 9) break; // same as isdigit(*file->data), but faster because we can use c later 144 | if (point_seen) fact /= 10.f; 145 | res = res * 10.f + (float) c; 146 | } 147 | return res * fact; 148 | } 149 | 150 | ValidFloat ReadValidFloat(GenericFile *file) { 151 | if (!ClearWhitespace(file)) 152 | return (ValidFloat) {.f = 0.f, .valid = false}; 153 | float vX = ReadFloat(file); 154 | return (ValidFloat) {.f = vX, .valid = true}; 155 | } 156 | 157 | float ReadFloatDefault(GenericFile *file, float def) { 158 | if (ClearWhitespace(file) && isdigit(*file->data)) { 159 | def = ReadFloat(file); 160 | } 161 | return def; 162 | } 163 | 164 | int ReadInt(GenericFile *file) { 165 | int res = 0; 166 | for (; *file->data; file->data++) { 167 | unsigned char c = *file->data - '0'; 168 | if (c > 9) break; // same as isdigit(*file->data), but faster because we can use c later 169 | 170 | res = res * 10 + c; 171 | } 172 | return res; 173 | } 174 | 175 | ValidInt ReadValidInt(GenericFile *file) { 176 | if (!ClearWhitespace(file)) 177 | return (ValidInt) {.i = 0, .valid = false}; 178 | int i = ReadInt(file); 179 | return (ValidInt) {.i = i, .valid = true}; 180 | } 181 | 182 | int ReadIntDefault(GenericFile *file, int def) { 183 | if (ClearWhitespace(file) && isdigit(*file->data)) { 184 | def = ReadInt(file); 185 | } 186 | return def; 187 | } 188 | 189 | ValidVec3 ReadColor(GenericFile *file) { 190 | 191 | ValidFloat r = ReadValidFloat(file); 192 | ValidFloat g = ReadValidFloat(file); 193 | ValidFloat b = ReadValidFloat(file); 194 | 195 | if (r.valid && g.valid && b.valid) 196 | return (ValidVec3) {.v = {.x = r.f, .y = g.f, .z = b.f}, .valid = true}; 197 | 198 | return (ValidVec3) {.v = {0}, .valid = false}; 199 | } 200 | 201 | OBJMat LoadMtlMat(GenericFile *file) { 202 | bool seen_newmtl = false; 203 | 204 | OBJMat mat = {0}; 205 | mat.opacity = 1.f; 206 | 207 | while (*file->data) { 208 | if (*file->data == 'K') { 209 | file->data++; 210 | if (*file->data == 'a') { 211 | file->data++; 212 | ValidVec3 a = ReadColor(file); 213 | if (a.valid) mat.ambient = a.v; 214 | } else if (*file->data == 'd') { 215 | file->data++; 216 | ValidVec3 d = ReadColor(file); 217 | if (d.valid) mat.diffuse = d.v; 218 | } else if (*file->data == 's') { 219 | file->data++; 220 | ValidVec3 s = ReadColor(file); 221 | if (s.valid) mat.specular = s.v; 222 | } 223 | } else if (*file->data == 'd') { 224 | file->data++; 225 | ValidFloat o = ReadValidFloat(file); 226 | if (o.valid) mat.opacity = o.f; 227 | if (mat.opacity > 1) mat.opacity = 1; 228 | } else if (strncmp(file->data, "newmtl", 6) == 0) { 229 | if (seen_newmtl) break; 230 | seen_newmtl = true; 231 | file->data += 6; 232 | 233 | char *name = ReadName(file); 234 | mat.name_hash = hash((unsigned char *) name); 235 | RL_FREE(name); 236 | } else if (strncmp(file->data, "map_", 4) == 0) { 237 | file->data += 4; 238 | 239 | if (*file->data == 'K') { 240 | file->data++; 241 | if (*file->data == 'a') { 242 | file->data++; 243 | RL_FREE(mat.ambient_map); 244 | mat.ambient_map = ReadName(file); 245 | } else if (*file->data == 'd') { 246 | file->data++; 247 | RL_FREE(mat.diffuse_map); 248 | mat.diffuse_map = ReadName(file); 249 | } else if (*file->data == 's') { 250 | file->data++; 251 | RL_FREE(mat.specular_map); 252 | mat.specular_map = ReadName(file); 253 | } 254 | } else if (*file->data == 'd') { 255 | file->data++; 256 | RL_FREE(mat.alpha_map); 257 | mat.alpha_map = ReadName(file); 258 | } else if (strncmp(file->data, "Ns", 2) == 0) { 259 | file->data += 2; 260 | RL_FREE(mat.highlight_map); 261 | mat.highlight_map = ReadName(file); 262 | } else if (strncmp(file->data, "bump", 4) == 0 || strncmp(file->data, "Bump", 4) == 0) { // Somewhat bad practice, but acceptable for now 263 | file->data += 4; 264 | RL_FREE(mat.bump_map); 265 | mat.bump_map = ReadName(file); 266 | } 267 | } else if (strncmp(file->data, "bump", 4) == 0) { 268 | file->data += 4; 269 | RL_FREE(mat.bump_map); 270 | mat.bump_map = ReadName(file); 271 | } else if (strncmp(file->data, "disp", 4) == 0) { 272 | file->data += 4; 273 | RL_FREE(mat.displacement_map); 274 | mat.displacement_map = ReadName(file); 275 | } else if (strncmp(file->data, "decal", 5) == 0) { 276 | file->data += 5; 277 | RL_FREE(mat.decal_map); 278 | mat.decal_map = ReadName(file); 279 | } else if (strncmp(file->data, "refl", 4) == 0) { 280 | file->data += 4; 281 | RL_FREE(mat.reflection_map); 282 | mat.reflection_map = ReadName(file); 283 | } 284 | IgnoreLine(file); 285 | } 286 | 287 | return mat; 288 | } 289 | 290 | char *ReadMtl(OBJFile *file, char *filename) { 291 | if (file->base) filename = AddBase(filename, file->base); 292 | char *data = LoadFileText(filename); 293 | if (!data) { return filename; } 294 | 295 | GenericFile mtl = (GenericFile) {.data = data}; 296 | const char *base = GetPrevDirectoryPath(filename); 297 | 298 | while (*mtl.data) { 299 | file->mats = (OBJMat *) RL_REALLOC(file->mats, sizeof(OBJMat) * ++file->mat_count); 300 | file->mats[file->mat_count - 1] = LoadMtlMat(&mtl); 301 | file->mats[file->mat_count - 1].base = PutStringOnHeap(base); 302 | } 303 | 304 | UnloadFileText(data); 305 | return filename; 306 | } 307 | 308 | // OBJ specific reader functions 309 | 310 | void ReadVertex(OBJFile *file) { 311 | ValidFloat vX = ReadValidFloat(&file->data); 312 | ValidFloat vY = ReadValidFloat(&file->data); 313 | ValidFloat vZ = ReadValidFloat(&file->data); 314 | 315 | if (!(vX.valid && vY.valid && vZ.valid)) { return; } 316 | 317 | ReadFloatDefault(&file->data, 1.f); // value ignored 318 | 319 | file->vertices = (Vector3 *) RL_REALLOC(file->vertices, sizeof(Vector3) * ++file->vertex_count); 320 | int vc = file->vertex_count - 1; 321 | file->vertices[vc].x = vX.f; 322 | file->vertices[vc].y = vY.f; 323 | file->vertices[vc].z = vZ.f; 324 | } 325 | 326 | void ReadTextureCoord(OBJFile *file) { 327 | ValidFloat tU = ReadValidFloat(&file->data); 328 | 329 | float tV = ReadFloatDefault(&file->data, 0.f); 330 | ReadFloatDefault(&file->data, 0.f); // value ignored 331 | 332 | if (!tU.valid) { return; } 333 | 334 | file->texcoords = (Vector2 *) RL_REALLOC(file->texcoords, sizeof(Vector2) * ++file->texcoord_count); 335 | int tc = file->texcoord_count - 1; 336 | file->texcoords[tc].x = tU.f; 337 | file->texcoords[tc].y = tV; 338 | } 339 | 340 | void ReadNormal(OBJFile *file) { 341 | ValidFloat nX = ReadValidFloat(&file->data); 342 | ValidFloat nY = ReadValidFloat(&file->data); 343 | ValidFloat nZ = ReadValidFloat(&file->data); 344 | 345 | if (!(nX.valid && nY.valid && nZ.valid)) { return; } 346 | 347 | file->normals = (Vector3 *) RL_REALLOC(file->normals, sizeof(Vector3) * ++file->normal_count); 348 | int nc = file->normal_count - 1; 349 | file->normals[nc].x = nX.f; 350 | file->normals[nc].y = nY.f; 351 | file->normals[nc].z = nZ.f; 352 | } 353 | 354 | #define InvalidEdge (ValidEdge){(Edge){0,0,0}, false} 355 | 356 | ValidEdge ReadEdge(GenericFile *file) { 357 | Edge e = {0}; 358 | 359 | ValidInt v = ReadValidInt(file); 360 | if (!v.valid) { return InvalidEdge; } 361 | e.vertex = v.i; 362 | if (*file->data == '/') { 363 | file->data++; 364 | int vt = ReadIntDefault(file, 0); 365 | if (*file->data == '/') { 366 | file->data++; 367 | ValidInt vn = ReadValidInt(file); 368 | if (!vn.valid) { 369 | return InvalidEdge; 370 | } 371 | 372 | e.texcoord = vt == 0 ? 1 : vt; // a 0 index isn't valid, but not setting the value is 373 | e.normal = vn.i; 374 | } else { 375 | if (vt == 0) { // a 0 index isn't valid and a single '/' without a value isn't either 376 | return InvalidEdge; 377 | } 378 | e.texcoord = 1; 379 | e.normal = vt; 380 | } 381 | } 382 | 383 | return (ValidEdge) {e, true}; 384 | } 385 | 386 | void ReadFace(OBJFile *file) { 387 | Face f; 388 | 389 | for (int i = 0; i < 3; i++) { 390 | ValidEdge vE = ReadEdge(&file->data); 391 | if (vE.valid) 392 | f.edges[i] = vE.e; 393 | else return; 394 | } 395 | 396 | file->faces = (Face *) RL_REALLOC(file->faces, sizeof(Face) * ++file->face_count); 397 | int fc = file->face_count - 1; 398 | file->faces[fc] = f; 399 | 400 | ClearWhitespace(&file->data); 401 | 402 | // Naïve triangulation 403 | while (isdigit(*file->data.data)) { 404 | if (!file->triangulation_warning) { 405 | TraceLog(LOG_WARNING, "MESH: Triangulation is only very basic. Try doing that in your modeling software."); 406 | file->triangulation_warning = true; 407 | } 408 | file->faces = (Face *) RL_REALLOC(file->faces, sizeof(Face) * ++file->face_count); 409 | f.edges[1] = f.edges[2]; 410 | 411 | ValidEdge vE = ReadEdge(&file->data); 412 | if (vE.valid) { 413 | f.edges[2] = vE.e; 414 | f.edges[2] = vE.e; 415 | file->faces[++fc] = f; 416 | } else return; 417 | ClearWhitespace(&file->data); 418 | } 419 | } 420 | 421 | OBJMesh LoadObjMesh(OBJFile *file) { 422 | unsigned long mat_hash; 423 | bool seen_o = false; 424 | 425 | while (*file->data.data) { 426 | if (*file->data.data == 'v') { 427 | if (isspace(*(++file->data.data))) { 428 | file->data.data++; 429 | ReadVertex(file); 430 | } else if (*file->data.data == 't') { 431 | file->data.data++; 432 | ReadTextureCoord(file); 433 | } else if (*file->data.data == 'n') { 434 | file->data.data++; 435 | ReadNormal(file); 436 | } 437 | } else if (*file->data.data == 'f') { 438 | file->data.data++; 439 | ReadFace(file); 440 | } else if (*file->data.data == 'o') { 441 | if (seen_o) break; 442 | seen_o = true; 443 | } else if (strncmp(file->data.data, "usemtl", 6) == 0) { 444 | file->data.data += 6; 445 | char *name = ReadName(&file->data); 446 | mat_hash = hash((unsigned char *) name); 447 | RL_FREE(name); 448 | } else if (strncmp(file->data.data, "mtllib", 6) == 0) { 449 | file->data.data += 6; 450 | char *name = ReadName(&file->data); 451 | RL_FREE(ReadMtl(file, name)); 452 | } 453 | IgnoreLine(&file->data); 454 | } 455 | 456 | Mesh m = (Mesh) {0}; 457 | if (file->vertex_count != 0) { 458 | if (!file->texcoord_count || !file->texcoords) { 459 | file->texcoords = (Vector2 *) RL_CALLOC(file->vertex_count, sizeof(Vector2)); // all texcoords will be 0,0 460 | } 461 | if (!file->normal_count || !file->normals) { 462 | file->normals = (Vector3 *) RL_CALLOC(file->vertex_count, sizeof(Vector3)); // all normals will be 0,0,0 463 | } 464 | 465 | m.vertexCount = file->face_count * 3; 466 | m.triangleCount = (int) file->face_count; 467 | 468 | m.vertices = (float *) RL_CALLOC(m.vertexCount * 3, sizeof(float)); 469 | m.texcoords = (float *) RL_CALLOC(m.vertexCount * 2, sizeof(float)); 470 | m.normals = (float *) RL_CALLOC(m.vertexCount * 3, sizeof(float)); 471 | 472 | // Sort vertices, texcoords and normals 473 | for (int i = 0; i < file->face_count; i++) { 474 | for (int j = 0; j < 3; j++) { 475 | int vIn = file->faces[i].edges[j].vertex - 1; 476 | int tIn = file->faces[i].edges[j].texcoord - 1; 477 | int nIn = file->faces[i].edges[j].normal - 1; 478 | 479 | // Three vertices per face, three floats per vertex 480 | m.vertices[i * 9 + j * 3] = file->vertices[vIn].x; 481 | m.vertices[i * 9 + j * 3 + 1] = file->vertices[vIn].y; 482 | m.vertices[i * 9 + j * 3 + 2] = file->vertices[vIn].z; 483 | 484 | // Three coords per face, two floats per coords 485 | m.texcoords[i * 6 + j * 2] = file->texcoords[tIn].x; 486 | m.texcoords[i * 6 + j * 2 + 1] = 1.f - file->texcoords[tIn].y; // raylib flips textures upside down 487 | 488 | // Three normals per face, three floats per normal 489 | m.normals[i * 9 + j * 3] = file->normals[nIn].x; 490 | m.normals[i * 9 + j * 3 + 1] = file->normals[nIn].y; 491 | m.normals[i * 9 + j * 3 + 2] = file->normals[nIn].z; 492 | } 493 | } 494 | } 495 | return (OBJMesh) {.mesh = m, .mat_hash = mat_hash}; 496 | } 497 | 498 | Color Vector3ToColor(Vector3 vec, float opacity) { 499 | return (Color) { 500 | .r = (char) roundf(vec.x * 255.f), .g = (char) roundf(vec.y * 255.f), .b = (char) roundf(vec.z * 255.f), .a = (char) roundf(opacity * 255.f) 501 | }; 502 | } 503 | 504 | Texture LoadTextureBase(char *filename, char *base) { 505 | Texture res; 506 | if (base) filename = AddBase(filename, base); 507 | res = LoadTexture(filename); 508 | RL_FREE(filename); 509 | return res; 510 | } 511 | 512 | // Wrapper around read LoadObjMesh and LoadMtlMat 513 | // Loads model without uploading meshes 514 | Model LoadObjDry(const char *filename) { 515 | char *data = LoadFileText(filename); 516 | if (!data) return (Model) {0}; 517 | 518 | OBJMesh *meshes = NULL; 519 | int mesh_count = 0; 520 | 521 | OBJFile file = (OBJFile) {0}; 522 | file.data.data = data; 523 | file.base = PutStringOnHeap(GetPrevDirectoryPath(filename)); 524 | 525 | while (*file.data.data) { 526 | meshes = (OBJMesh *) RL_REALLOC(meshes, sizeof(OBJMesh) * ++mesh_count); 527 | meshes[mesh_count - 1] = LoadObjMesh(&file); 528 | file.face_count = 0; 529 | RL_FREE(file.faces); 530 | file.faces = NULL; 531 | } 532 | 533 | UnloadFileText(data); 534 | RL_FREE(file.vertices); 535 | RL_FREE(file.texcoords); 536 | RL_FREE(file.normals); 537 | 538 | Model model = {0}; 539 | 540 | model.transform = (Matrix) { 541 | 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f 542 | }; 543 | model.meshes = (Mesh *) RL_CALLOC(mesh_count, sizeof(Mesh)); 544 | model.meshCount = mesh_count; 545 | model.materialCount = file.mat_count; 546 | model.materials = RL_CALLOC(file.mat_count, sizeof(Material)); 547 | model.meshMaterial = RL_CALLOC(mesh_count, sizeof(int)); 548 | 549 | for (int i = 0; i < file.mat_count; i++) { 550 | Material m = LoadMaterialDefault(); 551 | OBJMat names = file.mats[i]; 552 | 553 | m.maps[MATERIAL_MAP_ALBEDO].color = Vector3ToColor(names.diffuse, names.opacity); 554 | m.maps[MATERIAL_MAP_METALNESS].color = Vector3ToColor(names.specular, names.opacity); 555 | 556 | // if-check here, to prevent replacing default textures 557 | // These are used to display .color values even if no image is present 558 | if (names.diffuse_map) m.maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureBase(names.diffuse_map, names.base); 559 | else 560 | RL_FREE(names.diffuse_map); 561 | // NOTE: I'm not totally sure which one is right, but for raylib specular is the same as "metalness" so that's what 562 | // we are using if both are defined 563 | if (names.reflection_map) m.maps[MATERIAL_MAP_METALNESS].texture = LoadTextureBase(names.reflection_map, names.base); 564 | else 565 | RL_FREE(names.reflection_map); 566 | if (names.specular_map) { 567 | if (rlGetTextureIdDefault() != m.maps[MATERIAL_MAP_METALNESS].texture.id) 568 | UnloadTexture(m.maps[MATERIAL_MAP_METALNESS].texture); 569 | m.maps[MATERIAL_MAP_METALNESS].texture = LoadTextureBase(names.specular_map, names.base); 570 | } else 571 | RL_FREE(names.specular_map); 572 | 573 | if (names.highlight_map) m.maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureBase(names.highlight_map, names.base); 574 | else 575 | RL_FREE(names.highlight_map); 576 | if (names.bump_map) m.maps[MATERIAL_MAP_NORMAL].texture = LoadTextureBase(names.bump_map, names.base); 577 | else 578 | RL_FREE(names.bump_map); 579 | 580 | // Free unused maps (used ones are freed in LoadTextureBase because of reallocates) 581 | RL_FREE(names.alpha_map); 582 | RL_FREE(names.ambient_map); 583 | RL_FREE(names.decal_map); 584 | RL_FREE(names.displacement_map); 585 | 586 | model.materials[i] = m; 587 | } 588 | 589 | for (int i = 0; i < mesh_count; i++) { 590 | for (int j = 0; j < file.mat_count; j++) { 591 | if (file.mats[j].name_hash == meshes[i].mat_hash) 592 | model.meshMaterial[i] = j; 593 | } 594 | model.meshes[i] = meshes[i].mesh; 595 | } 596 | 597 | RL_FREE(meshes); 598 | RL_FREE(file.mats); 599 | RL_FREE(file.base); 600 | 601 | return model; 602 | } 603 | 604 | // Wrapper around LoadObjDry 605 | // This basically does the same job as LoadMaterial does for LoadOBJ 606 | Model LoadObj(const char *filename) { 607 | Model obj = LoadObjDry(filename); 608 | 609 | if (obj.materialCount == 0) { 610 | obj.materialCount = 1; 611 | obj.materials = RL_CALLOC(1, sizeof(Material)); 612 | obj.materials[0] = LoadMaterialDefault(); 613 | } 614 | 615 | for (int i = 0; i < obj.meshCount; i++) { 616 | UploadMesh(&obj.meshes[i], false); 617 | } 618 | 619 | return obj; 620 | } 621 | -------------------------------------------------------------------------------- /rlobj.h: -------------------------------------------------------------------------------- 1 | // rlobj (c) Nikolas Wipper 2021 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0, with exemptions for Ramon Santamaria and the 5 | * raylib contributors who may, at their discretion, instead license 6 | * any of the Covered Software under the zlib license. If a copy of 7 | * the MPL was not distributed with this file, You can obtain one 8 | * at https://mozilla.org/MPL/2.0/. */ 9 | 10 | #ifndef RLOBJ_LIBRARY_H 11 | #define RLOBJ_LIBRARY_H 12 | 13 | #include 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | // Naming is important to avoid linker errors 20 | // raylib has a LoadOBJ 21 | Model LoadObj(const char *filename); 22 | 23 | #ifdef __cplusplus 24 | }; 25 | #endif 26 | 27 | #endif //RLOBJ_LIBRARY_H 28 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | // rlobj (c) Nikolas Wipper 2021 2 | 3 | #include "rlobj.h" 4 | #include "stdio.h" 5 | #include "raymath.h" 6 | 7 | typedef struct TwoTimes { 8 | double t1, t2; 9 | } TwoTimes; 10 | 11 | TwoTimes CompareLoading(const char *filename) { 12 | double start1 = GetTime(); 13 | Model model = LoadModel(filename); 14 | double total1 = GetTime() - start1; 15 | 16 | double start2 = GetTime(); 17 | Model model2 = LoadObj(filename); 18 | double total2 = GetTime() - start2; 19 | 20 | UnloadModel(model); 21 | UnloadModel(model2); 22 | 23 | return (TwoTimes) {.t1 = total1 * 1000000, .t2 = total2 * 1000000}; 24 | } 25 | 26 | void Bench() { 27 | // From "models" examples 28 | TwoTimes bridge = CompareLoading("raylib/examples/models/resources/models/bridge.obj"); 29 | TwoTimes castle = CompareLoading("raylib/examples/models/resources/models/castle.obj"); 30 | TwoTimes cube = CompareLoading("raylib/examples/models/resources/models/cube.obj"); 31 | TwoTimes house = CompareLoading("raylib/examples/models/resources/models/house.obj"); 32 | TwoTimes market = CompareLoading("raylib/examples/models/resources/models/market.obj"); 33 | TwoTimes turret = CompareLoading("raylib/examples/models/resources/models/turret.obj"); 34 | TwoTimes well = CompareLoading("raylib/examples/models/resources/models/well.obj"); 35 | 36 | // From "shaders" examples 37 | TwoTimes barracks = CompareLoading("raylib/examples/shaders/resources/models/barracks.obj"); 38 | TwoTimes church = CompareLoading("raylib/examples/shaders/resources/models/church.obj"); 39 | TwoTimes watermill = CompareLoading("raylib/examples/shaders/resources/models/barracks.obj"); 40 | 41 | // μ is counted as two characters by printf so the format args have to be one char longer than below 42 | printf("| %15s | %22s | %11s | %13s | %8s |\n", "model name", "tinyobj-loader-c (μs)", "rlobj (μs)", "diff (μs)", "speedup"); 43 | 44 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "bridge.obj", bridge.t1, bridge.t2, bridge.t2 - bridge.t1, bridge.t1 / bridge.t2); 45 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "castle.obj", castle.t1, castle.t2, castle.t2 - castle.t1, castle.t1 / castle.t2); 46 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "cube.obj", cube.t1, cube.t2, cube.t2 - cube.t1, cube.t1 / cube.t2); 47 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "house.obj", house.t1, house.t2, house.t2 - house.t1, house.t1 / house.t2); 48 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "market.obj", market.t1, market.t2, market.t2 - market.t1, market.t1 / market.t2); 49 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "turret.obj", turret.t1, turret.t2, turret.t2 - turret.t1, turret.t1 / turret.t2); 50 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "well.obj", well.t1, well.t2, well.t2 - well.t1, well.t1 / well.t2); 51 | 52 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "barracks.obj", barracks.t1, barracks.t2, barracks.t2 - barracks.t1, barracks.t1 / barracks.t2); 53 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "church.obj", church.t1, church.t2, church.t2 - church.t1, church.t1 / church.t2); 54 | printf("| %15s | %21.2f | %10.2f | %12.2f | %8.2f |\n", "watermill.obj", watermill.t1, watermill.t2, watermill.t2 - watermill.t1, watermill.t1 / watermill.t2); 55 | 56 | putchar('\n'); 57 | 58 | double t1 = bridge.t1 + castle.t1 + cube.t1 + house.t1 + market.t1 + turret.t1 + well.t1 + barracks.t1 + church.t1 + watermill.t1; 59 | double t2 = bridge.t2 + castle.t2 + cube.t2 + house.t2 + market.t2 + turret.t2 + well.t2 + barracks.t2 + church.t2 + watermill.t2; 60 | printf("time to load all models with tinyobj-loader-c: %.2f\n", t1); 61 | printf("time to load all models with rlobj: %.2f\n", t2); 62 | printf("total difference in microseconds: %.2f\n", t2 - t1); 63 | printf("total speedup: %.2f\n", t1 / t2); 64 | } 65 | 66 | int main(void) { 67 | // Initialization 68 | //-------------------------------------------------------------------------------------- 69 | const int screenWidth = 1280; 70 | const int screenHeight = 840; 71 | 72 | SetConfigFlags(FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT); 73 | SetTraceLogLevel(LOG_ERROR); 74 | 75 | InitWindow(screenWidth, screenHeight, "raylib [models] test - models loading"); 76 | 77 | SetTargetFPS(60); 78 | 79 | Camera camera = {0}; 80 | camera.position = (Vector3) {50.0f, 50.0f, 50.0f}; 81 | camera.target = (Vector3) {0.0f, 10.0f, 0.0f}; 82 | camera.up = (Vector3) {0.0f, 1.0f, 0.0f}; 83 | camera.fovy = 45.0f; 84 | camera.projection = CAMERA_PERSPECTIVE; 85 | 86 | Bench(); 87 | 88 | Model model = LoadObj("raylib/examples/models/resources/models/castle.obj"); 89 | 90 | Vector3 position = {0.0f, 0.0f, 0.0f}; 91 | 92 | SetCameraMode(camera, CAMERA_ORBITAL); 93 | 94 | while (!WindowShouldClose()) { 95 | // Update 96 | //---------------------------------------------------------------------------------- 97 | UpdateCamera(&camera); 98 | //---------------------------------------------------------------------------------- 99 | 100 | // Draw 101 | //---------------------------------------------------------------------------------- 102 | BeginDrawing(); 103 | 104 | ClearBackground(RAYWHITE); 105 | 106 | BeginMode3D(camera); 107 | 108 | DrawModel(model, position, 1.f, WHITE); 109 | 110 | DrawGrid(20, 10.0f); 111 | 112 | EndMode3D(); 113 | 114 | DrawFPS(10, 10); 115 | 116 | EndDrawing(); 117 | //---------------------------------------------------------------------------------- 118 | } 119 | 120 | // De-Initialization 121 | //-------------------------------------------------------------------------------------- 122 | UnloadModel(model); // Unload model 123 | 124 | CloseWindow(); // Close window and OpenGL context 125 | //-------------------------------------------------------------------------------------- 126 | 127 | return 0; 128 | } 129 | --------------------------------------------------------------------------------