Skip to content

63. 3D – Adding stand alone images (trees, animals..)

May 23, 2013

How do you add things like animals and trees to a 3D map? Do they need to be in 3D? Not necessarily. I’ll show you a trick that lets you use 2D images instead.

The problem with 3D

Let’s suppose we want to put a dog in our scene. This dog.

The problem is that this dog is going to be extremely difficult to model in 3D – it would take a lot of triangles to get it looking even roughly realistic. And that’s a dog that’s just sitting doing nothing. If I want it to stand up or walk about, I’ll need whole sets of additional triangles for each position – and Codea just isn’t going to cope.

So we need to cheat. And there’s a way.

Cheating with 2D images

What I’ve done is to simply use a 2D image, but always keep it facing you. So if you walk around the image, it will turn to face you, and you will never see that it is a flat image.

Now this works well for animals, because they are quite likely to turn to face you, and also for trees, because they look similar from any direction. It wouldn’t work so well with a car, though, because cars don’t turn themselves around. So it has limitations, but we’ll try it anyway.

I’ll start with the code from last time, and add a tree image I’ve found for you (I’ve also found a nice one for the walls, just to make things look nice).

We need a new function to handle adding a stand alone image. It assumes the image starts by facing us, with x at the middle of the image.

function AddItem(x,y,z,w,i,r) --centred on x
    local h=i.height*w/i.width
    local m=mesh()
    local v,t={},{}
    if r~=nil then m.ang=r else m.rotate=true end

The parameters are the x,z position, the width in pixels (it doesn’t have to be the same as the image), the image, and a rotation number.

Note first how we calculate height from the width. I’m assuming you always want to use the whole image, and if you provide the width you want to draw, I can calculate the height by pro-rating.

We set up a mesh, and what we do next depends on what r is. There are two situations I want to allow:
(a) the image is at a fixed angle which will never change (r has a value)
(b) the image should turn to face us (r is nil)

Either way, we need to centre the image at x, so we send through the details to AddImage to create the vertices for us, with x = -w/2 (AddImage draws from the bottom left corner, so we have to adjust x by half the width).

We then store the details of x,y,z and if r has a value, we store it in the variable ang, which we included some time previously. If ang is set, then draw will rotate and draw.

If r is not set, then we need to face the user. I tell draw to do this by setting m.rotate=true.

The other changes I need to make are in draw.

        if m.rotate==true then
            --move to the centre of our image
            --calculate difference in x and z
            local dx,dz=m.pos.x-posX,-m.pos.z+posZ
            --calculate angle we need to turn
            local ang=math.atan(dx/-dz)/rad
            --turn and draw
        --if not rotating and no angle set, just draw
        elseif m.ang==nil then m:draw()
        --otherwise move to centre of our image, rotate, and draw

Calculating the rotation angle

I’ve commented the code, but I’d better explain how I calculate the angle. If you don’t care, skip this bit.

The diagram below shows our current position at lower left, at (x1,-z1), and our tree at upper right, at (x2,-z2). The angle a, marked with a red stripe, is the angle between us. We need to rotate the tree by this angle, so that it faces us.

But what is the angle?

You can figure out that the top angle (next to x2,y2) also has to be “a”. We also know the difference in x and z values between our current position and the tree, and I’ve marked them on the triangle above. So what we need is some formula that connects the angle “a” with the known x and z values.

Now, tan(angle)=opposite side / nearest side, ie
tan(a) = (x2-x1) / (z1-z2)

So we can solve a = atan((x2-x1) / ( z1-z2))
and that is what the code does above.

The code I’ve provided shows 2 trees, one of them rotating to face you, and a dog that also rotates.

Images that block each other

If you walk around and look at the tree images from different angles, you will see they sometimes block each other – the tree in front should be transparent except for the actual tree, but it seems to have a solid background that blocks stuff behind it.

Andrew Stacey explained this well in a forum post recently:

When OpenGL (in the guise of Codea) draws something on the screen, it saves the z-coordinate of the pixel. Then when a new thing is to be drawn, the new z-coordinate is compared with the saved one. If the new one is lower than the saved one, the assumption is that the new information is behind the existing one and thus occluded. It is therefore not drawn. Thus the actual order of drawing doesn’t matter: new stuff is only drawn if it is in front of the stuff already there.

Where this breaks down is with transparency. The system takes no notice of the transparency of the objects (for efficiency reasons). So if the new object is behind the current front object, but that current front object is transparent, it still doesn’t get drawn.

So your cube sides are transparent. It’s just that there’s nothing being drawn behind them to show that transparency because the stuff that ought to be behind is being drawn afterwards and therefore not drawn.

The solution to this is to sort the z-orders before drawing the meshes. This can get complicated when there are lots of meshes to do, but shouldn’t be hard with the code you have. Note that you only need to do this for the transparent parts of the image.

This is a real problem in 3D drawing. As Andrew says, the best solution is to sort your meshes so you draw them from furthest to nearest, so you are always drawing on top of the existing picture. I’ll cover this in the next post.

Cutting the calculation load

I have hundreds of rotating images in my 3D scene, and that is going to hit your draw speed. So I figured that because you don’t move far from one redraw to the next, you don’t need to recalculate on every redraw. So what I do in my project is recalculate every 20 redraws, storing the angle, and I use that stored angle for the next 19 redraws, then calculate and store it again. So I basically update rotation 2-3 times a second. I haven’t put that into this code, at this stage.

The code is here. Note that it starts by downloading the dog, tree and wall images, using the technique described in the previous post.

From → 3D

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: