In this tutorial, we’ll make our cube move around the 3D volume a bit, then improve how it looks. And we’ll improve how we look at it.

For today’s work, I’m going to duplicate yesterday’s project, CoCraTu-001 and rename it to CoCraTu-002. I want to keep each project separate for my purposes. You might prefer to do that. An even better idea would be to use a Git app, such as the excellent Working Copy, to maintain a historical repository of your source code. Details on that are beyond today’s scope, but may be found in a future chapter or appendix.

Our starting project looks like this:

function setup()
    -- Create a new craft scene
    scene = craft.scene()

    -- Create a new entity
    Cube = scene:entity()
    Cube.model = craft.model.cube(vec3(1,1,1))
    Cube.material = craft.material(asset.builtin.Materials.Basic)
    Cube.material.map = readImage(asset.builtin.Blocks.Missing)
    Cube.eulerAngles = vec3(0,0,0) 
    scene.camera.z = -4
    angle = 0
end

function update(dt)
    -- Update the scene (physics, transforms etc)
    angle = angle + 1
    Cube.eulerAngles = vec3(angle/10, angle, angle/5)
    scene:update(dt)
end

-- Called automatically by codea 
function draw()
    update(DeltaTime)

    -- Draw the scene
    scene:draw()	
end

We can change the position of an entity in 3-space by changing its ‘x’, ‘y’, and ‘z’ coordinates. We want our cube to move back and forth, toward us and away. That is motion in the Codea Craft Z direction.

Change your update to look like this:

function update(dt)
    -- Update the scene (physics, transforms etc)
    angle = angle + 1
    Cube.eulerAngles = vec3(angle/10, angle, angle/5)
    local sine = math.sin(math.rad(angle))
    Cube.z = 2*sine
    scene:update(dt)
end

Run the program and see the cube move back and forth:

sine motion

Math nerds will know that given an angle, the sine of the angle varies smoothly between -1 and 1, starting at zero when the angle is zero, and since we multiplied by 2, our cube moves from -2 (close to us) to 2 (far away).

In our code, we used math.rad to convert our angle, which is in degrees, to radians, which Codea’s math functions prefer. The difference between degrees and radians and which we use for which function, will probably trouble you in the future. I can only caution you to be careful and read the documentation.

The cosine function also varies between -1 and 1, and it is 90 degrees out of phase. What will happen if we adjust x by the cosine? Try it:

circular motion

The box moves in a circle! We have discovered the polar form of a circle, math nerds. Everyone else: if we set x to the cosine of an angle and z to the sine, the object will move in a circle. We could make ovals by adjusting the multipliers.

In general, to move an object, we change its x, y, and z coordinates, to make it do what we want. We’ll do more of that in later chapters.

Better Than a Cube

