Ooo, an accidental play on words. We’re going to change our cube’s direction, but also the direction of these articles.

My original model for these tutorials was to get close to the standard “cookbook” form, where the author lays out solutions that have been prediscovered and premasticated and premeditated, so they’re basically tell ‘em what you’re gonna tell ‘em, tell ‘em, then tell ‘em what ya told ‘em. I had in mind that I might present more than one program that did a particular thing, which I would probably have obtained by refactoring a flat design into a more structured one, but that it would all be presented as a fait accompli1.

I could still do that but it would take two or three or more days of discovery and search and asking questions on the forum and, generally, learning about Craft, before I could write the little section of tutorial that would hide all that learning and just provide the cookbook answer as if I had known it all along, and as if that’s all the reader needs to know.

That just doesn’t work for me. So I’m going back to my more common writing style, which is to start somewhere, code to somewhere else, then review what I’ve done and see whether there’s improvement to be had. I hope that works for you, all three of you who read these articles.

Thanks for watching me do my hobby.

Today, It Moves!

The previous tutorial rezzes a cube, which drops to a floor, and bounces around a bit. Today, we’re going to see whether we can influence what our cube does. I happen to know that we can. And I happen to know that there are issues as we’ll soon see.

Codea Craft physics includes two methods that can be used to influence what a rigidbody does, documented here. In essence, it goes like this:

  • applyForce – applies a vector representing force components to be applied to the body in question. Force will be applied at the center of the object, or optionally at a specific “world point”. This will tend to move the object in the direction indicated.
  • applyTorque - applies torque to the three axes of a provided force vector. This will tend to rotate the object around the net axis defined by the vector.

We’ll try both of these. We’ll start a duplicate with my current copy of CoCraTu-005, named CoCraTu-006. Here’s the starting program:

-- CoCraTu-006

function setup()
    scene = craft.scene()
    --scene.physics.gravity = vec3(0,0,0)
    createFloor()
    createBox()
    scene.camera:add(OrbitViewer, vec3(0,0,0), 20, 1, 20)
    angle = 0
end

function update(dt)
    scene:update(dt)
end

-- Called automatically by codea 
function draw()
    update(DeltaTime)
    scene:draw()	
end

function createBox()
    local box = scene:entity()
    local body = box:add(craft.rigidbody, DYNAMIC, 1) -- mass
    body.restitution = 0.8
    box:add(craft.shape.box, vec3(1,1,1))
    box.model = craft. model.cube(vec3(1,1,1))
    box.material = craft.material(asset.builtin.Materials.Specular)
    box.material.map = readImage(asset.builtin.Blocks.Missing)
    -- DELETE box.eulerAngles = vec3(math.random(10) - 5, math.random(10) - 5, math.random(10) - 5)
    box.y = 5
    return box
end

function createFloor()
    local floor = scene:entity()
    local body = floor:add(craft.rigidbody, STATIC)
    body.restitution = 0.9
    floor:add(craft.shape.box, vec3(25, 0.01, 25))
    floor.model = craft.model.cube(vec3(25, 0.1, 25))
    floor.y = -1.05
    floor.material = craft.material(asset.builtin.Materials.Specular)
    floor.material.map = readImage(asset.builtin.Blocks.Brick_Grey)
    floor.material.offsetRepeat = vec4(0,0,25,25)
    return floor
end

I’ve turned off the initial random rotation that I had, because I want the falling object to start out dead flat. We’ll leave the restitution in so that it will bounce. It starts like this:

bounce

We need access to the rigidbody of our cube, to apply our force and torque, so we’ll just set a global for learning purposes:

function createBox()
    local box = scene:entity()
    BoxBody = box:add(craft.rigidbody, DYNAMIC, 1) -- mass
    BoxBody.restitution = 0.8
    box:add(craft.shape.box, vec3(1,1,1))
    box.model = craft. model.cube(vec3(1,1,1))
    box.material = craft.material(asset.builtin.Materials.Specular)
    box.material.map = readImage(asset.builtin.Blocks.Missing)
    box.y = 5
    return box
