[ZS - Solved] Request - 2D Shape Drawer Help

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
Post Reply
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

[ZS - Solved] Request - 2D Shape Drawer Help

Post by Sarah »

Greetings all! As the title says, I am looking for reference material using the 2D shape drawer class. Working code examples would be excellent - especially things like basic shapes. Thank you! I hope everyone is well!
Last edited by Sarah on Sun Jul 24, 2022 11:08 am, edited 2 times in total.
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

Re: [ZS] Request - 2D Shape Drawer References

Post by Sarah »

After much digging and broken links, I came across this PR which has a working demo file!

Seriously, Marisa if you see this I'd love some explanation, that is probably the funniest demo I've seen and I can work with that!
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sarah »

Greetings! So after some playing around I've gotten it figured out how use the shape drawer. However now I'm having my usual issue: trigonometry :rocket:

So I need your help. From my understanding of the shape drawer, there's three basic steps:
  1. Map texture coordinates
    • Texture coordinates are a normalized value between 0 and 1, where (0,0) is upper left hand corner and (1, 1) is the lower right
  2. Triangulate shape
    • All of the coordinates now need connected into triangles.
  3. Establish screen vertices
    • These correspond to texture coordinates and determine where the shape is on the screen.
What I've been trying to do, as demonstrated in the example image below is write something that will allow for the creation of any shape, based on the number of vertices. To do this, there is one more vertex than the requested number - for example, if you define 4 vertices (the red ones) there's actually 5. This extra vertex is the center of the shape (the black vertex). What this means is that any shape is considered a circle, regardless of vertices, it's actually the visual result that determines if we think it's a triangle or a square or a dodecahedron.



What I'm stuck on is the trigonometry for doing this. The texture coordinates and the shape vertices must both be calculated this way to "fan" the triangles around the center point. What code I've written is below, it does not work, and any help is very, very much appreciated! Thank you!

Code: Select all

 private void generateShape(int VertCount)
    {
        Vector2 tsiz = TexMan.GetScaledSize(Texture);

        // Gah! Revenge of the circles!  Why do I hear my junior year geometry teacher laughing???  
        float texrad = Sqrt(pow(tsiz.x / 2, 2) + pow(tsiz.y / 2, 2));  // texrad is the radius of the circle
        float coordang = 360.0 / float(VertCount); // angle formed by each coord pair and the center

        array<ShapeVec> rawcoords;
        Shape.PushCoord((0.5, 0.5));    // Center of circle
        for (int i = 0; i < VertCount; i++)
        {
            rawcoords.Push(new("ShapeVec").Init((texrad * Cos(coordang * (i + 1))) / tsiz.x,
                (texrad * Sin(coordang * (i + 1))) / tsiz.y));

            if (rawcoords[rawcoords.Size() - 1].Coords.x < 0.0)
                rawcoords[rawcoords.Size() - 1].Coords.x += 1.0;
            if (rawcoords[rawcoords.Size() - 1].Coords.y < 0.0)
                rawcoords[rawcoords.Size() - 1].Coords.y += 1.0;

            console.printf(string.format("Generated coordinate #%d, x:%f, y:%f", i, rawcoords[rawcoords.Size() - 1].Coords.x, rawcoords[rawcoords.Size() -1].Coords.y));
        }

        for (int i = 0; i < rawcoords.Size(); i++)
            Shape.PushCoord(rawcoords[i].Coords);

        console.printf(string.format("Texture radius is: %f, coordinate angle is: %f, for %d vertices", texrad, coordang, VertCount));

        Shape.PushTriangle(0, 1, VertCount); // indices for the first triangle don't follow the pattern, all the others do.
        console.printf(string.format("Pushed triangle, indices a:0, b:1, c:%d", VertCount));
        for (int i = VertCount; i > 1; i--)
        {
            console.printf(string.format("Pushed triangle, indices a:0, b:%d, c:%d", i, i - 1));

            Shape.PushTriangle(0, i, i - 1);    // we rotate counter-clockwise, 0 - or the center is always the first index
        }

        // Push vertices
        array<ShapeVec> verts;
        // This is the center vertex
        verts.Push(new("ShapeVec").Init(xLocation + ((tsiz.x * ShapeScale) / 2), 
            yLocation + ((tsiz.y * ShapeScale) / 2)));

        for (int i = 0; i < VertCount; i++)
        {
            verts.Push(new("ShapeVec").Init(verts[0].Coords.x + (texrad * Cos(coordang * (i + 1))), 
                verts[0].Coords.y + (texrad + Sin(coordang * (i + 1)))));
        }

        for (int i = 0; i < verts.Size(); i++)
            Shape.PushVertex(verts[i].Coords);
    } 
Last edited by Sarah on Sun Jul 24, 2022 12:09 am, edited 1 time in total.
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sir Robin »

Did you figure this one out? I'm ok with geometry and trig, maybe I can help. But I haven't done anything with shape drawing so I don't understand what you're trying to do. Are you trying to cut the circle into slices like a pie and then map a triangle onto each slice?
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sarah »

