├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build └── qt-installer.qs ├── examples ├── caves.v3m └── dungeon.v3m ├── manual ├── img │ ├── caves-01.png │ ├── editor-01.png │ ├── editor-02.png │ ├── editor-03.png │ ├── editor-generic.png │ ├── editor-modifying.png │ └── github-markdown.css └── manual.htm ├── screenshots ├── caves-01.png ├── dungeon-01.png ├── editor-01.png ├── editor-02.png ├── editor-03.png ├── editor-04.png ├── editor-generic.png ├── editor-modifying.png ├── editor-modifying.xcf └── screenshot-0.png └── src ├── data.qrc ├── data ├── camera-xy.png ├── camera-xz.png ├── camera-yz.png ├── close.png ├── content-copy.png ├── content-cut.png ├── content-paste.png ├── delete.png ├── export.png ├── flip-horizontal.png ├── flip-vertical.png ├── focus.png ├── github-circle.png ├── grid.png ├── home.png ├── import.png ├── information-variant.png ├── new.png ├── open.png ├── pixel.png ├── redo-variant.png ├── rotate-left.png ├── rotate-right.png ├── save.png ├── settings.png ├── tileset.png ├── tilesets │ ├── dungeon.png │ ├── roguelike-caves.png │ └── roguelike-city.png ├── tool-cursor.png ├── tool-flip.png ├── tool-move.png ├── tool-select.png ├── undo-variant.png └── web.png ├── gl3w ├── gl3w.pri ├── include │ └── GL │ │ ├── gl3w.h │ │ └── glcorearb.h └── src │ └── gl3w.c ├── glsl ├── fragment.glsl └── vertex.glsl ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── mesh.cpp ├── mesh.h ├── modeleditorview.cpp ├── modeleditorview.h ├── openspritesheetdialog.cpp ├── openspritesheetdialog.h ├── openspritesheetdialog.ui ├── optionsdialog.cpp ├── optionsdialog.h ├── optionsdialog.ui ├── tilesetviewer.cpp ├── tilesetviewer.h └── versa-tile.pro /.gitignore: -------------------------------------------------------------------------------- 1 | old/ 2 | *.pro.user 3 | raw/ 4 | *~ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: cpp 4 | compiler: gcc 5 | 6 | before_install: 7 | - sudo apt-get -qq update 8 | - sudo apt-get install -y libassimp-dev libglm-dev 9 | 10 | install: 11 | - wget https://download.qt.io/official_releases/qt/5.9/5.9.1/qt-opensource-linux-x64-5.9.1.run 12 | - chmod +x qt-opensource-linux-x64-5.9.1.run 13 | - ./qt-opensource-linux-x64-5.9.1.run --no-force-installations --script build/qt-installer.qs --platform minimal 14 | 15 | script: 16 | - export PATH="~/Qt/5.9.1/gcc_64/bin/:$PATH" 17 | - export LD_LIBRARY_PATH="~/Qt/5.9.1/gcc_64/lib/:$LD_LIBRARY_PATH" 18 | - cd build 19 | - ~/Qt/5.9.1/gcc_64/bin/qmake -version 20 | - ~/Qt/5.9.1/gcc_64/bin/qmake ../src/versa-tile.pro 21 | - make -B 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VersaTile 3D Model Editor 2 | 3 | [![Build Status](https://travis-ci.org/MasterQ32/VersaTile.svg?branch=master)](https://travis-ci.org/MasterQ32/VersaTile) 4 | 5 | ![Screenshot](https://puu.sh/wZtBW/e4b61754c2.png) 6 | 7 | *VersaTile* is a simple 3D model editor that allows 8 | editing models based on tilesets. 9 | 10 | Tiles can be placed on an axis-aligned plane and a 11 | model can be constructed this way. 12 | 13 | ## Features 14 | 15 | - Model editing 16 | - Undo/redo 17 | - Vertex displacement 18 | - Post-placement edit (move/rotate/mirror/displace/…) 19 | - Export via Assimp 20 | 21 | ## Usage 22 | 23 | The editor is mainly controlled with the mouse. On the right hand side is a side bar 24 | that shows the models sprite sheet and allows the selection of sprites. 25 | 26 | The center of the editor contains a 3D viewport that shows the model and allows editing. 27 | 28 | ### Creating a model 29 | 30 | When creating a new model, the following dialog appears: 31 | 32 | ![Create new model dialog](https://puu.sh/wZtEf/189a1e35fa.png) 33 | 34 | It provides several options that must be set up correctly to import a sprite sheet: 35 | 36 | - **File Name** 37 | This option is required and defines the file that should be used as the sprite sheet. 38 | - **Sprite Size** 39 | This option defines the size of the smallest tile that can be selected in the sprite 40 | sheet view. This also defines the grid size. 41 | - **Spritesheet Margin** 42 | If the sprite sheet image has a border around the contained sprites, set this value to 43 | the number of pixels this border is wide. 44 | - **Sprite Padding** 45 | If there is a padding between the single sprites in the sheet, set this value to the 46 | number of pixels this padding is wide. 47 | - **Preview** 48 | Here you can see a preview of the imported sprites. The magenta lines will disappear in 49 | the editor, but are displayed here for you to check if your setup of the sprite sheet 50 | is correct. 51 | 52 | ### Navigating in the Viewport 53 | 54 | ![Generic Controls](https://puu.sh/wZuey/352a19e855.png) 55 | 56 | *VersaTile* uses a grid aligned movement for the camera focus. This grid is shown 57 | as white lines and aligns itself to fit a comfortable insertion position. 58 | 59 | To rotate the camera, hold the **right mouse button** and drag. Horizontal movement 60 | rotates the camera around the focus point, vertical movement looks up and down. 61 | 62 | To move the focus point, press W,A,S,D,Q and E. W moves the camera forward horizontally, 63 | S moves it backwards. A moves to the left and B to the right. E moves the camera upwards 64 | and Q downwards. 65 | 66 | For a quick rotation of the camera, all shortcuts listed in the table below. 67 | 68 | If the option `Automatic adjust grid orientation` is set, the displayed grid will align 69 | with the camera so always the most convenient grid is active. If this option is disabled, 70 | the active grid can be changed by pressing **G** or the **Toggle Grid Plane** button. 71 | 72 | ### Inserting a tile 73 | To insert a tile, select the sprite on the right side by clicking and dragging until 74 | the wanted portion is selected. Now a tile can be inserted in the 3D viewport on 75 | the white grid. 76 | 77 | When inserting, the tile snaps to the grid unless the **Shift** key is pressed, then 78 | a pixel perfect insertion can be done. 79 | 80 | When a good position is chosen, a left click will insert the tile at the previewed 81 | position. Then, another tile can be inserted. To insert a different tile, just 82 | select another sprite in the right panel. To end tile insertion, press **Space** or 83 | use the **Selection mode** button in the toolbar. 84 | 85 | ### Modifying an existing tile 86 | 87 | ![Modify Display](https://puu.sh/wZudI/c796116a40.png) 88 | 89 | Tiles can be modified in different ways: 90 | 91 | #### Translate / Move 92 | Click and drag the tiles center gizmo to move the tile on its own placement 93 | plane. You can move the tile up,down,left and right. 94 | 95 | #### Rotate Left / Right 96 | Click the corresponding button in the toolbar or menu to rotate the tile around 97 | its upper left corner. 98 | 99 | #### Flip Horizontal / Vertical 100 | Click the corresponding button in the toolbar or menu to mirror the contents of 101 | the tile. 102 | 103 | #### Displace Vertex 104 | This feature allows you to displace a vertex to the tiles own plane. Click and 105 | drag a vertex gizmo to displace it along the tiles normal. 106 | 107 | This can be used to achieve slopes and skewed tiles. 108 | 109 | #### Delete 110 | Click the button on the toolbar or press **Delete** to remove the tile from the 111 | model. 112 | 113 | ## Controls 114 | 115 | | Key | Function | 116 | |--------------|--------------------------------------------------------------| 117 | | W,A,S,D | Move camera focus horizontal | 118 | | Q,E | Move camera focus up/down | 119 | | Space | Cancel/Stop tile insertion | 120 | | H | Flip horizontal | 121 | | V | Flip vertical | 122 | | R,Ctrl+R | Rotate tile | 123 | | Shift | Enable fine grid snapping (pixel perfect) | 124 | | Num-2 | Rotate camera down 90° | 125 | | Num-8 | Rotate camera up 90° | 126 | | Num-4 | Rotate camera left 90° | 127 | | Num-6 | Rotate camera right 90° | 128 | | Num-Plus | Zoom camera in | 129 | | Num-Minus | Zoom camera out | 130 | | Right Mouse | Drag to rotate camera | 131 | | Left Mouse | Click to select, modify or insert tile | 132 | | Delete | Remove the selected tile | 133 | | Ctrl+Z | Undo the last modification | 134 | | Ctrl+Y | Redo the last undone modification | 135 | | Ctrl+H | Rotate camera into **Home** position | 136 | | Ctrl+T | Rotate camera into **Top** position | 137 | | Ctrl+F | Rotate camera into **Front** position | 138 | | Ctrl+G | Rotate camera into **Side** position | 139 | | Ctrl+Shift+H | Set camera home position | 140 | | G | When AutoGrid is disabled, selects the next grid option | 141 | | F | Moves the camera center to the currently selected tile. | 142 | 143 | ## Supported Export Formats 144 | *VersaTile* supports exporting the created 3D models with [Assimp](http://assimp.sourceforge.net/). 145 | Every format that can be exported by Assimp can also be used with *VersaTile*. 146 | 147 | On export, the **Y-axis** is **up** and the model is scaled in a way that a single 148 | texture pixel is equivalent to one model unit. 149 | 150 | So a model that is 64 pixels wide is also 64 units wide. 151 | 152 | ## Preferences 153 | 154 | In this dialog, you can set up your preferences on how *VeraTile* should behave. 155 | 156 | ### Behaviour 157 | 158 | ![Preferences dialog: Behaviour](https://puu.sh/wZtOm/c7513f9e86.png) 159 | 160 | #### Automatic adjust grid orientation 161 | When this option is active, the grid will align itself with the camera 162 | as soon as it is moved. 163 | 164 | #### Horizontal Grid Threshold 165 | This is a factor from `0.0`…`1.0` that scales the **y axis** when determining 166 | the current auto grid. 167 | 168 | The lower this is, the later the grid will align into "horizontal" mode, 169 | allowing more camera movement before swapping into horizontal mode. 170 | 171 | ### Display 172 | 173 | ![Preferences dialog: Display](https://puu.sh/wZtOY/36fa3cb9e2.png) 174 | 175 | #### Ground Display 176 | 177 | Determines the way a ground plane is shown. When **None**, no ground plane is 178 | visible, **Grid** is a static horizontal grid, even when the vertical one 179 | is shown and **Solid** is a solid, green surface that is supposed to resemble 180 | a grassy ground. 181 | 182 | #### Ground Size 183 | The extends of the ground in tiles. The larger this is, the larger the ground 184 | will be displayed. 185 | 186 | #### Show Coordinate Axis 187 | If this option is enabled, the coordinate axis are shown by three short lines in the 188 | colors red (x-axis), green (y-axis) and blue (z-axis). The intersection of the three 189 | lines is the current camera focus point. 190 | 191 | ## Credits 192 | 193 | Thanks for each [Crocotile3D](http://www.crocotile3d.com/) and [Sprytile](https://github.com/ChemiKhazi/Sprytile) 194 | for inspiring me to create *VersaTile*! 195 | 196 | Also thanks to [kenney.nl](http://kenney.nl/assets/roguelike-caves-dungeons) for delivering our default tile 197 | set! It suits this editor very much. 198 | 199 | ## More Screenshots 200 | 201 | ![](https://puu.sh/wYFlX/6b0fa22731.png) 202 | 203 | ![](https://puu.sh/wXGND/2a17f8ef45.png) 204 | 205 | -------------------------------------------------------------------------------- /build/qt-installer.qs: -------------------------------------------------------------------------------- 1 | // Emacs mode hint: -*- mode: JavaScript -*- 2 | 3 | function Controller() { 4 | installer.autoRejectMessageBoxes(); 5 | installer.installationFinished.connect(function() { 6 | gui.clickButton(buttons.NextButton); 7 | }) 8 | } 9 | 10 | Controller.prototype.WelcomePageCallback = function() { 11 | gui.clickButton(buttons.NextButton); 12 | } 13 | 14 | Controller.prototype.CredentialsPageCallback = function() { 15 | gui.clickButton(buttons.NextButton); 16 | } 17 | 18 | Controller.prototype.IntroductionPageCallback = function() { 19 | gui.clickButton(buttons.NextButton); 20 | } 21 | 22 | Controller.prototype.TargetDirectoryPageCallback = function() 23 | { 24 | gui.currentPageWidget().TargetDirectoryLineEdit.setText(installer.value("HomeDir") + "/Qt"); 25 | gui.clickButton(buttons.NextButton); 26 | } 27 | 28 | Controller.prototype.ComponentSelectionPageCallback = function() { 29 | var widget = gui.currentPageWidget(); 30 | 31 | widget.deselectAll(); 32 | widget.selectComponent("qt.591.gcc_64"); 33 | 34 | gui.clickButton(buttons.NextButton); 35 | } 36 | 37 | Controller.prototype.LicenseAgreementPageCallback = function() { 38 | gui.currentPageWidget().AcceptLicenseRadioButton.setChecked(true); 39 | gui.clickButton(buttons.NextButton); 40 | } 41 | 42 | Controller.prototype.StartMenuDirectoryPageCallback = function() { 43 | gui.clickButton(buttons.NextButton); 44 | } 45 | 46 | Controller.prototype.ReadyForInstallationPageCallback = function() 47 | { 48 | gui.clickButton(buttons.NextButton); 49 | } 50 | 51 | Controller.prototype.FinishedPageCallback = function() { 52 | var checkBoxForm = gui.currentPageWidget().LaunchQtCreatorCheckBoxForm 53 | if (checkBoxForm && checkBoxForm.launchQtCreatorCheckBox) { 54 | checkBoxForm.launchQtCreatorCheckBox.checked = false; 55 | } 56 | gui.clickButton(buttons.FinishButton); 57 | } -------------------------------------------------------------------------------- /examples/caves.v3m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/examples/caves.v3m -------------------------------------------------------------------------------- /examples/dungeon.v3m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/examples/dungeon.v3m -------------------------------------------------------------------------------- /manual/img/caves-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/manual/img/caves-01.png -------------------------------------------------------------------------------- /manual/img/editor-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/manual/img/editor-01.png -------------------------------------------------------------------------------- /manual/img/editor-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/manual/img/editor-02.png -------------------------------------------------------------------------------- /manual/img/editor-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/manual/img/editor-03.png -------------------------------------------------------------------------------- /manual/img/editor-generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/manual/img/editor-generic.png -------------------------------------------------------------------------------- /manual/img/editor-modifying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/manual/img/editor-modifying.png -------------------------------------------------------------------------------- /manual/img/github-markdown.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: octicons-link; 3 | src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); 4 | } 5 | 6 | .markdown-body { 7 | -ms-text-size-adjust: 100%; 8 | -webkit-text-size-adjust: 100%; 9 | line-height: 1.5; 10 | color: #24292e; 11 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 12 | font-size: 16px; 13 | line-height: 1.5; 14 | word-wrap: break-word; 15 | } 16 | 17 | .markdown-body .pl-c { 18 | color: #6a737d; 19 | } 20 | 21 | .markdown-body .pl-c1, 22 | .markdown-body .pl-s .pl-v { 23 | color: #005cc5; 24 | } 25 | 26 | .markdown-body .pl-e, 27 | .markdown-body .pl-en { 28 | color: #6f42c1; 29 | } 30 | 31 | .markdown-body .pl-smi, 32 | .markdown-body .pl-s .pl-s1 { 33 | color: #24292e; 34 | } 35 | 36 | .markdown-body .pl-ent { 37 | color: #22863a; 38 | } 39 | 40 | .markdown-body .pl-k { 41 | color: #d73a49; 42 | } 43 | 44 | .markdown-body .pl-s, 45 | .markdown-body .pl-pds, 46 | .markdown-body .pl-s .pl-pse .pl-s1, 47 | .markdown-body .pl-sr, 48 | .markdown-body .pl-sr .pl-cce, 49 | .markdown-body .pl-sr .pl-sre, 50 | .markdown-body .pl-sr .pl-sra { 51 | color: #032f62; 52 | } 53 | 54 | .markdown-body .pl-v, 55 | .markdown-body .pl-smw { 56 | color: #e36209; 57 | } 58 | 59 | .markdown-body .pl-bu { 60 | color: #b31d28; 61 | } 62 | 63 | .markdown-body .pl-ii { 64 | color: #fafbfc; 65 | background-color: #b31d28; 66 | } 67 | 68 | .markdown-body .pl-c2 { 69 | color: #fafbfc; 70 | background-color: #d73a49; 71 | } 72 | 73 | .markdown-body .pl-c2::before { 74 | content: "^M"; 75 | } 76 | 77 | .markdown-body .pl-sr .pl-cce { 78 | font-weight: bold; 79 | color: #22863a; 80 | } 81 | 82 | .markdown-body .pl-ml { 83 | color: #735c0f; 84 | } 85 | 86 | .markdown-body .pl-mh, 87 | .markdown-body .pl-mh .pl-en, 88 | .markdown-body .pl-ms { 89 | font-weight: bold; 90 | color: #005cc5; 91 | } 92 | 93 | .markdown-body .pl-mi { 94 | font-style: italic; 95 | color: #24292e; 96 | } 97 | 98 | .markdown-body .pl-mb { 99 | font-weight: bold; 100 | color: #24292e; 101 | } 102 | 103 | .markdown-body .pl-md { 104 | color: #b31d28; 105 | background-color: #ffeef0; 106 | } 107 | 108 | .markdown-body .pl-mi1 { 109 | color: #22863a; 110 | background-color: #f0fff4; 111 | } 112 | 113 | .markdown-body .pl-mc { 114 | color: #e36209; 115 | background-color: #ffebda; 116 | } 117 | 118 | .markdown-body .pl-mi2 { 119 | color: #f6f8fa; 120 | background-color: #005cc5; 121 | } 122 | 123 | .markdown-body .pl-mdr { 124 | font-weight: bold; 125 | color: #6f42c1; 126 | } 127 | 128 | .markdown-body .pl-ba { 129 | color: #586069; 130 | } 131 | 132 | .markdown-body .pl-sg { 133 | color: #959da5; 134 | } 135 | 136 | .markdown-body .pl-corl { 137 | text-decoration: underline; 138 | color: #032f62; 139 | } 140 | 141 | .markdown-body .octicon { 142 | display: inline-block; 143 | vertical-align: text-top; 144 | fill: currentColor; 145 | } 146 | 147 | .markdown-body a { 148 | background-color: transparent; 149 | -webkit-text-decoration-skip: objects; 150 | } 151 | 152 | .markdown-body a:active, 153 | .markdown-body a:hover { 154 | outline-width: 0; 155 | } 156 | 157 | .markdown-body strong { 158 | font-weight: inherit; 159 | } 160 | 161 | .markdown-body strong { 162 | font-weight: bolder; 163 | } 164 | 165 | .markdown-body h1 { 166 | font-size: 2em; 167 | margin: 0.67em 0; 168 | } 169 | 170 | .markdown-body img { 171 | border-style: none; 172 | } 173 | 174 | .markdown-body svg:not(:root) { 175 | overflow: hidden; 176 | } 177 | 178 | .markdown-body code, 179 | .markdown-body kbd, 180 | .markdown-body pre { 181 | font-family: monospace, monospace; 182 | font-size: 1em; 183 | } 184 | 185 | .markdown-body hr { 186 | box-sizing: content-box; 187 | height: 0; 188 | overflow: visible; 189 | } 190 | 191 | .markdown-body input { 192 | font: inherit; 193 | margin: 0; 194 | } 195 | 196 | .markdown-body input { 197 | overflow: visible; 198 | } 199 | 200 | .markdown-body [type="checkbox"] { 201 | box-sizing: border-box; 202 | padding: 0; 203 | } 204 | 205 | .markdown-body * { 206 | box-sizing: border-box; 207 | } 208 | 209 | .markdown-body input { 210 | font-family: inherit; 211 | font-size: inherit; 212 | line-height: inherit; 213 | } 214 | 215 | .markdown-body a { 216 | color: #0366d6; 217 | text-decoration: none; 218 | } 219 | 220 | .markdown-body a:hover { 221 | text-decoration: underline; 222 | } 223 | 224 | .markdown-body strong { 225 | font-weight: 600; 226 | } 227 | 228 | .markdown-body hr { 229 | height: 0; 230 | margin: 15px 0; 231 | overflow: hidden; 232 | background: transparent; 233 | border: 0; 234 | border-bottom: 1px solid #dfe2e5; 235 | } 236 | 237 | .markdown-body hr::before { 238 | display: table; 239 | content: ""; 240 | } 241 | 242 | .markdown-body hr::after { 243 | display: table; 244 | clear: both; 245 | content: ""; 246 | } 247 | 248 | .markdown-body table { 249 | border-spacing: 0; 250 | border-collapse: collapse; 251 | } 252 | 253 | .markdown-body td, 254 | .markdown-body th { 255 | padding: 0; 256 | } 257 | 258 | .markdown-body h1, 259 | .markdown-body h2, 260 | .markdown-body h3, 261 | .markdown-body h4, 262 | .markdown-body h5, 263 | .markdown-body h6 { 264 | margin-top: 0; 265 | margin-bottom: 0; 266 | } 267 | 268 | .markdown-body h1 { 269 | font-size: 32px; 270 | font-weight: 600; 271 | } 272 | 273 | .markdown-body h2 { 274 | font-size: 24px; 275 | font-weight: 600; 276 | } 277 | 278 | .markdown-body h3 { 279 | font-size: 20px; 280 | font-weight: 600; 281 | } 282 | 283 | .markdown-body h4 { 284 | font-size: 16px; 285 | font-weight: 600; 286 | } 287 | 288 | .markdown-body h5 { 289 | font-size: 14px; 290 | font-weight: 600; 291 | } 292 | 293 | .markdown-body h6 { 294 | font-size: 12px; 295 | font-weight: 600; 296 | } 297 | 298 | .markdown-body p { 299 | margin-top: 0; 300 | margin-bottom: 10px; 301 | } 302 | 303 | .markdown-body blockquote { 304 | margin: 0; 305 | } 306 | 307 | .markdown-body ul, 308 | .markdown-body ol { 309 | padding-left: 0; 310 | margin-top: 0; 311 | margin-bottom: 0; 312 | } 313 | 314 | .markdown-body ol ol, 315 | .markdown-body ul ol { 316 | list-style-type: lower-roman; 317 | } 318 | 319 | .markdown-body ul ul ol, 320 | .markdown-body ul ol ol, 321 | .markdown-body ol ul ol, 322 | .markdown-body ol ol ol { 323 | list-style-type: lower-alpha; 324 | } 325 | 326 | .markdown-body dd { 327 | margin-left: 0; 328 | } 329 | 330 | .markdown-body code { 331 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 332 | font-size: 12px; 333 | } 334 | 335 | .markdown-body pre { 336 | margin-top: 0; 337 | margin-bottom: 0; 338 | font: 12px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 339 | } 340 | 341 | .markdown-body .octicon { 342 | vertical-align: text-bottom; 343 | } 344 | 345 | .markdown-body .pl-0 { 346 | padding-left: 0 !important; 347 | } 348 | 349 | .markdown-body .pl-1 { 350 | padding-left: 4px !important; 351 | } 352 | 353 | .markdown-body .pl-2 { 354 | padding-left: 8px !important; 355 | } 356 | 357 | .markdown-body .pl-3 { 358 | padding-left: 16px !important; 359 | } 360 | 361 | .markdown-body .pl-4 { 362 | padding-left: 24px !important; 363 | } 364 | 365 | .markdown-body .pl-5 { 366 | padding-left: 32px !important; 367 | } 368 | 369 | .markdown-body .pl-6 { 370 | padding-left: 40px !important; 371 | } 372 | 373 | .markdown-body::before { 374 | display: table; 375 | content: ""; 376 | } 377 | 378 | .markdown-body::after { 379 | display: table; 380 | clear: both; 381 | content: ""; 382 | } 383 | 384 | .markdown-body>*:first-child { 385 | margin-top: 0 !important; 386 | } 387 | 388 | .markdown-body>*:last-child { 389 | margin-bottom: 0 !important; 390 | } 391 | 392 | .markdown-body a:not([href]) { 393 | color: inherit; 394 | text-decoration: none; 395 | } 396 | 397 | .markdown-body .anchor { 398 | float: left; 399 | padding-right: 4px; 400 | margin-left: -20px; 401 | line-height: 1; 402 | } 403 | 404 | .markdown-body .anchor:focus { 405 | outline: none; 406 | } 407 | 408 | .markdown-body p, 409 | .markdown-body blockquote, 410 | .markdown-body ul, 411 | .markdown-body ol, 412 | .markdown-body dl, 413 | .markdown-body table, 414 | .markdown-body pre { 415 | margin-top: 0; 416 | margin-bottom: 16px; 417 | } 418 | 419 | .markdown-body hr { 420 | height: 0.25em; 421 | padding: 0; 422 | margin: 24px 0; 423 | background-color: #e1e4e8; 424 | border: 0; 425 | } 426 | 427 | .markdown-body blockquote { 428 | padding: 0 1em; 429 | color: #6a737d; 430 | border-left: 0.25em solid #dfe2e5; 431 | } 432 | 433 | .markdown-body blockquote>:first-child { 434 | margin-top: 0; 435 | } 436 | 437 | .markdown-body blockquote>:last-child { 438 | margin-bottom: 0; 439 | } 440 | 441 | .markdown-body kbd { 442 | display: inline-block; 443 | padding: 3px 5px; 444 | font-size: 11px; 445 | line-height: 10px; 446 | color: #444d56; 447 | vertical-align: middle; 448 | background-color: #fafbfc; 449 | border: solid 1px #c6cbd1; 450 | border-bottom-color: #959da5; 451 | border-radius: 3px; 452 | box-shadow: inset 0 -1px 0 #959da5; 453 | } 454 | 455 | .markdown-body h1, 456 | .markdown-body h2, 457 | .markdown-body h3, 458 | .markdown-body h4, 459 | .markdown-body h5, 460 | .markdown-body h6 { 461 | margin-top: 24px; 462 | margin-bottom: 16px; 463 | font-weight: 600; 464 | line-height: 1.25; 465 | } 466 | 467 | .markdown-body h1 .octicon-link, 468 | .markdown-body h2 .octicon-link, 469 | .markdown-body h3 .octicon-link, 470 | .markdown-body h4 .octicon-link, 471 | .markdown-body h5 .octicon-link, 472 | .markdown-body h6 .octicon-link { 473 | color: #1b1f23; 474 | vertical-align: middle; 475 | visibility: hidden; 476 | } 477 | 478 | .markdown-body h1:hover .anchor, 479 | .markdown-body h2:hover .anchor, 480 | .markdown-body h3:hover .anchor, 481 | .markdown-body h4:hover .anchor, 482 | .markdown-body h5:hover .anchor, 483 | .markdown-body h6:hover .anchor { 484 | text-decoration: none; 485 | } 486 | 487 | .markdown-body h1:hover .anchor .octicon-link, 488 | .markdown-body h2:hover .anchor .octicon-link, 489 | .markdown-body h3:hover .anchor .octicon-link, 490 | .markdown-body h4:hover .anchor .octicon-link, 491 | .markdown-body h5:hover .anchor .octicon-link, 492 | .markdown-body h6:hover .anchor .octicon-link { 493 | visibility: visible; 494 | } 495 | 496 | .markdown-body h1 { 497 | padding-bottom: 0.3em; 498 | font-size: 2em; 499 | border-bottom: 1px solid #eaecef; 500 | } 501 | 502 | .markdown-body h2 { 503 | padding-bottom: 0.3em; 504 | font-size: 1.5em; 505 | border-bottom: 1px solid #eaecef; 506 | } 507 | 508 | .markdown-body h3 { 509 | font-size: 1.25em; 510 | } 511 | 512 | .markdown-body h4 { 513 | font-size: 1em; 514 | } 515 | 516 | .markdown-body h5 { 517 | font-size: 0.875em; 518 | } 519 | 520 | .markdown-body h6 { 521 | font-size: 0.85em; 522 | color: #6a737d; 523 | } 524 | 525 | .markdown-body ul, 526 | .markdown-body ol { 527 | padding-left: 2em; 528 | } 529 | 530 | .markdown-body ul ul, 531 | .markdown-body ul ol, 532 | .markdown-body ol ol, 533 | .markdown-body ol ul { 534 | margin-top: 0; 535 | margin-bottom: 0; 536 | } 537 | 538 | .markdown-body li>p { 539 | margin-top: 16px; 540 | } 541 | 542 | .markdown-body li+li { 543 | margin-top: 0.25em; 544 | } 545 | 546 | .markdown-body dl { 547 | padding: 0; 548 | } 549 | 550 | .markdown-body dl dt { 551 | padding: 0; 552 | margin-top: 16px; 553 | font-size: 1em; 554 | font-style: italic; 555 | font-weight: 600; 556 | } 557 | 558 | .markdown-body dl dd { 559 | padding: 0 16px; 560 | margin-bottom: 16px; 561 | } 562 | 563 | .markdown-body table { 564 | display: block; 565 | width: 100%; 566 | overflow: auto; 567 | } 568 | 569 | .markdown-body table th { 570 | font-weight: 600; 571 | } 572 | 573 | .markdown-body table th, 574 | .markdown-body table td { 575 | padding: 6px 13px; 576 | border: 1px solid #dfe2e5; 577 | } 578 | 579 | .markdown-body table tr { 580 | background-color: #fff; 581 | border-top: 1px solid #c6cbd1; 582 | } 583 | 584 | .markdown-body table tr:nth-child(2n) { 585 | background-color: #f6f8fa; 586 | } 587 | 588 | .markdown-body img { 589 | max-width: 100%; 590 | box-sizing: content-box; 591 | background-color: #fff; 592 | } 593 | 594 | .markdown-body code { 595 | padding: 0; 596 | padding-top: 0.2em; 597 | padding-bottom: 0.2em; 598 | margin: 0; 599 | font-size: 85%; 600 | background-color: rgba(27,31,35,0.05); 601 | border-radius: 3px; 602 | } 603 | 604 | .markdown-body code::before, 605 | .markdown-body code::after { 606 | letter-spacing: -0.2em; 607 | content: "\00a0"; 608 | } 609 | 610 | .markdown-body pre { 611 | word-wrap: normal; 612 | } 613 | 614 | .markdown-body pre>code { 615 | padding: 0; 616 | margin: 0; 617 | font-size: 100%; 618 | word-break: normal; 619 | white-space: pre; 620 | background: transparent; 621 | border: 0; 622 | } 623 | 624 | .markdown-body .highlight { 625 | margin-bottom: 16px; 626 | } 627 | 628 | .markdown-body .highlight pre { 629 | margin-bottom: 0; 630 | word-break: normal; 631 | } 632 | 633 | .markdown-body .highlight pre, 634 | .markdown-body pre { 635 | padding: 16px; 636 | overflow: auto; 637 | font-size: 85%; 638 | line-height: 1.45; 639 | background-color: #f6f8fa; 640 | border-radius: 3px; 641 | } 642 | 643 | .markdown-body pre code { 644 | display: inline; 645 | max-width: auto; 646 | padding: 0; 647 | margin: 0; 648 | overflow: visible; 649 | line-height: inherit; 650 | word-wrap: normal; 651 | background-color: transparent; 652 | border: 0; 653 | } 654 | 655 | .markdown-body pre code::before, 656 | .markdown-body pre code::after { 657 | content: normal; 658 | } 659 | 660 | .markdown-body .full-commit .btn-outline:not(:disabled):hover { 661 | color: #005cc5; 662 | border-color: #005cc5; 663 | } 664 | 665 | .markdown-body kbd { 666 | display: inline-block; 667 | padding: 3px 5px; 668 | font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 669 | line-height: 10px; 670 | color: #444d56; 671 | vertical-align: middle; 672 | background-color: #fafbfc; 673 | border: solid 1px #d1d5da; 674 | border-bottom-color: #c6cbd1; 675 | border-radius: 3px; 676 | box-shadow: inset 0 -1px 0 #c6cbd1; 677 | } 678 | 679 | .markdown-body :checked+.radio-label { 680 | position: relative; 681 | z-index: 1; 682 | border-color: #0366d6; 683 | } 684 | 685 | .markdown-body .task-list-item { 686 | list-style-type: none; 687 | } 688 | 689 | .markdown-body .task-list-item+.task-list-item { 690 | margin-top: 3px; 691 | } 692 | 693 | .markdown-body .task-list-item input { 694 | margin: 0 0.2em 0.25em -1.6em; 695 | vertical-align: middle; 696 | } 697 | 698 | .markdown-body hr { 699 | border-bottom-color: #eee; 700 | } 701 | -------------------------------------------------------------------------------- /manual/manual.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | VersaTile 3D Manual 6 | 7 | 14 | 15 | 16 | 17 |

