CoCraTu 7: An impulse strikes. Sometimes.
An experiment by Dave1707 inspires Ron to figure out what’s really going on with applyForce. Will he succeed? The answer may not surprise you.
Dave1707 on the Codea forum did an experiment that bears on our work yesterday, with applyForce
. I won’t duplicate that here, but the thread is here.
The concern we have is that the applyForce
function clearly doesn’t just turn on a force like a rocket and leave it on. If it did, the object affected would accelerate away without bound. Instead, the function is an “impulse”, a force applied for a period of time … and they don’t tell us what the time period is.
Dave’s experiment seems to indicate that the interval might be one draw cycle. We’ll work with Codea a bit and see what we can learn.
Let me just lay this out there. I’ve been working on it for a while to get something that sort of works. I’ll explain below.
First Cut
-- CoCraTu-007
function setup()
scene = craft.scene()
depth = -10
scene.physics.gravity = vec3(0,0,0)
ball = scene:entity()
BallBody = ball:add(craft.rigidbody, DYNAMIC, 1) -- mass 1
BallBody.sleepingAllowed = false
ball.position = vec3(0,depth,0)
ball:add(craft.shape.sphere, 1)
ball.model = craft.model.icosphere(1)
ball.material = craft.material(asset.builtin.Materials.Standard)
ball.material.diffuse = color(255,0,0)
reporter = ball:add(Reporter)
scene.camera:add(OrbitViewer, vec3(0,0,0), 60, 0, 1000)
end
function update(dt)
scene.debug:line(vec3(-10,depth-1,0), vec3(10,depth-1,0), color(255))
scene:update(dt)
end
function draw()
update(DeltaTime)
scene:draw()
reporter:draw()
end
function touched(touch)
if touch.state == BEGAN then
BallBody:applyForce(vec3(0,10,0))
print("force applied")
end
end
Reporter = class()
function Reporter:init(entity)
self.entity = entity
self.lastDrawTime = ElapsedTime
self.lastFixTime = ElapsedTime
self.deltaDrawTime = 0
self.deltaFixTime = 0
end
function Reporter:update()
self.deltaDrawTime = ElapsedTime - self.lastDrawTime
self.lastDrawTime = ElapsedTime
end
function Reporter:draw()
pushStyle()
textAlign(LEFT)
textMode(LEFT)
fill(255)
text("Draw Time "..self.deltaDrawTime, 400, 100)
text("Vel "..BallBody.linearVelocity.y, 400, 80)
popStyle()
end
This program displays a ball, a line under the ball, and two text messages. The first message, “Draw Time” is the elapsed time since the last draw/update cycle. The Vel message is the current y velocity of the ball.
The ball is set up for physics, with a rigidbody
as well as a visible ball for us humans. And it has a Reporter instance attached, which collects the elapsed time and draws it. That’s more than is needed right now. I was trying to collect physics cycle time too, but if Codea is actually providing it, I haven’t figured out how.
When I touch the screen, it prints “force applied” to the console and applies a vertical force impulse of 10 (units, newtons, whatever you want to call them). The force, we assume causes the ball to move. Since gravity is turned off and the ball isn’t touching anything, once started in motion, it will continue. Looking at the speed it’s going should give us information about the duration of the impulse.
Spoiler: The duration of the impulse appears to be DeltaTime, the duration of the update cycle.
One more thing, this line:
scene.debug:line(vec3(-10,depth-1,0), vec3(10,depth-1,0), color(255))
I’ve not found any documentation for what scene.debug can do, but one thing it can do is draw a line in 3D coordinates, rather than screen coordinates, so the line is actually right under the ball’s starting location.
The ball has radius 1, and is at y = -10, so y = -11 is right at the bottom of the ball.
I’ll run the program and show what we see.
This time, one touch makes the ball rise at a speed of 0.1666, which is enough to move a distance of 1 in six seconds. Since the force I’m applying is 10. We can see that the impulse duration was 1/60th of a second.
This surprises me, and it isn’t what I see every time. The draw cycle time shown is 0.008333, or 1/120th of a second.
I want to change how this works, so that the force is applied during an update cycle, not asynchronously.
Here’s the next version:
Second Cut
-- CoCraTu-007
function setup()
scene = craft.scene()
depth = -10
scene.physics.gravity = vec3(0,0,0)
ball = scene:entity()
BallBody = ball:add(craft.rigidbody, DYNAMIC, 1) -- mass 1
BallBody.sleepingAllowed = false
ball.position = vec3(0,depth,0)
ball:add(craft.shape.sphere, 1)
ball.model = craft.model.icosphere(1)
ball.material = craft.material(asset.builtin.Materials.Standard)
ball.material.diffuse = color(255,0,0)
reporter = ball:add(Reporter)
scene.camera:add(OrbitViewer, vec3(0,0,0), 60, 0, 1000)
apply = false
end
function update(dt)
scene.debug:line(vec3(-10,depth-1,0), vec3(10,depth-1,0), color(255))
scene:update(dt)
end
function draw()
update(DeltaTime)
scene:draw()
reporter:draw()
end
function touched(touch)
if touch.state == BEGAN then
apply = true
print("force applied")
end
end
Reporter = class()
function Reporter:init(entity)
self.entity = entity
self.lastDrawTime = ElapsedTime
self.lastFixTime = ElapsedTime
self.deltaDrawTime = 0
self.deltaFixTime = 0
self.applyDuration = 0
self.maxDuration = 0
end
function Reporter:update()
self.deltaDrawTime = ElapsedTime - self.lastDrawTime
self.lastDrawTime = ElapsedTime
if self.deltaDrawTime > self.maxDuration then
self.maxDuration = self.deltaDrawTime
end
if apply then
self.applyDuration = DeltaTime
self.maxDuration = 0
BallBody:applyForce(vec3(0,10,0))
apply = false
end
end
function Reporter:draw()
pushStyle()
textAlign(LEFT)
textMode(LEFT)
fill(255)
text("Draw Time "..self.deltaDrawTime, 400, 100)
text("Vel "..BallBody.linearVelocity.y, 400, 80)
text("Duration "..self.applyDuration, 400, 60)
text("Max Duration "..self.maxDuration, 400, 40)
popStyle()
end
I’m now setting the apply
flag on touch, then if it is set, in update, I apply the force (still 10), clear the flag, and clear maxDuration
, which is now displayed.
I did that because DeltaTime
is the time since the last update, and I wanted to see the time until the next update, thinking that that’s more likely to be the duration of an impulse: from now until next time around.
Be that as it may, I often see a velocity of 0.16, sometimes see 0.083. So the impulse duration in this test seems to be either 1/120th or 1/60th, even though my draw time seems constant at 0.0083.
Nothing new to see here.
What Have We Learned?
Not as much as we might like. It seems clear that the impulse time changes. On both my iPads, it appears that it can be zero, because sometimes it displays that it applied force but the ball does not gain any velocity. Other times it gets 1/120th of a second’s worth of force, usually it gets 1/60th of a second’s worth.
So I think we have gained a partial understanding, which is that the duration of force applied seems to be a lot like the time between draw updates. But it isn’t consistent. There are even cases where an applyForce
has no effect at all.
Without a better understanding of what the rules of physics are, we’ll have difficulty creating programs that use forces to move things around. We would at least want to be certain that if you tapped a button, some consistent force would be applied.
There may be ways around it. We could perhaps rig up something that looks for a change in velocity, equal, say, to applying the force for one second, and keep applying it until we get that amount of change.
We might try that in a future section. Today, I think the answer is “the bear is random and may or may not bite you”.