As I needed an OVAL drawing routine for the paint cart I'm working on, I was surprised to see how small of code it was required in order to do such an effective one !
Here is the original C source code by roger dahl found in stackoverflow.com :
for(int y=-radius; y<=radius; y++) for(int x=-radius; x<=radius; x++) if(x*x+y*y <= radius*radius) setpixel(origin.x+x, origin.y+y);
Here is my code for Pico-8:
-- lovely oval drawing function -- converted by dw817 -- original by roger dahl function oval(h,v,x,y,c) x=(x+1)/2 y=(y+1)/2 h-=1-x v-=1-y for i=-y,y do for j=-x,x do if ((j/x)^2+(i/y)^2<1) pset(h+j,v+i,c) end end end x1=64-30 y1=64-20 x2=60 y2=40 cls() rect(x1,y1,x1+x2-1,y1+y2-1,1) oval(x1,y1,x2,y2,12)
As you can see it's a real plotting monster, but it DOES work, and I checked, it does not draw over any pixels it previously drew, so it's going about as fast as it can I think.
On PICO-8 you could engage the power of the pseudo-GPU by using rectfill() to draw a symmetrical span on the current line as soon as you get to a pixel that's "inside" the oval.
By the way, a technical heads-up: the ^ power operator actually calls a hidden pow() function, even for simple powers like 2, so it (annoyingly) takes a fair bit of time. Thus, it'd probably be faster to do j*j/x/x + i*i/y/y.
function oval(h,v,x,y,c) x=(x+1)/2 y=(y+1)/2 h-=1-x v-=1-y for i=-y,y do for j=-x,0 do if j*j/x/x+i*i/y/y<1 then rectfill(h+j,v+i,h-j,v+i,c) break end end end end
You could also do something where you only iterate i=-y,0 and then draw the same span above and below the center point, but you'd want to make sure you don't draw the middle span twice.
Interesting method. I think for large circles the number of false hits outside the circle might be a little overpowering vs. the math for calculating the extents of the span based on y, but for small ones it might be better just to test and discard like this.
Felice strikes again !
I would really learn to hate you from sheer jealousy for being so darned smart and right all the time. 🙄
As the fact is I desperately need your tutelage and methodology as it and often has been superior to mine, and I thank you for that as I am always learning something new.
Now I =WAS= earlier thinking of using LINE() to speed up the oval drawing but wasn't really sure how except by waiting for I to not equal the last I and then record coordinates left and right from the last plot that didn't plot.
That would work, but yours is spot on.
Clearly you have a boffo way of doing it here.
And - I'd like to use this. Unfortunately I can't. Not with my current project.
The paint cart I'm working on - I want it to have a really robust UNDO and REDO feature up to 128 steps in the paint program. And because of this every element, line, circle, oval, rectangle, filled and otherwise, user sprites plotted and maybe no longer even loaded - must all use "plot a single pixel" method so that same pixel can later be unplotted from a table for UNDO or REDO.
I'm also checking to make sure that every single pixel plotted is not the same color as the one beneath. Using true lines or rectangles would not check for that and make errors in undo attempts.
Now make no mistake, your filled oval routine is awesome and wicked fast. It would be of great benefit for anyone doing vector graphics, like something Penguinsoft did years ago.
Just ... not for my current project. Watch out though. I'll likely find a use for it after this one. :)
I will look at the power ^ operand I am using though and see if I can improve upon it as you said it was slow.
How exactly do you plan to maintain an undo buffer at pixel level????
A in-memory cube of 128x128x128??
Given that you have only math shapes, why not simply record the commands that were entered by the user? (never heard of ‘Design Patterns’ by Erich Gamma?).
That will cost you a redraw of the whole screen (but, hey, if you can’t draw the scene you probably have already blown up pico limits!).
yeah i'd do what @freds72 suggests:
have a screen buffer store the screen at the furthest 'undo' point (use part of gfx and map for this) and just record the steps used to get from there to the present state as opcode-style bytes that are stepped through as needed*.
*potentially every frame. but you can get away with less often if you also buffer the current state of the screen before drawing your cursor and such on top.
Probably the most token-light way to do it, while still ram-light as well, would be to run-length-encode an XOR diff between the current screen and the previous screen into a string. This is specific to PICO-8 because the screen is small and the bit depth is minimal.
You could also constrain it to an affected rectangle, but just doing the whole screen might be fine, depending on how well your RLE works for large swathes of zeroes.
Nice thing about XOR is that it makes undo/redo very easy to implement, since it makes them symmetrical.
Actually I was incredibly and pleasantly pleased to find I can record well past 128 full-screens of data, provided they are strings. I watched memory decrease as well. Used up only half of it to do that.
More than enough, and in any case I'm not undoing entire 128x128 areas, no just "objects" that are drawn, like Ovals, etc. Here ... is a sample to show you.
Run it, 128 ovals are made. Press (Z) and an UNDO step is made, all the way back to the beginning. Understand I am not redrawing any ovals at all. I am drawing the contents of the pixels BEFORE the oval overwrites it. Then I just play in reverse the pixel set that was stored.
The number at the top is the amount of memory being taken. Not nearly as much as I thought it would.
And yes, I was talking to my Dr today about XOR, how really that Apple tank game I made was supposed to use XOR for each dot like Bill Budge did originally - instead I opted to have true graphic sprites.
Those of you who are not familiar, plotting XOR for PICO is where you take 15 and subtract the color # of the pixel you are plotting on.
If it was color #0 you get color #15, color #1 yields color #14 and so on. The real advantage is =IF= you plot the exact same thing 15-(current color of pixel) it RETURNS the original color back prior to the first XPLOT. A nifty way to draw a "ghost" image so you can see the area it takes without damaging the background at all or requiring an UNDO storage initially.
And no, my paint program will not just have shapes. You have lines, you can outline images, import sprites, onboard proportional text, system 3x5 test, onboard 8x8 font, onboard 8x8 stamps and you can manually import a full 128x128 custom sprite area to draw from as that area is perfectly blank and I'm not using any of it.
(import [image].png) All of this area can also be used as stamps for your page work.
Pages (and regions) are saved as either a clipboard source-code insert to your program or as a BBS copy to clipboard image for pasting directly to the forum.
Will work with 8-full 128x128x16 screens at a time. Undo will jump to the page you last drew on when you invoke it.
Neat stuff ! Likely I will do the XPLOT when people are selecting ovals and rectangles and stuff and only when they press (Z) to confirm drawing the element, it will go to draw it proper - once again saving it to the UNDO buffer first so anything drawn, vector, line, object, area, stamp, sprite, tiles, can always be undone.
Currently I am using 80.0488 memory in my main program which already includes the 128x128 system image (proportional text, custom 8x8 font, system icons and devices), 128x128 16-color logo, and multiple pixel and location lookup tables and arrays.
There's enough memory for it all. :)
He's my psych. I see him every week. He says tell me what you're doing every day, and he writes it down - and so well, we talk just about everything that I wind up doing, as of recent, chapters to my 3rd book and working on this paint program for PICO. :)
I had hoped I would finish writing it by the end of this month but looks like it's going to be October.
[Please log in to post a comment]