14. Meshes in 3D – nice and slow
Now we’ll start to work with meshes for 3D drawing. I’m going to go slowly, because this stuff can be difficult to understand.
Triangles
A mesh is a collection of one or more triangles in a 3D space. Triangles are used because they are the simplest shape that can be used to build up more complex shapes.
Each triangle has 3 points, or vertices. So a vertex is one of the 3 points of a triangle.
This is important because most of the settings are attached to a vertex. Not only are they used to position the triangle, but Codea interpolates the colour or texture between each vertex and its neighbour. So it’s a bit like setting the colour of every third pixel in an image, and interpolating the pixels in between.
The smaller you make your triangles, the more detailed your drawing will be – but of course, it will also draw more slowly, so there is a balance.
Defining a mesh
We start by defining a “mesh”, which is simply a collection of triangles, or, more accurately, it is a table of vertices, 3 per triangle. So if you have 4 triangles, you need to define 4 x 3 = 12 vertices. A vertex is a three point vector, or vec3, and it simply sets an x, y and z (for depth) value for one end of the triangle.
So this is a three point vector, for the point x=100, y=200, z=0
vec3(100,200,0)
This table holds one triangle, with 3 vectors
{vec3(100,100,0), vec3(300,100,0), vec3(300,400,500)}
This table holds two triangles, with 6 vectors. Each set of three makes up one triangle.
{vec3(100,100,0), vec3(300,100,0), vec3(300,400,500), {vec3(200,200,0), vec3(400,200,0), vec3(100,200,400)}
So if you wanted to describe a cube, which has 6 square faces, you will need at least 12 triangles, 2 per face. 12 triangles means 36 (x,y,z) vertices.
So your mesh will need to contain a list of 36 vertices, and, because there are only 8 vertices on a cube (the 8 corners), this means you’ll be reusing some vertices over and over, which gets confusing when you try to mentally turn the cube around to each face and try figuring out which vertices you are looking at. I worked through the sample in the 3D lab, and in the end I got so confused I found an old book and wrote all the vertices round the corners to see how it worked. So even for simple objects, it is complex. And that’s just straight objects. What about curved objects?
Well, for now we’ll just use one triangle suspended in hyperspace, to show how it works.
Clothing the mesh
A mesh is invisible – think of it as a wireframe outline – so we need to clothe it – each and every triangle in it.
There are three ways to do this, as shown below. I’m going to assume we’ve defined a mesh already with
m = mesh()
- Set a color for the whole mesh like this
m:setColors(255,255,255,255)
This is going to be really boring, so let’s look at the other options.
- Set a color for each vertex like this
m.colors={color(223, 45, 179, 255),color(74, 106, 201, 255),color(223, 121, 31, 255)}
If you are going to specify colors, then there needs to be one for each and every vertex in the mesh. So for our simple cube, you would need to specify 36 colors to match each vertex.
Anyway, if you apply the colors above to our little triangle, this is what you get.
See how the colors at each corner are blended into each other in the middle. So you can think of the mesh as asking for colors at regular intervals across the whole frame, and then interpolating in between. That is way faster than asking for a color at every pixel.
The third option is to apply a “texture” from an image (or part of an image), as discussed in the last post. This can be a little tricky, because what you have to specify for each vertex is its position in the texture image. So you have to imagine where the triangle fits over your texture, and figure out which coordinates to use for each vertex. Note that a texture is 2D, so you only specify x,y, and also that as in the previous post, the position is given as a fraction of height or width.
So if you have a texture image which is 300 wide x 200 high, and you want to use the triangular chunk bounded by (100,100), (150,175), (125,200), then you have to calculate everything in fractions of width and height, giving (I hope) a result of (0.33,0.5),(0.5,0.875),(0.417,1.0).
So the result for just one triangle might look like this
m.texture = "Planet Cute:Grass Block" -- Assign a texture m.texCoords = {vec2(0.33,0.5),vec2(0.5,0.875), vec2(0.417,1.0)}
Which looks like this
Ok, that was a dull choice, I’ll admit.
I’ll stop there for now, just baby steps at first.