We could build anything we want out of cubes and spheres and such, and we may see that in future tutorials, especially when we get to ‘voxels`, which are to volume what pixels are to flat pictures. But Codea also provides a large number of objects that are pre-made, which can be freely used to create games or other projects.

Change this line:

    Cube.model = craft.model.cube(vec3(1,1,1))

To this:

    Cube.model = craft.model()

And touch the parentheses. Navigate to Blocky_Characters and pick one. I picked this one:

    Cube.model = craft.model(asset.builtin.Blocky_Characters.Adventurer)

Remove the two lines below that one:

    Cube.material = craft.material(asset.builtin.Materials.Basic)
    Cube.material.map = readImage(asset.builtin.Blocks.Missing)

Insert in their place:

    Cube.scale = vec3(1,1,1)/8

Setup should be:

function setup()
    -- Create a new craft scene
    scene = craft.scene()

    -- Create a new entity
    Cube = scene:entity()
    Cube.model = craft.model(asset.builtin.Blocky_Characters.Adventurer)
    Cube.scale = vec3(1,1,1)/8
    Cube.eulerAngles = vec3(0,0,0) 
    scene.camera.z = -4
    angle = 0
end

Run the program. You should see the adventurer tumbling through space.

tumble

It’s worth noting that call to scale. You probably guessed that that makes our entity 1/8 its original size. You’ll often find that the models you import into Codea Craft are not the size you want. The Blocky Characters are quite large. I ran the program once to see how it looked and then scaled him down to a size that seems good. There is often this cut-and-try aspect to modeling.

Make him stop tumbling. How? Stop changing his eulerAngles. Delete:

    Cube.eulerAngles = vec3(angle/10, angle, angle/5)

Run the program. Now he’ll move in a circle.

We have two issues (at least). He seems to be above us a bit. His feet seem to be at our view level. We can move our camera upward, or move him downward. And he’s always facing away from us, instead of turning as he moves. He’d look more natural if he were facing the way he’s moving.

We fix the height issue by moving him down in setup:

function setup()
    -- Create a new craft scene
    scene = craft.scene()

    -- Create a new entity
    Cube = scene:entity()
    Cube.model = craft.model(asset.builtin.Blocky_Characters.Adventurer)
    Cube.scale = vec3(1,1,1)/8
    Cube.y = -1
    Cube.eulerAngles = vec3(0,0,0) 
    scene.camera.z = -4
    angle = 0
end

The Cube.y change moves him down one unit. That looks just right.

We already know how to rotate an entity: set its eulerAngles. We want him standing up, so we’ll only want to change the Y component.

Add one line to update:

function update(dt)
    -- Update the scene (physics, transforms etc)
    angle = angle + 1
    local sine = math.sin(math.rad(angle))
    Cube.z = 2*sine
    Cube.x = 2*math.cos(math.rad(angle))
    Cube.eulerAngles = vec3(0, -angle, 0)
    scene:update(dt)
end

How did we know that the right value was -angle? To tell the truth, I cut and tried. My first guess was angle, based on this diagram in my head:

sticky

But that made him turn the wrong way, so I negated the angle. The “root cause” of that is that I’m using x and z backward from the usual usage, where x is sin(angle) and y is cos(angle). Let’s change that.

function update(dt)
    -- Update the scene (physics, transforms etc)
    angle = angle + 1
    Cube.x = 2*math.sin(math.rad(angle))
    Cube.z = 2*math.cos(math.rad(angle))
    Cube.eulerAngles = vec3(0, angle + 90, 0)
    scene:update(dt)
end

We had to change the angle to positive, but had to add 90 degrees to it as well. In general, the objects we’ll find in Codea are oriented in surprising ways. Expect to have to discover adjustments like these.

Now our Adventurer moves counter-clockwise, facing the direction he’s moving.

The speed he moves may be faster or slower than you’d like, depending on how fast your device is. That’s why the update method is given DeltaTime as its input, which is the time elapsed, in seconds, since the prior update.

Suppose we would like our Adventurer to go around his circle in five seconds. Then his angle should change by 360/5 degrees per second, or 72 degrees per second, if my arithmetic is right. So instead of adjusting angle by 1, we can adjust it by dt*360/5:

function update(dt)
    -- Update the scene (physics, transforms etc)
    local degreesPerSecond = 360/5
    angle = angle + dt*degreesPerSecond
    Cube.x = 2*math.sin(math.rad(angle))
    Cube.z = 2*math.cos(math.rad(angle))
    Cube.eulerAngles = vec3(0, angle + 90, 0)
    scene:update(dt)
end

Now his trip will take five seconds on any device.

A Better Camera

The camera through which we are viewing our adventurer is in a fixed position, (0,0,-4). It will come as no surprise that we can change its position and the direction it looks by adjusting its x,y,z and eulerAngles. That’s tedious, though not difficult. It’s time to introduce a more powerful camera that comes in the Codea “Cameras” project: the OrbitViewer.

Add a dependency on “Cameras” by tapping the do button and selecting cameras:

cameras selection

Now change setup:

function setup()
    -- Create a new craft scene
    scene = craft.scene()

    -- Create a new entity
    Cube = scene:entity()
    Cube.model = craft.model(asset.builtin.Blocky_Characters.Adventurer)
    Cube.scale = vec3(1,1,1)/8
    Cube.y = -1
    Cube.eulerAngles = vec3(0,0,0) 
    scene.camera:add(OrbitViewer, vec3(0,0,0), 5, 10, 20)
    angle = 0
end

We removed our setting of scene.camera.z and replaced it with a call to OrbitViewer. The vector provided is the target at which the camera should look. In your copious free time, you might want to study the code in Cameras. You’ll see that it is fairly intricate, because of what we can do with it, which is to scroll and zoom all around our objects. Use one finger to rotate, two to scroll from side to side, and pinch with two to zoom.

When you lose the adventurer, double tap with one finger and the picture will re-center.

Using OrbitViewer we can inspect our 3D spaces from any viewpoint. We’ll use it commonly in our further chapters.

A Floor

Our adventurer is a bit hard to watch, since he seems to be walking in space. Let’s give him a floor. We just need another entity:

function setup()
    -- Create a new craft scene
    scene = craft.scene()

    -- Create a new entity
    Cube = scene:entity()
    Cube.model = craft.model(asset.builtin.Blocky_Characters.Adventurer)
    Cube.scale = vec3(1,1,1)/8
    Cube.y = -1
    Cube.eulerAngles = vec3(0,0,0) 
    floor = scene:entity()
    floor.model = craft.model.cube(vec3(6, 0.1, 6))
    floor.y = -1.05
    floor.material = craft.material(asset.builtin.Materials.Specular)
    floor.material.map = readImage(asset.builtin.Blocks.Dirt)
    scene.camera:add(OrbitViewer, vec3(0,0,0), 5, 10, 20)
    angle = 0
end

We created a new entity, floor, gave it a cube model like we had for the old adventurer, and positioned it lower than the adventurer by half its thickness (0.1 -> 0.05) so that his feet just touch the floor.

Now the adventurer walks in a circle on the floor and we can view him, and the floor, from any angle we desire.

adventurer on floor

Did you notice the shiny aspect of the floor as I rotated around? That’s because I used the Specular material, which is quite shiny–and there is light in the scene, coming from a standard light source. Naturally we can change that source and even add other ones.

But that’s for another day. Let’s clean up this code:

-- CoCraTu-002

function setup()
    -- Create a new craft scene
    scene = craft.scene()
    
    -- Create adventurer and floor
    Adventurer = createAdventurer()
    createFloor()
    scene.camera:add(OrbitViewer, vec3(0,0,0), 5, 10, 20)
    angle = 0
end

function update(dt)
    -- Update the scene (physics, transforms etc)
    local degreesPerSecond = 360/5
    angle = angle + dt*degreesPerSecond
    Adventurer.x = 2*math.sin(math.rad(angle))
    Adventurer.z = 2*math.cos(math.rad(angle))
    Adventurer.eulerAngles = vec3(0, angle + 90, 0)
    scene:update(dt)
end

-- Called automatically by codea 
function draw()
    update(DeltaTime)

    -- Draw the scene
    scene:draw()	
end

function createAdventurer()
    local adventurer = scene:entity()
    adventurer.model = craft.model(asset.builtin.Blocky_Characters.Adventurer)
    adventurer.scale = vec3(1,1,1)/8
    adventurer.y = -1
    adventurer.eulerAngles = vec3(0,0,0) 
    return adventurer
end

function createFloor()
    local floor = scene:entity()
    floor.model = craft.model.cube(vec3(6, 0.1, 6))
    floor.y = -1.05
    floor.material = craft.material(asset.builtin.Materials.Specular)
    floor.material.map = readImage(asset.builtin.Blocks.Dirt)
    return floor
end

Rather than write all that stuff in a long column of statements, we put it in a couple of functions, one which creates an adventurer and returns it, and one which creates the floor and returns it. We don’t need to update the floor, so we don’t save it up in setup, but we do save the Adventurer.

Looking forward, we’ll see that these tutorials will move from a collection of functions like createAdventurer and createFloor to creating small Codea objects to manage our entities. That will come in time.

For today, we have an adventurer moving in a circle, on a floor, and we can look at him from any viewpoint.

Reading

If you’re interested in the sines and cosines, try your own search or perhaps Sine Wave and Equation of Circle.

Challenge

Can you add another entity to the picture, moving differently? Do you find commonality between the Adventurer code and your new entity? Can you combine the common elements to remove the duplication? Are you ready to try a class for those two entities?

Next Time

I don’t know yet. Read on and find out.