This is an prototype for a vector drawing program in pico-8.
Arrow keys: Move cursor
Z: Add point / Select polygon
X: Toggle cursor speed (Normal, Fast, Slow)
Tab: Hide menu
Q: Hide cursor
Double click "NEW" to clear the image and start a new drawing.
To save image:
--Hit tab to hide menu
--Hit q to hide cursor
--Screenshot to desktop with F1 key
--Full Scene (into ROM):
--Double click SAV button
--Hit escape to exit cartridge
--Ctrl S to save cartridge
--(This is bit of a hack)
New in V0. 88
--fixed the bug with black shapes
--added save function (exports poly list to sprite-sheet. Poly compression is simple 1 byte per value
--added select function
--Click SEL to enter selection mode
--Selected polygons can be re-colored or deleted
--added delete function
--accelerate drawing solid color polygons by just using line command instead of pattern/bplot.
--store edge list in poly so that we don't recalc every time
--don't redraw all the polygons when we add a new one. removes flicker when drawing complex scenes.
--x button switches the speed of the cursor (slow, medium, and fast) this allows for "pixel perfect" drawing.
--cursor changes color so that it stands out against whatever is behind it
--drawing lines change color so that they match the current foreground
--Export clobbers save
--arrow keys move cursor
--z key starts drawing / adding to a polygon
--double click z key to close polygon
--click on the palate to change foreground / background / pattern
--The next polygon drawn will use that color pattern combo
Double click "new" to clear screen
Double click "exp" to save screen to sprite space on ROM
Very nice and useful tool. My niece's going to love it. :)
I've added a little feature to hide the HUD (cursor and palette) so she can use the entire canvas and take screenshots with only her drawings on screen.
TAB -> hides palette
A -> hides cursor
I would upload the cart here but I don't know how to do it yet. :( ... But it's an easy change... I'm pretty sure you could do that if you wanted to :)
Nice work, electricgryphon!
It might also be worth rendering to the spritesheet. Scaling could be very useful whether rendering to the screen or to the spritesheet. But whatever you do with it, great stuff and I am looking forward to permanent storage.
really nice work!
yeah scaling and beziers would be excellent additions!
also being able to edit polygons, eg. move each node.
beziers shouldn't be too difficult to implement, although the UI for them might be.
I added some new functionality.
Per Smosher's suggestion, I added a function to export the drawing into the sprite space of the ROM. (Double click the "EXP" button.) This at least stores the graphics someplace permanent, where you could pull them out with a text editor.
Felipebueno, I added the functions to show and hide menu and cursor. (Tab to hide menu and 'Q' to hide cursor.)
Darkhog, filled beziers are a bit beyond my skill-set right now.
Cool. Maybe you could add the option to print out polygons as an Lua table with debug printing (printh) so we could add these to our program, also publish drawing routine somewhere, maybe the wiki.
And having no beziers is okay, though if you'd be able to learn these and add them, that would be nice.
I'm also interested about how fast it can render, so I could do animations like ones in Another World/Out Of this world (that game uses vector graphics as well).
//edit: Also either I'm doing something wrong or sel/del options doesn't work in the current version.
Another thing I'd add are outline-only polygons/unclosed polygons.
- Starting and finishing polygon with X instead of Z would make unclosed polygon (line)
- Starting with Z, but finishing with double X, would make closed polygon, but it'd be transparent, with outline only.
Thanks innomin. I'll definitely check out your code.
I'm starting on the code to store polygon data in the rom. Currently, I'm using a naive approach of having each number stored as a byte. It's easy to read and write, but I don't necessarily need a full byte for everything. For example, the foreground and background color could be stored in a single byte. (4 bits for foreground and 4 bits for background.) I should be able to do this with bit wise operations...
If I really squished everything down it could be like this:
(Color 1)(color 2)(pattern)(length)(X1)(y1)(x2)(y2)etc
(4 bits)(4 bits)(4 bits)(6 bits)(7 bits)(7 bits) etc
However values don't land on even bytes. Seems like a lot of complexity to save 2 bits for every x,y pair.
To make things faster and simpler I am leaning towards using the MSB of the x values as a flag to say that we are at the end of a polygon. (Downside is that I'll have to read through all the points to know where the end is...)
(Color 1+ color 2)(pattern)(X1)(y1)(x2)(y2)etc
(4 bits + 4 bits)(1 byte)(1 byte)(1 byte) etc
It will be a good learning experience to muddle through this, but tips are definitely welcome.
That t-rex is awesome! Do you know roughly how many polys it was?
From eyeballing I estimated 40 with an average of 8 points per poly would bring it to ~720 bytes, which is pretty good without compression. I imagine Another World style backgrounds using similar datasize could still be reasonably detailed.
I don't think wasting 2 bits per X,Y pair is so bad -- better to keep it simple and lower on tokens. For use in backgrounds, it might be quite useful to give the X value 8 bits to allow either plotting points off the screen, or to have scrolling scenes. (leaving the Y high bit as a terminator)
If you end up wanting to encode the number of points in the pattern's byte for some reason, and 5 bits is enough (maximum 32+1 points), you could save a bit by swapping color 1 and color 2 when pattern is >= 8, assuming patterns 8..15 are the same as 7..0 inverted.
Added a bunch of new features today:
--Select tool (used for re-coloring shapes)
--Delete tool (erases the selected shape)
--Save vector scene to ROM
(See full list in notes at top)
Saving is finicky, but I think it's the best that we can do until we external data saving functionality beyond the ROM write functions.
There's a pesky bug that I haven't been able to figure out, where a polygon will occasionally draw black. It has proven difficult to reliably re-create, and usually seems to pop up on polygons with many points, but not always. The code is a bit of a mare's nest, but if an altruistic soul can figure out how to re-create I would be thankful.
It would be nice if you'd add ability to save every picture as string, then some code to unpack it into an array. This way we'd be able to have many scenes with little token loss (as string is just one token).
Also ability to scroll using player 2 arrows (esdf) would be great as this way we can build bigger scenes that span multiple screens.
//edit: Proposed format for string-packed scenes:
where POLY marks start of each polygon, then there are two numbers defining colors of polygon and third that defines pattern used, then list of vertex positions in XY format until next POLY is met. Everything separated with colons.
Also please add ability, when you'll be making a viewer to draw "scenes" at an offset, so we could have detailed vector "sprites" (can't really call them that, but not sure what else to call them) to use in games and to have animations with them.
Hmmm... I might be crazy, but it looks like pset(x,y,c) sometimes draws black to the screen when the color "c" is not black.
I added some debug code that checks if the color I am drawing is black (0). Then it draws the pixel using pset(x,y,c) . Then it checks the color of the pixel drawn with pixel_color = pget(x,y). Every once in a while, these values "c" and "pixel_color" don't match up. Possible bug?
Here's a picture of the bug I am seeing:
Here's my debug code:
function bplot(x,y) thecolor = cur_pattern[x%4+1][y%4+1] if(thecolor == 0) then thecolor=7 end --so thecolor can never be black (replace with white) pset(x,y,thecolor) --write thecolor to the pixel with pset if(pget(x,y)==0) then pause(1) end --but if I read the pixel that I just wrote, I see that it is black. end
Wow this is really moving along. Good work.
My guess is thecolor isn't actually in bounds, i.e. negative, greater than 15, or maybe a fixed point value or even nil. I had the same problem with rectfill and it turned out I was indexing an array wrong, getting the zero index out. Your code has +1 following the modulus, which was the fix for mine. Still, you might be getting nil.
Might want to check for thecolor != nil, thecolor < 16, thecolor > -1, flr(thecolor) == thecolor, and just print the value if any fail, along with the x, y values, ...and maybe even print out the values in cur_pattern.
Edit: strike all those tests. Just check for pget(x,y) != thecolor, and dump info and pause in that case.
Ah cool. Np.
Scanning through the code, I found only 2 uses of division that aren't inside a call to flr(), make_edge() and erase_cursor(). I don't actually see a case where either of these are likely to cause problems.
Overflow can cause negative values, but I haven't gotten any fixed point out of it. It's possible there is an edge case during overflow that does convert it to fixed point. Seems plausible with certain fixed-point formats but I haven't looked into what pico-8 is doing there.
Does this happen while drawing the poly manually, or does it only happen after recalling the data from storage? I guess if it was a save/load issue you would have seen more general corruption probably.
The x value was getting a floor, but the y value for polygon max height was sometimes coming in at a fraction after I allowed the cursor to move in 0.5 steps. The only surprise was that the bug was not happening way more often. I think I have it fixed now--gonna do a few more drawings to confirm.
[Please log in to post a comment]