Compare commits

...

3 commits

Author SHA1 Message Date
27b6459b9a add anim and transitions and re-org lua api scripts 2025-11-15 11:45:05 -06:00
a15d0db902 add LICENSE file 2025-11-15 11:44:18 -06:00
b27abeffb5 add jump to demo 9 2025-11-15 11:44:15 -06:00
27 changed files with 2319 additions and 640 deletions

373
LICENSE Normal file
View file

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View file

@ -31,3 +31,11 @@
@@@@@@@@@@@@@@@@@@@@@@@~ ,@@@,,0@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@~ ,@@@,,0@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@,,@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@,,@@@@@@@@@@@@@@@@@@@@@@@@@
</pre> </pre>
## License
pxl8 is free, open source and permissively licensed! All code in this repository is licensed under:
- Mozilla Public License, Version 2.0 ([LICENSE](LICENSE) or https://mozilla.org/MPL/2.0/)
Third-party dependencies (SDL3, LuaJIT, Fennel, linenoise, microui, miniz) retain their original licenses.

View file

@ -5,16 +5,24 @@
(var time 0) (var time 0)
(var active-demo :logo) (var active-demo :logo)
(var particles nil) (var particles nil)
(var fire-init false) (var fire-init? false)
(var rain-init false) (var rain-init? false)
(var snow-init false) (var snow-init? false)
(var use-famicube-palette false) (var use-famicube-palette? false)
(var logo-x 256) (var logo-x 256)
(var logo-y 148) (var logo-y 148)
(var logo-dx 100) (var logo-dx 100)
(var logo-dy 80) (var logo-dy 80)
(var logo-sprite nil) (var logo-sprite nil)
(var transition nil)
(var transition-pending nil)
(fn switch-demo [new-demo]
(set transition-pending new-demo)
(set transition (pxl8.transition_create :pixelate 0.5))
(pxl8.transition_set_color transition 0xFF000000)
(pxl8.transition_start transition))
(global init (fn [] (global init (fn []
(cube3d.init) (cube3d.init)
@ -26,24 +34,30 @@
(global update (fn [dt] (global update (fn [dt]
(set time (+ time dt)) (set time (+ time dt))
(when (pxl8.key_pressed "1") (set active-demo :logo)) (when transition
(when (pxl8.key_pressed "2") (set active-demo :plasma)) (pxl8.transition_update transition dt)
(when (pxl8.key_pressed "3") (set active-demo :tunnel)) (when (pxl8.transition_is_complete transition)
(when (pxl8.key_pressed "4") (set active-demo :raster)) (when transition-pending
(when (pxl8.key_pressed "5") (set active-demo transition-pending)
(set active-demo :fire) (set transition-pending nil)
(set fire-init false)) (when (= active-demo :fire) (set fire-init? false))
(when (pxl8.key_pressed "6") (when (= active-demo :rain) (set rain-init? false))
(set active-demo :rain) (when (= active-demo :snow) (set snow-init? false)))
(set rain-init false)) (pxl8.transition_destroy transition)
(when (pxl8.key_pressed "7") (set transition nil)))
(set active-demo :snow)
(set snow-init false)) (when (pxl8.key_pressed "1") (switch-demo :logo))
(when (pxl8.key_pressed "8") (set active-demo :cube3d)) (when (pxl8.key_pressed "2") (switch-demo :plasma))
(when (pxl8.key_pressed "9") (set active-demo :worldgen)) (when (pxl8.key_pressed "3") (switch-demo :tunnel))
(when (pxl8.key_pressed "4") (switch-demo :raster))
(when (pxl8.key_pressed "5") (switch-demo :fire))
(when (pxl8.key_pressed "6") (switch-demo :rain))
(when (pxl8.key_pressed "7") (switch-demo :snow))
(when (pxl8.key_pressed "8") (switch-demo :cube3d))
(when (pxl8.key_pressed "9") (switch-demo :worldgen))
(when (pxl8.key_pressed "=") (when (pxl8.key_pressed "=")
(set use-famicube-palette (not use-famicube-palette)) (set use-famicube-palette? (not use-famicube-palette?))
(local palette-path (if use-famicube-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
(pxl8.load_palette palette-path)) (pxl8.load_palette palette-path))
(case active-demo (case active-demo
@ -81,32 +95,35 @@
:fire (do :fire (do
(pxl8.clr 0) (pxl8.clr 0)
(when particles (when particles
(when (not fire-init) (when (not fire-init?)
(pxl8.particles_clear particles) (pxl8.particles_clear particles)
(pxl8.vfx_fire particles 160 140 100 12) (pxl8.vfx_fire particles 160 140 100 12)
(set fire-init true)) (set fire-init? true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
:rain (do :rain (do
(pxl8.clr 0) (pxl8.clr 0)
(when particles (when particles
(when (not rain-init) (when (not rain-init?)
(pxl8.particles_clear particles) (pxl8.particles_clear particles)
(pxl8.vfx_rain particles 320 10.0) (pxl8.vfx_rain particles 320 10.0)
(set rain-init true)) (set rain-init? true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
:snow (do :snow (do
(pxl8.clr 0) (pxl8.clr 0)
(when particles (when particles
(when (not snow-init) (when (not snow-init?)
(pxl8.particles_clear particles) (pxl8.particles_clear particles)
(pxl8.vfx_snow particles 320 5.0) (pxl8.vfx_snow particles 320 5.0)
(set snow-init true)) (set snow-init? true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
:cube3d (cube3d.frame) :cube3d (cube3d.frame)
:worldgen (worldgen.frame) :worldgen (worldgen.frame)
_ (pxl8.clr 0)))) _ (pxl8.clr 0))
(when transition
(pxl8.transition_render transition))))

View file

@ -4,20 +4,20 @@
(var angle-x 0) (var angle-x 0)
(var angle-y 0) (var angle-y 0)
(var angle-z 0) (var angle-z 0)
(var auto-rotate true) (var auto-rotate? true)
(var orthographic true) (var orthographic? true)
(var wireframe true) (var wireframe? true)
(var time 0) (var time 0)
(var zoom 5.0) (var zoom 5.0)
(var texture-id nil) (var texture-id nil)
(var use-texture false) (var use-texture? false)
(var affine false) (var affine? false)
(var cam-x 0) (var cam-x 0)
(var cam-y 2) (var cam-y 2)
(var cam-z 12) (var cam-z 12)
(var cam-yaw 0) (var cam-yaw 0)
(var cam-pitch -0.2) (var cam-pitch -0.2)
(var show-debug-ui false) (var show-debug-ui? false)
(var fps 0) (var fps 0)
(var fps-accumulator 0) (var fps-accumulator 0)
(var fps-frame-count 0) (var fps-frame-count 0)
@ -26,19 +26,19 @@
(set angle-x 0) (set angle-x 0)
(set angle-y 0) (set angle-y 0)
(set angle-z 0) (set angle-z 0)
(set auto-rotate true) (set auto-rotate? true)
(set orthographic true) (set orthographic? true)
(set wireframe true) (set wireframe? true)
(set time 0) (set time 0)
(set zoom 5.0) (set zoom 5.0)
(set use-texture false) (set use-texture? false)
(set affine false) (set affine? false)
(set cam-x 0) (set cam-x 0)
(set cam-y 2) (set cam-y 2)
(set cam-z 12) (set cam-z 12)
(set cam-yaw 0) (set cam-yaw 0)
(set cam-pitch -0.2) (set cam-pitch -0.2)
(set show-debug-ui false) (set show-debug-ui? false)
(set fps 0) (set fps 0)
(set fps-accumulator 0) (set fps-accumulator 0)
(set fps-frame-count 0) (set fps-frame-count 0)
@ -87,7 +87,7 @@
(let [wheel-y (pxl8.mouse_wheel_y)] (let [wheel-y (pxl8.mouse_wheel_y)]
(when (and (not= wheel-y 0) (not (pxl8.ui_has_mouse_focus))) (when (and (not= wheel-y 0) (not (pxl8.ui_has_mouse_focus)))
(if orthographic (if orthographic?
(set zoom (math.max 0.5 (math.min (- zoom (* wheel-y 0.2)) 10.0))) (set zoom (math.max 0.5 (math.min (- zoom (* wheel-y 0.2)) 10.0)))
(let [zoom-speed 0.5 (let [zoom-speed 0.5
forward-x (* (math.sin cam-yaw) wheel-y zoom-speed) forward-x (* (math.sin cam-yaw) wheel-y zoom-speed)
@ -103,7 +103,7 @@
right-x (* (math.cos cam-yaw) move-speed dt) right-x (* (math.cos cam-yaw) move-speed dt)
right-z (* (- (math.sin cam-yaw)) move-speed dt)] right-z (* (- (math.sin cam-yaw)) move-speed dt)]
(if orthographic (if orthographic?
(do (do
(when (pxl8.key_down "w") (when (pxl8.key_down "w")
(set zoom (math.max 0.5 (- zoom (* zoom-speed dt))))) (set zoom (math.max 0.5 (- zoom (* zoom-speed dt)))))
@ -149,20 +149,20 @@
(set cam-pitch (math.max -1.5 (math.min cam-pitch 1.5)))) (set cam-pitch (math.max -1.5 (math.min cam-pitch 1.5))))
(when (pxl8.key_pressed " ") (when (pxl8.key_pressed " ")
(set wireframe (not wireframe))) (set wireframe? (not wireframe?)))
(when (pxl8.key_pressed "f") (when (pxl8.key_pressed "f")
(set affine (not affine))) (set affine? (not affine?)))
(when (pxl8.key_pressed "p") (when (pxl8.key_pressed "p")
(set orthographic (not orthographic))) (set orthographic? (not orthographic?)))
(when (pxl8.key_pressed "r") (when (pxl8.key_pressed "r")
(set auto-rotate (not auto-rotate))) (set auto-rotate? (not auto-rotate?)))
(when (pxl8.key_pressed "t") (when (pxl8.key_pressed "t")
(set use-texture (not use-texture))) (set use-texture? (not use-texture?)))
(when (pxl8.key_pressed "F8") (when (pxl8.key_pressed "F8")
(set show-debug-ui (not show-debug-ui)) (set show-debug-ui? (not show-debug-ui?))
(pxl8.ui_window_set_open "Debug Menu (F8)" show-debug-ui)) (pxl8.ui_window_set_open "Debug Menu (F8)" show-debug-ui?))
(when auto-rotate (when auto-rotate?
(set angle-x (+ angle-x (* dt 0.7))) (set angle-x (+ angle-x (* dt 0.7)))
(set angle-y (+ angle-y (* dt 0.5))) (set angle-y (+ angle-y (* dt 0.5)))
(set angle-z (+ angle-z (* dt 0.3))))) (set angle-z (+ angle-z (* dt 0.3)))))
@ -178,7 +178,7 @@
(pxl8.set_model model)) (pxl8.set_model model))
(let [vertices (make-cube-vertices)] (let [vertices (make-cube-vertices)]
(if (and use-texture texture-id) (if (and use-texture? texture-id)
(let [faces (make-cube-faces-with-uvs)] (let [faces (make-cube-faces-with-uvs)]
(each [_i face-data (ipairs faces)] (each [_i face-data (ipairs faces)]
(let [tri-indices face-data.tri (let [tri-indices face-data.tri
@ -206,11 +206,11 @@
(pxl8.clr 0) (pxl8.clr 0)
(pxl8.clear_zbuffer) (pxl8.clear_zbuffer)
(pxl8.set_affine_textures affine) (pxl8.set_affine_textures affine?)
(pxl8.set_backface_culling true) (pxl8.set_backface_culling true)
(pxl8.set_wireframe wireframe) (pxl8.set_wireframe wireframe?)
(if orthographic (if orthographic?
(let [size zoom (let [size zoom
aspect (/ (pxl8.get_width) (pxl8.get_height)) aspect (/ (pxl8.get_width) (pxl8.get_height))
w (* size aspect) w (* size aspect)
@ -233,19 +233,19 @@
(draw-cube [3 1 -7] 0.9 1.2) (draw-cube [3 1 -7] 0.9 1.2)
(draw-cube [0 -2 -10] 1.1 -0.7) (draw-cube [0 -2 -10] 1.1 -0.7)
(let [new-state (debug-ui.render {:show-debug-ui show-debug-ui (let [new-state (debug-ui.render {:show-debug-ui show-debug-ui?
:fps fps :fps fps
:wireframe wireframe :wireframe wireframe?
:auto-rotate auto-rotate :auto-rotate auto-rotate?
:orthographic orthographic :orthographic orthographic?
:use-texture use-texture :use-texture use-texture?
:affine affine})] :affine affine?})]
(when (not= new-state.show-debug-ui nil) (set show-debug-ui new-state.show-debug-ui)) (when (not= new-state.show-debug-ui nil) (set show-debug-ui? new-state.show-debug-ui))
(when (not= new-state.wireframe nil) (set wireframe new-state.wireframe)) (when (not= new-state.wireframe nil) (set wireframe? new-state.wireframe))
(when (not= new-state.auto-rotate nil) (set auto-rotate new-state.auto-rotate)) (when (not= new-state.auto-rotate nil) (set auto-rotate? new-state.auto-rotate))
(when (not= new-state.orthographic nil) (set orthographic new-state.orthographic)) (when (not= new-state.orthographic nil) (set orthographic? new-state.orthographic))
(when (not= new-state.use-texture nil) (set use-texture new-state.use-texture)) (when (not= new-state.use-texture nil) (set use-texture? new-state.use-texture))
(when (not= new-state.affine nil) (set affine new-state.affine)))) (when (not= new-state.affine nil) (set affine? new-state.affine))))
{:init init {:init init
:update update :update update

View file

@ -2,11 +2,14 @@
(var world nil) (var world nil)
(var cam-x 1000) (var cam-x 1000)
(local cam-y 64) (var cam-y 64)
(var cam-z 1000) (var cam-z 1000)
(var cam-yaw 0) (var cam-yaw 0)
(var cam-pitch 0) (var cam-pitch 0)
(var bob-time 0) (var bob-time 0)
(var velocity-y 0)
(var grounded? true)
(var land-squash 0)
(local move-speed 200) (local move-speed 200)
(local turn-speed 2.0) (local turn-speed 2.0)
@ -15,6 +18,11 @@
(local max-pitch 1.5) (local max-pitch 1.5)
(local cell-size 64) (local cell-size 64)
(local grid-size 32) (local grid-size 32)
(local gravity -800)
(local jump-force 175)
(local ground-y 64)
(local land-squash-amount -4)
(local land-recovery-speed 20)
(fn init [] (fn init []
(set world (pxl8.world_new)) (set world (pxl8.world_new))
@ -72,31 +80,28 @@
(var move-right 0) (var move-right 0)
(when (pxl8.key_down "w") (when (pxl8.key_down "w")
(set move-forward (+ move-forward 1)) (set move-forward (+ move-forward 1)))
(set moving true))
(when (pxl8.key_down "s") (when (pxl8.key_down "s")
(set move-forward (- move-forward 1)) (set move-forward (- move-forward 1)))
(set moving true))
(when (pxl8.key_down "q") (when (pxl8.key_down "q")
(set move-right (- move-right 1)) (set move-right (- move-right 1)))
(set moving true))
(when (pxl8.key_down "e") (when (pxl8.key_down "e")
(set move-right (+ move-right 1)) (set move-right (+ move-right 1)))
(set moving true))
(set moving (or (not= move-forward 0) (not= move-right 0)))
(var new-x cam-x) (var new-x cam-x)
(var new-z cam-z) (var new-z cam-z)
(when moving (when moving
(let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))] (let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))
(when (> len 0) norm-forward (/ move-forward len)
(let [norm-forward (/ move-forward len) norm-right (/ move-right len)]
norm-right (/ move-right len)] (set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right)))))
(set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right))))) (set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right)))))))
(set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right)))))))))
(when (and (>= new-x 0) (<= new-x grid-max) (when (and (>= new-x 0) (<= new-x grid-max)
(>= new-z 0) (<= new-z grid-max)) (>= new-z 0) (<= new-z grid-max))
@ -115,7 +120,24 @@
(when (pxl8.key_down "down") (when (pxl8.key_down "down")
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(if moving (when (and (pxl8.key_pressed "space") grounded?)
(set velocity-y jump-force)
(set grounded? false))
(set velocity-y (+ velocity-y (* gravity dt)))
(set cam-y (+ cam-y (* velocity-y dt)))
(when (<= cam-y ground-y)
(when (not grounded?)
(set land-squash land-squash-amount))
(set cam-y ground-y)
(set velocity-y 0)
(set grounded? true))
(when (< land-squash 0)
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt)))))
(if (and moving grounded?)
(set bob-time (+ bob-time (* dt bob-speed))) (set bob-time (+ bob-time (* dt bob-speed)))
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))))) (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))))
@ -125,7 +147,7 @@
(when (pxl8.world_is_loaded world) (when (pxl8.world_is_loaded world)
(let [bob-offset (* (math.sin bob-time) bob-amount) (let [bob-offset (* (math.sin bob-time) bob-amount)
eye-y (+ cam-y bob-offset) eye-y (+ cam-y bob-offset land-squash)
forward-x (- (math.sin cam-yaw)) forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw)) forward-z (- (math.cos cam-yaw))
target-x (+ cam-x forward-x) target-x (+ cam-x forward-x)

View file

@ -340,6 +340,7 @@ case "$COMMAND" in
PXL8_SOURCE_FILES=" PXL8_SOURCE_FILES="
src/pxl8.c src/pxl8.c
src/pxl8_anim.c
src/pxl8_ase.c src/pxl8_ase.c
src/pxl8_atlas.c src/pxl8_atlas.c
src/pxl8_blit.c src/pxl8_blit.c
@ -354,6 +355,7 @@ case "$COMMAND" in
src/pxl8_sdl3.c src/pxl8_sdl3.c
src/pxl8_tilemap.c src/pxl8_tilemap.c
src/pxl8_tilesheet.c src/pxl8_tilesheet.c
src/pxl8_transition.c
src/pxl8_ui.c src/pxl8_ui.c
src/pxl8_vfx.c src/pxl8_vfx.c
src/pxl8_world.c src/pxl8_world.c

View file

@ -1,534 +1,174 @@
local ffi = require("ffi") local ffi = require("ffi")
local C = ffi.C
local game = _pxl8_game local core = require("pxl8.core")
local gfx = _pxl8_gfx local graphics = require("pxl8.graphics")
local input = _pxl8_input local input = require("pxl8.input")
local ui = _pxl8_ui local vfx = require("pxl8.vfx")
local particles = require("pxl8.particles")
local tilemap = require("pxl8.tilemap")
local gfx3d = require("pxl8.gfx3d")
local math3d = require("pxl8.math")
local ui = require("pxl8.ui")
local world = require("pxl8.world")
local transition = require("pxl8.transition")
local anim = require("pxl8.anim")
core.init(_pxl8_game, _pxl8_gfx, _pxl8_input, _pxl8_ui)
local pxl8 = {} local pxl8 = {}
function pxl8.clr(color) pxl8.get_fps = core.get_fps
C.pxl8_clr(gfx, color or 0) pxl8.get_width = core.get_width
end pxl8.get_height = core.get_height
pxl8.info = core.info
function pxl8.pixel(x, y, color) pxl8.warn = core.warn
if color then pxl8.error = core.error
C.pxl8_pixel(gfx, x, y, color) pxl8.debug = core.debug
else pxl8.trace = core.trace
return C.pxl8_get_pixel(gfx, x, y)
end pxl8.clr = graphics.clr
end pxl8.pixel = graphics.pixel
pxl8.line = graphics.line
function pxl8.line(x0, y0, x1, y1, color) pxl8.rect = graphics.rect
C.pxl8_line(gfx, x0, y0, x1, y1, color) pxl8.rect_fill = graphics.rect_fill
end pxl8.circle = graphics.circle
pxl8.circle_fill = graphics.circle_fill
function pxl8.rect(x, y, w, h, color) pxl8.text = graphics.text
C.pxl8_rect(gfx, x, y, w, h, color) pxl8.sprite = graphics.sprite
end pxl8.load_palette = graphics.load_palette
pxl8.load_sprite = graphics.load_sprite
function pxl8.rect_fill(x, y, w, h, color) pxl8.create_texture = graphics.create_texture
C.pxl8_rect_fill(gfx, x, y, w, h, color) pxl8.upload_atlas = graphics.upload_atlas
end pxl8.gfx_color_ramp = graphics.color_ramp
pxl8.gfx_fade_palette = graphics.fade_palette
function pxl8.circle(x, y, r, color)
C.pxl8_circle(gfx, x, y, r, color) pxl8.key_down = input.key_down
end pxl8.key_pressed = input.key_pressed
pxl8.key_released = input.key_released
function pxl8.circle_fill(x, y, r, color) pxl8.mouse_wheel_x = input.mouse_wheel_x
C.pxl8_circle_fill(gfx, x, y, r, color) pxl8.mouse_wheel_y = input.mouse_wheel_y
end pxl8.mouse_x = input.mouse_x
pxl8.mouse_y = input.mouse_y
function pxl8.text(str, x, y, color)
C.pxl8_text(gfx, str, x or 0, y or 0, color or 15) pxl8.vfx_raster_bars = vfx.raster_bars
end pxl8.vfx_plasma = vfx.plasma
pxl8.vfx_rotozoom = vfx.rotozoom
function pxl8.sprite(id, x, y, w, h, flip_x, flip_y) pxl8.vfx_tunnel = vfx.tunnel
C.pxl8_sprite(gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false) pxl8.vfx_explosion = vfx.explosion
end pxl8.vfx_fire = vfx.fire
pxl8.vfx_rain = vfx.rain
function pxl8.get_fps() pxl8.vfx_smoke = vfx.smoke
return C.pxl8_game_get_fps(game) pxl8.vfx_snow = vfx.snow
end pxl8.vfx_sparks = vfx.sparks
pxl8.vfx_starfield = vfx.starfield
function pxl8.get_width()
return C.pxl8_gfx_get_width(gfx) pxl8.particles_new = particles.new
end pxl8.particles_destroy = particles.destroy
pxl8.particles_clear = particles.clear
function pxl8.get_height() pxl8.particles_emit = particles.emit
return C.pxl8_gfx_get_height(gfx) pxl8.particles_update = particles.update
end pxl8.particles_render = particles.render
function pxl8.load_palette(filepath) pxl8.tilesheet_new = tilemap.tilesheet_new
return C.pxl8_gfx_load_palette(gfx, filepath) pxl8.tilesheet_destroy = tilemap.tilesheet_destroy
end pxl8.tilesheet_load = tilemap.tilesheet_load
pxl8.tilemap_new = tilemap.new
function pxl8.load_sprite(filepath) pxl8.tilemap_destroy = tilemap.destroy
local sprite_id = ffi.new("unsigned int[1]") pxl8.tilemap_set_tilesheet = tilemap.set_tilesheet
local result = C.pxl8_gfx_load_sprite(gfx, filepath, sprite_id) pxl8.tilemap_set_tile = tilemap.set_tile
if result == 0 then pxl8.tilemap_get_tile_id = tilemap.get_tile_id
return sprite_id[0] pxl8.tilemap_set_camera = tilemap.set_camera
else pxl8.tilemap_render = tilemap.render
return nil, result pxl8.tilemap_render_layer = tilemap.render_layer
end pxl8.tilemap_is_solid = tilemap.is_solid
end pxl8.tilemap_check_collision = tilemap.check_collision
pxl8.tilemap_get_tile_data = tilemap.get_tile_data
function pxl8.create_texture(pixels, width, height) pxl8.tilemap_load_ase = tilemap.load_ase
local pixel_data = ffi.new("u8[?]", width * height) pxl8.tilemap_set_tile_data = tilemap.set_tile_data
for i = 0, width * height - 1 do pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X
pixel_data[i] = pixels[i + 1] or 0 pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y
end pxl8.TILE_SOLID = tilemap.TILE_SOLID
local result = C.pxl8_gfx_create_texture(gfx, pixel_data, width, height) pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER
if result < 0 then
return nil pxl8.clear_zbuffer = gfx3d.clear_zbuffer
end pxl8.set_model = gfx3d.set_model
return result pxl8.set_view = gfx3d.set_view
end pxl8.set_projection = gfx3d.set_projection
pxl8.set_wireframe = gfx3d.set_wireframe
function pxl8.upload_atlas() pxl8.set_affine_textures = gfx3d.set_affine_textures
C.pxl8_gfx_upload_atlas(gfx) pxl8.set_backface_culling = gfx3d.set_backface_culling
end pxl8.draw_triangle_3d = gfx3d.draw_triangle
pxl8.draw_triangle_3d_textured = gfx3d.draw_triangle_textured
function pxl8.info(msg) pxl8.draw_line_3d = gfx3d.draw_line
C.pxl8_lua_info(msg)
end pxl8.mat4_identity = math3d.mat4_identity
pxl8.mat4_multiply = math3d.mat4_multiply
function pxl8.warn(msg) pxl8.mat4_translate = math3d.mat4_translate
C.pxl8_lua_warn(msg) pxl8.mat4_rotate_x = math3d.mat4_rotate_x
end pxl8.mat4_rotate_y = math3d.mat4_rotate_y
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
function pxl8.error(msg) pxl8.mat4_scale = math3d.mat4_scale
C.pxl8_lua_error(msg) pxl8.mat4_ortho = math3d.mat4_ortho
end pxl8.mat4_perspective = math3d.mat4_perspective
pxl8.mat4_lookat = math3d.mat4_lookat
function pxl8.debug(msg) pxl8.bounds = math3d.bounds
C.pxl8_lua_debug(msg)
end pxl8.ui_button = ui.button
pxl8.ui_checkbox = ui.checkbox
function pxl8.trace(msg) pxl8.ui_has_mouse_focus = ui.has_mouse_focus
C.pxl8_lua_trace(msg) pxl8.ui_indent = ui.indent
end pxl8.ui_label = ui.label
pxl8.ui_layout_row = ui.layout_row
function pxl8.key_down(key) pxl8.ui_window_begin = ui.window_begin
return C.pxl8_key_down(input, key) pxl8.ui_window_end = ui.window_end
end pxl8.ui_window_set_open = ui.window_set_open
function pxl8.key_pressed(key) pxl8.world_new = world.new
return C.pxl8_key_pressed(input, key) pxl8.world_destroy = world.destroy
end pxl8.world_load = world.load
pxl8.world_render = world.render
function pxl8.key_released(key) pxl8.world_unload = world.unload
return C.pxl8_key_released(input, key) pxl8.world_is_loaded = world.is_loaded
end pxl8.world_generate = world.generate
pxl8.world_apply_textures = world.apply_textures
function pxl8.mouse_wheel_x() pxl8.procgen_tex = world.procgen_tex
return C.pxl8_mouse_wheel_x(input) pxl8.PROCGEN_CAVE = world.PROCGEN_CAVE
end pxl8.PROCGEN_DUNGEON = world.PROCGEN_DUNGEON
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
function pxl8.mouse_wheel_y()
return C.pxl8_mouse_wheel_y(input) pxl8.transition_create = transition.create
end pxl8.transition_destroy = transition.destroy
pxl8.transition_get_progress = transition.get_progress
function pxl8.mouse_x() pxl8.transition_is_active = transition.is_active
return C.pxl8_mouse_x(input) pxl8.transition_is_complete = transition.is_complete
end pxl8.transition_render = transition.render
pxl8.transition_reset = transition.reset
function pxl8.mouse_y() pxl8.transition_set_color = transition.set_color
return C.pxl8_mouse_y(input) pxl8.transition_set_reverse = transition.set_reverse
end pxl8.transition_start = transition.start
pxl8.transition_stop = transition.stop
function pxl8.vfx_raster_bars(bars, time) pxl8.transition_update = transition.update
local c_bars = ffi.new("pxl8_raster_bar[?]", #bars)
for i, bar in ipairs(bars) do pxl8.anim_create = anim.create
c_bars[i-1].base_y = bar.base_y or 0 pxl8.anim_create_from_ase = anim.create_from_ase
c_bars[i-1].amplitude = bar.amplitude or 10 pxl8.anim_destroy = anim.destroy
c_bars[i-1].height = bar.height or 5 pxl8.anim_add_state = anim.add_state
c_bars[i-1].speed = bar.speed or 1.0 pxl8.anim_get_current_frame = anim.get_current_frame
c_bars[i-1].phase = bar.phase or 0 pxl8.anim_get_current_frame_id = anim.get_current_frame_id
c_bars[i-1].color = bar.color or 15 pxl8.anim_get_state = anim.get_state
c_bars[i-1].fade_color = bar.fade_color or bar.color or 15 pxl8.anim_has_state_machine = anim.has_state_machine
end pxl8.anim_is_complete = anim.is_complete
C.pxl8_vfx_raster_bars(gfx, c_bars, #bars, time) pxl8.anim_is_playing = anim.is_playing
end pxl8.anim_pause = anim.pause
pxl8.anim_play = anim.play
function pxl8.vfx_plasma(time, scale1, scale2, palette_offset) pxl8.anim_render_sprite = anim.render_sprite
C.pxl8_vfx_plasma(gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0) pxl8.anim_reset = anim.reset
end pxl8.anim_set_frame = anim.set_frame
pxl8.anim_set_loop = anim.set_loop
function pxl8.vfx_rotozoom(angle, zoom, cx, cy) pxl8.anim_set_reverse = anim.set_reverse
C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or pxl8.get_width()/2, cy or pxl8.get_height()/2) pxl8.anim_set_speed = anim.set_speed
end pxl8.anim_set_state = anim.set_state
pxl8.anim_stop = anim.stop
function pxl8.vfx_tunnel(time, speed, twist) pxl8.anim_update = anim.update
C.pxl8_vfx_tunnel(gfx, time, speed or 2.0, twist or 0.5)
end
function pxl8.particles_new(max_count)
return C.pxl8_particles_create(max_count or 1000)
end
function pxl8.particles_destroy(ps)
C.pxl8_particles_destroy(ps)
end
function pxl8.particles_clear(ps)
C.pxl8_particles_clear(ps)
end
function pxl8.particles_emit(ps, count)
C.pxl8_particles_emit(ps, count or 1)
end
function pxl8.particles_update(ps, dt)
C.pxl8_particles_update(ps, dt)
end
function pxl8.particles_render(ps)
C.pxl8_particles_render(ps, gfx)
end
function pxl8.vfx_explosion(ps, x, y, color, force)
C.pxl8_vfx_explosion(ps, x, y, color or 15, force or 200.0)
end
function pxl8.vfx_fire(ps, x, y, width, palette_start)
C.pxl8_vfx_fire(ps, x, y, width or 50, palette_start or 64)
end
function pxl8.vfx_rain(ps, width, wind)
C.pxl8_vfx_rain(ps, width or pxl8.get_width(), wind or 0.0)
end
function pxl8.vfx_smoke(ps, x, y, color)
C.pxl8_vfx_smoke(ps, x, y, color or 8)
end
function pxl8.vfx_snow(ps, width, wind)
C.pxl8_vfx_snow(ps, width or pxl8.get_width(), wind or 10.0)
end
function pxl8.vfx_sparks(ps, x, y, color)
C.pxl8_vfx_sparks(ps, x, y, color or 15)
end
function pxl8.vfx_starfield(ps, speed, spread)
C.pxl8_vfx_starfield(ps, speed or 5.0, spread or 500.0)
end
function pxl8.gfx_color_ramp(start, count, from_color, to_color)
C.pxl8_gfx_color_ramp(gfx, start, count, from_color, to_color)
end
function pxl8.gfx_fade_palette(start, count, amount, target_color)
C.pxl8_gfx_fade_palette(gfx, start, count, amount, target_color)
end
function pxl8.tilesheet_new(tile_size)
return C.pxl8_tilesheet_create(tile_size or 16)
end
function pxl8.tilesheet_destroy(tilesheet)
C.pxl8_tilesheet_destroy(tilesheet)
end
function pxl8.tilesheet_load(tilesheet, filepath)
return C.pxl8_tilesheet_load(tilesheet, filepath, gfx)
end
function pxl8.tilemap_new(width, height, tile_size)
return C.pxl8_tilemap_create(width, height, tile_size or 16)
end
function pxl8.tilemap_destroy(tilemap)
C.pxl8_tilemap_destroy(tilemap)
end
function pxl8.tilemap_set_tilesheet(tilemap, tilesheet)
return C.pxl8_tilemap_set_tilesheet(tilemap, tilesheet)
end
function pxl8.tilemap_set_tile(tilemap, layer, x, y, tile_id, flags)
C.pxl8_tilemap_set_tile(tilemap, layer or 0, x, y, tile_id or 0, flags or 0)
end
function pxl8.tilemap_get_tile_id(tilemap, layer, x, y)
return C.pxl8_tilemap_get_tile_id(tilemap, layer or 0, x, y)
end
function pxl8.tilemap_set_camera(tilemap, x, y)
C.pxl8_tilemap_set_camera(tilemap, x, y)
end
function pxl8.tilemap_render(tilemap)
C.pxl8_tilemap_render(tilemap, gfx)
end
function pxl8.tilemap_render_layer(tilemap, layer)
C.pxl8_tilemap_render_layer(tilemap, gfx, layer)
end
function pxl8.tilemap_is_solid(tilemap, x, y)
return C.pxl8_tilemap_is_solid(tilemap, x, y)
end
function pxl8.tilemap_check_collision(tilemap, x, y, w, h)
return C.pxl8_tilemap_check_collision(tilemap, x, y, w, h)
end
local tile_data = setmetatable({}, {__mode = "k"})
function pxl8.tilemap_get_tile_data(tilemap, tile_id)
if not tilemap or tile_id == 0 then return nil end
if not tile_data[tilemap] then return nil end
return tile_data[tilemap][tile_id]
end
function pxl8.tilemap_load_ase(tilemap, filepath, layer)
return C.pxl8_tilemap_load_ase(tilemap, filepath, layer or 0)
end
function pxl8.tilemap_set_tile_data(tilemap, tile_id, data)
if not tilemap or tile_id == 0 then return end
if not tile_data[tilemap] then tile_data[tilemap] = {} end
tile_data[tilemap][tile_id] = data
end
pxl8.TILE_FLIP_X = 1
pxl8.TILE_FLIP_Y = 2
pxl8.TILE_SOLID = 4
pxl8.TILE_TRIGGER = 8
function pxl8.clear_zbuffer()
C.pxl8_3d_clear_zbuffer(gfx)
end
function pxl8.set_model(mat)
C.pxl8_3d_set_model(gfx, mat)
end
function pxl8.set_view(mat)
C.pxl8_3d_set_view(gfx, mat)
end
function pxl8.set_projection(mat)
C.pxl8_3d_set_projection(gfx, mat)
end
function pxl8.set_wireframe(wireframe)
C.pxl8_3d_set_wireframe(gfx, wireframe)
end
function pxl8.set_affine_textures(affine)
C.pxl8_3d_set_affine_textures(gfx, affine)
end
function pxl8.set_backface_culling(culling)
C.pxl8_3d_set_backface_culling(gfx, culling)
end
function pxl8.draw_triangle_3d(v0, v1, v2, color)
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
C.pxl8_3d_draw_triangle_raw(gfx, vec0, vec1, vec2, color)
end
function pxl8.draw_triangle_3d_textured(v0, v1, v2, uv0, uv1, uv2, texture_id)
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
C.pxl8_3d_draw_triangle_textured(gfx, vec0, vec1, vec2,
uv0[1], uv0[2], uv1[1], uv1[2], uv2[1], uv2[2], texture_id)
end
function pxl8.draw_line_3d(p0, p1, color)
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})
C.pxl8_3d_draw_line_3d(gfx, vec0, vec1, color)
end
function pxl8.mat4_identity()
return C.pxl8_mat4_identity()
end
function pxl8.mat4_multiply(a, b)
return C.pxl8_mat4_multiply(a, b)
end
function pxl8.mat4_translate(x, y, z)
return C.pxl8_mat4_translate(x, y, z)
end
function pxl8.mat4_rotate_x(angle)
return C.pxl8_mat4_rotate_x(angle)
end
function pxl8.mat4_rotate_y(angle)
return C.pxl8_mat4_rotate_y(angle)
end
function pxl8.mat4_rotate_z(angle)
return C.pxl8_mat4_rotate_z(angle)
end
function pxl8.mat4_scale(x, y, z)
return C.pxl8_mat4_scale(x, y, z)
end
function pxl8.mat4_ortho(left, right, bottom, top, near, far)
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far)
end
function pxl8.mat4_perspective(fov, aspect, near, far)
return C.pxl8_mat4_perspective(fov, aspect, near, far)
end
function pxl8.mat4_lookat(eye, center, up)
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
end
function pxl8.bounds(x, y, w, h)
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
end
function pxl8.ui_button(label)
return C.pxl8_ui_button(ui, label)
end
function pxl8.ui_checkbox(label, state)
local state_ptr = ffi.new("bool[1]", state)
local changed = C.pxl8_ui_checkbox(ui, label, state_ptr)
return changed, state_ptr[0]
end
function pxl8.ui_has_mouse_focus()
return C.pxl8_ui_has_mouse_focus(ui)
end
function pxl8.ui_indent(amount)
C.pxl8_ui_indent(ui, amount)
end
function pxl8.ui_label(text)
C.pxl8_ui_label(ui, text)
end
function pxl8.ui_layout_row(item_count, widths, height)
local widths_array = widths
if type(widths) == "table" then
widths_array = ffi.new("int[?]", #widths, widths)
elseif type(widths) == "number" then
widths_array = ffi.new("int[1]", widths)
end
C.pxl8_ui_layout_row(ui, item_count, widths_array, height)
end
function pxl8.ui_window_begin(title, x, y, w, h, options)
local rect = ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
return C.pxl8_ui_window_begin(ui, title, rect, options or 0)
end
function pxl8.ui_window_end()
C.pxl8_ui_window_end(ui)
end
function pxl8.ui_window_set_open(title, open)
C.pxl8_ui_window_set_open(ui, title, open)
end
function pxl8.world_new()
return C.pxl8_world_create()
end
function pxl8.world_destroy(world)
C.pxl8_world_destroy(world)
end
function pxl8.world_load(world, filepath)
return C.pxl8_world_load(world, filepath)
end
function pxl8.world_render(world, camera_pos)
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
C.pxl8_world_render(world, gfx, vec)
end
function pxl8.world_unload(world)
C.pxl8_world_unload(world)
end
function pxl8.world_is_loaded(world)
return C.pxl8_world_is_loaded(world)
end
function pxl8.world_generate(world, params)
local c_params = ffi.new("pxl8_procgen_params")
c_params.type = params.type or C.PXL8_PROCGEN_CAVE
c_params.width = params.width or 32
c_params.height = params.height or 32
c_params.depth = params.depth or 0
c_params.seed = params.seed or 0
c_params.density = params.density or 0.45
c_params.iterations = params.iterations or 4
c_params.type_params = nil
return C.pxl8_world_generate(world, gfx, c_params)
end
pxl8.PROCGEN_CAVE = C.PXL8_PROCGEN_CAVE
pxl8.PROCGEN_DUNGEON = C.PXL8_PROCGEN_DUNGEON
pxl8.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
function pxl8.procgen_tex(params)
local width = params.width or 64
local height = params.height or 64
local buffer = ffi.new("u8[?]", width * height)
local tex_params = ffi.new("pxl8_procgen_tex_params")
local name = params.name or ""
ffi.copy(tex_params.name, name, math.min(#name, 15))
tex_params.seed = params.seed or 0
tex_params.width = width
tex_params.height = height
tex_params.scale = params.scale or 1.0
tex_params.roughness = params.roughness or 0.0
tex_params.base_color = params.base_color or 0
tex_params.variation = params.variation or 0
C.pxl8_procgen_tex(buffer, tex_params)
local tex_id = C.pxl8_gfx_create_texture(gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
function pxl8.world_apply_textures(world, texture_defs)
local count = #texture_defs
local textures = ffi.new("pxl8_world_texture[?]", count)
for i, def in ipairs(texture_defs) do
local idx = i - 1
ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
textures[idx].texture_id = def.texture_id or 0
if def.rule then
textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
function(normal, face, bsp)
return def.rule(normal[0], face, bsp)
end)
else
textures[idx].rule = nil
end
end
local result = C.pxl8_world_apply_textures(world, textures, count)
return result
end
return pxl8 return pxl8

110
src/lua/pxl8/anim.lua Normal file
View file

@ -0,0 +1,110 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local anim = {}
function anim.create(frame_ids, frame_durations)
local frame_count = #frame_ids
local c_frame_ids = ffi.new("u32[?]", frame_count)
local c_frame_durations = nil
for i = 1, frame_count do
c_frame_ids[i-1] = frame_ids[i]
end
if frame_durations then
c_frame_durations = ffi.new("u16[?]", frame_count)
for i = 1, frame_count do
c_frame_durations[i-1] = frame_durations[i]
end
end
return C.pxl8_anim_create(c_frame_ids, c_frame_durations, frame_count)
end
function anim.create_from_ase(filepath)
return C.pxl8_anim_create_from_ase(core.gfx, filepath)
end
function anim.destroy(a)
C.pxl8_anim_destroy(a)
end
function anim.add_state(a, name, state_anim)
return C.pxl8_anim_add_state(a, name, state_anim)
end
function anim.get_current_frame(a)
return C.pxl8_anim_get_current_frame(a)
end
function anim.get_current_frame_id(a)
return C.pxl8_anim_get_current_frame_id(a)
end
function anim.get_state(a)
local state_name = C.pxl8_anim_get_state(a)
if state_name ~= nil then
return ffi.string(state_name)
end
return nil
end
function anim.has_state_machine(a)
return C.pxl8_anim_has_state_machine(a)
end
function anim.is_complete(a)
return C.pxl8_anim_is_complete(a)
end
function anim.is_playing(a)
return C.pxl8_anim_is_playing(a)
end
function anim.pause(a)
C.pxl8_anim_pause(a)
end
function anim.play(a)
C.pxl8_anim_play(a)
end
function anim.render_sprite(a, x, y, w, h)
C.pxl8_anim_render_sprite(a, core.gfx, x, y, w, h)
end
function anim.reset(a)
C.pxl8_anim_reset(a)
end
function anim.set_frame(a, frame)
C.pxl8_anim_set_frame(a, frame)
end
function anim.set_loop(a, loop)
C.pxl8_anim_set_loop(a, loop)
end
function anim.set_reverse(a, reverse)
C.pxl8_anim_set_reverse(a, reverse)
end
function anim.set_speed(a, speed)
C.pxl8_anim_set_speed(a, speed)
end
function anim.set_state(a, name)
return C.pxl8_anim_set_state(a, name)
end
function anim.stop(a)
C.pxl8_anim_stop(a)
end
function anim.update(a, dt)
C.pxl8_anim_update(a, dt)
end
return anim

45
src/lua/pxl8/core.lua Normal file
View file

@ -0,0 +1,45 @@
local ffi = require("ffi")
local C = ffi.C
local core = {}
function core.init(game_ptr, gfx_ptr, input_ptr, ui_ptr)
core.game = game_ptr
core.gfx = gfx_ptr
core.input = input_ptr
core.ui = ui_ptr
end
function core.get_fps()
return C.pxl8_game_get_fps(core.game)
end
function core.get_width()
return C.pxl8_gfx_get_width(core.gfx)
end
function core.get_height()
return C.pxl8_gfx_get_height(core.gfx)
end
function core.info(msg)
C.pxl8_lua_info(msg)
end
function core.warn(msg)
C.pxl8_lua_warn(msg)
end
function core.error(msg)
C.pxl8_lua_error(msg)
end
function core.debug(msg)
C.pxl8_lua_debug(msg)
end
function core.trace(msg)
C.pxl8_lua_trace(msg)
end
return core

56
src/lua/pxl8/gfx3d.lua Normal file
View file

@ -0,0 +1,56 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local gfx3d = {}
function gfx3d.clear_zbuffer()
C.pxl8_3d_clear_zbuffer(core.gfx)
end
function gfx3d.set_model(mat)
C.pxl8_3d_set_model(core.gfx, mat)
end
function gfx3d.set_view(mat)
C.pxl8_3d_set_view(core.gfx, mat)
end
function gfx3d.set_projection(mat)
C.pxl8_3d_set_projection(core.gfx, mat)
end
function gfx3d.set_wireframe(wireframe)
C.pxl8_3d_set_wireframe(core.gfx, wireframe)
end
function gfx3d.set_affine_textures(affine)
C.pxl8_3d_set_affine_textures(core.gfx, affine)
end
function gfx3d.set_backface_culling(culling)
C.pxl8_3d_set_backface_culling(core.gfx, culling)
end
function gfx3d.draw_triangle(v0, v1, v2, color)
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
C.pxl8_3d_draw_triangle_raw(core.gfx, vec0, vec1, vec2, color)
end
function gfx3d.draw_triangle_textured(v0, v1, v2, uv0, uv1, uv2, texture_id)
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
C.pxl8_3d_draw_triangle_textured(core.gfx, vec0, vec1, vec2,
uv0[1], uv0[2], uv1[1], uv1[2], uv2[1], uv2[2], texture_id)
end
function gfx3d.draw_line(p0, p1, color)
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})
C.pxl8_3d_draw_line_3d(core.gfx, vec0, vec1, color)
end
return gfx3d

85
src/lua/pxl8/graphics.lua Normal file
View file

@ -0,0 +1,85 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local graphics = {}
function graphics.clr(color)
C.pxl8_clr(core.gfx, color or 0)
end
function graphics.pixel(x, y, color)
if color then
C.pxl8_pixel(core.gfx, x, y, color)
else
return C.pxl8_get_pixel(core.gfx, x, y)
end
end
function graphics.line(x0, y0, x1, y1, color)
C.pxl8_line(core.gfx, x0, y0, x1, y1, color)
end
function graphics.rect(x, y, w, h, color)
C.pxl8_rect(core.gfx, x, y, w, h, color)
end
function graphics.rect_fill(x, y, w, h, color)
C.pxl8_rect_fill(core.gfx, x, y, w, h, color)
end
function graphics.circle(x, y, r, color)
C.pxl8_circle(core.gfx, x, y, r, color)
end
function graphics.circle_fill(x, y, r, color)
C.pxl8_circle_fill(core.gfx, x, y, r, color)
end
function graphics.text(str, x, y, color)
C.pxl8_text(core.gfx, str, x or 0, y or 0, color or 15)
end
function graphics.sprite(id, x, y, w, h, flip_x, flip_y)
C.pxl8_sprite(core.gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false)
end
function graphics.load_palette(filepath)
return C.pxl8_gfx_load_palette(core.gfx, filepath)
end
function graphics.load_sprite(filepath)
local sprite_id = ffi.new("unsigned int[1]")
local result = C.pxl8_gfx_load_sprite(core.gfx, filepath, sprite_id)
if result == 0 then
return sprite_id[0]
else
return nil, result
end
end
function graphics.create_texture(pixels, width, height)
local pixel_data = ffi.new("u8[?]", width * height)
for i = 0, width * height - 1 do
pixel_data[i] = pixels[i + 1] or 0
end
local result = C.pxl8_gfx_create_texture(core.gfx, pixel_data, width, height)
if result < 0 then
return nil
end
return result
end
function graphics.upload_atlas()
C.pxl8_gfx_upload_atlas(core.gfx)
end
function graphics.color_ramp(start, count, from_color, to_color)
C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color)
end
function graphics.fade_palette(start, count, amount, target_color)
C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color)
end
return graphics

35
src/lua/pxl8/input.lua Normal file
View file

@ -0,0 +1,35 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local input = {}
function input.key_down(key)
return C.pxl8_key_down(core.input, key)
end
function input.key_pressed(key)
return C.pxl8_key_pressed(core.input, key)
end
function input.key_released(key)
return C.pxl8_key_released(core.input, key)
end
function input.mouse_wheel_x()
return C.pxl8_mouse_wheel_x(core.input)
end
function input.mouse_wheel_y()
return C.pxl8_mouse_wheel_y(core.input)
end
function input.mouse_x()
return C.pxl8_mouse_x(core.input)
end
function input.mouse_y()
return C.pxl8_mouse_y(core.input)
end
return input

53
src/lua/pxl8/math.lua Normal file
View file

@ -0,0 +1,53 @@
local ffi = require("ffi")
local C = ffi.C
local math3d = {}
function math3d.mat4_identity()
return C.pxl8_mat4_identity()
end
function math3d.mat4_multiply(a, b)
return C.pxl8_mat4_multiply(a, b)
end
function math3d.mat4_translate(x, y, z)
return C.pxl8_mat4_translate(x, y, z)
end
function math3d.mat4_rotate_x(angle)
return C.pxl8_mat4_rotate_x(angle)
end
function math3d.mat4_rotate_y(angle)
return C.pxl8_mat4_rotate_y(angle)
end
function math3d.mat4_rotate_z(angle)
return C.pxl8_mat4_rotate_z(angle)
end
function math3d.mat4_scale(x, y, z)
return C.pxl8_mat4_scale(x, y, z)
end
function math3d.mat4_ortho(left, right, bottom, top, near, far)
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far)
end
function math3d.mat4_perspective(fov, aspect, near, far)
return C.pxl8_mat4_perspective(fov, aspect, near, far)
end
function math3d.mat4_lookat(eye, center, up)
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
end
function math3d.bounds(x, y, w, h)
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
end
return math3d

View file

@ -0,0 +1,31 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local particles = {}
function particles.new(max_count)
return C.pxl8_particles_create(max_count or 1000)
end
function particles.destroy(ps)
C.pxl8_particles_destroy(ps)
end
function particles.clear(ps)
C.pxl8_particles_clear(ps)
end
function particles.emit(ps, count)
C.pxl8_particles_emit(ps, count or 1)
end
function particles.update(ps, dt)
C.pxl8_particles_update(ps, dt)
end
function particles.render(ps)
C.pxl8_particles_render(ps, core.gfx)
end
return particles

82
src/lua/pxl8/tilemap.lua Normal file
View file

@ -0,0 +1,82 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local tilemap = {}
tilemap.TILE_FLIP_X = 1
tilemap.TILE_FLIP_Y = 2
tilemap.TILE_SOLID = 4
tilemap.TILE_TRIGGER = 8
local tile_data = setmetatable({}, {__mode = "k"})
function tilemap.tilesheet_new(tile_size)
return C.pxl8_tilesheet_create(tile_size or 16)
end
function tilemap.tilesheet_destroy(tilesheet)
C.pxl8_tilesheet_destroy(tilesheet)
end
function tilemap.tilesheet_load(tilesheet, filepath)
return C.pxl8_tilesheet_load(tilesheet, filepath, core.gfx)
end
function tilemap.new(width, height, tile_size)
return C.pxl8_tilemap_create(width, height, tile_size or 16)
end
function tilemap.destroy(tm)
C.pxl8_tilemap_destroy(tm)
end
function tilemap.set_tilesheet(tm, tilesheet)
return C.pxl8_tilemap_set_tilesheet(tm, tilesheet)
end
function tilemap.set_tile(tm, layer, x, y, tile_id, flags)
C.pxl8_tilemap_set_tile(tm, layer or 0, x, y, tile_id or 0, flags or 0)
end
function tilemap.get_tile_id(tm, layer, x, y)
return C.pxl8_tilemap_get_tile_id(tm, layer or 0, x, y)
end
function tilemap.set_camera(tm, x, y)
C.pxl8_tilemap_set_camera(tm, x, y)
end
function tilemap.render(tm)
C.pxl8_tilemap_render(tm, core.gfx)
end
function tilemap.render_layer(tm, layer)
C.pxl8_tilemap_render_layer(tm, core.gfx, layer)
end
function tilemap.is_solid(tm, x, y)
return C.pxl8_tilemap_is_solid(tm, x, y)
end
function tilemap.check_collision(tm, x, y, w, h)
return C.pxl8_tilemap_check_collision(tm, x, y, w, h)
end
function tilemap.get_tile_data(tm, tile_id)
if not tm or tile_id == 0 then return nil end
if not tile_data[tm] then return nil end
return tile_data[tm][tile_id]
end
function tilemap.load_ase(tm, filepath, layer)
return C.pxl8_tilemap_load_ase(tm, filepath, layer or 0)
end
function tilemap.set_tile_data(tm, tile_id, data)
if not tm or tile_id == 0 then return end
if not tile_data[tm] then tile_data[tm] = {} end
tile_data[tm][tile_id] = data
end
return tilemap

View file

@ -0,0 +1,68 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local transition = {}
local transition_types = {
fade = 0,
wipe_left = 1,
wipe_right = 2,
wipe_up = 3,
wipe_down = 4,
circle_open = 5,
circle_close = 6,
dissolve = 7,
pixelate = 8
}
function transition.create(type_name, duration)
local type_id = transition_types[type_name] or 0
return C.pxl8_transition_create(type_id, duration or 1.0)
end
function transition.destroy(t)
C.pxl8_transition_destroy(t)
end
function transition.get_progress(t)
return C.pxl8_transition_get_progress(t)
end
function transition.is_active(t)
return C.pxl8_transition_is_active(t)
end
function transition.is_complete(t)
return C.pxl8_transition_is_complete(t)
end
function transition.render(t)
C.pxl8_transition_render(t, core.gfx)
end
function transition.reset(t)
C.pxl8_transition_reset(t)
end
function transition.set_color(t, color)
C.pxl8_transition_set_color(t, color)
end
function transition.set_reverse(t, reverse)
C.pxl8_transition_set_reverse(t, reverse)
end
function transition.start(t)
C.pxl8_transition_start(t)
end
function transition.stop(t)
C.pxl8_transition_stop(t)
end
function transition.update(t, dt)
C.pxl8_transition_update(t, dt)
end
return transition

52
src/lua/pxl8/ui.lua Normal file
View file

@ -0,0 +1,52 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local ui = {}
function ui.button(label)
return C.pxl8_ui_button(core.ui, label)
end
function ui.checkbox(label, state)
local state_ptr = ffi.new("bool[1]", state)
local changed = C.pxl8_ui_checkbox(core.ui, label, state_ptr)
return changed, state_ptr[0]
end
function ui.has_mouse_focus()
return C.pxl8_ui_has_mouse_focus(core.ui)
end
function ui.indent(amount)
C.pxl8_ui_indent(core.ui, amount)
end
function ui.label(text)
C.pxl8_ui_label(core.ui, text)
end
function ui.layout_row(item_count, widths, height)
local widths_array = widths
if type(widths) == "table" then
widths_array = ffi.new("int[?]", #widths, widths)
elseif type(widths) == "number" then
widths_array = ffi.new("int[1]", widths)
end
C.pxl8_ui_layout_row(core.ui, item_count, widths_array, height)
end
function ui.window_begin(title, x, y, w, h, options)
local rect = ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
return C.pxl8_ui_window_begin(core.ui, title, rect, options or 0)
end
function ui.window_end()
C.pxl8_ui_window_end(core.ui)
end
function ui.window_set_open(title, open)
C.pxl8_ui_window_set_open(core.ui, title, open)
end
return ui

65
src/lua/pxl8/vfx.lua Normal file
View file

@ -0,0 +1,65 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local vfx = {}
function vfx.raster_bars(bars, time)
local c_bars = ffi.new("pxl8_raster_bar[?]", #bars)
for i, bar in ipairs(bars) do
c_bars[i-1].base_y = bar.base_y or 0
c_bars[i-1].amplitude = bar.amplitude or 10
c_bars[i-1].height = bar.height or 5
c_bars[i-1].speed = bar.speed or 1.0
c_bars[i-1].phase = bar.phase or 0
c_bars[i-1].color = bar.color or 15
c_bars[i-1].fade_color = bar.fade_color or bar.color or 15
end
C.pxl8_vfx_raster_bars(core.gfx, c_bars, #bars, time)
end
function vfx.plasma(time, scale1, scale2, palette_offset)
C.pxl8_vfx_plasma(core.gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0)
end
function vfx.rotozoom(angle, zoom, cx, cy)
local width = C.pxl8_gfx_get_width(core.gfx)
local height = C.pxl8_gfx_get_height(core.gfx)
C.pxl8_vfx_rotozoom(core.gfx, angle, zoom, cx or width/2, cy or height/2)
end
function vfx.tunnel(time, speed, twist)
C.pxl8_vfx_tunnel(core.gfx, time, speed or 2.0, twist or 0.5)
end
function vfx.explosion(ps, x, y, color, force)
C.pxl8_vfx_explosion(ps, x, y, color or 15, force or 200.0)
end
function vfx.fire(ps, x, y, width, palette_start)
C.pxl8_vfx_fire(ps, x, y, width or 50, palette_start or 64)
end
function vfx.rain(ps, width, wind)
local w = width or C.pxl8_gfx_get_width(core.gfx)
C.pxl8_vfx_rain(ps, w, wind or 0.0)
end
function vfx.smoke(ps, x, y, color)
C.pxl8_vfx_smoke(ps, x, y, color or 8)
end
function vfx.snow(ps, width, wind)
local w = width or C.pxl8_gfx_get_width(core.gfx)
C.pxl8_vfx_snow(ps, w, wind or 10.0)
end
function vfx.sparks(ps, x, y, color)
C.pxl8_vfx_sparks(ps, x, y, color or 15)
end
function vfx.starfield(ps, speed, spread)
C.pxl8_vfx_starfield(ps, speed or 5.0, spread or 500.0)
end
return vfx

99
src/lua/pxl8/world.lua Normal file
View file

@ -0,0 +1,99 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local world = {}
world.PROCGEN_CAVE = C.PXL8_PROCGEN_CAVE
world.PROCGEN_DUNGEON = C.PXL8_PROCGEN_DUNGEON
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
function world.new()
return C.pxl8_world_create()
end
function world.destroy(w)
C.pxl8_world_destroy(w)
end
function world.load(w, filepath)
return C.pxl8_world_load(w, filepath)
end
function world.render(w, camera_pos)
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
C.pxl8_world_render(w, core.gfx, vec)
end
function world.unload(w)
C.pxl8_world_unload(w)
end
function world.is_loaded(w)
return C.pxl8_world_is_loaded(w)
end
function world.generate(w, params)
local c_params = ffi.new("pxl8_procgen_params")
c_params.type = params.type or C.PXL8_PROCGEN_CAVE
c_params.width = params.width or 32
c_params.height = params.height or 32
c_params.depth = params.depth or 0
c_params.seed = params.seed or 0
c_params.density = params.density or 0.45
c_params.iterations = params.iterations or 4
c_params.type_params = nil
return C.pxl8_world_generate(w, core.gfx, c_params)
end
function world.procgen_tex(params)
local width = params.width or 64
local height = params.height or 64
local buffer = ffi.new("u8[?]", width * height)
local tex_params = ffi.new("pxl8_procgen_tex_params")
local name = params.name or ""
ffi.copy(tex_params.name, name, math.min(#name, 15))
tex_params.seed = params.seed or 0
tex_params.width = width
tex_params.height = height
tex_params.scale = params.scale or 1.0
tex_params.roughness = params.roughness or 0.0
tex_params.base_color = params.base_color or 0
tex_params.variation = params.variation or 0
C.pxl8_procgen_tex(buffer, tex_params)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
function world.apply_textures(w, texture_defs)
local count = #texture_defs
local textures = ffi.new("pxl8_world_texture[?]", count)
for i, def in ipairs(texture_defs) do
local idx = i - 1
ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
textures[idx].texture_id = def.texture_id or 0
if def.rule then
textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
function(normal, face, bsp)
return def.rule(normal[0], face, bsp)
end)
else
textures[idx].rule = nil
end
end
local result = C.pxl8_world_apply_textures(w, textures, count)
return result
end
return world

457
src/pxl8_anim.c Normal file
View file

@ -0,0 +1,457 @@
#include "pxl8_anim.h"
#include <stdlib.h>
#include <string.h>
#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_macros.h"
#define PXL8_ANIM_MAX_STATES 32
typedef struct pxl8_anim_state {
char* name;
pxl8_anim* anim;
} pxl8_anim_state;
typedef struct pxl8_anim_state_machine {
pxl8_anim_state states[PXL8_ANIM_MAX_STATES];
u16 state_count;
u16 current_state;
} pxl8_anim_state_machine;
pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count) {
if (!frame_ids || frame_count == 0) {
pxl8_error("Invalid animation parameters");
return NULL;
}
pxl8_anim* anim = (pxl8_anim*)calloc(1, sizeof(pxl8_anim));
if (!anim) {
pxl8_error("Failed to allocate animation");
return NULL;
}
anim->frame_ids = (u32*)malloc(frame_count * sizeof(u32));
if (!anim->frame_ids) {
pxl8_error("Failed to allocate frame IDs");
free(anim);
return NULL;
}
memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32));
if (frame_durations) {
anim->frame_durations = (u16*)malloc(frame_count * sizeof(u16));
if (!anim->frame_durations) {
pxl8_error("Failed to allocate frame durations");
free(anim->frame_ids);
free(anim);
return NULL;
}
memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16));
} else {
anim->frame_durations = (u16*)calloc(frame_count, sizeof(u16));
if (!anim->frame_durations) {
pxl8_error("Failed to allocate frame durations");
free(anim->frame_ids);
free(anim);
return NULL;
}
for (u16 i = 0; i < frame_count; i++) {
anim->frame_durations[i] = 100;
}
}
anim->frame_count = frame_count;
anim->current_frame = 0;
anim->time_accumulator = 0.0f;
anim->loop = true;
anim->playing = true;
anim->reverse = false;
anim->speed = 1.0f;
anim->state_machine = NULL;
anim->on_complete = NULL;
anim->on_frame_change = NULL;
anim->userdata = NULL;
return anim;
}
pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
if (!gfx || !path) {
pxl8_error("Invalid parameters for ASE animation creation");
return NULL;
}
pxl8_ase_file ase_file;
pxl8_result result = pxl8_ase_load(path, &ase_file);
if (result != PXL8_OK) {
pxl8_error("Failed to load ASE file: %s", path);
return NULL;
}
if (ase_file.frame_count == 0) {
pxl8_error("ASE file has no frames: %s", path);
pxl8_ase_destroy(&ase_file);
return NULL;
}
u32* frame_ids = (u32*)malloc(ase_file.frame_count * sizeof(u32));
u16* frame_durations = (u16*)malloc(ase_file.frame_count * sizeof(u16));
if (!frame_ids || !frame_durations) {
pxl8_error("Failed to allocate frame arrays");
free(frame_ids);
free(frame_durations);
pxl8_ase_destroy(&ase_file);
return NULL;
}
for (u32 i = 0; i < ase_file.frame_count; i++) {
pxl8_ase_frame* frame = &ase_file.frames[i];
result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height);
if (result != PXL8_OK) {
pxl8_error("Failed to create texture for frame %u", i);
free(frame_ids);
free(frame_durations);
pxl8_ase_destroy(&ase_file);
return NULL;
}
frame_ids[i] = i;
frame_durations[i] = frame->duration;
}
pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count);
free(frame_ids);
free(frame_durations);
pxl8_ase_destroy(&ase_file);
return anim;
}
void pxl8_anim_destroy(pxl8_anim* anim) {
if (!anim) return;
if (anim->state_machine) {
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
free(anim->state_machine->states[i].name);
pxl8_anim_destroy(anim->state_machine->states[i].anim);
}
free(anim->state_machine);
}
free(anim->frame_ids);
free(anim->frame_durations);
free(anim);
}
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) {
if (!anim || !name || !state_anim) {
return PXL8_ERROR_NULL_POINTER;
}
if (!anim->state_machine) {
anim->state_machine = (pxl8_anim_state_machine*)calloc(1, sizeof(pxl8_anim_state_machine));
if (!anim->state_machine) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
}
if (anim->state_machine->state_count >= PXL8_ANIM_MAX_STATES) {
pxl8_error("Cannot add more states, maximum %d reached", PXL8_ANIM_MAX_STATES);
return PXL8_ERROR_OUT_OF_MEMORY;
}
u16 idx = anim->state_machine->state_count;
anim->state_machine->states[idx].name = strdup(name);
if (!anim->state_machine->states[idx].name) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
anim->state_machine->states[idx].anim = state_anim;
anim->state_machine->state_count++;
return PXL8_OK;
}
u16 pxl8_anim_get_current_frame(const pxl8_anim* anim) {
if (!anim) return 0;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
return state_anim->current_frame;
}
}
return anim->current_frame;
}
u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim) {
if (!anim || !anim->frame_ids) return 0;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim && state_anim->frame_ids) {
return state_anim->frame_ids[state_anim->current_frame];
}
}
return anim->frame_ids[anim->current_frame];
}
const char* pxl8_anim_get_state(const pxl8_anim* anim) {
if (!anim || !anim->state_machine) return NULL;
if (anim->state_machine->current_state < anim->state_machine->state_count) {
return anim->state_machine->states[anim->state_machine->current_state].name;
}
return NULL;
}
bool pxl8_anim_has_state_machine(const pxl8_anim* anim) {
return anim && anim->state_machine && anim->state_machine->state_count > 0;
}
bool pxl8_anim_is_complete(const pxl8_anim* anim) {
if (!anim) return true;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
return pxl8_anim_is_complete(state_anim);
}
}
if (anim->loop) return false;
if (anim->reverse) {
return anim->current_frame == 0;
} else {
return anim->current_frame >= anim->frame_count - 1;
}
}
bool pxl8_anim_is_playing(const pxl8_anim* anim) {
if (!anim) return false;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
return state_anim->playing;
}
}
return anim->playing;
}
void pxl8_anim_pause(pxl8_anim* anim) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
state_anim->playing = false;
return;
}
}
anim->playing = false;
}
void pxl8_anim_play(pxl8_anim* anim) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
state_anim->playing = true;
return;
}
}
anim->playing = true;
}
void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h) {
if (!anim || !gfx) return;
u32 sprite_id = pxl8_anim_get_current_frame_id(anim);
pxl8_sprite(gfx, sprite_id, x, y, w, h);
}
void pxl8_anim_reset(pxl8_anim* anim) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
pxl8_anim_reset(state_anim);
return;
}
}
anim->current_frame = anim->reverse ? anim->frame_count - 1 : 0;
anim->time_accumulator = 0.0f;
}
void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
pxl8_anim_set_frame(state_anim, frame);
return;
}
}
if (frame < anim->frame_count) {
anim->current_frame = frame;
anim->time_accumulator = 0.0f;
}
}
void pxl8_anim_set_loop(pxl8_anim* anim, bool loop) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
state_anim->loop = loop;
return;
}
}
anim->loop = loop;
}
void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
state_anim->reverse = reverse;
return;
}
}
anim->reverse = reverse;
}
void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
state_anim->speed = speed;
return;
}
}
anim->speed = speed;
}
pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name) {
if (!anim || !name) {
return PXL8_ERROR_NULL_POINTER;
}
if (!anim->state_machine) {
return PXL8_ERROR_INVALID_ARGUMENT;
}
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
if (strcmp(anim->state_machine->states[i].name, name) == 0) {
if (anim->state_machine->current_state != i) {
anim->state_machine->current_state = i;
pxl8_anim_reset(anim->state_machine->states[i].anim);
}
return PXL8_OK;
}
}
pxl8_error("Animation state not found: %s", name);
return PXL8_ERROR_INVALID_ARGUMENT;
}
void pxl8_anim_stop(pxl8_anim* anim) {
if (!anim) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
pxl8_anim_stop(state_anim);
return;
}
}
anim->playing = false;
pxl8_anim_reset(anim);
}
void pxl8_anim_update(pxl8_anim* anim, f32 dt) {
if (!anim || !anim->playing) return;
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
if (state_anim) {
pxl8_anim_update(state_anim, dt);
return;
}
}
if (anim->frame_count == 0 || !anim->frame_durations) return;
anim->time_accumulator += dt * anim->speed * 1000.0f;
u16 current_duration = anim->frame_durations[anim->current_frame];
if (current_duration == 0) return;
while (anim->time_accumulator >= current_duration) {
anim->time_accumulator -= current_duration;
u16 old_frame = anim->current_frame;
if (anim->reverse) {
if (anim->current_frame > 0) {
anim->current_frame--;
} else {
if (anim->loop) {
anim->current_frame = anim->frame_count - 1;
} else {
anim->playing = false;
if (anim->on_complete) {
anim->on_complete(anim->userdata);
}
break;
}
}
} else {
if (anim->current_frame < anim->frame_count - 1) {
anim->current_frame++;
} else {
if (anim->loop) {
anim->current_frame = 0;
} else {
anim->playing = false;
if (anim->on_complete) {
anim->on_complete(anim->userdata);
}
break;
}
}
}
if (old_frame != anim->current_frame && anim->on_frame_change) {
anim->on_frame_change(anim->current_frame, anim->userdata);
}
current_duration = anim->frame_durations[anim->current_frame];
if (current_duration == 0) break;
}
}

