New 3d Engine - Help filling 4-sided polygons

I made a perspective-correct 3d engine (that works with only 1 camera angle), rendering 600+ tiles at a smooth fps! The only problem is I need help filling in those tiles - they are 4-sided polygons and I can get all 4 points of each on the screen. I just need help filling those polygons - and I don’t know any way to in either blocks or typescript. If someone knows how, that info (like either fillrectangle that supports angles that aren’t strictly 90 degrees or a fill command similar to the image editor fill tool99 or any other trick to fill shapes in code) would be much appreciated.

If any of the Makecode team (@richard, @jwunderl, @Donuts, @hassan, etc.) or a forum user who knows a lot about Arcade know if this is possible easily or not, a reply would be much appreciated.

Here’s my current code:https://makecode.com/_1qhDeU52W4H5

5 Likes

Fill it in with triangles. Arcade does not have the APIs to fill triangles though unforuntely. Here is a Stack Overflow post I found that may help.


(If you were on stream today you will already know I suggested this, but for completeness and other people that didn’t join)

1 Like

I made a small update (which was sent on stream) but it still does not have filling of polygons

UnsignedArduino suggested some ways that could be feasible for filling, but I’m not sure (yet) exactly which route I’ll take in attempting this… I just wish sprite-utils had triangle fill… @jwunderl

3 Likes

I have been working on a 3d renderer also, and I have code for filling in a triangle. I was thinking of making it into an extension. So far, you can fill in a triangle with whatever color you like. I also had a special version of it that textures the triangle, without transforming the texture. I used the Barycentric Algorithm, which is short makes a rectangle around the triangle, and for each pixel in that rectangle, if it lies inside of the triangle, to draw there. this, and this were very helpful.

single color triangle filling

Screenshot 2023-03-20 6.18.30 PM
what it looks like in the simulator

Screenshot 2023-03-20 6.18.32 PM
the rectangles it uses to calculate

textured triangles

ezgif.com-video-to-gif (1)
an example of what I mean by “texture not being transformed”

Although this is really cool, it is one of the slower methods for filling triangles, especially if you have a long, skinny, diagonal triangle.

Shall I share the code?

1 Like

Do share the code! I’m working on something of my own, but if yours is faster I may use it and credit you!

1 Like

No problem!

The blocks are in the functions. For the regular triangle, the inputs are x1, y1, x2, y2, x3, y3, color, and a true/false statement for if you want the rectangle to be shown.

I will probably make this into an extension very soon…

2 Likes

Hi @Kiwiphoenix364

I made one, for your reference.

Target

  • Each line or each pixel drawn only once
  • No repeat calculation
  • Avoid trigonometric functions

Basic idea is:

  • Only draw each lines in X direction one by one, for Arcade setRow is hardware accelerated(c language)
  • Each x, get topY & btmY in all 3 edges. If out of range one edge range, don’t change topY/btmY
  • Draw a line (x, topY),(x,btmY). Or blit texture at (x, topY, x,btmY)

Ref:
drawLine(…) in https://github.com/microsoft/pxt-common-packages/libs/screen/image.cpp

Remake:

  • And some changes to make it all support loop from X left to right.
  • We need yeild Y at given X of 3 edges, intead of outputing all Y edge by edge. Typescript doesn’t support “yeild”, so I defined a Line class, with next() method.
  • For point position calculated by accumulation, keep state(via Line class instance) wait for next calling with next X.

Result

  • On my Meowbit :About 12ms each filling solid color, 17ms each filling with texture, with random points, running 100+ time continues without update screen. About 20+ms if update screen every time.
  • On my simulator: About 1~2ms

This is wrote in several hours, should have bug inside, LOL. Feel free to report bugs.

4 Likes

Well, I’ve been trying to make my own triangle generator, but it kinda makes Star Trek logos instead of triangles… That’s a problem…

Also, @AqeeAqee your triangle drawer isn’t SUPER fast (at least it works), but it is pretty much unplayable at this point…

This is my port of a gpu-optimized triangle drawer but using the CPU so its really slow…

I might try to make one (in the future) that calculates all the positions that it needs to draw lines, sticks them into an array, then loopes over that array to draw the lines at the end of the frame. I’m not sure if that’ll even be fast enough though… I might just resort to a wireframe art style like this if I can’t get a triangle-drawing algorithm that is fast enough…

I might as well just ask… @kwx do you know how to fill polygons efficiently? (like 300/frame)

1 Like

After last post, I think it over again, cause I am not very satisfied with the speed of result somehow. Suddenly, I awared that function calls are a little slow in Typescript, and there’s many function calls( about (maxX-minX)*3+3) times. So the Targets should append one “Avoid function call as much as possible”
Then I remaked a new version:

In this version:

  • removed class and next method
  • all minY&maxY of X are cached into a array(len=160x2),
  • only one function for each edge, no embeded function, calculated and cached in one call

Result

  • On my Meowbit :About 5.5ms each filling solid color, 8.8ms each filling with texture, with random points, running 100+ time continues without update screen. About 9.6/13ms if update screen every time.
  • On my simulator: About 0.2~0.5 ms

About Fill Polygon
This algorithm can fill polygons too. 4 or more edges, are all fine as long as any vertical line(x) cross edges 2 times at most.
Fortunately, what ever perspective transfrom applied, a rectangle/trapezoid will be still met this condition.
That means you need not split polygon into triangles in advance.
So I add a FillPolygon4() method