VersaTile 3D Model Editor

18 | 19 |

Screenshot

20 | 21 |

VersaTile is a simple 3D model editor that allows 22 | editing models based on tilesets.

23 | 24 |

Tiles can be placed on an axis-aligned plane and a 25 | model can be constructed this way.

26 | 27 |

Features

28 | 29 |
    30 |
  • Model editing
  • 31 |
  • Undo/redo
  • 32 |
  • Vertex displacement
  • 33 |
  • Post-placement edit (move/rotate/mirror/displace/…)
  • 34 |
  • Export via Assimp
  • 35 |
36 | 37 |

Usage

38 | 39 |

The editor is mainly controlled with the mouse. On the right hand side is a side bar 40 | that shows the models sprite sheet and allows the selection of sprites.

41 | 42 |

The center of the editor contains a 3D viewport that shows the model and allows editing.

43 | 44 |

Creating a model

45 | 46 |

When creating a new model, the following dialog appears:

47 | 48 |

Create new model dialog

49 | 50 |

It provides several options that must be set up correctly to import a sprite sheet:

51 | 52 |
    53 |
  • File Name 54 | This option is required and defines the file that should be used as the sprite sheet.
  • 55 |
  • Sprite Size 56 | This option defines the size of the smallest tile that can be selected in the sprite 57 | sheet view. This also defines the grid size.
  • 58 |
  • Spritesheet Margin 59 | If the sprite sheet image has a border around the contained sprites, set this value to 60 | the number of pixels this border is wide.
  • 61 |
  • Sprite Padding 62 | If there is a padding between the single sprites in the sheet, set this value to the 63 | number of pixels this padding is wide.
  • 64 |
  • Preview 65 | Here you can see a preview of the imported sprites. The magenta lines will disappear in 66 | the editor, but are displayed here for you to check if your setup of the sprite sheet 67 | is correct.
  • 68 |
69 | 70 |

Navigating in the Viewport

71 | 72 |

Generic Controls

73 | 74 |

VersaTile uses a grid aligned movement for the camera focus. This grid is shown 75 | as white lines and aligns itself to fit a comfortable insertion position.

76 | 77 |

To rotate the camera, hold the right mouse button and drag. Horizontal movement 78 | rotates the camera around the focus point, vertical movement looks up and down.

79 | 80 |

To move the focus point, press W,A,S,D,Q and E. W moves the camera forward horizontally, 81 | S moves it backwards. A moves to the left and B to the right. E moves the camera upwards 82 | and Q downwards.

83 | 84 |

For a quick rotation of the camera, all shortcuts listed in the table below.

85 | 86 |

If the option Automatic adjust grid orientation is set, the displayed grid will align 87 | with the camera so always the most convenient grid is active. If this option is disabled, 88 | the active grid can be changed by pressing G or the Toggle Grid Plane button.

89 | 90 |

Inserting a tile

91 | 92 |

