Log In  
Follow
ValerADHD
Follow

Cart #projectiondemo-1 | 2020-12-01 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
42

How does 3D projection work? this gif goes over the basics in 2 and a half minutes!
view the full gif split up here, with captions:
https://imgur.com/a/DjyHKag

P#84897 2020-12-01 12:43

Cart #easingcheatsheet-2 | 2020-11-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
42

load #easingcheatsheet

in the Pico-8 command line to load it straight into your Pico-8 application!

Easing functions are ways of modifying the rate of change for a linear interpolation function. You may have used these in animation programs (unity, blender, etc.) by manually editing animation curves. This editing isn't all that possible without a large engine backend, but you can easily create short math equations to imitate some of the commonly used curves. This is what I've done here, implementing a set of functions for these curves, and creating a little demo so you can see how each of them changes your interpolation!

Code

All of these functions are completely self sufficient (don't rely on anything outside the function), so you can easily pick and choose which ones you want! (476 tokens for all of them)
(also, none of the functions have any safety regulations, so passing values outside of 0-1 will result in unintended behavior. Be careful! you could also add t=mid(0,t,1) to the start of the functions to clamp the values)

Quadratic functions

function easeinquad(t)
    return t*t
end

function easeoutquad(t)
    t-=1
    return 1-t*t
end

function easeinoutquad(t)
    if(t<.5) then
        return t*t*2
    else
        t-=1
        return 1-t*t*2
    end
end

function easeoutinquad(t)
    if t<.5 then
        t-=.5
        return .5-t*t*2
    else
        t-=.5
        return .5+t*t*2
    end
end

Quartic functions

(these are very similar to quadratics, but flatter at the start and steeper towards the end)

function easeinquart(t)
    return t*t*t*t
end

function easeoutquart(t)
    t-=1
    return 1-t*t*t*t
end

function easeinoutquart(t)
    if t<.5 then
        return 8*t*t*t*t
    else
        t-=1
        return (1-8*t*t*t*t)
    end
end

function easeoutinquart(t)
    if t<.5 then
        t-=.5
        return .5-8*t*t*t*t
    else
        t-=.5
        return .5+8*t*t*t*t
    end
end

Overshooting functions

(these functions overshoot the range slightly and then return to it)

function easeinovershoot(t)
    return 2.7*t*t*t-1.7*t*t
end

function easeoutovershoot(t)
    t-=1
    return 1+2.7*t*t*t+1.7*t*t
end

function easeinoutovershoot(t)
    if(t<.5) then
        return (2.7*8*t*t*t-1.7*4*t*t)/2
    else
        t-=1
        return 1+(2.7*8*t*t*t+1.7*4*t*t)/2
    end
end

function easeoutinovershoot(t)
    if t<.5 then
        t-=.5
        return (2.7*8*t*t*t+1.7*4*t*t)/2+.5
    else
        t-=.5
        return (2.7*8*t*t*t-1.7*4*t*t)/2+.5
    end
end

Elastic functions

(these functions overshoot slightly and then oscillate near the edges of the range, like an elastic band)

function easeinelastic(t)
    if(t==0) return 0
    return 2^(10*t-10)*cos(2*t-2)
end

function easeoutelastic(t)
    if(t==1) return 1
    return 1-2^(-10*t)*cos(2*t)
end

function easeinoutelastic(t)
    if t<.5 then
        return 2^(10*2*t-10)*cos(2*2*t-2)/2
    else
        t-=.5
        return 1-2^(-10*2*t)*cos(2*2*t)/2
    end
end

function easeoutinelastic(t)
    if t<.5 then
        return .5-2^(-10*2*t)*cos(2*2*t)/2
    else
        t-=.5
        return 2^(10*2*t-10)*cos(2*2*t-2)/2+.5
    end
end

Bouncing functions

(these functions hit the edge values early, then bounce back a few times)

function easeinbounce(t)
    t=1-t
    local n1=7.5625
    local d1=2.75

    if (t<1/d1) then
        return 1-n1*t*t;
    elseif(t<2/d1) then
        t-=1.5/d1
        return 1-n1*t*t-.75;
    elseif(t<2.5/d1) then
        t-=2.25/d1
        return 1-n1*t*t-.9375;
    else
        t-=2.625/d1
        return 1-n1*t*t-.984375;
    end
end

function easeoutbounce(t)
    local n1=7.5625
    local d1=2.75

    if (t<1/d1) then
        return n1*t*t;
    elseif(t<2/d1) then
        t-=1.5/d1
        return n1*t*t+.75;
    elseif(t<2.5/d1) then
        t-=2.25/d1
        return n1*t*t+.9375;
    else
        t-=2.625/d1
        return n1*t*t+.984375;
    end
end

--(ease in+out function omitted, it doesn't really make sense for any uses IMO)

Other useful functions:

--(linear interpolation between a/b)
function lerp(a,b,t)
    return a+(b-a)*t
end

--(finds the t value that would
--return v in a lerp between a/b)
function invlerp(a,b,v)
    return (v-a)/(b-a)
end

Crediting:

If you use these in your own code, please just put a link to this BBS post in a comment near the function! I want to allow anyone reading your code to be able to find this and use it as well, for learning purposes.
All of the easing functions in this cart are based/translated from this site: https://easings.net/

Also, feel free to suggest any functions I'm missing! I omitted some of the functions that I thought were redundant/not commonly used, but correct me if I was wrong with that assumption!

P#84765 2020-11-27 21:13 ( Edited 2020-12-02 14:20)

Cart #subpix-0 | 2020-11-07 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
5

I recently saw a post on twitter about "subpixel rendering," and I wanted to try my hand at a few functions! A couple of hours later... I now have this. I can't promise it's perfect, nor do I have all of the shapes I want to try, but I'll release it now if anyone is interested.
Feel free to suggest any other shapes as well as finding bugs/fixes!

P#83942 2020-11-07 18:31

Cart #invertedcircle-1 | 2020-11-05 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1

I noticed that people relatively often ask for a way to draw an inverted circle and don't have an easy way to implement it, since it requires a bit of math that can be difficult to derive without experience. I decided to make a quick cart and standalone function to do that! This isn't amazingly efficient, and it only does circles (not ellipses), but it is a drop in function:
Update: new function utilizing trig, faster and now subpixel!

x=x or 64
    y=y or 64
    r=r or 32
    left=left or 0
    right=right or 128
    top=top or 0
    bottom=bottom or 128
    local p=r*6.28
    if(top<y-r)rectfill(left,top,right,y-r,c)
    if(bottom>y+r)rectfill(left,y+r,right,bottom,c)
    for d=-.001,.25,1/p do
        local ex,ey=r*cos(d),r*sin(d)
        if(x-ex>left) then
            if(y-ey>top) then
                rect(left,y-ey,x-ex,y-ey,c)
            end
            if(y+ey<bottom) then
                rect(left,y+ey,x-ex,y+ey,c)
            end
        end
        if(x+ex<right) then
            if(y-ey>top) then
                rect(x+ex,y-ey,right,y-ey,c)
            end
            if(y+ey<bottom) then
                rect(x+ex,y+ey,right,y+ey,c)
            end
        end
    end

Old versions:


Cart #invertedcircle-0 | 2020-11-05 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1

function invertedcircle(x,y,r,left,right,top,bottom,c)
    x=x or 64
    y=y or 64
    r=r or 32
    left=left or 0
    right=right or 128
    top=top or 0
    bottom=bottom or 128

    local rsqr=r*r
    for i=flr(top),bottom do
        local yr=i-y
        local w=max(0,(rsqr-yr*yr)^.5)
        if(x-w>left)rect(left,i,x-w,i,c)
        if(x+w<right)rect(right,i,x+w,i,c)
    end
end

P#83816 2020-11-05 20:16 ( Edited 2020-11-05 21:17)

Cart #spritesheetgiftest-0 | 2020-09-26 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
4

I was looking into methods for extending the spritesheet memory of a cartridge, but the only result I could find on google (after an admittedly short search) was a post that was slightly inefficient when it loaded the spritesheet into memory, making it only suitable for use in fullscreen graphics like a title screen. This was noted in the post, but I thought that I could make some improvements.

Limitations

This code is very small when inserted into the cart, other than the actual spritesheet data. The current data is stored in a completely uncompressed binary string, which in the worst case can balloon up to 16-20k characters.
(NOTE: This is only when storing a completely black spritesheet, which should ideally be never. If your spritesheet has large portions of completely black (color 0) space, try replacing it with some other color you're not using, or using palette swapping.) The average spritesheet should be able to be stored in 8-10k characters.
Currently my implementation only supports replacing the entire spritesheet at once, though this could be easily improved to allow copying 64x64, 32x32, 16x16, and 8x8 chunks individually.

Possible future improvements:

As mentioned earlier, this is severely limited by a lack of compression. It would be relatively easy to implement an RLE compression for the strings, then decompress them at runtime. Alternatively, it would also be easy implement PX9 compression for the spritesheet before it is loaded into a string, then decompress it at runtime.

Usage:

Export:

Cart #spritesheet_str_export-0 | 2020-09-26 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
4

This cart includes all of the functions to export a spritesheet from a cart. To use: Load cart in an offline instance of PICO-8, uncomment the line "reload(0,0,"mycart.p8"), and replace "mycart.p8" with the name of the cart you wish to export the spritesheet from. Alternately, draw a new spritesheet into this cartridge or use PICO-8's drag and drop feature to import an image. Run the cart, which will save the string to your clipboard.

Usage in your own carts:

Cart #spritesheet_str_import_1_1-0 | 2020-09-26 | Code ▽ | Embed ▽ | No License
4

^^Example^^: look at code for sample importing of a string.

Once you have your data saved into the clipboard, you can put it into your cartridge.
When you are going to paste this string into your own game, make sure to press CTRL-P to enable puny font. This method will not work properly without doing this.
Paste the clipboard data into a new string in your game.
Now, you're going to need to add these two methods to your game: (146 tokens)

function spritesheet_to_table()
    local tbl={}
    for y=0,127 do
        for x=0,15 do
            tbl[(y<<4)+x]=$((y<<6)+(x<<2))
        end
    end
    return tbl
end

function string_to_table(str)
    local tbl={}
    for i=0,8191,4 do
        local al,ah,eal,eah=
            (ord(str,i+1)),
            (ord(str,i+2)),
            (ord(str,i+3)),
            (ord(str,i+4))

        tbl[i\4]=
            al|(ah<<>8)|
            (eal<<>16)|(eah<<>24)

    end

    return tbl
end

function table_to_spritesheet(tbl)
    for y=0,127 do
        for x=0,15 do
            poke4((y<<6)+(x<<2),
                tbl[(y<<4)+x])
        end
    end
end

The first method should be called in your _init function like this:
('original_spritesheet_table' is the data for your base cartridge spritesheet, so that it can be loaded efficiently later.
'your_spritesheet_table' will be initialized here, replace the name with what you want to call it later. 'your_str' is what you named the string that you pasted into.)

original_spritesheet_table=spritesheet_to_table()
your_spritesheet_table=string_to_table(your_str)

This will load the string into Lua memory, which should come at almost no cost to your final product.

Now, whenever you want to switch to your other spritesheet, simply call

table_to_spritesheet(your_spritesheet_table)

To restore the original spritesheet, you need to call

table_to_spritesheet(original_spritesheet_table)

This function only takes 7% of the CPU time on a single frame (at 30fps), but since it needs to be called twice if you're going to use both spritesheets every frame, plan to have it use 15% of the CPU time.
NOTE: If you don't care about performance, but are low on tokens, you can remove the entire 'spritesheet_to_table()' function and instead use reload(0,0,0x1fff) to reset the spritesheet. However, this is vastly more inefficient, taking 20% of the CPU time of a frame at 30fps.

Explanation

These methods are not new, I just couldn't find any documentation of them for people to easily make use of them on the BBS.
This method makes use of the fact that a single number value in a Lua table is 4 bytes long, so you can easily make use of poke4 to quickly load a large amount of data into memory. This method first uses peek4 every 4 bytes in the spritesheet, loading the values directly into a table with no fiddling around with byte ordering. This allows it to entirely ignore the endianness of the functions. When you want to load a table back into memory, it's easy enough to just loop through the table again calling poke4 with each value of the table to quickly load 8px of the spritesheet in a single function call.
The most confusing part of this method, and the most easily breakable part, is the actual saving of this data. When loading it into a string, it is most efficient to store it in plaintext binary form. This storage is detailed by zep in this post:
https://www.lexaloffle.com/bbs/?tid=38692
However, a single character can only store a single byte. So whenever you save it as a string, and load it from a string, you need to decompose and recompose the 4 bytes of the number into 4 individual characters. This is only an intermediary form, so other than cartridge size, how this is stored is almost entirely independent of final performance. This means that in future, this could be optimized using methods like RLE or PX9 without affecting the final performance of the functions at all, as long as the decompression step comes before loading the data into the table.

Possible optimizations:

Currently the most of the saving/loading functions use nested for loops, but this could be simplified relatively easily to use a single larger for loop for more efficiency.
There may be other optimizations I missed/didn't implement, feel free to suggest them/show code for them if you find any!

P#82305 2020-09-26 20:31

Follow Lexaloffle:        
Generated 2021-05-17 15:36:13 | 0.089s | Q:31