From 53ea4dd8832d85790ea8eae570bd458c1044f0a9 Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 15 Nov 2025 08:30:51 -0600 Subject: [PATCH 1/6] add jump to demo #9 --- demo/main.fnl | 30 ++++++++--------- demo/mod/cube3d.fnl | 76 +++++++++++++++++++++---------------------- demo/mod/worldgen.fnl | 56 +++++++++++++++++++++---------- 3 files changed, 92 insertions(+), 70 deletions(-) diff --git a/demo/main.fnl b/demo/main.fnl index 6228e39..b84240c 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -5,10 +5,10 @@ (var time 0) (var active-demo :logo) (var particles nil) -(var fire-init false) -(var rain-init false) -(var snow-init false) -(var use-famicube-palette false) +(var fire-init? false) +(var rain-init? false) +(var snow-init? false) +(var use-famicube-palette? false) (var logo-x 256) (var logo-y 148) @@ -32,18 +32,18 @@ (when (pxl8.key_pressed "4") (set active-demo :raster)) (when (pxl8.key_pressed "5") (set active-demo :fire) - (set fire-init false)) + (set fire-init? false)) (when (pxl8.key_pressed "6") (set active-demo :rain) - (set rain-init false)) + (set rain-init? false)) (when (pxl8.key_pressed "7") (set active-demo :snow) - (set snow-init false)) + (set snow-init? false)) (when (pxl8.key_pressed "8") (set active-demo :cube3d)) (when (pxl8.key_pressed "9") (set active-demo :worldgen)) (when (pxl8.key_pressed "=") - (set use-famicube-palette (not use-famicube-palette)) - (local palette-path (if use-famicube-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) + (set use-famicube-palette? (not use-famicube-palette?)) + (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (pxl8.load_palette palette-path)) (case active-demo @@ -81,28 +81,28 @@ :fire (do (pxl8.clr 0) (when particles - (when (not fire-init) + (when (not fire-init?) (pxl8.particles_clear particles) (pxl8.vfx_fire particles 160 140 100 12) - (set fire-init true)) + (set fire-init? true)) (pxl8.particles_render particles))) :rain (do (pxl8.clr 0) (when particles - (when (not rain-init) + (when (not rain-init?) (pxl8.particles_clear particles) (pxl8.vfx_rain particles 320 10.0) - (set rain-init true)) + (set rain-init? true)) (pxl8.particles_render particles))) :snow (do (pxl8.clr 0) (when particles - (when (not snow-init) + (when (not snow-init?) (pxl8.particles_clear particles) (pxl8.vfx_snow particles 320 5.0) - (set snow-init true)) + (set snow-init? true)) (pxl8.particles_render particles))) :cube3d (cube3d.frame) diff --git a/demo/mod/cube3d.fnl b/demo/mod/cube3d.fnl index 4807d1e..97f76e3 100644 --- a/demo/mod/cube3d.fnl +++ b/demo/mod/cube3d.fnl @@ -4,20 +4,20 @@ (var angle-x 0) (var angle-y 0) (var angle-z 0) -(var auto-rotate true) -(var orthographic true) -(var wireframe true) +(var auto-rotate? true) +(var orthographic? true) +(var wireframe? true) (var time 0) (var zoom 5.0) (var texture-id nil) -(var use-texture false) -(var affine false) +(var use-texture? false) +(var affine? false) (var cam-x 0) (var cam-y 2) (var cam-z 12) (var cam-yaw 0) (var cam-pitch -0.2) -(var show-debug-ui false) +(var show-debug-ui? false) (var fps 0) (var fps-accumulator 0) (var fps-frame-count 0) @@ -26,19 +26,19 @@ (set angle-x 0) (set angle-y 0) (set angle-z 0) - (set auto-rotate true) - (set orthographic true) - (set wireframe true) + (set auto-rotate? true) + (set orthographic? true) + (set wireframe? true) (set time 0) (set zoom 5.0) - (set use-texture false) - (set affine false) + (set use-texture? false) + (set affine? false) (set cam-x 0) (set cam-y 2) (set cam-z 12) (set cam-yaw 0) (set cam-pitch -0.2) - (set show-debug-ui false) + (set show-debug-ui? false) (set fps 0) (set fps-accumulator 0) (set fps-frame-count 0) @@ -87,7 +87,7 @@ (let [wheel-y (pxl8.mouse_wheel_y)] (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))) (let [zoom-speed 0.5 forward-x (* (math.sin cam-yaw) wheel-y zoom-speed) @@ -103,7 +103,7 @@ right-x (* (math.cos cam-yaw) move-speed dt) right-z (* (- (math.sin cam-yaw)) move-speed dt)] - (if orthographic + (if orthographic? (do (when (pxl8.key_down "w") (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)))) (when (pxl8.key_pressed " ") - (set wireframe (not wireframe))) + (set wireframe? (not wireframe?))) (when (pxl8.key_pressed "f") - (set affine (not affine))) + (set affine? (not affine?))) (when (pxl8.key_pressed "p") - (set orthographic (not orthographic))) + (set orthographic? (not orthographic?))) (when (pxl8.key_pressed "r") - (set auto-rotate (not auto-rotate))) + (set auto-rotate? (not auto-rotate?))) (when (pxl8.key_pressed "t") - (set use-texture (not use-texture))) + (set use-texture? (not use-texture?))) (when (pxl8.key_pressed "F8") - (set show-debug-ui (not show-debug-ui)) - (pxl8.ui_window_set_open "Debug Menu (F8)" show-debug-ui)) + (set show-debug-ui? (not 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-y (+ angle-y (* dt 0.5))) (set angle-z (+ angle-z (* dt 0.3))))) @@ -178,7 +178,7 @@ (pxl8.set_model model)) (let [vertices (make-cube-vertices)] - (if (and use-texture texture-id) + (if (and use-texture? texture-id) (let [faces (make-cube-faces-with-uvs)] (each [_i face-data (ipairs faces)] (let [tri-indices face-data.tri @@ -206,11 +206,11 @@ (pxl8.clr 0) (pxl8.clear_zbuffer) - (pxl8.set_affine_textures affine) + (pxl8.set_affine_textures affine?) (pxl8.set_backface_culling true) - (pxl8.set_wireframe wireframe) + (pxl8.set_wireframe wireframe?) - (if orthographic + (if orthographic? (let [size zoom aspect (/ (pxl8.get_width) (pxl8.get_height)) w (* size aspect) @@ -233,19 +233,19 @@ (draw-cube [3 1 -7] 0.9 1.2) (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 - :wireframe wireframe - :auto-rotate auto-rotate - :orthographic orthographic - :use-texture use-texture - :affine affine})] - (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.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.use-texture nil) (set use-texture new-state.use-texture)) - (when (not= new-state.affine nil) (set affine new-state.affine)))) + :wireframe wireframe? + :auto-rotate auto-rotate? + :orthographic orthographic? + :use-texture use-texture? + :affine affine?})] + (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.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.use-texture nil) (set use-texture? new-state.use-texture)) + (when (not= new-state.affine nil) (set affine? new-state.affine)))) {:init init :update update diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index 1cbef1a..8e91b37 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -2,11 +2,14 @@ (var world nil) (var cam-x 1000) -(local cam-y 64) +(var cam-y 64) (var cam-z 1000) (var cam-yaw 0) (var cam-pitch 0) (var bob-time 0) +(var velocity-y 0) +(var grounded? true) +(var land-squash 0) (local move-speed 200) (local turn-speed 2.0) @@ -15,6 +18,11 @@ (local max-pitch 1.5) (local cell-size 64) (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 [] (set world (pxl8.world_new)) @@ -72,31 +80,28 @@ (var move-right 0) (when (pxl8.key_down "w") - (set move-forward (+ move-forward 1)) - (set moving true)) + (set move-forward (+ move-forward 1))) (when (pxl8.key_down "s") - (set move-forward (- move-forward 1)) - (set moving true)) + (set move-forward (- move-forward 1))) (when (pxl8.key_down "q") - (set move-right (- move-right 1)) - (set moving true)) + (set move-right (- move-right 1))) (when (pxl8.key_down "e") - (set move-right (+ move-right 1)) - (set moving true)) + (set move-right (+ move-right 1))) + + (set moving (or (not= move-forward 0) (not= move-right 0))) (var new-x cam-x) (var new-z cam-z) (when moving - (let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))] - (when (> len 0) - (let [norm-forward (/ move-forward len) - norm-right (/ move-right len)] - (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))))))))) + (let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right))) + norm-forward (/ move-forward len) + norm-right (/ move-right len)] + (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))))))) (when (and (>= new-x 0) (<= new-x grid-max) (>= new-z 0) (<= new-z grid-max)) @@ -115,7 +120,24 @@ (when (pxl8.key_down "down") (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))) (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))))) @@ -125,7 +147,7 @@ (when (pxl8.world_is_loaded world) (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-z (- (math.cos cam-yaw)) target-x (+ cam-x forward-x) From bbd3c1223ebe83e0bc12f0b60e81a0a93094d220 Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 15 Nov 2025 11:31:33 -0600 Subject: [PATCH 2/6] add LICENSE file --- LICENSE | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d0a1fa1 --- /dev/null +++ b/LICENSE @@ -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. From d8def23e26f66c6cf7142c5e070bb4dd9bab4d6d Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 15 Nov 2025 11:40:27 -0600 Subject: [PATCH 3/6] add anim and transitions and re-org lua api scripts --- README.md | 8 + demo/main.fnl | 49 ++- src/lua/pxl8.lua | 696 +++++++++--------------------------- src/lua/pxl8/anim.lua | 110 ++++++ src/lua/pxl8/core.lua | 45 +++ src/lua/pxl8/gfx3d.lua | 56 +++ src/lua/pxl8/graphics.lua | 85 +++++ src/lua/pxl8/input.lua | 35 ++ src/lua/pxl8/math.lua | 53 +++ src/lua/pxl8/particles.lua | 31 ++ src/lua/pxl8/tilemap.lua | 82 +++++ src/lua/pxl8/transition.lua | 68 ++++ src/lua/pxl8/ui.lua | 52 +++ src/lua/pxl8/vfx.lua | 65 ++++ src/lua/pxl8/world.lua | 99 +++++ src/pxl8_anim.c | 457 +++++++++++++++++++++++ src/pxl8_anim.h | 56 +++ src/pxl8_gfx.c | 2 +- src/pxl8_procgen.c | 40 +-- src/pxl8_script.c | 38 ++ src/pxl8_sdl3.c | 2 +- src/pxl8_transition.c | 248 +++++++++++++ src/pxl8_transition.h | 51 +++ 23 files changed, 1855 insertions(+), 573 deletions(-) create mode 100644 src/lua/pxl8/anim.lua create mode 100644 src/lua/pxl8/core.lua create mode 100644 src/lua/pxl8/gfx3d.lua create mode 100644 src/lua/pxl8/graphics.lua create mode 100644 src/lua/pxl8/input.lua create mode 100644 src/lua/pxl8/math.lua create mode 100644 src/lua/pxl8/particles.lua create mode 100644 src/lua/pxl8/tilemap.lua create mode 100644 src/lua/pxl8/transition.lua create mode 100644 src/lua/pxl8/ui.lua create mode 100644 src/lua/pxl8/vfx.lua create mode 100644 src/lua/pxl8/world.lua create mode 100644 src/pxl8_anim.c create mode 100644 src/pxl8_anim.h create mode 100644 src/pxl8_transition.c create mode 100644 src/pxl8_transition.h diff --git a/README.md b/README.md index d68f117..a4d7173 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,11 @@ @@@@@@@@@@@@@@@@@@@@@@@~ ,@@@,,0@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@,,@@@@@@@@@@@@@@@@@@@@@@@@@ + +## 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. diff --git a/demo/main.fnl b/demo/main.fnl index b84240c..32e8115 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -15,6 +15,14 @@ (var logo-dx 100) (var logo-dy 80) (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 [] (cube3d.init) @@ -26,21 +34,27 @@ (global update (fn [dt] (set time (+ time dt)) - (when (pxl8.key_pressed "1") (set active-demo :logo)) - (when (pxl8.key_pressed "2") (set active-demo :plasma)) - (when (pxl8.key_pressed "3") (set active-demo :tunnel)) - (when (pxl8.key_pressed "4") (set active-demo :raster)) - (when (pxl8.key_pressed "5") - (set active-demo :fire) - (set fire-init? false)) - (when (pxl8.key_pressed "6") - (set active-demo :rain) - (set rain-init? false)) - (when (pxl8.key_pressed "7") - (set active-demo :snow) - (set snow-init? false)) - (when (pxl8.key_pressed "8") (set active-demo :cube3d)) - (when (pxl8.key_pressed "9") (set active-demo :worldgen)) + (when transition + (pxl8.transition_update transition dt) + (when (pxl8.transition_is_complete transition) + (when transition-pending + (set active-demo transition-pending) + (set transition-pending nil) + (when (= active-demo :fire) (set fire-init? false)) + (when (= active-demo :rain) (set rain-init? false)) + (when (= active-demo :snow) (set snow-init? false))) + (pxl8.transition_destroy transition) + (set transition nil))) + + (when (pxl8.key_pressed "1") (switch-demo :logo)) + (when (pxl8.key_pressed "2") (switch-demo :plasma)) + (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 "=") (set use-famicube-palette? (not use-famicube-palette?)) (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) @@ -109,4 +123,7 @@ :worldgen (worldgen.frame) - _ (pxl8.clr 0)))) + _ (pxl8.clr 0)) + + (when transition + (pxl8.transition_render transition)))) diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 7b6d540..24c202e 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -1,534 +1,174 @@ local ffi = require("ffi") -local C = ffi.C -local game = _pxl8_game -local gfx = _pxl8_gfx -local input = _pxl8_input -local ui = _pxl8_ui + +local core = require("pxl8.core") +local graphics = require("pxl8.graphics") +local input = require("pxl8.input") +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 = {} -function pxl8.clr(color) - C.pxl8_clr(gfx, color or 0) -end - -function pxl8.pixel(x, y, color) - if color then - C.pxl8_pixel(gfx, x, y, color) - else - return C.pxl8_get_pixel(gfx, x, y) - end -end - -function pxl8.line(x0, y0, x1, y1, color) - C.pxl8_line(gfx, x0, y0, x1, y1, color) -end - -function pxl8.rect(x, y, w, h, color) - C.pxl8_rect(gfx, x, y, w, h, color) -end - -function pxl8.rect_fill(x, y, w, h, color) - C.pxl8_rect_fill(gfx, x, y, w, h, color) -end - -function pxl8.circle(x, y, r, color) - C.pxl8_circle(gfx, x, y, r, color) -end - -function pxl8.circle_fill(x, y, r, color) - C.pxl8_circle_fill(gfx, x, y, r, color) -end - -function pxl8.text(str, x, y, color) - C.pxl8_text(gfx, str, x or 0, y or 0, color or 15) -end - -function pxl8.sprite(id, x, y, w, h, flip_x, flip_y) - 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) -end - -function pxl8.get_fps() - return C.pxl8_game_get_fps(game) -end - -function pxl8.get_width() - return C.pxl8_gfx_get_width(gfx) -end - -function pxl8.get_height() - return C.pxl8_gfx_get_height(gfx) -end - -function pxl8.load_palette(filepath) - return C.pxl8_gfx_load_palette(gfx, filepath) -end - -function pxl8.load_sprite(filepath) - local sprite_id = ffi.new("unsigned int[1]") - local result = C.pxl8_gfx_load_sprite(gfx, filepath, sprite_id) - if result == 0 then - return sprite_id[0] - else - return nil, result - end -end - -function pxl8.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(gfx, pixel_data, width, height) - if result < 0 then - return nil - end - return result -end - -function pxl8.upload_atlas() - C.pxl8_gfx_upload_atlas(gfx) -end - -function pxl8.info(msg) - C.pxl8_lua_info(msg) -end - -function pxl8.warn(msg) - C.pxl8_lua_warn(msg) -end - -function pxl8.error(msg) - C.pxl8_lua_error(msg) -end - -function pxl8.debug(msg) - C.pxl8_lua_debug(msg) -end - -function pxl8.trace(msg) - C.pxl8_lua_trace(msg) -end - -function pxl8.key_down(key) - return C.pxl8_key_down(input, key) -end - -function pxl8.key_pressed(key) - return C.pxl8_key_pressed(input, key) -end - -function pxl8.key_released(key) - return C.pxl8_key_released(input, key) -end - -function pxl8.mouse_wheel_x() - return C.pxl8_mouse_wheel_x(input) -end - -function pxl8.mouse_wheel_y() - return C.pxl8_mouse_wheel_y(input) -end - -function pxl8.mouse_x() - return C.pxl8_mouse_x(input) -end - -function pxl8.mouse_y() - return C.pxl8_mouse_y(input) -end - -function pxl8.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(gfx, c_bars, #bars, time) -end - -function pxl8.vfx_plasma(time, scale1, scale2, palette_offset) - C.pxl8_vfx_plasma(gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0) -end - -function pxl8.vfx_rotozoom(angle, zoom, cx, cy) - C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or pxl8.get_width()/2, cy or pxl8.get_height()/2) -end - -function pxl8.vfx_tunnel(time, speed, twist) - 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 +pxl8.get_fps = core.get_fps +pxl8.get_width = core.get_width +pxl8.get_height = core.get_height +pxl8.info = core.info +pxl8.warn = core.warn +pxl8.error = core.error +pxl8.debug = core.debug +pxl8.trace = core.trace + +pxl8.clr = graphics.clr +pxl8.pixel = graphics.pixel +pxl8.line = graphics.line +pxl8.rect = graphics.rect +pxl8.rect_fill = graphics.rect_fill +pxl8.circle = graphics.circle +pxl8.circle_fill = graphics.circle_fill +pxl8.text = graphics.text +pxl8.sprite = graphics.sprite +pxl8.load_palette = graphics.load_palette +pxl8.load_sprite = graphics.load_sprite +pxl8.create_texture = graphics.create_texture +pxl8.upload_atlas = graphics.upload_atlas +pxl8.gfx_color_ramp = graphics.color_ramp +pxl8.gfx_fade_palette = graphics.fade_palette + +pxl8.key_down = input.key_down +pxl8.key_pressed = input.key_pressed +pxl8.key_released = input.key_released +pxl8.mouse_wheel_x = input.mouse_wheel_x +pxl8.mouse_wheel_y = input.mouse_wheel_y +pxl8.mouse_x = input.mouse_x +pxl8.mouse_y = input.mouse_y + +pxl8.vfx_raster_bars = vfx.raster_bars +pxl8.vfx_plasma = vfx.plasma +pxl8.vfx_rotozoom = vfx.rotozoom +pxl8.vfx_tunnel = vfx.tunnel +pxl8.vfx_explosion = vfx.explosion +pxl8.vfx_fire = vfx.fire +pxl8.vfx_rain = vfx.rain +pxl8.vfx_smoke = vfx.smoke +pxl8.vfx_snow = vfx.snow +pxl8.vfx_sparks = vfx.sparks +pxl8.vfx_starfield = vfx.starfield + +pxl8.particles_new = particles.new +pxl8.particles_destroy = particles.destroy +pxl8.particles_clear = particles.clear +pxl8.particles_emit = particles.emit +pxl8.particles_update = particles.update +pxl8.particles_render = particles.render + +pxl8.tilesheet_new = tilemap.tilesheet_new +pxl8.tilesheet_destroy = tilemap.tilesheet_destroy +pxl8.tilesheet_load = tilemap.tilesheet_load +pxl8.tilemap_new = tilemap.new +pxl8.tilemap_destroy = tilemap.destroy +pxl8.tilemap_set_tilesheet = tilemap.set_tilesheet +pxl8.tilemap_set_tile = tilemap.set_tile +pxl8.tilemap_get_tile_id = tilemap.get_tile_id +pxl8.tilemap_set_camera = tilemap.set_camera +pxl8.tilemap_render = tilemap.render +pxl8.tilemap_render_layer = tilemap.render_layer +pxl8.tilemap_is_solid = tilemap.is_solid +pxl8.tilemap_check_collision = tilemap.check_collision +pxl8.tilemap_get_tile_data = tilemap.get_tile_data +pxl8.tilemap_load_ase = tilemap.load_ase +pxl8.tilemap_set_tile_data = tilemap.set_tile_data +pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X +pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y +pxl8.TILE_SOLID = tilemap.TILE_SOLID +pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER + +pxl8.clear_zbuffer = gfx3d.clear_zbuffer +pxl8.set_model = gfx3d.set_model +pxl8.set_view = gfx3d.set_view +pxl8.set_projection = gfx3d.set_projection +pxl8.set_wireframe = gfx3d.set_wireframe +pxl8.set_affine_textures = gfx3d.set_affine_textures +pxl8.set_backface_culling = gfx3d.set_backface_culling +pxl8.draw_triangle_3d = gfx3d.draw_triangle +pxl8.draw_triangle_3d_textured = gfx3d.draw_triangle_textured +pxl8.draw_line_3d = gfx3d.draw_line + +pxl8.mat4_identity = math3d.mat4_identity +pxl8.mat4_multiply = math3d.mat4_multiply +pxl8.mat4_translate = math3d.mat4_translate +pxl8.mat4_rotate_x = math3d.mat4_rotate_x +pxl8.mat4_rotate_y = math3d.mat4_rotate_y +pxl8.mat4_rotate_z = math3d.mat4_rotate_z +pxl8.mat4_scale = math3d.mat4_scale +pxl8.mat4_ortho = math3d.mat4_ortho +pxl8.mat4_perspective = math3d.mat4_perspective +pxl8.mat4_lookat = math3d.mat4_lookat +pxl8.bounds = math3d.bounds + +pxl8.ui_button = ui.button +pxl8.ui_checkbox = ui.checkbox +pxl8.ui_has_mouse_focus = ui.has_mouse_focus +pxl8.ui_indent = ui.indent +pxl8.ui_label = ui.label +pxl8.ui_layout_row = ui.layout_row +pxl8.ui_window_begin = ui.window_begin +pxl8.ui_window_end = ui.window_end +pxl8.ui_window_set_open = ui.window_set_open + +pxl8.world_new = world.new +pxl8.world_destroy = world.destroy +pxl8.world_load = world.load +pxl8.world_render = world.render +pxl8.world_unload = world.unload +pxl8.world_is_loaded = world.is_loaded +pxl8.world_generate = world.generate +pxl8.world_apply_textures = world.apply_textures +pxl8.procgen_tex = world.procgen_tex +pxl8.PROCGEN_CAVE = world.PROCGEN_CAVE +pxl8.PROCGEN_DUNGEON = world.PROCGEN_DUNGEON +pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN + +pxl8.transition_create = transition.create +pxl8.transition_destroy = transition.destroy +pxl8.transition_get_progress = transition.get_progress +pxl8.transition_is_active = transition.is_active +pxl8.transition_is_complete = transition.is_complete +pxl8.transition_render = transition.render +pxl8.transition_reset = transition.reset +pxl8.transition_set_color = transition.set_color +pxl8.transition_set_reverse = transition.set_reverse +pxl8.transition_start = transition.start +pxl8.transition_stop = transition.stop +pxl8.transition_update = transition.update + +pxl8.anim_create = anim.create +pxl8.anim_create_from_ase = anim.create_from_ase +pxl8.anim_destroy = anim.destroy +pxl8.anim_add_state = anim.add_state +pxl8.anim_get_current_frame = anim.get_current_frame +pxl8.anim_get_current_frame_id = anim.get_current_frame_id +pxl8.anim_get_state = anim.get_state +pxl8.anim_has_state_machine = anim.has_state_machine +pxl8.anim_is_complete = anim.is_complete +pxl8.anim_is_playing = anim.is_playing +pxl8.anim_pause = anim.pause +pxl8.anim_play = anim.play +pxl8.anim_render_sprite = anim.render_sprite +pxl8.anim_reset = anim.reset +pxl8.anim_set_frame = anim.set_frame +pxl8.anim_set_loop = anim.set_loop +pxl8.anim_set_reverse = anim.set_reverse +pxl8.anim_set_speed = anim.set_speed +pxl8.anim_set_state = anim.set_state +pxl8.anim_stop = anim.stop +pxl8.anim_update = anim.update return pxl8 diff --git a/src/lua/pxl8/anim.lua b/src/lua/pxl8/anim.lua new file mode 100644 index 0000000..d15ba5c --- /dev/null +++ b/src/lua/pxl8/anim.lua @@ -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 diff --git a/src/lua/pxl8/core.lua b/src/lua/pxl8/core.lua new file mode 100644 index 0000000..a00f2c2 --- /dev/null +++ b/src/lua/pxl8/core.lua @@ -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 diff --git a/src/lua/pxl8/gfx3d.lua b/src/lua/pxl8/gfx3d.lua new file mode 100644 index 0000000..9e63a82 --- /dev/null +++ b/src/lua/pxl8/gfx3d.lua @@ -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 diff --git a/src/lua/pxl8/graphics.lua b/src/lua/pxl8/graphics.lua new file mode 100644 index 0000000..d7570e3 --- /dev/null +++ b/src/lua/pxl8/graphics.lua @@ -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 diff --git a/src/lua/pxl8/input.lua b/src/lua/pxl8/input.lua new file mode 100644 index 0000000..a9c9637 --- /dev/null +++ b/src/lua/pxl8/input.lua @@ -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 diff --git a/src/lua/pxl8/math.lua b/src/lua/pxl8/math.lua new file mode 100644 index 0000000..3dcbe15 --- /dev/null +++ b/src/lua/pxl8/math.lua @@ -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 diff --git a/src/lua/pxl8/particles.lua b/src/lua/pxl8/particles.lua new file mode 100644 index 0000000..6d0e5fb --- /dev/null +++ b/src/lua/pxl8/particles.lua @@ -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 diff --git a/src/lua/pxl8/tilemap.lua b/src/lua/pxl8/tilemap.lua new file mode 100644 index 0000000..5df37b9 --- /dev/null +++ b/src/lua/pxl8/tilemap.lua @@ -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 diff --git a/src/lua/pxl8/transition.lua b/src/lua/pxl8/transition.lua new file mode 100644 index 0000000..4db8399 --- /dev/null +++ b/src/lua/pxl8/transition.lua @@ -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 diff --git a/src/lua/pxl8/ui.lua b/src/lua/pxl8/ui.lua new file mode 100644 index 0000000..63ce2a3 --- /dev/null +++ b/src/lua/pxl8/ui.lua @@ -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 diff --git a/src/lua/pxl8/vfx.lua b/src/lua/pxl8/vfx.lua new file mode 100644 index 0000000..a4372b4 --- /dev/null +++ b/src/lua/pxl8/vfx.lua @@ -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 diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua new file mode 100644 index 0000000..c0b7296 --- /dev/null +++ b/src/lua/pxl8/world.lua @@ -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 diff --git a/src/pxl8_anim.c b/src/pxl8_anim.c new file mode 100644 index 0000000..e6d0468 --- /dev/null +++ b/src/pxl8_anim.c @@ -0,0 +1,457 @@ +#include "pxl8_anim.h" + +#include +#include + +#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; + } +} diff --git a/src/pxl8_anim.h b/src/pxl8_anim.h new file mode 100644 index 0000000..3cac3bb --- /dev/null +++ b/src/pxl8_anim.h @@ -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 diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 2b7b6b0..7264c26 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -141,7 +141,7 @@ pxl8_gfx* pxl8_gfx_create( i32 window_width, i32 window_height ) { - pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(*gfx)); + pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx)); if (!gfx) { pxl8_error("Failed to allocate graphics context"); return NULL; diff --git a/src/pxl8_procgen.c b/src/pxl8_procgen.c index 137a246..1c6ce2a 100644 --- a/src/pxl8_procgen.c +++ b/src/pxl8_procgen.c @@ -28,26 +28,12 @@ static f32 prng_float(void) { return (f32)prng_next() / (f32)0xFFFFFFFF; } -static cave_grid* cave_grid_create(i32 width, i32 height) { - cave_grid* grid = malloc(sizeof(cave_grid)); - if (!grid) return NULL; - +static bool cave_grid_init(cave_grid* grid, i32 width, i32 height) { grid->width = width; grid->height = height; grid->cells = calloc(width * height, sizeof(u8)); - if (!grid->cells) { - free(grid); - return NULL; - } - - return grid; -} - -static void cave_grid_destroy(cave_grid* grid) { - if (!grid) return; - free(grid->cells); - free(grid); + return grid->cells != NULL; } 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) { - cave_grid* temp = cave_grid_create(grid->width, grid->height); - if (!temp) return; + cave_grid temp; + if (!cave_grid_init(&temp, grid->width, grid->height)) return; for (i32 y = 0; y < grid->height; y++) { for (i32 x = 0; x < grid->width; x++) { i32 neighbors = cave_grid_count_neighbors(grid, x, y); 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); - cave_grid_destroy(temp); + memcpy(grid->cells, temp.cells, grid->width * grid->height); + free(temp.cells); } 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) { prng_seed(params->seed); - cave_grid* grid = cave_grid_create(params->width, params->height); - if (!grid) { + cave_grid grid; + if (!cave_grid_init(&grid, params->width, params->height)) { 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++) { - cave_grid_smooth(grid); + cave_grid_smooth(&grid); } - pxl8_result result = cave_to_bsp(bsp, grid); - cave_grid_destroy(grid); + pxl8_result result = cave_to_bsp(bsp, &grid); + free(grid.cells); return result; } diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 87c1bf4..f03a8fd 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -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_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\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, w; } pxl8_vec4;\n" "typedef struct { float m[16]; } pxl8_mat4;\n" diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index 1735b82..6ecda08 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -25,7 +25,7 @@ static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 win_w, i32 win_h) { (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) { pxl8_error("Failed to allocate SDL3 context"); return NULL; diff --git a/src/pxl8_transition.c b/src/pxl8_transition.c new file mode 100644 index 0000000..64c8e81 --- /dev/null +++ b/src/pxl8_transition.c @@ -0,0 +1,248 @@ +#include "pxl8_transition.h" + +#include +#include + +#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); + } + } +} diff --git a/src/pxl8_transition.h b/src/pxl8_transition.h new file mode 100644 index 0000000..4ca3e4c --- /dev/null +++ b/src/pxl8_transition.h @@ -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 From b27abeffb599387d47ebbbdd54daf2ac03cfa410 Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 15 Nov 2025 08:30:51 -0600 Subject: [PATCH 4/6] add jump to demo 9 --- demo/main.fnl | 30 ++++++++--------- demo/mod/cube3d.fnl | 76 +++++++++++++++++++++---------------------- demo/mod/worldgen.fnl | 56 +++++++++++++++++++++---------- 3 files changed, 92 insertions(+), 70 deletions(-) diff --git a/demo/main.fnl b/demo/main.fnl index 6228e39..b84240c 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -5,10 +5,10 @@ (var time 0) (var active-demo :logo) (var particles nil) -(var fire-init false) -(var rain-init false) -(var snow-init false) -(var use-famicube-palette false) +(var fire-init? false) +(var rain-init? false) +(var snow-init? false) +(var use-famicube-palette? false) (var logo-x 256) (var logo-y 148) @@ -32,18 +32,18 @@ (when (pxl8.key_pressed "4") (set active-demo :raster)) (when (pxl8.key_pressed "5") (set active-demo :fire) - (set fire-init false)) + (set fire-init? false)) (when (pxl8.key_pressed "6") (set active-demo :rain) - (set rain-init false)) + (set rain-init? false)) (when (pxl8.key_pressed "7") (set active-demo :snow) - (set snow-init false)) + (set snow-init? false)) (when (pxl8.key_pressed "8") (set active-demo :cube3d)) (when (pxl8.key_pressed "9") (set active-demo :worldgen)) (when (pxl8.key_pressed "=") - (set use-famicube-palette (not use-famicube-palette)) - (local palette-path (if use-famicube-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) + (set use-famicube-palette? (not use-famicube-palette?)) + (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (pxl8.load_palette palette-path)) (case active-demo @@ -81,28 +81,28 @@ :fire (do (pxl8.clr 0) (when particles - (when (not fire-init) + (when (not fire-init?) (pxl8.particles_clear particles) (pxl8.vfx_fire particles 160 140 100 12) - (set fire-init true)) + (set fire-init? true)) (pxl8.particles_render particles))) :rain (do (pxl8.clr 0) (when particles - (when (not rain-init) + (when (not rain-init?) (pxl8.particles_clear particles) (pxl8.vfx_rain particles 320 10.0) - (set rain-init true)) + (set rain-init? true)) (pxl8.particles_render particles))) :snow (do (pxl8.clr 0) (when particles - (when (not snow-init) + (when (not snow-init?) (pxl8.particles_clear particles) (pxl8.vfx_snow particles 320 5.0) - (set snow-init true)) + (set snow-init? true)) (pxl8.particles_render particles))) :cube3d (cube3d.frame) diff --git a/demo/mod/cube3d.fnl b/demo/mod/cube3d.fnl index 4807d1e..97f76e3 100644 --- a/demo/mod/cube3d.fnl +++ b/demo/mod/cube3d.fnl @@ -4,20 +4,20 @@ (var angle-x 0) (var angle-y 0) (var angle-z 0) -(var auto-rotate true) -(var orthographic true) -(var wireframe true) +(var auto-rotate? true) +(var orthographic? true) +(var wireframe? true) (var time 0) (var zoom 5.0) (var texture-id nil) -(var use-texture false) -(var affine false) +(var use-texture? false) +(var affine? false) (var cam-x 0) (var cam-y 2) (var cam-z 12) (var cam-yaw 0) (var cam-pitch -0.2) -(var show-debug-ui false) +(var show-debug-ui? false) (var fps 0) (var fps-accumulator 0) (var fps-frame-count 0) @@ -26,19 +26,19 @@ (set angle-x 0) (set angle-y 0) (set angle-z 0) - (set auto-rotate true) - (set orthographic true) - (set wireframe true) + (set auto-rotate? true) + (set orthographic? true) + (set wireframe? true) (set time 0) (set zoom 5.0) - (set use-texture false) - (set affine false) + (set use-texture? false) + (set affine? false) (set cam-x 0) (set cam-y 2) (set cam-z 12) (set cam-yaw 0) (set cam-pitch -0.2) - (set show-debug-ui false) + (set show-debug-ui? false) (set fps 0) (set fps-accumulator 0) (set fps-frame-count 0) @@ -87,7 +87,7 @@ (let [wheel-y (pxl8.mouse_wheel_y)] (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))) (let [zoom-speed 0.5 forward-x (* (math.sin cam-yaw) wheel-y zoom-speed) @@ -103,7 +103,7 @@ right-x (* (math.cos cam-yaw) move-speed dt) right-z (* (- (math.sin cam-yaw)) move-speed dt)] - (if orthographic + (if orthographic? (do (when (pxl8.key_down "w") (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)))) (when (pxl8.key_pressed " ") - (set wireframe (not wireframe))) + (set wireframe? (not wireframe?))) (when (pxl8.key_pressed "f") - (set affine (not affine))) + (set affine? (not affine?))) (when (pxl8.key_pressed "p") - (set orthographic (not orthographic))) + (set orthographic? (not orthographic?))) (when (pxl8.key_pressed "r") - (set auto-rotate (not auto-rotate))) + (set auto-rotate? (not auto-rotate?))) (when (pxl8.key_pressed "t") - (set use-texture (not use-texture))) + (set use-texture? (not use-texture?))) (when (pxl8.key_pressed "F8") - (set show-debug-ui (not show-debug-ui)) - (pxl8.ui_window_set_open "Debug Menu (F8)" show-debug-ui)) + (set show-debug-ui? (not 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-y (+ angle-y (* dt 0.5))) (set angle-z (+ angle-z (* dt 0.3))))) @@ -178,7 +178,7 @@ (pxl8.set_model model)) (let [vertices (make-cube-vertices)] - (if (and use-texture texture-id) + (if (and use-texture? texture-id) (let [faces (make-cube-faces-with-uvs)] (each [_i face-data (ipairs faces)] (let [tri-indices face-data.tri @@ -206,11 +206,11 @@ (pxl8.clr 0) (pxl8.clear_zbuffer) - (pxl8.set_affine_textures affine) + (pxl8.set_affine_textures affine?) (pxl8.set_backface_culling true) - (pxl8.set_wireframe wireframe) + (pxl8.set_wireframe wireframe?) - (if orthographic + (if orthographic? (let [size zoom aspect (/ (pxl8.get_width) (pxl8.get_height)) w (* size aspect) @@ -233,19 +233,19 @@ (draw-cube [3 1 -7] 0.9 1.2) (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 - :wireframe wireframe - :auto-rotate auto-rotate - :orthographic orthographic - :use-texture use-texture - :affine affine})] - (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.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.use-texture nil) (set use-texture new-state.use-texture)) - (when (not= new-state.affine nil) (set affine new-state.affine)))) + :wireframe wireframe? + :auto-rotate auto-rotate? + :orthographic orthographic? + :use-texture use-texture? + :affine affine?})] + (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.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.use-texture nil) (set use-texture? new-state.use-texture)) + (when (not= new-state.affine nil) (set affine? new-state.affine)))) {:init init :update update diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index 1cbef1a..8e91b37 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -2,11 +2,14 @@ (var world nil) (var cam-x 1000) -(local cam-y 64) +(var cam-y 64) (var cam-z 1000) (var cam-yaw 0) (var cam-pitch 0) (var bob-time 0) +(var velocity-y 0) +(var grounded? true) +(var land-squash 0) (local move-speed 200) (local turn-speed 2.0) @@ -15,6 +18,11 @@ (local max-pitch 1.5) (local cell-size 64) (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 [] (set world (pxl8.world_new)) @@ -72,31 +80,28 @@ (var move-right 0) (when (pxl8.key_down "w") - (set move-forward (+ move-forward 1)) - (set moving true)) + (set move-forward (+ move-forward 1))) (when (pxl8.key_down "s") - (set move-forward (- move-forward 1)) - (set moving true)) + (set move-forward (- move-forward 1))) (when (pxl8.key_down "q") - (set move-right (- move-right 1)) - (set moving true)) + (set move-right (- move-right 1))) (when (pxl8.key_down "e") - (set move-right (+ move-right 1)) - (set moving true)) + (set move-right (+ move-right 1))) + + (set moving (or (not= move-forward 0) (not= move-right 0))) (var new-x cam-x) (var new-z cam-z) (when moving - (let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))] - (when (> len 0) - (let [norm-forward (/ move-forward len) - norm-right (/ move-right len)] - (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))))))))) + (let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right))) + norm-forward (/ move-forward len) + norm-right (/ move-right len)] + (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))))))) (when (and (>= new-x 0) (<= new-x grid-max) (>= new-z 0) (<= new-z grid-max)) @@ -115,7 +120,24 @@ (when (pxl8.key_down "down") (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))) (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))))) @@ -125,7 +147,7 @@ (when (pxl8.world_is_loaded world) (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-z (- (math.cos cam-yaw)) target-x (+ cam-x forward-x) From a15d0db902740f42fc8f95e4c52a53321bc21cde Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 15 Nov 2025 11:31:33 -0600 Subject: [PATCH 5/6] add LICENSE file --- LICENSE | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d0a1fa1 --- /dev/null +++ b/LICENSE @@ -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. From 27b6459b9a69f4981e0fd26ff1a1ff9abd20491f Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 15 Nov 2025 11:40:27 -0600 Subject: [PATCH 6/6] add anim and transitions and re-org lua api scripts --- README.md | 8 + demo/main.fnl | 49 ++- pxl8.sh | 2 + src/lua/pxl8.lua | 696 +++++++++--------------------------- src/lua/pxl8/anim.lua | 110 ++++++ src/lua/pxl8/core.lua | 45 +++ src/lua/pxl8/gfx3d.lua | 56 +++ src/lua/pxl8/graphics.lua | 85 +++++ src/lua/pxl8/input.lua | 35 ++ src/lua/pxl8/math.lua | 53 +++ src/lua/pxl8/particles.lua | 31 ++ src/lua/pxl8/tilemap.lua | 82 +++++ src/lua/pxl8/transition.lua | 68 ++++ src/lua/pxl8/ui.lua | 52 +++ src/lua/pxl8/vfx.lua | 65 ++++ src/lua/pxl8/world.lua | 99 +++++ src/pxl8_anim.c | 457 +++++++++++++++++++++++ src/pxl8_anim.h | 56 +++ src/pxl8_gfx.c | 2 +- src/pxl8_procgen.c | 40 +-- src/pxl8_script.c | 38 ++ src/pxl8_sdl3.c | 2 +- src/pxl8_transition.c | 248 +++++++++++++ src/pxl8_transition.h | 51 +++ 24 files changed, 1857 insertions(+), 573 deletions(-) create mode 100644 src/lua/pxl8/anim.lua create mode 100644 src/lua/pxl8/core.lua create mode 100644 src/lua/pxl8/gfx3d.lua create mode 100644 src/lua/pxl8/graphics.lua create mode 100644 src/lua/pxl8/input.lua create mode 100644 src/lua/pxl8/math.lua create mode 100644 src/lua/pxl8/particles.lua create mode 100644 src/lua/pxl8/tilemap.lua create mode 100644 src/lua/pxl8/transition.lua create mode 100644 src/lua/pxl8/ui.lua create mode 100644 src/lua/pxl8/vfx.lua create mode 100644 src/lua/pxl8/world.lua create mode 100644 src/pxl8_anim.c create mode 100644 src/pxl8_anim.h create mode 100644 src/pxl8_transition.c create mode 100644 src/pxl8_transition.h diff --git a/README.md b/README.md index d68f117..a4d7173 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,11 @@ @@@@@@@@@@@@@@@@@@@@@@@~ ,@@@,,0@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@,,@@@@@@@@@@@@@@@@@@@@@@@@@ + +## 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. diff --git a/demo/main.fnl b/demo/main.fnl index b84240c..32e8115 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -15,6 +15,14 @@ (var logo-dx 100) (var logo-dy 80) (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 [] (cube3d.init) @@ -26,21 +34,27 @@ (global update (fn [dt] (set time (+ time dt)) - (when (pxl8.key_pressed "1") (set active-demo :logo)) - (when (pxl8.key_pressed "2") (set active-demo :plasma)) - (when (pxl8.key_pressed "3") (set active-demo :tunnel)) - (when (pxl8.key_pressed "4") (set active-demo :raster)) - (when (pxl8.key_pressed "5") - (set active-demo :fire) - (set fire-init? false)) - (when (pxl8.key_pressed "6") - (set active-demo :rain) - (set rain-init? false)) - (when (pxl8.key_pressed "7") - (set active-demo :snow) - (set snow-init? false)) - (when (pxl8.key_pressed "8") (set active-demo :cube3d)) - (when (pxl8.key_pressed "9") (set active-demo :worldgen)) + (when transition + (pxl8.transition_update transition dt) + (when (pxl8.transition_is_complete transition) + (when transition-pending + (set active-demo transition-pending) + (set transition-pending nil) + (when (= active-demo :fire) (set fire-init? false)) + (when (= active-demo :rain) (set rain-init? false)) + (when (= active-demo :snow) (set snow-init? false))) + (pxl8.transition_destroy transition) + (set transition nil))) + + (when (pxl8.key_pressed "1") (switch-demo :logo)) + (when (pxl8.key_pressed "2") (switch-demo :plasma)) + (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 "=") (set use-famicube-palette? (not use-famicube-palette?)) (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) @@ -109,4 +123,7 @@ :worldgen (worldgen.frame) - _ (pxl8.clr 0)))) + _ (pxl8.clr 0)) + + (when transition + (pxl8.transition_render transition)))) diff --git a/pxl8.sh b/pxl8.sh index 3e89c0d..3096771 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -340,6 +340,7 @@ case "$COMMAND" in PXL8_SOURCE_FILES=" src/pxl8.c + src/pxl8_anim.c src/pxl8_ase.c src/pxl8_atlas.c src/pxl8_blit.c @@ -354,6 +355,7 @@ case "$COMMAND" in src/pxl8_sdl3.c src/pxl8_tilemap.c src/pxl8_tilesheet.c + src/pxl8_transition.c src/pxl8_ui.c src/pxl8_vfx.c src/pxl8_world.c diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 7b6d540..24c202e 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -1,534 +1,174 @@ local ffi = require("ffi") -local C = ffi.C -local game = _pxl8_game -local gfx = _pxl8_gfx -local input = _pxl8_input -local ui = _pxl8_ui + +local core = require("pxl8.core") +local graphics = require("pxl8.graphics") +local input = require("pxl8.input") +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 = {} -function pxl8.clr(color) - C.pxl8_clr(gfx, color or 0) -end - -function pxl8.pixel(x, y, color) - if color then - C.pxl8_pixel(gfx, x, y, color) - else - return C.pxl8_get_pixel(gfx, x, y) - end -end - -function pxl8.line(x0, y0, x1, y1, color) - C.pxl8_line(gfx, x0, y0, x1, y1, color) -end - -function pxl8.rect(x, y, w, h, color) - C.pxl8_rect(gfx, x, y, w, h, color) -end - -function pxl8.rect_fill(x, y, w, h, color) - C.pxl8_rect_fill(gfx, x, y, w, h, color) -end - -function pxl8.circle(x, y, r, color) - C.pxl8_circle(gfx, x, y, r, color) -end - -function pxl8.circle_fill(x, y, r, color) - C.pxl8_circle_fill(gfx, x, y, r, color) -end - -function pxl8.text(str, x, y, color) - C.pxl8_text(gfx, str, x or 0, y or 0, color or 15) -end - -function pxl8.sprite(id, x, y, w, h, flip_x, flip_y) - 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) -end - -function pxl8.get_fps() - return C.pxl8_game_get_fps(game) -end - -function pxl8.get_width() - return C.pxl8_gfx_get_width(gfx) -end - -function pxl8.get_height() - return C.pxl8_gfx_get_height(gfx) -end - -function pxl8.load_palette(filepath) - return C.pxl8_gfx_load_palette(gfx, filepath) -end - -function pxl8.load_sprite(filepath) - local sprite_id = ffi.new("unsigned int[1]") - local result = C.pxl8_gfx_load_sprite(gfx, filepath, sprite_id) - if result == 0 then - return sprite_id[0] - else - return nil, result - end -end - -function pxl8.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(gfx, pixel_data, width, height) - if result < 0 then - return nil - end - return result -end - -function pxl8.upload_atlas() - C.pxl8_gfx_upload_atlas(gfx) -end - -function pxl8.info(msg) - C.pxl8_lua_info(msg) -end - -function pxl8.warn(msg) - C.pxl8_lua_warn(msg) -end - -function pxl8.error(msg) - C.pxl8_lua_error(msg) -end - -function pxl8.debug(msg) - C.pxl8_lua_debug(msg) -end - -function pxl8.trace(msg) - C.pxl8_lua_trace(msg) -end - -function pxl8.key_down(key) - return C.pxl8_key_down(input, key) -end - -function pxl8.key_pressed(key) - return C.pxl8_key_pressed(input, key) -end - -function pxl8.key_released(key) - return C.pxl8_key_released(input, key) -end - -function pxl8.mouse_wheel_x() - return C.pxl8_mouse_wheel_x(input) -end - -function pxl8.mouse_wheel_y() - return C.pxl8_mouse_wheel_y(input) -end - -function pxl8.mouse_x() - return C.pxl8_mouse_x(input) -end - -function pxl8.mouse_y() - return C.pxl8_mouse_y(input) -end - -function pxl8.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(gfx, c_bars, #bars, time) -end - -function pxl8.vfx_plasma(time, scale1, scale2, palette_offset) - C.pxl8_vfx_plasma(gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0) -end - -function pxl8.vfx_rotozoom(angle, zoom, cx, cy) - C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or pxl8.get_width()/2, cy or pxl8.get_height()/2) -end - -function pxl8.vfx_tunnel(time, speed, twist) - 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 +pxl8.get_fps = core.get_fps +pxl8.get_width = core.get_width +pxl8.get_height = core.get_height +pxl8.info = core.info +pxl8.warn = core.warn +pxl8.error = core.error +pxl8.debug = core.debug +pxl8.trace = core.trace + +pxl8.clr = graphics.clr +pxl8.pixel = graphics.pixel +pxl8.line = graphics.line +pxl8.rect = graphics.rect +pxl8.rect_fill = graphics.rect_fill +pxl8.circle = graphics.circle +pxl8.circle_fill = graphics.circle_fill +pxl8.text = graphics.text +pxl8.sprite = graphics.sprite +pxl8.load_palette = graphics.load_palette +pxl8.load_sprite = graphics.load_sprite +pxl8.create_texture = graphics.create_texture +pxl8.upload_atlas = graphics.upload_atlas +pxl8.gfx_color_ramp = graphics.color_ramp +pxl8.gfx_fade_palette = graphics.fade_palette + +pxl8.key_down = input.key_down +pxl8.key_pressed = input.key_pressed +pxl8.key_released = input.key_released +pxl8.mouse_wheel_x = input.mouse_wheel_x +pxl8.mouse_wheel_y = input.mouse_wheel_y +pxl8.mouse_x = input.mouse_x +pxl8.mouse_y = input.mouse_y + +pxl8.vfx_raster_bars = vfx.raster_bars +pxl8.vfx_plasma = vfx.plasma +pxl8.vfx_rotozoom = vfx.rotozoom +pxl8.vfx_tunnel = vfx.tunnel +pxl8.vfx_explosion = vfx.explosion +pxl8.vfx_fire = vfx.fire +pxl8.vfx_rain = vfx.rain +pxl8.vfx_smoke = vfx.smoke +pxl8.vfx_snow = vfx.snow +pxl8.vfx_sparks = vfx.sparks +pxl8.vfx_starfield = vfx.starfield + +pxl8.particles_new = particles.new +pxl8.particles_destroy = particles.destroy +pxl8.particles_clear = particles.clear +pxl8.particles_emit = particles.emit +pxl8.particles_update = particles.update +pxl8.particles_render = particles.render + +pxl8.tilesheet_new = tilemap.tilesheet_new +pxl8.tilesheet_destroy = tilemap.tilesheet_destroy +pxl8.tilesheet_load = tilemap.tilesheet_load +pxl8.tilemap_new = tilemap.new +pxl8.tilemap_destroy = tilemap.destroy +pxl8.tilemap_set_tilesheet = tilemap.set_tilesheet +pxl8.tilemap_set_tile = tilemap.set_tile +pxl8.tilemap_get_tile_id = tilemap.get_tile_id +pxl8.tilemap_set_camera = tilemap.set_camera +pxl8.tilemap_render = tilemap.render +pxl8.tilemap_render_layer = tilemap.render_layer +pxl8.tilemap_is_solid = tilemap.is_solid +pxl8.tilemap_check_collision = tilemap.check_collision +pxl8.tilemap_get_tile_data = tilemap.get_tile_data +pxl8.tilemap_load_ase = tilemap.load_ase +pxl8.tilemap_set_tile_data = tilemap.set_tile_data +pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X +pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y +pxl8.TILE_SOLID = tilemap.TILE_SOLID +pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER + +pxl8.clear_zbuffer = gfx3d.clear_zbuffer +pxl8.set_model = gfx3d.set_model +pxl8.set_view = gfx3d.set_view +pxl8.set_projection = gfx3d.set_projection +pxl8.set_wireframe = gfx3d.set_wireframe +pxl8.set_affine_textures = gfx3d.set_affine_textures +pxl8.set_backface_culling = gfx3d.set_backface_culling +pxl8.draw_triangle_3d = gfx3d.draw_triangle +pxl8.draw_triangle_3d_textured = gfx3d.draw_triangle_textured +pxl8.draw_line_3d = gfx3d.draw_line + +pxl8.mat4_identity = math3d.mat4_identity +pxl8.mat4_multiply = math3d.mat4_multiply +pxl8.mat4_translate = math3d.mat4_translate +pxl8.mat4_rotate_x = math3d.mat4_rotate_x +pxl8.mat4_rotate_y = math3d.mat4_rotate_y +pxl8.mat4_rotate_z = math3d.mat4_rotate_z +pxl8.mat4_scale = math3d.mat4_scale +pxl8.mat4_ortho = math3d.mat4_ortho +pxl8.mat4_perspective = math3d.mat4_perspective +pxl8.mat4_lookat = math3d.mat4_lookat +pxl8.bounds = math3d.bounds + +pxl8.ui_button = ui.button +pxl8.ui_checkbox = ui.checkbox +pxl8.ui_has_mouse_focus = ui.has_mouse_focus +pxl8.ui_indent = ui.indent +pxl8.ui_label = ui.label +pxl8.ui_layout_row = ui.layout_row +pxl8.ui_window_begin = ui.window_begin +pxl8.ui_window_end = ui.window_end +pxl8.ui_window_set_open = ui.window_set_open + +pxl8.world_new = world.new +pxl8.world_destroy = world.destroy +pxl8.world_load = world.load +pxl8.world_render = world.render +pxl8.world_unload = world.unload +pxl8.world_is_loaded = world.is_loaded +pxl8.world_generate = world.generate +pxl8.world_apply_textures = world.apply_textures +pxl8.procgen_tex = world.procgen_tex +pxl8.PROCGEN_CAVE = world.PROCGEN_CAVE +pxl8.PROCGEN_DUNGEON = world.PROCGEN_DUNGEON +pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN + +pxl8.transition_create = transition.create +pxl8.transition_destroy = transition.destroy +pxl8.transition_get_progress = transition.get_progress +pxl8.transition_is_active = transition.is_active +pxl8.transition_is_complete = transition.is_complete +pxl8.transition_render = transition.render +pxl8.transition_reset = transition.reset +pxl8.transition_set_color = transition.set_color +pxl8.transition_set_reverse = transition.set_reverse +pxl8.transition_start = transition.start +pxl8.transition_stop = transition.stop +pxl8.transition_update = transition.update + +pxl8.anim_create = anim.create +pxl8.anim_create_from_ase = anim.create_from_ase +pxl8.anim_destroy = anim.destroy +pxl8.anim_add_state = anim.add_state +pxl8.anim_get_current_frame = anim.get_current_frame +pxl8.anim_get_current_frame_id = anim.get_current_frame_id +pxl8.anim_get_state = anim.get_state +pxl8.anim_has_state_machine = anim.has_state_machine +pxl8.anim_is_complete = anim.is_complete +pxl8.anim_is_playing = anim.is_playing +pxl8.anim_pause = anim.pause +pxl8.anim_play = anim.play +pxl8.anim_render_sprite = anim.render_sprite +pxl8.anim_reset = anim.reset +pxl8.anim_set_frame = anim.set_frame +pxl8.anim_set_loop = anim.set_loop +pxl8.anim_set_reverse = anim.set_reverse +pxl8.anim_set_speed = anim.set_speed +pxl8.anim_set_state = anim.set_state +pxl8.anim_stop = anim.stop +pxl8.anim_update = anim.update return pxl8 diff --git a/src/lua/pxl8/anim.lua b/src/lua/pxl8/anim.lua new file mode 100644 index 0000000..d15ba5c --- /dev/null +++ b/src/lua/pxl8/anim.lua @@ -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 diff --git a/src/lua/pxl8/core.lua b/src/lua/pxl8/core.lua new file mode 100644 index 0000000..a00f2c2 --- /dev/null +++ b/src/lua/pxl8/core.lua @@ -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 diff --git a/src/lua/pxl8/gfx3d.lua b/src/lua/pxl8/gfx3d.lua new file mode 100644 index 0000000..9e63a82 --- /dev/null +++ b/src/lua/pxl8/gfx3d.lua @@ -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 diff --git a/src/lua/pxl8/graphics.lua b/src/lua/pxl8/graphics.lua new file mode 100644 index 0000000..d7570e3 --- /dev/null +++ b/src/lua/pxl8/graphics.lua @@ -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 diff --git a/src/lua/pxl8/input.lua b/src/lua/pxl8/input.lua new file mode 100644 index 0000000..a9c9637 --- /dev/null +++ b/src/lua/pxl8/input.lua @@ -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 diff --git a/src/lua/pxl8/math.lua b/src/lua/pxl8/math.lua new file mode 100644 index 0000000..3dcbe15 --- /dev/null +++ b/src/lua/pxl8/math.lua @@ -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 diff --git a/src/lua/pxl8/particles.lua b/src/lua/pxl8/particles.lua new file mode 100644 index 0000000..6d0e5fb --- /dev/null +++ b/src/lua/pxl8/particles.lua @@ -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 diff --git a/src/lua/pxl8/tilemap.lua b/src/lua/pxl8/tilemap.lua new file mode 100644 index 0000000..5df37b9 --- /dev/null +++ b/src/lua/pxl8/tilemap.lua @@ -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 diff --git a/src/lua/pxl8/transition.lua b/src/lua/pxl8/transition.lua new file mode 100644 index 0000000..4db8399 --- /dev/null +++ b/src/lua/pxl8/transition.lua @@ -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 diff --git a/src/lua/pxl8/ui.lua b/src/lua/pxl8/ui.lua new file mode 100644 index 0000000..63ce2a3 --- /dev/null +++ b/src/lua/pxl8/ui.lua @@ -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 diff --git a/src/lua/pxl8/vfx.lua b/src/lua/pxl8/vfx.lua new file mode 100644 index 0000000..a4372b4 --- /dev/null +++ b/src/lua/pxl8/vfx.lua @@ -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 diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua new file mode 100644 index 0000000..c0b7296 --- /dev/null +++ b/src/lua/pxl8/world.lua @@ -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 diff --git a/src/pxl8_anim.c b/src/pxl8_anim.c new file mode 100644 index 0000000..e6d0468 --- /dev/null +++ b/src/pxl8_anim.c @@ -0,0 +1,457 @@ +#include "pxl8_anim.h" + +#include +#include + +#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; + } +} diff --git a/src/pxl8_anim.h b/src/pxl8_anim.h new file mode 100644 index 0000000..3cac3bb --- /dev/null +++ b/src/pxl8_anim.h @@ -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 diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 2b7b6b0..7264c26 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -141,7 +141,7 @@ pxl8_gfx* pxl8_gfx_create( i32 window_width, i32 window_height ) { - pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(*gfx)); + pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx)); if (!gfx) { pxl8_error("Failed to allocate graphics context"); return NULL; diff --git a/src/pxl8_procgen.c b/src/pxl8_procgen.c index 137a246..1c6ce2a 100644 --- a/src/pxl8_procgen.c +++ b/src/pxl8_procgen.c @@ -28,26 +28,12 @@ static f32 prng_float(void) { return (f32)prng_next() / (f32)0xFFFFFFFF; } -static cave_grid* cave_grid_create(i32 width, i32 height) { - cave_grid* grid = malloc(sizeof(cave_grid)); - if (!grid) return NULL; - +static bool cave_grid_init(cave_grid* grid, i32 width, i32 height) { grid->width = width; grid->height = height; grid->cells = calloc(width * height, sizeof(u8)); - if (!grid->cells) { - free(grid); - return NULL; - } - - return grid; -} - -static void cave_grid_destroy(cave_grid* grid) { - if (!grid) return; - free(grid->cells); - free(grid); + return grid->cells != NULL; } 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) { - cave_grid* temp = cave_grid_create(grid->width, grid->height); - if (!temp) return; + cave_grid temp; + if (!cave_grid_init(&temp, grid->width, grid->height)) return; for (i32 y = 0; y < grid->height; y++) { for (i32 x = 0; x < grid->width; x++) { i32 neighbors = cave_grid_count_neighbors(grid, x, y); 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); - cave_grid_destroy(temp); + memcpy(grid->cells, temp.cells, grid->width * grid->height); + free(temp.cells); } 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) { prng_seed(params->seed); - cave_grid* grid = cave_grid_create(params->width, params->height); - if (!grid) { + cave_grid grid; + if (!cave_grid_init(&grid, params->width, params->height)) { 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++) { - cave_grid_smooth(grid); + cave_grid_smooth(&grid); } - pxl8_result result = cave_to_bsp(bsp, grid); - cave_grid_destroy(grid); + pxl8_result result = cave_to_bsp(bsp, &grid); + free(grid.cells); return result; } diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 87c1bf4..f03a8fd 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -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_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\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, w; } pxl8_vec4;\n" "typedef struct { float m[16]; } pxl8_mat4;\n" diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index 1735b82..6ecda08 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -25,7 +25,7 @@ static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 win_w, i32 win_h) { (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) { pxl8_error("Failed to allocate SDL3 context"); return NULL; diff --git a/src/pxl8_transition.c b/src/pxl8_transition.c new file mode 100644 index 0000000..64c8e81 --- /dev/null +++ b/src/pxl8_transition.c @@ -0,0 +1,248 @@ +#include "pxl8_transition.h" + +#include +#include + +#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); + } + } +} diff --git a/src/pxl8_transition.h b/src/pxl8_transition.h new file mode 100644 index 0000000..4ca3e4c --- /dev/null +++ b/src/pxl8_transition.h @@ -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