56
src/pxl8_anim.h Normal file
View file

@ -0,0 +1,56 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_types.h"
typedef struct pxl8_anim_state_machine pxl8_anim_state_machine;
typedef struct pxl8_anim {
u32* frame_ids;
u16* frame_durations;
u16 frame_count;
u16 current_frame;
f32 time_accumulator;
bool loop;
bool playing;
bool reverse;
f32 speed;
pxl8_anim_state_machine* state_machine;
void (*on_complete)(void* userdata);
void (*on_frame_change)(u16 frame, void* userdata);
void* userdata;
} pxl8_anim;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);
pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);
void pxl8_anim_destroy(pxl8_anim* anim);
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);
u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);
u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);
const char* pxl8_anim_get_state(const pxl8_anim* anim);
bool pxl8_anim_has_state_machine(const pxl8_anim* anim);
bool pxl8_anim_is_complete(const pxl8_anim* anim);
bool pxl8_anim_is_playing(const pxl8_anim* anim);
void pxl8_anim_pause(pxl8_anim* anim);
void pxl8_anim_play(pxl8_anim* anim);
void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);
void pxl8_anim_reset(pxl8_anim* anim);
void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);
void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);
void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);
void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);
pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name);
void pxl8_anim_stop(pxl8_anim* anim);
void pxl8_anim_update(pxl8_anim* anim, f32 dt);
#ifdef __cplusplus
}
#endif