end

And let’s add a parameter:

function setup()
    scene = craft.scene()
    --scene.physics.gravity = vec3(0,0,0)
    createFloor()
    createBox()
    scene.camera:add(OrbitViewer, vec3(0,0,0), 20, 1, 20)
    angle = 0
    parameter.action("Twist", twist)
end

function twist()
    BoxBody:applyTorque(vec3(0,40,0))
end

The value 40 was picked by increasing the value until the box did what I wanted. There may be some more intelligent way to do it, involving moments or inertia or something.

Now we can tap the Twist button and the cube will rotate around its y (vertical) axis:

twist

Did you notice that after the box stopped bouncing, it was no longer responding to my taps? This confused me for a very long time, until I realized that a rigidbody can apparently go to sleep. I learned how to display its status, so let’s do that:

function draw()
    update(DeltaTime)
    scene:draw()	
    if BoxBody.awake then
        text("awake", 400,100)
    else
        text("asleep", 400,100)
    end
end

Now we can see that after the box stops bouncing, it falls asleep. After that, it does not respond to Twist.

sleep

There’s also a flag–you’ll love this–named sleepingAllowed. If we set it to false, we can find out that the object does not go to sleep. However, our display code now shows it as asleep all the time! Something is rotten here.

function createBox()
    local box = scene:entity()
    BoxBody = box:add(craft.rigidbody, DYNAMIC, 1) -- mass
    BoxBody.restitution = 0.8
    BoxBody.sleepingAllowed = false
    box:add(craft.shape.box, vec3(1,1,1))
    box.model = craft. model.cube(vec3(1,1,1))
    box.material = craft.material(asset.builtin.Materials.Specular)
    box.material.map = readImage(asset.builtin.Blocks.Missing)
    box.y = 5
    return box
end

asleep

Bizarre. We can tap our button and the cube usually moves, even though the awake flag now signals asleep. I suspect that there’s some kind of bug, and I’ve filed a report.

Meanwhile we can also try applying a force to see what happens:

function setup()
    scene = craft.scene()
    --scene.physics.gravity = vec3(0,0,0)
    createFloor()
    createBox()
    scene.camera:add(OrbitViewer, vec3(0,0,0), 20, 1, 20)
    angle = 0
    parameter.action("Twist", twist)
    parameter.action("Force", force)
end

function force()
    BoxBody:applyForce(vec3(0,800,0))
end

function twist()
    BoxBody:applyTorque(vec3(0,40,0))
end

Now, tapping the Force button tends to throw the cube upward. The effect seems a bit random, but of course the resultant motion is a function of both our upward force, and the other motion of the cube. upward

To really understand what’s going on here, we need to know the time interval over which the force or torque are applied, since the change in velocity is proportional to the time the force is applied. I’ve inquired of the Codea powers that be, and will report here what I find out.

For now, we’ve learned a bit, and we’ll think about how to “apply” this learning in upcoming sections. Let’s sum up.

Summary

Codea Craft allows us to applyForce or applyTorque to a rigidbody, and the physics engine changes that object’s motion as a function of the force applied and the residual motion of the object. The effect appears to be somewhat random: we need information that does not seem to be available yet.

Craft also provides the sleepingAllowed flag, and the awake flag on rigidbody. It appears that setting

body.sleepingAllowed = false

will cause the body never to sleep–but to report always that it is asleep. I feel pretty sure that that’s a bug.

It’s also apparently the case that when a body is asleep, applying force or torque will not wake it up. I’ve reported a bug on that as well.

We’ll see in future sessions what use we can make of these functions. I think they’ll be useful even if they are a bit random.

See you then, I hope!



  1. Not to be confused with the early sixties Fiat Accompli, which was not well thought of in the automotive market.