From 1c6d5a96290cdfd3cd1a39f5acede0dd5f22ba62 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Fri, 1 Nov 2024 02:36:09 +0200 Subject: [PATCH 1/1] initial broken commit --- .gitignore | 6 + COPYING | 674 ++++++++++++++++++++++++++++++++++++++ Makefile | 44 +++ README.md | 22 ++ examples/Makefile | 7 + include/3dgfx.h | 163 ++++++++++ src/3dgfx.c | 935 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/mesh.c | 608 ++++++++++++++++++++++++++++++++++ src/mesh.h | 60 ++++ src/meshload.c | 464 ++++++++++++++++++++++++++ src/polyclip.c | 331 +++++++++++++++++++ src/polyclip.h | 38 +++ src/polyfill.c | 455 ++++++++++++++++++++++++++ src/polyfill.h | 97 ++++++ src/polytmpl.h | 314 ++++++++++++++++++ src/scene.c | 637 ++++++++++++++++++++++++++++++++++++ src/scene.h | 92 ++++++ 17 files changed, 4947 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README.md create mode 100644 examples/Makefile create mode 100644 include/3dgfx.h create mode 100644 src/3dgfx.c create mode 100644 src/mesh.c create mode 100644 src/mesh.h create mode 100644 src/meshload.c create mode 100644 src/polyclip.c create mode 100644 src/polyclip.h create mode 100644 src/polyfill.c create mode 100644 src/polyfill.h create mode 100644 src/polytmpl.h create mode 100644 src/scene.c create mode 100644 src/scene.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48f0e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.o +*.d +*.swp +*.a +*.so* +*.obj diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b80979 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +src = $(wildcard src/*.c) +obj = $(src:.c=.o) +dep = $(src:.c=.d) + +name = retro3d +alib = lib$(name).a +solib = lib$(name).so.$(somajor).$(sominor) +soname = lib$(name).so.$(somajor) +ldname = lib$(name).so + +warn = -pedantic -Wall +opt = -O3 -ffast-math +dbg = -g +inc = -Iinclude + +CFLAGS = $(warn) $(dbg) $(opt) $(inc) -MMD +LDFLAGS = + +.PHONY: all +all: $(solib) $(alib) + +$(alib): $(obj) + $(AR) rcs $@ $(obj) + +$(solib): $(obj) + $(CC) -o $@ -shared -Wl,-soname,$(soname) + +-include $(dep) + +.PHONY: clean +clean: + rm -f $(obj) $(alib) $(solib) + +.PHONY: cleandep +cleandep: + rm -f $(dep) + +.PHONY: examples +examples: + $(MAKE) -C examples + +.PHONY: clean-examples +clean-examples: + $(MAKE) -C examples clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..e01e01a --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +Retro 3D software renderer +========================== + +Retro 3D is a library for rendering real-time 3D graphics on retro platforms. +Retro platforms differ quite a lot, so there are multiple implementations of the +same API, targetting different eras of computing. The main differentiation is +between systems with a floating point unit (like mid-90s pentium PCs), and +systems without a floating point unit, or with an unusably slow FPU, like an +early 90s 386 PC, or something like a Gameboy Advance. + +License +------- +Copyright (C) 2024 John Tsiombikas + +This library is free software. Feel free to use, modify, and/or redistribute it, +under the terms of the GNU General Public License version 3, or at your option +any later version published by the Free Software Foundation. See COPYING for +details. + +If you want to use this library for a project with a GPLv3-incompatible free +software license, send me an email and I'll add a license-specific exception. +The idea is to cut out proprietary software, not limit free software uses. diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..0e79b63 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,7 @@ +.PHONY: all +all: + echo all + +.PHONY: clean +clean: + echo foo diff --git a/include/3dgfx.h b/include/3dgfx.h new file mode 100644 index 0000000..c9f60af --- /dev/null +++ b/include/3dgfx.h @@ -0,0 +1,163 @@ +#ifndef THREEDGFX_H_ +#define THREEDGFX_H_ + +#include + +#define G3D_PIXFMT16 +typedef uint16_t g3d_pixel; + +#ifdef G3D_PIXFMT16 +#define G3D_PACK_RGB(r, g, b) PACK_RGB16(r, g, b) +#define G3D_UNPACK_R(c) UNPACK_R16(c) +#define G3D_UNPACK_G(c) UNPACK_G16(c) +#define G3D_UNPACK_B(c) UNPACK_B16(c) +#endif +#ifdef G3D_PIXFMT32 +#define G3D_PACK_RGB(r, g, b) PACK_RGB32(r, g, b) +#define G3D_UNPACK_R(c) UNPACK_R32(c) +#define G3D_UNPACK_G(c) UNPACK_G32(c) +#define G3D_UNPACK_B(c) UNPACK_B32(c) +#endif + + +struct g3d_vertex { + float x, y, z, w; + float nx, ny, nz; + float u, v; + unsigned char r, g, b, a; +}; + +enum { + G3D_POINTS = 1, + G3D_LINES = 2, + G3D_TRIANGLES = 3, + G3D_QUADS = 4 +}; + +/* g3d_enable/g3d_disable bits */ +enum { + G3D_CULL_FACE = 0x000001, + G3D_DEPTH_TEST = 0x000002, + G3D_LIGHTING = 0x000004, + G3D_LIGHT0 = 0x000008, + G3D_LIGHT1 = 0x000010, + G3D_LIGHT2 = 0x000020, + G3D_LIGHT3 = 0x000040, + G3D_TEXTURE_2D = 0x000080, + G3D_ALPHA_BLEND = 0x000100, + G3D_TEXTURE_GEN = 0x000200, + G3D_CLIP_FRUSTUM = 0x000800,/* when disabled, don't clip against the frustum */ + G3D_CLIP_PLANE0 = 0x001000, /* user-defined 3D clipping planes XXX not impl. */ + G3D_CLIP_PLANE1 = 0x002000, + G3D_CLIP_PLANE2 = 0x004000, + G3D_CLIP_PLANE3 = 0x008000, + + G3D_TEXTURE_MAT = 0x010000, + G3D_SPECULAR = 0x020000, + + G3D_ADD_BLEND = 0x040000, + + G3D_ALL = 0x7fffffff +}; + +/* arg to g3d_front_face */ +enum { G3D_CCW, G3D_CW }; + +/* arg to g3d_polygon_mode */ +enum { + G3D_WIRE, + G3D_FLAT, + G3D_GOURAUD +}; + +/* arg to g3d_texture_mode */ +enum { + G3D_TEX_MODULATE = 1, + G3D_TEX_ADD, + G3D_TEX_REPLACE +}; + +/* matrix stacks */ +enum { + G3D_MODELVIEW, + G3D_PROJECTION, + G3D_TEXTURE, + + G3D_NUM_MATRICES +}; + +/* clear bits */ +enum { + G3D_COLOR_BUFFER_BIT = 1, + G3D_DEPTH_BUFFER_BIT = 2 +}; + +int g3d_init(void); +void g3d_destroy(void); +void g3d_reset(void); + +void g3d_framebuffer(int width, int height, void *pixels); +void g3d_framebuffer_addr(void *pixels); +void g3d_viewport(int x, int y, int w, int h); + +void g3d_clear_color(unsigned char r, unsigned char g, unsigned char b); +void g3d_clear_depth(float z); +void g3d_clear(unsigned int mask); + +void g3d_enable(unsigned int opt); +void g3d_disable(unsigned int opt); +void g3d_setopt(unsigned int opt, unsigned int mask); +unsigned int g3d_getopt(unsigned int mask); + +void g3d_front_face(unsigned int order); +void g3d_polygon_mode(int pmode); +int g3d_get_polygon_mode(void); +void g3d_texture_mode(int pmode); +int g3d_get_texture_mode(void); + +void g3d_matrix_mode(int mmode); + +void g3d_load_identity(void); +void g3d_load_matrix(const float *m); +void g3d_mult_matrix(const float *m); +void g3d_push_matrix(void); +void g3d_pop_matrix(void); + +void g3d_translate(float x, float y, float z); +void g3d_rotate(float angle, float x, float y, float z); +void g3d_scale(float x, float y, float z); +void g3d_ortho(float left, float right, float bottom, float top, float znear, float zfar); +void g3d_frustum(float left, float right, float bottom, float top, float znear, float zfar); +void g3d_perspective(float vfov, float aspect, float znear, float zfar); + +/* returns pointer to the *internal* matrix, and if argument m is not null, + * also copies the internal matrix there. */ +const float *g3d_get_matrix(int which, float *m); + +void g3d_light_pos(int idx, float x, float y, float z); +void g3d_light_dir(int idx, float x, float y, float z); +void g3d_light_color(int idx, float r, float g, float b); + +void g3d_light_ambient(float r, float g, float b); + +void g3d_mtl_diffuse(float r, float g, float b); +void g3d_mtl_specular(float r, float g, float b); +void g3d_mtl_shininess(float shin); + +void g3d_set_texture(int xsz, int ysz, void *pixels); + +void g3d_draw(int prim, const struct g3d_vertex *varr, int varr_size); +void g3d_draw_indexed(int prim, const struct g3d_vertex *varr, int varr_size, + const uint16_t *iarr, int iarr_size); + +void g3d_begin(int prim); +void g3d_end(void); +void g3d_vertex(float x, float y, float z); +void g3d_normal(float x, float y, float z); +void g3d_color3b(unsigned char r, unsigned char g, unsigned char b); +void g3d_color4b(unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void g3d_color3f(float r, float g, float b); +void g3d_color4f(float r, float g, float b, float a); +void g3d_texcoord(float u, float v); + +#endif /* THREEDGFX_H_ */ diff --git a/src/3dgfx.c b/src/3dgfx.c new file mode 100644 index 0000000..353b5fb --- /dev/null +++ b/src/3dgfx.c @@ -0,0 +1,935 @@ +#ifndef BUILD_OPENGL +#include +#include +#include +#include +#include +#include "3dgfx.h" +#include "polyfill.h" +#include "polyclip.h" + + +#undef CORRECT_NORMAL_MATRIX +#ifdef CORRECT_NORMAL_MATRIX +#include +#endif + +#define ENABLE_ZBUFFER + +#define STACK_SIZE 16 +typedef float g3d_matrix[16]; + +#define MAX_LIGHTS 4 + +#define IMM_VBUF_SIZE 256 + +#define NORMALIZE(v) \ + do { \ + float len = sqrt((v)[0] * (v)[0] + (v)[1] * (v)[1] + (v)[2] * (v)[2]); \ + if(len != 0.0) { \ + float s = 1.0 / len; \ + (v)[0] *= s; \ + (v)[1] *= s; \ + (v)[2] *= s; \ + } \ + } while(0) + +enum {LT_POS, LT_DIR}; +struct light { + int type; + float x, y, z; + float r, g, b; +}; + +struct material { + float kd[3]; + float ks[3]; + float shin; +}; + +struct g3d_state { + unsigned int opt; + int frontface; + int polymode, texmode; + + g3d_matrix mat[G3D_NUM_MATRICES][STACK_SIZE]; + int mtop[G3D_NUM_MATRICES]; + int mmode; + + g3d_matrix norm_mat; + + float ambient[3]; + struct light lt[MAX_LIGHTS]; + struct material mtl; + + int width, height; + g3d_pixel *pixels; + + int vport[4]; + + uint16_t clear_color; + uint32_t clear_depth; + + /* immediate mode */ + int imm_prim; + int imm_numv, imm_pcount; + struct g3d_vertex imm_curv; + struct g3d_vertex imm_vbuf[IMM_VBUF_SIZE]; +}; + +static void calc_grad(struct g3d_vertex *v); + +static void imm_flush(void); +static __inline void xform4_vec3(const float *mat, float *vec); +static __inline void xform3_vec3(const float *mat, float *vec); +static void shade(struct g3d_vertex *v); + +static struct g3d_state *st; +static const float idmat[] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 +}; + +int g3d_init(void) +{ + if(!(st = calloc(1, sizeof *st))) { + fprintf(stderr, "failed to allocate G3D context\n"); + return -1; + } + g3d_reset(); + + return 0; +} + +void g3d_destroy(void) +{ +#ifdef ENABLE_ZBUFFER + free(pfill_zbuf); +#endif + free(st); +} + +void g3d_reset(void) +{ + int i; + +#ifdef ENABLE_ZBUFFER + free(pfill_zbuf); +#endif + memset(st, 0, sizeof *st); + + st->opt = G3D_CLIP_FRUSTUM; + st->polymode = POLYFILL_FLAT; + st->texmode = POLYFILL_TEXMOD; + + for(i=0; iclear_depth = 0xffffff; +} + +void g3d_framebuffer(int width, int height, void *pixels) +{ + static int max_height; + +#ifdef ENABLE_ZBUFFER + static int max_npixels; + int npixels = width * height; + + if(npixels > max_npixels) { + free(pfill_zbuf); + pfill_zbuf = malloc(npixels * sizeof *pfill_zbuf); + max_npixels = npixels; + } +#endif + + if(height > max_height) { + polyfill_fbheight(height); + max_height = height; + } + + st->width = width; + st->height = height; + + pfill_fb.pixels = pixels; + pfill_fb.width = width; + pfill_fb.height = height; + + g3d_viewport(0, 0, width, height); +} + +/* set the framebuffer pointer, without resetting the size */ +void g3d_framebuffer_addr(void *pixels) +{ + pfill_fb.pixels = pixels; +} + +void g3d_viewport(int x, int y, int w, int h) +{ + st->vport[0] = x; + st->vport[1] = y; + st->vport[2] = w; + st->vport[3] = h; +} + +void g3d_clear_color(unsigned char r, unsigned char g, unsigned char b) +{ + st->clear_color = PACK_RGB16(r, g, b); +} + +void g3d_clear_depth(float z) +{ + int iz = (int)(z * (float)0xffffff); + if(iz < 0) iz = 0; + if(iz > 0xffffff) iz = 0xffffff; + st->clear_depth = iz; +} + +void g3d_clear(unsigned int mask) +{ + if(mask & G3D_COLOR_BUFFER_BIT) { + memset16(pfill_fb.pixels, st->clear_color, pfill_fb.width * pfill_fb.height); + } + if(mask & G3D_DEPTH_BUFFER_BIT) { + memset16(pfill_zbuf, st->clear_depth, pfill_fb.width * pfill_fb.height * sizeof *pfill_zbuf / 2); + } +} + +void g3d_enable(unsigned int opt) +{ + st->opt |= opt; +} + +void g3d_disable(unsigned int opt) +{ + st->opt &= ~opt; +} + +void g3d_setopt(unsigned int opt, unsigned int mask) +{ + st->opt = (st->opt & ~mask) | (opt & mask); +} + +unsigned int g3d_getopt(unsigned int mask) +{ + return st->opt & mask; +} + +void g3d_front_face(unsigned int order) +{ + st->frontface = order; +} + +void g3d_polygon_mode(int pmode) +{ + st->polymode = pmode; +} + +int g3d_get_polygon_mode(void) +{ + return st->polymode; +} + +void g3d_texture_mode(int tmode) +{ + st->texmode = tmode; +} + +int g3d_get_texture_mode(void) +{ + return st->texmode; +} + +void g3d_matrix_mode(int mmode) +{ + st->mmode = mmode; +} + +void g3d_load_identity(void) +{ + int top = st->mtop[st->mmode]; + memcpy(st->mat[st->mmode][top], idmat, 16 * sizeof(float)); +} + +void g3d_load_matrix(const float *m) +{ + int top = st->mtop[st->mmode]; + memcpy(st->mat[st->mmode][top], m, 16 * sizeof(float)); +} + +#define M(i,j) (((i) << 2) + (j)) +void g3d_mult_matrix(const float *m2) +{ + int i, j, top = st->mtop[st->mmode]; + float m1[16]; + float *dest = st->mat[st->mmode][top]; + + memcpy(m1, dest, sizeof m1); + + for(i=0; i<4; i++) { + for(j=0; j<4; j++) { + *dest++ = m1[M(0,j)] * m2[M(i,0)] + + m1[M(1,j)] * m2[M(i,1)] + + m1[M(2,j)] * m2[M(i,2)] + + m1[M(3,j)] * m2[M(i,3)]; + } + } +} + +void g3d_push_matrix(void) +{ + int top = st->mtop[st->mmode]; + if(top >= STACK_SIZE) { + fprintf(stderr, "g3d_push_matrix overflow\n"); + return; + } + memcpy(st->mat[st->mmode][top + 1], st->mat[st->mmode][top], 16 * sizeof(float)); + st->mtop[st->mmode] = top + 1; +} + +void g3d_pop_matrix(void) +{ + if(st->mtop[st->mmode] <= 0) { + fprintf(stderr, "g3d_pop_matrix underflow\n"); + return; + } + --st->mtop[st->mmode]; +} + +void g3d_translate(float x, float y, float z) +{ + float m[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + m[12] = x; + m[13] = y; + m[14] = z; + g3d_mult_matrix(m); +} + +void g3d_rotate(float deg, float x, float y, float z) +{ + float m[16] = {0}; + + float angle = M_PI * deg / 180.0f; + float sina = sin(angle); + float cosa = cos(angle); + float one_minus_cosa = 1.0f - cosa; + float nxsq = x * x; + float nysq = y * y; + float nzsq = z * z; + + m[0] = nxsq + (1.0f - nxsq) * cosa; + m[4] = x * y * one_minus_cosa - z * sina; + m[8] = x * z * one_minus_cosa + y * sina; + m[1] = x * y * one_minus_cosa + z * sina; + m[5] = nysq + (1.0 - nysq) * cosa; + m[9] = y * z * one_minus_cosa - x * sina; + m[2] = x * z * one_minus_cosa - y * sina; + m[6] = y * z * one_minus_cosa + x * sina; + m[10] = nzsq + (1.0 - nzsq) * cosa; + m[15] = 1.0f; + + g3d_mult_matrix(m); +} + +void g3d_scale(float x, float y, float z) +{ + float m[16] = {0}; + m[0] = x; + m[5] = y; + m[10] = z; + m[15] = 1.0f; + g3d_mult_matrix(m); +} + +void g3d_ortho(float left, float right, float bottom, float top, float znear, float zfar) +{ + float m[16] = {0}; + + float dx = right - left; + float dy = top - bottom; + float dz = zfar - znear; + + m[0] = 2.0 / dx; + m[5] = 2.0 / dy; + m[10] = -2.0 / dz; + m[12] = -(right + left) / dx; + m[13] = -(top + bottom) / dy; + m[14] = -(zfar + znear) / dz; + m[15] = 1.0f; + + g3d_mult_matrix(m); +} + +void g3d_frustum(float left, float right, float bottom, float top, float nr, float fr) +{ + float m[16] = {0}; + + float dx = right - left; + float dy = top - bottom; + float dz = fr - nr; + + float a = (right + left) / dx; + float b = (top + bottom) / dy; + float c = -(fr + nr) / dz; + float d = -2.0 * fr * nr / dz; + + m[0] = 2.0 * nr / dx; + m[5] = 2.0 * nr / dy; + m[8] = a; + m[9] = b; + m[10] = c; + m[11] = -1.0f; + m[14] = d; + + g3d_mult_matrix(m); +} + +void g3d_perspective(float vfov_deg, float aspect, float znear, float zfar) +{ + float m[16] = {0}; + + float vfov = M_PI * vfov_deg / 180.0f; + float s = 1.0f / tan(vfov * 0.5f); + float range = znear - zfar; + + m[0] = s / aspect; + m[5] = s; + m[10] = (znear + zfar) / range; + m[11] = -1.0f; + m[14] = 2.0f * znear * zfar / range; + + g3d_mult_matrix(m); +} + +const float *g3d_get_matrix(int which, float *m) +{ + int top = st->mtop[which]; + + if(m) { + memcpy(m, st->mat[which][top], 16 * sizeof(float)); + } + return st->mat[which][top]; +} + +void g3d_light_pos(int idx, float x, float y, float z) +{ + int mvtop = st->mtop[G3D_MODELVIEW]; + + st->lt[idx].type = LT_POS; + st->lt[idx].x = x; + st->lt[idx].y = y; + st->lt[idx].z = z; + + xform4_vec3(st->mat[G3D_MODELVIEW][mvtop], &st->lt[idx].x); +} + +void g3d_light_dir(int idx, float x, float y, float z) +{ + int mvtop = st->mtop[G3D_MODELVIEW]; + + st->lt[idx].type = LT_DIR; + st->lt[idx].x = x; + st->lt[idx].y = y; + st->lt[idx].z = z; + + /* calc the normal matrix */ +#ifdef CORRECT_NORMAL_MATRIX + memcpy(st->norm_mat, st->mat[G3D_MODELVIEW][mvtop], 16 * sizeof(float)); + cgm_minverse(st->norm_mat); + cgm_mtranspose(st->norm_mat); +#else + memcpy(st->norm_mat, st->mat[G3D_MODELVIEW][mvtop], 16 * sizeof(float)); + st->norm_mat[12] = st->norm_mat[13] = st->norm_mat[14] = 0.0f; +#endif + + xform4_vec3(st->norm_mat, &st->lt[idx].x); + + NORMALIZE(&st->lt[idx].x); +} + +void g3d_light_color(int idx, float r, float g, float b) +{ + st->lt[idx].r = r; + st->lt[idx].g = g; + st->lt[idx].b = b; +} + +void g3d_light_ambient(float r, float g, float b) +{ + st->ambient[0] = r; + st->ambient[1] = g; + st->ambient[2] = b; +} + +void g3d_mtl_diffuse(float r, float g, float b) +{ + st->mtl.kd[0] = r; + st->mtl.kd[1] = g; + st->mtl.kd[2] = b; +} + +void g3d_mtl_specular(float r, float g, float b) +{ + st->mtl.ks[0] = r; + st->mtl.ks[1] = g; + st->mtl.ks[2] = b; +} + +void g3d_mtl_shininess(float shin) +{ + st->mtl.shin = shin; +} + +static INLINE int calc_mask(unsigned int x) +{ + return x - 1; +} + +void g3d_set_texture(int xsz, int ysz, void *pixels) +{ + pfill_tex.pixels = pixels; + pfill_tex.width = xsz; + pfill_tex.height = ysz; + + pfill_tex.xshift = calc_shift(xsz); + pfill_tex.yshift = calc_shift(ysz); + pfill_tex.xmask = calc_mask(xsz); + pfill_tex.ymask = calc_mask(ysz); +} + +void g3d_draw(int prim, const struct g3d_vertex *varr, int varr_size) +{ + g3d_draw_indexed(prim, varr, varr_size, 0, 0); +} + +#define NEED_NORMALS (st->opt & (G3D_LIGHTING | G3D_TEXTURE_GEN)) + +void g3d_draw_indexed(int prim, const struct g3d_vertex *varr, int varr_size, + const uint16_t *iarr, int iarr_size) +{ + int i, j, vnum, nfaces, fill_mode, num_tri; + struct pvertex pv[16], *pvtri; + struct g3d_vertex v[16], *vtri; + int mvtop = st->mtop[G3D_MODELVIEW]; + int ptop = st->mtop[G3D_PROJECTION]; + struct g3d_vertex *tmpv; + + tmpv = alloca(prim * 6 * sizeof *tmpv); + + /* calc the normal matrix */ + if(NEED_NORMALS) { +#ifdef CORRECT_NORMAL_MATRIX + memcpy(st->norm_mat, st->mat[G3D_MODELVIEW][mvtop], 16 * sizeof(float)); + cgm_minverse(st->norm_mat); + cgm_mtranspose(st->norm_mat); +#else + memcpy(st->norm_mat, st->mat[G3D_MODELVIEW][mvtop], 16 * sizeof(float)); + st->norm_mat[12] = st->norm_mat[13] = st->norm_mat[14] = 0.0f; +#endif + } + + nfaces = (iarr ? iarr_size : varr_size) / prim; + + for(j=0; jmat[G3D_MODELVIEW][mvtop], &v[i].x); + + if(NEED_NORMALS) { + xform3_vec3(st->norm_mat, &v[i].nx); + if(st->opt & G3D_LIGHTING) { + shade(v + i); + } + if(st->opt & G3D_TEXTURE_GEN) { + v[i].u = v[i].nx * 0.5 + 0.5; + v[i].v = 0.5 - v[i].ny * 0.5; + } + } + if(st->opt & G3D_TEXTURE_MAT) { + float *mat = st->mat[G3D_TEXTURE][st->mtop[G3D_TEXTURE]]; + float x = mat[0] * v[i].u + mat[4] * v[i].v + mat[12]; + float y = mat[1] * v[i].u + mat[5] * v[i].v + mat[13]; + float w = mat[3] * v[i].u + mat[7] * v[i].v + mat[15]; + v[i].u = x / w; + v[i].v = y / w; + } + xform4_vec3(st->mat[G3D_PROJECTION][ptop], &v[i].x); + } + + /* clipping */ + if(st->opt & G3D_CLIP_FRUSTUM) { + for(i=0; i<6; i++) { + memcpy(tmpv, v, vnum * sizeof *v); + + if(clip_frustum(v, &vnum, tmpv, vnum, i) < 0) { + /* polygon completely outside of view volume. discard */ + vnum = 0; + break; + } + } + + if(!vnum) continue; + } + + for(i=0; iopt & G3D_DEPTH_TEST) { + v[i].z /= v[i].w; + } +#endif + } + + /* viewport transformation */ + v[i].x = (v[i].x * 0.5f + 0.5f) * (float)st->vport[2] + st->vport[0]; + v[i].y = (0.5f - v[i].y * 0.5f) * (float)st->vport[3] + st->vport[1]; + + /* convert pos to 24.8 fixed point */ + pv[i].x = cround64(v[i].x * 256.0f); + pv[i].y = cround64(v[i].y * 256.0f); +#ifdef ENABLE_ZBUFFER + if(st->opt & G3D_DEPTH_TEST) { + /* after div/w z is in [-1, 1], remap it to [0, 0xffffff] */ + pv[i].z = cround64(v[i].z * 8388607.5f + 8388607.5f); + } +#endif + /* convert tex coords to 16.16 fixed point */ + pv[i].u = cround64(v[i].u * 65536.0f); + pv[i].v = cround64(v[i].v * 65536.0f); + /* pass the color through as is */ + pv[i].r = v[i].r; + pv[i].g = v[i].g; + pv[i].b = v[i].b; + pv[i].a = v[i].a; + } + + switch(vnum) { + case 1: + if(st->opt & (G3D_ALPHA_BLEND | G3D_ADD_BLEND)) { + int r, g, b, inv_alpha; + g3d_pixel *dest = pfill_fb.pixels + (pv[0].y >> 8) * st->width + (pv[0].x >> 8); + if(st->opt & G3D_ALPHA_BLEND) { + inv_alpha = 255 - pv[0].a; + r = ((int)pv[0].r * pv[0].a + G3D_UNPACK_R(*dest) * inv_alpha) >> 8; + g = ((int)pv[0].g * pv[0].a + G3D_UNPACK_G(*dest) * inv_alpha) >> 8; + b = ((int)pv[0].b * pv[0].a + G3D_UNPACK_B(*dest) * inv_alpha) >> 8; + } else { + r = (int)pv[0].r + G3D_UNPACK_R(*dest); + g = (int)pv[0].g + G3D_UNPACK_G(*dest); + b = (int)pv[0].b + G3D_UNPACK_B(*dest); + if(r > 255) r = 255; + if(g > 255) g = 255; + if(b > 255) b = 255; + } + *dest++ = G3D_PACK_RGB(r, g, b); + } else { + g3d_pixel *dest = pfill_fb.pixels + (pv[0].y >> 8) * st->width + (pv[0].x >> 8); + *dest = G3D_PACK_RGB(pv[0].r, pv[0].g, pv[0].b); + } + break; + + case 2: + { + g3d_pixel col = G3D_PACK_RGB(pv[0].r, pv[0].g, pv[0].b); + draw_line(pv[0].x >> 8, pv[0].y >> 8, pv[1].x >> 8, pv[1].y >> 8, col); + } + break; + + default: + fill_mode = st->polymode; + if(st->opt & G3D_TEXTURE_2D) { + fill_mode |= st->texmode << POLYFILL_TEXMODE_SHIFT; + } + if(st->opt & G3D_ALPHA_BLEND) { + fill_mode |= POLYFILL_ALPHA_BIT; + } else if(st->opt & G3D_ADD_BLEND) { + fill_mode |= POLYFILL_ADD_BIT; + } +#ifdef ENABLE_ZBUFFER + if(st->opt & G3D_DEPTH_TEST) { + fill_mode |= POLYFILL_ZBUF_BIT; + } +#endif + num_tri = vnum - 2; + vtri = v; + pvtri = pv; + for(;;) { + /* backface culling */ + if(st->opt & G3D_CULL_FACE) { + int32_t ax = pvtri[1].x - pvtri[0].x; + int32_t ay = pvtri[1].y - pvtri[0].y; + int32_t bx = pvtri[2].x - pvtri[0].x; + int32_t by = pvtri[2].y - pvtri[0].y; + int32_t cross_z = (ax >> 4) * (by >> 4) - (ay >> 4) * (bx >> 4); + int sign = (cross_z >> 31) & 1; + + if(!(sign ^ st->frontface)) { + goto skip_triangle; /* back-facing */ + } + + if(st->frontface != G3D_CCW) { + struct pvertex revtri[3]; + revtri[0] = pvtri[0]; + revtri[1] = pvtri[2]; + revtri[2] = pvtri[1]; + + calc_grad(vtri); + polyfill(fill_mode, revtri); + goto skip_triangle; + } + } + + calc_grad(vtri); + polyfill(fill_mode, pvtri); +skip_triangle: + if(--num_tri == 0) break; + vtri[1] = vtri[0]; + pvtri[1] = pvtri[0]; + vtri++; + pvtri++; + } + } + } +} + +#define ATTR_DELTAS(attr) \ + float d##attr##02 = v[0].attr - v[2].attr; \ + float d##attr##12 = v[1].attr - v[2].attr + +#define DFDX(attr) \ + (dx ? (d##attr##12 * dy02 - d##attr##02 * dy12) / dx : 0) +#define DFDY(attr) \ + (dy ? (d##attr##12 * dx02 - d##attr##02 * dx12) / dy : 0) + +static void calc_grad(struct g3d_vertex *v) +{ + float dx02 = v[0].x - v[2].x; + float dx12 = v[1].x - v[2].x; + float dy02 = v[0].y - v[2].y; + float dy12 = v[1].y - v[2].y; + + float dx = dx12 * dy02 - dx02 * dy12; + float dy = dx02 * dy12 - dx12 * dy02; + + if(st->polymode == POLYFILL_GOURAUD) { + ATTR_DELTAS(r); + ATTR_DELTAS(g); + ATTR_DELTAS(b); + pgrad.drdx = cround64(DFDX(r) * 4096.0f); + pgrad.drdy = cround64(DFDY(r) * 4096.0f); + pgrad.dgdx = cround64(DFDX(g) * 4096.0f); + pgrad.dgdy = cround64(DFDY(g) * 4096.0f); + pgrad.dbdx = cround64(DFDX(b) * 4096.0f); + pgrad.dbdy = cround64(DFDY(b) * 4096.0f); + if(st->opt & G3D_ALPHA_BLEND) { + ATTR_DELTAS(a); + pgrad.dadx = cround64(DFDX(a) * 4096.0f); + pgrad.dady = cround64(DFDY(a) * 4096.0f); + } + } + + if(st->opt & G3D_DEPTH_TEST) { + ATTR_DELTAS(z); + pgrad.dzdx = cround64(DFDX(z) * 8388607.5f); + pgrad.dzdy = cround64(DFDY(z) * 8388607.5f); + } + + if(st->opt & G3D_TEXTURE_2D) { + ATTR_DELTAS(u); + ATTR_DELTAS(v); + pgrad.dudx = cround64(DFDX(u) * 65536.0f); + pgrad.dudy = cround64(DFDY(u) * 65536.0f); + pgrad.dvdx = cround64(DFDX(v) * 65536.0f); + pgrad.dvdy = cround64(DFDY(v) * 65536.0f); + } +} + +void g3d_begin(int prim) +{ + st->imm_prim = prim; + st->imm_pcount = prim; + st->imm_numv = 0; +} + +void g3d_end(void) +{ + imm_flush(); +} + +static void imm_flush(void) +{ + int numv = st->imm_numv; + st->imm_numv = 0; + g3d_draw_indexed(st->imm_prim, st->imm_vbuf, numv, 0, 0); +} + +void g3d_vertex(float x, float y, float z) +{ + struct g3d_vertex *vptr = st->imm_vbuf + st->imm_numv++; + *vptr = st->imm_curv; + vptr->x = x; + vptr->y = y; + vptr->z = z; + vptr->w = 1.0f; + + if(!--st->imm_pcount) { + if(st->imm_numv >= IMM_VBUF_SIZE - st->imm_prim) { + imm_flush(); + } + st->imm_pcount = st->imm_prim; + } +} + +void g3d_normal(float x, float y, float z) +{ + st->imm_curv.nx = x; + st->imm_curv.ny = y; + st->imm_curv.nz = z; +} + +#define CLAMP(x, a, b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +void g3d_color3b(unsigned char r, unsigned char g, unsigned char b) +{ + st->imm_curv.r = MIN(r, 255); + st->imm_curv.g = MIN(g, 255); + st->imm_curv.b = MIN(b, 255); + st->imm_curv.a = 255; +} + +void g3d_color4b(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + st->imm_curv.r = MIN(r, 255); + st->imm_curv.g = MIN(g, 255); + st->imm_curv.b = MIN(b, 255); + st->imm_curv.a = MIN(a, 255); +} + +void g3d_color3f(float r, float g, float b) +{ + int ir = r * 255.0f; + int ig = g * 255.0f; + int ib = b * 255.0f; + st->imm_curv.r = CLAMP(ir, 0, 255); + st->imm_curv.g = CLAMP(ig, 0, 255); + st->imm_curv.b = CLAMP(ib, 0, 255); + st->imm_curv.a = 255; +} + +void g3d_color4f(float r, float g, float b, float a) +{ + int ir = r * 255.0f; + int ig = g * 255.0f; + int ib = b * 255.0f; + int ia = a * 255.0f; + st->imm_curv.r = CLAMP(ir, 0, 255); + st->imm_curv.g = CLAMP(ig, 0, 255); + st->imm_curv.b = CLAMP(ib, 0, 255); + st->imm_curv.a = CLAMP(ia, 0, 255); +} + +void g3d_texcoord(float u, float v) +{ + st->imm_curv.u = u; + st->imm_curv.v = v; +} + +static __inline void xform4_vec3(const float *mat, float *vec) +{ + float x = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12]; + float y = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13]; + float z = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14]; + vec[3] = mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15]; + vec[2] = z; + vec[1] = y; + vec[0] = x; +} + +static __inline void xform3_vec3(const float *mat, float *vec) +{ + float x = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2]; + float y = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2]; + vec[2] = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2]; + vec[1] = y; + vec[0] = x; +} + +static void shade(struct g3d_vertex *v) +{ + int i, r, g, b; + float color[3]; + + color[0] = st->ambient[0] * st->mtl.kd[0]; + color[1] = st->ambient[1] * st->mtl.kd[1]; + color[2] = st->ambient[2] * st->mtl.kd[2]; + + for(i=0; iopt & (G3D_LIGHT0 << i))) { + continue; + } + + ldir[0] = st->lt[i].x; + ldir[1] = st->lt[i].y; + ldir[2] = st->lt[i].z; + + if(st->lt[i].type != LT_DIR) { + ldir[0] -= v->x; + ldir[1] -= v->y; + ldir[2] -= v->z; + NORMALIZE(ldir); + } + + if((ndotl = v->nx * ldir[0] + v->ny * ldir[1] + v->nz * ldir[2]) < 0.0f) { + ndotl = 0.0f; + } + + color[0] += st->mtl.kd[0] * st->lt[i].r * ndotl; + color[1] += st->mtl.kd[1] * st->lt[i].g * ndotl; + color[2] += st->mtl.kd[2] * st->lt[i].b * ndotl; + + if(st->opt & G3D_SPECULAR) { + float ndoth; + ldir[2] += 1.0f; + NORMALIZE(ldir); + if((ndoth = v->nx * ldir[0] + v->ny * ldir[1] + v->nz * ldir[2]) < 0.0f) { + ndoth = 0.0f; + } + ndoth = pow(ndoth, st->mtl.shin); + + color[0] += st->mtl.ks[0] * st->lt[i].r * ndoth; + color[1] += st->mtl.ks[1] * st->lt[i].g * ndoth; + color[2] += st->mtl.ks[2] * st->lt[i].b * ndoth; + } + } + + r = cround64(color[0] * 255.0); + g = cround64(color[1] * 255.0); + b = cround64(color[2] * 255.0); + + v->r = r > 255 ? 255 : r; + v->g = g > 255 ? 255 : g; + v->b = b > 255 ? 255 : b; +} + +#endif /* !def BUILD_OPENGL */ diff --git a/src/mesh.c b/src/mesh.c new file mode 100644 index 0000000..3dbfbc1 --- /dev/null +++ b/src/mesh.c @@ -0,0 +1,608 @@ +#include +#include +#include +#include +#include "mesh.h" +#include "3dgfx.h" +#include "util.h" + +void init_g3dmtl(struct g3d_material *mtl) +{ + mtl->name = 0; + mtl->r = mtl->g = mtl->b = mtl->a = 1.0f; + mtl->sr = mtl->sg = mtl->sb = 0.0f; + mtl->shin = 60.0f; + mtl->texmap = mtl->envmap = 0; +} + +int init_mesh(struct g3d_mesh *mesh, int prim, int num_verts, int num_idx) +{ + mesh->name = 0; + mesh->prim = prim; + if(num_verts > 0) { + mesh->varr = malloc_nf(num_verts * sizeof *mesh->varr); + } else { + mesh->varr = 0; + } + if(num_idx > 0) { + mesh->iarr = malloc_nf(num_idx * prim * sizeof *mesh->iarr); + } else { + mesh->iarr = 0; + } + mesh->vcount = num_verts; + mesh->icount = num_idx; + mesh->mtl = 0; + return 0; +} + +void free_mesh(struct g3d_mesh *mesh) +{ + destroy_mesh(mesh); + free(mesh); +} + +void destroy_mesh(struct g3d_mesh *mesh) +{ + if(mesh) { + free(mesh->name); + free(mesh->varr); + free(mesh->iarr); + } +} + +int copy_mesh(struct g3d_mesh *dest, struct g3d_mesh *src) +{ + dest->prim = src->prim; + if(src->varr) { + if(!(dest->varr = malloc(src->vcount * sizeof *src->varr))) { + return -1; + } + memcpy(dest->varr, src->varr, src->vcount * sizeof *src->varr); + } + dest->vcount = src->vcount; + if(src->iarr) { + if(!(dest->iarr = malloc(src->icount * sizeof *src->iarr))) { + free(dest->varr); + dest->varr = 0; + return -1; + } + memcpy(dest->iarr, src->iarr, src->icount * sizeof *src->iarr); + } + dest->icount = src->icount; + return 0; +} + +static struct { + int prim; + struct g3d_vertex *varr; + const float *xform; +} zsort_cls; + +static int zsort_cmp(const void *aptr, const void *bptr) +{ + int i; + float za = 0.0f; + float zb = 0.0f; + const float *m = zsort_cls.xform; + const struct g3d_vertex *va = (const struct g3d_vertex*)aptr; + const struct g3d_vertex *vb = (const struct g3d_vertex*)bptr; + + for(i=0; ix + m[6] * va->y + m[10] * va->z + m[14]; + zb += m[2] * vb->x + m[6] * vb->y + m[10] * vb->z + m[14]; + ++va; + ++vb; + } + + za -= zb; + return *(int*)&za; +} + +static int zsort_indexed_cmp(const void *aptr, const void *bptr) +{ + int i; + float za = 0.0f; + float zb = 0.0f; + const uint16_t *a = (const uint16_t*)aptr; + const uint16_t *b = (const uint16_t*)bptr; + + const float *m = zsort_cls.xform; + + for(i=0; ix + m[6] * va->y + m[10] * va->z + m[14]; + zb += m[2] * vb->x + m[6] * vb->y + m[10] * vb->z + m[14]; + } + + za -= zb; + return *(int*)&za; +} + + +void zsort_mesh(struct g3d_mesh *m) +{ + zsort_cls.varr = m->varr; + zsort_cls.xform = g3d_get_matrix(G3D_MODELVIEW, 0); + zsort_cls.prim = m->prim; + + if(m->iarr) { + int nfaces = m->icount / m->prim; + qsort(m->iarr, nfaces, m->prim * sizeof *m->iarr, zsort_indexed_cmp); + } else { + int nfaces = m->vcount / m->prim; + qsort(m->varr, nfaces, m->prim * sizeof *m->varr, zsort_cmp); + } +} + + +void draw_mesh(struct g3d_mesh *mesh) +{ + struct g3d_material *mtl; + + if((mtl = mesh->mtl)) { + g3d_mtl_diffuse(mtl->r, mtl->g, mtl->b); + g3d_mtl_specular(mtl->sr, mtl->sg, mtl->sb); + g3d_mtl_shininess(mtl->shin); + + if(mtl->texmap) { + g3d_enable(G3D_TEXTURE_2D); + g3d_set_texture(mtl->texmap->width, mtl->texmap->height, mtl->texmap->pixels); + } else if(mtl->envmap) { + g3d_enable(G3D_TEXTURE_2D); + g3d_set_texture(mtl->envmap->width, mtl->envmap->height, mtl->envmap->pixels); + g3d_enable(G3D_TEXTURE_GEN); + } + } + + if(mesh->iarr) { + g3d_draw_indexed(mesh->prim, mesh->varr, mesh->vcount, mesh->iarr, mesh->icount); + } else { + g3d_draw(mesh->prim, mesh->varr, mesh->vcount); + } + + if(mtl) { + if(mtl->texmap) { + g3d_disable(G3D_TEXTURE_2D); + } else if(mtl->envmap) { + g3d_disable(G3D_TEXTURE_2D); + g3d_disable(G3D_TEXTURE_GEN); + } + } +} + +void apply_mesh_xform(struct g3d_mesh *mesh, const float *xform) +{ + int i; + struct g3d_vertex *v = mesh->varr; + + for(i=0; ivcount; i++) { + float x = xform[0] * v->x + xform[4] * v->y + xform[8] * v->z + xform[12]; + float y = xform[1] * v->x + xform[5] * v->y + xform[9] * v->z + xform[13]; + v->z = xform[2] * v->x + xform[6] * v->y + xform[10] * v->z + xform[14]; + v->x = x; + v->y = y; + x = xform[0] * v->nx + xform[4] * v->ny + xform[8] * v->nz; + y = xform[1] * v->nx + xform[5] * v->ny + xform[9] * v->nz; + v->nz = xform[2] * v->nx + xform[6] * v->ny + xform[10] * v->nz; + v->nx = x; + v->ny = y; + ++v; + } +} + +int append_mesh(struct g3d_mesh *ma, struct g3d_mesh *mb) +{ + int i, new_vcount, new_icount; + void *tmp; + uint16_t *iptr; + + if(ma->prim != mb->prim) { + fprintf(stderr, "append_mesh failed, primitive mismatch\n"); + return -1; + } + + if(ma->iarr || mb->iarr) { + if(!ma->iarr) { + if(indexify_mesh(ma) == -1) { + return -1; + } + } else if(!mb->iarr) { + if(indexify_mesh(mb) == -1) { + return -1; + } + } + + new_icount = ma->icount + mb->icount; + if(!(iptr = realloc(ma->iarr, new_icount * sizeof *iptr))) { + fprintf(stderr, "append_mesh: failed to allocate combined index buffer (%d indices)\n", new_icount); + return -1; + } + ma->iarr = iptr; + + iptr += ma->icount; + for(i=0; iicount; i++) { + *iptr++ = mb->iarr[i] + ma->vcount; + } + ma->icount = new_icount; + } + + new_vcount = ma->vcount + mb->vcount; + if(!(tmp = realloc(ma->varr, new_vcount * sizeof *ma->varr))) { + fprintf(stderr, "append_mesh: failed to allocate combined vertex buffer (%d verts)\n", new_vcount); + return -1; + } + ma->varr = tmp; + memcpy(ma->varr + ma->vcount, mb->varr, mb->vcount * sizeof *ma->varr); + ma->vcount = new_vcount; + return 0; +} + +#define FEQ(a, b) ((a) - (b) < 1e-5 && (b) - (a) < 1e-5) +static int cmp_vertex(struct g3d_vertex *a, struct g3d_vertex *b) +{ + if(!FEQ(a->x, b->x) || !FEQ(a->y, b->y) || !FEQ(a->z, b->z) || !FEQ(a->w, b->w)) + return -1; + if(!FEQ(a->nx, b->nx) || !FEQ(a->ny, b->ny) || !FEQ(a->nz, b->nz)) + return -1; + if(!FEQ(a->u, b->u) || !FEQ(a->v, b->v)) + return -1; + if(a->r != b->r || a->g != b->g || a->b != b->b || a->a != b->a) + return -1; + return 0; +} + +static int find_existing(struct g3d_vertex *v, struct g3d_vertex *varr, int vcount) +{ + int i; + for(i=0; iiarr) { + fprintf(stderr, "indexify_mesh failed: already indexed\n"); + return -1; + } + + nfaces = mesh->vcount / mesh->prim; + max_icount = mesh->vcount; + + if(!(mesh->iarr = malloc(max_icount * sizeof *mesh->iarr))) { + fprintf(stderr, "indexify_mesh failed to allocate index buffer of %d indices\n", max_icount); + return -1; + } + + vin = vout = mesh->varr; + iout = mesh->iarr; + + for(i=0; iprim; j++) { + if((idx = find_existing(vin, mesh->varr, out_vcount)) >= 0) { + *iout++ = idx; + } else { + *iout++ = out_vcount++; + if(vin != vout) { + *vout++ = *vin; + } + } + ++vin; + } + } + + /* XXX also shrink buffers? I'll just leave them to max size for now */ + return 0; +} + +void normalize_mesh_normals(struct g3d_mesh *mesh) +{ + int i; + struct g3d_vertex *v = mesh->varr; + + for(i=0; ivcount; i++) { + float mag = sqrt(v->nx * v->nx + v->ny * v->ny + v->nz * v->nz); + float s = (mag == 0.0f) ? 1.0f : 1.0f / mag; + v->nx *= s; + v->ny *= s; + v->nz *= s; + ++v; + } +} + + +void calc_mesh_centroid(struct g3d_mesh *mesh, float *cent) +{ + int i; + float s = 1.0f / (float)mesh->vcount; + cent[0] = cent[1] = cent[2] = 0.0f; + + for(i=0; ivcount; i++) { + cent[0] += mesh->varr[i].x; + cent[1] += mesh->varr[i].y; + cent[2] += mesh->varr[i].z; + } + cent[0] *= s; + cent[1] *= s; + cent[2] *= s; +} + +static void sphvec(float *res, float theta, float phi, float rad) +{ + theta = -theta; + res[0] = sin(theta) * sin(phi) * rad; + res[1] = cos(phi) * rad; + res[2] = cos(theta) * sin(phi) * rad; +} + +int gen_sphere_mesh(struct g3d_mesh *mesh, float rad, int usub, int vsub) +{ + int i, j; + int nfaces, uverts, vverts; + struct g3d_vertex *vptr; + uint16_t *iptr; + + init_mesh(mesh, G3D_QUADS, 0, 0); + + if(usub < 4) usub = 4; + if(vsub < 2) vsub = 2; + + uverts = usub + 1; + vverts = vsub + 1; + + mesh->vcount = uverts * vverts; + nfaces = usub * vsub; + mesh->icount = nfaces * 4; + + if(!(mesh->varr = malloc(mesh->vcount * sizeof *mesh->varr))) { + fprintf(stderr, "gen_sphere_mesh: failed to allocate vertex buffer (%d vertices)\n", mesh->vcount); + return -1; + } + if(!(mesh->iarr = malloc(mesh->icount * sizeof *mesh->iarr))) { + fprintf(stderr, "gen_sphere_mesh: failed to allocate index buffer (%d indices)\n", mesh->icount); + return -1; + } + vptr = mesh->varr; + iptr = mesh->iarr; + + for(i=0; ix, theta, phi, rad); + vptr->w = 1.0f; + + vptr->nx = vptr->x / rad; + vptr->ny = vptr->y / rad; + vptr->nz = vptr->z / rad; + vptr->u = u; + vptr->v = v; + vptr->r = chess ? 255 : 64; + vptr->g = 128; + vptr->b = chess ? 64 : 255; + ++vptr; + + if(i < usub && j < vsub) { + int idx = i * vverts + j; + *iptr++ = idx; + *iptr++ = idx + vverts; + *iptr++ = idx + vverts + 1; + *iptr++ = idx + 1; + } + } + } + return 0; +} + +int gen_plane_mesh(struct g3d_mesh *m, float width, float height, int usub, int vsub) +{ + int i, j; + int nfaces, nverts, nidx, uverts, vverts; + float x, y, u, v, du, dv; + struct g3d_vertex *vptr; + uint16_t *iptr; + + init_mesh(m, G3D_QUADS, 0, 0); + + if(usub < 1) usub = 1; + if(vsub < 1) vsub = 1; + + nfaces = usub * vsub; + uverts = usub + 1; + vverts = vsub + 1; + du = 1.0f / (float)usub; + dv = 1.0f / (float)vsub; + + nverts = uverts * vverts; + nidx = nfaces * 4; + + if(!(m->varr = malloc(nverts * sizeof *m->varr))) { + fprintf(stderr, "gen_plane_mesh: failed to allocate vertex buffer (%d vertices)\n", nverts); + return -1; + } + if(!(m->iarr = malloc(nidx * sizeof *m->iarr))) { + fprintf(stderr, "gen_plane_mesh: failed to allocate index buffer (%d indices)\n", nidx); + free(m->varr); + m->varr = 0; + return -1; + } + + m->vcount = nverts; + m->icount = nidx; + + vptr = m->varr; + iptr = m->iarr; + + v = 0.0f; + for(i=0; ix = x; + vptr->y = y; + vptr->z = 0.0f; + vptr->w = 1.0f; + vptr->nx = 0.0f; + vptr->ny = 0.0f; + vptr->nz = 1.0f; + vptr->u = u; + vptr->v = v; + vptr->r = vptr->g = vptr->b = vptr->a = 255; + ++vptr; + + u += du; + } + v += dv; + } + + for(i=0; i 0 ? &tmpmesh : mesh; + if(gen_plane_mesh(m, sz, sz, sub, sub) == -1) + return -1; + g3d_load_identity(); + g3d_rotate(rotface[i][0], rotface[i][1], rotface[i][2], rotface[i][3]); + g3d_translate(0, 0, offs / 2.0f); + apply_mesh_xform(m, g3d_get_matrix(G3D_MODELVIEW, 0)); + if(i > 0) { + if(append_mesh(mesh, m) == -1) { + return -1; + } + } + } + + g3d_pop_matrix(); + return 0; +} + +static void torusvec(float *res, float theta, float phi, float mr, float rr) +{ + float rx, ry, rz; + theta = -theta; + + rx = -cos(phi) * rr + mr; + ry = sin(phi) * rr; + rz = 0.0f; + + res[0] = rx * sin(theta) + rz * cos(theta); + res[1] = ry; + res[2] = -rx * cos(theta) + rz * sin(theta); +} + +int gen_torus_mesh(struct g3d_mesh *mesh, float rad, float ringrad, int usub, int vsub) +{ + int i, j; + int nfaces, uverts, vverts; + struct g3d_vertex *vptr; + uint16_t *iptr; + + init_mesh(mesh, G3D_QUADS, 0, 0); + + if(usub < 4) usub = 4; + if(vsub < 2) vsub = 2; + + uverts = usub + 1; + vverts = vsub + 1; + + mesh->vcount = uverts * vverts; + nfaces = usub * vsub; + mesh->icount = nfaces * 4; + + printf("generating torus with %d faces (%d vertices)\n", nfaces, mesh->vcount); + + if(!(mesh->varr = malloc(mesh->vcount * sizeof *mesh->varr))) { + return -1; + } + if(!(mesh->iarr = malloc(mesh->icount * sizeof *mesh->iarr))) { + return -1; + } + vptr = mesh->varr; + iptr = mesh->iarr; + + for(i=0; ix, theta, phi, rad, ringrad); + vptr->w = 1.0f; + + vptr->nx = (vptr->x - rcent[0]) / ringrad; + vptr->ny = (vptr->y - rcent[1]) / ringrad; + vptr->nz = (vptr->z - rcent[2]) / ringrad; + vptr->u = u; + vptr->v = v; + vptr->r = chess ? 255 : 64; + vptr->g = 128; + vptr->b = chess ? 64 : 255; + ++vptr; + + if(i < usub && j < vsub) { + int idx = i * vverts + j; + *iptr++ = idx; + *iptr++ = idx + 1; + *iptr++ = idx + vverts + 1; + *iptr++ = idx + vverts; + } + } + } + return 0; +} + diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..fb1b921 --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,60 @@ +#ifndef MESH_H_ +#define MESH_H_ + +#include "3dgfx.h" +#include "image.h" +#include "inttypes.h" + +struct g3d_material { + float r, g, b, a; + float sr, sg, sb, shin; + + struct image *texmap, *envmap; + char *name; +}; + +struct g3d_mesh { + int prim; + struct g3d_vertex *varr; + uint16_t *iarr; + int vcount, icount; + char *name; + + struct g3d_material *mtl; +}; + +void init_g3dmtl(struct g3d_material *mtl); + +int init_mesh(struct g3d_mesh *mesh, int prim, int num_verts, int num_idx); + +void free_mesh(struct g3d_mesh *mesh); +void destroy_mesh(struct g3d_mesh *mesh); + +int copy_mesh(struct g3d_mesh *dest, struct g3d_mesh *src); + +/* takes pointer to a dynamic array (dynarr_*) and populates it */ +#define load_meshes(mesharr, fname) load_meshes_impl(&(mesharr), fname) +int load_meshes_impl(struct g3d_mesh **mesh, const char *fname); +/* TODO: idx -1 -> merge all meshes into one? */ +int load_mesh(struct g3d_mesh *mesh, const char *fname, int idx); +int load_named_mesh(struct g3d_mesh *mesh, const char *fname, const char *mname); +int save_mesh(struct g3d_mesh *mesh, const char *fname); +struct g3d_mesh *find_mesh(struct g3d_mesh *meshes, const char *mname); + +void zsort_mesh(struct g3d_mesh *mesh); +void draw_mesh(struct g3d_mesh *mesh); + +void apply_mesh_xform(struct g3d_mesh *mesh, const float *xform); +int append_mesh(struct g3d_mesh *ma, struct g3d_mesh *mb); +int indexify_mesh(struct g3d_mesh *mesh); + +void normalize_mesh_normals(struct g3d_mesh *mesh); + +void calc_mesh_centroid(struct g3d_mesh *mesh, float *cent); + +int gen_sphere_mesh(struct g3d_mesh *mesh, float rad, int usub, int vsub); +int gen_plane_mesh(struct g3d_mesh *mesh, float width, float height, int usub, int vsub); +int gen_cube_mesh(struct g3d_mesh *mesh, float sz, int sub); +int gen_torus_mesh(struct g3d_mesh *mesh, float rad, float ringrad, int usub, int vsub); + +#endif /* MESH_H_ */ diff --git a/src/meshload.c b/src/meshload.c new file mode 100644 index 0000000..cd7f94f --- /dev/null +++ b/src/meshload.c @@ -0,0 +1,464 @@ +#include +#include +#include +#include +#include +#include "mesh.h" +#include "dynarr.h" +#include "rbtree.h" +#include "3dgfx.h" +#include "util.h" + +typedef struct { float x, y; } vec2_t; +typedef struct { float x, y, z; } vec3_t; +typedef struct { float x, y, z, w; } vec4_t; + + +struct vertex_pos_color { + float x, y, z; + float r, g, b, a; +}; + +struct facevertex { + int vidx, tidx, nidx; +}; + +static char *clean_line(char *s); +static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn); +static int cmp_facevert(const void *ap, const void *bp); +static void free_rbnode_key(struct rbnode *n, void *cls); + +static int newmesh(struct g3d_mesh *m, const char *name) +{ + memset(m, 0, sizeof *m); + if(!(m->varr = dynarr_alloc(0, sizeof *m->varr)) || + !(m->iarr = dynarr_alloc(0, sizeof *m->iarr))) { + fprintf(stderr, "load_mesh: failed to allocate resizable mesh arrays\n"); + return -1; + } + if(name) { + m->name = strdup(name); + } + return 0; +} + +static int endmesh(struct g3d_mesh **marr, struct g3d_mesh *m, int prim) +{ + void *tmp; + + if(dynarr_size(m->varr) && dynarr_size(m->iarr)) { + m->vcount = dynarr_size(m->varr); + m->icount = dynarr_size(m->iarr); + m->varr = dynarr_finalize(m->varr); + m->iarr = dynarr_finalize(m->iarr); + m->prim = prim; + + if(!(tmp = dynarr_push(*marr, m))) { + fprintf(stderr, "load_meshes: failed to add new mesh (%d)\n", dynarr_size(*marr)); + return -1; + } + *marr = tmp; + + printf(" - %s mesh: %s: %d vertices, %d faces\n", prim == 4 ? "quad" : "triangle", + m->name ? m->name : "?", m->vcount, m->icount / m->prim); + } else { + dynarr_free(m->varr); + m->varr = 0; + dynarr_free(m->iarr); + m->iarr = 0; + free(m->name); + } + return 0; +} + +/* merge of different indices per attribute happens during face processing. + * + * A triplet of (vertex index/texcoord index/normal index) is used as the key + * to search in a balanced binary search tree for vertex buffer index assigned + * to the same triplet if it has been encountered before. That index is + * appended to the index buffer. + * + * If a particular triplet has not been encountered before, a new g3d_vertex is + * appended to the vertex buffer. The index of this new vertex is appended to + * the index buffer, and also inserted into the tree for future searches. + */ +int load_meshes_impl(struct g3d_mesh **meshes, const char *fname) +{ + int i, line_num = 0, result = -1; + int found_quad = 0; + FILE *fp = 0; + char buf[256]; + struct vertex_pos_color *varr = 0; + vec3_t *narr = 0; + vec2_t *tarr = 0; + struct rbtree *rbtree = 0; + struct g3d_mesh mesh; + + if(!(fp = fopen(fname, "rb"))) { + fprintf(stderr, "load_mesh: failed to open file: %s\n", fname); + goto err; + } + + if(!(rbtree = rb_create(cmp_facevert))) { + fprintf(stderr, "load_mesh: failed to create facevertex binary search tree\n"); + goto err; + } + rb_set_delete_func(rbtree, free_rbnode_key, 0); + + if(newmesh(&mesh, 0) == -1) { + goto err; + } + if(!(varr = dynarr_alloc(0, sizeof *varr)) || + !(narr = dynarr_alloc(0, sizeof *narr)) || + !(tarr = dynarr_alloc(0, sizeof *tarr))) { + fprintf(stderr, "load_mesh: failed to allocate resizable vertex array\n"); + goto err; + } + + while(fgets(buf, sizeof buf, fp)) { + char *line = clean_line(buf); + ++line_num; + + if(!*line) continue; + + switch(line[0]) { + case 'g': + endmesh(meshes, &mesh, found_quad ? 4 : 3); + found_quad = 0; + rb_clear(rbtree); + if(newmesh(&mesh, clean_line(line + 2)) == -1) { + goto err; + } + break; + + case 'v': + if(isspace(line[1])) { + /* vertex */ + struct vertex_pos_color v; + int num; + + num = sscanf(line + 2, "%f %f %f %f %f %f %f", &v.x, &v.y, &v.z, &v.r, &v.g, &v.b, &v.a); + if(num < 3) { + fprintf(stderr, "%s:%d: invalid vertex definition: \"%s\"\n", fname, line_num, line); + goto err; + } + switch(num) { + case 3: + v.r = 1.0f; + case 4: + v.g = 1.0f; + case 5: + v.b = 1.0f; + case 6: + v.a = 1.0f; + } + if(!(varr = dynarr_push(varr, &v))) { + fprintf(stderr, "load_mesh: failed to resize vertex buffer\n"); + goto err; + } + + } else if(line[1] == 't' && isspace(line[2])) { + /* texcoord */ + vec2_t tc; + if(sscanf(line + 3, "%f %f", &tc.x, &tc.y) != 2) { + fprintf(stderr, "%s:%d: invalid texcoord definition: \"%s\"\n", fname, line_num, line); + goto err; + } + tc.y = 1.0f - tc.y; + if(!(tarr = dynarr_push(tarr, &tc))) { + fprintf(stderr, "load_mesh: failed to resize texcoord buffer\n"); + goto err; + } + + } else if(line[1] == 'n' && isspace(line[2])) { + /* normal */ + vec3_t norm; + if(sscanf(line + 3, "%f %f %f", &norm.x, &norm.y, &norm.z) != 3) { + fprintf(stderr, "%s:%d: invalid normal definition: \"%s\"\n", fname, line_num, line); + goto err; + } + if(!(narr = dynarr_push(narr, &norm))) { + fprintf(stderr, "load_mesh: failed to resize normal buffer\n"); + goto err; + } + } + break; + + case 'f': + if(isspace(line[1])) { + /* face */ + char *ptr = line + 2; + struct facevertex fv; + struct rbnode *node; + int vsz = dynarr_size(varr); + int tsz = dynarr_size(tarr); + int nsz = dynarr_size(narr); + + for(i=0; i<4; i++) { + if(!(ptr = parse_face_vert(ptr, &fv, vsz, tsz, nsz))) { + if(i < 3 || found_quad) { + fprintf(stderr, "%s:%d: invalid face definition: \"%s\"\n", fname, line_num, line); + goto err; + } else { + break; + } + } + + if((node = rb_find(rbtree, &fv))) { + uint16_t idx = (int)(intptr_t)node->data; + if(!(mesh.iarr = dynarr_push(mesh.iarr, &idx))) { + fprintf(stderr, "load_mesh: failed to resize index array\n"); + goto err; + } + } else { + uint16_t newidx = dynarr_size(mesh.varr); + struct g3d_vertex v; + struct facevertex *newfv; + + v.x = varr[fv.vidx].x; + v.y = varr[fv.vidx].y; + v.z = varr[fv.vidx].z; + v.w = 1.0f; + v.r = cround64(varr[fv.vidx].r * 255.0); + v.g = cround64(varr[fv.vidx].g * 255.0); + v.b = cround64(varr[fv.vidx].b * 255.0); + v.a = cround64(varr[fv.vidx].a * 255.0); + if(fv.tidx >= 0) { + v.u = tarr[fv.tidx].x; + v.v = tarr[fv.tidx].y; + } else { + v.u = v.x; + v.v = v.y; + } + if(fv.nidx >= 0) { + v.nx = narr[fv.nidx].x; + v.ny = narr[fv.nidx].y; + v.nz = narr[fv.nidx].z; + } else { + v.nx = v.ny = 0.0f; + v.nz = 1.0f; + } + + if(!(mesh.varr = dynarr_push(mesh.varr, &v))) { + fprintf(stderr, "load_mesh: failed to resize combined vertex array\n"); + goto err; + } + if(!(mesh.iarr = dynarr_push(mesh.iarr, &newidx))) { + fprintf(stderr, "load_mesh: failed to resize index array\n"); + goto err; + } + + if((newfv = malloc(sizeof *newfv))) { + *newfv = fv; + } + if(!newfv || rb_insert(rbtree, newfv, (void*)(intptr_t)newidx) == -1) { + fprintf(stderr, "load_mesh: failed to insert facevertex to the binary search tree\n"); + goto err; + } + } + } + if(i > 3) found_quad = 1; + } + break; + + default: + break; + } + } + + endmesh(meshes, &mesh, found_quad ? 4 : 3); + + result = 0; /* success */ + + +err: + if(fp) fclose(fp); + dynarr_free(varr); + dynarr_free(narr); + dynarr_free(tarr); + if(result == -1) { + dynarr_free(mesh.varr); + dynarr_free(mesh.iarr); + free(mesh.name); + } + rb_free(rbtree); + return result; +} + +int load_mesh(struct g3d_mesh *mesh, const char *fname, int idx) +{ + int i, res = -1; + struct g3d_mesh *meshes = dynarr_alloc(0, sizeof *meshes); + + if(load_meshes(meshes, fname) == -1) { + dynarr_free(meshes); + return -1; + } + + if(idx >= 0 && idx < dynarr_size(meshes)) { + *mesh = meshes[idx]; + res = 0; + } + + for(i=0; iprim) { + printf("loaded mesh %s (%d) with %d faces\n", fname, idx, mesh->icount / mesh->prim); + } + return res; +} + +int load_named_mesh(struct g3d_mesh *mesh, const char *fname, const char *mname) +{ + int i, res = -1; + struct g3d_mesh *meshes = dynarr_alloc(0, sizeof *meshes); + + if(load_meshes(meshes, fname) == -1) { + dynarr_free(meshes); + return -1; + } + + for(i=0; iname && strcmp(mesh->name, mname) == 0) { + *mesh = meshes[i]; + res = 0; + continue; + } + free_mesh(meshes + i); + } + dynarr_free(meshes); + return res; + +} + +int save_mesh(struct g3d_mesh *mesh, const char *fname) +{ + int i, idx, fvcount, nverts; + FILE *fp; + + if(!(fp = fopen(fname, "wb"))) { + fprintf(stderr, "save_mesh: failed to open %s for writing\n", fname); + return -1; + } + fprintf(fp, "# Wavefront OBJ file shoved in your FACE by Mindlapse. Deal with it\n"); + + for(i=0; ivcount; i++) { + struct g3d_vertex *v = mesh->varr + i; + fprintf(fp, "v %f %f %f %f %f %f %f\n", v->x, v->y, v->z, v->r / 255.0f, v->g / 255.0f, + v->b / 255.0f, v->a / 255.0f); + } + for(i=0; ivcount; i++) { + fprintf(fp, "vn %f %f %f\n", mesh->varr[i].nx, mesh->varr[i].ny, mesh->varr[i].nz); + } + for(i=0; ivcount; i++) { + fprintf(fp, "vt %f %f\n", mesh->varr[i].u, mesh->varr[i].v); + } + + fvcount = mesh->prim; + nverts = mesh->iarr ? mesh->icount : mesh->vcount; + for(i=0; iiarr ? mesh->iarr[i] : i) + 1; + + if(fvcount == mesh->prim) { + fprintf(fp, "\nf"); + fvcount = 0; + } + fprintf(fp, " %d/%d/%d", idx, idx, idx); + ++fvcount; + } + fprintf(fp, "\n"); + + fclose(fp); + return 0; +} + +struct g3d_mesh *find_mesh(struct g3d_mesh *meshes, const char *mname) +{ + int i; + for(i=0; iname && strcmp(meshes->name, mname) == 0) { + return meshes + i; + } + } + return 0; +} + +static char *clean_line(char *s) +{ + char *end; + + while(*s && isspace(*s)) ++s; + if(!*s) return 0; + + end = s; + while(*end && *end != '#') ++end; + *end-- = 0; + + while(end > s && isspace(*end)) *end-- = 0; + + return s; +} + +static char *parse_idx(char *ptr, int *idx, int arrsz) +{ + char *endp; + int val = strtol(ptr, &endp, 10); + if(endp == ptr) return 0; + + if(val < 0) { /* convert negative indices */ + *idx = arrsz + val; + } else { + *idx = val - 1; /* indices in obj are 1-based */ + } + return endp; +} + +/* possible face-vertex definitions: + * 1. vertex + * 2. vertex/texcoord + * 3. vertex//normal + * 4. vertex/texcoord/normal + */ +static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn) +{ + if(!(ptr = parse_idx(ptr, &fv->vidx, numv))) + return 0; + if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0; + + if(*++ptr == '/') { /* no texcoord */ + fv->tidx = -1; + ++ptr; + } else { + if(!(ptr = parse_idx(ptr, &fv->tidx, numt))) + return 0; + if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0; + ++ptr; + } + + if(!(ptr = parse_idx(ptr, &fv->nidx, numn))) + return 0; + return (!*ptr || isspace(*ptr)) ? ptr : 0; +} + +static int cmp_facevert(const void *ap, const void *bp) +{ + const struct facevertex *a = ap; + const struct facevertex *b = bp; + + if(a->vidx == b->vidx) { + if(a->tidx == b->tidx) { + return a->nidx - b->nidx; + } + return a->tidx - b->tidx; + } + return a->vidx - b->vidx; +} + +static void free_rbnode_key(struct rbnode *n, void *cls) +{ + free(n->key); +} diff --git a/src/polyclip.c b/src/polyclip.c new file mode 100644 index 0000000..d4b1c52 --- /dev/null +++ b/src/polyclip.c @@ -0,0 +1,331 @@ +#include +#include +#include +#include "polyclip.h" + +struct ray { + float origin[3]; + float dir[3]; +}; + +static int clip_edge(struct g3d_vertex *poly, int *vnumptr, + const struct g3d_vertex *v0, const struct g3d_vertex *v1, + const struct cplane *plane); +static int check_clip_edge(const struct g3d_vertex *v0, + const struct g3d_vertex *v1, const struct cplane *plane); +static int clip_edge_frustum(struct g3d_vertex *poly, int *vnumptr, + const struct g3d_vertex *v0, const struct g3d_vertex *v1, int fplane); +static float distance_signed(float *pos, const struct cplane *plane); +static int intersect(const struct ray *ray, const struct cplane *plane, float *t); +static int inside_frustum_plane(const struct g3d_vertex *v, int fplane); + + +int clip_poly(struct g3d_vertex *vout, int *voutnum, + const struct g3d_vertex *vin, int vnum, struct cplane *plane) +{ + int i, nextidx, res; + int edges_clipped = 0; + + *voutnum = 0; + + for(i=0; i= vnum) nextidx = 0; + res = clip_edge(vout, voutnum, vin + i, vin + nextidx, plane); + if(res == 0) { + ++edges_clipped; + } + } + + if(*voutnum <= 0) { + assert(edges_clipped == 0); + return -1; + } + + return edges_clipped > 0 ? 0 : 1; +} + +int check_clip_poly(const struct g3d_vertex *v, int vnum, struct cplane *plane) +{ + int i, nextidx, res = 0; + int edges_clipped = 0; + + for(i=0; i= vnum) nextidx = 0; + res = check_clip_edge(v + i, v + nextidx, plane); + if(res == 0) { + ++edges_clipped; + } + } + return edges_clipped ? 0 : res; +} + +int clip_frustum(struct g3d_vertex *vout, int *voutnum, + const struct g3d_vertex *vin, int vnum, int fplane) +{ + int i, nextidx, res; + int edges_clipped = 0; + + if(vnum == 1) { + /* special case: point clipping */ + return inside_frustum_plane(vin, fplane) ? 1 : -1; + } + + *voutnum = 0; + + for(i=0; i= vnum) nextidx = 0; + res = clip_edge_frustum(vout, voutnum, vin + i, vin + nextidx, fplane); + if(res == 0) { + ++edges_clipped; + } + } + + if(*voutnum <= 0) { + assert(edges_clipped == 0); + return -1; + } + + return edges_clipped > 0 ? 0 : 1; +} + +#define LERP_VATTR(res, v0, v1, t) \ + do { \ + (res)->nx = (v0)->nx + ((v1)->nx - (v0)->nx) * (t); \ + (res)->ny = (v0)->ny + ((v1)->ny - (v0)->ny) * (t); \ + (res)->nz = (v0)->nz + ((v1)->nz - (v0)->nz) * (t); \ + (res)->u = (v0)->u + ((v1)->u - (v0)->u) * (t); \ + (res)->v = (v0)->v + ((v1)->v - (v0)->v) * (t); \ + (res)->r = (v0)->r + ((v1)->r - (v0)->r) * (t); \ + (res)->g = (v0)->g + ((v1)->g - (v0)->g) * (t); \ + (res)->b = (v0)->b + ((v1)->b - (v0)->b) * (t); \ + } while(0) + + +/* returns: + * 1 -> both inside + * 0 -> straddling and clipped + * -1 -> both outside + * + * also returns the size of the polygon through vnumptr + */ +static int clip_edge(struct g3d_vertex *poly, int *vnumptr, + const struct g3d_vertex *v0, const struct g3d_vertex *v1, + const struct cplane *plane) +{ + float pos0[3], pos1[3]; + float d0, d1, t; + struct ray ray; + int i, vnum = *vnumptr; + + pos0[0] = v0->x; pos0[1] = v0->y; pos0[2] = v0->z; + pos1[0] = v1->x; pos1[1] = v1->y; pos1[2] = v1->z; + + d0 = distance_signed(pos0, plane); + d1 = distance_signed(pos1, plane); + + for(i=0; i<3; i++) { + ray.origin[i] = pos0[i]; + ray.dir[i] = pos1[i] - pos0[i]; + } + + if(d0 >= 0.0) { + /* start inside */ + if(d1 >= 0.0) { + /* all inside */ + poly[vnum++] = *v1; /* append v1 */ + *vnumptr = vnum; + return 1; + } else { + /* going out */ + struct g3d_vertex *vptr = poly + vnum; + + intersect(&ray, plane, &t); + + vptr->x = ray.origin[0] + ray.dir[0] * t; + vptr->y = ray.origin[1] + ray.dir[1] * t; + vptr->z = ray.origin[2] + ray.dir[2] * t; + vptr->w = 1.0f; + + LERP_VATTR(vptr, v0, v1, t); + vnum++; /* append new vertex on the intersection point */ + } + } else { + /* start outside */ + if(d1 >= 0) { + /* going in */ + struct g3d_vertex *vptr = poly + vnum; + + intersect(&ray, plane, &t); + + vptr->x = ray.origin[0] + ray.dir[0] * t; + vptr->y = ray.origin[1] + ray.dir[1] * t; + vptr->z = ray.origin[2] + ray.dir[2] * t; + vptr->w = 1.0f; + + LERP_VATTR(vptr, v0, v1, t); + vnum++; /* append new vertex on the intersection point */ + + /* then append v1 ... */ + poly[vnum++] = *v1; + } else { + /* all outside */ + return -1; + } + } + + *vnumptr = vnum; + return 0; +} + +/* same as above, but only checks for clipping and classifies the edge */ +static int check_clip_edge(const struct g3d_vertex *v0, + const struct g3d_vertex *v1, const struct cplane *plane) +{ + float pos0[3], pos1[3]; + float d0, d1; + + pos0[0] = v0->x; pos0[1] = v0->y; pos0[2] = v0->z; + pos1[0] = v1->x; pos1[1] = v1->y; pos1[2] = v1->z; + + d0 = distance_signed(pos0, plane); + d1 = distance_signed(pos1, plane); + + if(d0 > 0.0f && d1 > 0.0f) { + return 1; + } + if(d0 < 0.0f && d1 < 0.0f) { + return -1; + } + return 0; +} + +static float distance_signed(float *pos, const struct cplane *plane) +{ + float dx = pos[0] - plane->x; + float dy = pos[1] - plane->y; + float dz = pos[2] - plane->z; + return dx * plane->nx + dy * plane->ny + dz * plane->nz; +} + +static int intersect(const struct ray *ray, const struct cplane *plane, float *t) +{ + float orig_pt_dir[3]; + + float ndotdir = plane->nx * ray->dir[0] + plane->ny * ray->dir[1] + plane->nz * ray->dir[2]; + if(fabs(ndotdir) < 1e-6) { + *t = 0.0f; + return 0; + } + + orig_pt_dir[0] = plane->x - ray->origin[0]; + orig_pt_dir[1] = plane->y - ray->origin[1]; + orig_pt_dir[2] = plane->z - ray->origin[2]; + + *t = (plane->nx * orig_pt_dir[0] + plane->ny * orig_pt_dir[1] + plane->nz * orig_pt_dir[2]) / ndotdir; + return 1; +} + +/* homogeneous frustum clipper helpers */ + +static int inside_frustum_plane(const struct g3d_vertex *v, int fplane) +{ + switch(fplane) { + case CLIP_LEFT: + return v->x >= -v->w; + case CLIP_RIGHT: + return v->x <= v->w; + case CLIP_BOTTOM: + return v->y >= -v->w; + case CLIP_TOP: + return v->y <= v->w; + case CLIP_NEAR: + return v->z >= -v->w; + case CLIP_FAR: + return v->z <= v->w; + } + assert(0); + return 0; +} + +static float intersect_frustum(const struct g3d_vertex *a, const struct g3d_vertex *b, int fplane) +{ + switch(fplane) { + case CLIP_LEFT: + return (-a->w - a->x) / (b->x - a->x + b->w - a->w); + case CLIP_RIGHT: + return (a->w - a->x) / (b->x - a->x - b->w + a->w); + case CLIP_BOTTOM: + return (-a->w - a->y) / (b->y - a->y + b->w - a->w); + case CLIP_TOP: + return (a->w - a->y) / (b->y - a->y - b->w + a->w); + case CLIP_NEAR: + return (-a->w - a->z) / (b->z - a->z + b->w - a->w); + case CLIP_FAR: + return (a->w - a->z) / (b->z - a->z - b->w + a->w); + } + + assert(0); + return 0; +} + +static int clip_edge_frustum(struct g3d_vertex *poly, int *vnumptr, + const struct g3d_vertex *v0, const struct g3d_vertex *v1, int fplane) +{ + int vnum = *vnumptr; + int in0, in1; + float t; + + in0 = inside_frustum_plane(v0, fplane); + in1 = inside_frustum_plane(v1, fplane); + + if(in0) { + /* start inside */ + if(in1) { + /* all inside */ + poly[vnum++] = *v1; /* append v1 */ + *vnumptr = vnum; + return 1; + } else { + /* going out */ + struct g3d_vertex *vptr = poly + vnum; + + t = intersect_frustum(v0, v1, fplane); + + vptr->x = v0->x + (v1->x - v0->x) * t; + vptr->y = v0->y + (v1->y - v0->y) * t; + vptr->z = v0->z + (v1->z - v0->z) * t; + vptr->w = v0->w + (v1->w - v0->w) * t; + + LERP_VATTR(vptr, v0, v1, t); + ++vnum; /* append new vertex on the intersection point */ + } + } else { + /* start outside */ + if(in1) { + /* going in */ + struct g3d_vertex *vptr = poly + vnum; + + t = intersect_frustum(v0, v1, fplane); + + vptr->x = v0->x + (v1->x - v0->x) * t; + vptr->y = v0->y + (v1->y - v0->y) * t; + vptr->z = v0->z + (v1->z - v0->z) * t; + vptr->w = v0->w + (v1->w - v0->w) * t; + + LERP_VATTR(vptr, v0, v1, t); + ++vnum; /* append new vertex on the intersection point */ + + /* then append v1 ... */ + poly[vnum++] = *v1; + } else { + /* all outside */ + return -1; + } + } + + *vnumptr = vnum; + return 0; +} diff --git a/src/polyclip.h b/src/polyclip.h new file mode 100644 index 0000000..adee29d --- /dev/null +++ b/src/polyclip.h @@ -0,0 +1,38 @@ +#ifndef POLYCLIP_H_ +#define POLYCLIP_H_ + +#include "3dgfx.h" + +struct cplane { + float x, y, z; + float nx, ny, nz; +}; + +enum { + CLIP_LEFT, CLIP_RIGHT, + CLIP_BOTTOM, CLIP_TOP, + CLIP_NEAR, CLIP_FAR +}; + +/* Generic polygon clipper + * returns: + * 1 -> fully inside, not clipped + * 0 -> straddling the plane and clipped + * -1 -> fully outside, not clipped + * in all cases, vertices are copied to vout, and the vertex count is written + * to wherever voutnum is pointing + */ +int clip_poly(struct g3d_vertex *vout, int *voutnum, + const struct g3d_vertex *vin, int vnum, struct cplane *plane); + +/* only checks if the polygon would be clipped by the plane, and classifies it + * as inside/outside/straddling, without actually producing a clipped polygon. + * return values are the same as clip_poly. + */ +int check_clip_poly(const struct g3d_vertex *v, int vnum, struct cplane *plane); + +/* Special-case frustum clipper (might be slightly faster) */ +int clip_frustum(struct g3d_vertex *vout, int *voutnum, + const struct g3d_vertex *vin, int vnum, int fplane); + +#endif /* POLYCLIP_H_ */ diff --git a/src/polyfill.c b/src/polyfill.c new file mode 100644 index 0000000..4ea2316 --- /dev/null +++ b/src/polyfill.c @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include "polyfill.h" +#include "gfxutil.h" +#include "util.h" + +/*#define DEBUG_OVERDRAW G3D_PACK_RGB(10, 10, 10)*/ + +#define FILL_POLY_BITS 0x03 + +/*void polyfill_tex_flat_new(struct pvertex *varr);*/ + +/* mode bits: 00-wire 01-flat 10-gouraud 11-reserved + * bit 2-3: texture mode: 00-none 01-modulate 10-add 11-replace + * bit 4-5: blend mode: 00-none 01-alpha 10-additive 11-reserved + * bit 6: zbuffering + */ +void (*fillfunc[])(struct pvertex*) = { /* zbbttpp */ + polyfill_wire, polyfill_flat, polyfill_gour, 0, /* 00000xx */ + polyfill_tex_wire, polyfill_tex_flat, polyfill_tex_gour, 0, /* 00001xx */ + 0, polyfill_addtex_flat, polyfill_addtex_gour, 0, /* 00010xx */ + 0, polyfill_repltex_flat, polyfill_repltex_flat, 0, /* 00011xx */ + + polyfill_alpha_wire, polyfill_alpha_flat, polyfill_alpha_gour, 0, /* 00100xx */ + polyfill_alpha_tex_wire, polyfill_alpha_tex_flat, polyfill_alpha_tex_gour,0,/* 00101xx */ + 0, 0, 0, 0, /* 00110xx */ + 0, 0, 0, 0, /* 00111xx */ + + polyfill_add_wire, polyfill_add_flat, polyfill_add_gour, 0, /* 01000xx */ + polyfill_add_tex_wire, polyfill_add_tex_flat, polyfill_add_tex_gour, 0, /* 01001xx */ + 0, 0, 0, 0, /* 01010xx */ + 0, 0, 0, 0, /* 01011xx */ + + 0, 0, 0, 0, /* 01100xx */ + 0, 0, 0, 0, /* 01101xx */ + 0, 0, 0, 0, /* 01110xx */ + 0, 0, 0, 0, /* 01111xx */ + + polyfill_wire, polyfill_flat_zbuf, polyfill_gour_zbuf, 0, /* 10000xx */ + polyfill_tex_wire, polyfill_tex_flat_zbuf, polyfill_tex_gour_zbuf, 0, /* 10001xx */ + 0, polyfill_addtex_flat_zbuf, polyfill_addtex_gour_zbuf, 0, /* 10010xx */ + 0, polyfill_repltex_flat_zbuf, polyfill_repltex_flat_zbuf, 0, /* 10011xx */ + + polyfill_alpha_wire, polyfill_alpha_flat_zbuf, polyfill_alpha_gour_zbuf, 0, /* 10100xx */ + polyfill_alpha_tex_wire,polyfill_alpha_tex_flat_zbuf,polyfill_alpha_tex_gour_zbuf,0,/* 10101xx */ + 0, 0, 0, 0, /* 10110xx */ + 0, 0, 0, 0, /* 10111xx */ + + polyfill_add_wire, polyfill_add_flat_zbuf, polyfill_add_gour_zbuf, 0, /* 11000xx */ + polyfill_add_tex_wire, polyfill_add_tex_flat_zbuf, polyfill_add_tex_gour_zbuf, 0, /* 11001xx */ + 0, 0, 0, 0, /* 11010xx */ + 0, 0, 0, 0, /* 11011xx */ + + 0, 0, 0, 0, /* 11100xx */ + 0, 0, 0, 0, /* 11101xx */ + 0, 0, 0, 0, /* 11110xx */ + 0, 0, 0, 0, /* 11111xx */ +}; + +struct pimage pfill_fb, pfill_tex; +uint32_t *pfill_zbuf; +struct pgradient pgrad; + +#define EDGEPAD 8 +static struct pvertex *edgebuf, *left, *right; +static int edgebuf_size; +/*static int fbheight;*/ + +/* +#define CHECKEDGE(x) \ + do { \ + assert(x >= 0); \ + assert(x < fbheight); \ + } while(0) +*/ +#define CHECKEDGE(x) + + +void polyfill_fbheight(int height) +{ + int newsz = (height * 2 + EDGEPAD * 3) * sizeof *edgebuf; + + if(newsz > edgebuf_size) { + free(edgebuf); + if(!(edgebuf = malloc(newsz))) { + fprintf(stderr, "failed to allocate edge table buffer (%d bytes)\n", newsz); + abort(); + } + edgebuf_size = newsz; + + left = edgebuf + EDGEPAD; + right = edgebuf + height + EDGEPAD * 2; + +#ifndef NDEBUG + memset(edgebuf, 0xaa, EDGEPAD * sizeof *edgebuf); + memset(edgebuf + height + EDGEPAD, 0xaa, EDGEPAD * sizeof *edgebuf); + memset(edgebuf + height * 2 + EDGEPAD * 2, 0xaa, EDGEPAD * sizeof *edgebuf); +#endif + } + + /*fbheight = height;*/ +} + +void polyfill(int mode, struct pvertex *verts) +{ +#ifndef NDEBUG + if(!fillfunc[mode]) { + fprintf(stderr, "polyfill mode %d not implemented\n", mode); + abort(); + } +#endif + + fillfunc[mode](verts); +} + +void polyfill_wire(struct pvertex *verts) +{ + int i, x0, y0, x1, y1; + struct pvertex *v = verts; + unsigned short color = ((v->r << 8) & 0xf800) | + ((v->g << 3) & 0x7e0) | ((v->b >> 3) & 0x1f); + + for(i=0; i<2; i++) { + x0 = v->x >> 8; + y0 = v->y >> 8; + ++v; + x1 = v->x >> 8; + y1 = v->y >> 8; + if(clip_line(&x0, &y0, &x1, &y1, 0, 0, pfill_fb.width, pfill_fb.height)) { + draw_line(x0, y0, x1, y1, color); + } + } + x0 = verts[0].x >> 8; + y0 = verts[0].y >> 8; + if(clip_line(&x1, &y1, &x0, &y0, 0, 0, pfill_fb.width, pfill_fb.height)) { + draw_line(x1, y1, x0, y0, color); + } +} + +void polyfill_tex_wire(struct pvertex *verts) +{ + polyfill_wire(verts); /* TODO */ +} + +void polyfill_alpha_wire(struct pvertex *verts) +{ + polyfill_wire(verts); /* TODO */ +} + +void polyfill_alpha_tex_wire(struct pvertex *verts) +{ + polyfill_wire(verts); /* TODO */ +} + +void polyfill_add_wire(struct pvertex *verts) +{ + polyfill_wire(verts); /* TODO */ +} + +void polyfill_add_tex_wire(struct pvertex *verts) +{ + polyfill_wire(verts); /* TODO */ +} + +#define VNEXT(p) (((p) == varr + 2) ? varr : (p) + 1) +#define VPREV(p) ((p) == varr ? varr + 2 : (p) - 1) +#define VSUCC(p, side) ((side) == 0 ? VNEXT(p) : VPREV(p)) + +/* extra bits of precision to use when interpolating colors. + * try tweaking this if you notice strange quantization artifacts. + */ +#define COLOR_SHIFT 12 + + +#define POLYFILL polyfill_flat +#undef GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_gour +#define GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_tex_flat +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_tex_gour +#define GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_flat +#undef GOURAUD +#undef TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_gour +#define GOURAUD +#undef TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_tex_flat +#undef GOURAUD +#define TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_tex_gour +#define GOURAUD +#define TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_flat +#undef GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_gour +#define GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_tex_flat +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_tex_gour +#define GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#undef ZBUF +#include "polytmpl.h" +#undef POLYFILL + +/* ---- zbuffer variants ----- */ + +#define POLYFILL polyfill_flat_zbuf +#undef GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_gour_zbuf +#define GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_tex_flat_zbuf +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_tex_gour_zbuf +#define GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_flat_zbuf +#undef GOURAUD +#undef TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_gour_zbuf +#define GOURAUD +#undef TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_tex_flat_zbuf +#undef GOURAUD +#define TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_alpha_tex_gour_zbuf +#define GOURAUD +#define TEXMAP +#define BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_flat_zbuf +#undef GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_gour_zbuf +#define GOURAUD +#undef TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_tex_flat_zbuf +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_add_tex_gour_zbuf +#define GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#define BLEND_ADD +#define ZBUF +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_addtex_flat +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#define TEX_ADD +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_addtex_gour +#define GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#define TEX_ADD +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_addtex_flat_zbuf +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#define TEX_ADD +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_addtex_gour_zbuf +#define GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#define TEX_ADD +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_repltex_flat +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#undef ZBUF +#undef TEX_ADD +#define TEX_REPL +#include "polytmpl.h" +#undef POLYFILL + +#define POLYFILL polyfill_repltex_flat_zbuf +#undef GOURAUD +#define TEXMAP +#undef BLEND_ALPHA +#undef BLEND_ADD +#define ZBUF +#undef TEX_ADD +#define TEX_REPL +#include "polytmpl.h" +#undef POLYFILL diff --git a/src/polyfill.h b/src/polyfill.h new file mode 100644 index 0000000..ec34675 --- /dev/null +++ b/src/polyfill.h @@ -0,0 +1,97 @@ +#ifndef POLYFILL_H_ +#define POLYFILL_H_ + +#include "inttypes.h" +#include "3dgfx.h" + +#define POLYFILL_MODE_MASK 0x03 +#define POLYFILL_TEX_MASK 0x0c +#define POLYFILL_TEXMODE_SHIFT 2 +#define POLYFILL_ALPHA_BIT 0x10 +#define POLYFILL_ADD_BIT 0x20 +#define POLYFILL_ZBUF_BIT 0x40 + +enum { + POLYFILL_WIRE, + POLYFILL_FLAT, + POLYFILL_GOURAUD +}; + +enum { + POLYFILL_NOTEX, + POLYFILL_TEXMOD, + POLYFILL_TEXADD, + POLYFILL_TEXREPL +}; + +/* projected vertices for the rasterizer */ +struct pvertex { + int32_t x, y; /* 24.8 fixed point */ + int32_t u, v; /* 16.16 fixed point */ + int32_t r, g, b, a; /* int 0-255 */ + int32_t z; /* 0-(2^24-1) */ +}; + +struct pgradient { + int32_t dudx, dudy, dvdx, dvdy; + int32_t drdx, drdy, dgdx, dgdy, dbdx, dbdy, dadx, dady; + int32_t dzdx, dzdy; +}; + +struct pimage { + g3d_pixel *pixels; + int width, height; + + int xshift, yshift; + unsigned int xmask, ymask; +}; + +extern struct pimage pfill_fb; +extern struct pimage pfill_tex; +extern uint32_t *pfill_zbuf; +extern struct pgradient pgrad; + +void polyfill_fbheight(int height); + +void polyfill(int mode, struct pvertex *verts); + +void polyfill_wire(struct pvertex *verts); +void polyfill_flat(struct pvertex *verts); +void polyfill_gour(struct pvertex *verts); +void polyfill_tex_wire(struct pvertex *verts); +void polyfill_tex_flat(struct pvertex *verts); +void polyfill_tex_gour(struct pvertex *verts); +void polyfill_alpha_wire(struct pvertex *verts); +void polyfill_alpha_flat(struct pvertex *verts); +void polyfill_alpha_gour(struct pvertex *verts); +void polyfill_alpha_tex_wire(struct pvertex *verts); +void polyfill_alpha_tex_flat(struct pvertex *verts); +void polyfill_alpha_tex_gour(struct pvertex *verts); +void polyfill_add_wire(struct pvertex *verts); +void polyfill_add_flat(struct pvertex *verts); +void polyfill_add_gour(struct pvertex *verts); +void polyfill_add_tex_wire(struct pvertex *verts); +void polyfill_add_tex_flat(struct pvertex *verts); +void polyfill_add_tex_gour(struct pvertex *verts); +void polyfill_flat_zbuf(struct pvertex *verts); +void polyfill_gour_zbuf(struct pvertex *verts); +void polyfill_tex_flat_zbuf(struct pvertex *verts); +void polyfill_tex_gour_zbuf(struct pvertex *verts); +void polyfill_alpha_flat_zbuf(struct pvertex *verts); +void polyfill_alpha_gour_zbuf(struct pvertex *verts); +void polyfill_alpha_tex_flat_zbuf(struct pvertex *verts); +void polyfill_alpha_tex_gour_zbuf(struct pvertex *verts); +void polyfill_add_flat_zbuf(struct pvertex *verts); +void polyfill_add_gour_zbuf(struct pvertex *verts); +void polyfill_add_tex_flat_zbuf(struct pvertex *verts); +void polyfill_add_tex_gour_zbuf(struct pvertex *verts); + +void polyfill_addtex_flat(struct pvertex *verts); +void polyfill_addtex_gour(struct pvertex *verts); +void polyfill_addtex_flat_zbuf(struct pvertex *verts); +void polyfill_addtex_gour_zbuf(struct pvertex *verts); + +void polyfill_repltex_flat(struct pvertex *verts); +void polyfill_repltex_flat_zbuf(struct pvertex *verts); + +#endif /* POLYFILL_H_ */ diff --git a/src/polytmpl.h b/src/polytmpl.h new file mode 100644 index 0000000..a00ad42 --- /dev/null +++ b/src/polytmpl.h @@ -0,0 +1,314 @@ +#ifdef _MSC_VER +#pragma warning (disable: 4101) +#endif + +#if !defined(GOURAUD) && !defined(TEXMAP) && !defined(ZBUF) +#define NOLERP +#endif + +void POLYFILL(struct pvertex *varr) +{ + int i, line, top, bot; + struct pvertex *v, *vn, *tab; + int32_t x, y0, y1, dx, dy, slope, fx, fy; + int start, len; + g3d_pixel *fbptr, *pptr, color; +#ifdef GOURAUD + int32_t r, g, b, dr, dg, db, rslope, gslope, bslope; +#ifdef BLEND_ALPHA + int32_t a, da, aslope; +#endif +#endif /* GOURAUD */ +#ifdef TEXMAP + int32_t tu, tv, du, dv, uslope, vslope; + int tx, ty; + g3d_pixel texel; +#endif +#ifdef ZBUF + int32_t z, dz, zslope; + uint32_t *zptr; +#endif + +#if !defined(GOURAUD) + /* for flat shading we already know the color, so pack it once */ + color = G3D_PACK_RGB(varr[0].r, varr[0].g, varr[0].b); +#endif + + top = pfill_fb.height; + bot = 0; + + for(i=0; i<3; i++) { + /* scan the edge between the current and next vertex */ + v = varr + i; + vn = VNEXT(v); + + if(vn->y == v->y) continue; /* XXX ??? */ + + if(vn->y >= v->y) { + /* inrementing Y: left side */ + tab = left; + } else { + /* decrementing Y: right side, flip vertices to trace bottom->up */ + tab = right; + v = vn; + vn = varr + i; + } + + /* calculate edge slope */ + dx = vn->x - v->x; + dy = vn->y - v->y; + slope = (dx << 8) / dy; + +#ifdef GOURAUD + r = v->r << COLOR_SHIFT; + g = v->g << COLOR_SHIFT; + b = v->b << COLOR_SHIFT; + dr = (vn->r << COLOR_SHIFT) - r; + dg = (vn->g << COLOR_SHIFT) - g; + db = (vn->b << COLOR_SHIFT) - b; + rslope = (dr << 8) / dy; + gslope = (dg << 8) / dy; + bslope = (db << 8) / dy; +#ifdef BLEND_ALPHA + a = v->a << COLOR_SHIFT; + da = (vn->a << COLOR_SHIFT) - a; + aslope = (da << 8) / dy; +#endif /* BLEND_ALPHA */ +#endif /* GOURAUD */ +#ifdef TEXMAP + tu = v->u; + tv = v->v; + du = vn->u - tu; + dv = vn->v - tv; + uslope = (du << 8) / dy; + vslope = (dv << 8) / dy; +#endif /* TEXMAP */ +#ifdef ZBUF + z = v->z; + dz = vn->z - z; + zslope = (dz << 8) / dy; +#endif /* ZBUF */ + + y0 = (v->y + 0x100) & 0xffffff00; /* start from the next scanline */ + fy = y0 - v->y; /* fractional part before the next scanline */ + fx = (fy * slope) >> 8; /* X adjust for the step to the next scanline */ + x = v->x + fx; /* adjust X */ + y1 = vn->y & 0xffffff00; /* last scanline of the edge <= vn->y */ + + /* also adjust other interpolated attributes */ +#ifdef GOURAUD + r += (fy * rslope) >> 8; + g += (fy * gslope) >> 8; + b += (fy * bslope) >> 8; +#ifdef BLEND_ALPHA + a += (fy * aslope) >> 8; +#endif /* BLEND_ALPHA */ +#endif /* GOURAUD */ +#ifdef TEXMAP +#ifdef FLTUV + tu += uslope * (fy / 256.0f); + tv += vslope * (fy / 256.0f); +#else + tu += (fy * uslope) >> 8; + tv += (fy * vslope) >> 8; +#endif +#endif /* TEXMAP */ +#ifdef ZBUF + z += (fy * zslope) >> 8; +#endif + + line = y0 >> 8; + if(line < top) top = line; + if((y1 >> 8) > bot) bot = y1 >> 8; + + if(line > 0) tab += line; + + while(line <= (y1 >> 8) && line < pfill_fb.height) { + if(line >= 0) { + int val = x < 0 ? 0 : x >> 8; + tab->x = val < pfill_fb.width ? val : pfill_fb.width - 1; +#ifdef GOURAUD + tab->r = r; + tab->g = g; + tab->b = b; +#ifdef BLEND_ALPHA + tab->a = a; +#endif /* BLEND_ALPHA */ +#endif /* GOURAUD */ +#ifdef TEXMAP + tab->u = tu; + tab->v = tv; +#endif /* TEXMAP */ +#ifdef ZBUF + tab->z = z; +#endif + tab++; + } + x += slope; +#ifdef GOURAUD + r += rslope; + g += gslope; + b += bslope; +#ifdef BLEND_ALPHA + a += aslope; +#endif /* BLEND_ALPHA */ +#endif /* GOURAUD */ +#ifdef TEXMAP + tu += uslope; + tv += vslope; +#endif /* TEXMAP */ +#ifdef ZBUF + z += zslope; +#endif /* ZBUF */ + line++; + } + } + + if(top < 0) top = 0; + if(bot >= pfill_fb.height) bot = pfill_fb.height - 1; + + fbptr = pfill_fb.pixels + top * pfill_fb.width; + for(i=top; i<=bot; i++) { + start = left[i].x; + len = right[i].x - start; + /* XXX we probably need more precision in left/right.x */ + +#ifndef NOLERP + dx = len == 0 ? 256 : (len << 8); +#endif + +#ifdef GOURAUD + r = left[i].r; + g = left[i].g; + b = left[i].b; +#ifdef BLEND_ALPHA + a = left[i].a; +#endif /* BLEND_ALPHA */ +#endif /* GOURAUD */ +#ifdef TEXMAP + tu = left[i].u; + tv = left[i].v; +#endif /* TEXMAP */ +#ifdef ZBUF + z = left[i].z; + zptr = pfill_zbuf + i * pfill_fb.width + start; +#endif /* ZBUF */ + + pptr = fbptr + start; + while(len-- > 0) { +#if defined(GOURAUD) || defined(TEXMAP) || defined(BLEND_ALPHA) || defined(BLEND_ADD) + int cr, cg, cb; +#endif +#if defined(BLEND_ALPHA) || defined(BLEND_ADD) + g3d_pixel fbcol; +#endif +#ifdef BLEND_ALPHA + int alpha, inv_alpha; +#endif +#ifdef ZBUF + uint32_t cz = z; + z += pgrad.dzdx; + + if(cz <= *zptr) { + *zptr++ = cz; + } else { + /* ZFAIL: advance all attributes and continue */ +#ifdef GOURAUD + r += pgrad.drdx; + g += pgrad.dgdx; + b += pgrad.dbdx; +#ifdef BLEND_ALPHA + a += pgrad.dadx; +#endif +#endif /* GOURAUD */ +#ifdef TEXMAP + tu += pgrad.dudx; + tv += pgrad.dvdx; +#endif /* TEXMAP */ + /* skip pixel */ + pptr++; + zptr++; + continue; + } +#endif /* ZBUF */ + +#ifdef GOURAUD + /* we upped the color precision to while interpolating the + * edges, now drop the extra bits before packing + */ + cr = r < 0 ? 0 : (r >> COLOR_SHIFT); + cg = g < 0 ? 0 : (g >> COLOR_SHIFT); + cb = b < 0 ? 0 : (b >> COLOR_SHIFT); + r += pgrad.drdx; + g += pgrad.dgdx; + b += pgrad.dbdx; +#endif /* GOURAUD */ +#ifdef TEXMAP + tx = (tu >> (16 - pfill_tex.xshift)) & pfill_tex.xmask; + ty = (tv >> (16 - pfill_tex.yshift)) & pfill_tex.ymask; + texel = pfill_tex.pixels[(ty << pfill_tex.xshift) + tx]; + + tu += pgrad.dudx; + tv += pgrad.dvdx; + +#ifndef GOURAUD + /* for flat textured, cr,cg,cb would not be initialized */ + cr = varr[0].r; + cg = varr[0].g; + cb = varr[0].b; +#endif /* !GOURAUD */ + /* This is not correct, should be /255, but it's much faster + * to shift by 8 (/256), and won't make a huge difference + */ +#ifdef TEX_ADD + cr = cr + G3D_UNPACK_R(texel); + cg = cg + G3D_UNPACK_G(texel); + cb = cb + G3D_UNPACK_B(texel); +#elif defined(TEX_REPL) + color = texel; +#else + cr = (cr * G3D_UNPACK_R(texel)) >> 8; + cg = (cg * G3D_UNPACK_G(texel)) >> 8; + cb = (cb * G3D_UNPACK_B(texel)) >> 8; +#endif + +#endif /* TEXMAP */ + +#ifdef BLEND_ALPHA +#ifdef GOURAUD + alpha = a >> COLOR_SHIFT; + inv_alpha = 255 - alpha; + a += pgrad.dadx; +#else + alpha = varr[0].a; +#endif + inv_alpha = 255 - alpha; + fbcol = *pptr; + cr = (cr * alpha + G3D_UNPACK_R(fbcol) * inv_alpha) >> 8; + cg = (cg * alpha + G3D_UNPACK_G(fbcol) * inv_alpha) >> 8; + cb = (cb * alpha + G3D_UNPACK_B(fbcol) * inv_alpha) >> 8; +#endif /* BLEND_ALPHA */ +#ifdef BLEND_ADD + fbcol = *pptr; + cr += G3D_UNPACK_R(fbcol); + cg += G3D_UNPACK_G(fbcol); + cb += G3D_UNPACK_B(fbcol); +#endif /* BLEND_ADD */ + +#ifdef DEBUG_OVERDRAW + *pptr++ += DEBUG_OVERDRAW; +#else +#if defined(GOURAUD) || (defined(TEXMAP) && !defined(TEX_REPL)) || defined(BLEND_ALPHA) || defined(BLEND_ADD) + if(cr >= 255) cr = 255; + if(cg >= 255) cg = 255; + if(cb >= 255) cb = 255; + color = G3D_PACK_RGB(cr, cg, cb); +#endif + *pptr++ = color; +#endif + } + fbptr += pfill_fb.width; + } +} + +#undef NOLERP diff --git a/src/scene.c b/src/scene.c new file mode 100644 index 0000000..fb6980a --- /dev/null +++ b/src/scene.c @@ -0,0 +1,637 @@ +#include +#include "scene.h" +#include "darray.h" +#include "util.h" +#include "goat3d.h" +#include "cgmath/cgmath.h" + +void scn_init(struct g3d_scene *scn) +{ + scn->meshes = darr_alloc(0, sizeof *scn->meshes); + scn->mtls = darr_alloc(0, sizeof *scn->mtls); + scn->nodes = darr_alloc(0, sizeof *scn->nodes); + scn->anims = darr_alloc(0, sizeof *scn->anims); + + scn->texpath = 0; +} + +void scn_destroy(struct g3d_scene *scn) +{ + scn_clear(scn); + darr_free(scn->meshes); + darr_free(scn->mtls); + darr_free(scn->nodes); + darr_free(scn->anims); +} + +struct g3d_scene *scn_create(void) +{ + struct g3d_scene *scn = malloc_nf(sizeof *scn); + scn_init(scn); + return scn; +} + +void scn_free(struct g3d_scene *scn) +{ + if(!scn) return; + scn_destroy(scn); + free(scn); +} + +void scn_clear(struct g3d_scene *scn) +{ + int i, num; + + num = darr_size(scn->meshes); + for(i=0; imeshes[i]); + } + darr_clear(scn->meshes); + + num = darr_size(scn->mtls); + for(i=0; imtls[i]->texmap) { + destroy_image(scn->mtls[i]->texmap); + free(scn->mtls[i]->texmap); + } + if(scn->mtls[i]->envmap) { + destroy_image(scn->mtls[i]->envmap); + free(scn->mtls[i]->envmap); + } + free(scn->mtls[i]->name); + free(scn->mtls[i]); + } + darr_clear(scn->mtls); + + num = darr_size(scn->nodes); + for(i=0; inodes[i]); + } + darr_clear(scn->nodes); + + num = darr_size(scn->anims); + for(i=0; ianims[i]); + } + darr_clear(scn->anims); +} + +void scn_init_node(struct g3d_node *node) +{ + memset(node, 0, sizeof *node); + cgm_midentity(node->xform); + cgm_vcons(&node->scale, 1, 1, 1); + cgm_qcons(&node->rot, 0, 0, 0, 1); +} + +void scn_destroy_node(struct g3d_node *node) +{ + if(!node) return; + free(node->name); +} + +void scn_free_node(struct g3d_node *node) +{ + if(!node) return; + scn_destroy_node(node); + free(node); +} + +void scn_init_anim(struct g3d_anim *anim) +{ + anim->name = 0; + anim->tracks = darr_alloc(0, sizeof *anim->tracks); +} + +void scn_destroy_anim(struct g3d_anim *anim) +{ + int i, num; + + if(!anim) return; + free(anim->name); + + num = darr_size(anim->tracks); + for(i=0; itracks[i]); + free(anim->tracks[i]); + } + darr_free(anim->tracks); +} + +void scn_free_anim(struct g3d_anim *anim) +{ + if(!anim) return; + scn_destroy_anim(anim); + free(anim); +} + +void scn_init_track(struct g3d_track *track) +{ + track->trk = goat3d_create_track(); +} + +void scn_destroy_track(struct g3d_track *track) +{ + goat3d_destroy_track(track->trk); + free(track->trk); +} + +void scn_add_mesh(struct g3d_scene *scn, struct g3d_mesh *mesh) +{ + darr_push(scn->meshes, &mesh); +} + +void scn_add_mtl(struct g3d_scene *scn, struct g3d_material *mtl) +{ + darr_push(scn->mtls, &mtl); +} + +void scn_add_node(struct g3d_scene *scn, struct g3d_node *node) +{ + darr_push(scn->nodes, &node); +} + +void scn_add_anim(struct g3d_scene *scn, struct g3d_anim *anim) +{ + darr_push(scn->anims, &anim); +} + +int scn_mesh_count(struct g3d_scene *scn) +{ + return darr_size(scn->meshes); +} + +int scn_mtl_count(struct g3d_scene *scn) +{ + return darr_size(scn->mtls); +} + +int scn_node_count(struct g3d_scene *scn) +{ + return darr_size(scn->nodes); +} + +int scn_anim_count(struct g3d_scene *scn) +{ + return darr_size(scn->anims); +} + +struct g3d_mesh *scn_find_mesh(struct g3d_scene *scn, const char *name) +{ + int i, num = darr_size(scn->meshes); + for(i=0; imeshes[i]->name, name) == 0) { + return scn->meshes[i]; + } + } + return 0; +} + +struct g3d_material *scn_find_mtl(struct g3d_scene *scn, const char *name) +{ + int i, num = darr_size(scn->mtls); + for(i=0; imtls[i]->name, name) == 0) { + return scn->mtls[i]; + } + } + return 0; +} + +struct g3d_node *scn_find_node(struct g3d_scene *scn, const char *name) +{ + int i, num = darr_size(scn->nodes); + for(i=0; inodes[i]->name, name) == 0) { + return scn->nodes[i]; + } + } + return 0; +} + +struct g3d_anim *scn_find_anim(struct g3d_scene *scn, const char *name) +{ + int i, num = darr_size(scn->anims); + for(i=0; ianims[i]->name, name) == 0) { + return scn->anims[i]; + } + } + return 0; +} + + +int conv_goat3d_scene(struct g3d_scene *scn, struct goat3d *g) +{ + int i, num; + long count, nfaces; + struct g3d_mesh *mesh; + struct g3d_material *mtl; + struct g3d_node *node; + struct g3d_anim *anim; + + printf("loaded goat3d scene: %s\n", goat3d_get_name(g)); + + num = goat3d_get_mtl_count(g); + for(i=0; iicount / 3; + count++; + } else { + free(mesh); + } + } + printf(" - %ld meshes with %ld total polygons\n", count, nfaces); + + num = goat3d_get_node_count(g); + for(i=0; inodes[i], goat3d_get_node(g, i)); + } + + count = 0; + num = goat3d_get_anim_count(g); + for(i=0; iname = strdup_nf(goat3d_get_mesh_name(srcmesh)); + vdst = dstmesh->varr; + idxdst = dstmesh->iarr; + + for(i=0; ix = vsrc[0]; + vdst->y = vsrc[1]; + vdst->z = vsrc[2]; + vdst->w = 1; + + if((vsrc = goat3d_get_mesh_attrib(srcmesh, GOAT3D_MESH_ATTR_NORMAL, i))) { + vdst->nx = vsrc[0]; + vdst->ny = vsrc[1]; + vdst->nz = vsrc[2]; + } else { + vdst->nx = vdst->ny = vdst->nz = 0; + } + + if((vsrc = goat3d_get_mesh_attrib(srcmesh, GOAT3D_MESH_ATTR_TEXCOORD, i))) { + vdst->u = vsrc[0]; + vdst->v = 1.0f - vsrc[1]; + } else { + vdst->u = vdst->v = 0; + } + + if((vsrc = goat3d_get_mesh_attrib(srcmesh, GOAT3D_MESH_ATTR_COLOR, i))) { + vdst->r = (int)(vsrc[0] * 255.0f); + vdst->g = (int)(vsrc[1] * 255.0f); + vdst->b = (int)(vsrc[2] * 255.0f); + vdst->a = (int)(vsrc[3] * 255.0f); + } else { + vdst->r = vdst->g = vdst->b = vdst->a = 255; + } + vdst++; + } + + for(i=0; imtl = scn_find_mtl(scn, goat3d_get_mtl_name(gmtl)); + } + + return 0; +} + +int conv_goat3d_mtl(struct g3d_scene *scn, struct g3d_material *dstmtl, struct goat3d_material *srcmtl) +{ + const float *mattr; + const char *str; + + init_g3dmtl(dstmtl); + if((str = goat3d_get_mtl_name(srcmtl))) { + dstmtl->name = strdup_nf(str); + } + + if((mattr = goat3d_get_mtl_attrib(srcmtl, GOAT3D_MAT_ATTR_DIFFUSE))) { + dstmtl->r = mattr[0]; + dstmtl->g = mattr[1]; + dstmtl->b = mattr[2]; + } + if((mattr = goat3d_get_mtl_attrib(srcmtl, GOAT3D_MAT_ATTR_ALPHA))) { + dstmtl->a = *mattr; + } + if((mattr = goat3d_get_mtl_attrib(srcmtl, GOAT3D_MAT_ATTR_SPECULAR))) { + dstmtl->sr = mattr[0]; + dstmtl->sg = mattr[1]; + dstmtl->sb = mattr[2]; + } + if((mattr = goat3d_get_mtl_attrib(srcmtl, GOAT3D_MAT_ATTR_SHININESS))) { + dstmtl->shin = *mattr; + } + + if((str = goat3d_get_mtl_attrib_map(srcmtl, GOAT3D_MAT_ATTR_DIFFUSE))) { + if(scn->texpath) { + char *buf = alloca(strlen(str) + strlen(scn->texpath) + 2); + sprintf(buf, "%s/%s", scn->texpath, str); + str = buf; + } + dstmtl->texmap = malloc_nf(sizeof *dstmtl->texmap); + if(load_image(dstmtl->texmap, str) == -1) { + free(dstmtl->texmap); + dstmtl->texmap = 0; + } + } + + return 0; +} + +int conv_goat3d_node(struct g3d_scene *scn, struct g3d_node *dstnode, struct goat3d_node *srcnode) +{ + struct goat3d_mesh *gmesh; + + scn_init_node(dstnode); + + dstnode->name = strdup_nf(goat3d_get_node_name(srcnode)); + if(goat3d_get_node_type(srcnode) == GOAT3D_NODE_MESH) { + if((gmesh = goat3d_get_node_object(srcnode))) { + dstnode->mesh = scn_find_mesh(scn, goat3d_get_mesh_name(gmesh)); + } + } + + goat3d_get_node_pivot(srcnode, &dstnode->pivot.x, &dstnode->pivot.y, &dstnode->pivot.z); + goat3d_get_node_position(srcnode, &dstnode->pos.x, &dstnode->pos.y, &dstnode->pos.z); + goat3d_get_node_rotation(srcnode, &dstnode->rot.x, &dstnode->rot.y, &dstnode->rot.z, &dstnode->rot.w); + goat3d_get_node_scaling(srcnode, &dstnode->scale.x, &dstnode->scale.y, &dstnode->scale.z); + goat3d_get_node_matrix(srcnode, dstnode->xform); + dstnode->xform_valid = 1; + return 0; +} + +int link_goat3d_node(struct g3d_scene *scn, struct g3d_node *dstnode, struct goat3d_node *srcnode) +{ + int i, num; + struct goat3d_node *gnode; + struct g3d_node *node, *tail; + + if((gnode = goat3d_get_node_parent(srcnode))) { + dstnode->parent = scn_find_node(scn, goat3d_get_node_name(gnode)); + } + + num = goat3d_get_node_child_count(srcnode); + for(i=0; inext = 0; + if(dstnode->child) { + tail->next = node; + tail = node; + } else { + dstnode->child = tail = node; + } + } + } + + return 0; +} + +int conv_goat3d_anim(struct g3d_scene *scn, struct g3d_anim *dstanim, struct goat3d_anim *srcanim) +{ + int i, num; + const char *str; + struct g3d_track *trk; + struct goat3d_track *gtrk; + + scn_init_anim(dstanim); + + if((str = goat3d_get_anim_name(srcanim))) { + dstanim->name = strdup_nf(str); + } + + dstanim->dur = goat3d_get_anim_timeline(srcanim, &dstanim->start, &dstanim->end); + + num = goat3d_get_anim_track_count(srcanim); + for(i=0; itracks, &trk); + } + } + + return 0; +} + +int conv_goat3d_track(struct g3d_scene *scn, struct g3d_track *dsttrk, struct goat3d_track *srctrk) +{ + int i, num; + struct goat3d_key key; + struct goat3d_node *gnode; + const char *name; + + scn_init_track(dsttrk); + + if(!(gnode = goat3d_get_track_node(srctrk)) || !(name = goat3d_get_node_name(gnode))) { + return -1; + } + if(!(dsttrk->node = scn_find_node(scn, name))) { + fprintf(stderr, "conv_goat3d_track: failed to find node: %s\n", name); + return -1; + } + + if((name = goat3d_get_track_name(srctrk))) { + goat3d_set_track_name(dsttrk->trk, name); + } + goat3d_set_track_type(dsttrk->trk, goat3d_get_track_type(srctrk)); + goat3d_set_track_interp(dsttrk->trk, goat3d_get_track_interp(srctrk)); + goat3d_set_track_extrap(dsttrk->trk, goat3d_get_track_extrap(srctrk)); + + num = goat3d_get_track_key_count(srctrk); + for(i=0; itrk, &key); + } + return 0; +} + +void scn_merge_anims(struct g3d_scene *scn) +{ + int i, j; + struct g3d_anim *dest, *anim; + long t0, t1; + + dest = scn->anims[0]; + for(i=1; ianims); i++) { + anim = scn->anims[i]; + for(j=0; jtracks); j++) { + struct g3d_track *trk = anim->tracks[j]; + + darr_push(dest->tracks, &trk); + + goat3d_get_track_timeline(trk->trk, &t0, &t1); + if(t0 < dest->start) dest->start = t0; + if(t1 > dest->end) dest->end = t1; + } + + free(anim->name); + free(anim); + } + + darr_resize(scn->anims, 1); + dest->dur = dest->end - dest->start; +} + +void scn_eval_anim(struct g3d_anim *anim, long tm) +{ + int i, num; + struct g3d_track *trk; + struct g3d_node *node; + + num = darr_size(anim->tracks); + for(i=0; itracks[i]; + if(!(node = trk->node)) { + printf("skip track without node\n"); + continue; + } + + switch(goat3d_get_track_type(trk->trk)) { + case GOAT3D_TRACK_POS: + goat3d_get_track_vec3(trk->trk, tm, &node->pos.x, &node->pos.y, &node->pos.z); + node->xform_valid = 0; + break; + case GOAT3D_TRACK_ROT: + goat3d_get_track_quat(trk->trk, tm, &node->rot.x, &node->rot.y, &node->rot.z, &node->rot.w); + cgm_qnormalize(&node->rot); + node->xform_valid = 0; + break; + case GOAT3D_TRACK_SCALE: + goat3d_get_track_vec3(trk->trk, tm, &node->scale.x, &node->scale.y, &node->scale.z); + node->xform_valid = 0; + break; + default: + break; + } + } +} + +void scn_calc_node_matrix(struct g3d_node *node) +{ + int i; + float rmat[16]; + float *mat = node->xform; + + cgm_mtranslation(mat, node->pivot.x, node->pivot.y, node->pivot.z); + cgm_mrotation_quat(rmat, &node->rot); + + for(i=0; i<3; i++) { + mat[i] = rmat[i]; + mat[4 + i] = rmat[4 + i]; + mat[8 + i] = rmat[8 + i]; + } + + mat[0] *= node->scale.x; mat[4] *= node->scale.y; mat[8] *= node->scale.z; mat[12] += node->pos.x; + mat[1] *= node->scale.x; mat[5] *= node->scale.y; mat[9] *= node->scale.z; mat[13] += node->pos.y; + mat[2] *= node->scale.x; mat[6] *= node->scale.y; mat[10] *= node->scale.z; mat[14] += node->pos.z; + + cgm_mpretranslate(mat, -node->pivot.x, -node->pivot.y, -node->pivot.z); + + /* that's basically: pivot * rotation * translation * scaling * -pivot */ + + node->xform_valid = 1; +} + +void scn_draw(struct g3d_scene *scn) +{ + int i, num; + struct g3d_node *node; + + if(darr_empty(scn->nodes)) { + num = darr_size(scn->meshes); + for(i=0; imeshes[i]); + } + } else { + num = darr_size(scn->nodes); + for(i=0; inodes[i]; + if(node->parent) continue; + + scn_draw_node(node); + } + } +} + +void scn_draw_node(struct g3d_node *node) +{ + struct g3d_node *n; + + if(!node->xform_valid) { + scn_calc_node_matrix(node); + } + + g3d_push_matrix(); + g3d_mult_matrix(node->xform); + + if(node->mesh) { + draw_mesh(node->mesh); + } + + n = node->child; + while(n) { + scn_draw_node(n); + n = n->next; + } + + g3d_pop_matrix(); +} diff --git a/src/scene.h b/src/scene.h new file mode 100644 index 0000000..3e11e43 --- /dev/null +++ b/src/scene.h @@ -0,0 +1,92 @@ +#ifndef SCENE_H_ +#define SCENE_H_ + +#include "cgmath/cgmath.h" +#include "mesh.h" +#include "goat3d.h" + +struct g3d_node { + char *name; + struct g3d_mesh *mesh; + struct g3d_node *parent; + struct g3d_node *child, *next; + + cgm_vec3 pos; + cgm_quat rot; + cgm_vec3 scale; + cgm_vec3 pivot; + + float xform[16]; + int xform_valid; +}; + +struct g3d_track { + struct g3d_node *node; + struct goat3d_track *trk; +}; + +struct g3d_anim { + char *name; + struct g3d_track **tracks; /* darray */ + long start, end, dur; +}; + +struct g3d_scene { + /* dynamic arrays (darray) */ + struct g3d_mesh **meshes; + struct g3d_node **nodes; + struct g3d_anim **anims; + struct g3d_material **mtls; + + char *texpath; +}; + +void scn_init(struct g3d_scene *scn); +void scn_destroy(struct g3d_scene *scn); + +struct g3d_scene *scn_create(void); +void scn_free(struct g3d_scene *scn); + +void scn_clear(struct g3d_scene *scn); + +void scn_init_node(struct g3d_node *node); +void scn_destroy_node(struct g3d_node *node); +void scn_free_node(struct g3d_node *node); + +void scn_init_anim(struct g3d_anim *anim); +void scn_destroy_anim(struct g3d_anim *anim); +void scn_free_anim(struct g3d_anim *anim); + +void scn_init_track(struct g3d_track *track); +void scn_destroy_track(struct g3d_track *track); + +void scn_add_mesh(struct g3d_scene *scn, struct g3d_mesh *mesh); +void scn_add_mtl(struct g3d_scene *scn, struct g3d_material *mtl); +void scn_add_node(struct g3d_scene *scn, struct g3d_node *node); +void scn_add_anim(struct g3d_scene *scn, struct g3d_anim *anim); + +int scn_mesh_count(struct g3d_scene *scn); +int scn_mtl_count(struct g3d_scene *scn); +int scn_node_count(struct g3d_scene *scn); +int scn_anim_count(struct g3d_scene *scn); + +struct g3d_mesh *scn_find_mesh(struct g3d_scene *scn, const char *name); +struct g3d_material *scn_find_mtl(struct g3d_scene *scn, const char *name); +struct g3d_node *scn_find_node(struct g3d_scene *scn, const char *name); +struct g3d_anim *scn_find_anim(struct g3d_scene *scn, const char *name); + +int conv_goat3d_scene(struct g3d_scene *scn, struct goat3d *g); +int conv_goat3d_mesh(struct g3d_scene *scn, struct g3d_mesh *dstmesh, struct goat3d_mesh *srcmesh); +int conv_goat3d_mtl(struct g3d_scene *scn, struct g3d_material *destmtl, struct goat3d_material *srcmtl); +int conv_goat3d_node(struct g3d_scene *scn, struct g3d_node *dstnode, struct goat3d_node *srcnode); +int link_goat3d_node(struct g3d_scene *scn, struct g3d_node *dstnode, struct goat3d_node *srcnode); +int conv_goat3d_anim(struct g3d_scene *scn, struct g3d_anim *dstanim, struct goat3d_anim *srcanim); +int conv_goat3d_track(struct g3d_scene *scn, struct g3d_track *dsttrk, struct goat3d_track *srctrk); + +void scn_merge_anims(struct g3d_scene *scn); + +void scn_eval_anim(struct g3d_anim *anim, long tm); +void scn_draw(struct g3d_scene *scn); +void scn_draw_node(struct g3d_node *node); + +#endif /* SCENE_H_ */ -- 1.7.10.4