View file

@ -141,7 +141,7 @@ pxl8_gfx* pxl8_gfx_create(
i32 window_width, i32 window_width,
i32 window_height i32 window_height
) { ) {
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(*gfx)); pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx));
if (!gfx) { if (!gfx) {
pxl8_error("Failed to allocate graphics context"); pxl8_error("Failed to allocate graphics context");
return NULL; return NULL;

View file

@ -28,26 +28,12 @@ static f32 prng_float(void) {
return (f32)prng_next() / (f32)0xFFFFFFFF; return (f32)prng_next() / (f32)0xFFFFFFFF;
} }
static cave_grid* cave_grid_create(i32 width, i32 height) { static bool cave_grid_init(cave_grid* grid, i32 width, i32 height) {
cave_grid* grid = malloc(sizeof(cave_grid));
if (!grid) return NULL;
grid->width = width; grid->width = width;
grid->height = height; grid->height = height;
grid->cells = calloc(width * height, sizeof(u8)); grid->cells = calloc(width * height, sizeof(u8));
if (!grid->cells) { return grid->cells != NULL;
free(grid);
return NULL;
}
return grid;
}
static void cave_grid_destroy(cave_grid* grid) {
if (!grid) return;
free(grid->cells);
free(grid);
} }
static u8 cave_grid_get(const cave_grid* grid, i32 x, i32 y) { static u8 cave_grid_get(const cave_grid* grid, i32 x, i32 y) {
@ -102,19 +88,19 @@ static void cave_grid_initialize(cave_grid* grid, f32 density) {
} }
static void cave_grid_smooth(cave_grid* grid) { static void cave_grid_smooth(cave_grid* grid) {
cave_grid* temp = cave_grid_create(grid->width, grid->height); cave_grid temp;
if (!temp) return; if (!cave_grid_init(&temp, grid->width, grid->height)) return;
for (i32 y = 0; y < grid->height; y++) { for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) { for (i32 x = 0; x < grid->width; x++) {
i32 neighbors = cave_grid_count_neighbors(grid, x, y); i32 neighbors = cave_grid_count_neighbors(grid, x, y);
u8 value = (neighbors > 4) ? 1 : 0; u8 value = (neighbors > 4) ? 1 : 0;
cave_grid_set(temp, x, y, value); cave_grid_set(&temp, x, y, value);
} }
} }
memcpy(grid->cells, temp->cells, grid->width * grid->height); memcpy(grid->cells, temp.cells, grid->width * grid->height);
cave_grid_destroy(temp); free(temp.cells);
} }
static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) {
@ -366,19 +352,19 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) {
static pxl8_result procgen_cave(pxl8_bsp* bsp, const pxl8_procgen_params* params) { static pxl8_result procgen_cave(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
prng_seed(params->seed); prng_seed(params->seed);
cave_grid* grid = cave_grid_create(params->width, params->height); cave_grid grid;
if (!grid) { if (!cave_grid_init(&grid, params->width, params->height)) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
cave_grid_initialize(grid, params->density); cave_grid_initialize(&grid, params->density);
for (i32 i = 0; i < params->iterations; i++) { for (i32 i = 0; i < params->iterations; i++) {
cave_grid_smooth(grid); cave_grid_smooth(&grid);
} }
pxl8_result result = cave_to_bsp(bsp, grid); pxl8_result result = cave_to_bsp(bsp, &grid);
cave_grid_destroy(grid); free(grid.cells);
return result; return result;
} }

View file

@ -188,6 +188,44 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist);\n" "void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist);\n"
"void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\n" "void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\n"
"\n" "\n"
"typedef struct pxl8_transition pxl8_transition;\n"
"typedef struct pxl8_anim pxl8_anim;\n"
"\n"
"pxl8_transition* pxl8_transition_create(i32 type, f32 duration);\n"
"void pxl8_transition_destroy(pxl8_transition* transition);\n"
"f32 pxl8_transition_get_progress(const pxl8_transition* transition);\n"
"bool pxl8_transition_is_active(const pxl8_transition* transition);\n"
"bool pxl8_transition_is_complete(const pxl8_transition* transition);\n"
"void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);\n"
"void pxl8_transition_reset(pxl8_transition* transition);\n"
"void pxl8_transition_set_color(pxl8_transition* transition, u32 color);\n"
"void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);\n"
"void pxl8_transition_start(pxl8_transition* transition);\n"
"void pxl8_transition_stop(pxl8_transition* transition);\n"
"void pxl8_transition_update(pxl8_transition* transition, f32 dt);\n"
"\n"
"pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);\n"
"pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);\n"
"void pxl8_anim_destroy(pxl8_anim* anim);\n"
"i32 pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);\n"
"u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);\n"
"u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);\n"
"const char* pxl8_anim_get_state(const pxl8_anim* anim);\n"
"bool pxl8_anim_has_state_machine(const pxl8_anim* anim);\n"
"bool pxl8_anim_is_complete(const pxl8_anim* anim);\n"
"bool pxl8_anim_is_playing(const pxl8_anim* anim);\n"
"void pxl8_anim_pause(pxl8_anim* anim);\n"
"void pxl8_anim_play(pxl8_anim* anim);\n"
"void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n"
"void pxl8_anim_reset(pxl8_anim* anim);\n"
"void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);\n"
"void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);\n"
"void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);\n"
"void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);\n"
"i32 pxl8_anim_set_state(pxl8_anim* anim, const char* name);\n"
"void pxl8_anim_stop(pxl8_anim* anim);\n"
"void pxl8_anim_update(pxl8_anim* anim, f32 dt);\n"
"\n"
"typedef struct { float x, y, z; } pxl8_vec3;\n" "typedef struct { float x, y, z; } pxl8_vec3;\n"
"typedef struct { float x, y, z, w; } pxl8_vec4;\n" "typedef struct { float x, y, z, w; } pxl8_vec4;\n"
"typedef struct { float m[16]; } pxl8_mat4;\n" "typedef struct { float m[16]; } pxl8_mat4;\n"