No I haven't. But essentially yes, I am trying to slice the circle like a pie. Where I'm stuck is calculating those points around the circle. Mostly.

Code: Select all

rawcoords.Push(new("ShapeVec").Init((texrad * Cos(coordang * (i + 1))) / tsiz.x,
                (texrad * Sin(coordang * (i + 1))) / tsiz.y));
This bit of code should generate the vertex coordinates on a circle. The problem I'm currently having is that these coordinates are cartesian - the center of the circle is (0,0), which results in coordinates with negative values. This is normal.

Now, these coordinates must be mapped to a proportional value with a range of 0 and 1. Why? Because texture coordinates are floats between 0 and 1. A coordinate at (0,0) on the texture will be the upper left hand corner, and (1, 1) is the lower right hand corner. Converting between these cartesian coordinates and this normalized range is what's got me stuck.

I updated my code example with my most recent code. Thank you again for the help!
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sir Robin »

So you're just trying to convert the unit circle (-1 .. 1) to (0 .. 1) ?
That's pretty easy. Sin and Cos return values in the (-1..1) range, so first cut that in half and you've got (-0.5 .. 0.5) and then add 0.5 and you've got (0 .. 1)

Are you wanting to get the vertex coordinates of an N-sided regular polygon whose center is at (0.5, 0.5) and whose outer radius is 0.5 ? It sounds like that's what you're trying to do. Or am I completely misunderstanding?
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sir Robin »

Code: Select all

    private void RegularPolygon(vector2 center, float radius, int NumSides)
    {
        console.printf("Regular Polygon with %d sides", NumSides);//DEBUG
        float angle = 360 / NumSides;
        console.printf("Angle per side: %.3f", angle);//DEBUG
        for (int i = 0; i <= NumSides; i++)
        {
            float x = radius * cos(i * angle) + center.x;
            float y = radius * sin(i * angle) + center.y;
            console.printf("Coords: (%.3f,%.3f)", x, y);//DEBUG
        }
    }
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sarah »

I'm going to try your function. From a surface glance it doesn't look like we're doing anything different, but my equations are not producing circles.

Here is my current function. I actually realized last night that the...

Ok, lets name the different vertex types and stick to them.
  1. Raw Coordinate - this is a Vector2 that will have X/Y values in a range of -1 to 1, inclusively. These are cartesian coordinates, (0,0), is the center of the projection, splitting the coordinates into 4 quadrants. These coordinates need shifted to become Texture Coordinates.
  2. Texture Coordinate - this is a Vector2 with its X/Y values set in a range of 0-1, inclusively. This vertex should be thought of as drawn on the texture itself.
  3. Shape Vertex - this is a Vector2 whos X/Y values should be greater than 0. This vertex corresponds to a Texture Coordinate, and determines where that Texture Coordinate appears on the screen. It is the location of a Texture Coordinate.
BTW, I wrote that to keep things straight in my head as much as help communication. I'm not even talking about triangles here, that's actually a bit of the code I've had working from the start - I initially experimented with drawing a square using 4 triangles instead of 2, this gave me the idea to treat all shapes as circles.

So last night I realized that I can shift from Raw Coordinates to Texture Coordinates by doing: (A ShapeVec is a wrapper class for Vector2's so I can use array<>)

Code: Select all

rawcoords.Push(new("ShapeVec").Init((((texrad * Cos(coordang * (i + 1))) / tsiz.x) + 1) / 2,
                (((texrad * Sin(coordang * (i + 1))) / tsiz.y) + 1) / 2));
The conversion happens by taking the result of (texrad * Sin(coordang * (i + 1))) / tsiz.y) and adding 1 to it, and finally dividing that result in half.

My equation for calculating Shape Vertices is basically the same, which is wrong, it was 1:00am :stuppor: What I have done now is export the Shape Vertex array to the entire class, such that I can use those vectors to draw other stuff - in this case text - just draw an 8 or something where the vertex should be. So I'll try your equations, surely I'm just not seeing what I'm doing wrong.

Ok, for a bit more completeness, entire class in the spoiler below, it's only 200 lines. Thank you again for your help!
Spoiler: ZScript Windows ZShape Class
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sir Robin »

Ok, I'll try to figure out what you're doing in that code.

Meanwhile if you run my function like this: RegularPolygon((0.5,0.5),0.5,4) it will produce the unit circle exactly as you described in your 3rd post.

It sounds like you just need to get your coordinates into different scales. Here's the code to do that as described:

