added cgmath, libanim, and libpsys
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 26 Dec 2021 23:40:29 +0000 (01:40 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sun, 26 Dec 2021 23:40:29 +0000 (01:40 +0200)
32 files changed:
Makefile
Makefile.android
libs/Makefile
libs/anim/COPYING [new file with mode: 0644]
libs/anim/COPYING.LESSER [new file with mode: 0644]
libs/anim/Makefile [new file with mode: 0644]
libs/anim/README.md [new file with mode: 0644]
libs/anim/anim.c [new file with mode: 0644]
libs/anim/anim.h [new file with mode: 0644]
libs/anim/config.h [new file with mode: 0644]
libs/anim/dynarr.c [new file with mode: 0644]
libs/anim/dynarr.h [new file with mode: 0644]
libs/anim/track.c [new file with mode: 0644]
libs/anim/track.h [new file with mode: 0644]
libs/cgmath/LICENSE [new file with mode: 0644]
libs/cgmath/README.md [new file with mode: 0644]
libs/cgmath/cgmath.h [new file with mode: 0644]
libs/cgmath/cgmmat.inl [new file with mode: 0644]
libs/cgmath/cgmmisc.inl [new file with mode: 0644]
libs/cgmath/cgmquat.inl [new file with mode: 0644]
libs/cgmath/cgmray.inl [new file with mode: 0644]
libs/cgmath/cgmvec3.inl [new file with mode: 0644]
libs/cgmath/cgmvec4.inl [new file with mode: 0644]
libs/psys/Makefile [new file with mode: 0644]
libs/psys/pattr.c [new file with mode: 0644]
libs/psys/pattr.h [new file with mode: 0644]
libs/psys/pstrack.c [new file with mode: 0644]
libs/psys/pstrack.h [new file with mode: 0644]
libs/psys/psys.c [new file with mode: 0644]
libs/psys/psys.h [new file with mode: 0644]
libs/psys/rndval.c [new file with mode: 0644]
libs/psys/rndval.h [new file with mode: 0644]

index 718b7dd..12024ae 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,11 +7,11 @@ warn = -pedantic -Wall
 dbg = -g
 #opt = -O3 -ffast-math -fno-strict-aliasing
 def = -DMINIGLUT_USE_LIBC -DGLEW_STATIC
-incdir = -Isrc -Ilibs/imago/src -Ilibs/glew
+incdir = -Isrc -Ilibs -Ilibs/imago/src -Ilibs/glew
 libdir = -Llibs/unix
 
 CFLAGS = $(warn) $(dbg) $(opt) $(def) $(incdir) -fcommon -MMD
-LDFLAGS = $(libdir) $(libsys) $(libgl) -lm -limago
+LDFLAGS = $(libdir) $(libsys) $(libgl) -limago -lpsys -lanim $(libc)
 
 sys ?= $(shell uname -s | sed 's/MINGW.*/mingw/')
 ifeq ($(sys), mingw)
@@ -20,11 +20,13 @@ ifeq ($(sys), mingw)
        libgl = -lopengl32
        libsys = -lmingw32 -lgdi32 -lwinmm -mconsole
        libdir = -Llibs/w32
+       libc = -lm
 else
        libgl = -lGL -lX11 -lXext
+       libc = -lm -ldl
 endif
 
-$(bin): $(obj)
+$(bin): $(obj) Makefile
        $(CC) -o $@ $(obj) $(LDFLAGS)
 
 -include $(dep)
index f3b75eb..336d7c9 100644 (file)
@@ -20,12 +20,12 @@ warn = -pedantic -Wall
 dbg = -g
 opt = -O3 -ffast-math -fno-strict-aliasing
 def = -DGLDEF
-incdir = -Isrc -Ilibs/imago/src
+incdir = -Isrc -Ilibs -Ilibs/imago/src
 libdir = -Llibs/android
 
 CC = $(TC)clang
 CFLAGS = $(CCSYSROOT) $(ISYS) $(warn) $(dbg) $(opt) $(def) $(incdir) -fPIC -fcommon -MMD
-LDFLAGS = $(LDSYSROOT) $(libdir) -lm -landroid -llog -lEGL -lGLESv2 -limago
+LDFLAGS = $(LDSYSROOT) $(libdir) -lm -landroid -llog -lEGL -lGLESv2 -limago -lpsys -lanim
 
 $(name).apk: $(name).aligned.apk keystore.jks
        apksigner sign --ks keystore.jks --ks-key-alias androidkey --ks-pass pass:android --key-pass pass:android --out $@ $<
index ed4d4d9..0800384 100644 (file)
@@ -1,8 +1,9 @@
 .PHONY: all
-all: imago
+all: imago anim psys
 
 .PHONY: clean
-clean: clean-imago
+clean: clean-imago clean-anim clean-psys
+
 
 .PHONY: imago
 imago:
@@ -11,3 +12,19 @@ imago:
 .PHONY: clean-imago
 clean-imago:
        $(MAKE) -C imago clean
+
+.PHONY: anim
+anim:
+       $(MAKE) -C anim
+
+.PHONY: clean-anim
+clean-anim:
+       $(MAKE) -C anim clean
+
+.PHONY: psys
+psys:
+       $(MAKE) -C psys
+
+.PHONY: clean-psys
+clean-psys:
+       $(MAKE) -C psys clean
diff --git a/libs/anim/COPYING b/libs/anim/COPYING
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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
+<http://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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/libs/anim/COPYING.LESSER b/libs/anim/COPYING.LESSER
new file mode 100644 (file)
index 0000000..65c5ca8
--- /dev/null
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser 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
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/libs/anim/Makefile b/libs/anim/Makefile
new file mode 100644 (file)
index 0000000..7e142dd
--- /dev/null
@@ -0,0 +1,29 @@
+obj = anim.o track.o dynarr.o
+lib = ../unix/libanim.a
+
+sys ?= $(shell uname -s | sed 's/MINGW.*/mingw/')
+ifeq ($(sys), mingw)
+       obj = anim.w32.o track.w32.o dynarr.w32.o
+       lib = ../w32/libanim.a
+endif
+ifeq ($(sys), android-arm64)
+       obj = anim.arm64.o track.arm64.o dynarr.arm64.o
+       lib = ../android/libanim.a
+endif
+
+CFLAGS = -O3 -ffast-math -fno-strict-aliasing -I..
+
+$(lib): $(obj)
+
+$(lib): $(obj)
+       $(AR) rcs $@ $(obj)
+
+%.arm64.o: %.c
+       $(CC) -o $@ $(CFLAGS) -c $<
+
+%.w32.o: %.c
+       $(CC) -o $@ $(CFLAGS) -c $<
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(lib)
diff --git a/libs/anim/README.md b/libs/anim/README.md
new file mode 100644 (file)
index 0000000..ddcb2b9
--- /dev/null
@@ -0,0 +1,48 @@
+libanim
+=======
+
+About
+-----
+Libanim is a C animation library, which can be used as a generic framework to
+add keyframe interpolation tracks for any single-valued or 3-vector parameters,
+or at a slightly higher level as a hierarchical PRS (position/rotation/scaling)
+animation node framework for 3D graphics programs.
+
+Version 2 of libanim dropped the dependency to libvmath, and instead carries a
+copy of gph-cmath (https://github.com/jtsiomb/gph-cmath) internally. The API
+has been reworked to avoid forcing a dependency to any math library to the user
+program, relying on floats and float pointers instead, which can be aliased to
+any kind contiguous `x,y,z` vector and `x,y,z,w` quaternion, or simple arrays
+of floats. Matrix arguments are expected to be arrays of 16 contiguous floats,
+in OpenGL-compatible order.
+
+Programs written for earlier versions of libanim, and using the high-level PRS
+interface in `anim.h` are not source-compatible, nor binary-compatible with
+libanim 2. Though in practice the API changes are minor, and porting should be
+straightforward. Programs using only the low-level keyframe tracks in `track.h`
+are unaffected by these changes.
+
+License
+-------
+Copyright (C) 2012-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software. You may use, modify, and redistribute it under
+the terms of the GNU Lesser General Public License v3 or (at your option), any
+later version published by the Free Software Foundation. See COPYING and
+COPYING.LESSER for details.
+
+Build
+-----
+To build and install libanim on UNIX, run the usual:
+
+    ./configure
+    make
+    make install
+
+See `./configure --help` for a complete list of build-time options.
+
+To cross-compile for windows with mingw-w64, try the following incantation:
+
+    ./configure --prefix=/usr/i686-w64-mingw32
+    make CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar sys=mingw
+    make install sys=mingw
diff --git a/libs/anim/anim.c b/libs/anim/anim.c
new file mode 100644 (file)
index 0000000..06514e2
--- /dev/null
@@ -0,0 +1,1017 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include "anim.h"
+#include "dynarr.h"
+
+#include "cgmath/cgmath.h"
+
+#define ROT_USE_SLERP
+
+static void invalidate_cache(struct anm_node *node);
+
+int anm_init_animation(struct anm_animation *anim)
+{
+       int i, j;
+       static const float defaults[] = {
+               0.0f, 0.0f, 0.0f,               /* default position */
+               0.0f, 0.0f, 0.0f, 1.0f, /* default rotation quat */
+               1.0f, 1.0f, 1.0f                /* default scale factor */
+       };
+
+       anim->name = 0;
+
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               if(anm_init_track(anim->tracks + i) == -1) {
+                       for(j=0; j<i; j++) {
+                               anm_destroy_track(anim->tracks + i);
+                       }
+               }
+               anm_set_track_default(anim->tracks + i, defaults[i]);
+       }
+       return 0;
+}
+
+void anm_destroy_animation(struct anm_animation *anim)
+{
+       int i;
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               anm_destroy_track(anim->tracks + i);
+       }
+       free(anim->name);
+}
+
+void anm_set_animation_name(struct anm_animation *anim, const char *name)
+{
+       char *newname = malloc(strlen(name) + 1);
+       if(!newname) return;
+
+       strcpy(newname, name);
+
+       free(anim->name);
+       anim->name = newname;
+}
+
+/* ---- node implementation ----- */
+
+int anm_init_node(struct anm_node *node)
+{
+       memset(node, 0, sizeof *node);
+
+       node->cur_anim[1] = -1;
+
+       if(!(node->animations = anm_dynarr_alloc(1, sizeof *node->animations))) {
+               return -1;
+       }
+       if(anm_init_animation(node->animations) == -1) {
+               anm_dynarr_free(node->animations);
+               return -1;
+       }
+
+#ifdef ANIM_THREAD_SAFE
+       /* initialize thread-local matrix cache */
+       pthread_key_create(&node->cache_key, 0);
+       pthread_mutex_init(&node->cache_list_lock, 0);
+#endif
+
+       return 0;
+}
+
+void anm_destroy_node(struct anm_node *node)
+{
+       int i, num_anim;
+       free(node->name);
+
+       num_anim = anm_get_animation_count(node);
+       for(i=0; i<num_anim; i++) {
+               anm_destroy_animation(node->animations + i);
+       }
+       anm_dynarr_free(node->animations);
+
+#ifdef ANIM_THREAD_SAFE
+       /* destroy thread-specific cache */
+       pthread_key_delete(node->cache_key);
+
+       while(node->cache_list) {
+               struct mat_cache *tmp = node->cache_list;
+               node->cache_list = tmp->next;
+               free(tmp);
+       }
+#endif
+}
+
+void anm_destroy_node_tree(struct anm_node *tree)
+{
+       struct anm_node *c, *tmp;
+
+       if(!tree) return;
+
+       c = tree->child;
+       while(c) {
+               tmp = c;
+               c = c->next;
+
+               anm_destroy_node_tree(tmp);
+       }
+       anm_destroy_node(tree);
+}
+
+struct anm_node *anm_create_node(void)
+{
+       struct anm_node *n;
+
+       if((n = malloc(sizeof *n))) {
+               if(anm_init_node(n) == -1) {
+                       free(n);
+                       return 0;
+               }
+       }
+       return n;
+}
+
+void anm_free_node(struct anm_node *node)
+{
+       anm_destroy_node(node);
+       free(node);
+}
+
+void anm_free_node_tree(struct anm_node *tree)
+{
+       struct anm_node *c, *tmp;
+
+       if(!tree) return;
+
+       c = tree->child;
+       while(c) {
+               tmp = c;
+               c = c->next;
+
+               anm_free_node_tree(tmp);
+       }
+
+       anm_free_node(tree);
+}
+
+int anm_set_node_name(struct anm_node *node, const char *name)
+{
+       char *str;
+
+       if(!(str = malloc(strlen(name) + 1))) {
+               return -1;
+       }
+       strcpy(str, name);
+       free(node->name);
+       node->name = str;
+       return 0;
+}
+
+const char *anm_get_node_name(struct anm_node *node)
+{
+       return node->name ? node->name : "";
+}
+
+void anm_link_node(struct anm_node *p, struct anm_node *c)
+{
+       c->next = p->child;
+       p->child = c;
+
+       c->parent = p;
+       invalidate_cache(c);
+}
+
+int anm_unlink_node(struct anm_node *p, struct anm_node *c)
+{
+       struct anm_node *iter;
+
+       if(p->child == c) {
+               p->child = c->next;
+               c->next = 0;
+               invalidate_cache(c);
+               return 0;
+       }
+
+       iter = p->child;
+       while(iter->next) {
+               if(iter->next == c) {
+                       iter->next = c->next;
+                       c->next = 0;
+                       invalidate_cache(c);
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+void anm_set_pivot(struct anm_node *node, float x, float y, float z)
+{
+       node->pivot[0] = x;
+       node->pivot[1] = y;
+       node->pivot[2] = z;
+}
+
+void anm_get_pivot(struct anm_node *node, float *x, float *y, float *z)
+{
+       *x = node->pivot[0];
+       *y = node->pivot[1];
+       *z = node->pivot[2];
+}
+
+
+/* animation management */
+
+int anm_use_node_animation(struct anm_node *node, int aidx)
+{
+       if(aidx == node->cur_anim[0] && node->cur_anim[1] == -1) {
+               return 0;       /* no change, no invalidation */
+       }
+
+       if(aidx < 0 || aidx >= anm_get_animation_count(node)) {
+               return -1;
+       }
+
+       node->cur_anim[0] = aidx;
+       node->cur_anim[1] = -1;
+       node->cur_mix = 0;
+       node->blend_dur = -1;
+
+       invalidate_cache(node);
+       return 0;
+}
+
+int anm_use_node_animations(struct anm_node *node, int aidx, int bidx, float t)
+{
+       int num_anim;
+
+       if(node->cur_anim[0] == aidx && node->cur_anim[1] == bidx &&
+                       fabs(t - node->cur_mix) < 1e-6) {
+               return 0;       /* no change, no invalidation */
+       }
+
+       num_anim = anm_get_animation_count(node);
+       if(aidx < 0 || aidx >= num_anim) {
+               return anm_use_animation(node, bidx);
+       }
+       if(bidx < 0 || bidx >= num_anim) {
+               return anm_use_animation(node, aidx);
+       }
+       node->cur_anim[0] = aidx;
+       node->cur_anim[1] = bidx;
+       node->cur_mix = t;
+
+       invalidate_cache(node);
+       return 0;
+}
+
+int anm_use_animation(struct anm_node *node, int aidx)
+{
+       struct anm_node *child;
+
+       if(anm_use_node_animation(node, aidx) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_use_animation(child, aidx) == -1) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+}
+
+int anm_use_animations(struct anm_node *node, int aidx, int bidx, float t)
+{
+       struct anm_node *child;
+
+       if(anm_use_node_animations(node, aidx, bidx, t) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_use_animations(child, aidx, bidx, t) == -1) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+
+}
+
+void anm_set_node_animation_offset(struct anm_node *node, anm_time_t offs, int which)
+{
+       if(which < 0 || which >= 2) {
+               return;
+       }
+       node->cur_anim_offset[which] = offs;
+}
+
+anm_time_t anm_get_animation_offset(const struct anm_node *node, int which)
+{
+       if(which < 0 || which >= 2) {
+               return 0;
+       }
+       return node->cur_anim_offset[which];
+}
+
+void anm_set_animation_offset(struct anm_node *node, anm_time_t offs, int which)
+{
+       struct anm_node *c = node->child;
+       while(c) {
+               anm_set_animation_offset(c, offs, which);
+               c = c->next;
+       }
+
+       anm_set_node_animation_offset(node, offs, which);
+}
+
+int anm_get_active_animation_index(const struct anm_node *node, int which)
+{
+       if(which < 0 || which >= 2) return -1;
+       return node->cur_anim[which];
+}
+
+struct anm_animation *anm_get_active_animation(const struct anm_node *node, int which)
+{
+       int idx = anm_get_active_animation_index(node, which);
+       if(idx < 0 || idx >= anm_get_animation_count(node)) {
+               return 0;
+       }
+       return node->animations + idx;
+}
+
+float anm_get_active_animation_mix(const struct anm_node *node)
+{
+       return node->cur_mix;
+}
+
+int anm_get_animation_count(const struct anm_node *node)
+{
+       return anm_dynarr_size(node->animations);
+}
+
+int anm_add_node_animation(struct anm_node *node)
+{
+       struct anm_animation newanim;
+       anm_init_animation(&newanim);
+
+       node->animations = anm_dynarr_push(node->animations, &newanim);
+       return 0;
+}
+
+int anm_remove_node_animation(struct anm_node *node, int idx)
+{
+       fprintf(stderr, "anm_remove_animation: unimplemented!");
+       abort();
+       return 0;
+}
+
+int anm_add_animation(struct anm_node *node)
+{
+       struct anm_node *child;
+
+       if(anm_add_node_animation(node) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_add_animation(child)) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+}
+
+int anm_remove_animation(struct anm_node *node, int idx)
+{
+       struct anm_node *child;
+
+       if(anm_remove_node_animation(node, idx) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_remove_animation(child, idx) == -1) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+}
+
+struct anm_animation *anm_get_animation(struct anm_node *node, int idx)
+{
+       if(idx < 0 || idx > anm_get_animation_count(node)) {
+               return 0;
+       }
+       return node->animations + idx;
+}
+
+struct anm_animation *anm_get_animation_by_name(struct anm_node *node, const char *name)
+{
+       return anm_get_animation(node, anm_find_animation(node, name));
+}
+
+int anm_find_animation(struct anm_node *node, const char *name)
+{
+       int i, count = anm_get_animation_count(node);
+       for(i=0; i<count; i++) {
+               if(strcmp(node->animations[i].name, name) == 0) {
+                       return i;
+               }
+       }
+       return -1;
+}
+
+/* all the rest act on the current animation(s) */
+
+void anm_set_interpolator(struct anm_node *node, enum anm_interpolator in)
+{
+       int i;
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               anm_set_track_interpolator(anim->tracks + i, in);
+       }
+       invalidate_cache(node);
+}
+
+void anm_set_extrapolator(struct anm_node *node, enum anm_extrapolator ex)
+{
+       int i;
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               anm_set_track_extrapolator(anim->tracks + i, ex);
+       }
+       invalidate_cache(node);
+}
+
+void anm_set_node_active_animation_name(struct anm_node *node, const char *name)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_animation_name(anim, name);
+}
+
+void anm_set_active_animation_name(struct anm_node *node, const char *name)
+{
+       struct anm_node *child;
+
+       anm_set_node_active_animation_name(node, name);
+
+       child = node->child;
+       while(child) {
+               anm_set_active_animation_name(child, name);
+               child = child->next;
+       }
+}
+
+const char *anm_get_active_animation_name(struct anm_node *node)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(anim) {
+               return anim->name;
+       }
+       return 0;
+}
+
+/* ---- high level animation blending ---- */
+void anm_transition(struct anm_node *node, int anmidx, anm_time_t start, anm_time_t dur)
+{
+       struct anm_node *c = node->child;
+
+       if(anmidx == node->cur_anim[0]) {
+               return;
+       }
+
+       while(c) {
+               anm_transition(c, anmidx, start, dur);
+               c = c->next;
+       }
+
+       anm_node_transition(node, anmidx, start, dur);
+}
+
+void anm_node_transition(struct anm_node *node, int anmidx, anm_time_t start, anm_time_t dur)
+{
+       if(anmidx == node->cur_anim[0]) {
+               return;
+       }
+
+       node->cur_anim[1] = anmidx;
+       node->cur_anim_offset[1] = start;
+       node->blend_dur = dur;
+}
+
+
+#define BLEND_START_TM node->cur_anim_offset[1]
+
+static anm_time_t animation_time(struct anm_node *node, anm_time_t tm, int which)
+{
+       float t;
+
+       if(node->blend_dur >= 0) {
+               /* we're in transition... */
+               t = (float)(tm - BLEND_START_TM) / (float)node->blend_dur;
+               if(t < 0.0) t = 0.0;
+
+               node->cur_mix = t;
+
+               if(t > 1.0) {
+                       /* switch completely over to the target animation and stop blending */
+                       anm_use_node_animation(node, node->cur_anim[1]);
+                       node->cur_anim_offset[0] = node->cur_anim_offset[1];
+               }
+       }
+
+       return tm - node->cur_anim_offset[which];
+}
+
+
+void anm_set_position(struct anm_node *node, const float *pos, anm_time_t tm)
+{
+       anm_set_position3f(node, pos[0], pos[1], pos[2], tm);
+}
+
+void anm_set_position3f(struct anm_node *node, float x, float y, float z, anm_time_t tm)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_value(anim->tracks + ANM_TRACK_POS_X, tm, x);
+       anm_set_value(anim->tracks + ANM_TRACK_POS_Y, tm, y);
+       anm_set_value(anim->tracks + ANM_TRACK_POS_Z, tm, z);
+       invalidate_cache(node);
+}
+
+void anm_get_node_position(struct anm_node *node, float *pos, anm_time_t tm)
+{
+       anm_time_t tm0 = animation_time(node, tm, 0);
+       struct anm_animation *anim0 = anm_get_active_animation(node, 0);
+       struct anm_animation *anim1 = anm_get_active_animation(node, 1);
+
+       if(!anim0) {
+               pos[0] = pos[1] = pos[2] = 0.0f;
+               return;
+       }
+
+       pos[0] = anm_get_value(anim0->tracks + ANM_TRACK_POS_X, tm0);
+       pos[1] = anm_get_value(anim0->tracks + ANM_TRACK_POS_Y, tm0);
+       pos[2] = anm_get_value(anim0->tracks + ANM_TRACK_POS_Z, tm0);
+
+       if(anim1) {
+               anm_time_t tm1 = animation_time(node, tm, 1);
+               float x1 = anm_get_value(anim1->tracks + ANM_TRACK_POS_X, tm1);
+               float y1 = anm_get_value(anim1->tracks + ANM_TRACK_POS_Y, tm1);
+               float z1 = anm_get_value(anim1->tracks + ANM_TRACK_POS_Z, tm1);
+
+               pos[0] = pos[0] + (x1 - pos[0]) * node->cur_mix;
+               pos[1] = pos[1] + (y1 - pos[1]) * node->cur_mix;
+               pos[2] = pos[2] + (z1 - pos[2]) * node->cur_mix;
+       }
+}
+
+void anm_set_rotation(struct anm_node *node, const float *qrot, anm_time_t tm)
+{
+       anm_set_rotation4f(node, qrot[0], qrot[1], qrot[2], qrot[3], tm);
+}
+
+void anm_set_rotation4f(struct anm_node *node, float x, float y, float z, float w, anm_time_t tm)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_X, tm, x);
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_Y, tm, y);
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_Z, tm, z);
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_W, tm, w);
+       invalidate_cache(node);
+}
+
+void anm_set_rotation_axis(struct anm_node *node, float angle, float x, float y, float z, anm_time_t tm)
+{
+       cgm_quat q;
+       cgm_qrotation(&q, angle, x, y, z);
+
+       anm_set_rotation(node, (float*)&q, tm);
+}
+
+static void get_node_rotation(cgm_quat *qres, struct anm_node *node, anm_time_t tm, struct anm_animation *anim)
+{
+#ifndef ROT_USE_SLERP
+       qres->x = anm_get_value(anim->tracks + ANM_TRACK_ROT_X, tm);
+       qres->y = anm_get_value(anim->tracks + ANM_TRACK_ROT_Y, tm);
+       qres->z = anm_get_value(anim->tracks + ANM_TRACK_ROT_Z, tm);
+       qres->w = anm_get_value(anim->tracks + ANM_TRACK_ROT_W, tm);
+#else
+       int idx0, idx1, last_idx;
+       anm_time_t tstart, tend;
+       float t, dt;
+       struct anm_track *track_x, *track_y, *track_z, *track_w;
+       cgm_quat q1, q2;
+
+       track_x = anim->tracks + ANM_TRACK_ROT_X;
+       track_y = anim->tracks + ANM_TRACK_ROT_Y;
+       track_z = anim->tracks + ANM_TRACK_ROT_Z;
+       track_w = anim->tracks + ANM_TRACK_ROT_W;
+
+       if(!track_x->count) {
+               qres->x = track_x->def_val;
+               qres->y = track_y->def_val;
+               qres->z = track_z->def_val;
+               qres->w = track_w->def_val;
+               return;
+       }
+
+       last_idx = track_x->count - 1;
+
+       tstart = track_x->keys[0].time;
+       tend = track_x->keys[last_idx].time;
+
+       if(tstart == tend) {
+               qres->x = track_x->keys[0].val;
+               qres->y = track_y->keys[0].val;
+               qres->z = track_z->keys[0].val;
+               qres->w = track_w->keys[0].val;
+               return;
+       }
+
+       tm = anm_remap_time(track_x, tm, tstart, tend);
+
+       idx0 = anm_get_key_interval(track_x, tm);
+       assert(idx0 >= 0 && idx0 < track_x->count);
+       idx1 = idx0 + 1;
+
+       if(idx0 == last_idx) {
+               qres->x = track_x->keys[idx0].val;
+               qres->y = track_y->keys[idx0].val;
+               qres->z = track_z->keys[idx0].val;
+               qres->w = track_w->keys[idx0].val;
+               return;
+       }
+
+       dt = (float)(track_x->keys[idx1].time - track_x->keys[idx0].time);
+       t = (float)(tm - track_x->keys[idx0].time) / dt;
+
+       q1.x = track_x->keys[idx0].val;
+       q1.y = track_y->keys[idx0].val;
+       q1.z = track_z->keys[idx0].val;
+       q1.w = track_w->keys[idx0].val;
+
+       q2.x = track_x->keys[idx1].val;
+       q2.y = track_y->keys[idx1].val;
+       q2.z = track_z->keys[idx1].val;
+       q2.w = track_w->keys[idx1].val;
+
+       cgm_qslerp(qres, &q1, &q2, t);
+#endif
+}
+
+//get_node_rotation(cgm_quat *qres, struct anm_node *node, anm_time_t tm, struct anm_animation *anim)
+void anm_get_node_rotation(struct anm_node *node, float *qrot, anm_time_t tm)
+{
+       anm_time_t tm0 = animation_time(node, tm, 0);
+       struct anm_animation *anim0 = anm_get_active_animation(node, 0);
+       struct anm_animation *anim1 = anm_get_active_animation(node, 1);
+
+       if(!anim0) {
+               qrot[0] = qrot[1] = qrot[2] = 0.0f;
+               qrot[3] = 1.0f;
+               return;
+       }
+
+
+       if(anim1) {
+               cgm_quat q0, q1;
+               anm_time_t tm1 = animation_time(node, tm, 1);
+
+               get_node_rotation(&q0, node, tm0, anim0);
+               get_node_rotation(&q1, node, tm1, anim1);
+
+               cgm_qslerp((cgm_quat*)qrot, &q0, &q1, node->cur_mix);
+       } else {
+               get_node_rotation((cgm_quat*)qrot, node, tm0, anim0);
+       }
+}
+
+void anm_set_scaling(struct anm_node *node, const float *scale, anm_time_t tm)
+{
+       anm_set_scaling3f(node, scale[0], scale[1], scale[2], tm);
+}
+
+void anm_set_scaling3f(struct anm_node *node, float x, float y, float z, anm_time_t tm)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_value(anim->tracks + ANM_TRACK_SCL_X, tm, x);
+       anm_set_value(anim->tracks + ANM_TRACK_SCL_Y, tm, y);
+       anm_set_value(anim->tracks + ANM_TRACK_SCL_Z, tm, z);
+       invalidate_cache(node);
+}
+
+void anm_get_node_scaling(struct anm_node *node, float *scale, anm_time_t tm)
+{
+       anm_time_t tm0 = animation_time(node, tm, 0);
+       struct anm_animation *anim0 = anm_get_active_animation(node, 0);
+       struct anm_animation *anim1 = anm_get_active_animation(node, 1);
+
+       if(!anim0) {
+               scale[0] = scale[1] = scale[2] = 1.0f;
+               return;
+       }
+
+       scale[0] = anm_get_value(anim0->tracks + ANM_TRACK_SCL_X, tm0);
+       scale[1] = anm_get_value(anim0->tracks + ANM_TRACK_SCL_Y, tm0);
+       scale[2] = anm_get_value(anim0->tracks + ANM_TRACK_SCL_Z, tm0);
+
+       if(anim1) {
+               anm_time_t tm1 = animation_time(node, tm, 1);
+               float x1 = anm_get_value(anim1->tracks + ANM_TRACK_SCL_X, tm1);
+               float y1 = anm_get_value(anim1->tracks + ANM_TRACK_SCL_Y, tm1);
+               float z1 = anm_get_value(anim1->tracks + ANM_TRACK_SCL_Z, tm1);
+
+               scale[0] = scale[0] + (x1 - scale[0]) * node->cur_mix;
+               scale[1] = scale[1] + (y1 - scale[1]) * node->cur_mix;
+               scale[2] = scale[2] + (z1 - scale[2]) * node->cur_mix;
+       }
+}
+
+void anm_get_position(struct anm_node *node, float *pos, anm_time_t tm)
+{
+       if(!node->parent) {
+               anm_get_node_position(node, pos, tm);
+       } else {
+               float xform[16];
+               anm_get_matrix(node, xform, tm);
+               cgm_mget_translation(xform, (cgm_vec3*)pos);
+       }
+}
+
+void anm_get_rotation(struct anm_node *node, float *qrot, anm_time_t tm)
+{
+       if(!node->parent) {
+               anm_get_node_rotation(node, qrot, tm);
+       } else {
+               cgm_quat rot;
+               anm_get_node_rotation(node, &rot.x, tm);
+               anm_get_rotation(node->parent, qrot, tm);
+               cgm_qmul((cgm_quat*)qrot, &rot);
+       }
+}
+
+void anm_get_scaling(struct anm_node *node, float *scale, anm_time_t tm)
+{
+       anm_get_node_scaling(node, scale, tm);
+       if(node->parent) {
+               cgm_vec3 ps;
+               anm_get_scaling(node->parent, &ps.x, tm);
+               cgm_vmul((cgm_vec3*)scale, &ps);
+       }
+}
+
+void anm_get_node_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+       int i;
+       float rmat[16];
+       cgm_vec3 pos, scale;
+       cgm_quat rot;
+
+       anm_get_node_position(node, &pos.x, tm);
+       anm_get_node_rotation(node, &rot.x, tm);
+       anm_get_node_scaling(node, &scale.x, tm);
+
+       cgm_mtranslation(mat, node->pivot[0], node->pivot[1], node->pivot[2]);
+       cgm_mrotation_quat(rmat, &rot);
+
+       /*
+       for(i=0; i<3; i++) {
+               mat[i * 4] = rmat[i * 4];
+               mat[i * 4 + 1] = rmat[i * 4 + 1];
+               mat[i * 4 + 2] = rmat[i * 4 + 2];
+       }
+       */
+       for(i=0; i<3; i++) {
+               mat[i] = rmat[i];
+               mat[4 + i] = rmat[4 + i];
+               mat[8 + i] = rmat[8 + i];
+       }
+
+       mat[0] *= scale.x; mat[4] *= scale.y; mat[8] *= scale.z; mat[12] += pos.x;
+       mat[1] *= scale.x; mat[5] *= scale.y; mat[9] *= scale.z; mat[13] += pos.y;
+       mat[2] *= scale.x; mat[6] *= scale.y; mat[10] *= scale.z; mat[14] += pos.z;
+
+       cgm_mpretranslate(mat, -node->pivot[0], -node->pivot[1], -node->pivot[2]);
+
+       /* that's basically: pivot * rotation * translation * scaling * -pivot */
+}
+
+void anm_get_node_inv_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+       anm_get_node_matrix(node, mat, tm);
+       cgm_minverse(mat);
+}
+
+void anm_eval_node(struct anm_node *node, anm_time_t tm)
+{
+       anm_get_node_matrix(node, node->matrix, tm);
+}
+
+void anm_eval(struct anm_node *node, anm_time_t tm)
+{
+       struct anm_node *c;
+
+       anm_eval_node(node, tm);
+
+       if(node->parent) {
+               /* due to post-order traversal, the parent matrix is already evaluated */
+               cgm_mmul(node->matrix, node->parent->matrix);
+       }
+
+       /* recersively evaluate all children */
+       c = node->child;
+       while(c) {
+               anm_eval(c, tm);
+               c = c->next;
+       }
+}
+
+float *anm_get_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+#ifdef ANIM_THREAD_SAFE
+       struct mat_cache *cache = pthread_getspecific(node->cache_key);
+       if(!cache) {
+               cache = malloc(sizeof *cache);
+               assert(cache);
+
+               pthread_mutex_lock(&node->cache_list_lock);
+               cache->next = node->cache_list;
+               node->cache_list = cache;
+               pthread_mutex_unlock(&node->cache_list_lock);
+
+               cache->time = ANM_TIME_INVAL;
+               cache->inv_time = ANM_TIME_INVAL;
+               pthread_setspecific(node->cache_key, cache);
+       }
+#else
+       struct mat_cache *cache = &node->cache;
+#endif
+
+       if(cache->time != tm) {
+               anm_get_node_matrix(node, cache->matrix, tm);
+
+               if(node->parent) {
+                       float parent_mat[16];
+
+                       anm_get_matrix(node->parent, parent_mat, tm);
+                       cgm_mmul(cache->matrix, parent_mat);
+               }
+               cache->time = tm;
+       }
+
+       if(mat) {
+               cgm_mcopy(mat, cache->matrix);
+       }
+       return cache->matrix;
+}
+
+float *anm_get_inv_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+#ifdef ANIM_THREAD_SAFE
+       struct mat_cache *cache = pthread_getspecific(node->cache_key);
+       if(!cache) {
+               cache = malloc(sizeof *cache);
+               assert(cache);
+
+               pthread_mutex_lock(&node->cache_list_lock);
+               cache->next = node->cache_list;
+               node->cache_list = cache;
+               pthread_mutex_unlock(&node->cache_list_lock);
+
+               cache->inv_time = ANM_TIME_INVAL;
+               cache->inv_time = ANM_TIME_INVAL;
+               pthread_setspecific(node->cache_key, cache);
+       }
+#else
+       struct mat_cache *cache = &node->cache;
+#endif
+
+       if(cache->inv_time != tm) {
+               anm_get_matrix(node, cache->inv_matrix, tm);
+               cgm_minverse(cache->inv_matrix);
+               cache->inv_time = tm;
+       }
+
+       if(mat) {
+               cgm_mcopy(mat, cache->inv_matrix);
+       }
+       return cache->inv_matrix;
+}
+
+anm_time_t anm_get_start_time(struct anm_node *node)
+{
+       int i, j;
+       struct anm_node *c;
+       anm_time_t res = LONG_MAX;
+
+       for(j=0; j<2; j++) {
+               struct anm_animation *anim = anm_get_active_animation(node, j);
+               if(!anim) break;
+
+               for(i=0; i<ANM_NUM_TRACKS; i++) {
+                       if(anim->tracks[i].count) {
+                               anm_time_t tm = anim->tracks[i].keys[0].time;
+                               if(tm < res) {
+                                       res = tm;
+                               }
+                       }
+               }
+       }
+
+       c = node->child;
+       while(c) {
+               anm_time_t tm = anm_get_start_time(c);
+               if(tm < res) {
+                       res = tm;
+               }
+               c = c->next;
+       }
+       return res;
+}
+
+anm_time_t anm_get_end_time(struct anm_node *node)
+{
+       int i, j;
+       struct anm_node *c;
+       anm_time_t res = LONG_MIN;
+
+       for(j=0; j<2; j++) {
+               struct anm_animation *anim = anm_get_active_animation(node, j);
+               if(!anim) break;
+
+               for(i=0; i<ANM_NUM_TRACKS; i++) {
+                       if(anim->tracks[i].count) {
+                               anm_time_t tm = anim->tracks[i].keys[anim->tracks[i].count - 1].time;
+                               if(tm > res) {
+                                       res = tm;
+                               }
+                       }
+               }
+       }
+
+       c = node->child;
+       while(c) {
+               anm_time_t tm = anm_get_end_time(c);
+               if(tm > res) {
+                       res = tm;
+               }
+               c = c->next;
+       }
+       return res;
+}
+
+static void invalidate_cache(struct anm_node *node)
+{
+       struct anm_node *c;
+
+#ifdef ANIM_THREAD_SAFE
+       struct mat_cache *cache = pthread_getspecific(node->cache_key);
+       if(cache) {
+          cache->time = cache->inv_time = ANM_TIME_INVAL;
+       }
+#else
+       node->cache.time = node->cache.inv_time = ANM_TIME_INVAL;
+#endif
+
+       c = node->child;
+       while(c) {
+               invalidate_cache(c);
+               c = c->next;
+       }
+}
diff --git a/libs/anim/anim.h b/libs/anim/anim.h
new file mode 100644 (file)
index 0000000..13d0348
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef LIBANIM_H_
+#define LIBANIM_H_
+
+#include "config.h"
+
+#if _MSC_VER >= 1900
+#define _TIMESPEC_DEFINED
+#endif
+
+#ifdef ANIM_THREAD_SAFE
+#include <pthread.h>
+#endif
+
+#include "track.h"
+
+enum {
+       ANM_TRACK_POS_X,
+       ANM_TRACK_POS_Y,
+       ANM_TRACK_POS_Z,
+
+       ANM_TRACK_ROT_X,
+       ANM_TRACK_ROT_Y,
+       ANM_TRACK_ROT_Z,
+       ANM_TRACK_ROT_W,
+
+       ANM_TRACK_SCL_X,
+       ANM_TRACK_SCL_Y,
+       ANM_TRACK_SCL_Z,
+
+       ANM_NUM_TRACKS
+};
+
+struct anm_animation {
+       char *name;
+       struct anm_track tracks[ANM_NUM_TRACKS];
+};
+
+struct anm_node {
+       char *name;
+
+       int cur_anim[2];
+       anm_time_t cur_anim_offset[2];
+       float cur_mix;
+
+       /* high-level animation blending transition duration */
+       anm_time_t blend_dur;
+
+       struct anm_animation *animations;
+       float pivot[3];
+
+       /* matrix cache */
+       struct mat_cache {
+               float matrix[16], inv_matrix[16];
+               anm_time_t time, inv_time;
+               struct mat_cache *next;
+#ifdef ANIM_THREAD_SAFE
+       } *cache_list;
+       pthread_key_t cache_key;
+       pthread_mutex_t cache_list_lock;
+#else
+       } cache;
+#endif
+
+       /* matrix calculated by anm_eval functions (no locking, meant as a pre-pass) */
+       float matrix[16];
+
+       struct anm_node *parent;
+       struct anm_node *child;
+       struct anm_node *next;
+
+       void *data;     /* user data pointer */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int anm_init_animation(struct anm_animation *anim);
+void anm_destroy_animation(struct anm_animation *anim);
+
+void anm_set_animation_name(struct anm_animation *anim, const char *name);
+
+
+/* ---- node/hierarchy management ---- */
+
+/* node constructor and destructor */
+int anm_init_node(struct anm_node *node);
+void anm_destroy_node(struct anm_node *node);
+
+/* recursively destroy an animation node tree */
+void anm_destroy_node_tree(struct anm_node *tree);
+
+/* helper functions to allocate/construct and destroy/free with
+ * a single call. They call anm_init_node and anm_destroy_node
+ * internally.
+ */
+struct anm_node *anm_create_node(void);
+void anm_free_node(struct anm_node *node);
+
+/* recursively destroy and free the nodes of a node tree */
+void anm_free_node_tree(struct anm_node *tree);
+
+int anm_set_node_name(struct anm_node *node, const char *name);
+const char *anm_get_node_name(struct anm_node *node);
+
+/* link and unlink nodes with parent/child relations */
+void anm_link_node(struct anm_node *parent, struct anm_node *child);
+int anm_unlink_node(struct anm_node *parent, struct anm_node *child);
+
+void anm_set_pivot(struct anm_node *node, float x, float y, float z);
+void anm_get_pivot(struct anm_node *node, float *x, float *y, float *z);
+
+/* ---- multiple animations and animation blending ---- */
+
+/* set active animation(s) */
+int anm_use_node_animation(struct anm_node *node, int aidx);
+int anm_use_node_animations(struct anm_node *node, int aidx, int bidx, float t);
+/* recursive variants */
+int anm_use_animation(struct anm_node *node, int aidx);
+int anm_use_animations(struct anm_node *node, int aidx, int bidx, float t);
+
+/* set/get current animation offset(s) */
+void anm_set_node_animation_offset(struct anm_node *node, anm_time_t offs, int which);
+anm_time_t anm_get_animation_offset(const struct anm_node *node, int which);
+/* recursive variant */
+void anm_set_animation_offset(struct anm_node *node, anm_time_t offs, int which);
+
+/* returns the requested current animation index, which can be 0 or 1 */
+int anm_get_active_animation_index(const struct anm_node *node, int which);
+/* returns the requested current animation, which can be 0 or 1 */
+struct anm_animation *anm_get_active_animation(const struct anm_node *node, int which);
+float anm_get_active_animation_mix(const struct anm_node *node);
+
+int anm_get_animation_count(const struct anm_node *node);
+
+/* add/remove an animation to the specified node */
+int anm_add_node_animation(struct anm_node *node);
+int anm_remove_node_animation(struct anm_node *node, int idx);
+
+/* add/remove an animation to the specified node and all it's descendants */
+int anm_add_animation(struct anm_node *node);
+int anm_remove_animation(struct anm_node *node, int idx);
+
+struct anm_animation *anm_get_animation(struct anm_node *node, int idx);
+struct anm_animation *anm_get_animation_by_name(struct anm_node *node, const char *name);
+
+int anm_find_animation(struct anm_node *node, const char *name);
+
+/* set the interpolator for the (first) currently active animation */
+void anm_set_interpolator(struct anm_node *node, enum anm_interpolator in);
+/* set the extrapolator for the (first) currently active animation */
+void anm_set_extrapolator(struct anm_node *node, enum anm_extrapolator ex);
+
+/* set the name of the currently active animation of this node only */
+void anm_set_node_active_animation_name(struct anm_node *node, const char *name);
+/* recursively set the name of the currently active animation for this node
+ * and all it's descendants */
+void anm_set_active_animation_name(struct anm_node *node, const char *name);
+/* get the name of the currently active animation of this node */
+const char *anm_get_active_animation_name(struct anm_node *node);
+
+
+/* ---- high level animation blending interface ---- */
+/* XXX this convenience interface assumes monotonically increasing time values
+ *     in all subsequent calls to anm_get_* and anm_eval_* functions.
+ *
+ * anmidx: index of the animation to transition to
+ * start: when to start the transition
+ * dur: transition duration
+ *
+ * sets up a transition from the current animation (cur_anim[0]) to another animation.
+ * at time start + dur, the transition will be completed, cur_anim[0] will be the new
+ * animation and cur_anim_offset[0] will be equal to start.
+ */
+void anm_transition(struct anm_node *node, int anmidx, anm_time_t start, anm_time_t dur);
+/* non-recursive variant, acts on a single node (you probably DON'T want to use this) */
+void anm_node_transition(struct anm_node *node, int anmidx, anm_time_t start, anm_time_t dur);
+
+
+/* ---- keyframes / PRS interpolation ---- */
+
+void anm_set_position(struct anm_node *node, const float *pos, anm_time_t tm);
+void anm_set_position3f(struct anm_node *node, float x, float y, float z, anm_time_t tm);
+void anm_get_node_position(struct anm_node *node, float *pos, anm_time_t tm);
+
+void anm_set_rotation(struct anm_node *node, const float *qrot, anm_time_t tm);
+void anm_set_rotation4f(struct anm_node *node, float x, float y, float z, float w, anm_time_t tm);
+void anm_set_rotation_axis(struct anm_node *node, float angle, float x, float y, float z, anm_time_t tm);
+void anm_get_node_rotation(struct anm_node *node, float *qrot, anm_time_t tm);
+
+void anm_set_scaling(struct anm_node *node, const float *scale, anm_time_t tm);
+void anm_set_scaling3f(struct anm_node *node, float x, float y, float z, anm_time_t tm);
+void anm_get_node_scaling(struct anm_node *node, float *scale, anm_time_t tm);
+
+/* these three return the full p/r/s taking hierarchy into account */
+void anm_get_position(struct anm_node *node, float *pos, anm_time_t tm);
+void anm_get_rotation(struct anm_node *node, float *qrot, anm_time_t tm);
+void anm_get_scaling(struct anm_node *node, float *scale, anm_time_t tm);
+
+/* those return the start and end times of the whole tree */
+anm_time_t anm_get_start_time(struct anm_node *node);
+anm_time_t anm_get_end_time(struct anm_node *node);
+
+
+/* ---- transformation matrices ---- */
+
+/* these calculate the matrix and inverse matrix of this node alone */
+void anm_get_node_matrix(struct anm_node *node, float *mat, anm_time_t tm);
+void anm_get_node_inv_matrix(struct anm_node *node, float *mat, anm_time_t tm);
+
+/* ---- top-down matrix calculation interface ---- */
+
+/* calculate and set the matrix of this node */
+void anm_eval_node(struct anm_node *node, anm_time_t tm);
+/* calculate and set the matrix of this node and all its children recursively */
+void anm_eval(struct anm_node *node, anm_time_t tm);
+
+
+/* ---- bottom-up lazy matrix calculation interface ---- */
+
+/* These calculate the matrix and inverse matrix of this node taking hierarchy
+ * into account. The results are cached in thread-specific storage and returned
+ * if there's no change in time or tracks from the last query...
+ *
+ * A pointer to the internal cached matrix is returned, and also if mat is not
+ * null, the matrix is copied there.
+ */
+float *anm_get_matrix(struct anm_node *node, float *mat, anm_time_t tm);
+float *anm_get_inv_matrix(struct anm_node *node, float *mat, anm_time_t tm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBANIM_H_ */
diff --git a/libs/anim/config.h b/libs/anim/config.h
new file mode 100644 (file)
index 0000000..f00310c
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef ANIM_CONFIG_H_
+#define ANIM_CONFIG_H_
+
+#undef ANIM_THREAD_SAFE
+
+#endif /* ANIM_CONFIG_H_ */
diff --git a/libs/anim/dynarr.c b/libs/anim/dynarr.c
new file mode 100644 (file)
index 0000000..9a51f4c
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2014 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "dynarr.h"
+
+/* The array descriptor keeps auxilliary information needed to manipulate
+ * the dynamic array. It's allocated adjacent to the array buffer.
+ */
+struct arrdesc {
+       int nelem, szelem;
+       int max_elem;
+       int bufsz;      /* not including the descriptor */
+};
+
+#define DESC(x)                ((struct arrdesc*)((char*)(x) - sizeof(struct arrdesc)))
+
+void *anm_dynarr_alloc(int elem, int szelem)
+{
+       struct arrdesc *desc;
+
+       if(!(desc = malloc(elem * szelem + sizeof *desc))) {
+               return 0;
+       }
+       desc->nelem = desc->max_elem = elem;
+       desc->szelem = szelem;
+       desc->bufsz = elem * szelem;
+       return (char*)desc + sizeof *desc;
+}
+
+void anm_dynarr_free(void *da)
+{
+       if(da) {
+               free(DESC(da));
+       }
+}
+
+void *anm_dynarr_resize(void *da, int elem)
+{
+       int newsz;
+       void *tmp;
+       struct arrdesc *desc;
+
+       if(!da) return 0;
+       desc = DESC(da);
+
+       newsz = desc->szelem * elem;
+
+       if(!(tmp = realloc(desc, newsz + sizeof *desc))) {
+               return 0;
+       }
+       desc = tmp;
+
+       desc->nelem = desc->max_elem = elem;
+       desc->bufsz = newsz;
+       return (char*)desc + sizeof *desc;
+}
+
+int anm_dynarr_empty(void *da)
+{
+       return DESC(da)->nelem ? 0 : 1;
+}
+
+int anm_dynarr_size(void *da)
+{
+       return DESC(da)->nelem;
+}
+
+
+/* stack semantics */
+void *anm_dynarr_push(void *da, void *item)
+{
+       struct arrdesc *desc;
+       int nelem;
+
+       desc = DESC(da);
+       nelem = desc->nelem;
+
+       if(nelem >= desc->max_elem) {
+               /* need to resize */
+               struct arrdesc *tmp;
+               int newsz = desc->max_elem ? desc->max_elem * 2 : 1;
+
+               if(!(tmp = anm_dynarr_resize(da, newsz))) {
+                       fprintf(stderr, "failed to resize\n");
+                       return da;
+               }
+               da = tmp;
+               desc = DESC(da);
+               desc->nelem = nelem;
+       }
+
+       memcpy((char*)da + desc->nelem++ * desc->szelem, item, desc->szelem);
+       return da;
+}
+
+void *anm_dynarr_pop(void *da)
+{
+       struct arrdesc *desc;
+       int nelem;
+
+       desc = DESC(da);
+       nelem = desc->nelem;
+
+       if(!nelem) return da;
+
+       if(nelem <= desc->max_elem / 3) {
+               /* reclaim space */
+               struct arrdesc *tmp;
+               int newsz = desc->max_elem / 2;
+
+               if(!(tmp = anm_dynarr_resize(da, newsz))) {
+                       fprintf(stderr, "failed to resize\n");
+                       return da;
+               }
+               da = tmp;
+               desc = DESC(da);
+               desc->nelem = nelem;
+       }
+       desc->nelem--;
+
+       return da;
+}
diff --git a/libs/anim/dynarr.h b/libs/anim/dynarr.h
new file mode 100644 (file)
index 0000000..3d8459f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2014 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DYNARR_H_
+#define DYNARR_H_
+
+void *anm_dynarr_alloc(int elem, int szelem);
+void anm_dynarr_free(void *da);
+void *anm_dynarr_resize(void *da, int elem);
+
+int anm_dynarr_empty(void *da);
+int anm_dynarr_size(void *da);
+
+/* stack semantics */
+void *anm_dynarr_push(void *da, void *item);
+void *anm_dynarr_pop(void *da);
+
+
+#endif /* DYNARR_H_ */
diff --git a/libs/anim/track.c b/libs/anim/track.c
new file mode 100644 (file)
index 0000000..1ffb7a8
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2015 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "track.h"
+#include "dynarr.h"
+
+static int keycmp(const void *a, const void *b);
+static int find_prev_key(struct anm_keyframe *arr, int start, int end, anm_time_t tm);
+
+static float interp_step(float v0, float v1, float v2, float v3, float t);
+static float interp_linear(float v0, float v1, float v2, float v3, float t);
+static float interp_cubic(float v0, float v1, float v2, float v3, float t);
+
+static anm_time_t remap_extend(anm_time_t tm, anm_time_t start, anm_time_t end);
+static anm_time_t remap_clamp(anm_time_t tm, anm_time_t start, anm_time_t end);
+static anm_time_t remap_repeat(anm_time_t tm, anm_time_t start, anm_time_t end);
+static anm_time_t remap_pingpong(anm_time_t tm, anm_time_t start, anm_time_t end);
+
+/* XXX keep this in sync with enum anm_interpolator at track.h */
+static float (*interp[])(float, float, float, float, float) = {
+       interp_step,
+       interp_linear,
+       interp_cubic,
+       0
+};
+
+/* XXX keep this in sync with enum anm_extrapolator at track.h */
+static anm_time_t (*remap_time[])(anm_time_t, anm_time_t, anm_time_t) = {
+       remap_extend,
+       remap_clamp,
+       remap_repeat,
+       remap_pingpong,
+       0
+};
+
+int anm_init_track(struct anm_track *track)
+{
+       memset(track, 0, sizeof *track);
+
+       if(!(track->keys = anm_dynarr_alloc(0, sizeof *track->keys))) {
+               return -1;
+       }
+       track->interp = ANM_INTERP_LINEAR;
+       track->extrap = ANM_EXTRAP_CLAMP;
+       return 0;
+}
+
+void anm_destroy_track(struct anm_track *track)
+{
+       anm_dynarr_free(track->keys);
+}
+
+struct anm_track *anm_create_track(void)
+{
+       struct anm_track *track;
+
+       if((track = malloc(sizeof *track))) {
+               if(anm_init_track(track) == -1) {
+                       free(track);
+                       return 0;
+               }
+       }
+       return track;
+}
+
+void anm_free_track(struct anm_track *track)
+{
+       anm_destroy_track(track);
+       free(track);
+}
+
+void anm_copy_track(struct anm_track *dest, const struct anm_track *src)
+{
+       free(dest->name);
+       if(dest->keys) {
+               anm_dynarr_free(dest->keys);
+       }
+
+       if(src->name) {
+               dest->name = malloc(strlen(src->name) + 1);
+               strcpy(dest->name, src->name);
+       }
+
+       dest->count = src->count;
+       dest->keys = anm_dynarr_alloc(src->count, sizeof *dest->keys);
+       memcpy(dest->keys, src->keys, src->count * sizeof *dest->keys);
+
+       dest->def_val = src->def_val;
+       dest->interp = src->interp;
+       dest->extrap = src->extrap;
+}
+
+int anm_set_track_name(struct anm_track *track, const char *name)
+{
+       char *tmp;
+
+       if(!(tmp = malloc(strlen(name) + 1))) {
+               return -1;
+       }
+       free(track->name);
+       track->name = tmp;
+       return 0;
+}
+
+const char *anm_get_track_name(struct anm_track *track)
+{
+       return track->name;
+}
+
+void anm_set_track_interpolator(struct anm_track *track, enum anm_interpolator in)
+{
+       track->interp = in;
+}
+
+void anm_set_track_extrapolator(struct anm_track *track, enum anm_extrapolator ex)
+{
+       track->extrap = ex;
+}
+
+anm_time_t anm_remap_time(struct anm_track *track, anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       return remap_time[track->extrap](tm, start, end);
+}
+
+void anm_set_track_default(struct anm_track *track, float def)
+{
+       track->def_val = def;
+}
+
+int anm_set_keyframe(struct anm_track *track, struct anm_keyframe *key)
+{
+       int idx = anm_get_key_interval(track, key->time);
+
+       /* if we got a valid keyframe index, compare them... */
+       if(idx >= 0 && idx < track->count && keycmp(key, track->keys + idx) == 0) {
+               /* ... it's the same key, just update the value */
+               track->keys[idx].val = key->val;
+       } else {
+               /* ... it's a new key, add it and re-sort them */
+               void *tmp;
+               if(!(tmp = anm_dynarr_push(track->keys, key))) {
+                       return -1;
+               }
+               track->keys = tmp;
+               /* TODO lazy qsort */
+               qsort(track->keys, ++track->count, sizeof *track->keys, keycmp);
+       }
+       return 0;
+}
+
+static int keycmp(const void *a, const void *b)
+{
+       return ((struct anm_keyframe*)a)->time - ((struct anm_keyframe*)b)->time;
+}
+
+struct anm_keyframe *anm_get_keyframe(struct anm_track *track, int idx)
+{
+       if(idx < 0 || idx >= track->count) {
+               return 0;
+       }
+       return track->keys + idx;
+}
+
+int anm_get_key_interval(struct anm_track *track, anm_time_t tm)
+{
+       int last;
+
+       if(!track->count || tm < track->keys[0].time) {
+               return -1;
+       }
+
+       last = track->count - 1;
+       if(tm > track->keys[last].time) {
+               return last;
+       }
+
+       return find_prev_key(track->keys, 0, last, tm);
+}
+
+static int find_prev_key(struct anm_keyframe *arr, int start, int end, anm_time_t tm)
+{
+       int mid;
+
+       if(end - start <= 1) {
+               return start;
+       }
+
+       mid = (start + end) / 2;
+       if(tm < arr[mid].time) {
+               return find_prev_key(arr, start, mid, tm);
+       }
+       if(tm > arr[mid].time) {
+               return find_prev_key(arr, mid, end, tm);
+       }
+       return mid;
+}
+
+int anm_set_value(struct anm_track *track, anm_time_t tm, float val)
+{
+       struct anm_keyframe key;
+       key.time = tm;
+       key.val = val;
+
+       return anm_set_keyframe(track, &key);
+}
+
+float anm_get_value(struct anm_track *track, anm_time_t tm)
+{
+       int idx0, idx1, last_idx;
+       anm_time_t tstart, tend;
+       float t, dt;
+       float v0, v1, v2, v3;
+
+       if(!track->count) {
+               return track->def_val;
+       }
+
+       last_idx = track->count - 1;
+
+       tstart = track->keys[0].time;
+       tend = track->keys[last_idx].time;
+
+       if(tstart == tend) {
+               return track->keys[0].val;
+       }
+
+       tm = remap_time[track->extrap](tm, tstart, tend);
+
+       idx0 = anm_get_key_interval(track, tm);
+       assert(idx0 >= 0 && idx0 < track->count);
+       idx1 = idx0 + 1;
+
+       if(idx0 == last_idx) {
+               return track->keys[idx0].val;
+       }
+
+       dt = (float)(track->keys[idx1].time - track->keys[idx0].time);
+       t = (float)(tm - track->keys[idx0].time) / dt;
+
+       v1 = track->keys[idx0].val;
+       v2 = track->keys[idx1].val;
+
+       /* get the neigboring values to allow for cubic interpolation */
+       v0 = idx0 > 0 ? track->keys[idx0 - 1].val : v1;
+       v3 = idx1 < last_idx ? track->keys[idx1 + 1].val : v2;
+
+       return interp[track->interp](v0, v1, v2, v3, t);
+}
+
+
+static float interp_step(float v0, float v1, float v2, float v3, float t)
+{
+       return v1;
+}
+
+static float interp_linear(float v0, float v1, float v2, float v3, float t)
+{
+       return v1 + (v2 - v1) * t;
+}
+
+static float interp_cubic(float a, float b, float c, float d, float t)
+{
+       float x, y, z, w;
+       float tsq = t * t;
+
+       x = -a + 3.0 * b - 3.0 * c + d;
+       y = 2.0 * a - 5.0 * b + 4.0 * c - d;
+       z = c - a;
+       w = 2.0 * b;
+
+       return 0.5 * (x * tsq * t + y * tsq + z * t + w);
+}
+
+static anm_time_t remap_extend(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       return remap_repeat(tm, start, end);
+}
+
+static anm_time_t remap_clamp(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       if(start == end) {
+               return start;
+       }
+       return tm < start ? start : (tm >= end ? end : tm);
+}
+
+static anm_time_t remap_repeat(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       anm_time_t x, interv = end - start;
+
+       if(interv == 0) {
+               return start;
+       }
+
+       x = (tm - start) % interv;
+       if(x < 0) {
+               x += interv;
+       }
+       return x + start;
+
+       /*if(tm < start) {
+               while(tm < start) {
+                       tm += interv;
+               }
+               return tm;
+       }
+       return (tm - start) % interv + start;*/
+}
+
+static anm_time_t remap_pingpong(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       anm_time_t interv = end - start;
+       anm_time_t x = remap_repeat(tm, start, end + interv);
+
+       return x > end ? end + interv - x : x;
+}
diff --git a/libs/anim/track.h b/libs/anim/track.h
new file mode 100644 (file)
index 0000000..89749dd
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2014 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* An animation track defines the values of a single scalar over time
+ * and supports various interpolation and extrapolation modes.
+ */
+#ifndef LIBANIM_TRACK_H_
+#define LIBANIM_TRACK_H_
+
+#include <limits.h>
+#include "config.h"
+
+enum anm_interpolator {
+       ANM_INTERP_STEP,
+       ANM_INTERP_LINEAR,
+       ANM_INTERP_CUBIC
+};
+
+enum anm_extrapolator {
+       ANM_EXTRAP_EXTEND,      /* extend to infinity */
+       ANM_EXTRAP_CLAMP,       /* clamp to last value */
+       ANM_EXTRAP_REPEAT,      /* repeat motion */
+       ANM_EXTRAP_PINGPONG     /* repeat with mirroring */
+};
+
+typedef long anm_time_t;
+#define ANM_TIME_INVAL LONG_MIN
+
+#define ANM_SEC2TM(x)  ((anm_time_t)((x) * 1000))
+#define ANM_MSEC2TM(x) ((anm_time_t)(x))
+#define ANM_TM2SEC(x)  ((x) / 1000.0)
+#define ANM_TM2MSEC(x) (x)
+
+struct anm_keyframe {
+       anm_time_t time;
+       float val;
+};
+
+struct anm_track {
+       char *name;
+       int count;
+       struct anm_keyframe *keys;
+
+       float def_val;
+
+       enum anm_interpolator interp;
+       enum anm_extrapolator extrap;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* track constructor and destructor */
+int anm_init_track(struct anm_track *track);
+void anm_destroy_track(struct anm_track *track);
+
+/* helper functions that use anm_init_track and anm_destroy_track internally */
+struct anm_track *anm_create_track(void);
+void anm_free_track(struct anm_track *track);
+
+/* copies track src to dest
+ * XXX: dest must have been initialized first
+ */
+void anm_copy_track(struct anm_track *dest, const struct anm_track *src);
+
+int anm_set_track_name(struct anm_track *track, const char *name);
+const char *anm_get_track_name(struct anm_track *track);
+
+void anm_set_track_interpolator(struct anm_track *track, enum anm_interpolator in);
+void anm_set_track_extrapolator(struct anm_track *track, enum anm_extrapolator ex);
+
+anm_time_t anm_remap_time(struct anm_track *track, anm_time_t tm, anm_time_t start, anm_time_t end);
+
+void anm_set_track_default(struct anm_track *track, float def);
+
+/* set or update a keyframe */
+int anm_set_keyframe(struct anm_track *track, struct anm_keyframe *key);
+
+/* get the idx-th keyframe, returns null if it doesn't exist */
+struct anm_keyframe *anm_get_keyframe(struct anm_track *track, int idx);
+
+/* Finds the 0-based index of the intra-keyframe interval which corresponds
+ * to the specified time. If the time falls exactly onto the N-th keyframe
+ * the function returns N.
+ *
+ * Special cases:
+ * - if the time is before the first keyframe -1 is returned.
+ * - if the time is after the last keyframe, the index of the last keyframe
+ *   is returned.
+ */
+int anm_get_key_interval(struct anm_track *track, anm_time_t tm);
+
+int anm_set_value(struct anm_track *track, anm_time_t tm, float val);
+
+/* evaluates and returns the value of the track for a particular time */
+float anm_get_value(struct anm_track *track, anm_time_t tm);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* LIBANIM_TRACK_H_ */
diff --git a/libs/cgmath/LICENSE b/libs/cgmath/LICENSE
new file mode 100644 (file)
index 0000000..536e666
--- /dev/null
@@ -0,0 +1,20 @@
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/libs/cgmath/README.md b/libs/cgmath/README.md
new file mode 100644 (file)
index 0000000..119aa4a
--- /dev/null
@@ -0,0 +1,29 @@
+gph-cmath: C math library for graphics
+======================================
+
+About
+-----
+gph-cmath is a C math library for graphics programs. It provides a plethora of
+operations on vectors, matrices and quaternions, among other things.
+
+It's conceptually a companion to my C++ math library
+[gph-math](http://github.com/jtsiomb/gph-math), but where gph-math is designed
+with intuitiveness and ease of use as the main priority, this C version is
+designed to be as low-overhead as possible, making it more suitable for more
+resource-constrained target systems.
+For instance most functions modify their first argument, instead of doing an
+extra copy to provide a more natural 3 operand interface. Leaving the copy to
+the user for the cases where it's necessary.
+
+License
+-------
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software. Feel free to use, modify, and/or redistribute it
+under the terms of the MIT/X11 license. See LICENSE for details.
+
+How to use
+----------
+There's nothing to build. All functions are static inline, defined in the header
+files. Either type `make install` to install them system-wide, or just copy all
+files under `src/` to your project source tree.
diff --git a/libs/cgmath/cgmath.h b/libs/cgmath/cgmath.h
new file mode 100644 (file)
index 0000000..36c3d54
--- /dev/null
@@ -0,0 +1,263 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ *
+ * Function prefixes signify the data type of their operand(s):
+ * - cgm_v... functions are operations on cgm_vec3 vectors
+ * - cgm_w... functions are operations on cgm_vec4 vectors
+ * - cgm_q... functions are operations on cgm_quat quaternions (w + xi + yj + zk)
+ * - cgm_m... functions are operations on 4x4 matrices (stored as linear 16 float arrays)
+ * - cgm_r... functions are operations on cgm_ray rays
+ *
+ * NOTE: *ALL* matrix arguments are pointers to 16 floats. Even the functions
+ * which operate on 3x3 matrices, actually use the upper 3x3 of a 4x4 matrix,
+ * and still expect an array of 16 floats.
+ *
+ * NOTE: matrices are treated by all operations as column-major, to match OpenGL
+ * conventions, so everything is pretty much transposed.
+*/
+#ifndef CGMATH_H_
+#define CGMATH_H_
+
+#include <math.h>
+#include <string.h>
+
+typedef struct {
+       float x, y;
+} cgm_vec2;
+
+typedef struct {
+       float x, y, z;
+} cgm_vec3;
+
+typedef struct {
+       float x, y, z, w;
+} cgm_vec4, cgm_quat;
+
+typedef struct {
+       cgm_vec3 origin, dir;
+} cgm_ray;
+
+typedef enum cgm_euler_mode {
+       CGM_EULER_XYZ,
+       CGM_EULER_XZY,
+       CGM_EULER_YXZ,
+       CGM_EULER_YZX,
+       CGM_EULER_ZXY,
+       CGM_EULER_ZYX,
+       CGM_EULER_ZXZ,
+       CGM_EULER_ZYZ,
+       CGM_EULER_YXY,
+       CGM_EULER_YZY,
+       CGM_EULER_XYX,
+       CGM_EULER_XZX
+} cgm_euler_mode;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* --- operations on cgm_vec3 --- */
+static inline void cgm_vcons(cgm_vec3 *v, float x, float y, float z);
+
+static inline void cgm_vadd(cgm_vec3 *a, const cgm_vec3 *b);
+static inline void cgm_vadd_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s); /* a+b*s */
+static inline void cgm_vsub(cgm_vec3 *a, const cgm_vec3 *b);
+static inline void cgm_vsub_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s); /* a-b*s */
+static inline void cgm_vmul(cgm_vec3 *a, const cgm_vec3 *b);
+static inline void cgm_vscale(cgm_vec3 *v, float s);
+static inline void cgm_vmul_m4v3(cgm_vec3 *v, const float *m); /* m4x4 * v */
+static inline void cgm_vmul_v3m4(cgm_vec3 *v, const float *m); /* v * m4x4 */
+static inline void cgm_vmul_m3v3(cgm_vec3 *v, const float *m); /* m3x3 * v (m still 16 floats) */
+static inline void cgm_vmul_v3m3(cgm_vec3 *v, const float *m); /* v * m3x3 (m still 16 floats) */
+
+static inline float cgm_vdot(const cgm_vec3 *a, const cgm_vec3 *b);
+static inline void cgm_vcross(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b);
+static inline float cgm_vlength(const cgm_vec3 *v);
+static inline float cgm_vlength_sq(const cgm_vec3 *v);
+static inline float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b);
+static inline float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b);
+static inline void cgm_vnormalize(cgm_vec3 *v);
+
+static inline void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n);
+static inline int cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior);
+
+static inline void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q);
+static inline void cgm_vrotate_axis(cgm_vec3 *v, int axis, float angle);
+static inline void cgm_vrotate(cgm_vec3 *v, float angle, float x, float y, float z);
+static inline void cgm_vrotate_euler(cgm_vec3 *v, float a, float b, float c, enum cgm_euler_mode mode);
+
+static inline void cgm_vlerp(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b, float t);
+
+#define cgm_velem(vptr, idx)   ((&(vptr)->x)[idx])
+
+/* --- operations on cgm_vec4 --- */
+static inline void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w);
+
+static inline void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b);
+static inline void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b);
+static inline void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b);
+static inline void cgm_wscale(cgm_vec4 *v, float s);
+
+static inline void cgm_wmul_m4v4(cgm_vec4 *v, const float *m);
+static inline void cgm_wmul_v4m4(cgm_vec4 *v, const float *m);
+static inline void cgm_wmul_m34v4(cgm_vec4 *v, const float *m);        /* doesn't affect w */
+static inline void cgm_wmul_v4m43(cgm_vec4 *v, const float *m);        /* doesn't affect w */
+static inline void cgm_wmul_m3v4(cgm_vec4 *v, const float *m); /* (m still 16 floats) */
+static inline void cgm_wmul_v4m3(cgm_vec4 *v, const float *m); /* (m still 16 floats) */
+
+static inline float cgm_wdot(const cgm_vec4 *a, const cgm_vec4 *b);
+
+static inline float cgm_wlength(const cgm_vec4 *v);
+static inline float cgm_wlength_sq(const cgm_vec4 *v);
+static inline float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b);
+static inline float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b);
+static inline void cgm_wnormalize(cgm_vec4 *v);
+
+static inline void cgm_wlerp(cgm_vec4 *res, const cgm_vec4 *a, const cgm_vec4 *b, float t);
+
+#define cgm_welem(vptr, idx)   ((&(vptr)->x)[idx])
+
+/* --- operations on quaternions --- */
+static inline void cgm_qcons(cgm_quat *q, float x, float y, float z, float w);
+
+static inline void cgm_qneg(cgm_quat *q);
+static inline void cgm_qadd(cgm_quat *a, const cgm_quat *b);
+static inline void cgm_qsub(cgm_quat *a, const cgm_quat *b);
+static inline void cgm_qmul(cgm_quat *a, const cgm_quat *b);
+
+static inline float cgm_qlength(const cgm_quat *q);
+static inline float cgm_qlength_sq(const cgm_quat *q);
+static inline void cgm_qnormalize(cgm_quat *q);
+static inline void cgm_qconjugate(cgm_quat *q);
+static inline void cgm_qinvert(cgm_quat *q);
+
+static inline void cgm_qrotation(cgm_quat *q, float angle, float x, float y, float z);
+static inline void cgm_qrotate(cgm_quat *q, float angle, float x, float y, float z);
+
+static inline void cgm_qslerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t);
+static inline void cgm_qlerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t);
+
+#define cgm_qelem(qptr, idx)   ((&(qptr)->x)[idx])
+
+/* --- operations on matrices --- */
+static inline void cgm_mcopy(float *dest, const float *src);
+static inline void cgm_mzero(float *m);
+static inline void cgm_midentity(float *m);
+
+static inline void cgm_mmul(float *a, const float *b);
+static inline void cgm_mpremul(float *a, const float *b);
+
+static inline void cgm_msubmatrix(float *m, int row, int col);
+static inline void cgm_mupper3(float *m);
+static inline float cgm_msubdet(const float *m, int row, int col);
+static inline float cgm_mcofactor(const float *m, int row, int col);
+static inline float cgm_mdet(const float *m);
+static inline void cgm_mtranspose(float *m);
+static inline void cgm_mcofmatrix(float *m);
+static inline int cgm_minverse(float *m);      /* returns 0 on success, -1 for singular */
+
+static inline void cgm_mtranslation(float *m, float x, float y, float z);
+static inline void cgm_mscaling(float *m, float sx, float sy, float sz);
+static inline void cgm_mrotation_x(float *m, float angle);
+static inline void cgm_mrotation_y(float *m, float angle);
+static inline void cgm_mrotation_z(float *m, float angle);
+static inline void cgm_mrotation_axis(float *m, int idx, float angle);
+static inline void cgm_mrotation(float *m, float angle, float x, float y, float z);
+static inline void cgm_mrotation_euler(float *m, float a, float b, float c, int mode);
+static inline void cgm_mrotation_quat(float *m, const cgm_quat *q);
+
+static inline void cgm_mtranslate(float *m, float x, float y, float z);
+static inline void cgm_mscale(float *m, float sx, float sy, float sz);
+static inline void cgm_mrotate_x(float *m, float angle);
+static inline void cgm_mrotate_y(float *m, float angle);
+static inline void cgm_mrotate_z(float *m, float angle);
+static inline void cgm_mrotate_axis(float *m, int idx, float angle);
+static inline void cgm_mrotate(float *m, float angle, float x, float y, float z);
+static inline void cgm_mrotate_euler(float *m, float a, float b, float c, int mode);
+static inline void cgm_mrotate_quat(float *m, const cgm_quat *q);
+
+static inline void cgm_mpretranslate(float *m, float x, float y, float z);
+static inline void cgm_mprescale(float *m, float sx, float sy, float sz);
+static inline void cgm_mprerotate_x(float *m, float angle);
+static inline void cgm_mprerotate_y(float *m, float angle);
+static inline void cgm_mprerotate_z(float *m, float angle);
+static inline void cgm_mprerotate_axis(float *m, int idx, float angle);
+static inline void cgm_mprerotate(float *m, float angle, float x, float y, float z);
+static inline void cgm_mprerotate_euler(float *m, float a, float b, float c, int mode);
+static inline void cgm_mprerotate_quat(float *m, const cgm_quat *q);
+
+static inline void cgm_mget_translation(const float *m, cgm_vec3 *res);
+static inline void cgm_mget_rotation(const float *m, cgm_quat *res);
+static inline void cgm_mget_scaling(const float *m, cgm_vec3 *res);
+static inline void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res);
+
+static inline void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+               const cgm_vec3 *up);
+static inline void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+               const cgm_vec3 *up);
+static inline void cgm_mortho(float *m, float left, float right, float bot, float top,
+               float znear, float zfar);
+static inline void cgm_mfrustum(float *m, float left, float right, float bot, float top,
+               float znear, float zfar);
+static inline void cgm_mperspective(float *m, float vfov, float aspect, float znear, float zfar);
+
+static inline void cgm_mmirror(float *m, float a, float b, float c, float d);
+
+/* --- operations on rays --- */
+static inline void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, float dy, float dz);
+
+static inline void cgm_rmul_mr(cgm_ray *ray, const float *m);  /* m4x4 * ray */
+static inline void cgm_rmul_rm(cgm_ray *ray, const float *m);  /* ray * m4x4 */
+
+static inline void cgm_rreflect(cgm_ray *ray, const cgm_vec3 *n);
+static inline void cgm_rrefract(cgm_ray *ray, const cgm_vec3 *n, float ior);
+
+/* --- miscellaneous utility functions --- */
+static inline float cgm_deg_to_rad(float deg);
+static inline float cgm_rad_to_deg(float rad);
+
+static inline float cgm_smoothstep(float a, float b, float x);
+static inline float cgm_lerp(float a, float b, float t);
+static inline float cgm_bezier(float a, float b, float c, float d, float t);
+static inline float cgm_bspline(float a, float b, float c, float d, float t);
+static inline float cgm_spline(float a, float b, float c, float d, float t);
+
+static inline void cgm_discrand(cgm_vec3 *v, float rad);
+static inline void cgm_sphrand(cgm_vec3 *v, float rad);
+
+static inline void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
+               const float *inv_viewproj);
+static inline void cgm_glu_unproject(float winx, float winy, float winz,
+               const float *view, const float *proj, const int *vp,
+               float *objx, float *objy, float *objz);
+
+static inline void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
+               const float *viewmat, const float *projmat);
+
+static inline void cgm_raypos(cgm_vec3 *p, const cgm_ray *ray, float t);
+
+/* calculate barycentric coordinates of point pt in triangle (a, b, c) */
+static inline void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
+               const cgm_vec3 *b, const cgm_vec3 *c, const cgm_vec3 *pt);
+
+/* convert between unit vectors and spherical coordinates */
+static inline void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v);
+static inline void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi);
+
+#include "cgmvec3.inl"
+#include "cgmvec4.inl"
+#include "cgmquat.inl"
+#include "cgmmat.inl"
+#include "cgmray.inl"
+#include "cgmmisc.inl"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CGMATH_H_ */
diff --git a/libs/cgmath/cgmmat.inl b/libs/cgmath/cgmmat.inl
new file mode 100644 (file)
index 0000000..2eb4519
--- /dev/null
@@ -0,0 +1,623 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ */
+static inline void cgm_mcopy(float *dest, const float *src)
+{
+       memcpy(dest, src, 16 * sizeof(float));
+}
+
+static inline void cgm_mzero(float *m)
+{
+       static float z[16];
+       cgm_mcopy(m, z);
+}
+
+static inline void cgm_midentity(float *m)
+{
+       static float id[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
+       cgm_mcopy(m, id);
+}
+
+static inline void cgm_mmul(float *a, const float *b)
+{
+       int i, j;
+       float res[16];
+       float *resptr = res;
+       float *arow = a;
+
+       for(i=0; i<4; i++) {
+               for(j=0; j<4; j++) {
+                       *resptr++ = arow[0] * b[j] + arow[1] * b[4 + j] +
+                               arow[2] * b[8 + j] + arow[3] * b[12 + j];
+               }
+               arow += 4;
+       }
+       cgm_mcopy(a, res);
+}
+
+static inline void cgm_mpremul(float *a, const float *b)
+{
+       int i, j;
+       float res[16];
+       float *resptr = res;
+       const float *brow = b;
+
+       for(i=0; i<4; i++) {
+               for(j=0; j<4; j++) {
+                       *resptr++ = brow[0] * a[j] + brow[1] * a[4 + j] +
+                               brow[2] * a[8 + j] + brow[3] * a[12 + j];
+               }
+               brow += 4;
+       }
+       cgm_mcopy(a, res);
+}
+
+static inline void cgm_msubmatrix(float *m, int row, int col)
+{
+       float orig[16];
+       int i, j, subi, subj;
+
+       cgm_mcopy(orig, m);
+
+       subi = 0;
+       for(i=0; i<4; i++) {
+               if(i == row) continue;
+
+               subj = 0;
+               for(j=0; j<4; j++) {
+                       if(j == col) continue;
+
+                       m[subi * 4 + subj++] = orig[i * 4 + j];
+               }
+               subi++;
+       }
+
+       cgm_mupper3(m);
+}
+
+static inline void cgm_mupper3(float *m)
+{
+       m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0.0f;
+       m[15] = 1.0f;
+}
+
+static inline float cgm_msubdet(const float *m, int row, int col)
+{
+       float tmp[16];
+       float subdet00, subdet01, subdet02;
+
+       cgm_mcopy(tmp, m);
+       cgm_msubmatrix(tmp, row, col);
+
+       subdet00 = tmp[5] * tmp[10] - tmp[6] * tmp[9];
+       subdet01 = tmp[4] * tmp[10] - tmp[6] * tmp[8];
+       subdet02 = tmp[4] * tmp[9] - tmp[5] * tmp[8];
+
+       return tmp[0] * subdet00 - tmp[1] * subdet01 + tmp[2] * subdet02;
+}
+
+static inline float cgm_mcofactor(const float *m, int row, int col)
+{
+       float min = cgm_msubdet(m, row, col);
+       return (row + col) & 1 ? -min : min;
+}
+
+static inline float cgm_mdet(const float *m)
+{
+       return m[0] * cgm_msubdet(m, 0, 0) - m[1] * cgm_msubdet(m, 0, 1) +
+               m[2] * cgm_msubdet(m, 0, 2) - m[3] * cgm_msubdet(m, 0, 3);
+}
+
+static inline void cgm_mtranspose(float *m)
+{
+       int i, j;
+       for(i=0; i<4; i++) {
+               for(j=0; j<i; j++) {
+                       int a = i * 4 + j;
+                       int b = j * 4 + i;
+                       float tmp = m[a];
+                       m[a] = m[b];
+                       m[b] = tmp;
+               }
+       }
+}
+
+static inline void cgm_mcofmatrix(float *m)
+{
+       float tmp[16];
+       int i, j;
+
+       cgm_mcopy(tmp, m);
+
+       for(i=0; i<4; i++) {
+               for(j=0; j<4; j++) {
+                       m[i * 4 + j] = cgm_mcofactor(tmp, i, j);
+               }
+       }
+}
+
+static inline int cgm_minverse(float *m)
+{
+       int i, j;
+       float tmp[16];
+       float inv_det;
+       float det = cgm_mdet(m);
+       if(det == 0.0f) return -1;
+       inv_det = 1.0f / det;
+
+       cgm_mcopy(tmp, m);
+
+       for(i=0; i<4; i++) {
+               for(j=0; j<4; j++) {
+                       m[i * 4 + j] = cgm_mcofactor(tmp, j, i) * inv_det;      /* transposed */
+               }
+       }
+       return 0;
+}
+
+static inline void cgm_mtranslation(float *m, float x, float y, float z)
+{
+       cgm_midentity(m);
+       m[12] = x;
+       m[13] = y;
+       m[14] = z;
+}
+
+static inline void cgm_mscaling(float *m, float sx, float sy, float sz)
+{
+       cgm_mzero(m);
+       m[0] = sx;
+       m[5] = sy;
+       m[10] = sz;
+       m[15] = 1.0f;
+}
+
+static inline void cgm_mrotation_x(float *m, float angle)
+{
+       float sa = sin(angle);
+       float ca = cos(angle);
+
+       cgm_midentity(m);
+       m[5] = ca;
+       m[6] = sa;
+       m[9] = -sa;
+       m[10] = ca;
+}
+
+static inline void cgm_mrotation_y(float *m, float angle)
+{
+       float sa = sin(angle);
+       float ca = cos(angle);
+
+       cgm_midentity(m);
+       m[0] = ca;
+       m[2] = -sa;
+       m[8] = sa;
+       m[10] = ca;
+}
+
+static inline void cgm_mrotation_z(float *m, float angle)
+{
+       float sa = sin(angle);
+       float ca = cos(angle);
+
+       cgm_midentity(m);
+       m[0] = ca;
+       m[1] = sa;
+       m[4] = -sa;
+       m[5] = ca;
+}
+
+static inline void cgm_mrotation_axis(float *m, int idx, float angle)
+{
+       switch(idx) {
+       case 0:
+               cgm_mrotation_x(m, angle);
+               break;
+       case 1:
+               cgm_mrotation_y(m, angle);
+               break;
+       case 2:
+               cgm_mrotation_z(m, angle);
+               break;
+       }
+}
+
+static inline void cgm_mrotation(float *m, float angle, float x, float y, float z)
+{
+       float sa = sin(angle);
+       float ca = cos(angle);
+       float invca = 1.0f - ca;
+       float xsq = x * x;
+       float ysq = y * y;
+       float zsq = z * z;
+
+       cgm_mzero(m);
+       m[15] = 1.0f;
+
+       m[0] = xsq + (1.0f - xsq) * ca;
+       m[4] = x * y * invca - z * sa;
+       m[8] = x * z * invca + y * sa;
+
+       m[1] = x * y * invca + z * sa;
+       m[5] = ysq + (1.0f - ysq) * ca;
+       m[9] = y * z * invca - x * sa;
+
+       m[2] = x * z * invca - y * sa;
+       m[6] = y * z * invca + x * sa;
+       m[10] = zsq + (1.0f - zsq) * ca;
+}
+
+static inline void cgm_mrotation_euler(float *m, float a, float b, float c, int mode)
+{
+       /* this array must match the EulerMode enum */
+       static const int axis[][3] = {
+               {0, 1, 2}, {0, 2, 1},
+               {1, 0, 2}, {1, 2, 0},
+               {2, 0, 1}, {2, 1, 0},
+               {2, 0, 2}, {2, 1, 2},
+               {1, 0, 1}, {1, 2, 1},
+               {0, 1, 0}, {0, 2, 0}
+       };
+
+       float ma[16], mb[16];
+       cgm_mrotation_axis(ma, axis[mode][0], a);
+       cgm_mrotation_axis(mb, axis[mode][1], b);
+       cgm_mrotation_axis(m, axis[mode][2], c);
+       cgm_mmul(m, mb);
+       cgm_mmul(m, ma);
+}
+
+static inline void cgm_mrotation_quat(float *m, const cgm_quat *q)
+{
+       float xsq2 = 2.0f * q->x * q->x;
+       float ysq2 = 2.0f * q->y * q->y;
+       float zsq2 = 2.0f * q->z * q->z;
+       float sx = 1.0f - ysq2 - zsq2;
+       float sy = 1.0f - xsq2 - zsq2;
+       float sz = 1.0f - xsq2 - ysq2;
+
+       m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0.0f;
+       m[15] = 1.0f;
+
+       m[0] = sx;
+       m[1] = 2.0f * q->x * q->y + 2.0f * q->w * q->z;
+       m[2] = 2.0f * q->z * q->x - 2.0f * q->w * q->y;
+       m[4] = 2.0f * q->x * q->y - 2.0f * q->w * q->z;
+       m[5] = sy;
+       m[6] = 2.0f * q->y * q->z + 2.0f * q->w * q->x;
+       m[8] = 2.0f * q->z * q->x + 2.0f * q->w * q->y;
+       m[9] = 2.0f * q->y * q->z - 2.0f * q->w * q->x;
+       m[10] = sz;
+}
+
+static inline void cgm_mtranslate(float *m, float x, float y, float z)
+{
+       float tm[16];
+       cgm_mtranslation(tm, x, y, z);
+       cgm_mmul(m, tm);
+}
+
+static inline void cgm_mscale(float *m, float sx, float sy, float sz)
+{
+       float sm[16];
+       cgm_mscaling(sm, sx, sy, sz);
+       cgm_mmul(m, sm);
+}
+
+static inline void cgm_mrotate_x(float *m, float angle)
+{
+       float rm[16];
+       cgm_mrotation_x(rm, angle);
+       cgm_mmul(m, rm);
+}
+
+static inline void cgm_mrotate_y(float *m, float angle)
+{
+       float rm[16];
+       cgm_mrotation_y(rm, angle);
+       cgm_mmul(m, rm);
+}
+
+static inline void cgm_mrotate_z(float *m, float angle)
+{
+       float rm[16];
+       cgm_mrotation_z(rm, angle);
+       cgm_mmul(m, rm);
+}
+
+static inline void cgm_mrotate_axis(float *m, int idx, float angle)
+{
+       float rm[16];
+       cgm_mrotation_axis(rm, idx, angle);
+       cgm_mmul(m, rm);
+}
+
+static inline void cgm_mrotate(float *m, float angle, float x, float y, float z)
+{
+       float rm[16];
+       cgm_mrotation(rm, angle, x, y, z);
+       cgm_mmul(m, rm);
+}
+
+static inline void cgm_mrotate_euler(float *m, float a, float b, float c, int mode)
+{
+       float rm[16];
+       cgm_mrotation_euler(rm, a, b, c, mode);
+       cgm_mmul(m, rm);
+}
+
+static inline void cgm_mrotate_quat(float *m, const cgm_quat *q)
+{
+       float rm[16];
+       cgm_mrotation_quat(rm, q);
+       cgm_mmul(m, rm);
+}
+
+
+static inline void cgm_mpretranslate(float *m, float x, float y, float z)
+{
+       float tm[16];
+       cgm_mtranslation(tm, x, y, z);
+       cgm_mpremul(m, tm);
+}
+
+static inline void cgm_mprescale(float *m, float sx, float sy, float sz)
+{
+       float sm[16];
+       cgm_mscaling(sm, sx, sy, sz);
+       cgm_mpremul(m, sm);
+}
+
+static inline void cgm_mprerotate_x(float *m, float angle)
+{
+       float rm[16];
+       cgm_mrotation_x(rm, angle);
+       cgm_mpremul(m, rm);
+}
+
+static inline void cgm_mprerotate_y(float *m, float angle)
+{
+       float rm[16];
+       cgm_mrotation_y(rm, angle);
+       cgm_mpremul(m, rm);
+}
+
+static inline void cgm_mprerotate_z(float *m, float angle)
+{
+       float rm[16];
+       cgm_mrotation_z(rm, angle);
+       cgm_mpremul(m, rm);
+}
+
+static inline void cgm_mprerotate_axis(float *m, int idx, float angle)
+{
+       float rm[16];
+       cgm_mrotation_axis(rm, idx, angle);
+       cgm_mpremul(m, rm);
+}
+
+static inline void cgm_mprerotate(float *m, float angle, float x, float y, float z)
+{
+       float rm[16];
+       cgm_mrotation(rm, angle, x, y, z);
+       cgm_mpremul(m, rm);
+}
+
+static inline void cgm_mprerotate_euler(float *m, float a, float b, float c, int mode)
+{
+       float rm[16];
+       cgm_mrotation_euler(rm, a, b, c, mode);
+       cgm_mpremul(m, rm);
+}
+
+static inline void cgm_mprerotate_quat(float *m, const cgm_quat *q)
+{
+       float rm[16];
+       cgm_mrotation_quat(rm, q);
+       cgm_mpremul(m, rm);
+}
+
+
+static inline void cgm_mget_translation(const float *m, cgm_vec3 *res)
+{
+       res->x = m[12];
+       res->y = m[13];
+       res->z = m[14];
+}
+
+/* Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
+ * article "Quaternion Calculus and Fast Animation".
+ * adapted from: http://www.geometrictools.com/LibMathematics/Algebra/Wm5Quaternion.inl
+ */
+static inline void cgm_mget_rotation(const float *m, cgm_quat *res)
+{
+       static const int next[3] = {1, 2, 0};
+       float quat[4];
+       int i, j, k;
+
+       float trace = m[0] + m[5] + m[10];
+       float root;
+
+       if(trace > 0.0f) {
+               /* |w| > 1/2 */
+               root = sqrt(trace + 1.0f);      /* 2w */
+               res->w = 0.5f * root;
+               root = 0.5f / root;     /* 1 / 4w */
+               res->x = (m[6] - m[9]) * root;
+               res->y = (m[8] - m[2]) * root;
+               res->z = (m[1] - m[4]) * root;
+       } else {
+               /* |w| <= 1/2 */
+               i = 0;
+               if(m[5] > m[0]) {
+                       i = 1;
+               }
+               if(m[10] > m[i * 4 + i]) {
+                       i = 2;
+               }
+               j = next[i];
+               k = next[j];
+
+               root = sqrt(m[i * 4 + i] - m[j * 4 + j] - m[k * 4 + k] + 1.0f);
+               quat[i + 1] = 0.5f * root;
+               root = 0.5f / root;
+               quat[0] = (m[j + 4 + k] - m[k * 4 + j]) * root;
+               quat[j + 1] = (m[i * 4 + j] - m[j * 4 + i]) * root;
+               quat[k + 1] = (m[i * 4 + k] - m[k * 4 + i]) * root;
+               res->w = quat[0];
+               res->x = quat[1];
+               res->y = quat[2];
+               res->z = quat[3];
+       }
+}
+
+static inline void cgm_mget_scaling(const float *m, cgm_vec3 *res)
+{
+       res->x = sqrt(m[0] * m[0] + m[4] * m[4] + m[8] * m[8]);
+       res->y = sqrt(m[1] * m[1] + m[5] * m[5] + m[9] * m[9]);
+       res->z = sqrt(m[2] * m[2] + m[6] * m[6] + m[10] * m[10]);
+}
+
+static inline void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res)
+{
+       int row = p >> 1;
+       const float *rowptr = m + row * 4;
+
+       if((p & 1) == 0) {
+               res->x = m[12] + rowptr[0];
+               res->y = m[13] + rowptr[1];
+               res->z = m[14] + rowptr[2];
+               res->w = m[15] + rowptr[3];
+       } else {
+               res->x = m[12] - rowptr[0];
+               res->y = m[13] - rowptr[1];
+               res->z = m[14] - rowptr[2];
+               res->w = m[15] - rowptr[3];
+       }
+}
+
+static inline void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+               const cgm_vec3 *up)
+{
+       float trans[16];
+       cgm_vec3 dir = *targ, right, vup;
+
+       cgm_vsub(&dir, pos);
+       cgm_vnormalize(&dir);
+       cgm_vcross(&right, &dir, up);
+       cgm_vnormalize(&right);
+       cgm_vcross(&vup, &right, &dir);
+       cgm_vnormalize(&vup);
+
+       cgm_midentity(m);
+       m[0] = right.x;
+       m[1] = right.y;
+       m[2] = right.z;
+       m[4] = vup.x;
+       m[5] = vup.y;
+       m[6] = vup.z;
+       m[8] = -dir.x;
+       m[9] = -dir.y;
+       m[10] = -dir.z;
+
+       cgm_mtranslation(trans, pos->x, pos->y, pos->z);
+       cgm_mmul(m, trans);
+}
+
+static inline void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+               const cgm_vec3 *up)
+{
+       float rot[16];
+       cgm_vec3 dir = *targ, right, vup;
+
+       cgm_vsub(&dir, pos);
+       cgm_vnormalize(&dir);
+       cgm_vcross(&right, &dir, up);
+       cgm_vnormalize(&right);
+       cgm_vcross(&vup, &right, &dir);
+       cgm_vnormalize(&vup);
+
+       cgm_midentity(rot);
+       rot[0] = right.x;
+       rot[4] = right.y;
+       rot[8] = right.z;
+       rot[1] = vup.x;
+       rot[5] = vup.y;
+       rot[9] = vup.z;
+       rot[2] = -dir.x;
+       rot[6] = -dir.y;
+       rot[10] = -dir.z;
+
+       cgm_mtranslation(m, -pos->x, -pos->y, -pos->z);
+       cgm_mmul(m, rot);
+}
+
+static inline void cgm_mortho(float *m, float left, float right, float bot, float top,
+               float znear, float zfar)
+{
+       float dx = right - left;
+       float dy = top - bot;
+       float dz = zfar - znear;
+
+       cgm_midentity(m);
+       m[0] = 2.0f / dx;
+       m[5] = 2.0f / dy;
+       m[10] = -2.0f / dz;
+       m[12] = -(right + left) / dx;
+       m[13] = -(top + bot) / dy;
+       m[14] = -(zfar + znear) / dz;
+}
+
+static inline void cgm_mfrustum(float *m, float left, float right, float bot, float top,
+               float znear, float zfar)
+{
+       float dx = right - left;
+       float dy = top - bot;
+       float dz = zfar - znear;
+
+       cgm_mzero(m);
+       m[0] = 2.0f * znear / dx;
+       m[5] = 2.0f * znear / dy;
+       m[8] = (right + left) / dx;
+       m[9] = (top + bot) / dy;
+       m[10] = -(zfar + znear) / dz;
+       m[14] = -2.0f * zfar * znear / dz;
+       m[11] = -1.0f;
+}
+
+static inline void cgm_mperspective(float *m, float vfov, float aspect, float znear, float zfar)
+{
+       float s = 1.0f / (float)tan(vfov / 2.0f);
+       float range = znear - zfar;
+
+       cgm_mzero(m);
+       m[0] = s / aspect;
+       m[5] = s;
+       m[10] = (znear + zfar) / range;
+       m[14] = 2.0f * znear * zfar / range;
+       m[11] = -1.0f;
+}
+
+static inline void cgm_mmirror(float *m, float a, float b, float c, float d)
+{
+       m[0] = 1.0f - 2.0f * a * a;
+       m[5] = 1.0f - 2.0f * b * b;
+       m[10] = 1.0f - 2.0f * c * c;
+       m[15] = 1.0f;
+
+       m[1] = m[4] = -2.0f * a * b;
+       m[2] = m[8] = -2.0f * a * c;
+       m[6] = m[9] = -2.0f * b * c;
+
+       m[12] = -2.0f * a * d;
+       m[13] = -2.0f * b * d;
+       m[14] = -2.0f * c * d;
+
+       m[3] = m[7] = m[11] = 0.0f;
+}
diff --git a/libs/cgmath/cgmmisc.inl b/libs/cgmath/cgmmisc.inl
new file mode 100644 (file)
index 0000000..bf9e958
--- /dev/null
@@ -0,0 +1,203 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ */
+#include <stdlib.h>
+
+static inline float cgm_deg_to_rad(float deg)
+{
+       return M_PI * deg / 180.0f;
+}
+
+static inline float cgm_rad_to_deg(float rad)
+{
+       return 180.0f * rad / M_PI;
+}
+
+static inline float cgm_smoothstep(float a, float b, float x)
+{
+       if(x < a) return 0.0f;
+       if(x >= b) return 1.0f;
+
+       x = (x - a) / (b - a);
+       return x * x * (3.0f - 2.0f * x);
+}
+
+static inline float cgm_lerp(float a, float b, float t)
+{
+       return a + (b - a) * t;
+}
+
+static inline float cgm_bezier(float a, float b, float c, float d, float t)
+{
+       float omt, omt3, t3, f;
+       t3 = t * t * t;
+       omt = 1.0f - t;
+       omt3 = omt * omt * omt;
+       f = 3.0f * t * omt;
+
+       return (a * omt3) + (b * f * omt) + (c * f * t) + (d * t3);
+}
+
+static inline float cgm_bspline(float a, float b, float c, float d, float t)
+{
+       static const float mat[] = {
+               -1, 3, -3, 1,
+               3, -6, 0, 4,
+               -3, 3, 3, 1,
+               1, 0, 0, 0
+       };
+       cgm_vec4 tmp, qfact;
+       float tsq = t * t;
+
+       cgm_wcons(&qfact, tsq * t, tsq, t, 1.0f);
+       cgm_wcons(&tmp, a, b, c, d);
+       cgm_wmul_m4v4(&tmp, mat);
+       cgm_wscale(&tmp, 1.0f / 6.0f);
+       return cgm_wdot(&tmp, &qfact);
+}
+
+static inline float cgm_spline(float a, float b, float c, float d, float t)
+{
+       static const float mat[] = {
+               -1, 2, -1, 0,
+               3, -5, 0, 2,
+               -3, 4, 1, 0,
+               1, -1, 0, 0
+       };
+       cgm_vec4 tmp, qfact;
+       float tsq = t * t;
+
+       cgm_wcons(&qfact, tsq * t, tsq, t, 1.0f);
+       cgm_wcons(&tmp, a, b, c, d);
+       cgm_wmul_m4v4(&tmp, mat);
+       cgm_wscale(&tmp, 1.0f / 6.0f);
+       return cgm_wdot(&tmp, &qfact);
+}
+
+static inline void cgm_discrand(cgm_vec3 *pt, float rad)
+{
+       float theta = 2.0f * M_PI * (float)rand() / RAND_MAX;
+       float r = sqrt((float)rand() / RAND_MAX) * rad;
+       pt->x = cos(theta) * r;
+       pt->y = sin(theta) * r;
+       pt->z = 0.0f;
+}
+
+static inline void cgm_sphrand(cgm_vec3 *pt, float rad)
+{
+       float u, v, theta, phi;
+
+       u = (float)rand() / RAND_MAX;
+       v = (float)rand() / RAND_MAX;
+
+       theta = 2.0f * M_PI * u;
+       phi = acos(2.0f * v - 1.0f);
+
+       pt->x = cos(theta) * sin(phi) * rad;
+       pt->y = sin(theta) * sin(phi) * rad;
+       pt->z = cos(phi) * rad;
+}
+
+static inline void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
+               const float *inv_viewproj)
+{
+       cgm_vec4 pos;
+
+       pos.x = 2.0f * norm_scrpos->x - 1.0f;
+       pos.y = 2.0f * norm_scrpos->y - 1.0f;
+       pos.z = 2.0f * norm_scrpos->z - 1.0f;
+       pos.w = 1.0f;
+
+       cgm_wmul_m4v4(&pos, inv_viewproj);
+
+       res->x = pos.x / pos.w;
+       res->y = pos.y / pos.w;
+       res->z = pos.z / pos.w;
+}
+
+static inline void cgm_glu_unproject(float winx, float winy, float winz,
+               const float *view, const float *proj, const int *vp,
+               float *objx, float *objy, float *objz)
+{
+       cgm_vec3 npos, res;
+       float inv_pv[16];
+
+       cgm_mcopy(inv_pv, proj);
+       cgm_mmul(inv_pv, view);
+
+       npos.x = (winx - vp[0]) / vp[2];
+       npos.y = (winy - vp[1]) / vp[4];
+       npos.z = winz;
+
+       cgm_unproject(&res, &npos, inv_pv);
+
+       *objx = res.x;
+       *objy = res.y;
+       *objz = res.z;
+}
+
+static inline void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
+               const float *viewmat, const float *projmat)
+{
+       cgm_vec3 npos, farpt;
+       float inv_pv[16];
+
+       cgm_mcopy(inv_pv, projmat);
+       cgm_mmul(inv_pv, viewmat);
+
+       cgm_vcons(&npos, nx, ny, 0.0f);
+       cgm_unproject(&ray->origin, &npos, inv_pv);
+       npos.z = 1.0f;
+       cgm_unproject(&farpt, &npos, inv_pv);
+
+       ray->dir.x = farpt.x - ray->origin.x;
+       ray->dir.y = farpt.y - ray->origin.y;
+       ray->dir.z = farpt.z - ray->origin.z;
+}
+
+static inline void cgm_raypos(cgm_vec3 *p, const cgm_ray *ray, float t)
+{
+       p->x = ray->origin.x + ray->dir.x * t;
+       p->y = ray->origin.y + ray->dir.y * t;
+       p->z = ray->origin.z + ray->dir.z * t;
+}
+
+static inline void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
+               const cgm_vec3 *b, const cgm_vec3 *c, const cgm_vec3 *pt)
+{
+       float d00, d01, d11, d20, d21, denom;
+       cgm_vec3 v0 = *b, v1 = *c, v2 = *pt;
+
+       cgm_vsub(&v0, a);
+       cgm_vsub(&v1, a);
+       cgm_vsub(&v2, a);
+
+       d00 = cgm_vdot(&v0, &v0);
+       d01 = cgm_vdot(&v0, &v1);
+       d11 = cgm_vdot(&v1, &v1);
+       d20 = cgm_vdot(&v2, &v0);
+       d21 = cgm_vdot(&v2, &v1);
+       denom = d00 * d11 - d01 * d01;
+
+       bary->y = (d11 * d20 - d01 * d21) / denom;
+       bary->z = (d00 * d21 - d01 * d20) / denom;
+       bary->x = 1.0f - bary->y - bary->z;
+}
+
+static inline void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v)
+{
+       *theta = atan2(v->z, v->x);
+       *phi = acos(v->y);
+}
+
+static inline void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi)
+{
+       v->x = sin(theta) * cos(phi);
+       v->y = sin(phi);
+       v->z = cos(theta) * cos(phi);
+}
diff --git a/libs/cgmath/cgmquat.inl b/libs/cgmath/cgmquat.inl
new file mode 100644 (file)
index 0000000..743d818
--- /dev/null
@@ -0,0 +1,159 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ */
+static inline void cgm_qcons(cgm_quat *q, float x, float y, float z, float w)
+{
+       q->x = x;
+       q->y = y;
+       q->z = z;
+       q->w = w;
+}
+
+
+static inline void cgm_qneg(cgm_quat *q)
+{
+       q->x = -q->x;
+       q->y = -q->y;
+       q->z = -q->z;
+       q->w = -q->w;
+}
+
+static inline void cgm_qadd(cgm_quat *a, const cgm_quat *b)
+{
+       a->x += b->x;
+       a->y += b->y;
+       a->z += b->z;
+       a->w += b->w;
+}
+
+static inline void cgm_qsub(cgm_quat *a, const cgm_quat *b)
+{
+       a->x -= b->x;
+       a->y -= b->y;
+       a->z -= b->z;
+       a->w -= b->w;
+}
+
+static inline void cgm_qmul(cgm_quat *a, const cgm_quat *b)
+{
+       float x, y, z, dot;
+       cgm_vec3 cross;
+
+       dot = a->x * b->x + a->y * b->y + a->z * b->z;
+       cgm_vcross(&cross, (cgm_vec3*)a, (cgm_vec3*)b);
+
+       x = a->w * b->x + b->w * a->x + cross.x;
+       y = a->w * b->y + b->w * a->y + cross.y;
+       z = a->w * b->z + b->w * a->z + cross.z;
+       a->w = a->w * b->w - dot;
+       a->x = x;
+       a->y = y;
+       a->z = z;
+}
+
+static inline float cgm_qlength(const cgm_quat *q)
+{
+       return sqrt(q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w);
+}
+
+static inline float cgm_qlength_sq(const cgm_quat *q)
+{
+       return q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w;
+}
+
+static inline void cgm_qnormalize(cgm_quat *q)
+{
+       float len = cgm_qlength(q);
+       if(len != 0.0f) {
+               float s = 1.0f / len;
+               q->x *= s;
+               q->y *= s;
+               q->z *= s;
+               q->w *= s;
+       }
+}
+
+static inline void cgm_qconjugate(cgm_quat *q)
+{
+       q->x = -q->x;
+       q->y = -q->y;
+       q->z = -q->z;
+}
+
+static inline void cgm_qinvert(cgm_quat *q)
+{
+       float len_sq = cgm_qlength_sq(q);
+       cgm_qconjugate(q);
+       if(len_sq != 0.0f) {
+               float s = 1.0f / len_sq;
+               q->x *= s;
+               q->y *= s;
+               q->z *= s;
+               q->w *= s;
+       }
+}
+
+static inline void cgm_qrotation(cgm_quat *q, float angle, float x, float y, float z)
+{
+       float hangle = angle * 0.5f;
+       float sin_ha = sin(hangle);
+       q->w = cos(hangle);
+       q->x = x * sin_ha;
+       q->y = y * sin_ha;
+       q->z = z * sin_ha;
+}
+
+static inline void cgm_qrotate(cgm_quat *q, float angle, float x, float y, float z)
+{
+       cgm_quat qrot;
+       cgm_qrotation(&qrot, angle, x, y, z);
+       cgm_qmul(q, &qrot);
+}
+
+static inline void cgm_qslerp(cgm_quat *res, const cgm_quat *quat1, const cgm_quat *q2, float t)
+{
+       float angle, dot, a, b, sin_angle;
+       cgm_quat q1 = *quat1;
+
+       dot = quat1->x * q2->x + quat1->y * q2->y + quat1->z * q2->z + quat1->w * q2->w;
+       if(dot < 0.0f) {
+               /* make sure we inteprolate across the shortest arc */
+               cgm_qneg(&q1);
+               dot = -dot;
+       }
+
+       /* clamp dot to [-1, 1] in order to avoid domain errors in acos due to
+        * floating point imprecisions
+        */
+       if(dot < -1.0f) dot = -1.0f;
+       if(dot > 1.0f) dot = 1.0f;
+       angle = acos(dot);
+
+       sin_angle = sin(angle);
+       if(sin_angle == 0.0f) {
+               /* use linear interpolation to avoid div/zero */
+               a = 1.0f;
+               b = t;
+       } else {
+               a = sin((1.0f - t) * angle) / sin_angle;
+               b = sin(t * angle) / sin_angle;
+       }
+
+       res->x = q1.x * a + q2->x * b;
+       res->y = q1.y * a + q2->y * b;
+       res->z = q1.z * a + q2->z * b;
+       res->w = q1.w * a + q2->w * b;
+}
+
+static inline void cgm_qlerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t)
+{
+       res->x = a->x + (b->x - a->x) * t;
+       res->y = a->y + (b->y - a->y) * t;
+       res->z = a->z + (b->z - a->z) * t;
+       res->w = a->w + (b->w - a->w) * t;
+}
diff --git a/libs/cgmath/cgmray.inl b/libs/cgmath/cgmray.inl
new file mode 100644 (file)
index 0000000..063a7e0
--- /dev/null
@@ -0,0 +1,39 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ */
+static inline void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, float dy, float dz)
+{
+       r->origin.x = x;
+       r->origin.y = y;
+       r->origin.z = z;
+       r->dir.x = dx;
+       r->dir.y = dy;
+       r->dir.z = dz;
+}
+
+static inline void cgm_rmul_mr(cgm_ray *ray, const float *m)
+{
+       cgm_vmul_m4v3(&ray->origin, m);
+       cgm_vmul_m3v3(&ray->dir, m);
+}
+
+static inline void cgm_rmul_rm(cgm_ray *ray, const float *m)
+{
+       cgm_vmul_v3m4(&ray->origin, m);
+       cgm_vmul_v3m3(&ray->dir, m);
+}
+
+static inline void cgm_rreflect(cgm_ray *ray, const cgm_vec3 *n)
+{
+       cgm_vreflect(&ray->dir, n);
+}
+
+static inline void cgm_rrefract(cgm_ray *ray, const cgm_vec3 *n, float ior)
+{
+       cgm_vrefract(&ray->dir, n, ior);
+}
diff --git a/libs/cgmath/cgmvec3.inl b/libs/cgmath/cgmvec3.inl
new file mode 100644 (file)
index 0000000..0a2ec4b
--- /dev/null
@@ -0,0 +1,205 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ */
+static inline void cgm_vcons(cgm_vec3 *v, float x, float y, float z)
+{
+       v->x = x;
+       v->y = y;
+       v->z = z;
+}
+
+static inline void cgm_vadd(cgm_vec3 *a, const cgm_vec3 *b)
+{
+       a->x += b->x;
+       a->y += b->y;
+       a->z += b->z;
+}
+
+static inline void cgm_vadd_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s)
+{
+       a->x += b->x * s;
+       a->y += b->y * s;
+       a->z += b->z * s;
+}
+
+static inline void cgm_vsub(cgm_vec3 *a, const cgm_vec3 *b)
+{
+       a->x -= b->x;
+       a->y -= b->y;
+       a->z -= b->z;
+}
+
+static inline void cgm_vsub_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s)
+{
+       a->x -= b->x * s;
+       a->y -= b->y * s;
+       a->z -= b->z * s;
+}
+
+static inline void cgm_vmul(cgm_vec3 *a, const cgm_vec3 *b)
+{
+       a->x *= b->x;
+       a->y *= b->y;
+       a->z *= b->z;
+}
+
+static inline void cgm_vscale(cgm_vec3 *v, float s)
+{
+       v->x *= s;
+       v->y *= s;
+       v->z *= s;
+}
+
+static inline void cgm_vmul_m4v3(cgm_vec3 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[4] + v->z * m[8] + m[12];
+       float y = v->x * m[1] + v->y * m[5] + v->z * m[9] + m[13];
+       v->z = v->x * m[2] + v->y * m[6] + v->z * m[10] + m[14];
+       v->x = x;
+       v->y = y;
+}
+
+static inline void cgm_vmul_v3m4(cgm_vec3 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[1] + v->z * m[2] + m[3];
+       float y = v->x * m[4] + v->y * m[5] + v->z * m[6] + m[7];
+       v->z = v->x * m[8] + v->y * m[9] + v->z * m[10] + m[11];
+       v->x = x;
+       v->y = y;
+}
+
+static inline void cgm_vmul_m3v3(cgm_vec3 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[4] + v->z * m[8];
+       float y = v->x * m[1] + v->y * m[5] + v->z * m[9];
+       v->z = v->x * m[2] + v->y * m[6] + v->z * m[10];
+       v->x = x;
+       v->y = y;
+}
+
+static inline void cgm_vmul_v3m3(cgm_vec3 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[1] + v->z * m[2];
+       float y = v->x * m[4] + v->y * m[5] + v->z * m[6];
+       v->z = v->x * m[8] + v->y * m[9] + v->z * m[10];
+       v->x = x;
+       v->y = y;
+}
+
+static inline float cgm_vdot(const cgm_vec3 *a, const cgm_vec3 *b)
+{
+       return a->x * b->x + a->y * b->y + a->z * b->z;
+}
+
+static inline void cgm_vcross(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b)
+{
+       res->x = a->y * b->z - a->z * b->y;
+       res->y = a->z * b->x - a->x * b->z;
+       res->z = a->x * b->y - a->y * b->x;
+}
+
+static inline float cgm_vlength(const cgm_vec3 *v)
+{
+       return sqrt(v->x * v->x + v->y * v->y + v->z * v->z);
+}
+
+static inline float cgm_vlength_sq(const cgm_vec3 *v)
+{
+       return v->x * v->x + v->y * v->y + v->z * v->z;
+}
+
+static inline float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b)
+{
+       float dx = a->x - b->x;
+       float dy = a->y - b->y;
+       float dz = a->z - b->z;
+       return sqrt(dx * dx + dy * dy + dz * dz);
+}
+
+static inline float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b)
+{
+       float dx = a->x - b->x;
+       float dy = a->y - b->y;
+       float dz = a->z - b->z;
+       return dx * dx + dy * dy + dz * dz;
+}
+
+static inline void cgm_vnormalize(cgm_vec3 *v)
+{
+       float len = cgm_vlength(v);
+       if(len != 0.0f) {
+               float s = 1.0f / len;
+               v->x *= s;
+               v->y *= s;
+               v->z *= s;
+       }
+}
+
+static inline void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n)
+{
+       float ndotv2 = cgm_vdot(v, n) * 2.0f;
+       v->x -= n->x * ndotv2;
+       v->y -= n->y * ndotv2;
+       v->z -= n->z * ndotv2;
+}
+
+static inline int cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior)
+{
+       float sqrt_k;
+       float ndotv = cgm_vdot(v, n);
+       float k = 1.0f - ior * ior * (1.0f - ndotv * ndotv);
+
+       if(k < 0.0f) {
+               cgm_vreflect(v, n);     /* TIR */
+               return -1;
+       }
+       sqrt_k = sqrt(k);
+       v->x = ior * v->x - (ior * ndotv + sqrt_k) * n->x;
+       v->y = ior * v->y - (ior * ndotv + sqrt_k) * n->y;
+       v->z = ior * v->z - (ior * ndotv + sqrt_k) * n->z;
+       return 0;
+}
+
+static inline void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q)
+{
+       cgm_quat vq, inv_q = *q, tmp_q = *q;
+
+       cgm_qcons(&vq, v->x, v->y, v->z, 0.0f);
+       cgm_qinvert(&inv_q);
+       cgm_qmul(&tmp_q, &vq);
+       cgm_qmul(&tmp_q, &inv_q);
+       cgm_vcons(v, tmp_q.x, tmp_q.y, tmp_q.z);
+}
+
+static inline void cgm_vrotate_axis(cgm_vec3 *v, int axis, float angle)
+{
+       float m[16];
+       cgm_mrotation_axis(m, axis, angle);
+       cgm_vmul_m3v3(v, m);
+}
+
+static inline void cgm_vrotate(cgm_vec3 *v, float angle, float x, float y, float z)
+{
+       float m[16];
+       cgm_mrotation(m, angle, x, y, z);
+       cgm_vmul_m3v3(v, m);
+}
+
+static inline void cgm_vrotate_euler(cgm_vec3 *v, float a, float b, float c, enum cgm_euler_mode mode)
+{
+       float m[16];
+       cgm_mrotation_euler(m, a, b, c, mode);
+       cgm_vmul_m3v3(v, m);
+}
+
+static inline void cgm_vlerp(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b, float t)
+{
+       res->x = a->x + (b->x - a->x) * t;
+       res->y = a->y + (b->y - a->y) * t;
+       res->z = a->z + (b->z - a->z) * t;
+}
diff --git a/libs/cgmath/cgmvec4.inl b/libs/cgmath/cgmvec4.inl
new file mode 100644 (file)
index 0000000..b68856c
--- /dev/null
@@ -0,0 +1,158 @@
+/* gph-cmath - C graphics math library
+ * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ *
+ * This program is free software. Feel free to use, modify, and/or redistribute
+ * it under the terms of the MIT/X11 license. See LICENSE for details.
+ * If you intend to redistribute parts of the code without the LICENSE file
+ * replace this paragraph with the full contents of the LICENSE file.
+ */
+static inline void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w)
+{
+       v->x = x;
+       v->y = y;
+       v->z = z;
+       v->w = w;
+}
+
+static inline void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b)
+{
+       a->x += b->x;
+       a->y += b->y;
+       a->z += b->z;
+       a->w += b->w;
+}
+
+static inline void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b)
+{
+       a->x -= b->x;
+       a->y -= b->y;
+       a->z -= b->z;
+       a->w -= b->w;
+}
+
+static inline void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b)
+{
+       a->x *= b->x;
+       a->y *= b->y;
+       a->z *= b->z;
+       a->w *= b->w;
+}
+
+static inline void cgm_wscale(cgm_vec4 *v, float s)
+{
+       v->x *= s;
+       v->y *= s;
+       v->z *= s;
+       v->w *= s;
+}
+
+static inline void cgm_wmul_m4v4(cgm_vec4 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[4] + v->z * m[8] + v->w * m[12];
+       float y = v->x * m[1] + v->y * m[5] + v->z * m[9] + v->w * m[13];
+       float z = v->x * m[2] + v->y * m[6] + v->z * m[10] + v->w * m[14];
+       v->w = v->x * m[3] + v->y * m[7] + v->z * m[11] + v->w * m[15];
+       v->x = x;
+       v->y = y;
+       v->z = z;
+}
+
+static inline void cgm_wmul_v4m4(cgm_vec4 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[1] + v->z * m[2] + v->w * m[3];
+       float y = v->x * m[4] + v->y * m[5] + v->z * m[6] + v->w * m[7];
+       float z = v->x * m[8] + v->y * m[9] + v->z * m[10] + v->w * m[11];
+       v->w = v->x * m[12] + v->y * m[13] + v->z * m[14] + v->w * m[15];
+       v->x = x;
+       v->y = y;
+       v->z = z;
+}
+
+static inline void cgm_wmul_m34v4(cgm_vec4 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[4] + v->z * m[8] + v->w * m[12];
+       float y = v->x * m[1] + v->y * m[5] + v->z * m[9] + v->w * m[13];
+       v->z = v->x * m[2] + v->y * m[6] + v->z * m[10] + v->w * m[14];
+       v->x = x;
+       v->y = y;
+}
+
+static inline void cgm_wmul_v4m43(cgm_vec4 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[1] + v->z * m[2] + v->w * m[3];
+       float y = v->x * m[4] + v->y * m[5] + v->z * m[6] + v->w * m[7];
+       v->z = v->x * m[8] + v->y * m[9] + v->z * m[10] + v->w * m[11];
+       v->x = x;
+       v->y = y;
+}
+
+static inline void cgm_wmul_m3v4(cgm_vec4 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[4] + v->z * m[8];
+       float y = v->x * m[1] + v->y * m[5] + v->z * m[9];
+       v->z = v->x * m[2] + v->y * m[6] + v->z * m[10];
+       v->x = x;
+       v->y = y;
+}
+
+static inline void cgm_wmul_v4m3(cgm_vec4 *v, const float *m)
+{
+       float x = v->x * m[0] + v->y * m[1] + v->z * m[2];
+       float y = v->x * m[4] + v->y * m[5] + v->z * m[6];
+       v->z = v->x * m[8] + v->y * m[9] + v->z * m[10];
+       v->x = x;
+       v->y = y;
+}
+
+static inline float cgm_wdot(const cgm_vec4 *a, const cgm_vec4 *b)
+{
+       return a->x * b->x + a->y * b->y + a->z * b->z + a->w * b->w;
+}
+
+static inline float cgm_wlength(const cgm_vec4 *v)
+{
+       return sqrt(v->x * v->x + v->y * v->y + v->z * v->z + v->w * v->w);
+}
+
+static inline float cgm_wlength_sq(const cgm_vec4 *v)
+{
+       return v->x * v->x + v->y * v->y + v->z * v->z + v->w * v->w;
+}
+
+static inline float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b)
+{
+       float dx = a->x - b->x;
+       float dy = a->y - b->y;
+       float dz = a->z - b->z;
+       float dw = a->w - b->w;
+       return sqrt(dx * dx + dy * dy + dz * dz + dw * dw);
+}
+
+static inline float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b)
+{
+       float dx = a->x - b->x;
+       float dy = a->y - b->y;
+       float dz = a->z - b->z;
+       float dw = a->w - b->w;
+       return dx * dx + dy * dy + dz * dz + dw * dw;
+}
+
+static inline void cgm_wnormalize(cgm_vec4 *v)
+{
+       float len = cgm_wlength(v);
+       if(len != 0.0f) {
+               float s = 1.0f / len;
+               v->x *= s;
+               v->y *= s;
+               v->z *= s;
+               v->w *= s;
+       }
+}
+
+static inline void cgm_wlerp(cgm_vec4 *res, const cgm_vec4 *a, const cgm_vec4 *b, float t)
+{
+       res->x = a->x + (b->x - a->x) * t;
+       res->y = a->y + (b->y - a->y) * t;
+       res->z = a->z + (b->z - a->z) * t;
+       res->w = a->w + (b->w - a->w) * t;
+}
diff --git a/libs/psys/Makefile b/libs/psys/Makefile
new file mode 100644 (file)
index 0000000..9d434ac
--- /dev/null
@@ -0,0 +1,28 @@
+src = $(wildcard *.c)
+obj = $(src:.c=.o)
+alib = ../unix/libpsys.a
+
+sys ?= $(shell uname -s | sed 's/MINGW.*/mingw/')
+ifeq ($(sys), mingw)
+       obj = $(src:.c=.w32.o)
+       alib = ../w32/libpsys.a
+endif
+ifeq ($(sys), android-arm64)
+       obj = $(src:.c=.arm64.o)
+       alib = ../android/libpsys.a
+endif
+
+CFLAGS = -O3 -ffast-math -fno-strict-aliasing -I..
+
+$(alib): $(obj)
+       $(AR) rcs $@ $(obj)
+
+%.arm64.o: %.c
+       $(CC) -o $@ $(CFLAGS) -c $<
+
+%.w32.o: %.c
+       $(CC) -o $@ $(CFLAGS) -c $<
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(alib)
diff --git a/libs/psys/pattr.c b/libs/psys/pattr.c
new file mode 100644 (file)
index 0000000..9f0f9ad
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+
+#ifdef __MSVCRT__
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+#include "pattr.h"
+#include "psys_gl.h"
+
+enum {
+       OPT_STR,
+       OPT_NUM,
+       OPT_NUM_RANGE,
+       OPT_VEC,
+       OPT_VEC_RANGE
+};
+
+struct cfgopt {
+       char *name;
+       int type;
+       long tm;
+       char *valstr;
+       float val[3], valrng[3];
+};
+
+static int init_particle_attr(struct psys_particle_attributes *pattr);
+static void destroy_particle_attr(struct psys_particle_attributes *pattr);
+static struct cfgopt *get_cfg_opt(const char *line);
+static void release_cfg_opt(struct cfgopt *opt);
+static char *stripspace(char *str);
+
+static void *tex_cls;
+static unsigned int (*load_texture)(const char*, void*) = psys_gl_load_texture;
+static void (*unload_texture)(unsigned int, void*) = psys_gl_unload_texture;
+
+
+void psys_texture_loader(unsigned int (*load)(const char*, void*), void (*unload)(unsigned int, void*), void *cls)
+{
+       load_texture = load;
+       unload_texture = unload;
+       tex_cls = cls;
+}
+
+struct psys_attributes *psys_create_attr(void)
+{
+       struct psys_attributes *attr = malloc(sizeof *attr);
+       if(attr) {
+               if(psys_init_attr(attr) == -1) {
+                       free(attr);
+                       attr = 0;
+               }
+       }
+       return attr;
+}
+
+void psys_free_attr(struct psys_attributes *attr)
+{
+       psys_destroy_attr(attr);
+       free(attr);
+}
+
+int psys_init_attr(struct psys_attributes *attr)
+{
+       memset(attr, 0, sizeof *attr);
+
+       if(psys_init_track3(&attr->spawn_range) == -1)
+               goto err;
+       if(psys_init_track(&attr->rate) == -1)
+               goto err;
+       if(psys_init_anm_rnd(&attr->life) == -1)
+               goto err;
+       if(psys_init_anm_rnd(&attr->size) == -1)
+               goto err;
+       if(psys_init_anm_rnd3(&attr->dir) == -1)
+               goto err;
+       if(psys_init_track3(&attr->grav) == -1)
+               goto err;
+
+       if(init_particle_attr(&attr->part_attr) == -1)
+               goto err;
+
+       attr->max_particles = -1;
+
+       anm_set_track_default(&attr->size.value.trk, 1.0);
+       anm_set_track_default(&attr->life.value.trk, 1.0);
+
+       attr->blending = PSYS_ADD;
+       return 0;
+
+err:
+       psys_destroy_attr(attr);
+       return -1;
+}
+
+
+static int init_particle_attr(struct psys_particle_attributes *pattr)
+{
+       if(psys_init_track3(&pattr->color) == -1) {
+               return -1;
+       }
+       if(psys_init_track(&pattr->alpha) == -1) {
+               psys_destroy_track3(&pattr->color);
+               return -1;
+       }
+       if(psys_init_track(&pattr->size) == -1) {
+               psys_destroy_track3(&pattr->color);
+               psys_destroy_track(&pattr->alpha);
+               return -1;
+       }
+
+       anm_set_track_default(&pattr->color.x, 1.0);
+       anm_set_track_default(&pattr->color.y, 1.0);
+       anm_set_track_default(&pattr->color.z, 1.0);
+       anm_set_track_default(&pattr->alpha.trk, 1.0);
+       anm_set_track_default(&pattr->size.trk, 1.0);
+       return 0;
+}
+
+
+void psys_destroy_attr(struct psys_attributes *attr)
+{
+       psys_destroy_track3(&attr->spawn_range);
+       psys_destroy_track(&attr->rate);
+       psys_destroy_anm_rnd(&attr->life);
+       psys_destroy_anm_rnd(&attr->size);
+       psys_destroy_anm_rnd3(&attr->dir);
+       psys_destroy_track3(&attr->grav);
+
+       destroy_particle_attr(&attr->part_attr);
+
+       if(attr->tex && unload_texture) {
+               unload_texture(attr->tex, tex_cls);
+       }
+}
+
+static void destroy_particle_attr(struct psys_particle_attributes *pattr)
+{
+       psys_destroy_track3(&pattr->color);
+       psys_destroy_track(&pattr->alpha);
+       psys_destroy_track(&pattr->size);
+}
+
+void psys_copy_attr(struct psys_attributes *dest, const struct psys_attributes *src)
+{
+       dest->tex = src->tex;
+
+       psys_copy_track3(&dest->spawn_range, &src->spawn_range);
+       psys_copy_track(&dest->rate, &src->rate);
+
+       psys_copy_anm_rnd(&dest->life, &src->life);
+       psys_copy_anm_rnd(&dest->size, &src->size);
+       psys_copy_anm_rnd3(&dest->dir, &src->dir);
+
+       psys_copy_track3(&dest->grav, &src->grav);
+
+       dest->drag = src->drag;
+       dest->max_particles = src->max_particles;
+
+       dest->blending = src->blending;
+
+       /* also copy the particle attributes */
+       psys_copy_track3(&dest->part_attr.color, &src->part_attr.color);
+       psys_copy_track(&dest->part_attr.alpha, &src->part_attr.alpha);
+       psys_copy_track(&dest->part_attr.size, &src->part_attr.size);
+}
+
+void psys_eval_attr(struct psys_attributes *attr, anm_time_t tm)
+{
+       float tmp[3];
+
+       psys_eval_track3(&attr->spawn_range, tm);
+       psys_eval_track(&attr->rate, tm);
+       psys_eval_anm_rnd(&attr->life, tm);
+       psys_eval_anm_rnd(&attr->size, tm);
+       psys_eval_anm_rnd3(&attr->dir, tm, tmp);
+       psys_eval_track3(&attr->grav, tm);
+}
+
+int psys_load_attr(struct psys_attributes *attr, const char *fname)
+{
+       FILE *fp;
+       int res;
+
+       if(!fname) {
+               return -1;
+       }
+
+       if(!(fp = fopen(fname, "r"))) {
+               fprintf(stderr, "psys_load_attr: failed to read file: %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+       res = psys_load_attr_stream(attr, fp);
+       fclose(fp);
+       return res;
+}
+
+int psys_load_attr_stream(struct psys_attributes *attr, FILE *fp)
+{
+       int lineno = 0;
+       char buf[512];
+       struct cfgopt *opt = 0;
+
+       psys_init_attr(attr);
+
+       while(fgets(buf, sizeof buf, fp)) {
+
+               lineno++;
+
+               if(!(opt = get_cfg_opt(buf))) {
+                       continue;
+               }
+
+               if(strcmp(opt->name, "texture") == 0) {
+                       if(opt->type != OPT_STR) {
+                               goto err;
+                       }
+                       if(!load_texture) {
+                               fprintf(stderr, "particle system requests a texture, but no texture loader available!\n");
+                               goto err;
+                       }
+                       if(!(attr->tex = load_texture(opt->valstr, tex_cls))) {
+                               fprintf(stderr, "failed to load texture: %s\n", opt->valstr);
+                               goto err;
+                       }
+
+                       release_cfg_opt(opt);
+                       continue;
+
+               } else if(strcmp(opt->name, "blending") == 0) {
+                       if(opt->type != OPT_STR) {
+                               goto err;
+                       }
+
+                       /* parse blending mode */
+                       if(strcmp(opt->valstr, "add") == 0 || strcmp(opt->valstr, "additive") == 0) {
+                               attr->blending = PSYS_ADD;
+                       } else if(strcmp(opt->valstr, "alpha") == 0) {
+                               attr->blending = PSYS_ALPHA;
+                       } else {
+                               fprintf(stderr, "invalid blending mode: %s\n", opt->valstr);
+                               goto err;
+                       }
+
+                       release_cfg_opt(opt);
+                       continue;
+
+               } else if(opt->type == OPT_STR) {
+                       fprintf(stderr, "invalid particle config: '%s'\n", opt->name);
+                       goto err;
+               }
+
+               if(strcmp(opt->name, "spawn_range") == 0) {
+                       psys_set_value3(&attr->spawn_range, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
+               } else if(strcmp(opt->name, "rate") == 0) {
+                       psys_set_value(&attr->rate, opt->tm, opt->val[0]);
+               } else if(strcmp(opt->name, "life") == 0) {
+                       psys_set_anm_rnd(&attr->life, opt->tm, opt->val[0], opt->valrng[0]);
+               } else if(strcmp(opt->name, "size") == 0) {
+                       psys_set_anm_rnd(&attr->size, opt->tm, opt->val[0], opt->valrng[0]);
+               } else if(strcmp(opt->name, "dir") == 0) {
+                       psys_set_anm_rnd3(&attr->dir, opt->tm, opt->val, opt->valrng);
+               } else if(strcmp(opt->name, "grav") == 0) {
+                       psys_set_value3(&attr->grav, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
+               } else if(strcmp(opt->name, "drag") == 0) {
+                       attr->drag = opt->val[0];
+               } else if(strcmp(opt->name, "pcolor") == 0) {
+                       psys_set_value3(&attr->part_attr.color, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
+               } else if(strcmp(opt->name, "palpha") == 0) {
+                       psys_set_value(&attr->part_attr.alpha, opt->tm, opt->val[0]);
+               } else if(strcmp(opt->name, "psize") == 0) {
+                       psys_set_value(&attr->part_attr.size, opt->tm, opt->val[0]);
+               } else {
+                       fprintf(stderr, "unrecognized particle config option: %s\n", opt->name);
+                       goto err;
+               }
+
+               release_cfg_opt(opt);
+       }
+
+       return 0;
+
+err:
+       fprintf(stderr, "Line %d: error parsing particle definition\n", lineno);
+       release_cfg_opt(opt);
+       return -1;
+}
+
+static struct cfgopt *get_cfg_opt(const char *line)
+{
+       char *buf, *tmp;
+       struct cfgopt *opt;
+
+       /* allocate a working buffer on the stack that could fit the current line */
+       buf = alloca(strlen(line) + 1);
+
+       line = stripspace((char*)line);
+       if(line[0] == '#' || !line[0]) {
+               return 0;       /* skip empty lines and comments */
+       }
+
+       if(!(opt = malloc(sizeof *opt))) {
+               return 0;
+       }
+       memset(opt, 0, sizeof *opt);
+
+       if(!(opt->valstr = strchr(line, '='))) {
+               release_cfg_opt(opt);
+               return 0;
+       }
+       *opt->valstr++ = 0;
+       opt->valstr = stripspace(opt->valstr);
+
+       strcpy(buf, line);
+       buf = stripspace(buf);
+
+       /* parse the keyframe time specifier if it exists */
+       if((tmp = strchr(buf, '('))) {
+               char *endp;
+               float tval;
+
+               *tmp++ = 0;
+               opt->name = malloc(strlen(buf) + 1);
+               strcpy(opt->name, buf);
+
+               tval = strtod(tmp, &endp);
+               if(endp == tmp) { /* nada ... */
+                       opt->tm = 0;
+               } else if(*endp == 's') {       /* seconds suffix */
+                       opt->tm = (long)(tval * 1000.0f);
+               } else {
+                       opt->tm = (long)tval;
+               }
+       } else {
+               opt->name = malloc(strlen(buf) + 1);
+               strcpy(opt->name, buf);
+               opt->tm = 0;
+       }
+
+       if(sscanf(opt->valstr, "[%f %f %f] ~ [%f %f %f]", opt->val, opt->val + 1, opt->val + 2,
+                               opt->valrng, opt->valrng + 1, opt->valrng + 2) == 6) {
+               /* value is a vector range */
+               opt->type = OPT_VEC_RANGE;
+
+       } else if(sscanf(opt->valstr, "%f ~ %f", opt->val, opt->valrng) == 2) {
+               /* value is a number range */
+               opt->type = OPT_NUM_RANGE;
+               opt->val[1] = opt->val[2] = opt->val[0];
+               opt->valrng[1] = opt->valrng[2] = opt->valrng[0];
+
+       } else if(sscanf(opt->valstr, "[%f %f %f]", opt->val, opt->val + 1, opt->val + 2) == 3) {
+               /* value is a vector */
+               opt->type = OPT_VEC;
+               opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
+
+       } else if(sscanf(opt->valstr, "%f", opt->val) == 1) {
+               /* value is a number */
+               opt->type = OPT_NUM;
+               opt->val[1] = opt->val[2] = opt->val[0];
+               opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
+
+       } else if(sscanf(opt->valstr, "\"%s\"", buf) == 1) {
+               /* just a string... strip the quotes */
+               if(buf[strlen(buf) - 1] == '\"') {
+                       buf[strlen(buf) - 1] = 0;
+               }
+               opt->type = OPT_STR;
+               opt->valstr = malloc(strlen(buf) + 1);
+               assert(opt->valstr);
+               strcpy(opt->valstr, buf);
+       } else {
+               /* fuck it ... */
+               release_cfg_opt(opt);
+               return 0;
+       }
+
+       return opt;
+}
+
+static void release_cfg_opt(struct cfgopt *opt)
+{
+       if(opt) {
+               free(opt->name);
+               opt->name = 0;
+       }
+       opt = 0;
+}
+
+
+int psys_save_attr(const struct psys_attributes *attr, const char *fname)
+{
+       FILE *fp;
+       int res;
+
+       if(!(fp = fopen(fname, "w"))) {
+               fprintf(stderr, "psys_save_attr: failed to write file: %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+       res = psys_save_attr_stream(attr, fp);
+       fclose(fp);
+       return res;
+}
+
+int psys_save_attr_stream(const struct psys_attributes *attr, FILE *fp)
+{
+       return -1;      /* TODO */
+}
+
+
+static char *stripspace(char *str)
+{
+       char *end;
+
+       while(*str && isspace(*str)) {
+               str++;
+       }
+
+       end = str + strlen(str) - 1;
+       while(end >= str && isspace(*end)) {
+               *end-- = 0;
+       }
+       return str;
+}
diff --git a/libs/psys/pattr.h b/libs/psys/pattr.h
new file mode 100644 (file)
index 0000000..da18c1f
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef PATTR_H_
+#define PATTR_H_
+
+#include <stdio.h>
+#include "pstrack.h"
+#include "rndval.h"
+
+/* the particle attributes vary from 0 to 1 during its lifetime */
+struct psys_particle_attributes {
+       struct psys_track3 color;
+       struct psys_track alpha;
+       struct psys_track size;
+};
+
+enum psys_blending { PSYS_ADD, PSYS_ALPHA };
+
+struct psys_attributes {
+       unsigned int tex;       /* OpenGL texture to use for the billboard */
+
+       struct psys_track3 spawn_range; /* radius of emmiter */
+       struct psys_track rate;                 /* spawn rate particles per sec */
+       struct psys_anm_rnd life;               /* particle life in seconds */
+       struct psys_anm_rnd size;               /* base particle size */
+       struct psys_anm_rnd3 dir;               /* particle shoot direction */
+
+       struct psys_track3 grav;                /* external force (usually gravity) */
+       float drag;     /* I don't think this needs to animate */
+
+       enum psys_blending blending;
+
+       /* particle attributes */
+       struct psys_particle_attributes part_attr;
+
+       /* limits */
+       int max_particles;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void psys_texture_loader(unsigned int (*load)(const char*, void*), void (*unload)(unsigned int, void*), void *cls);
+
+struct psys_attributes *psys_create_attr(void);
+void psys_free_attr(struct psys_attributes *attr);
+
+int psys_init_attr(struct psys_attributes *attr);
+void psys_destroy_attr(struct psys_attributes *attr);
+
+/* copies particle system attributes src to dest
+ * XXX: dest must have been initialized first
+ */
+void psys_copy_attr(struct psys_attributes *dest, const struct psys_attributes *src);
+
+void psys_eval_attr(struct psys_attributes *attr, anm_time_t tm);
+
+int psys_load_attr(struct psys_attributes *attr, const char *fname);
+int psys_load_attr_stream(struct psys_attributes *attr, FILE *fp);
+
+int psys_save_attr(const struct psys_attributes *attr, const char *fname);
+int psys_save_attr_stream(const struct psys_attributes *attr, FILE *fp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PATTR_H_ */
diff --git a/libs/psys/pstrack.c b/libs/psys/pstrack.c
new file mode 100644 (file)
index 0000000..5df8fb4
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "pstrack.h"
+
+int psys_init_track(struct psys_track *track)
+{
+       track->cache_tm = ANM_TIME_INVAL;
+
+       if(anm_init_track(&track->trk) == -1) {
+               return -1;
+       }
+       return 0;
+}
+
+void psys_destroy_track(struct psys_track *track)
+{
+       anm_destroy_track(&track->trk);
+}
+
+int psys_init_track3(struct psys_track3 *track)
+{
+       track->cache_tm = ANM_TIME_INVAL;
+
+       if(anm_init_track(&track->x) == -1) {
+               return -1;
+       }
+       if(anm_init_track(&track->y) == -1) {
+               anm_destroy_track(&track->x);
+               return -1;
+       }
+       if(anm_init_track(&track->z) == -1) {
+               anm_destroy_track(&track->x);
+               anm_destroy_track(&track->z);
+               return -1;
+       }
+       return 0;
+}
+
+void psys_destroy_track3(struct psys_track3 *track)
+{
+       anm_destroy_track(&track->x);
+       anm_destroy_track(&track->y);
+       anm_destroy_track(&track->z);
+}
+
+void psys_copy_track(struct psys_track *dest, const struct psys_track *src)
+{
+       anm_copy_track(&dest->trk, &src->trk);
+       dest->cache_tm = ANM_TIME_INVAL;
+}
+
+void psys_copy_track3(struct psys_track3 *dest, const struct psys_track3 *src)
+{
+       anm_copy_track(&dest->x, &src->x);
+       anm_copy_track(&dest->y, &src->y);
+       anm_copy_track(&dest->z, &src->z);
+
+       dest->cache_tm = ANM_TIME_INVAL;
+}
+
+void psys_eval_track(struct psys_track *track, anm_time_t tm)
+{
+       if(track->cache_tm != tm) {
+               track->cache_tm = tm;
+               track->cache_val = anm_get_value(&track->trk, tm);
+       }
+}
+
+void psys_set_value(struct psys_track *track, anm_time_t tm, float v)
+{
+       anm_set_value(&track->trk, tm, v);
+       track->cache_tm = ANM_TIME_INVAL;
+}
+
+float psys_get_value(struct psys_track *track, anm_time_t tm)
+{
+       psys_eval_track(track, tm);
+       return track->cache_val;
+}
+
+float psys_get_cur_value(struct psys_track *track)
+{
+       return track->cache_val;
+}
+
+
+void psys_eval_track3(struct psys_track3 *track, anm_time_t tm)
+{
+       if(track->cache_tm != tm) {
+               track->cache_tm = tm;
+               track->cache_vec[0] = anm_get_value(&track->x, tm);
+               track->cache_vec[1] = anm_get_value(&track->y, tm);
+               track->cache_vec[2] = anm_get_value(&track->z, tm);
+       }
+}
+
+void psys_set_value3(struct psys_track3 *track, anm_time_t tm, float x, float y, float z)
+{
+       anm_set_value(&track->x, tm, x);
+       anm_set_value(&track->y, tm, y);
+       anm_set_value(&track->z, tm, z);
+       track->cache_tm = ANM_TIME_INVAL;
+}
+
+float *psys_get_value3(struct psys_track3 *track, anm_time_t tm, float *vec)
+{
+       psys_eval_track3(track, tm);
+       if(vec) {
+               vec[0] = track->cache_vec[0];
+               vec[1] = track->cache_vec[1];
+               vec[2] = track->cache_vec[2];
+       }
+       return track->cache_vec;
+}
+
+float *psys_get_cur_value3(struct psys_track3 *track, float *vec)
+{
+       if(vec) {
+               vec[0] = track->cache_vec[0];
+               vec[1] = track->cache_vec[1];
+               vec[2] = track->cache_vec[2];
+       }
+       return track->cache_vec;
+}
diff --git a/libs/psys/pstrack.h b/libs/psys/pstrack.h
new file mode 100644 (file)
index 0000000..e71f042
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef PSTRACK_H_
+#define PSTRACK_H_
+
+#include <anim/anim.h>
+
+struct psys_track {
+       struct anm_track trk;
+
+       anm_time_t cache_tm;
+       float cache_val;
+};
+
+struct psys_track3 {
+       struct anm_track x, y, z;
+
+       anm_time_t cache_tm;
+       float cache_vec[3];
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int psys_init_track(struct psys_track *track);
+void psys_destroy_track(struct psys_track *track);
+
+int psys_init_track3(struct psys_track3 *track);
+void psys_destroy_track3(struct psys_track3 *track);
+
+/* XXX dest must have been initialized first */
+void psys_copy_track(struct psys_track *dest, const struct psys_track *src);
+void psys_copy_track3(struct psys_track3 *dest, const struct psys_track3 *src);
+
+void psys_eval_track(struct psys_track *track, anm_time_t tm);
+void psys_set_value(struct psys_track *track, anm_time_t tm, float v);
+float psys_get_value(struct psys_track *track, anm_time_t tm);
+float psys_get_cur_value(struct psys_track *track);
+
+void psys_eval_track3(struct psys_track3 *track, anm_time_t tm);
+void psys_set_value3(struct psys_track3 *track, anm_time_t tm, float x, float y, float z);
+/* returns pointer to the internal cached value, and if vec is not null, also copies it there */
+float *psys_get_value3(struct psys_track3 *track, anm_time_t tm, float *vec);
+float *psys_get_cur_value3(struct psys_track3 *track, float *vec);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PSTRACK_H_ */
diff --git a/libs/psys/psys.c b/libs/psys/psys.c
new file mode 100644 (file)
index 0000000..6825ae7
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <float.h>
+#include <assert.h>
+#include "psys.h"
+#include <pthread.h>
+
+static int spawn_particle(struct psys_emitter *em, struct psys_particle *p);
+static void update_particle(struct psys_emitter *em, struct psys_particle *p, long tm, float dt, void *cls);
+
+/* particle pool */
+static struct psys_particle *ppool;
+static int ppool_size;
+static pthread_mutex_t pool_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static struct psys_particle *palloc(void);
+static void pfree(struct psys_particle *p);
+
+/* --- constructors and shit --- */
+
+struct psys_emitter *psys_create(void)
+{
+       struct psys_emitter *em;
+
+       if(!(em = malloc(sizeof *em))) {
+               return 0;
+       }
+       if(psys_init(em) == -1) {
+               free(em);
+               return 0;
+       }
+       return em;
+}
+
+void psys_free(struct psys_emitter *em)
+{
+       psys_destroy(em);
+       free(em);
+}
+
+int psys_init(struct psys_emitter *em)
+{
+       memset(em, 0, sizeof *em);
+
+       if(anm_init_node(&em->prs) == -1) {
+               return -1;
+       }
+       if(psys_init_attr(&em->attr) == -1) {
+               anm_destroy_node(&em->prs);
+               return -1;
+       }
+
+       em->spawn = 0;  /* no custom spawning, just the defaults */
+       em->update = update_particle;
+       return 0;
+}
+
+void psys_destroy(struct psys_emitter *em)
+{
+       struct psys_particle *part;
+
+       part = em->plist;
+       while(part) {
+               struct psys_particle *tmp = part;
+               part = part->next;
+               pfree(tmp);
+       }
+
+       psys_destroy_attr(&em->attr);
+}
+
+void psys_set_pos(struct psys_emitter *em, const float *pos, long tm)
+{
+       anm_set_position(&em->prs, pos, ANM_MSEC2TM(tm));
+}
+
+void psys_set_pos3f(struct psys_emitter *em, float x, float y, float z, long tm)
+{
+       anm_set_position3f(&em->prs, x, y, z, ANM_MSEC2TM(tm));
+}
+
+void psys_set_rot(struct psys_emitter *em, const float *qrot, long tm)
+{
+       anm_set_rotation(&em->prs, qrot, ANM_MSEC2TM(tm));
+}
+
+void psys_set_pivot(struct psys_emitter *em, const float *pivot)
+{
+       anm_set_pivot(&em->prs, pivot[0], pivot[1], pivot[2]);
+}
+
+void psys_set_pivot3f(struct psys_emitter *em, float x, float y, float z)
+{
+       anm_set_pivot(&em->prs, x, y, z);
+}
+
+void psys_get_pos(struct psys_emitter *em, float *pos, long tm)
+{
+       anm_get_node_position(&em->prs, pos, ANM_MSEC2TM(tm));
+}
+
+void psys_get_rot(struct psys_emitter *em, float *qrot, long tm)
+{
+       anm_get_node_rotation(&em->prs, qrot, ANM_MSEC2TM(tm));
+}
+
+void psys_get_pivot(struct psys_emitter *em, float *pivot)
+{
+       anm_get_pivot(&em->prs, pivot, pivot + 1, pivot + 2);
+}
+
+void psys_clear_collision_planes(struct psys_emitter *em)
+{
+       struct psys_plane *plane;
+
+       plane = em->planes;
+       while(plane) {
+               struct psys_plane *tmp = plane;
+               plane = plane->next;
+               free(tmp);
+       }
+}
+
+int psys_add_collision_plane(struct psys_emitter *em, const float *plane, float elast)
+{
+       struct psys_plane *node;
+
+       if(!(node = malloc(sizeof *node))) {
+               return -1;
+       }
+       node->nx = plane[0];
+       node->ny = plane[1];
+       node->nz = plane[2];
+       node->d = plane[3];
+       node->elasticity = elast;
+       node->next = em->planes;
+       em->planes = node;
+       return 0;
+}
+
+void psys_add_particle(struct psys_emitter *em, struct psys_particle *p)
+{
+       p->next = em->plist;
+       em->plist = p;
+
+       em->pcount++;
+}
+
+void psys_spawn_func(struct psys_emitter *em, psys_spawn_func_t func, void *cls)
+{
+       em->spawn = func;
+       em->spawn_cls = cls;
+}
+
+void psys_update_func(struct psys_emitter *em, psys_update_func_t func, void *cls)
+{
+       em->update = func;
+       em->upd_cls = cls;
+}
+
+void psys_draw_func(struct psys_emitter *em, psys_draw_func_t draw,
+               psys_draw_start_func_t start, psys_draw_end_func_t end, void *cls)
+{
+       em->draw = draw;
+       em->draw_start = start;
+       em->draw_end = end;
+       em->draw_cls = cls;
+}
+
+/* --- update and render --- */
+
+void psys_update(struct psys_emitter *em, long tm)
+{
+       long delta_ms, spawn_tm, spawn_dt;
+       float dt;
+       int i, spawn_count;
+       struct psys_particle *p, pdummy;
+       anm_time_t atm = ANM_MSEC2TM(tm);
+
+       if(!em->update) {
+               static int once;
+               if(!once) {
+                       once = 1;
+                       fprintf(stderr, "psys_update called without an update callback\n");
+               }
+       }
+
+       delta_ms = tm - em->last_update;
+       if(delta_ms <= 0) {
+               return;
+       }
+       dt = (float)delta_ms / 1000.0f;
+
+       psys_eval_attr(&em->attr, atm);
+
+       /* how many particles to spawn for this interval ? */
+       em->spawn_acc += psys_get_cur_value(&em->attr.rate) * delta_ms;
+       if(em->spawn_acc >= 1000) {
+               spawn_count = em->spawn_acc / 1000;
+               em->spawn_acc %= 1000;
+       } else {
+               spawn_count = 0;
+       }
+
+       if(spawn_count) {
+               spawn_dt = delta_ms / spawn_count;
+       }
+       spawn_tm = em->last_update;
+       for(i=0; i<spawn_count; i++) {
+               if(em->attr.max_particles >= 0 && em->pcount >= em->attr.max_particles) {
+                       break;
+               }
+
+               /* update emitter position for this spawning */
+               anm_get_position(&em->prs, em->cur_pos, ANM_MSEC2TM(spawn_tm));
+
+               if(!(p = palloc())) {
+                       return;
+               }
+               if(spawn_particle(em, p) == -1) {
+                       pfree(p);
+               }
+               spawn_tm += spawn_dt;
+       }
+
+       /* update all particles */
+       p = em->plist;
+       while(p) {
+               em->update(em, p, tm, dt, em->upd_cls);
+               p = p->next;
+       }
+
+       /* cleanup dead particles */
+       pdummy.next = em->plist;
+       p = &pdummy;
+       while(p->next) {
+               if(p->next->life <= 0) {
+                       struct psys_particle *tmp = p->next;
+                       p->next = p->next->next;
+                       pfree(tmp);
+                       em->pcount--;
+               } else {
+                       p = p->next;
+               }
+       }
+       em->plist = pdummy.next;
+
+       em->last_update = tm;
+}
+
+void psys_draw(const struct psys_emitter *em)
+{
+       struct psys_particle *p;
+
+       assert(em->draw);
+
+       if(em->draw_start) {
+               em->draw_start(em, em->draw_cls);
+       }
+
+       p = em->plist;
+       while(p) {
+               em->draw(em, p, em->draw_cls);
+               p = p->next;
+       }
+
+       if(em->draw_end) {
+               em->draw_end(em, em->draw_cls);
+       }
+}
+
+static int spawn_particle(struct psys_emitter *em, struct psys_particle *p)
+{
+       int i;
+       struct psys_rnd3 rpos;
+
+       for(i=0; i<3; i++) {
+               rpos.value[i] = em->cur_pos[i];
+       }
+       psys_get_cur_value3(&em->attr.spawn_range, rpos.range);
+
+       psys_eval_rnd3(&rpos, &p->x);
+       psys_eval_anm_rnd3(&em->attr.dir, PSYS_EVAL_CUR, &p->vx);
+       p->base_size = psys_eval_anm_rnd(&em->attr.size, PSYS_EVAL_CUR);
+       p->max_life = p->life = psys_eval_anm_rnd(&em->attr.life, PSYS_EVAL_CUR);
+
+       p->pattr = &em->attr.part_attr;
+
+       if(em->spawn && em->spawn(em, p, em->spawn_cls) == -1) {
+               return -1;
+       }
+
+       psys_add_particle(em, p);
+       return 0;
+}
+
+static void update_particle(struct psys_emitter *em, struct psys_particle *p, long tm, float dt, void *cls)
+{
+       float accel[3], grav[3];
+       anm_time_t t;
+
+       psys_get_cur_value3(&em->attr.grav, grav);
+
+       accel[0] = grav[0] - p->vx * em->attr.drag;
+       accel[1] = grav[1] - p->vy * em->attr.drag;
+       accel[2] = grav[2] - p->vz * em->attr.drag;
+
+       p->vx += accel[0] * dt;
+       p->vy += accel[1] * dt;
+       p->vz += accel[2] * dt;
+
+       p->x += p->vx * dt;
+       p->y += p->vy * dt;
+       p->z += p->vz * dt;
+
+       /* update particle attributes */
+       t = (anm_time_t)(1000.0f * (p->max_life - p->life) / p->max_life);
+
+       psys_get_value3(&p->pattr->color, t, &p->r);
+       p->alpha = psys_get_value(&p->pattr->alpha, t);
+       p->size = p->base_size * psys_get_value(&p->pattr->size, t);
+
+       p->life -= dt;
+}
+
+/* --- particle allocation pool --- */
+
+static struct psys_particle *palloc(void)
+{
+       struct psys_particle *p;
+
+       pthread_mutex_lock(&pool_lock);
+       if(ppool) {
+               p = ppool;
+               ppool = ppool->next;
+               ppool_size--;
+       } else {
+               p = malloc(sizeof *p);
+       }
+       pthread_mutex_unlock(&pool_lock);
+
+       return p;
+}
+
+static void pfree(struct psys_particle *p)
+{
+       pthread_mutex_lock(&pool_lock);
+       p->next = ppool;
+       ppool = p;
+       ppool_size++;
+       pthread_mutex_unlock(&pool_lock);
+}
diff --git a/libs/psys/psys.h b/libs/psys/psys.h
new file mode 100644 (file)
index 0000000..bc32d1f
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef LIBPSYS_H_
+#define LIBPSYS_H_
+
+#include <anim/anim.h>
+#include "rndval.h"
+#include "pattr.h"
+
+struct psys_particle;
+struct psys_emitter;
+
+typedef int (*psys_spawn_func_t)(struct psys_emitter*, struct psys_particle*, void*);
+typedef void (*psys_update_func_t)(struct psys_emitter*, struct psys_particle*, long, float, void*);
+
+typedef void (*psys_draw_func_t)(const struct psys_emitter*, const struct psys_particle*, void*);
+typedef void (*psys_draw_start_func_t)(const struct psys_emitter*, void*);
+typedef void (*psys_draw_end_func_t)(const struct psys_emitter*, void*);
+
+
+struct psys_plane {
+       float nx, ny, nz, d;
+       float elasticity;
+       struct psys_plane *next;
+};
+
+
+struct psys_emitter {
+       struct anm_node prs;
+       float cur_pos[3];
+
+       struct psys_attributes attr;
+
+       /* list of active particles */
+       struct psys_particle *plist;
+       int pcount;     /* number of active particles */
+
+       /* list of collision planes */
+       struct psys_plane *planes;
+
+       /* custom spawn closure */
+       void *spawn_cls;
+       psys_spawn_func_t spawn;
+
+       /* custom particle update closure */
+       void *upd_cls;
+       psys_update_func_t update;
+
+       /* custom draw closure */
+       void *draw_cls;
+       psys_draw_func_t draw;
+       psys_draw_start_func_t draw_start;
+       psys_draw_end_func_t draw_end;
+
+       long spawn_acc;         /* partial spawn accumulator */
+       long last_update;       /* last update time (to calc dt) */
+};
+
+
+struct psys_particle {
+       float x, y, z;
+       float vx, vy, vz;
+       float life, max_life;
+       float base_size;
+
+       struct psys_particle_attributes *pattr;
+
+       /* current particle attr values calculated during update */
+       float r, g, b;
+       float alpha, size;
+
+       struct psys_particle *next;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct psys_emitter *psys_create(void);
+void psys_free(struct psys_emitter *em);
+
+int psys_init(struct psys_emitter *em);
+void psys_destroy(struct psys_emitter *em);
+
+/* set properties */
+
+/* set emitter position. pos should point to 3 floats (xyz) */
+void psys_set_pos(struct psys_emitter *em, const float *pos, long tm);
+void psys_set_pos3f(struct psys_emitter *em, float x, float y, float z, long tm);
+/* set emitter rotation quaternion. qrot should point to 4 floats (xyzw) */
+void psys_set_rot(struct psys_emitter *em, const float *qrot, long tm);
+void psys_set_rot4f(struct psys_emitter *em, float x, float y, float z, float w, long tm);
+/* set emitter rotation by defining a rotation axis, and an angle */
+void psys_set_rot_axis(struct psys_emitter *em, float angle, float x, float y, float z, long tm);
+/* set rotation pivot point. pos should point to 3 floats (xyz) */
+void psys_set_pivot(struct psys_emitter *em, const float *pivot);
+void psys_set_pivot3f(struct psys_emitter *em, float x, float y, float z);
+
+/* pos should be a pointer to 3 floats (xyz) */
+void psys_get_pos(struct psys_emitter *em, float *pos, long tm);
+/* qrot should be a pointer to 4 floats (xyzw) */
+void psys_get_rot(struct psys_emitter *em, float *qrot, long tm);
+/* pivot should be a pointer to 3 floats (xyz) */
+void psys_get_pivot(struct psys_emitter *em, float *pivot);
+
+void psys_clear_collision_planes(struct psys_emitter *em);
+int psys_add_collision_plane(struct psys_emitter *em, const float *plane, float elast);
+
+void psys_add_particle(struct psys_emitter *em, struct psys_particle *p);
+
+void psys_spawn_func(struct psys_emitter *em, psys_spawn_func_t func, void *cls);
+void psys_update_func(struct psys_emitter *em, psys_update_func_t func, void *cls);
+void psys_draw_func(struct psys_emitter *em, psys_draw_func_t draw,
+               psys_draw_start_func_t start, psys_draw_end_func_t end, void *cls);
+
+/* update and render */
+
+void psys_update(struct psys_emitter *em, long tm);
+void psys_draw(const struct psys_emitter *em);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBPSYS_H_ */
diff --git a/libs/psys/rndval.c b/libs/psys/rndval.c
new file mode 100644 (file)
index 0000000..97a952d
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdlib.h>
+#include "rndval.h"
+
+int psys_init_anm_rnd(struct psys_anm_rnd *r)
+{
+       if(psys_init_track(&r->value) == -1) {
+               return -1;
+       }
+       if(psys_init_track(&r->range) == -1) {
+               psys_destroy_track(&r->value);
+               return -1;
+       }
+       return 0;
+}
+
+void psys_destroy_anm_rnd(struct psys_anm_rnd *r)
+{
+       psys_destroy_track(&r->value);
+       psys_destroy_track(&r->range);
+}
+
+int psys_init_anm_rnd3(struct psys_anm_rnd3 *r)
+{
+       if(psys_init_track3(&r->value) == -1) {
+               return -1;
+       }
+       if(psys_init_track3(&r->range) == -1) {
+               psys_destroy_track3(&r->value);
+               return -1;
+       }
+       return 0;
+}
+
+void psys_destroy_anm_rnd3(struct psys_anm_rnd3 *r)
+{
+       psys_destroy_track3(&r->value);
+       psys_destroy_track3(&r->range);
+}
+
+void psys_copy_anm_rnd(struct psys_anm_rnd *dest, const struct psys_anm_rnd *src)
+{
+       psys_copy_track(&dest->value, &src->value);
+       psys_copy_track(&dest->range, &src->range);
+}
+
+void psys_copy_anm_rnd3(struct psys_anm_rnd3 *dest, const struct psys_anm_rnd3 *src)
+{
+       psys_copy_track3(&dest->value, &src->value);
+       psys_copy_track3(&dest->range, &src->range);
+}
+
+void psys_set_rnd(struct psys_rnd *r, float val, float range)
+{
+       r->value = val;
+       r->range = range;
+}
+
+void psys_set_rnd3(struct psys_rnd3 *r, const float *val, const float *range)
+{
+       int i;
+       for(i=0; i<3; i++) {
+               r->value[i] = val[i];
+               r->range[i] = range[i];
+       }
+}
+
+void psys_set_anm_rnd(struct psys_anm_rnd *r, anm_time_t tm, float val, float range)
+{
+       psys_set_value(&r->value, tm, val);
+       psys_set_value(&r->range, tm, range);
+}
+
+void psys_set_anm_rnd3(struct psys_anm_rnd3 *r, anm_time_t tm, const float *val, const float *range)
+{
+       psys_set_value3(&r->value, tm, val[0], val[1], val[2]);
+       psys_set_value3(&r->range, tm, range[0], range[1], range[2]);
+}
+
+
+float psys_eval_rnd(struct psys_rnd *r)
+{
+       return r->value + r->range * (float)rand() / (float)RAND_MAX - 0.5 * r->range;
+}
+
+void psys_eval_rnd3(struct psys_rnd3 *r, float *val)
+{
+       val[0] = r->value[0] + r->range[0] * (float)rand() / (float)RAND_MAX - 0.5 * r->range[0];
+       val[1] = r->value[1] + r->range[1] * (float)rand() / (float)RAND_MAX - 0.5 * r->range[1];
+       val[2] = r->value[2] + r->range[2] * (float)rand() / (float)RAND_MAX - 0.5 * r->range[2];
+}
+
+
+float psys_eval_anm_rnd(struct psys_anm_rnd *r, anm_time_t tm)
+{
+       struct psys_rnd tmp;
+       if(tm == ANM_TIME_INVAL) {
+               tmp.value = psys_get_cur_value(&r->value);
+               tmp.range = psys_get_cur_value(&r->range);
+       } else {
+               tmp.value = psys_get_value(&r->value, tm);
+               tmp.range = psys_get_value(&r->range, tm);
+       }
+       return psys_eval_rnd(&tmp);
+}
+
+void psys_eval_anm_rnd3(struct psys_anm_rnd3 *r, anm_time_t tm, float *val)
+{
+       struct psys_rnd3 tmp;
+       if(tm == ANM_TIME_INVAL) {
+               psys_get_cur_value3(&r->value, tmp.value);
+               psys_get_cur_value3(&r->range, tmp.range);
+       } else {
+               psys_get_value3(&r->value, tm, tmp.value);
+               psys_get_value3(&r->range, tm, tmp.range);
+       }
+       psys_eval_rnd3(&tmp, val);
+}
diff --git a/libs/psys/rndval.h b/libs/psys/rndval.h
new file mode 100644 (file)
index 0000000..c28a5a0
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef RNDVAL_H_
+#define RNDVAL_H_
+
+#include "pstrack.h"
+
+struct psys_rnd {
+       float value, range;
+};
+
+struct psys_rnd3 {
+       float value[3], range[3];
+};
+
+struct psys_anm_rnd {
+       struct psys_track value, range;
+};
+
+struct psys_anm_rnd3 {
+       struct psys_track3 value, range;
+};
+
+#define PSYS_EVAL_CUR  ANM_TIME_INVAL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int psys_init_anm_rnd(struct psys_anm_rnd *v);
+void psys_destroy_anm_rnd(struct psys_anm_rnd *v);
+int psys_init_anm_rnd3(struct psys_anm_rnd3 *v);
+void psys_destroy_anm_rnd3(struct psys_anm_rnd3 *v);
+
+void psys_copy_anm_rnd(struct psys_anm_rnd *dest, const struct psys_anm_rnd *src);
+void psys_copy_anm_rnd3(struct psys_anm_rnd3 *dest, const struct psys_anm_rnd3 *src);
+
+void psys_set_rnd(struct psys_rnd *r, float val, float range);
+void psys_set_rnd3(struct psys_rnd3 *r, const float *val, const float *range);
+
+void psys_set_anm_rnd(struct psys_anm_rnd *r, anm_time_t tm, float val, float range);
+void psys_set_anm_rnd3(struct psys_anm_rnd3 *r, anm_time_t tm, const float *val, const float *range);
+
+float psys_eval_rnd(struct psys_rnd *r);
+void psys_eval_rnd3(struct psys_rnd3 *r, float *val);
+
+float psys_eval_anm_rnd(struct psys_anm_rnd *r, anm_time_t tm);
+void psys_eval_anm_rnd3(struct psys_anm_rnd3 *r, anm_time_t tm, float *val);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RNDVAL_H_ */