View file

@ -25,7 +25,7 @@ static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution,
const char* title, i32 win_w, i32 win_h) { const char* title, i32 win_w, i32 win_h) {
(void)mode; (void)mode;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(*ctx)); pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context));
if (!ctx) { if (!ctx) {
pxl8_error("Failed to allocate SDL3 context"); pxl8_error("Failed to allocate SDL3 context");
return NULL; return NULL;

248
src/pxl8_transition.c Normal file
View file

@ -0,0 +1,248 @@
#include "pxl8_transition.h"
#include <stdlib.h>
#include <math.h>
#include "pxl8_macros.h"
#include "pxl8_math.h"
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration) {
if (duration <= 0.0f) {
pxl8_error("Invalid transition duration: %f", duration);
return NULL;
}
pxl8_transition* transition = (pxl8_transition*)calloc(1, sizeof(pxl8_transition));
if (!transition) {
pxl8_error("Failed to allocate transition");
return NULL;
}
transition->type = type;
transition->duration = duration;
transition->time = 0.0f;
transition->active = false;
transition->reverse = false;
transition->color = 0xFF000000;
transition->on_complete = NULL;
transition->userdata = NULL;
return transition;
}
void pxl8_transition_destroy(pxl8_transition* transition) {
if (!transition) return;
free(transition);
}
f32 pxl8_transition_get_progress(const pxl8_transition* transition) {
if (!transition) return 0.0f;
f32 t = transition->time / transition->duration;
if (t < 0.0f) t = 0.0f;
if (t > 1.0f) t = 1.0f;
return transition->reverse ? 1.0f - t : t;
}
bool pxl8_transition_is_active(const pxl8_transition* transition) {
return transition && transition->active;
}
bool pxl8_transition_is_complete(const pxl8_transition* transition) {
if (!transition) return true;
return transition->time >= transition->duration;
}
void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
if (!transition || !gfx || !transition->active) return;
f32 progress = pxl8_transition_get_progress(transition);
i32 width = pxl8_gfx_get_width(gfx);
i32 height = pxl8_gfx_get_height(gfx);
switch (transition->type) {
case PXL8_TRANSITION_FADE: {
u8 alpha = (u8)(progress * 255.0f);
u32 fade_color = (transition->color & 0x00FFFFFF) | (alpha << 24);
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
u32 bg = pxl8_get_pixel(gfx, x, y);
u32 r_bg = (bg >> 16) & 0xFF;
u32 g_bg = (bg >> 8) & 0xFF;
u32 b_bg = bg & 0xFF;
u32 r_fg = (fade_color >> 16) & 0xFF;
u32 g_fg = (fade_color >> 8) & 0xFF;
u32 b_fg = fade_color & 0xFF;
u32 r = (r_bg * (255 - alpha) + r_fg * alpha) / 255;
u32 g = (g_bg * (255 - alpha) + g_fg * alpha) / 255;
u32 b = (b_bg * (255 - alpha) + b_fg * alpha) / 255;
pxl8_pixel(gfx, x, y, 0xFF000000 | (r << 16) | (g << 8) | b);
}
}
break;
}
case PXL8_TRANSITION_WIPE_LEFT: {
i32 wipe_x = (i32)(width * progress);
pxl8_rect_fill(gfx, 0, 0, wipe_x, height, transition->color);
break;
}
case PXL8_TRANSITION_WIPE_RIGHT: {
i32 wipe_x = (i32)(width * (1.0f - progress));
pxl8_rect_fill(gfx, wipe_x, 0, width - wipe_x, height, transition->color);
break;
}
case PXL8_TRANSITION_WIPE_UP: {
i32 wipe_y = (i32)(height * progress);
pxl8_rect_fill(gfx, 0, 0, width, wipe_y, transition->color);
break;
}
case PXL8_TRANSITION_WIPE_DOWN: {
i32 wipe_y = (i32)(height * (1.0f - progress));
pxl8_rect_fill(gfx, 0, wipe_y, width, height - wipe_y, transition->color);
break;
}
case PXL8_TRANSITION_CIRCLE_CLOSE: {
i32 center_x = width / 2;
i32 center_y = height / 2;
i32 max_radius = (i32)sqrtf((f32)(center_x * center_x + center_y * center_y));
i32 radius = (i32)(max_radius * (1.0f - progress));
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
i32 dx = x - center_x;
i32 dy = y - center_y;
i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy));
if (dist > radius) {
pxl8_pixel(gfx, x, y, transition->color);
}
}
}
break;
}
case PXL8_TRANSITION_CIRCLE_OPEN: {
i32 center_x = width / 2;
i32 center_y = height / 2;
i32 max_radius = (i32)sqrtf((f32)(center_x * center_x + center_y * center_y));
i32 radius = (i32)(max_radius * progress);
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
i32 dx = x - center_x;
i32 dy = y - center_y;
i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy));
if (dist < radius) {
pxl8_pixel(gfx, x, y, transition->color);
}
}
}
break;
}
case PXL8_TRANSITION_DISSOLVE: {
u32 seed = 12345;
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
seed = seed * 1103515245 + 12345;
f32 noise = (f32)((seed / 65536) % 1000) / 1000.0f;
if (noise < progress) {
pxl8_pixel(gfx, x, y, transition->color);
}
}
}
break;
}
case PXL8_TRANSITION_PIXELATE: {
i32 max_block_size = 32;
i32 block_size = (i32)(max_block_size * progress);
if (block_size < 1) block_size = 1;
u8* fb = pxl8_gfx_get_framebuffer(gfx);
if (!fb) break;
for (i32 y = 0; y < height; y += block_size) {
for (i32 x = 0; x < width; x += block_size) {
u32 color_sum_r = 0, color_sum_g = 0, color_sum_b = 0;
i32 count = 0;
for (i32 by = 0; by < block_size && y + by < height; by++) {
for (i32 bx = 0; bx < block_size && x + bx < width; bx++) {
u32 color = pxl8_get_pixel(gfx, x + bx, y + by);
color_sum_r += (color >> 16) & 0xFF;
color_sum_g += (color >> 8) & 0xFF;
color_sum_b += color & 0xFF;
count++;
}
}
if (count > 0) {
u32 avg_color = 0xFF000000 |
((color_sum_r / count) << 16) |
((color_sum_g / count) << 8) |
(color_sum_b / count);
for (i32 by = 0; by < block_size && y + by < height; by++) {
for (i32 bx = 0; bx < block_size && x + bx < width; bx++) {
pxl8_pixel(gfx, x + bx, y + by, avg_color);
}
}
}
}
}
break;
}
}
}
void pxl8_transition_reset(pxl8_transition* transition) {
if (!transition) return;
transition->time = 0.0f;
transition->active = false;
}
void pxl8_transition_set_color(pxl8_transition* transition, u32 color) {
if (!transition) return;
transition->color = color;
}
void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse) {
if (!transition) return;
transition->reverse = reverse;
}
void pxl8_transition_start(pxl8_transition* transition) {
if (!transition) return;
transition->active = true;
transition->time = 0.0f;
}
void pxl8_transition_stop(pxl8_transition* transition) {
if (!transition) return;
transition->active = false;
}
void pxl8_transition_update(pxl8_transition* transition, f32 dt) {
if (!transition || !transition->active) return;
transition->time += dt;
if (transition->time >= transition->duration) {
transition->time = transition->duration;
transition->active = false;
if (transition->on_complete) {
transition->on_complete(transition->userdata);
}
}
}