Code: Select all

	private void CalcCoords(int NumSides, float TextureWidth, float TextureHeight)
	{
		console.printf("calculate Regular Polygon with %d sides", NumSides);//DEBUG
		float angle = 360 / NumSides;
		console.printf("Angle per side: %.3f", angle);//DEBUG
		for (int i = 0; i <= NumSides; i++)
		{
			//raw x,y are on the unit circle (-1 .. 1)
			float rawx = cos(i * angle);
			float rawy = sin(i * angle);
			
			//texture x,y are in the range (0 .. 1)
			float texx = (rawx + 1) / 2;
			float texy = (rawy + 1) / 2;
			
			//shape x,y are in the range of (0 .. width),(0 .. height)
			float shapex = texx * TextureWidth;
			float shapey = texy * TextureHeight;

			console.printf("Coords: (%6.3f,%6.3f) (%6.3f,%6.3f) (%.3f,%.3f)", rawx, rawy, texx, texy, shapex, shapey);//DEBUG
		}
	}
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

Re: [ZS] Request - 2D Shape Drawer Help

Post by Sarah »

Success! Thank you Sir Robin! Its the same equations again and again, why can't I remember that? Lol thank you!

I can change the shape by simply changing the number of vertices given to the control, as desired! Now, yes the square is a diamond, that's actually expected behavior, I'm going to include both a scale and angle members. Scale I've already played with, it's perfectly feasible to have a multiplier like a TEXTURES lump and make shapes bigger or smaller based on that value. Angle will be similar to scale but require some more trig to calculate where, say, a vertex in a square should actually be.

Ah, I can see writing sliders now just to tie the slider to the vertex count of a shape so I can change it in real time. Odd little satisfying things.




What's the point of all this? Buttons, actually! ZScript Windows defines a handful of button types within a single class, it's a mess, and I want to rewrite it for more flexibility. Right now, one type, a "ZButton" - which is a replication of buttons in ZWindows - the GDCC version of my gui system - can handle a dynamic width, but not a dynamic height. If I have more than one line of text in a button - that's bad gui design anyway, but still - it will wrap and be cut off. My standard button type can handle that, but that button type has issues with clipping and frames and...grr ok start over and write a new button class or classes, so I thought, ok, I wanted a shape drawer control anyway, I'll build it and use it as the basis for the new buttons. The rapid adoption of a circle-based system for drawing shapes is actually due to my own gui design - rounded corners - I did it first Microsoft! - but that is achieved with textures. This shape drawer could allow me to draw shapes like what's in the screenshots without graphics bloating the package! Not like they really do, I use TEXTURES to do stuff like rotate textures and then tile in code.
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: [ZS - Solved] Request - 2D Shape Drawer Help

Post by Sir Robin »

if you want squares to not be diamonds you can add half an angle to the calculations:

Code: Select all

         float rawx = cos((0.5 + i) * angle);
         float rawy = sin((0.5 + i) * angle);
Now any shape whose sides are a multiple of four will have their edges aligned to the axes.

But that's a square inside a circle inside a square, so it's going to be a bit smaller than the square texture you started with.

If the point of all this is to cut out buttons with round edges, can't you just do that with an alpha channel in the texture?
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: [ZS - Solved] Request - 2D Shape Drawer Help

Post by Sir Robin »

If you're using n-gons where n is a multiple of 4, this will maximize your texture space:

Code: Select all

    private void CalcCoordsPerQuad(int SegsPerQuad, float TextureWidth, float TextureHeight)
    {
        int NumSides = SegsPerQuad * 4;
        console.printf("calculate Regular Polygon with %d sides", NumSides);//DEBUG

        float angle = 360 / NumSides;
        console.printf("Angle per side: %.3f", angle);//DEBUG

        float newRadius = 1 / cos(angle/2);
        console.printf("new radius: %.3f", newRadius);//DEBUG

        for (int i = 0; i <= NumSides; i++)
        {
            //raw x,y are on the unit circle (-1 .. 1)
            float rawx = newRadius * cos((0.5 + i) * angle);
            float rawy = newRadius * sin((0.5 + i) * angle);

            //texture x,y are in the range (0 .. 1)
            float texx = (rawx + 1) / 2;
            float texy = (rawy + 1) / 2;
            
            //shape x,y are in the range of (0 .. width),(0 .. height)
            float shapex = texx * TextureWidth;
            float shapey = texy * TextureHeight;

            console.printf("Coords: (%6.3f,%6.3f) (%6.3f,%6.3f) (%.3f,%.3f)", rawx, rawy, texx, texy, shapex, shapey);//DEBUG
        }
    }
Just give it the number of sides divided by 4, so 1 for square, 2 for octagon, 3 for dodecagon, etc

I'm sure there is a way to make it for a shape with any number of sides but I don't know the math for that off-hand
User avatar
Sarah
Posts: 551
Joined: Wed Sep 06, 2006 12:36 pm
Preferred Pronouns: She/Her
Operating System Version (Optional): Debian 11 (bullseye), Windows 10
Location: Middle of Nowheresville Il.
Contact:

Re: [ZS - Solved] Request - 2D Shape Drawer Help

Post by Sarah »

Well you're certainly having fun with this! This quad calculator looks promising, but I think to account for a shape with any number of sides, we're actually talking about a circle, so we're back to the math we were already doing. Thank you though for your help!
Post Reply

Return to “Scripting”