initial broken commit master
authorJohn Tsiombikas <nuclear@member.fsf.org>
Fri, 1 Nov 2024 00:36:09 +0000 (02:36 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Fri, 1 Nov 2024 00:36:09 +0000 (02:36 +0200)
17 files changed:
.gitignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
examples/Makefile [new file with mode: 0644]
include/3dgfx.h [new file with mode: 0644]
src/3dgfx.c [new file with mode: 0644]
src/mesh.c [new file with mode: 0644]
src/mesh.h [new file with mode: 0644]
src/meshload.c [new file with mode: 0644]
src/polyclip.c [new file with mode: 0644]
src/polyclip.h [new file with mode: 0644]
src/polyfill.c [new file with mode: 0644]
src/polyfill.h [new file with mode: 0644]
src/polytmpl.h [new file with mode: 0644]
src/scene.c [new file with mode: 0644]
src/scene.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..48f0e47
--- /dev/null
@@ -0,0 +1,6 @@
+*.o
+*.d
+*.swp
+*.a
+*.so*
+*.obj
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..f288702
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <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>.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..5b80979
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+src = $(wildcard src/*.c)
+obj = $(src:.c=.o)
+dep = $(src:.c=.d)
+
+name = retro3d
+alib = lib$(name).a
+solib = lib$(name).so.$(somajor).$(sominor)
+soname = lib$(name).so.$(somajor)
+ldname = lib$(name).so
+
+warn = -pedantic -Wall
+opt = -O3 -ffast-math
+dbg = -g
+inc = -Iinclude
+
+CFLAGS = $(warn) $(dbg) $(opt) $(inc) -MMD
+LDFLAGS =
+
+.PHONY: all
+all: $(solib) $(alib)
+
+$(alib): $(obj)
+       $(AR) rcs $@ $(obj)
+
+$(solib): $(obj)
+       $(CC) -o $@ -shared -Wl,-soname,$(soname)
+
+-include $(dep)
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(alib) $(solib)
+
+.PHONY: cleandep
+cleandep:
+       rm -f $(dep)
+
+.PHONY: examples
+examples:
+       $(MAKE) -C examples
+
+.PHONY: clean-examples
+clean-examples:
+       $(MAKE) -C examples clean
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..e01e01a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+Retro 3D software renderer
+==========================
+
+Retro 3D is a library for rendering real-time 3D graphics on retro platforms.
+Retro platforms differ quite a lot, so there are multiple implementations of the
+same API, targetting different eras of computing. The main differentiation is
+between systems with a floating point unit (like mid-90s pentium PCs), and
+systems without a floating point unit, or with an unusably slow FPU, like an
+early 90s 386 PC, or something like a Gameboy Advance.
+
+License
+-------
+Copyright (C) 2024 John Tsiombikas <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.
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644 (file)
index 0000000..0e79b63
--- /dev/null
@@ -0,0 +1,7 @@
+.PHONY: all
+all:
+       echo all
+
+.PHONY: clean
+clean:
+       echo foo
diff --git a/include/3dgfx.h b/include/3dgfx.h
new file mode 100644 (file)
index 0000000..c9f60af
--- /dev/null
@@ -0,0 +1,163 @@
+#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_ */
diff --git a/src/3dgfx.c b/src/3dgfx.c
new file mode 100644 (file)
index 0000000..353b5fb
--- /dev/null
@@ -0,0 +1,935 @@
+#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 */
diff --git a/src/mesh.c b/src/mesh.c
new file mode 100644 (file)
index 0000000..3dbfbc1
--- /dev/null
@@ -0,0 +1,608 @@
+#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;
+}
+
diff --git a/src/mesh.h b/src/mesh.h
new file mode 100644 (file)
index 0000000..fb1b921
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef MESH_H_
+#define MESH_H_
+
+#include "3dgfx.h"
+#include "image.h"
+#include "inttypes.h"
+
+struct g3d_material {
+       float r, g, b, a;
+       float sr, sg, sb, shin;
+
+       struct image *texmap, *envmap;
+       char *name;
+};
+
+struct g3d_mesh {
+       int prim;
+       struct g3d_vertex *varr;
+       uint16_t *iarr;
+       int vcount, icount;
+       char *name;
+
+       struct g3d_material *mtl;
+};
+
+void init_g3dmtl(struct g3d_material *mtl);
+
+int init_mesh(struct g3d_mesh *mesh, int prim, int num_verts, int num_idx);
+
+void free_mesh(struct g3d_mesh *mesh);
+void destroy_mesh(struct g3d_mesh *mesh);
+
+int copy_mesh(struct g3d_mesh *dest, struct g3d_mesh *src);
+
+/* takes pointer to a dynamic array (dynarr_*) and populates it */
+#define load_meshes(mesharr, fname) load_meshes_impl(&(mesharr), fname)
+int load_meshes_impl(struct g3d_mesh **mesh, const char *fname);
+/* TODO: idx -1 -> merge all meshes into one? */
+int load_mesh(struct g3d_mesh *mesh, const char *fname, int idx);
+int load_named_mesh(struct g3d_mesh *mesh, const char *fname, const char *mname);
+int save_mesh(struct g3d_mesh *mesh, const char *fname);
+struct g3d_mesh *find_mesh(struct g3d_mesh *meshes, const char *mname);
+
+void zsort_mesh(struct g3d_mesh *mesh);
+void draw_mesh(struct g3d_mesh *mesh);
+
+void apply_mesh_xform(struct g3d_mesh *mesh, const float *xform);
+int append_mesh(struct g3d_mesh *ma, struct g3d_mesh *mb);
+int indexify_mesh(struct g3d_mesh *mesh);
+
+void normalize_mesh_normals(struct g3d_mesh *mesh);
+
+void calc_mesh_centroid(struct g3d_mesh *mesh, float *cent);
+
+int gen_sphere_mesh(struct g3d_mesh *mesh, float rad, int usub, int vsub);
+int gen_plane_mesh(struct g3d_mesh *mesh, float width, float height, int usub, int vsub);
+int gen_cube_mesh(struct g3d_mesh *mesh, float sz, int sub);
+int gen_torus_mesh(struct g3d_mesh *mesh, float rad, float ringrad, int usub, int vsub);
+
+#endif /* MESH_H_ */
diff --git a/src/meshload.c b/src/meshload.c
new file mode 100644 (file)
index 0000000..cd7f94f
--- /dev/null
@@ -0,0 +1,464 @@
+#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);
+}
diff --git a/src/polyclip.c b/src/polyclip.c
new file mode 100644 (file)
index 0000000..d4b1c52
--- /dev/null
@@ -0,0 +1,331 @@
+#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;
+}
diff --git a/src/polyclip.h b/src/polyclip.h
new file mode 100644 (file)
index 0000000..adee29d
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef POLYCLIP_H_
+#define POLYCLIP_H_
+
+#include "3dgfx.h"
+
+struct cplane {
+       float x, y, z;
+       float nx, ny, nz;
+};
+
+enum {
+       CLIP_LEFT, CLIP_RIGHT,
+       CLIP_BOTTOM, CLIP_TOP,
+       CLIP_NEAR, CLIP_FAR
+};
+
+/* Generic polygon clipper
+ * returns:
+ *  1 -> fully inside, not clipped
+ *  0 -> straddling the plane and clipped
+ * -1 -> fully outside, not clipped
+ * in all cases, vertices are copied to vout, and the vertex count is written
+ * to wherever voutnum is pointing
+ */
+int clip_poly(struct g3d_vertex *vout, int *voutnum,
+               const struct g3d_vertex *vin, int vnum, struct cplane *plane);
+
+/* only checks if the polygon would be clipped by the plane, and classifies it
+ * as inside/outside/straddling, without actually producing a clipped polygon.
+ * return values are the same as clip_poly.
+ */
+int check_clip_poly(const struct g3d_vertex *v, int vnum, struct cplane *plane);
+
+/* Special-case frustum clipper (might be slightly faster) */
+int clip_frustum(struct g3d_vertex *vout, int *voutnum,
+               const struct g3d_vertex *vin, int vnum, int fplane);
+
+#endif /* POLYCLIP_H_ */
diff --git a/src/polyfill.c b/src/polyfill.c
new file mode 100644 (file)
index 0000000..4ea2316
--- /dev/null
@@ -0,0 +1,455 @@
+#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
diff --git a/src/polyfill.h b/src/polyfill.h
new file mode 100644 (file)
index 0000000..ec34675
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef POLYFILL_H_
+#define POLYFILL_H_
+
+#include "inttypes.h"
+#include "3dgfx.h"
+
+#define POLYFILL_MODE_MASK     0x03
+#define POLYFILL_TEX_MASK      0x0c
+#define POLYFILL_TEXMODE_SHIFT 2
+#define POLYFILL_ALPHA_BIT     0x10
+#define POLYFILL_ADD_BIT       0x20
+#define POLYFILL_ZBUF_BIT      0x40
+
+enum {
+       POLYFILL_WIRE,
+       POLYFILL_FLAT,
+       POLYFILL_GOURAUD
+};
+
+enum {
+       POLYFILL_NOTEX,
+       POLYFILL_TEXMOD,
+       POLYFILL_TEXADD,
+       POLYFILL_TEXREPL
+};
+
+/* projected vertices for the rasterizer */
+struct pvertex {
+       int32_t x, y; /* 24.8 fixed point */
+       int32_t u, v; /* 16.16 fixed point */
+       int32_t r, g, b, a;  /* int 0-255 */
+       int32_t z;      /* 0-(2^24-1) */
+};
+
+struct pgradient {
+       int32_t dudx, dudy, dvdx, dvdy;
+       int32_t drdx, drdy, dgdx, dgdy, dbdx, dbdy, dadx, dady;
+       int32_t dzdx, dzdy;
+};
+
+struct pimage {
+       g3d_pixel *pixels;
+       int width, height;
+
+       int xshift, yshift;
+       unsigned int xmask, ymask;
+};
+
+extern struct pimage pfill_fb;
+extern struct pimage pfill_tex;
+extern uint32_t *pfill_zbuf;
+extern struct pgradient pgrad;
+
+void polyfill_fbheight(int height);
+
+void polyfill(int mode, struct pvertex *verts);
+
+void polyfill_wire(struct pvertex *verts);
+void polyfill_flat(struct pvertex *verts);
+void polyfill_gour(struct pvertex *verts);
+void polyfill_tex_wire(struct pvertex *verts);
+void polyfill_tex_flat(struct pvertex *verts);
+void polyfill_tex_gour(struct pvertex *verts);
+void polyfill_alpha_wire(struct pvertex *verts);
+void polyfill_alpha_flat(struct pvertex *verts);
+void polyfill_alpha_gour(struct pvertex *verts);
+void polyfill_alpha_tex_wire(struct pvertex *verts);
+void polyfill_alpha_tex_flat(struct pvertex *verts);
+void polyfill_alpha_tex_gour(struct pvertex *verts);
+void polyfill_add_wire(struct pvertex *verts);
+void polyfill_add_flat(struct pvertex *verts);
+void polyfill_add_gour(struct pvertex *verts);
+void polyfill_add_tex_wire(struct pvertex *verts);
+void polyfill_add_tex_flat(struct pvertex *verts);
+void polyfill_add_tex_gour(struct pvertex *verts);
+void polyfill_flat_zbuf(struct pvertex *verts);
+void polyfill_gour_zbuf(struct pvertex *verts);
+void polyfill_tex_flat_zbuf(struct pvertex *verts);
+void polyfill_tex_gour_zbuf(struct pvertex *verts);
+void polyfill_alpha_flat_zbuf(struct pvertex *verts);
+void polyfill_alpha_gour_zbuf(struct pvertex *verts);
+void polyfill_alpha_tex_flat_zbuf(struct pvertex *verts);
+void polyfill_alpha_tex_gour_zbuf(struct pvertex *verts);
+void polyfill_add_flat_zbuf(struct pvertex *verts);
+void polyfill_add_gour_zbuf(struct pvertex *verts);
+void polyfill_add_tex_flat_zbuf(struct pvertex *verts);
+void polyfill_add_tex_gour_zbuf(struct pvertex *verts);
+
+void polyfill_addtex_flat(struct pvertex *verts);
+void polyfill_addtex_gour(struct pvertex *verts);
+void polyfill_addtex_flat_zbuf(struct pvertex *verts);
+void polyfill_addtex_gour_zbuf(struct pvertex *verts);
+
+void polyfill_repltex_flat(struct pvertex *verts);
+void polyfill_repltex_flat_zbuf(struct pvertex *verts);
+
+#endif /* POLYFILL_H_ */
diff --git a/src/polytmpl.h b/src/polytmpl.h
new file mode 100644 (file)
index 0000000..a00ad42
--- /dev/null
@@ -0,0 +1,314 @@
+#ifdef _MSC_VER
+#pragma warning (disable: 4101)
+#endif
+
+#if !defined(GOURAUD) && !defined(TEXMAP) && !defined(ZBUF)
+#define NOLERP
+#endif
+
+void POLYFILL(struct pvertex *varr)
+{
+       int i, line, top, bot;
+       struct pvertex *v, *vn, *tab;
+       int32_t x, y0, y1, dx, dy, slope, fx, fy;
+       int start, len;
+       g3d_pixel *fbptr, *pptr, color;
+#ifdef GOURAUD
+       int32_t r, g, b, dr, dg, db, rslope, gslope, bslope;
+#ifdef BLEND_ALPHA
+       int32_t a, da, aslope;
+#endif
+#endif /* GOURAUD */
+#ifdef TEXMAP
+       int32_t tu, tv, du, dv, uslope, vslope;
+       int tx, ty;
+       g3d_pixel texel;
+#endif
+#ifdef ZBUF
+       int32_t z, dz, zslope;
+       uint32_t *zptr;
+#endif
+
+#if !defined(GOURAUD)
+       /* for flat shading we already know the color, so pack it once */
+       color = G3D_PACK_RGB(varr[0].r, varr[0].g, varr[0].b);
+#endif
+
+       top = pfill_fb.height;
+       bot = 0;
+
+       for(i=0; i<3; i++) {
+               /* scan the edge between the current and next vertex */
+               v = varr + i;
+               vn = VNEXT(v);
+
+               if(vn->y == v->y) continue;     /* XXX ??? */
+
+               if(vn->y >= v->y) {
+                       /* inrementing Y: left side */
+                       tab = left;
+               } else {
+                       /* decrementing Y: right side, flip vertices to trace bottom->up */
+                       tab = right;
+                       v = vn;
+                       vn = varr + i;
+               }
+
+               /* calculate edge slope */
+               dx = vn->x - v->x;
+               dy = vn->y - v->y;
+               slope = (dx << 8) / dy;
+
+#ifdef GOURAUD
+               r = v->r << COLOR_SHIFT;
+               g = v->g << COLOR_SHIFT;
+               b = v->b << COLOR_SHIFT;
+               dr = (vn->r << COLOR_SHIFT) - r;
+               dg = (vn->g << COLOR_SHIFT) - g;
+               db = (vn->b << COLOR_SHIFT) - b;
+               rslope = (dr << 8) / dy;
+               gslope = (dg << 8) / dy;
+               bslope = (db << 8) / dy;
+#ifdef BLEND_ALPHA
+               a = v->a << COLOR_SHIFT;
+               da = (vn->a << COLOR_SHIFT) - a;
+               aslope = (da << 8) / dy;
+#endif /* BLEND_ALPHA */
+#endif /* GOURAUD */
+#ifdef TEXMAP
+               tu = v->u;
+               tv = v->v;
+               du = vn->u - tu;
+               dv = vn->v - tv;
+               uslope = (du << 8) / dy;
+               vslope = (dv << 8) / dy;
+#endif /* TEXMAP */
+#ifdef ZBUF
+               z = v->z;
+               dz = vn->z - z;
+               zslope = (dz << 8) / dy;
+#endif /* ZBUF */
+
+               y0 = (v->y + 0x100) & 0xffffff00;       /* start from the next scanline */
+               fy = y0 - v->y;                                         /* fractional part before the next scanline */
+               fx = (fy * slope) >> 8;                         /* X adjust for the step to the next scanline */
+               x = v->x + fx;                                          /* adjust X */
+               y1 = vn->y & 0xffffff00;                        /* last scanline of the edge <= vn->y */
+
+               /* also adjust other interpolated attributes */
+#ifdef GOURAUD
+               r += (fy * rslope) >> 8;
+               g += (fy * gslope) >> 8;
+               b += (fy * bslope) >> 8;
+#ifdef BLEND_ALPHA
+               a += (fy * aslope) >> 8;
+#endif /* BLEND_ALPHA */
+#endif /* GOURAUD */
+#ifdef TEXMAP
+#ifdef FLTUV
+               tu += uslope * (fy / 256.0f);
+               tv += vslope * (fy / 256.0f);
+#else
+               tu += (fy * uslope) >> 8;
+               tv += (fy * vslope) >> 8;
+#endif
+#endif /* TEXMAP */
+#ifdef ZBUF
+               z += (fy * zslope) >> 8;
+#endif
+
+               line = y0 >> 8;
+               if(line < top) top = line;
+               if((y1 >> 8) > bot) bot = y1 >> 8;
+
+               if(line > 0) tab += line;
+
+               while(line <= (y1 >> 8) && line < pfill_fb.height) {
+                       if(line >= 0) {
+                               int val = x < 0 ? 0 : x >> 8;
+                               tab->x = val < pfill_fb.width ? val : pfill_fb.width - 1;
+#ifdef GOURAUD
+                               tab->r = r;
+                               tab->g = g;
+                               tab->b = b;
+#ifdef BLEND_ALPHA
+                               tab->a = a;
+#endif /* BLEND_ALPHA */
+#endif /* GOURAUD */
+#ifdef TEXMAP
+                               tab->u = tu;
+                               tab->v = tv;
+#endif /* TEXMAP */
+#ifdef ZBUF
+                               tab->z = z;
+#endif
+                               tab++;
+                       }
+                       x += slope;
+#ifdef GOURAUD
+                       r += rslope;
+                       g += gslope;
+                       b += bslope;
+#ifdef BLEND_ALPHA
+                       a += aslope;
+#endif /* BLEND_ALPHA */
+#endif /* GOURAUD */
+#ifdef TEXMAP
+                       tu += uslope;
+                       tv += vslope;
+#endif /* TEXMAP */
+#ifdef ZBUF
+                       z += zslope;
+#endif /* ZBUF */
+                       line++;
+               }
+       }
+
+       if(top < 0) top = 0;
+       if(bot >= pfill_fb.height) bot = pfill_fb.height - 1;
+
+       fbptr = pfill_fb.pixels + top * pfill_fb.width;
+       for(i=top; i<=bot; i++) {
+               start = left[i].x;
+               len = right[i].x - start;
+               /* XXX we probably need more precision in left/right.x */
+
+#ifndef NOLERP
+               dx = len == 0 ? 256 : (len << 8);
+#endif
+
+#ifdef GOURAUD
+               r = left[i].r;
+               g = left[i].g;
+               b = left[i].b;
+#ifdef BLEND_ALPHA
+               a = left[i].a;
+#endif /* BLEND_ALPHA */
+#endif /* GOURAUD */
+#ifdef TEXMAP
+               tu = left[i].u;
+               tv = left[i].v;
+#endif /* TEXMAP */
+#ifdef ZBUF
+               z = left[i].z;
+               zptr = pfill_zbuf + i * pfill_fb.width + start;
+#endif /* ZBUF */
+
+               pptr = fbptr + start;
+               while(len-- > 0) {
+#if defined(GOURAUD) || defined(TEXMAP) || defined(BLEND_ALPHA) || defined(BLEND_ADD)
+                       int cr, cg, cb;
+#endif
+#if defined(BLEND_ALPHA) || defined(BLEND_ADD)
+                       g3d_pixel fbcol;
+#endif
+#ifdef BLEND_ALPHA
+                       int alpha, inv_alpha;
+#endif
+#ifdef ZBUF
+                       uint32_t cz = z;
+                       z += pgrad.dzdx;
+
+                       if(cz <= *zptr) {
+                               *zptr++ = cz;
+                       } else {
+                               /* ZFAIL: advance all attributes and continue */
+#ifdef GOURAUD
+                               r += pgrad.drdx;
+                               g += pgrad.dgdx;
+                               b += pgrad.dbdx;
+#ifdef BLEND_ALPHA
+                               a += pgrad.dadx;
+#endif
+#endif /* GOURAUD */
+#ifdef TEXMAP
+                               tu += pgrad.dudx;
+                               tv += pgrad.dvdx;
+#endif /* TEXMAP */
+                               /* skip pixel */
+                               pptr++;
+                               zptr++;
+                               continue;
+                       }
+#endif /* ZBUF */
+
+#ifdef GOURAUD
+                       /* we upped the color precision to while interpolating the
+                        * edges, now drop the extra bits before packing
+                        */
+                       cr = r < 0 ? 0 : (r >> COLOR_SHIFT);
+                       cg = g < 0 ? 0 : (g >> COLOR_SHIFT);
+                       cb = b < 0 ? 0 : (b >> COLOR_SHIFT);
+                       r += pgrad.drdx;
+                       g += pgrad.dgdx;
+                       b += pgrad.dbdx;
+#endif /* GOURAUD */
+#ifdef TEXMAP
+                       tx = (tu >> (16 - pfill_tex.xshift)) & pfill_tex.xmask;
+                       ty = (tv >> (16 - pfill_tex.yshift)) & pfill_tex.ymask;
+                       texel = pfill_tex.pixels[(ty << pfill_tex.xshift) + tx];
+
+                       tu += pgrad.dudx;
+                       tv += pgrad.dvdx;
+
+#ifndef GOURAUD
+                       /* for flat textured, cr,cg,cb would not be initialized */
+                       cr = varr[0].r;
+                       cg = varr[0].g;
+                       cb = varr[0].b;
+#endif /* !GOURAUD */
+                       /* This is not correct, should be /255, but it's much faster
+                        * to shift by 8 (/256), and won't make a huge difference
+                        */
+#ifdef TEX_ADD
+                       cr = cr + G3D_UNPACK_R(texel);
+                       cg = cg + G3D_UNPACK_G(texel);
+                       cb = cb + G3D_UNPACK_B(texel);
+#elif defined(TEX_REPL)
+                       color = texel;
+#else
+                       cr = (cr * G3D_UNPACK_R(texel)) >> 8;
+                       cg = (cg * G3D_UNPACK_G(texel)) >> 8;
+                       cb = (cb * G3D_UNPACK_B(texel)) >> 8;
+#endif
+
+#endif /* TEXMAP */
+
+#ifdef BLEND_ALPHA
+#ifdef GOURAUD
+                       alpha = a >> COLOR_SHIFT;
+                       inv_alpha = 255 - alpha;
+                       a += pgrad.dadx;
+#else
+                       alpha = varr[0].a;
+#endif
+                       inv_alpha = 255 - alpha;
+                       fbcol = *pptr;
+                       cr = (cr * alpha + G3D_UNPACK_R(fbcol) * inv_alpha) >> 8;
+                       cg = (cg * alpha + G3D_UNPACK_G(fbcol) * inv_alpha) >> 8;
+                       cb = (cb * alpha + G3D_UNPACK_B(fbcol) * inv_alpha) >> 8;
+#endif /* BLEND_ALPHA */
+#ifdef BLEND_ADD
+                       fbcol = *pptr;
+                       cr += G3D_UNPACK_R(fbcol);
+                       cg += G3D_UNPACK_G(fbcol);
+                       cb += G3D_UNPACK_B(fbcol);
+#endif /* BLEND_ADD */
+
+#ifdef DEBUG_OVERDRAW
+                       *pptr++ += DEBUG_OVERDRAW;
+#else
+#if defined(GOURAUD) || (defined(TEXMAP) && !defined(TEX_REPL)) || defined(BLEND_ALPHA) || defined(BLEND_ADD)
+                       if(cr >= 255) cr = 255;
+                       if(cg >= 255) cg = 255;
+                       if(cb >= 255) cb = 255;
+                       color = G3D_PACK_RGB(cr, cg, cb);
+#endif
+                       *pptr++ = color;
+#endif
+               }
+               fbptr += pfill_fb.width;
+       }
+}
+
+#undef NOLERP
diff --git a/src/scene.c b/src/scene.c
new file mode 100644 (file)
index 0000000..fb6980a
--- /dev/null
@@ -0,0 +1,637 @@
+#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();
+}
diff --git a/src/scene.h b/src/scene.h
new file mode 100644 (file)
index 0000000..3e11e43
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef SCENE_H_
+#define SCENE_H_
+
+#include "cgmath/cgmath.h"
+#include "mesh.h"
+#include "goat3d.h"
+
+struct g3d_node {
+       char *name;
+       struct g3d_mesh *mesh;
+       struct g3d_node *parent;
+       struct g3d_node *child, *next;
+
+       cgm_vec3 pos;
+       cgm_quat rot;
+       cgm_vec3 scale;
+       cgm_vec3 pivot;
+
+       float xform[16];
+       int xform_valid;
+};
+
+struct g3d_track {
+       struct g3d_node *node;
+       struct goat3d_track *trk;
+};
+
+struct g3d_anim {
+       char *name;
+       struct g3d_track **tracks;      /* darray */
+       long start, end, dur;
+};
+
+struct g3d_scene {
+       /* dynamic arrays (darray) */
+       struct g3d_mesh **meshes;
+       struct g3d_node **nodes;
+       struct g3d_anim **anims;
+       struct g3d_material **mtls;
+
+       char *texpath;
+};
+
+void scn_init(struct g3d_scene *scn);
+void scn_destroy(struct g3d_scene *scn);
+
+struct g3d_scene *scn_create(void);
+void scn_free(struct g3d_scene *scn);
+
+void scn_clear(struct g3d_scene *scn);
+
+void scn_init_node(struct g3d_node *node);
+void scn_destroy_node(struct g3d_node *node);
+void scn_free_node(struct g3d_node *node);
+
+void scn_init_anim(struct g3d_anim *anim);
+void scn_destroy_anim(struct g3d_anim *anim);
+void scn_free_anim(struct g3d_anim *anim);
+
+void scn_init_track(struct g3d_track *track);
+void scn_destroy_track(struct g3d_track *track);
+
+void scn_add_mesh(struct g3d_scene *scn, struct g3d_mesh *mesh);
+void scn_add_mtl(struct g3d_scene *scn, struct g3d_material *mtl);
+void scn_add_node(struct g3d_scene *scn, struct g3d_node *node);
+void scn_add_anim(struct g3d_scene *scn, struct g3d_anim *anim);
+
+int scn_mesh_count(struct g3d_scene *scn);
+int scn_mtl_count(struct g3d_scene *scn);
+int scn_node_count(struct g3d_scene *scn);
+int scn_anim_count(struct g3d_scene *scn);
+
+struct g3d_mesh *scn_find_mesh(struct g3d_scene *scn, const char *name);
+struct g3d_material *scn_find_mtl(struct g3d_scene *scn, const char *name);
+struct g3d_node *scn_find_node(struct g3d_scene *scn, const char *name);
+struct g3d_anim *scn_find_anim(struct g3d_scene *scn, const char *name);
+
+int conv_goat3d_scene(struct g3d_scene *scn, struct goat3d *g);
+int conv_goat3d_mesh(struct g3d_scene *scn, struct g3d_mesh *dstmesh, struct goat3d_mesh *srcmesh);
+int conv_goat3d_mtl(struct g3d_scene *scn, struct g3d_material *destmtl, struct goat3d_material *srcmtl);
+int conv_goat3d_node(struct g3d_scene *scn, struct g3d_node *dstnode, struct goat3d_node *srcnode);
+int link_goat3d_node(struct g3d_scene *scn, struct g3d_node *dstnode, struct goat3d_node *srcnode);
+int conv_goat3d_anim(struct g3d_scene *scn, struct g3d_anim *dstanim, struct goat3d_anim *srcanim);
+int conv_goat3d_track(struct g3d_scene *scn, struct g3d_track *dsttrk, struct goat3d_track *srctrk);
+
+void scn_merge_anims(struct g3d_scene *scn);
+
+void scn_eval_anim(struct g3d_anim *anim, long tm);
+void scn_draw(struct g3d_scene *scn);
+void scn_draw_node(struct g3d_node *node);
+
+#endif /* SCENE_H_ */