To insert a tile, select the sprite on the right side by clicking and dragging until 93 | the wanted portion is selected. Now a tile can be inserted in the 3D viewport on 94 | the white grid.

95 | 96 |

When inserting, the tile snaps to the grid unless the Shift key is pressed, then 97 | a pixel perfect insertion can be done.

98 | 99 |

When a good position is chosen, a left click will insert the tile at the previewed 100 | position. Then, another tile can be inserted. To insert a different tile, just 101 | select another sprite in the right panel. To end tile insertion, press Space or 102 | use the Selection mode button in the toolbar.

103 | 104 |

Modifying an existing tile

105 | 106 |

Modify Display

107 | 108 |

Tiles can be modified in different ways:

109 | 110 |

Translate / Move

111 | 112 |

Click and drag the tiles center gizmo to move the tile on its own placement 113 | plane. You can move the tile up,down,left and right.

114 | 115 |

Rotate Left / Right

116 | 117 |

Click the corresponding button in the toolbar or menu to rotate the tile around 118 | its upper left corner.

119 | 120 |

Flip Horizontal / Vertical

121 | 122 |

Click the corresponding button in the toolbar or menu to mirror the contents of 123 | the tile.

124 | 125 |

Displace Vertex

126 | 127 |

This feature allows you to displace a vertex to the tiles own plane. Click and 128 | drag a vertex gizmo to displace it along the tiles normal.

129 | 130 |

This can be used to achieve slopes and skewed tiles.

131 | 132 |

Delete

133 | 134 |

Click the button on the toolbar or press Delete to remove the tile from the 135 | model.

136 | 137 |

Controls

138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 |
KeyFunction
W,A,S,DMove camera focus horizontal
Q,EMove camera focus up/down
SpaceCancel/Stop tile insertion
HFlip horizontal
VFlip vertical
R,Ctrl+RRotate tile
ShiftEnable fine grid snapping (pixel perfect)
Num-2Rotate camera down 90°
Num-8Rotate camera up 90°
Num-4Rotate camera left 90°
Num-6Rotate camera right 90°
Num-PlusZoom camera in
Num-MinusZoom camera out
Right MouseDrag to rotate camera
Left MouseClick to select, modify or insert tile
DeleteRemove the selected tile
Ctrl+ZUndo the last modification
Ctrl+YRedo the last undone modification
Ctrl+HRotate camera into Home position
Ctrl+TRotate camera into Top position
Ctrl+FRotate camera into Front position
Ctrl+GRotate camera into Side position
Ctrl+Shift+HSet camera home position
GWhen AutoGrid is disabled, selects the next grid option
FMoves the camera center to the currently selected tile.
246 | 247 |

Supported Export Formats

248 | 249 |

VersaTile supports exporting the created 3D models with Assimp. 250 | Every format that can be exported by Assimp can also be used with VersaTile.

251 | 252 |

On export, the Y-axis is up and the model is scaled in a way that a single 253 | texture pixel is equivalent to one model unit.

254 | 255 |

So a model that is 64 pixels wide is also 64 units wide.

256 | 257 |

Preferences

258 | 259 |

In this dialog, you can set up your preferences on how VeraTile should behave.

260 | 261 |

Behaviour

262 | 263 |

Preferences dialog: Behaviour

264 | 265 |

Automatic adjust grid orientation

266 | 267 |

When this option is active, the grid will align itself with the camera 268 | as soon as it is moved.

269 | 270 |

Horizontal Grid Threshold

271 | 272 |

This is a factor from 0.01.0 that scales the y axis when determining 273 | the current auto grid.

274 | 275 |

The lower this is, the later the grid will align into "horizontal" mode, 276 | allowing more camera movement before swapping into horizontal mode.

277 | 278 |

Display

279 | 280 |

Preferences dialog: Display

281 | 282 |

Ground Display

283 | 284 |

Determines the way a ground plane is shown. When None, no ground plane is 285 | visible, Grid is a static horizontal grid, even when the vertical one 286 | is shown and Solid is a solid, green surface that is supposed to resemble 287 | a grassy ground.

288 | 289 |

Ground Size

290 | 291 |

The extends of the ground in tiles. The larger this is, the larger the ground 292 | will be displayed.

293 | 294 |

Show Coordinate Axis

295 | 296 |

If this option is enabled, the coordinate axis are shown by three short lines in the 297 | colors red (x-axis), green (y-axis) and blue (z-axis). The intersection of the three 298 | lines is the current camera focus point.

299 | 300 |

Credits

301 | 302 |

Thanks for each Crocotile3D and Sprytile 303 | for inspiring me to create VersaTile!

304 | 305 |

Also thanks to kenney.nl for delivering our default tile 306 | set! It suits this editor very much.

