--- /dev/null
+*.o
+*.d
+*.swp
+*.a
+*.so*
+*.obj
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <https://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<https://www.gnu.org/licenses/>.
+
+ 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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
--- /dev/null
+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
--- /dev/null
+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 <nuclear@mutantstargoat.com>
+
+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.
--- /dev/null
+.PHONY: all
+all:
+ echo all
+
+.PHONY: clean
+clean:
+ echo foo
--- /dev/null
+#ifndef THREEDGFX_H_
+#define THREEDGFX_H_
+
+#include <stdint.h>
+
+#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_ */
--- /dev/null
+#ifndef BUILD_OPENGL
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include "3dgfx.h"
+#include "polyfill.h"
+#include "polyclip.h"
+
+
+#undef CORRECT_NORMAL_MATRIX
+#ifdef CORRECT_NORMAL_MATRIX
+#include <cgmath/cgmath.h>
+#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; i<G3D_NUM_MATRICES; i++) {
+ g3d_matrix_mode(i);
+ g3d_load_identity();
+ }
+
+ for(i=0; i<MAX_LIGHTS; i++) {
+ g3d_light_dir(i, 0, 0, 1);
+ g3d_light_color(i, 1, 1, 1);
+ }
+ g3d_light_ambient(0.1, 0.1, 0.1);
+
+ g3d_mtl_diffuse(1, 1, 1);
+
+ st->clear_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; j<nfaces; j++) {
+ vnum = prim; /* reset vnum for each iteration */
+
+ for(i=0; i<vnum; i++) {
+ v[i] = iarr ? varr[*iarr++] : *varr++;
+
+ xform4_vec3(st->mat[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; i<vnum; i++) {
+ if(v[i].w != 0.0f) {
+ v[i].x /= v[i].w;
+ v[i].y /= v[i].w;
+#ifdef ENABLE_ZBUFFER
+ if(st->opt & 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; i<MAX_LIGHTS; i++) {
+ float ldir[3];
+ float ndotl;
+
+ if(!(st->opt & (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 */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#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; i<zsort_cls.prim; i++) {
+ za += m[2] * va->x + 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; i<zsort_cls.prim; i++) {
+ const struct g3d_vertex *va = zsort_cls.varr + a[i];
+ const struct g3d_vertex *vb = zsort_cls.varr + b[i];
+
+ za += m[2] * va->x + 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; i<mesh->vcount; 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; i<mb->icount; 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; i<vcount; i++) {
+ if(cmp_vertex(v, varr++) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int indexify_mesh(struct g3d_mesh *mesh)
+{
+ int i, j, nfaces, max_icount, idx;
+ int out_vcount = 0;
+ struct g3d_vertex *vin, *vout;
+ uint16_t *iout;
+
+ if(mesh->iarr) {
+ 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; i<nfaces; i++) {
+ for(j=0; j<mesh->prim; 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; i<mesh->vcount; 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; i<mesh->vcount; 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; i<uverts; i++) {
+ float u = (float)i / (float)(uverts - 1);
+ float theta = u * 2.0 * M_PI;
+
+ for(j=0; j<vverts; j++) {
+ float v = (float)j / (float)(vverts - 1);
+ float phi = v * M_PI;
+ int chess = (i & 1) == (j & 1);
+
+ sphvec(&vptr->x, 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; i<vverts; i++) {
+ y = (v - 0.5) * height;
+ u = 0.0f;
+
+ for(j=0; j<uverts; j++) {
+ x = (u - 0.5) * width;
+
+ vptr->x = 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<vsub; i++) {
+ for(j=0; j<usub; j++) {
+ int idx = i * uverts + j;
+ *iptr++ = idx;
+ *iptr++ = idx + 1;
+ *iptr++ = idx + uverts + 1;
+ *iptr++ = idx + uverts;
+ }
+ }
+ return 0;
+}
+
+int gen_cube_mesh(struct g3d_mesh *mesh, float sz, int sub)
+{
+ int i;
+ float offs;
+ struct g3d_mesh *m;
+ struct g3d_mesh tmpmesh;
+ static float rotface[][4] = {
+ {0, 0, 1, 0},
+ {90, 0, 1, 0},
+ {180, 0, 1, 0},
+ {270, 0, 1, 0},
+ {90, 1, 0, 0},
+ {-90, 1, 0, 0}
+ };
+
+ offs = sz;
+ sz = fabs(sz);
+
+ g3d_matrix_mode(G3D_MODELVIEW);
+ g3d_push_matrix();
+
+ for(i=0; i<6; i++) {
+ m = 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; i<uverts; i++) {
+ float u = (float)i / (float)(uverts - 1);
+ float theta = u * 2.0 * M_PI;
+ float rcent[3];
+
+ torusvec(rcent, theta, 0, rad, 0);
+
+ for(j=0; j<vverts; j++) {
+ float v = (float)j / (float)(vverts - 1);
+ float phi = v * 2.0 * M_PI;
+ int chess = (i & 1) == (j & 1);
+
+ torusvec(&vptr->x, 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;
+}
+
--- /dev/null
+#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_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#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; i<dynarr_size(meshes); i++) {
+ if(i == idx) continue;
+ free_mesh(meshes + i);
+ }
+ dynarr_free(meshes);
+
+ if(mesh->prim) {
+ 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; i<dynarr_size(meshes); i++) {
+ if(mesh->name && 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; i<mesh->vcount; 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; i<mesh->vcount; i++) {
+ fprintf(fp, "vn %f %f %f\n", mesh->varr[i].nx, mesh->varr[i].ny, mesh->varr[i].nz);
+ }
+ for(i=0; i<mesh->vcount; 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; i<nverts; i++) {
+ idx = (mesh->iarr ? 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; i<dynarr_size(meshes); i++) {
+ if(meshes->name && 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);
+}
--- /dev/null
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+#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; i++) {
+ nextidx = i + 1;
+ if(nextidx >= 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; i++) {
+ nextidx = i + 1;
+ if(nextidx >= 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; i++) {
+ nextidx = i + 1;
+ if(nextidx >= 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;
+}
--- /dev/null
+#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_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#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
--- /dev/null
+#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_ */
--- /dev/null
+#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
--- /dev/null
+#include <string.h>
+#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; i<num; i++) {
+ free_mesh(scn->meshes[i]);
+ }
+ darr_clear(scn->meshes);
+
+ num = darr_size(scn->mtls);
+ for(i=0; i<num; i++) {
+ if(scn->mtls[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; i<num; i++) {
+ scn_free_node(scn->nodes[i]);
+ }
+ darr_clear(scn->nodes);
+
+ num = darr_size(scn->anims);
+ for(i=0; i<num; i++) {
+ scn_free_anim(scn->anims[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; i<num; i++) {
+ scn_destroy_track(anim->tracks[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; i<num; i++) {
+ if(strcmp(scn->meshes[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; i<num; i++) {
+ if(strcmp(scn->mtls[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; i<num; i++) {
+ if(strcmp(scn->nodes[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; i<num; i++) {
+ if(strcmp(scn->anims[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; i<num; i++) {
+ mtl = malloc_nf(sizeof *mtl);
+ if(conv_goat3d_mtl(scn, mtl, goat3d_get_mtl(g, i)) != -1) {
+ scn_add_mtl(scn, mtl);
+ } else {
+ free(mtl);
+ }
+ }
+
+ count = nfaces = 0;
+ num = goat3d_get_mesh_count(g);
+ for(i=0; i<num; i++) {
+ mesh = malloc_nf(sizeof *mesh);
+ if(conv_goat3d_mesh(scn, mesh, goat3d_get_mesh(g, i)) != -1) {
+ scn_add_mesh(scn, mesh);
+ nfaces += mesh->icount / 3;
+ count++;
+ } else {
+ free(mesh);
+ }
+ }
+ printf(" - %ld meshes with %ld total polygons\n", count, nfaces);
+
+ num = goat3d_get_node_count(g);
+ for(i=0; i<num; i++) {
+ node = malloc_nf(sizeof *node);
+ if(conv_goat3d_node(scn, node, goat3d_get_node(g, i)) != -1) {
+ scn_add_node(scn, node);
+ } else {
+ free(node);
+ }
+ }
+
+ for(i=0; i<num; i++) {
+ link_goat3d_node(scn, scn->nodes[i], goat3d_get_node(g, i));
+ }
+
+ count = 0;
+ num = goat3d_get_anim_count(g);
+ for(i=0; i<num; i++) {
+ anim = malloc_nf(sizeof *anim);
+ if(conv_goat3d_anim(scn, anim, goat3d_get_anim(g, i)) != -1) {
+ scn_add_anim(scn, anim);
+ count++;
+ } else {
+ free(anim);
+ }
+ }
+ printf(" - %ld animations\n", count);
+
+ return 0;
+}
+
+int conv_goat3d_mesh(struct g3d_scene *scn, struct g3d_mesh *dstmesh, struct goat3d_mesh *srcmesh)
+{
+ int i, nverts, nfaces;
+ struct g3d_vertex *vdst;
+ float *vsrc;
+ int *idxsrc;
+ uint16_t *idxdst;
+ struct goat3d_material *gmtl;
+
+ nverts = goat3d_get_mesh_vertex_count(srcmesh);
+ nfaces = goat3d_get_mesh_face_count(srcmesh);
+ if(!(nverts | nfaces)) return -1;
+
+ init_mesh(dstmesh, G3D_TRIANGLES, nverts, nfaces * 3);
+ dstmesh->name = strdup_nf(goat3d_get_mesh_name(srcmesh));
+ vdst = dstmesh->varr;
+ idxdst = dstmesh->iarr;
+
+ for(i=0; i<nverts; i++) {
+ vsrc = goat3d_get_mesh_attrib(srcmesh, GOAT3D_MESH_ATTR_VERTEX, i);
+ vdst->x = 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; i<nfaces; i++) {
+ idxsrc = goat3d_get_mesh_face(srcmesh, i);
+ idxdst[0] = idxsrc[0];
+ idxdst[1] = idxsrc[1];
+ idxdst[2] = idxsrc[2];
+ idxdst += 3;
+ }
+
+ if((gmtl = goat3d_get_mesh_mtl(srcmesh))) {
+ dstmesh->mtl = 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; i<num; i++) {
+ gnode = goat3d_get_node_child(srcnode, i);
+ if((node = scn_find_node(scn, goat3d_get_node_name(gnode)))) {
+ node->next = 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; i<num; i++) {
+ gtrk = goat3d_get_anim_track(srcanim, i);
+
+ trk = malloc_nf(sizeof *trk);
+ if(conv_goat3d_track(scn, trk, gtrk) != -1) {
+ darr_push(dstanim->tracks, &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; i<num; i++) {
+ goat3d_get_track_key(srctrk, i, &key);
+ goat3d_set_track_key(dsttrk->trk, &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; i<darr_size(scn->anims); i++) {
+ anim = scn->anims[i];
+ for(j=0; j<darr_size(anim->tracks); 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; i<num; i++) {
+ trk = anim->tracks[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; i<num; i++) {
+ draw_mesh(scn->meshes[i]);
+ }
+ } else {
+ num = darr_size(scn->nodes);
+ for(i=0; i<num; i++) {
+ node = scn->nodes[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();
+}
--- /dev/null
+#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_ */