Result

  • On my Meowbit :About 5.9ms each filling solid color, 10.6ms each filling with texture, with random points, running 100+ time continues without update screen. About 10/15ms if update screen every time.
  • On my simulator: About 0.3~0.6 ms

Some 4-edge-polygon example:
Meet condition:



image
image

image

Not met condition:


2 Likes

This is something that should really be done in the “c++ layer” of arcade. I’ve opened issues for triangle fill and polygon fill.

If anyone is interested in contributing this, I would be happy to walk you through the steps for adding code to said c++.

Just comment on those github issues!

5 Likes

@richard
I am glad to, if you feel I’m qualified.
BTW, my time zone is much early than west coast US, hope it’s not a big problem, if need communication frequently.

So that confirms you’re adding polygon support natively??? NEAT! I haven’t coded in c++ but Id assume it isn’t too difficult. I probably won’t contribute though because it probably wouldn’t be a super good 1st project… Especially to optimize math for a first project in a different programming language…

cool project!

I’ve updated the triangle issue with some getting started code and instructions!

Also, as @Kiwiphoenix364 mentioned this might be a tough project for someone new to C++ or coding in general

1 Like

the most i have done with c++ was make a simple command line shell for the nintendo ds/dsi
sadly i couldn’t find any graphics libraries tho . but thats understandable the console is 18yrs old and most websites about homebrew dev for it are shutdown or offline

updated with new version, 4-side-polygon fill.

(except color logic is nonsense :joy:)

6 Likes

Take a look at the triangles.ts file in my renderer demo https://arcade.makecode.com/03733-23293-89994-81627. It’s all based on slicing polygons into trapezoids with vertical left and right edges, and then drawing them one vertical pixel strip at a time in a byte buffer, then copying that byte buffer to the screen with image.setRows(x, buf). It draws the 1-pixel-wide slices of all triangles that intersect this X position simultaneously, so each vertical pixel column only gets written to the screen once. Also, it exclusively uses fixed-point (integer) math for this part of the codesince floating point math is extremely slow on hardware.

At the low level, I’ve done some loop unrolling to speed things up, for example this function that draws two alternating colors for dithering:

export function drawTriLineAlternating(tri: ActiveTrapezoid, buf: Buffer, col0: number, col1: number) {
        const y0 = Fx.toIntFloor(tri.a_y)
        const y1 = Fx.toIntFloor(tri.b_y)

        let y = y0
        if (y & 1) {
            // Swap the colors so that col0 is consistently on even Y
            const tmp = col0
            col0 = col1
            col1 = tmp
        }

        let y1end = y1 - 7
        while (y <= y1end) {
            buf.setUint8(y, col0)
            buf.setUint8(y + 1, col1)
            buf.setUint8(y + 2, col0)
            buf.setUint8(y + 3, col1)
            buf.setUint8(y + 4, col0)
            buf.setUint8(y + 5, col1)
            buf.setUint8(y + 6, col0)
            buf.setUint8(y + 7, col1)
            y += 8
        }
        if (y <= y1 - 3) {
            buf.setUint8(y, col0)
            buf.setUint8(y + 1, col1)
            buf.setUint8(y + 2, col0)
            buf.setUint8(y + 3, col1)
            y += 4
        }
        if (y <= y1 - 1) {
            buf.setUint8(y, col0)
            buf.setUint8(y + 1, col1)
            y += 2
        }
        if (y <= y1) {
            buf.setUint8(y, col0)
            //++y
        }
        tri.a_y = Fx.add(tri.a_y, tri.a_dydx)
        tri.b_y = Fx.add(tri.b_y, tri.b_dydx)
    }

Of course, having native code to do triangle drawing would be way faster than trying to do so in TypeScript, so having that would make a lot of fancier graphics possible.

1 Like

I made this triangle renderer but it has worse perf than yours @AqeeAqee :

It was based off of this algorithm. It does have some glitching though.

Also, @AqeeAqee , thanks for making your quadrilateral renderer work with my game! I had trouble because it kept crashing (I wasn’t rounding the numbers, as I later found out.) I will use the version that you made until the c++ triangle renderer is finished and ready for the game. Thank you!

It’s so cool bc now I can do stuff like this:

In case you are wondering:
This project started out as a quest to revamp my old project, which originally was supposed to be a remake of the engine of the flash (and now HTML5) game series “Run.” (run, run 3) That project was entirely hardcoded and used basically no math, with sprites (manually scaled bc no scaling), rotation that wasn’t proper, and bugs (not to mention the fact that the tunnel only had 4 sides, and wan’t perspective corrected.) This project resolves many (if not all) of those old issues, but I don’t think I’m going to be just ripping off the original Run games. I’ll try to make it unique, even though it will still be running through gravity changing tunnels, like it being first-person (probably) and maybe with some sort of collectible. It’ll be inspired by the look of the original games, but it won’t be a remake.

4 Likes

Is it possible to add curves and make this into a runner like thing?

example

screen-0

3 Likes

Neat! This is an excellent example! I may not take the effort on implementing this algorithm depending on how long it takes for the C++ to be written (as there already is one that runs), but if it takes a long time for the C++, I will definitely put this to use!

3 Likes