307 | 308 | 309 | -------------------------------------------------------------------------------- /screenshots/caves-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/caves-01.png -------------------------------------------------------------------------------- /screenshots/dungeon-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/dungeon-01.png -------------------------------------------------------------------------------- /screenshots/editor-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-01.png -------------------------------------------------------------------------------- /screenshots/editor-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-02.png -------------------------------------------------------------------------------- /screenshots/editor-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-03.png -------------------------------------------------------------------------------- /screenshots/editor-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-04.png -------------------------------------------------------------------------------- /screenshots/editor-generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-generic.png -------------------------------------------------------------------------------- /screenshots/editor-modifying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-modifying.png -------------------------------------------------------------------------------- /screenshots/editor-modifying.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/editor-modifying.xcf -------------------------------------------------------------------------------- /screenshots/screenshot-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/screenshots/screenshot-0.png -------------------------------------------------------------------------------- /src/data.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | data/close.png 4 | data/new.png 5 | data/open.png 6 | data/pixel.png 7 | data/save.png 8 | glsl/vertex.glsl 9 | glsl/fragment.glsl 10 | data/home.png 11 | data/rotate-left.png 12 | data/rotate-right.png 13 | data/tool-cursor.png 14 | data/tool-flip.png 15 | data/tool-move.png 16 | data/tool-select.png 17 | data/flip-vertical.png 18 | data/flip-horizontal.png 19 | data/delete.png 20 | data/content-copy.png 21 | data/content-cut.png 22 | data/content-paste.png 23 | data/export.png 24 | data/github-circle.png 25 | data/information-variant.png 26 | data/undo-variant.png 27 | data/redo-variant.png 28 | data/settings.png 29 | data/focus.png 30 | data/import.png 31 | data/grid.png 32 | data/tilesets/roguelike-caves.png 33 | data/camera-xy.png 34 | data/camera-xz.png 35 | data/camera-yz.png 36 | data/tileset.png 37 | data/web.png 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/data/camera-xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/camera-xy.png -------------------------------------------------------------------------------- /src/data/camera-xz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/camera-xz.png -------------------------------------------------------------------------------- /src/data/camera-yz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/camera-yz.png -------------------------------------------------------------------------------- /src/data/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/close.png -------------------------------------------------------------------------------- /src/data/content-copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/content-copy.png -------------------------------------------------------------------------------- /src/data/content-cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/content-cut.png -------------------------------------------------------------------------------- /src/data/content-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/content-paste.png -------------------------------------------------------------------------------- /src/data/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/delete.png -------------------------------------------------------------------------------- /src/data/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/export.png -------------------------------------------------------------------------------- /src/data/flip-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/flip-horizontal.png -------------------------------------------------------------------------------- /src/data/flip-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/flip-vertical.png -------------------------------------------------------------------------------- /src/data/focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/focus.png -------------------------------------------------------------------------------- /src/data/github-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/github-circle.png -------------------------------------------------------------------------------- /src/data/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/grid.png -------------------------------------------------------------------------------- /src/data/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/home.png -------------------------------------------------------------------------------- /src/data/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/import.png -------------------------------------------------------------------------------- /src/data/information-variant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/information-variant.png -------------------------------------------------------------------------------- /src/data/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/new.png -------------------------------------------------------------------------------- /src/data/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/open.png -------------------------------------------------------------------------------- /src/data/pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/pixel.png -------------------------------------------------------------------------------- /src/data/redo-variant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/redo-variant.png -------------------------------------------------------------------------------- /src/data/rotate-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/rotate-left.png -------------------------------------------------------------------------------- /src/data/rotate-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/rotate-right.png -------------------------------------------------------------------------------- /src/data/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/save.png -------------------------------------------------------------------------------- /src/data/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/settings.png -------------------------------------------------------------------------------- /src/data/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tileset.png -------------------------------------------------------------------------------- /src/data/tilesets/dungeon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tilesets/dungeon.png -------------------------------------------------------------------------------- /src/data/tilesets/roguelike-caves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tilesets/roguelike-caves.png -------------------------------------------------------------------------------- /src/data/tilesets/roguelike-city.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tilesets/roguelike-city.png -------------------------------------------------------------------------------- /src/data/tool-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tool-cursor.png -------------------------------------------------------------------------------- /src/data/tool-flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tool-flip.png -------------------------------------------------------------------------------- /src/data/tool-move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tool-move.png -------------------------------------------------------------------------------- /src/data/tool-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/tool-select.png -------------------------------------------------------------------------------- /src/data/undo-variant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/undo-variant.png -------------------------------------------------------------------------------- /src/data/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikskuh/VersaTile/634765dbd6445d198d7d7533e2b5f57edd1eda84/src/data/web.png -------------------------------------------------------------------------------- /src/gl3w/gl3w.pri: -------------------------------------------------------------------------------- 1 | SOURCES += \ 2 | $$PWD/src/gl3w.c 3 | 4 | INCLUDEPATH += $$PWD/include 5 | DEPENDPATH += $$PWD/include 6 | 7 | win32 { 8 | LIBS += -lOpenGL32 9 | } 10 | !win32 { 11 | LIBS += -ldl -lGL 12 | } 13 | -------------------------------------------------------------------------------- /src/gl3w/src/gl3w.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This file was generated with gl3w_gen.py, part of gl3w 4 | (hosted at https://github.com/skaslev/gl3w) 5 | 6 | This is free and unencumbered software released into the public domain. 7 | 8 | Anyone is free to copy, modify, publish, use, compile, sell, or 9 | distribute this software, either in source code form or as a compiled 10 | binary, for any purpose, commercial or non-commercial, and by any 11 | means. 12 | 13 | In jurisdictions that recognize copyright laws, the author or authors 14 | of this software dedicate any and all copyright interest in the 15 | software to the public domain. We make this dedication for the benefit 16 | of the public at large and to the detriment of our heirs and 17 | successors. We intend this dedication to be an overt act of 18 | relinquishment in perpetuity of all present and future rights to this 19 | software under copyright law. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 25 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 26 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | */ 30 | 31 | #include 32 | #include 33 | 34 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 35 | 36 | #if defined(_WIN32) 37 | #define WIN32_LEAN_AND_MEAN 1 38 | #include 39 | 40 | static HMODULE libgl; 41 | 42 | static int open_libgl(void) 43 | { 44 | libgl = LoadLibraryA("opengl32.dll"); 45 | if (!libgl) 46 | return GL3W_ERROR_LIBRARY_OPEN; 47 | 48 | return GL3W_OK; 49 | } 50 | 51 | static void close_libgl(void) 52 | { 53 | FreeLibrary(libgl); 54 | } 55 | 56 | static GL3WglProc get_proc(const char *proc) 57 | { 58 | GL3WglProc res; 59 | 60 | res = (GL3WglProc)wglGetProcAddress(proc); 61 | if (!res) 62 | res = (GL3WglProc)GetProcAddress(libgl, proc); 63 | return res; 64 | } 65 | #elif defined(__APPLE__) 66 | #include 67 | 68 | static void *libgl; 69 | 70 | static int open_libgl(void) 71 | { 72 | libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_GLOBAL); 73 | if (!libgl) 74 | return GL3W_ERROR_LIBRARY_OPEN; 75 | 76 | return GL3W_OK; 77 | } 78 | 79 | static void close_libgl(void) 80 | { 81 | dlclose(libgl); 82 | } 83 | 84 | static GL3WglProc get_proc(const char *proc) 85 | { 86 | GL3WglProc res; 87 | 88 | *(void **)(&res) = dlsym(libgl, proc); 89 | return res; 90 | } 91 | #else 92 | #include 93 | #include 94 | 95 | static void *libgl; 96 | static PFNGLXGETPROCADDRESSPROC glx_get_proc_address; 97 | 98 | static int open_libgl(void) 99 | { 100 | libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_GLOBAL); 101 | if (!libgl) 102 | return GL3W_ERROR_LIBRARY_OPEN; 103 | 104 | *(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB"); 105 | return GL3W_OK; 106 | } 107 | 108 | static void close_libgl(void) 109 | { 110 | dlclose(libgl); 111 | } 112 | 113 | static GL3WglProc get_proc(const char *proc) 114 | { 115 | GL3WglProc res; 116 | 117 | res = glx_get_proc_address((const GLubyte *)proc); 118 | if (!res) 119 | *(void **)(&res) = dlsym(libgl, proc); 120 | return res; 121 | } 122 | #endif 123 | 124 | static struct { 125 | int major, minor; 126 | } version; 127 | 128 | static int parse_version(void) 129 | { 130 | if (!glGetIntegerv) 131 | return GL3W_ERROR_INIT; 132 | 133 | glGetIntegerv(GL_MAJOR_VERSION, &version.major); 134 | glGetIntegerv(GL_MINOR_VERSION, &version.minor); 135 | 136 | if (version.major < 3) 137 | return GL3W_ERROR_OPENGL_VERSION; 138 | return GL3W_OK; 139 | } 140 | 141 | static void load_procs(GL3WGetProcAddressProc proc); 142 | 143 | int gl3wInit(void) 144 | { 145 | return gl3wInit2(get_proc); 146 | } 147 | 148 | int gl3wInit2(GL3WGetProcAddressProc proc) 149 | { 150 | int res = open_libgl(); 151 | if (res) 152 | return res; 153 | 154 | atexit(close_libgl); 155 | load_procs(proc); 156 | return parse_version(); 157 | } 158 | 159 | int gl3wIsSupported(int major, int minor) 160 | { 161 | if (major < 3) 162 | return 0; 163 | if (version.major == major) 164 | return version.minor >= minor; 165 | return version.major >= major; 166 | } 167 | 168 | GL3WglProc gl3wGetProcAddress(const char *proc) 169 | { 170 | return get_proc(proc); 171 | } 172 | 173 | static const char *proc_names[] = { 174 | "glActiveShaderProgram", 175 | "glActiveTexture", 176 | "glAttachShader", 177 | "glBeginConditionalRender", 178 | "glBeginQuery", 179 | "glBeginQueryIndexed", 180 | "glBeginTransformFeedback", 181 | "glBindAttribLocation", 182 | "glBindBuffer", 183 | "glBindBufferBase", 184 | "glBindBufferRange", 185 | "glBindBuffersBase", 186 | "glBindBuffersRange", 187 | "glBindFragDataLocation", 188 | "glBindFragDataLocationIndexed", 189 | "glBindFramebuffer", 190 | "glBindImageTexture", 191 | "glBindImageTextures", 192 | "glBindProgramPipeline", 193 | "glBindRenderbuffer", 194 | "glBindSampler", 195 | "glBindSamplers", 196 | "glBindTexture", 197 | "glBindTextureUnit", 198 | "glBindTextures", 199 | "glBindTransformFeedback", 200 | "glBindVertexArray", 201 | "glBindVertexBuffer", 202 | "glBindVertexBuffers", 203 | "glBlendColor", 204 | "glBlendEquation", 205 | "glBlendEquationSeparate", 206 | "glBlendEquationSeparatei", 207 | "glBlendEquationi", 208 | "glBlendFunc", 209 | "glBlendFuncSeparate", 210 | "glBlendFuncSeparatei", 211 | "glBlendFunci", 212 | "glBlitFramebuffer", 213 | "glBlitNamedFramebuffer", 214 | "glBufferData", 215 | "glBufferStorage", 216 | "glBufferSubData", 217 | "glCheckFramebufferStatus", 218 | "glCheckNamedFramebufferStatus", 219 | "glClampColor", 220 | "glClear", 221 | "glClearBufferData", 222 | "glClearBufferSubData", 223 | "glClearBufferfi", 224 | "glClearBufferfv", 225 | "glClearBufferiv", 226 | "glClearBufferuiv", 227 | "glClearColor", 228 | "glClearDepth", 229 | "glClearDepthf", 230 | "glClearNamedBufferData", 231 | "glClearNamedBufferSubData", 232 | "glClearNamedFramebufferfi", 233 | "glClearNamedFramebufferfv", 234 | "glClearNamedFramebufferiv", 235 | "glClearNamedFramebufferuiv", 236 | "glClearStencil", 237 | "glClearTexImage", 238 | "glClearTexSubImage", 239 | "glClientWaitSync", 240 | "glClipControl", 241 | "glColorMask", 242 | "glColorMaski", 243 | "glCompileShader", 244 | "glCompressedTexImage1D", 245 | "glCompressedTexImage2D", 246 | "glCompressedTexImage3D", 247 | "glCompressedTexSubImage1D", 248 | "glCompressedTexSubImage2D", 249 | "glCompressedTexSubImage3D", 250 | "glCompressedTextureSubImage1D", 251 | "glCompressedTextureSubImage2D", 252 | "glCompressedTextureSubImage3D", 253 | "glCopyBufferSubData", 254 | "glCopyImageSubData", 255 | "glCopyNamedBufferSubData", 256 | "glCopyTexImage1D", 257 | "glCopyTexImage2D", 258 | "glCopyTexSubImage1D", 259 | "glCopyTexSubImage2D", 260 | "glCopyTexSubImage3D", 261 | "glCopyTextureSubImage1D", 262 | "glCopyTextureSubImage2D", 263 | "glCopyTextureSubImage3D", 264 | "glCreateBuffers", 265 | "glCreateFramebuffers", 266 | "glCreateProgram", 267 | "glCreateProgramPipelines", 268 | "glCreateQueries", 269 | "glCreateRenderbuffers", 270 | "glCreateSamplers", 271 | "glCreateShader", 272 | "glCreateShaderProgramv", 273 | "glCreateTextures", 274 | "glCreateTransformFeedbacks", 275 | "glCreateVertexArrays", 276 | "glCullFace", 277 | "glDebugMessageCallback", 278 | "glDebugMessageControl", 279 | "glDebugMessageInsert", 280 | "glDeleteBuffers", 281 | "glDeleteFramebuffers", 282 | "glDeleteProgram", 283 | "glDeleteProgramPipelines", 284 | "glDeleteQueries", 285 | "glDeleteRenderbuffers", 286 | "glDeleteSamplers", 287 | "glDeleteShader", 288 | "glDeleteSync", 289 | "glDeleteTextures", 290 | "glDeleteTransformFeedbacks", 291 | "glDeleteVertexArrays", 292 | "glDepthFunc", 293 | "glDepthMask", 294 | "glDepthRange", 295 | "glDepthRangeArrayv", 296 | "glDepthRangeIndexed", 297 | "glDepthRangef", 298 | "glDetachShader", 299 | "glDisable", 300 | "glDisableVertexArrayAttrib", 301 | "glDisableVertexAttribArray", 302 | "glDisablei", 303 | "glDispatchCompute", 304 | "glDispatchComputeIndirect", 305 | "glDrawArrays", 306 | "glDrawArraysIndirect", 307 | "glDrawArraysInstanced", 308 | "glDrawArraysInstancedBaseInstance", 309 | "glDrawBuffer", 310 | "glDrawBuffers", 311 | "glDrawElements", 312 | "glDrawElementsBaseVertex", 313 | "glDrawElementsIndirect", 314 | "glDrawElementsInstanced", 315 | "glDrawElementsInstancedBaseInstance", 316 | "glDrawElementsInstancedBaseVertex", 317 | "glDrawElementsInstancedBaseVertexBaseInstance", 318 | "glDrawRangeElements", 319 | "glDrawRangeElementsBaseVertex", 320 | "glDrawTransformFeedback", 321 | "glDrawTransformFeedbackInstanced", 322 | "glDrawTransformFeedbackStream", 323 | "glDrawTransformFeedbackStreamInstanced", 324 | "glEnable", 325 | "glEnableVertexArrayAttrib", 326 | "glEnableVertexAttribArray", 327 | "glEnablei", 328 | "glEndConditionalRender", 329 | "glEndQuery", 330 | "glEndQueryIndexed", 331 | "glEndTransformFeedback", 332 | "glFenceSync", 333 | "glFinish", 334 | "glFlush", 335 | "glFlushMappedBufferRange", 336 | "glFlushMappedNamedBufferRange", 337 | "glFramebufferParameteri", 338 | "glFramebufferRenderbuffer", 339 | "glFramebufferTexture", 340 | "glFramebufferTexture1D", 341 | "glFramebufferTexture2D", 342 | "glFramebufferTexture3D", 343 | "glFramebufferTextureLayer", 344 | "glFrontFace", 345 | "glGenBuffers", 346 | "glGenFramebuffers", 347 | "glGenProgramPipelines", 348 | "glGenQueries", 349 | "glGenRenderbuffers", 350 | "glGenSamplers", 351 | "glGenTextures", 352 | "glGenTransformFeedbacks", 353 | "glGenVertexArrays", 354 | "glGenerateMipmap", 355 | "glGenerateTextureMipmap", 356 | "glGetActiveAtomicCounterBufferiv", 357 | "glGetActiveAttrib", 358 | "glGetActiveSubroutineName", 359 | "glGetActiveSubroutineUniformName", 360 | "glGetActiveSubroutineUniformiv", 361 | "glGetActiveUniform", 362 | "glGetActiveUniformBlockName", 363 | "glGetActiveUniformBlockiv", 364 | "glGetActiveUniformName", 365 | "glGetActiveUniformsiv", 366 | "glGetAttachedShaders", 367 | "glGetAttribLocation", 368 | "glGetBooleani_v", 369 | "glGetBooleanv", 370 | "glGetBufferParameteri64v", 371 | "glGetBufferParameteriv", 372 | "glGetBufferPointerv", 373 | "glGetBufferSubData", 374 | "glGetCompressedTexImage", 375 | "glGetCompressedTextureImage", 376 | "glGetCompressedTextureSubImage", 377 | "glGetDebugMessageLog", 378 | "glGetDoublei_v", 379 | "glGetDoublev", 380 | "glGetError", 381 | "glGetFloati_v", 382 | "glGetFloatv", 383 | "glGetFragDataIndex", 384 | "glGetFragDataLocation", 385 | "glGetFramebufferAttachmentParameteriv", 386 | "glGetFramebufferParameteriv", 387 | "glGetGraphicsResetStatus", 388 | "glGetInteger64i_v", 389 | "glGetInteger64v", 390 | "glGetIntegeri_v", 391 | "glGetIntegerv", 392 | "glGetInternalformati64v", 393 | "glGetInternalformativ", 394 | "glGetMultisamplefv", 395 | "glGetNamedBufferParameteri64v", 396 | "glGetNamedBufferParameteriv", 397 | "glGetNamedBufferPointerv", 398 | "glGetNamedBufferSubData", 399 | "glGetNamedFramebufferAttachmentParameteriv", 400 | "glGetNamedFramebufferParameteriv", 401 | "glGetNamedRenderbufferParameteriv", 402 | "glGetObjectLabel", 403 | "glGetObjectPtrLabel", 404 | "glGetPointerv", 405 | "glGetProgramBinary", 406 | "glGetProgramInfoLog", 407 | "glGetProgramInterfaceiv", 408 | "glGetProgramPipelineInfoLog", 409 | "glGetProgramPipelineiv", 410 | "glGetProgramResourceIndex", 411 | "glGetProgramResourceLocation", 412 | "glGetProgramResourceLocationIndex", 413 | "glGetProgramResourceName", 414 | "glGetProgramResourceiv", 415 | "glGetProgramStageiv", 416 | "glGetProgramiv", 417 | "glGetQueryBufferObjecti64v", 418 | "glGetQueryBufferObjectiv", 419 | "glGetQueryBufferObjectui64v", 420 | "glGetQueryBufferObjectuiv", 421 | "glGetQueryIndexediv", 422 | "glGetQueryObjecti64v", 423 | "glGetQueryObjectiv", 424 | "glGetQueryObjectui64v", 425 | "glGetQueryObjectuiv", 426 | "glGetQueryiv", 427 | "glGetRenderbufferParameteriv", 428 | "glGetSamplerParameterIiv", 429 | "glGetSamplerParameterIuiv", 430 | "glGetSamplerParameterfv", 431 | "glGetSamplerParameteriv", 432 | "glGetShaderInfoLog", 433 | "glGetShaderPrecisionFormat", 434 | "glGetShaderSource", 435 | "glGetShaderiv", 436 | "glGetString", 437 | "glGetStringi", 438 | "glGetSubroutineIndex", 439 | "glGetSubroutineUniformLocation", 440 | "glGetSynciv", 441 | "glGetTexImage", 442 | "glGetTexLevelParameterfv", 443 | "glGetTexLevelParameteriv", 444 | "glGetTexParameterIiv", 445 | "glGetTexParameterIuiv", 446 | "glGetTexParameterfv", 447 | "glGetTexParameteriv", 448 | "glGetTextureImage", 449 | "glGetTextureLevelParameterfv", 450 | "glGetTextureLevelParameteriv", 451 | "glGetTextureParameterIiv", 452 | "glGetTextureParameterIuiv", 453 | "glGetTextureParameterfv", 454 | "glGetTextureParameteriv", 455 | "glGetTextureSubImage", 456 | "glGetTransformFeedbackVarying", 457 | "glGetTransformFeedbacki64_v", 458 | "glGetTransformFeedbacki_v", 459 | "glGetTransformFeedbackiv", 460 | "glGetUniformBlockIndex", 461 | "glGetUniformIndices", 462 | "glGetUniformLocation", 463 | "glGetUniformSubroutineuiv", 464 | "glGetUniformdv", 465 | "glGetUniformfv", 466 | "glGetUniformiv", 467 | "glGetUniformuiv", 468 | "glGetVertexArrayIndexed64iv", 469 | "glGetVertexArrayIndexediv", 470 | "glGetVertexArrayiv", 471 | "glGetVertexAttribIiv", 472 | "glGetVertexAttribIuiv", 473 | "glGetVertexAttribLdv", 474 | "glGetVertexAttribPointerv", 475 | "glGetVertexAttribdv", 476 | "glGetVertexAttribfv", 477 | "glGetVertexAttribiv", 478 | "glGetnCompressedTexImage", 479 | "glGetnTexImage", 480 | "glGetnUniformdv", 481 | "glGetnUniformfv", 482 | "glGetnUniformiv", 483 | "glGetnUniformuiv", 484 | "glHint", 485 | "glInvalidateBufferData", 486 | "glInvalidateBufferSubData", 487 | "glInvalidateFramebuffer", 488 | "glInvalidateNamedFramebufferData", 489 | "glInvalidateNamedFramebufferSubData", 490 | "glInvalidateSubFramebuffer", 491 | "glInvalidateTexImage", 492 | "glInvalidateTexSubImage", 493 | "glIsBuffer", 494 | "glIsEnabled", 495 | "glIsEnabledi", 496 | "glIsFramebuffer", 497 | "glIsProgram", 498 | "glIsProgramPipeline", 499 | "glIsQuery", 500 | "glIsRenderbuffer", 501 | "glIsSampler", 502 | "glIsShader", 503 | "glIsSync", 504 | "glIsTexture", 505 | "glIsTransformFeedback", 506 | "glIsVertexArray", 507 | "glLineWidth", 508 | "glLinkProgram", 509 | "glLogicOp", 510 | "glMapBuffer", 511 | "glMapBufferRange", 512 | "glMapNamedBuffer", 513 | "glMapNamedBufferRange", 514 | "glMemoryBarrier", 515 | "glMemoryBarrierByRegion", 516 | "glMinSampleShading", 517 | "glMultiDrawArrays", 518 | "glMultiDrawArraysIndirect", 519 | "glMultiDrawElements", 520 | "glMultiDrawElementsBaseVertex", 521 | "glMultiDrawElementsIndirect", 522 | "glNamedBufferData", 523 | "glNamedBufferStorage", 524 | "glNamedBufferSubData", 525 | "glNamedFramebufferDrawBuffer", 526 | "glNamedFramebufferDrawBuffers", 527 | "glNamedFramebufferParameteri", 528 | "glNamedFramebufferReadBuffer", 529 | "glNamedFramebufferRenderbuffer", 530 | "glNamedFramebufferTexture", 531 | "glNamedFramebufferTextureLayer", 532 | "glNamedRenderbufferStorage", 533 | "glNamedRenderbufferStorageMultisample", 534 | "glObjectLabel", 535 | "glObjectPtrLabel", 536 | "glPatchParameterfv", 537 | "glPatchParameteri", 538 | "glPauseTransformFeedback", 539 | "glPixelStoref", 540 | "glPixelStorei", 541 | "glPointParameterf", 542 | "glPointParameterfv", 543 | "glPointParameteri", 544 | "glPointParameteriv", 545 | "glPointSize", 546 | "glPolygonMode", 547 | "glPolygonOffset", 548 | "glPopDebugGroup", 549 | "glPrimitiveRestartIndex", 550 | "glProgramBinary", 551 | "glProgramParameteri", 552 | "glProgramUniform1d", 553 | "glProgramUniform1dv", 554 | "glProgramUniform1f", 555 | "glProgramUniform1fv", 556 | "glProgramUniform1i", 557 | "glProgramUniform1iv", 558 | "glProgramUniform1ui", 559 | "glProgramUniform1uiv", 560 | "glProgramUniform2d", 561 | "glProgramUniform2dv", 562 | "glProgramUniform2f", 563 | "glProgramUniform2fv", 564 | "glProgramUniform2i", 565 | "glProgramUniform2iv", 566 | "glProgramUniform2ui", 567 | "glProgramUniform2uiv", 568 | "glProgramUniform3d", 569 | "glProgramUniform3dv", 570 | "glProgramUniform3f", 571 | "glProgramUniform3fv", 572 | "glProgramUniform3i", 573 | "glProgramUniform3iv", 574 | "glProgramUniform3ui", 575 | "glProgramUniform3uiv", 576 | "glProgramUniform4d", 577 | "glProgramUniform4dv", 578 | "glProgramUniform4f", 579 | "glProgramUniform4fv", 580 | "glProgramUniform4i", 581 | "glProgramUniform4iv", 582 | "glProgramUniform4ui", 583 | "glProgramUniform4uiv", 584 | "glProgramUniformMatrix2dv", 585 | "glProgramUniformMatrix2fv", 586 | "glProgramUniformMatrix2x3dv", 587 | "glProgramUniformMatrix2x3fv", 588 | "glProgramUniformMatrix2x4dv", 589 | "glProgramUniformMatrix2x4fv", 590 | "glProgramUniformMatrix3dv", 591 | "glProgramUniformMatrix3fv", 592 | "glProgramUniformMatrix3x2dv", 593 | "glProgramUniformMatrix3x2fv", 594 | "glProgramUniformMatrix3x4dv", 595 | "glProgramUniformMatrix3x4fv", 596 | "glProgramUniformMatrix4dv", 597 | "glProgramUniformMatrix4fv", 598 | "glProgramUniformMatrix4x2dv", 599 | "glProgramUniformMatrix4x2fv", 600 | "glProgramUniformMatrix4x3dv", 601 | "glProgramUniformMatrix4x3fv", 602 | "glProvokingVertex", 603 | "glPushDebugGroup", 604 | "glQueryCounter", 605 | "glReadBuffer", 606 | "glReadPixels", 607 | "glReadnPixels", 608 | "glReleaseShaderCompiler", 609 | "glRenderbufferStorage", 610 | "glRenderbufferStorageMultisample", 611 | "glResumeTransformFeedback", 612 | "glSampleCoverage", 613 | "glSampleMaski", 614 | "glSamplerParameterIiv", 615 | "glSamplerParameterIuiv", 616 | "glSamplerParameterf", 617 | "glSamplerParameterfv", 618 | "glSamplerParameteri", 619 | "glSamplerParameteriv", 620 | "glScissor", 621 | "glScissorArrayv", 622 | "glScissorIndexed", 623 | "glScissorIndexedv", 624 | "glShaderBinary", 625 | "glShaderSource", 626 | "glShaderStorageBlockBinding", 627 | "glStencilFunc", 628 | "glStencilFuncSeparate", 629 | "glStencilMask", 630 | "glStencilMaskSeparate", 631 | "glStencilOp", 632 | "glStencilOpSeparate", 633 | "glTexBuffer", 634 | "glTexBufferRange", 635 | "glTexImage1D", 636 | "glTexImage2D", 637 | "glTexImage2DMultisample", 638 | "glTexImage3D", 639 | "glTexImage3DMultisample", 640 | "glTexParameterIiv", 641 | "glTexParameterIuiv", 642 | "glTexParameterf", 643 | "glTexParameterfv", 644 | "glTexParameteri", 645 | "glTexParameteriv", 646 | "glTexStorage1D", 647 | "glTexStorage2D", 648 | "glTexStorage2DMultisample", 649 | "glTexStorage3D", 650 | "glTexStorage3DMultisample", 651 | "glTexSubImage1D", 652 | "glTexSubImage2D", 653 | "glTexSubImage3D", 654 | "glTextureBarrier", 655 | "glTextureBuffer", 656 | "glTextureBufferRange", 657 | "glTextureParameterIiv", 658 | "glTextureParameterIuiv", 659 | "glTextureParameterf", 660 | "glTextureParameterfv", 661 | "glTextureParameteri", 662 | "glTextureParameteriv", 663 | "glTextureStorage1D", 664 | "glTextureStorage2D", 665 | "glTextureStorage2DMultisample", 666 | "glTextureStorage3D", 667 | "glTextureStorage3DMultisample", 668 | "glTextureSubImage1D", 669 | "glTextureSubImage2D", 670 | "glTextureSubImage3D", 671 | "glTextureView", 672 | "glTransformFeedbackBufferBase", 673 | "glTransformFeedbackBufferRange", 674 | "glTransformFeedbackVaryings", 675 | "glUniform1d", 676 | "glUniform1dv", 677 | "glUniform1f", 678 | "glUniform1fv", 679 | "glUniform1i", 680 | "glUniform1iv", 681 | "glUniform1ui", 682 | "glUniform1uiv", 683 | "glUniform2d", 684 | "glUniform2dv", 685 | "glUniform2f", 686 | "glUniform2fv", 687 | "glUniform2i", 688 | "glUniform2iv", 689 | "glUniform2ui", 690 | "glUniform2uiv", 691 | "glUniform3d", 692 | "glUniform3dv", 693 | "glUniform3f", 694 | "glUniform3fv", 695 | "glUniform3i", 696 | "glUniform3iv", 697 | "glUniform3ui", 698 | "glUniform3uiv", 699 | "glUniform4d", 700 | "glUniform4dv", 701 | "glUniform4f", 702 | "glUniform4fv", 703 | "glUniform4i", 704 | "glUniform4iv", 705 | "glUniform4ui", 706 | "glUniform4uiv", 707 | "glUniformBlockBinding", 708 | "glUniformMatrix2dv", 709 | "glUniformMatrix2fv", 710 | "glUniformMatrix2x3dv", 711 | "glUniformMatrix2x3fv", 712 | "glUniformMatrix2x4dv", 713 | "glUniformMatrix2x4fv", 714 | "glUniformMatrix3dv", 715 | "glUniformMatrix3fv", 716 | "glUniformMatrix3x2dv", 717 | "glUniformMatrix3x2fv", 718 | "glUniformMatrix3x4dv", 719 | "glUniformMatrix3x4fv", 720 | "glUniformMatrix4dv", 721 | "glUniformMatrix4fv", 722 | "glUniformMatrix4x2dv", 723 | "glUniformMatrix4x2fv", 724 | "glUniformMatrix4x3dv", 725 | "glUniformMatrix4x3fv", 726 | "glUniformSubroutinesuiv", 727 | "glUnmapBuffer", 728 | "glUnmapNamedBuffer", 729 | "glUseProgram", 730 | "glUseProgramStages", 731 | "glValidateProgram", 732 | "glValidateProgramPipeline", 733 | "glVertexArrayAttribBinding", 734 | "glVertexArrayAttribFormat", 735 | "glVertexArrayAttribIFormat", 736 | "glVertexArrayAttribLFormat", 737 | "glVertexArrayBindingDivisor", 738 | "glVertexArrayElementBuffer", 739 | "glVertexArrayVertexBuffer", 740 | "glVertexArrayVertexBuffers", 741 | "glVertexAttrib1d", 742 | "glVertexAttrib1dv", 743 | "glVertexAttrib1f", 744 | "glVertexAttrib1fv", 745 | "glVertexAttrib1s", 746 | "glVertexAttrib1sv", 747 | "glVertexAttrib2d", 748 | "glVertexAttrib2dv", 749 | "glVertexAttrib2f", 750 | "glVertexAttrib2fv", 751 | "glVertexAttrib2s", 752 | "glVertexAttrib2sv", 753 | "glVertexAttrib3d", 754 | "glVertexAttrib3dv", 755 | "glVertexAttrib3f", 756 | "glVertexAttrib3fv", 757 | "glVertexAttrib3s", 758 | "glVertexAttrib3sv", 759 | "glVertexAttrib4Nbv", 760 | "glVertexAttrib4Niv", 761 | "glVertexAttrib4Nsv", 762 | "glVertexAttrib4Nub", 763 | "glVertexAttrib4Nubv", 764 | "glVertexAttrib4Nuiv", 765 | "glVertexAttrib4Nusv", 766 | "glVertexAttrib4bv", 767 | "glVertexAttrib4d", 768 | "glVertexAttrib4dv", 769 | "glVertexAttrib4f", 770 | "glVertexAttrib4fv", 771 | "glVertexAttrib4iv", 772 | "glVertexAttrib4s", 773 | "glVertexAttrib4sv", 774 | "glVertexAttrib4ubv", 775 | "glVertexAttrib4uiv", 776 | "glVertexAttrib4usv", 777 | "glVertexAttribBinding", 778 | "glVertexAttribDivisor", 779 | "glVertexAttribFormat", 780 | "glVertexAttribI1i", 781 | "glVertexAttribI1iv", 782 | "glVertexAttribI1ui", 783 | "glVertexAttribI1uiv", 784 | "glVertexAttribI2i", 785 | "glVertexAttribI2iv", 786 | "glVertexAttribI2ui", 787 | "glVertexAttribI2uiv", 788 | "glVertexAttribI3i", 789 | "glVertexAttribI3iv", 790 | "glVertexAttribI3ui", 791 | "glVertexAttribI3uiv", 792 | "glVertexAttribI4bv", 793 | "glVertexAttribI4i", 794 | "glVertexAttribI4iv", 795 | "glVertexAttribI4sv", 796 | "glVertexAttribI4ubv", 797 | "glVertexAttribI4ui", 798 | "glVertexAttribI4uiv", 799 | "glVertexAttribI4usv", 800 | "glVertexAttribIFormat", 801 | "glVertexAttribIPointer", 802 | "glVertexAttribL1d", 803 | "glVertexAttribL1dv", 804 | "glVertexAttribL2d", 805 | "glVertexAttribL2dv", 806 | "glVertexAttribL3d", 807 | "glVertexAttribL3dv", 808 | "glVertexAttribL4d", 809 | "glVertexAttribL4dv", 810 | "glVertexAttribLFormat", 811 | "glVertexAttribLPointer", 812 | "glVertexAttribP1ui", 813 | "glVertexAttribP1uiv", 814 | "glVertexAttribP2ui", 815 | "glVertexAttribP2uiv", 816 | "glVertexAttribP3ui", 817 | "glVertexAttribP3uiv", 818 | "glVertexAttribP4ui", 819 | "glVertexAttribP4uiv", 820 | "glVertexAttribPointer", 821 | "glVertexBindingDivisor", 822 | "glViewport", 823 | "glViewportArrayv", 824 | "glViewportIndexedf", 825 | "glViewportIndexedfv", 826 | "glWaitSync", 827 | }; 828 | 829 | union GL3WProcs gl3wProcs; 830 | 831 | static void load_procs(GL3WGetProcAddressProc proc) 832 | { 833 | size_t i; 834 | for (i = 0; i < ARRAY_SIZE(proc_names); i++) 835 | gl3wProcs.ptr[i] = proc(proc_names[i]); 836 | } 837 | -------------------------------------------------------------------------------- /src/glsl/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 uv; 4 | layout(location = 0) out vec4 color; 5 | 6 | uniform sampler2D texDiffuse; 7 | uniform vec4 vecTint; 8 | uniform int iAlphaTest; 9 | 10 | void main() 11 | { 12 | ivec2 size = textureSize(texDiffuse, 0); 13 | vec2 realuv = vec2( 14 | uv.x / float(size.x), 15 | uv.y / float(size.y)); 16 | color = vecTint * texture(texDiffuse, realuv); 17 | if(iAlphaTest != 0 && color.a < 0.5) { 18 | discard; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/glsl/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec3 vPosition; 4 | layout(location = 1) in vec2 vUV; 5 | 6 | uniform mat4 matTransform; 7 | 8 | out vec2 uv; 9 | 10 | void main() 11 | { 12 | uv = vUV; 13 | gl_Position = matTransform * vec4(vPosition.xyz, 1.0); 14 | } 15 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | int main(int argc, char *argv[]) 4 | { 5 | QSurfaceFormat fmt; 6 | fmt.setVersion(3, 3); 7 | QSurfaceFormat::setDefaultFormat(fmt); 8 | 9 | QApplication a(argc, argv); 10 | MainWindow w; 11 | w.show(); 12 | 13 | return a.exec(); 14 | } 15 | -------------------------------------------------------------------------------- /src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "optionsdialog.h" 21 | #include "openspritesheetdialog.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | MainWindow::MainWindow(QWidget *parent) : 29 | QMainWindow(parent), 30 | ui(new Ui::MainWindow) 31 | { 32 | ui->setupUi(this); 33 | 34 | this->mve = this->ui->modelViewEditor; 35 | this->tse = this->ui->tileSetViewer; 36 | 37 | connect( 38 | this->tse, &TileSetViewer::spriteSelected, 39 | this->mve, &ModelEditorView::beginInsertSprite); 40 | connect( 41 | this->mve, &ModelEditorView::meshChanged, 42 | this, &MainWindow::on_mve_modelHasChanged); 43 | 44 | connect( 45 | this->mve, &ModelEditorView::selectionCleared, 46 | this->tse, &TileSetViewer::resetSelection); 47 | 48 | Mesh mesh; 49 | mesh.texture = OpenSpritesheetDialog::renderImage( 50 | QImage(":/data/tilesets/roguelike-caves.png"), 51 | 16, 52 | 1, 53 | 0); 54 | mesh.minimumTileSize = 16; 55 | this->setModel(mesh); 56 | } 57 | 58 | MainWindow::~MainWindow() 59 | { 60 | delete ui; 61 | } 62 | 63 | void MainWindow::on_actionUndo_triggered() 64 | { 65 | this->mve->undo(); 66 | } 67 | 68 | void MainWindow::on_actionQuit_triggered() 69 | { 70 | QApplication::quit(); 71 | } 72 | 73 | void MainWindow::on_actionSave_triggered() 74 | { 75 | if(this->mCurrentFile.fileName().isEmpty()) { 76 | this->on_actionSave_As_triggered(); 77 | } else { 78 | this->save(); 79 | } 80 | } 81 | 82 | void MainWindow::on_actionSave_As_triggered() 83 | { 84 | QString fileName = QFileDialog::getSaveFileName( 85 | this, 86 | "", 87 | this->mCurrentFile.absolutePath(), 88 | "Versa-Tile Models (*.v3m)"); 89 | if(fileName.isNull()) { 90 | return; 91 | } 92 | this->mCurrentFile.setFile(fileName); 93 | if(this->mCurrentFile.suffix() != "v3m") { 94 | this->mCurrentFile.setFile(this->mCurrentFile.filePath() + ".v3m"); 95 | } 96 | this->save(); 97 | } 98 | 99 | void MainWindow::on_actionNew_triggered() 100 | { 101 | if(this->ensureModelIsSave() == false) { 102 | return; 103 | } 104 | OpenSpritesheetDialog dialog(this); 105 | dialog.setSpriteSize(this->mve->mesh().minimumTileSize); 106 | if(dialog.exec() != QDialog::Accepted) { 107 | return; 108 | } 109 | 110 | Mesh mesh; 111 | mesh.texture = dialog.spriteSheet(); 112 | mesh.minimumTileSize = dialog.spriteSize(); 113 | this->setModel(mesh); 114 | this->mModelIsDirty = false; 115 | updateTitle(); 116 | } 117 | 118 | void MainWindow::closeEvent(QCloseEvent *event) 119 | { 120 | if(this->mModelIsDirty == false) { 121 | return; 122 | } 123 | int result = QMessageBox::question( 124 | this, 125 | this->windowTitle(), 126 | "Do you want to save your changes?", 127 | QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), 128 | QMessageBox::Cancel); 129 | switch(result) 130 | { 131 | case QMessageBox::Yes: 132 | this->on_actionSave_triggered(); 133 | if(this->mModelIsDirty) { 134 | event->ignore(); 135 | } 136 | break; 137 | case QMessageBox::No: 138 | return; 139 | case QMessageBox::Cancel: 140 | event->ignore(); 141 | break; 142 | } 143 | } 144 | 145 | void MainWindow::on_actionOpen_triggered() 146 | { 147 | if(this->ensureModelIsSave() == false) { 148 | return; 149 | } 150 | QString fileName = QFileDialog::getOpenFileName( 151 | this, 152 | "", 153 | this->mCurrentFile.absolutePath(), 154 | "Versa-Tile Models (*.v3m)"); 155 | if(fileName.isNull()) { 156 | return; 157 | } 158 | 159 | QFile file(fileName); 160 | if(file.open(QFile::ReadOnly) == false) { 161 | QMessageBox::warning( 162 | this, 163 | this->windowTitle(), 164 | tr("Failed to open file!")); 165 | return; 166 | } 167 | 168 | Mesh mesh; 169 | mesh.load(file); 170 | file.close(); 171 | 172 | this->setModel(mesh); 173 | this->mCurrentFile.setFile(fileName); 174 | updateTitle(); 175 | } 176 | 177 | void MainWindow::on_mve_modelHasChanged() 178 | { 179 | if(this->mModelIsDirty == false) { 180 | qDebug() << "Mark model as dirty..."; 181 | } 182 | this->mModelIsDirty = true; 183 | updateTitle(); 184 | } 185 | 186 | bool MainWindow::ensureModelIsSave() 187 | { 188 | if(this->mModelIsDirty == false) { 189 | return true; 190 | } 191 | int result = QMessageBox::question( 192 | this, 193 | this->windowTitle(), 194 | tr("Do you want to save your changes?"), 195 | QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, 196 | QMessageBox::NoButton); 197 | switch(result) 198 | { 199 | case QMessageBox::Yes: 200 | this->on_actionSave_triggered(); 201 | return (this->mModelIsDirty == false); 202 | case QMessageBox::No: 203 | return true; 204 | case QMessageBox::Cancel: 205 | return false; 206 | default: 207 | qWarning() << "Unhandled msg box option: " << result; 208 | return false; 209 | } 210 | } 211 | 212 | void MainWindow::save() 213 | { 214 | qDebug() << "Save model to" << this->mCurrentFile.absoluteFilePath(); 215 | QFile file(this->mCurrentFile.absoluteFilePath()); 216 | if(file.open(QFile::WriteOnly) == false) { 217 | QMessageBox::critical( 218 | this, 219 | this->windowTitle(), 220 | tr("Failed to save file!")); 221 | return; 222 | } 223 | this->mve->mesh().save(file); 224 | file.flush(); 225 | file.close(); 226 | 227 | this->mModelIsDirty = false; 228 | updateTitle(); 229 | } 230 | 231 | void MainWindow::setModel(const Mesh & mesh) 232 | { 233 | qDebug() << "Change mesh, set mesh as clean."; 234 | this->mve->setMesh(mesh); 235 | this->tse->setMesh(mesh); 236 | this->mModelIsDirty = false; 237 | updateTitle(); 238 | } 239 | 240 | void MainWindow::updateTitle() 241 | { 242 | QString title; 243 | 244 | if(mCurrentFile.exists()) { 245 | title += mCurrentFile.fileName(); 246 | if(mModelIsDirty == true) { 247 | title += "* - "; 248 | } 249 | else { 250 | title += " - "; 251 | } 252 | } 253 | 254 | title += "VersaTile 3D Editor"; 255 | this->setWindowTitle(title); 256 | } 257 | 258 | void MainWindow::on_actionRotateRight_triggered() 259 | { 260 | this->mve->rotateRight(); 261 | } 262 | 263 | void MainWindow::on_actionRotateLeft_triggered() 264 | { 265 | this->mve->rotateLeft(); 266 | } 267 | 268 | void MainWindow::on_actionFlip_Horizontal_triggered() 269 | { 270 | this->mve->flipHorizontal(); 271 | } 272 | 273 | void MainWindow::on_actionFlip_Vertical_triggered() 274 | { 275 | this->mve->flipVertical(); 276 | } 277 | 278 | void MainWindow::on_actionDelete_Face_triggered() 279 | { 280 | this->mve->deleteSelection(); 281 | } 282 | 283 | void MainWindow::on_toolButtonZoomX1_clicked() 284 | { 285 | this->tse->setScale(1); 286 | } 287 | 288 | void MainWindow::on_toolButtonZoomX2_clicked() 289 | { 290 | this->tse->setScale(2); 291 | } 292 | 293 | void MainWindow::on_toolButtonZoomX3_clicked() 294 | { 295 | this->tse->setScale(3); 296 | } 297 | 298 | void MainWindow::on_toolButtonZoomX4_clicked() 299 | { 300 | this->tse->setScale(4); 301 | } 302 | 303 | void MainWindow::on_actionGitHub_Page_triggered() 304 | { 305 | QDesktopServices::openUrl(QUrl("https://github.com/MasterQ32/VersaTile")); 306 | } 307 | 308 | void MainWindow::on_actionAbout_triggered() 309 | { 310 | QMessageBox::about( 311 | this, 312 | this->windowTitle(), 313 | "VersaTile is a simple 3D model editor.\n" 314 | "Made by: Felix ¨MasterQ32¨ Queißner\n" 315 | "Licence: GNU Puplic Licence"); 316 | } 317 | 318 | static aiVector3D C(const Mesh & mesh, const glm::ivec3 & vec) 319 | { 320 | Q_UNUSED(mesh); 321 | return aiVector3D(vec.x, vec.y, vec.z); 322 | } 323 | 324 | static aiVector3D C(const Mesh & mesh, const glm::vec3 & vec) 325 | { 326 | Q_UNUSED(mesh); 327 | return aiVector3D(vec.x, vec.y, vec.z); 328 | } 329 | 330 | static aiVector3D C(const Mesh &mesh, const glm::ivec2 &vec) 331 | { 332 | return aiVector3D( 333 | float(vec.x) / float(mesh.texture.width()), 334 | float(vec.y) / float(mesh.texture.height()), 335 | 0.0f); 336 | } 337 | 338 | 339 | void MainWindow::on_actionExport_triggered() 340 | { 341 | using namespace Assimp; 342 | 343 | QStringList filterNames; 344 | 345 | Exporter exporter; 346 | size_t cnt = exporter.GetExportFormatCount(); 347 | for(size_t i = 0; i < cnt; i++) { 348 | aiExportFormatDesc const * desc = exporter.GetExportFormatDescription(i); 349 | filterNames << QString(desc->description) + "(*." + QString(desc->fileExtension) + ")"; 350 | } 351 | 352 | if(this->mCurrentExport.isFile() == false) { 353 | this->mCurrentExport = QFileInfo(this->mCurrentFile); 354 | } 355 | 356 | QFileDialog dialog(this); 357 | dialog.setNameFilters(filterNames); 358 | dialog.setFileMode(QFileDialog::AnyFile); 359 | dialog.setAcceptMode(QFileDialog::AcceptSave); 360 | dialog.setDirectory(this->mCurrentExport.dir()); 361 | if(dialog.exec() != QFileDialog::Accepted) { 362 | return; 363 | } 364 | 365 | QFileInfo fileInfo(dialog.selectedFiles()[0]); 366 | 367 | QString textureName = fileInfo.absolutePath() 368 | + "/" 369 | + fileInfo.completeBaseName() 370 | + ".png"; 371 | 372 | this->mCurrentExportFilter = dialog.selectedNameFilter(); 373 | int index = filterNames.indexOf(dialog.selectedNameFilter()); 374 | 375 | const Mesh & src = this->mve->mesh(); 376 | 377 | std::vector positions; 378 | std::vector normals; 379 | std::vector uvcoords; 380 | 381 | auto emitVertex = [&](Vertex const & vtx, glm::vec3 const & normal) -> size_t 382 | { 383 | size_t idx = positions.size(); 384 | positions.push_back(C(src, vtx.position)); 385 | normals.push_back(C(src, normal)); 386 | uvcoords.push_back(C(src, vtx.uv)); 387 | return idx; 388 | }; 389 | 390 | aiMesh * mesh = new aiMesh; 391 | mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; 392 | mesh->mFaces = new aiFace[2 * src.faces.size()]; 393 | mesh->mNumFaces = 2 * src.faces.size(); 394 | mesh->mMaterialIndex = 0; 395 | 396 | for(unsigned int i = 0; i < src.faces.size(); i++) 397 | { 398 | const Face & face = src.faces[i]; 399 | 400 | aiFace & dst0 = mesh->mFaces[2*i+0]; 401 | aiFace & dst1 = mesh->mFaces[2*i+1]; 402 | 403 | dst0.mNumIndices = 3; 404 | dst0.mIndices = new unsigned int[3]; 405 | 406 | dst1.mNumIndices = 3; 407 | dst1.mIndices = new unsigned int[3]; 408 | 409 | glm::vec3 n0 = glm::normalize(glm::cross( 410 | glm::vec3(face.vertices[1].position - face.vertices[0].position), 411 | glm::vec3(face.vertices[3].position - face.vertices[0].position))); 412 | dst0.mIndices[0] = emitVertex(face.vertices[0], n0); 413 | dst0.mIndices[1] = emitVertex(face.vertices[1], n0); 414 | dst0.mIndices[2] = emitVertex(face.vertices[3], n0); 415 | 416 | glm::vec3 n1 = glm::normalize(glm::cross( 417 | glm::vec3(face.vertices[2].position - face.vertices[0].position), 418 | glm::vec3(face.vertices[3].position - face.vertices[0].position))); 419 | dst1.mIndices[0] = emitVertex(face.vertices[0], n1); 420 | dst1.mIndices[1] = emitVertex(face.vertices[2], n1); 421 | dst1.mIndices[2] = emitVertex(face.vertices[3], n1); 422 | } 423 | 424 | mesh->mNumVertices = positions.size(); 425 | mesh->mVertices = new aiVector3D[mesh->mNumVertices]; 426 | mesh->mNormals = new aiVector3D[mesh->mNumVertices]; 427 | mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; 428 | mesh->mNumUVComponents[0] = 2; 429 | 430 | memcpy(mesh->mVertices, positions.data(), sizeof(aiVector3D) * mesh->mNumVertices); 431 | memcpy(mesh->mNormals, normals.data(), sizeof(aiVector3D) * mesh->mNumVertices); 432 | memcpy(mesh->mTextureCoords[0], uvcoords.data(), sizeof(aiVector3D) * mesh->mNumVertices); 433 | 434 | unsigned int meshIndex = 0; 435 | 436 | aiNode * node = new aiNode; 437 | node->mMeshes = &meshIndex; 438 | node->mNumMeshes = 1; 439 | node->mTransformation = aiMatrix4x4(); 440 | node->mName = aiString("root"); 441 | 442 | aiString name("TilesetTexture"); 443 | aiColor3D diffuse(1.0f, 1.0f, 1.0f); 444 | aiString texName(textureName.toUtf8().data()); 445 | int btrue = 1; 446 | 447 | aiMaterial * material = new aiMaterial; 448 | material->AddProperty(&name, AI_MATKEY_NAME); 449 | material->AddProperty(&btrue, 1, AI_MATKEY_TWOSIDED); 450 | material->AddProperty(&texName, AI_MATKEY_TEXTURE_DIFFUSE(0)); 451 | material->AddProperty(&texName, AI_MATKEY_TEXTURE_AMBIENT(0)); 452 | material->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); 453 | 454 | aiMesh ** meshes = new aiMesh*[1]; 455 | meshes[0] = mesh; 456 | 457 | aiMaterial ** materials = new aiMaterial*[1]; 458 | materials[0] = material; 459 | 460 | aiScene * scene = new aiScene; 461 | scene->mNumMeshes = 1; 462 | scene->mNumMaterials = 1; 463 | scene->mMeshes = meshes; 464 | scene->mRootNode = node; 465 | scene->mMaterials = materials; 466 | 467 | aiReturn result = exporter.Export( 468 | scene, 469 | exporter.GetExportFormatDescription(index)->id, 470 | fileInfo.absoluteFilePath().toStdString().c_str(), 471 | aiProcess_Triangulate | aiProcess_FlipUVs); 472 | 473 | if(result == aiReturn_SUCCESS) 474 | { 475 | src.texture.save( 476 | textureName, 477 | "png"); 478 | } 479 | } 480 | 481 | void MainWindow::on_actionHome_triggered() 482 | { 483 | this->mve->gotoCameraHome(); 484 | } 485 | 486 | void MainWindow::on_actionSet_Home_triggered() 487 | { 488 | this->mve->setCameraHome(); 489 | } 490 | 491 | void MainWindow::on_actionTop_triggered() 492 | { 493 | this->mve->gotoCameraTop(); 494 | } 495 | 496 | void MainWindow::on_actionFront_triggered() 497 | { 498 | this->mve->gotoCameraFront(); 499 | } 500 | 501 | void MainWindow::on_actionSide_triggered() 502 | { 503 | this->mve->gotoCameraSide(); 504 | } 505 | 506 | void MainWindow::on_actionRedo_triggered() 507 | { 508 | this->mve->redo(); 509 | } 510 | 511 | void MainWindow::on_actionPreferences_triggered() 512 | { 513 | OptionsDialog dialog(this); 514 | dialog.exec(); 515 | this->mve->loadSettings(); 516 | } 517 | 518 | void MainWindow::on_actionSelection_Mode_triggered() 519 | { 520 | this->mve->resetInsertMode(); 521 | } 522 | 523 | void MainWindow::on_actionFocus_Selection_triggered() 524 | { 525 | this->mve->setCameraToSelection(); 526 | } 527 | 528 | void MainWindow::on_actionUpdate_texture_triggered() 529 | { 530 | int currentSize = this->mve->mesh().minimumTileSize; 531 | OpenSpritesheetDialog dialog(this); 532 | dialog.setSpriteSize(currentSize); 533 | 534 | bool accepted = false; 535 | do { 536 | 537 | if(dialog.exec() != QDialog::Accepted) { 538 | return; 539 | } 540 | 541 | if(currentSize != dialog.spriteSize()) { 542 | auto result = QMessageBox::question( 543 | this, 544 | this->windowTitle(), 545 | tr("The current model has a sprite size of %1, but the imported spritesheet has %2.\n" 546 | "Do you really want to import the sprite sheet?").arg(currentSize).arg(dialog.spriteSize()), 547 | QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel), 548 | QMessageBox::Yes); 549 | switch(result) { 550 | case QMessageBox::Yes: 551 | accepted = true; 552 | break; 553 | case QMessageBox::No: continue; 554 | case QMessageBox::Cancel: return; 555 | default: continue; 556 | } 557 | } else { 558 | accepted = true; 559 | } 560 | 561 | } while(!accepted); 562 | 563 | QImage texture = dialog.spriteSheet(); 564 | int spriteSize = dialog.spriteSize(); 565 | 566 | Mesh mesh(this->mve->mesh()); 567 | mesh.texture = texture; 568 | mesh.minimumTileSize = spriteSize; 569 | this->mve->setMesh(mesh); 570 | this->tse->setMesh(mesh); 571 | } 572 | 573 | void MainWindow::on_actionSwap_Grid_Plane_triggered() 574 | { 575 | this->mve->selectNextGrid(); 576 | } 577 | 578 | void MainWindow::on_actionHomepage_triggered() 579 | { 580 | QDesktopServices::openUrl(QUrl("https://mq32.de/projects/versatile.htm")); 581 | } 582 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | #include "modeleditorview.h" 8 | #include "tilesetviewer.h" 9 | 10 | namespace Ui { 11 | class MainWindow; 12 | } 13 | 14 | class MainWindow : public QMainWindow 15 | { 16 | Q_OBJECT 17 | public: 18 | explicit MainWindow(QWidget *parent = 0); 19 | ~MainWindow(); 20 | 21 | private: 22 | /** 23 | * @brief Checks if the model is dirty, then 24 | * asks the user if he wants to save his changes. 25 | * @return If the model can be changed, this function will return true. 26 | */ 27 | bool ensureModelIsSave(); 28 | 29 | /** 30 | * @brief Saves the current model under the mCurrentFile path 31 | */ 32 | void save(); 33 | 34 | /** 35 | * @brief Changes the mesh 36 | * @param model 37 | */ 38 | void setModel(const Mesh & mesh); 39 | 40 | void updateTitle(); 41 | 42 | void on_mve_modelHasChanged(); 43 | 44 | virtual void closeEvent(QCloseEvent *event) override; 45 | 46 | private slots: 47 | void on_actionUndo_triggered(); 48 | 49 | void on_actionQuit_triggered(); 50 | 51 | void on_actionSave_triggered(); 52 | 53 | void on_actionSave_As_triggered(); 54 | 55 | void on_actionNew_triggered(); 56 | 57 | void on_actionOpen_triggered(); 58 | 59 | void on_actionRotateRight_triggered(); 60 | 61 | void on_actionRotateLeft_triggered(); 62 | 63 | void on_actionFlip_Horizontal_triggered(); 64 | 65 | void on_actionFlip_Vertical_triggered(); 66 | 67 | void on_actionDelete_Face_triggered(); 68 | 69 | void on_toolButtonZoomX1_clicked(); 70 | 71 | void on_toolButtonZoomX2_clicked(); 72 | 73 | void on_toolButtonZoomX3_clicked(); 74 | 75 | void on_toolButtonZoomX4_clicked(); 76 | 77 | void on_actionGitHub_Page_triggered(); 78 | 79 | void on_actionAbout_triggered(); 80 | 81 | void on_actionExport_triggered(); 82 | 83 | void on_actionHome_triggered(); 84 | 85 | void on_actionSet_Home_triggered(); 86 | 87 | void on_actionTop_triggered(); 88 | 89 | void on_actionFront_triggered(); 90 | 91 | void on_actionSide_triggered(); 92 | 93 | void on_actionRedo_triggered(); 94 | 95 | void on_actionPreferences_triggered(); 96 | 97 | void on_actionSelection_Mode_triggered(); 98 | 99 | void on_actionFocus_Selection_triggered(); 100 | 101 | void on_actionUpdate_texture_triggered(); 102 | 103 | void on_actionSwap_Grid_Plane_triggered(); 104 | 105 | void on_actionHomepage_triggered(); 106 | 107 | private: 108 | Ui::MainWindow *ui; 109 | ModelEditorView *mve; 110 | TileSetViewer *tse; 111 | QFileInfo mCurrentFile; 112 | bool mModelIsDirty; 113 | QFileInfo mCurrentExport; 114 | QString mCurrentExportFilter; 115 | }; 116 | 117 | #endif // MAINWINDOW_H 118 | -------------------------------------------------------------------------------- /src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 931 10 | 536 11 | 12 | 13 | 14 | VersaTile 3D Editor 15 | 16 | 17 | 18 | 19 | 750 20 | 0 21 | 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 931 30 | 17 31 | 32 | 33 | 34 | 35 | Fi&le 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Ed&it 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | &View 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Help 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | File Toolbar 93 | 94 | 95 | TopToolBarArea 96 | 97 | 98 | false 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | :/data/tileset.png:/data/tileset.png 111 | 112 | 113 | QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable 114 | 115 | 116 | Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea 117 | 118 | 119 | Tile Set 120 | 121 | 122 | 2 123 | 124 | 125 | 126 | 127 | QLayout::SetMinimumSize 128 | 129 | 130 | 131 | 132 | QLayout::SetDefaultConstraint 133 | 134 | 135 | 136 | 137 | ×1 138 | 139 | 140 | 141 | 142 | 143 | 144 | ×2 145 | 146 | 147 | 148 | 149 | 150 | 151 | ×3 152 | 153 | 154 | 155 | 156 | 157 | 158 | ×4 159 | 160 | 161 | 162 | 163 | 164 | 165 | Qt::Horizontal 166 | 167 | 168 | 169 | 40 170 | 20 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | true 181 | 182 | 183 | 184 | 185 | 0 186 | 0 187 | 156 188 | 411 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | Tools Toolbar 200 | 201 | 202 | TopToolBarArea 203 | 204 | 205 | false 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | Camera Toolbar 221 | 222 | 223 | TopToolBarArea 224 | 225 | 226 | false 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | :/data/new.png:/data/new.png 238 | 239 | 240 | &New 241 | 242 | 243 | Create a new model 244 | 245 | 246 | Ctrl+N 247 | 248 | 249 | 250 | 251 | 252 | :/data/open.png:/data/open.png 253 | 254 | 255 | &Open... 256 | 257 | 258 | Open an existing model 259 | 260 | 261 | Ctrl+O 262 | 263 | 264 | 265 | 266 | 267 | :/data/save.png:/data/save.png 268 | 269 | 270 | &Save 271 | 272 | 273 | Ctrl+S 274 | 275 | 276 | 277 | 278 | Sa&ve As... 279 | 280 | 281 | Save file as... 282 | 283 | 284 | Ctrl+Shift+S 285 | 286 | 287 | 288 | 289 | 290 | :/data/settings.png:/data/settings.png 291 | 292 | 293 | &Preferences... 294 | 295 | 296 | 297 | 298 | &Quit 299 | 300 | 301 | 302 | 303 | 304 | :/data/content-copy.png:/data/content-copy.png 305 | 306 | 307 | C&opy 308 | 309 | 310 | Ctrl+C 311 | 312 | 313 | 314 | 315 | 316 | :/data/content-cut.png:/data/content-cut.png 317 | 318 | 319 | &Cut 320 | 321 | 322 | Ctrl+X 323 | 324 | 325 | 326 | 327 | 328 | :/data/content-paste.png:/data/content-paste.png 329 | 330 | 331 | &Paste 332 | 333 | 334 | Ctrl+V 335 | 336 | 337 | 338 | 339 | 340 | :/data/home.png:/data/home.png 341 | 342 | 343 | &Home 344 | 345 | 346 | Ctrl+H 347 | 348 | 349 | 350 | 351 | Set Home 352 | 353 | 354 | Set camera home 355 | 356 | 357 | Ctrl+Shift+H 358 | 359 | 360 | 361 | 362 | 363 | :/data/camera-xz.png:/data/camera-xz.png 364 | 365 | 366 | &Top 367 | 368 | 369 | View from top 370 | 371 | 372 | Ctrl+T 373 | 374 | 375 | 376 | 377 | 378 | :/data/camera-yz.png:/data/camera-yz.png 379 | 380 | 381 | &Side 382 | 383 | 384 | View from the side 385 | 386 | 387 | Ctrl+G 388 | 389 | 390 | 391 | 392 | 393 | :/data/camera-xy.png:/data/camera-xy.png 394 | 395 | 396 | &Front 397 | 398 | 399 | View from the front 400 | 401 | 402 | Ctrl+F 403 | 404 | 405 | 406 | 407 | 408 | :/data/rotate-left.png:/data/rotate-left.png 409 | 410 | 411 | Rotate &left 412 | 413 | 414 | Rotate left 415 | 416 | 417 | Shift+R 418 | 419 | 420 | 421 | 422 | 423 | :/data/undo-variant.png:/data/undo-variant.png 424 | 425 | 426 | &Undo 427 | 428 | 429 | Ctrl+Z 430 | 431 | 432 | 433 | 434 | 435 | :/data/rotate-right.png:/data/rotate-right.png 436 | 437 | 438 | &Rotate right 439 | 440 | 441 | Rotate right 442 | 443 | 444 | R 445 | 446 | 447 | 448 | 449 | 450 | :/data/flip-horizontal.png:/data/flip-horizontal.png 451 | 452 | 453 | &Flip horizontal 454 | 455 | 456 | Flip horizontal 457 | 458 | 459 | H 460 | 461 | 462 | 463 | 464 | 465 | :/data/flip-vertical.png:/data/flip-vertical.png 466 | 467 | 468 | Flip &vertical 469 | 470 | 471 | Flip vertical 472 | 473 | 474 | V 475 | 476 | 477 | 478 | 479 | 480 | :/data/delete.png:/data/delete.png 481 | 482 | 483 | &Delete tile 484 | 485 | 486 | Deletes the selected tile 487 | 488 | 489 | Del 490 | 491 | 492 | 493 | 494 | 495 | :/data/redo-variant.png:/data/redo-variant.png 496 | 497 | 498 | R&edo 499 | 500 | 501 | Ctrl+Y 502 | 503 | 504 | 505 | 506 | 507 | :/data/export.png:/data/export.png 508 | 509 | 510 | &Export... 511 | 512 | 513 | Export 3D model... 514 | 515 | 516 | 517 | 518 | 519 | :/data/information-variant.png:/data/information-variant.png 520 | 521 | 522 | &About... 523 | 524 | 525 | 526 | 527 | 528 | :/data/github-circle.png:/data/github-circle.png 529 | 530 | 531 | &GitHub Page... 532 | 533 | 534 | Go to the GitHub page 535 | 536 | 537 | 538 | 539 | 540 | :/data/tool-select.png:/data/tool-select.png 541 | 542 | 543 | Selection mode 544 | 545 | 546 | Selection mode 547 | 548 | 549 | Space 550 | 551 | 552 | 553 | 554 | 555 | :/data/focus.png:/data/focus.png 556 | 557 | 558 | Focus Selection 559 | 560 | 561 | Focus selection 562 | 563 | 564 | F 565 | 566 | 567 | 568 | 569 | 570 | :/data/import.png:/data/import.png 571 | 572 | 573 | Update texture... 574 | 575 | 576 | Updates the texture 577 | 578 | 579 | 580 | 581 | 582 | :/data/grid.png:/data/grid.png 583 | 584 | 585 | Swap grid plane 586 | 587 | 588 | Swaps the currently active grid 589 | 590 | 591 | G 592 | 593 | 594 | 595 | 596 | 597 | :/data/web.png:/data/web.png 598 | 599 | 600 | Homepage... 601 | 602 | 603 | 604 | 605 | 606 | 607 | TileSetViewer 608 | QWidget 609 |
tilesetviewer.h
610 | 1 611 |
612 | 613 | ModelEditorView 614 | QWidget 615 |
modeleditorview.h
616 | 1 617 |
618 |
619 | 620 | 621 | 622 | 623 |
624 | -------------------------------------------------------------------------------- /src/mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "mesh.h" 2 | 3 | #include 4 | 5 | #define EPSILON 0.000001 6 | 7 | static bool triangle_intersection(const glm::vec3 V1, // Triangle vertices 8 | const glm::vec3 V2, 9 | const glm::vec3 V3, 10 | const glm::vec3 O, //Ray origin 11 | const glm::vec3 D, //Ray direction 12 | float* out ) 13 | { 14 | glm::vec3 e1, e2; //Edge1, Edge2 15 | glm::vec3 P, Q, T; 16 | float det, inv_det, u, v; 17 | float t; 18 | 19 | //Find vectors for two edges sharing V1 20 | e1 = V2 - V1; 21 | e2 = V3 - V1; 22 | //Begin calculating determinant - also used to calculate u parameter 23 | P = glm::cross(D, e2); 24 | //if determinant is near zero, ray lies in plane of triangle or ray is parallel to plane of triangle 25 | det = glm::dot(e1, P); 26 | //NOT CULLING 27 | if(det > -EPSILON && det < EPSILON) return 0; 28 | inv_det = 1.f / det; 29 | 30 | //calculate distance from V1 to ray origin 31 | T = O - V1; 32 | 33 | //Calculate u parameter and test bound 34 | u = glm::dot(T, P) * inv_det; 35 | //The intersection lies outside of the triangle 36 | if(u < 0.f || u > 1.f) return 0; 37 | 38 | //Prepare to test v parameter 39 | Q = glm::cross(T, e1); 40 | 41 | //Calculate V parameter and test bound 42 | v = glm::dot(D, Q) * inv_det; 43 | //The intersection lies outside of the triangle 44 | if(v < 0.f || u + v > 1.f) return 0; 45 | 46 | t = glm::dot(e2, Q) * inv_det; 47 | 48 | if(t > EPSILON) { //ray intersection 49 | if(out) *out = t; 50 | return 1; 51 | } 52 | 53 | // No hit, no win 54 | return 0; 55 | } 56 | 57 | bool Face::intersects(glm::vec3 origin, glm::vec3 direction, float * distance) const 58 | { 59 | direction = glm::normalize(direction); 60 | // 0, 1, 3 61 | bool hit1 = triangle_intersection( 62 | glm::vec3(this->vertices[0].position), 63 | glm::vec3(this->vertices[1].position), 64 | glm::vec3(this->vertices[3].position), 65 | origin, 66 | direction, 67 | distance); 68 | if(hit1) return true; 69 | // 0, 2, 3 70 | return triangle_intersection( 71 | glm::vec3(this->vertices[0].position), 72 | glm::vec3(this->vertices[2].position), 73 | glm::vec3(this->vertices[3].position), 74 | origin, 75 | direction, 76 | distance); 77 | } 78 | 79 | void Mesh::save(QFile & target) const 80 | { 81 | QDataStream io(&target); 82 | io.setVersion(QDataStream::Qt_5_8); 83 | io.setByteOrder(QDataStream::LittleEndian); 84 | io.setFloatingPointPrecision(QDataStream::DoublePrecision); 85 | 86 | io << QString("VERSATILE MESH"); 87 | io << quint8(1) << quint8(2); // 1.2 is it 88 | io << this->texture; 89 | io << this->minimumTileSize; 90 | io << quint32(this->faces.size()); 91 | for(size_t i = 0; i < this->faces.size(); i++) 92 | { 93 | Face const & face = this->faces[i]; 94 | io << face.fulcrum.x << face.fulcrum.y << face.fulcrum.z; 95 | io << face.normal.x << face.normal.y << face.normal.z; 96 | for(int v = 0; v < 4; v++) 97 | { 98 | Vertex const & vertex = face.vertices[v]; 99 | io << vertex.position.x << vertex.position.y << vertex.position.z; 100 | io << vertex.uv.x << vertex.uv.y; 101 | } 102 | } 103 | } 104 | 105 | void Mesh::load(QFile & source) 106 | { 107 | QDataStream io(&source); 108 | io.setVersion(QDataStream::Qt_5_8); 109 | io.setByteOrder(QDataStream::LittleEndian); 110 | io.setFloatingPointPrecision(QDataStream::DoublePrecision); 111 | 112 | QString header; 113 | quint8 versionMajor, versionMinor; 114 | io >> header >> versionMajor >> versionMinor; 115 | if(header != "VERSATILE MESH") { 116 | qFatal("Invalid file!"); 117 | } 118 | if(versionMajor != 1) { 119 | qFatal("Unsupported file version!"); 120 | } 121 | 122 | io >> this->texture; 123 | if(versionMinor >= 2) { 124 | io >> this->minimumTileSize; 125 | } else { 126 | // Previous versions had 16 px fixed. 127 | this->minimumTileSize = 16; 128 | } 129 | 130 | quint32 count; 131 | io >> count; 132 | this->faces.clear(); 133 | this->faces.resize(count); 134 | for(quint32 i = 0; i < count; i++) 135 | { 136 | Face & face = this->faces[i]; 137 | if(versionMinor > 0) 138 | { 139 | io >> face.fulcrum.x >> face.fulcrum.y >> face.fulcrum.z; 140 | io >> face.normal.x >> face.normal.y >> face.normal.z; 141 | } 142 | for(int v = 0; v < 4; v++) 143 | { 144 | Vertex & vertex = face.vertices[v]; 145 | io >> vertex.position.x >> vertex.position.y >> vertex.position.z; 146 | io >> vertex.uv.x >> vertex.uv.y; 147 | } 148 | if(versionMinor == 0) 149 | { 150 | face.fulcrum = face.vertices[0].position; 151 | face.normal = glm::ivec3(glm::normalize(glm::vec3(glm::cross( 152 | glm::vec3(face.vertices[1].position - face.vertices[0].position), 153 | glm::vec3(face.vertices[2].position - face.vertices[0].position))))); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/mesh.h: -------------------------------------------------------------------------------- 1 | #ifndef MESH_H 2 | #define MESH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct Vertex 10 | { 11 | Vertex() : position(), uv() { } 12 | 13 | Vertex(glm::vec3 vec) : position(vec), uv() { } 14 | 15 | Vertex(glm::vec3 vec, glm::ivec2 uv) : position(vec), uv(uv) { } 16 | 17 | Vertex(glm::ivec3 vec) : position(vec), uv() { } 18 | 19 | Vertex(glm::ivec3 vec, glm::ivec2 uv) : position(vec), uv(uv) { } 20 | 21 | glm::ivec3 position; 22 | glm::ivec2 uv; 23 | }; 24 | 25 | struct Face 26 | { 27 | /** 28 | * @brief A point on the plane this face is attached to. 29 | */ 30 | glm::ivec3 fulcrum; 31 | 32 | /** 33 | * @brief The normale of the plane this face is attached to. 34 | */ 35 | glm::ivec3 normal; 36 | 37 | Vertex vertices[4]; 38 | 39 | bool intersects(glm::vec3 origin, glm::vec3 direction, float * distance = nullptr) const; 40 | }; 41 | 42 | struct Mesh 43 | { 44 | std::vector faces; 45 | QImage texture; 46 | 47 | qint32 minimumTileSize; 48 | 49 | void save(QFile & target) const; 50 | 51 | void load(QFile & source); 52 | }; 53 | 54 | #endif // MESH_H 55 | -------------------------------------------------------------------------------- /src/modeleditorview.h: -------------------------------------------------------------------------------- 1 | #ifndef MODELEDITORVIEW_H 2 | #define MODELEDITORVIEW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "mesh.h" 11 | 12 | class ModelEditorView : public QOpenGLWidget 13 | { 14 | Q_OBJECT 15 | public: 16 | enum Tool 17 | { 18 | Select, 19 | Move, 20 | DisplaceVertex, 21 | Create, 22 | }; 23 | enum RotateDir 24 | { 25 | Left, 26 | Right 27 | }; 28 | enum Flipping 29 | { 30 | None = 0, 31 | Horizontal = 1, 32 | Vertical = 2, 33 | }; 34 | public: 35 | explicit ModelEditorView(QWidget *parent = 0); 36 | 37 | virtual void initializeGL() override; 38 | 39 | virtual void resizeGL(int w, int h) override; 40 | 41 | virtual void paintGL() override; 42 | 43 | void setMesh(const Mesh & mesh); 44 | 45 | const Mesh & mesh() const { return this->mMesh; } 46 | 47 | Mesh & mesh() { return this->mMesh; } 48 | 49 | bool hasMesh() const 50 | { 51 | return !this->mMesh.texture.isNull(); 52 | } 53 | 54 | void setPlaneAxis(int axis) 55 | { 56 | this->mPlaneAxis = axis % 3; 57 | this->repaint(); 58 | } 59 | 60 | glm::ivec3 planeNormal() const 61 | { 62 | glm::ivec3 normal(0,0,0); 63 | normal[this->mPlaneAxis % 3] = 1; 64 | return normal; 65 | } 66 | 67 | void switchToTool(Tool tool); 68 | 69 | void beginInsertSprite(QRect rect) { 70 | this->mSpriteToInsert = rect; 71 | this->mSpriteToInsertRotation = 0; 72 | this->mSpriteToInsertFlipping = None; 73 | this->mCurrentTool = Create; 74 | this->repaint(); 75 | } 76 | 77 | void loadSettings(); 78 | 79 | void undo(); 80 | 81 | void redo(); 82 | 83 | void rotateRight(); 84 | 85 | void rotateLeft(); 86 | 87 | void flipVertical(); 88 | 89 | void flipHorizontal(); 90 | 91 | void deleteSelection(); 92 | 93 | void clearSelection() { 94 | this->mSelectedFace = -1; 95 | this->updateGizmos(); 96 | this->repaint(); 97 | } 98 | 99 | public: 100 | 101 | virtual void focusInEvent(QFocusEvent *event) override; 102 | 103 | virtual void focusOutEvent(QFocusEvent *event) override; 104 | 105 | virtual void mouseMoveEvent(QMouseEvent *event) override; 106 | 107 | virtual void mousePressEvent(QMouseEvent *event) override; 108 | 109 | virtual void mouseReleaseEvent(QMouseEvent *event) override; 110 | 111 | virtual void wheelEvent(QWheelEvent *event) override; 112 | 113 | virtual void keyPressEvent(QKeyEvent *event) override; 114 | virtual void keyReleaseEvent(QKeyEvent *event) override; 115 | private: 116 | void getPlane(glm::ivec3 & normal,glm::ivec3 & tangent, glm::ivec3 & cotangent) const; 117 | 118 | void getPlane(int index, glm::ivec3 & normal,glm::ivec3 & tangent, glm::ivec3 & cotangent) const; 119 | 120 | void getRay(int x, int y, glm::vec3 & origin, glm::vec3 & direction) const; 121 | 122 | int determinePlane(const glm::vec3 & direction); 123 | 124 | glm::ivec3 raycastAgainstPlane(int x, int y) const; 125 | 126 | glm::ivec3 raycastAgainstPlane(glm::vec3 origin, glm::vec3 normal, int x, int y) const; 127 | 128 | bool getFaceToInsert(Face & face); 129 | 130 | void addUndoStep(); 131 | 132 | void rotateFace(Face & face, RotateDir dir); 133 | 134 | /** 135 | * @brief Gets a pointer to the current selection if any. 136 | * @return pointer to the currently selected face or nullptr. 137 | */ 138 | Face * getSelection() { 139 | if(this->hasSelection()) { 140 | return &this->mMesh.faces[this->mSelectedFace]; 141 | } else { 142 | return nullptr; 143 | } 144 | } 145 | 146 | bool hasSelection() { 147 | if(this->mCurrentTool == Create) { 148 | return false; 149 | } 150 | return this->mSelectedFace >= 0 && this->mSelectedFace < int(this->mMesh.faces.size()); 151 | } 152 | 153 | bool hasInsertion() { 154 | return (this->mCurrentTool == Create); 155 | } 156 | 157 | void updateGizmos(); 158 | 159 | signals: 160 | 161 | void meshIsAboutToChange(); 162 | void meshChanged(); 163 | void selectionCleared(); 164 | 165 | private: 166 | void setPan(const QVariant & value); 167 | void setTilt(const QVariant & value); 168 | void setZoom(const QVariant & value); 169 | void limitTilt(); 170 | void limitZoom(); 171 | 172 | void animate(float from, float to, void (ModelEditorView::*target)(const QVariant &)); 173 | 174 | public: 175 | void setCameraHome(); 176 | 177 | void gotoCameraHome(); 178 | void gotoCameraFront(); 179 | void gotoCameraTop(); 180 | void gotoCameraSide(); 181 | 182 | void resetInsertMode(); 183 | 184 | void setCameraToSelection(); 185 | 186 | void selectNextGrid(); 187 | private: 188 | void updateAutoGrid(); 189 | 190 | private: 191 | Mesh mMesh; 192 | QScopedPointer mTexture; 193 | QScopedPointer mPixel; 194 | GLuint shader, vao, vbuffer; 195 | bool mOpenGLReady; 196 | GLint locMatTransform, locTexDiffuse, locVecTint, locIAlphaTest; 197 | QPoint mLastMouse; 198 | float mPan, mTilt, mZoom; 199 | glm::vec3 mCameraPosition; 200 | glm::ivec3 mCameraFocus; 201 | glm::mat4 matViewProj; 202 | int mPlaneAxis; 203 | QRect mSpriteToInsert; 204 | int mSpriteToInsertRotation; 205 | Flipping mSpriteToInsertFlipping; 206 | glm::ivec3 mCursorPosition; 207 | bool mSnapToCoarseGrid; 208 | QStack mUndoStack, mRedoStack; 209 | int mSelectedFace; 210 | Tool mCurrentTool; 211 | QPoint mGizmoPositions[5]; // stores screen positions of vertex [0]-[3] and sprite center in [4] 212 | glm::ivec3 mMoveOffsetToCursor; 213 | int mMoveVertexIndex; 214 | glm::ivec3 mMoveVertexOrigin, mMoveVertexDirection; 215 | glm::vec3 mMoveVertexPlaneNormal; // <- must be vec3 as it can also be smootly rotated 216 | 217 | float mHomePan, mHomeTilt, mHomeZoom; 218 | 219 | bool mAutoGrid; 220 | float mAutoGridThreshold; 221 | int mGroundMode, mGroundSize; 222 | bool mRenderAxis; 223 | }; 224 | 225 | #endif // MODELEDITORVIEW_H 226 | -------------------------------------------------------------------------------- /src/openspritesheetdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "openspritesheetdialog.h" 2 | #include "ui_openspritesheetdialog.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | OpenSpritesheetDialog::OpenSpritesheetDialog(QWidget *parent) : 12 | QDialog(parent), 13 | ui(new Ui::OpenSpritesheetDialog) 14 | { 15 | ui->setupUi(this); 16 | 17 | this->mScene = new QGraphicsScene(this); 18 | this->ui->preview->setScene(this->mScene); 19 | this->checkOk(); 20 | } 21 | 22 | 23 | void OpenSpritesheetDialog::checkOk() 24 | { 25 | this->ui->buttonBox 26 | ->button( QDialogButtonBox::Ok ) 27 | ->setEnabled(!this->mSpriteSheet.isNull() ); 28 | } 29 | 30 | QImage OpenSpritesheetDialog::renderImage(QImage const & src, int size, int padding, int margin) 31 | { 32 | if(src.isNull()) { 33 | return src; 34 | } 35 | 36 | int numX = (src.width() - 2 * margin) / (size + padding); 37 | int numY = (src.height() - 2 * margin) / (size + padding); 38 | 39 | if(numX <= 0 || numY <= 0) { 40 | return QImage(); 41 | } 42 | QImage dest(numX * size, numY * size, src.format()); 43 | dest.fill(qRgba(0, 0, 0, 0)); 44 | { 45 | QPainter painter(&dest); 46 | painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 47 | for(int y = 0; y < numY; y++) 48 | { 49 | for(int x = 0; x < numX; x++) 50 | { 51 | painter.drawImage( 52 | QRect(size * x, size * y, size, size), 53 | src, 54 | QRect( 55 | margin + (size + padding) * x, 56 | margin + (size + padding) * y, 57 | size, 58 | size)); 59 | } 60 | } 61 | } 62 | return dest; 63 | } 64 | 65 | void OpenSpritesheetDialog::renderImage() 66 | { 67 | int margin = this->ui->spritesheetMargin->value(); 68 | int padding = this->ui->spritePadding->value(); 69 | int size = this->ui->spriteSize->value(); 70 | 71 | this->mSpriteSheet = renderImage( 72 | this->mSourceImage, 73 | size, 74 | padding, 75 | margin); 76 | } 77 | 78 | OpenSpritesheetDialog::~OpenSpritesheetDialog() 79 | { 80 | delete ui; 81 | } 82 | 83 | void OpenSpritesheetDialog::on_selectFile_clicked() 84 | { 85 | QString file = QFileDialog::getOpenFileName( 86 | this, 87 | "Select the spritesheet image you want to use", 88 | this->ui->fileName->text(), 89 | "Image Files (*.png *.jpg *.bmp)"); 90 | if(file.isNull()) { 91 | return; 92 | } 93 | this->ui->fileName->setText(file); 94 | } 95 | 96 | 97 | void OpenSpritesheetDialog::setSpriteSize(int size) 98 | { 99 | this->ui->spriteSize->setValue(size); 100 | } 101 | 102 | int OpenSpritesheetDialog::spriteSize() const 103 | { 104 | return this->ui->spriteSize->value(); 105 | } 106 | 107 | void OpenSpritesheetDialog::updatePreview() 108 | { 109 | this->mScene->clear(); 110 | if(this->mSpriteSheet.isNull() == false) 111 | { 112 | // this->mScene->setSceneRect(this->mSpriteSheet.rect()); 113 | 114 | int size = this->ui->spriteSize->value(); 115 | int numX = this->mSpriteSheet.width() / size; 116 | int numY = this->mSpriteSheet.height() / size; 117 | 118 | for(int y = 0; y < numY; y++) { 119 | for(int x = 0; x < numX; x++) { 120 | QGraphicsPixmapItem * pmap = this->mScene->addPixmap( 121 | QPixmap::fromImage( 122 | this->mSpriteSheet.copy(size * x, size * y, size, size))); 123 | pmap->setPos( 124 | 1 + (size + 1) * x, 125 | 1 + (size + 1) * y); 126 | } 127 | } 128 | 129 | for(int x = 0; x <= numX; x++) { 130 | this->mScene->addLine( 131 | (size + 1) * x, 132 | 0, 133 | (size + 1) * x, 134 | (size + 1) * numY, 135 | QPen(QColor(255,0,2550))); 136 | } 137 | 138 | for(int y = 0; y <= numY; y++) { 139 | this->mScene->addLine( 140 | 0, 141 | (size + 1) * y, 142 | (size + 1) * numX, 143 | (size + 1) * y, 144 | QPen(QColor(255,0,0))); 145 | } 146 | } 147 | else 148 | { 149 | this->mScene->setSceneRect(QRect(0,0,0,0)); 150 | } 151 | 152 | this->ui->preview->resetCachedContent(); 153 | this->ui->preview->updateSceneRect(this->mScene->sceneRect()); 154 | } 155 | 156 | void OpenSpritesheetDialog::on_fileName_textChanged(const QString &fileName) 157 | { 158 | this->mSourceImage = QImage(fileName).convertToFormat(QImage::Format_ARGB32); 159 | if(this->mSourceImage.isNull() == false) 160 | { 161 | auto limit = qMin( 162 | this->mSourceImage.width(), 163 | this->mSourceImage.height()); 164 | this->ui->spriteSize->setMaximum(limit); 165 | this->ui->spritePadding->setMaximum(limit / 2); 166 | this->ui->spritesheetMargin->setMaximum(limit / 2); 167 | 168 | this->renderImage(); 169 | } 170 | 171 | this->updatePreview(); 172 | this->checkOk(); 173 | } 174 | 175 | void OpenSpritesheetDialog::on_spritesheetMargin_valueChanged(int arg1) 176 | { 177 | Q_UNUSED(arg1); 178 | this->renderImage(); 179 | this->updatePreview(); 180 | this->checkOk(); 181 | } 182 | 183 | void OpenSpritesheetDialog::on_spritePadding_valueChanged(int arg1) 184 | { 185 | Q_UNUSED(arg1); 186 | this->renderImage(); 187 | this->updatePreview(); 188 | this->checkOk(); 189 | } 190 | 191 | void OpenSpritesheetDialog::on_spriteSize_valueChanged(int arg1) 192 | { 193 | Q_UNUSED(arg1); 194 | this->renderImage(); 195 | this->updatePreview(); 196 | this->checkOk(); 197 | } 198 | -------------------------------------------------------------------------------- /src/openspritesheetdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENSPRITESHEETDIALOG_H 2 | #define OPENSPRITESHEETDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class OpenSpritesheetDialog; 9 | } 10 | 11 | class OpenSpritesheetDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | QImage mSpriteSheet; 16 | QImage mSourceImage; 17 | 18 | QGraphicsScene * mScene; 19 | 20 | public: 21 | explicit OpenSpritesheetDialog(QWidget *parent = 0); 22 | ~OpenSpritesheetDialog(); 23 | 24 | const QImage & spriteSheet() const { 25 | return this->mSpriteSheet; 26 | } 27 | 28 | int spriteSize() const; 29 | 30 | void setSpriteSize(int size); 31 | 32 | static QImage renderImage(QImage const & src, int size, int padding = 0, int margin = 0); 33 | 34 | private slots: 35 | void on_selectFile_clicked(); 36 | 37 | void on_fileName_textChanged(const QString &arg1); 38 | 39 | void on_spritesheetMargin_valueChanged(int arg1); 40 | 41 | void on_spritePadding_valueChanged(int arg1); 42 | 43 | void on_spriteSize_valueChanged(int arg1); 44 | 45 | private: 46 | void checkOk(); 47 | 48 | void renderImage(); 49 | 50 | void updatePreview(); 51 | 52 | private: 53 | Ui::OpenSpritesheetDialog *ui; 54 | }; 55 | 56 | #endif // OPENSPRITESHEETDIALOG_H 57 | -------------------------------------------------------------------------------- /src/openspritesheetdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpenSpritesheetDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 424 10 | 365 11 | 12 | 13 | 14 | Open sprite sheet 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | QFormLayout::AllNonFixedFieldsGrow 24 | 25 | 26 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 27 | 28 | 29 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 30 | 31 | 32 | 33 | 34 | File Name 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ... 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Sprite Size 56 | 57 | 58 | 59 | 60 | 61 | 62 | Spritesheet Margin 63 | 64 | 65 | 66 | 67 | 68 | 69 | Sprite Padding 70 | 71 | 72 | 73 | 74 | 75 | 76 | Preview: 77 | 78 | 79 | 80 | 81 | 82 | 83 | px 84 | 85 | 86 | 1 87 | 88 | 89 | 16 90 | 91 | 92 | 93 | 94 | 95 | 96 | px 97 | 98 | 99 | 100 | 101 | 102 | 103 | px 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | true 113 | 114 | 115 | 116 | 0 117 | 0 118 | 119 | 120 | 121 | false 122 | 123 | 124 | QFrame::StyledPanel 125 | 126 | 127 | QFrame::Sunken 128 | 129 | 130 | 1 131 | 132 | 133 | QAbstractScrollArea::AdjustIgnored 134 | 135 | 136 | QGraphicsView::ScrollHandDrag 137 | 138 | 139 | 140 | 141 | 142 | 143 | Qt::Horizontal 144 | 145 | 146 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | buttonBox 156 | accepted() 157 | OpenSpritesheetDialog 158 | accept() 159 | 160 | 161 | 248 162 | 254 163 | 164 | 165 | 157 166 | 274 167 | 168 | 169 | 170 | 171 | buttonBox 172 | rejected() 173 | OpenSpritesheetDialog 174 | reject() 175 | 176 | 177 | 316 178 | 260 179 | 180 | 181 | 286 182 | 274 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /src/optionsdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "optionsdialog.h" 2 | #include "ui_optionsdialog.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | OptionsDialog::OptionsDialog(QWidget *parent) : 10 | QDialog(parent), 11 | ui(new Ui::OptionsDialog), 12 | mSettings("mq32.de", "versa-tile") 13 | { 14 | ui->setupUi(this); 15 | 16 | this->mSettings.beginGroup("behaviour"); 17 | this->ui->cbEnableAutoGrid->setChecked(this->mSettings.value("autogrid", true).toBool()); 18 | this->ui->slAutoGridThreshold->setValue(this->mSettings.value("gridthresh", 50).toInt()); 19 | this->mSettings.endGroup(); 20 | 21 | this->mSettings.beginGroup("display"); 22 | this->ui->cbGroundMode->setCurrentIndex(this->mSettings.value("groundmode", 2).toInt()); 23 | this->ui->sbGroundSize->setValue(this->mSettings.value("groundsize", 10).toInt()); 24 | this->ui->cbShowCoordinateAxis->setChecked(this->mSettings.value("axis", false).toBool()); 25 | this->mSettings.endGroup(); 26 | } 27 | 28 | OptionsDialog::~OptionsDialog() 29 | { 30 | delete ui; 31 | } 32 | 33 | void OptionsDialog::on_buttonBox_accepted() 34 | { 35 | this->mSettings.beginGroup("behaviour"); 36 | this->mSettings.setValue("autogrid", this->ui->cbEnableAutoGrid->isChecked()); 37 | this->mSettings.setValue("gridthresh", this->ui->slAutoGridThreshold->value()); 38 | this->mSettings.endGroup(); 39 | 40 | this->mSettings.beginGroup("display"); 41 | this->mSettings.setValue("groundmode", this->ui->cbGroundMode->currentIndex()); 42 | this->mSettings.setValue("groundsize", this->ui->sbGroundSize->value()); 43 | this->mSettings.setValue("axis", this->ui->cbShowCoordinateAxis->isChecked()); 44 | this->mSettings.endGroup(); 45 | } 46 | -------------------------------------------------------------------------------- /src/optionsdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONSDIALOG_H 2 | #define OPTIONSDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class OptionsDialog; 9 | } 10 | 11 | class OptionsDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit OptionsDialog(QWidget *parent = 0); 17 | ~OptionsDialog(); 18 | 19 | private slots: 20 | void on_buttonBox_accepted(); 21 | 22 | private: 23 | Ui::OptionsDialog *ui; 24 | QSettings mSettings; 25 | }; 26 | 27 | #endif // OPTIONSDIALOG_H 28 | -------------------------------------------------------------------------------- /src/optionsdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | OptionsDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 423 10 | 273 11 | 12 | 13 | 14 | Versa-Tile Preferences 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | Behaviour 25 | 26 | 27 | 28 | QFormLayout::AllNonFixedFieldsGrow 29 | 30 | 31 | 32 | 33 | Automatically adjust grid orientation 34 | 35 | 36 | 37 | 38 | 39 | 40 | Enabled 41 | 42 | 43 | 44 | 45 | 46 | 47 | 100 48 | 49 | 50 | 10 51 | 52 | 53 | 60 54 | 55 | 56 | true 57 | 58 | 59 | Qt::Horizontal 60 | 61 | 62 | false 63 | 64 | 65 | false 66 | 67 | 68 | 69 | 70 | 71 | 72 | Horizontal Grid Threshold 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Display 81 | 82 | 83 | 84 | QFormLayout::AllNonFixedFieldsGrow 85 | 86 | 87 | 88 | 89 | Ground Display 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | None 98 | 99 | 100 | 101 | 102 | Grid 103 | 104 | 105 | 106 | 107 | Solid 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Ground Size 116 | 117 | 118 | 119 | 120 | 121 | 122 | Tiles 123 | 124 | 125 | 126 | 127 | 128 | 1 129 | 130 | 131 | 1500 132 | 133 | 134 | 135 | 136 | 137 | 138 | Show Coordinate Axis 139 | 140 | 141 | 142 | 143 | 144 | 145 | Enabled 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Qt::Horizontal 157 | 158 | 159 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | buttonBox 169 | accepted() 170 | OptionsDialog 171 | accept() 172 | 173 | 174 | 248 175 | 254 176 | 177 | 178 | 157 179 | 274 180 | 181 | 182 | 183 | 184 | buttonBox 185 | rejected() 186 | OptionsDialog 187 | reject() 188 | 189 | 190 | 316 191 | 260 192 | 193 | 194 | 286 195 | 274 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /src/tilesetviewer.cpp: -------------------------------------------------------------------------------- 1 | #include "tilesetviewer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | TileSetViewer::TileSetViewer(QWidget *parent) : QWidget(parent), mScale(1) 8 | { 9 | 10 | } 11 | 12 | void TileSetViewer::setMesh(const Mesh & mesh) 13 | { 14 | this->mMesh = mesh; 15 | if(this->texture().isNull() == false) { 16 | this->setMaximumSize(this->minimumSizeHint()); 17 | } 18 | this->repaint(); 19 | } 20 | 21 | QSize TileSetViewer::sizeHint() const 22 | { 23 | return this->minimumSizeHint(); 24 | } 25 | 26 | QSize TileSetViewer::minimumSizeHint() const 27 | { 28 | if(this->texture().isNull()) { 29 | return QSize(256, 256); 30 | } else { 31 | return QSize( 32 | this->mScale * this->texture().width() + 3, 33 | this->mScale * this->texture().height() + 3); 34 | } 35 | } 36 | 37 | void TileSetViewer::paintEvent(QPaintEvent *event) 38 | { 39 | QPainter painter(this); 40 | 41 | if(this->texture().isNull()) 42 | return; 43 | 44 | painter.setPen(QColor(0, 0, 0)); 45 | painter.drawRect( 46 | 1, 1, 47 | this->mScale * this->texture().width() + 1, 48 | this->mScale * this->texture().width() + 1); 49 | painter.drawImage( 50 | QRect(2, 2, this->texture().width() * this->mScale, this->texture().height() * this->mScale), 51 | this->texture()); 52 | 53 | if(this->mSelection.width() > 0 && this->mSelection.height() > 0) 54 | { 55 | painter.setPen(QColor(255, 255, 255)); 56 | painter.drawRect( 57 | this->mScale * this->tileSize() * this->mSelection.x() + 1, 58 | this->mScale * this->tileSize() * this->mSelection.y() + 1, 59 | this->mScale * this->tileSize() * this->mSelection.width() + 1, 60 | this->mScale * this->tileSize() * this->mSelection.height() + 1); 61 | } 62 | 63 | event->accept(); 64 | } 65 | 66 | void TileSetViewer::mouseMoveEvent(QMouseEvent *event) 67 | { 68 | int x = event->x() - 4; 69 | int y = event->y() - 4; 70 | if(x < 0 || x >= (this->mScale * this->texture().width())) { 71 | return; 72 | } 73 | if(y < 0 || y >= (this->mScale * this->texture().height())) { 74 | return; 75 | } 76 | 77 | int tx = (x / this->mScale) / this->tileSize(); 78 | int ty = (y / this->mScale) / this->tileSize(); 79 | 80 | if(event->buttons() & Qt::LeftButton) { 81 | this->mSelection = QRect(tx, ty, 1, 1).united(this->mStartSelection); 82 | } 83 | this->repaint(); 84 | } 85 | 86 | void TileSetViewer::mousePressEvent(QMouseEvent *event) 87 | { 88 | int x = event->x() - 4; 89 | int y = event->y() - 4; 90 | if(x < 0 || x >= (this->mScale * this->texture().width())) { 91 | return; 92 | } 93 | if(y < 0 || y >= (this->mScale * this->texture().height())) { 94 | return; 95 | } 96 | 97 | int tx = (x / this->mScale) / this->tileSize(); 98 | int ty = (y / this->mScale) / this->tileSize(); 99 | 100 | if(event->button() == Qt::LeftButton) { 101 | this->mStartSelection = QRect(tx, ty, 1, 1); 102 | this->mSelection = this->mStartSelection; 103 | qDebug() << this->mStartSelection << this->mSelection; 104 | } 105 | this->repaint(); 106 | } 107 | 108 | void TileSetViewer::mouseReleaseEvent(QMouseEvent *event) 109 | { 110 | if(event->button() == Qt::LeftButton) 111 | { 112 | if(this->mSelection.width() > 0) { 113 | // finally, we have selected something :) 114 | this->spriteSelected(QRect( 115 | this->tileSize() * this->mSelection.x(), 116 | this->tileSize() * this->mSelection.y(), 117 | this->tileSize() * this->mSelection.width(), 118 | this->tileSize() * this->mSelection.height())); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/tilesetviewer.h: -------------------------------------------------------------------------------- 1 | #ifndef TILESETVIEWER_H 2 | #define TILESETVIEWER_H 3 | 4 | #include 5 | #include 6 | #include "mesh.h" 7 | 8 | class TileSetViewer : public QWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit TileSetViewer(QWidget *parent = 0); 13 | 14 | virtual QSize sizeHint() const override; 15 | 16 | virtual QSize minimumSizeHint() const override; 17 | 18 | virtual void paintEvent(QPaintEvent *event) override; 19 | 20 | virtual void mouseMoveEvent(QMouseEvent *event) override; 21 | 22 | virtual void mousePressEvent(QMouseEvent *event) override; 23 | 24 | virtual void mouseReleaseEvent(QMouseEvent *event) override; 25 | 26 | void resetSelection() { 27 | this->mSelection = QRect(); 28 | this->repaint(); 29 | } 30 | 31 | void setMesh(const Mesh & mesh); 32 | 33 | const Mesh & mesh() const { 34 | return this->mMesh; 35 | } 36 | 37 | const QImage & texture() const { 38 | return this->mMesh.texture; 39 | } 40 | 41 | int tileSize() const { 42 | return this->mMesh.minimumTileSize; 43 | } 44 | 45 | int scale() const { return this->mScale; } 46 | 47 | void setScale(int scale) 48 | { 49 | this->mScale = scale; 50 | if(this->mScale < 0) this->mScale = 1; 51 | if(this->mScale > 4) this->mScale = 4; 52 | this->setFixedSize(this->minimumSizeHint()); 53 | this->updateGeometry(); 54 | this->repaint(); 55 | } 56 | 57 | signals: 58 | void spriteSelected(QRect sprite); 59 | 60 | public slots: 61 | 62 | private: 63 | Mesh mMesh; 64 | QRect mSelection; 65 | QRect mStartSelection; 66 | int mScale; 67 | }; 68 | 69 | #endif // TILESETVIEWER_H 70 | -------------------------------------------------------------------------------- /src/versa-tile.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2017-04-24T21:05:26 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | linux { 10 | CONFIG += link_pkgconfig 11 | PKGCONFIG += assimp 12 | } 13 | win32 { 14 | LIBS += -L$$quote(C:\Users\Felix\Desktop\assimp\build\x64) assimp-vc140-mt.lib IrrXML.lib zlibstatic.lib 15 | INCLUDEPATH += $$quote(C:\Users\Felix\Desktop\assimp\include) 16 | DEPENDPATH += $$quote(C:\Users\Felix\Desktop\assimp\include) 17 | INCLUDEPATH += $$quote(C:\Users\Felix\Desktop\assimp\build\include) 18 | DEPENDPATH += $$quote(C:\Users\Felix\Desktop\assimp\build\include) 19 | 20 | INCLUDEPATH += $$quote(../glm/) 21 | DEPENDPATH += $$quote(../glm/) 22 | } 23 | 24 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 25 | 26 | TARGET = versa-tile 27 | TEMPLATE = app 28 | CONFIG += c++11 29 | 30 | # The following define makes your compiler emit warnings if you use 31 | # any feature of Qt which as been marked as deprecated (the exact warnings 32 | # depend on your compiler). Please consult the documentation of the 33 | # deprecated API in order to know how to port your code away from it. 34 | DEFINES += QT_DEPRECATED_WARNINGS 35 | 36 | # You can also make your code fail to compile if you use deprecated APIs. 37 | # In order to do so, uncomment the following line. 38 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 39 | DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 40 | 41 | 42 | SOURCES += main.cpp\ 43 | mainwindow.cpp \ 44 | modeleditorview.cpp \ 45 | tilesetviewer.cpp \ 46 | mesh.cpp \ 47 | optionsdialog.cpp \ 48 | openspritesheetdialog.cpp 49 | 50 | HEADERS += mainwindow.h \ 51 | modeleditorview.h \ 52 | tilesetviewer.h \ 53 | mesh.h \ 54 | optionsdialog.h \ 55 | openspritesheetdialog.h 56 | 57 | FORMS += mainwindow.ui \ 58 | optionsdialog.ui \ 59 | openspritesheetdialog.ui 60 | 61 | RESOURCES += \ 62 | data.qrc 63 | 64 | include($$PWD/gl3w/gl3w.pri) 65 | --------------------------------------------------------------------------------