51
src/pxl8_transition.h Normal file
View file

@ -0,0 +1,51 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_types.h"
typedef enum pxl8_transition_type {
PXL8_TRANSITION_FADE,
PXL8_TRANSITION_WIPE_LEFT,
PXL8_TRANSITION_WIPE_RIGHT,
PXL8_TRANSITION_WIPE_UP,
PXL8_TRANSITION_WIPE_DOWN,
PXL8_TRANSITION_CIRCLE_OPEN,
PXL8_TRANSITION_CIRCLE_CLOSE,
PXL8_TRANSITION_DISSOLVE,
PXL8_TRANSITION_PIXELATE
} pxl8_transition_type;
typedef struct pxl8_transition {
pxl8_transition_type type;
f32 duration;
f32 time;
bool active;
bool reverse;
u32 color;
void (*on_complete)(void* userdata);
void* userdata;
} pxl8_transition;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration);
void pxl8_transition_destroy(pxl8_transition* transition);
f32 pxl8_transition_get_progress(const pxl8_transition* transition);
bool pxl8_transition_is_active(const pxl8_transition* transition);
bool pxl8_transition_is_complete(const pxl8_transition* transition);
void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);
void pxl8_transition_reset(pxl8_transition* transition);
void pxl8_transition_set_color(pxl8_transition* transition, u32 color);
void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);
void pxl8_transition_start(pxl8_transition* transition);
void pxl8_transition_stop(pxl8_transition* transition);
void pxl8_transition_update(pxl8_transition* transition, f32 dt);
#ifdef __cplusplus
}
#endif