Codea Craft 6: What shall we do now?
I’ve made a couple of objects move, in two different ways. I figure that topic is spiked. What should I look at next? This session is gonna be rambling, I’ll wager.
Wednesday
I do have one idea of something to do, but I’m not sure it’s the next step. The idea is, make some object (pretend it’s a bird) circle around the Orc’s head, or around the ship. The way to do that, I believe from my reading, is to make t a child of the thing it circles, and compute its relative position. So, since we know how to do a circle, it should be easy. (Hold my beer.)
There are some other capabilities in the entity
that I wonder about, though, notably the stuff about transformations. Each entity has a transformation matrix, I gather. I’d expect such a thing to be able to move, resize, and rotate the object. This would be a good thing to understand.
So my first effort today will be to look a bit at the documentation, and to see whether I can find any examples of interesting entity behavior, either in the docs, in the examples, or maybe on the Codea forum.
I’ll get back to you in … five minutes … with this short report.
Apparently the transform is just something that the entity “knows”. You can give it a point or direction to transform (from local to world, or world to local). I’m not sure just what that means, but I reckon it won’t be too hard to find out.
My hypothesis is that there’s an internal transform that is “summing up” the various moves and rotations we apply, so that the object can be drawn in the right place and orientation. So if we want to know where the object is, for example, we could ask to have <0,0,0> transformed. And if we wanted to know where the prow of the ship was, we could ask for <0,1,0>, assuming that the ship is Y forward, which I suspect it is.
I think I’ll test that. I could do that in a copy of the program (because I don’t want to ruin this one), or I could break down and put this one under version control. That would be better, but I’d have to figure out (again) how to do it. Probably worth doing.
I’ll get back to you in … four minutes … to report that I
- Started Working Copy
- Created a new repo named Orc-1
- Imported the codea project into it
- observed it had all the tabs
- made the initial commit
Now, if things go as I remember, when I make changes in Codea, Working Copy will notice them and be prepared to commit them, revert them, and so on. If things go as I remember.
As a first experiment, I think I’ll try transforming a zero vector with the ship entity, and displaying the result on screen. I sort of expect it to look just like the coordinates of the ship.
Success! I added a display to draw:
-- Called automatically by codea
function draw()
update(DeltaTime, Scene)
Scene:draw()
-- transform playground
local tvec = Ship:transformPoint(vec3(0,0,0))
local tstr = string.format("tvec is %f %f %f", tvec.x, tvec.y, tvec.z)
text(tstr, 500, 500)
end
And sure enough, that vector is the current coordinates of the ship, as shown in this picture, taken near the 90 degree point:
So that tells me – I could be wrong but I’d bet not – that we could find out where the ship is relative to the Orc, by using ‘inverseTransformPoint’, and that we could compute a move by taking a proposed step forward and transforming it with transformDirection
and then moving by what came back.
In our present case, we are computing the position from a control variable theta
, which is the angle around the circle that we are tracking. If we had a wandering object like my Braitenberg bugs, we might use this capability.
I’ll take a glance at Working Copy to see if it has kept up … and it has not. Apparently just importing the code isn’t the right trick. I’d better relearn how to use Working Copy before going any further.
Off to read. Back in … 38 minutes … took several tries.
Setting Up Working Copy
I tried a number of things that didn’t seem to work. By then I was confused. So I fumbled around and read things for over a half an hour.
What works is to click Working Copy’s + to add a repo, and then pick select sync folder. I drilled into “On MyiPad / Codea”, which is where my Codea files are, and Lo! it imported all the projects, 889 files worth. I figured in for a penny and committed all of them.
Then I made a simple change to Orc, adding a comment, and sure enough Working Copy saw the change. So I committed that, then put the point tracking back, made sure that worked in Codea, and that, too, showed up in Working Copy, so I committed again.
It seems to me that now, any project I create should automatically show up in Working Copy, at least in the On My IPad / Codea folder. We’ll see, going forward, if that’s the case. Meanwhile, in my copious free time, I’ll look for some documentation. And I posted a note on the Codea forum as well.
Anyway, that’s what we learned about Working Copy today.
Back to Orcs
I think I have a mental handle on the entity transform. It’s an apparently secret matrix that can be used to convert a point or direction from local to world coordinates or back. I’m not sure what that’s good for, but I’m glad to know it.
In order to test the transform, I had to set up a global Ship variable so that the top-level draw function could access it. I tried calling text
during update, but it had no effect. That isn’t much of a surprise. I don’t know whether scene:draw
calls draw on our class. I’ll give it a draw method to find out.
function Orc:draw()
text("HELLO ORC!", 700,700)
end
Nothing happens, so I guess not. Just for fun, I’ll revert that change from Working Copy. That seems to have worked. I had to click off the Orc tab in Codea to pick up the change. That seems OK. And I didn’t wait very long so it might have done the right thing anyway.
Time out
Well, my two hours are up. I’ve learned two things at least:
- How to set up and use Working Copy (highly valuable)
- What the transform methods are in
entity
(probably useful)
Not much, but that’s the reality. of trying to learn things, sometimes it goes slowly.
I can think of a couple of cute things to try, but while I’m away I think I’ll review the Codea Craft documents a bit and see whether we can come up with some kind of a game or something that will use more of its capability.
See you next time … both of you! :)
Thursday
This morning, I plan to make an object fly around the ship, as the ship sails around the rather wooden-looking sea. I plan to make the object a child of the ship, so that its moves will be done in ship-local coordinates. We’ll see how that goes.
I plan to proceed in roughly these steps:
- Make some object, probably a sphere.
- Make it appear somewhere near the ship, perhaps a bit above it.
- Rotate it simply around the ship, much as the ship rotates around the center of the scene.
- Experiment with the sphere’s path, just to make it interesting.
- Maybe … import something more interesting that a sphere, but I’ll probably leave that for another day.
I expect this to go easily. I’m probably mistaken. Here goes, starting up Working Copy and Codea.
I’ll be back in … twenty minutes. I found an example on setting a parent, and another example that suggested how to display a capsule. Here’s the code, and the result:
function setup()
Scene = craft.scene()
setupSky(Scene)
setupOrc(Scene)
local ship = setupWatercraft(Scene)
setupFollower(Scene, ship)
setupCamera(Scene)
end
function setupFollower(scene, shipEntity)
local f = scene:entity()
local cap = f:add(craft.renderer, craft.model("Primitives:Capsule"))
f.material = craft.material("Materials:Standard")
f.parent = shipEntity
f.position = vec3(0,2,0)
end
function setupWatercraft(scene)
local entity = scene:entity()
entity:add(Watercraft)
return entity
end
Note that I had to adjust setupWatercraft
to return the ship entity so that we could use it as our parent.
The capsule only moved a tiny way up, and I think I know the reason why (ish). The ship, I think, is scaled down and certainly the local coordinates are going to be relative to the ship scale. Let’s look at the ship setup and see what we can figure out.
function Watercraft:init(entity)
Ship = entity -- darn
self.entity = entity
self.radiusVector = vec2(1.5,0)
self.theta = 0
self.deltaTheta = 0.01 -- random guess
entity.model = craft.model("Watercraft:watercraftPack_003")
entity.position = vec3(0,-1,0)
entity.scale = vec3(1,1,1) / 8
entity.eulerAngles = vec3(0, 180, 0)
end
There we go, the ship is scaled down by a factor of 8 in all dimensions. So, therefore, is my positioning of the capsule. I’ll try a few values and see what I like.
Ten looks good:
Now to give it an update and make it circle around. I can rip off the code that moves the ship as a basis for this. After a bit of thought, I decide not to create a new class at this point, instead doing the update at the same time as the ship. This is a bit of a hack but should be OK as a start.
As a first attempt, to be sure I was hooked up, I did this:
function Watercraft:update(dt)
local pos2d = self.radiusVector:rotate(self.theta)
self.theta = self.theta + self.deltaTheta
self.entity.x = pos2d.x
self.entity.z = pos2d.y
self.entity.eulerAngles = vec3(0, math.deg(-self.theta), 0)
local cap = self.entity.children[1]
cap.position = vec3(5,10,0)
end
If this worked, it should move the capsule outboard a bit, and it does:
Now to move it. I think I’ll use elapsed time to get the angle to use, and work from there:
function Watercraft:update(dt)
local pos2d = self.radiusVector:rotate(self.theta)
self.theta = self.theta + self.deltaTheta
self.entity.x = pos2d.x
self.entity.z = pos2d.y
self.entity.eulerAngles = vec3(0, math.deg(-self.theta), 0)
local cap = self.entity.children[1]
local angle = ElapsedTime
local newVec = vec2(5,0):rotate(angle)
cap.x = newVec.x
cap.z = newVec.y
end
I tweaked a multiplier on ElapsedTime
around, and decided to use it as is. And again I confused y and z, at first setting cap.y
from newVec.y
. The capsule rotated around the ship around its forward axis, that is, going under “water”. Then I wrote the obvious:
cap.z = newVec.z
Which gave a really obscure error about an operator ? and a nil. Of course there is no newVec.z
, so I fixed that.
This repeated error is coming up again and again. I have put in an order with Amazon for more cleverness, but it is slow in arriving. I need to figure out a better way to manipulate these things that makes my mistake more nearly impossible.
I’ll have to think about that.
Now for the fancy bit, item 4, experimenting with the path. I plan to add two sinusoidal components to it, one in x and one in z. Yes, dammit, this time I really mean z. I’ll use different elapsed time multipliers to make the capsule’s path seem more random.
And here we are:
function Watercraft:update(dt)
local pos2d = self.radiusVector:rotate(self.theta)
self.theta = self.theta + self.deltaTheta
self.entity.x = pos2d.x
self.entity.z = pos2d.y
self.entity.eulerAngles = vec3(0, math.deg(-self.theta), 0)
-- flying capsule logic
local cap = self.entity.children[1]
local angle = ElapsedTime
local xAngle = ElapsedTime*1.2
local xOffset = math.sin(xAngle)*4
local yAngle = ElapsedTime*0.4
local yOffset = math.sin(yAngle)*2
local newVec = vec2(5,0):rotate(angle)
cap.x = newVec.x + xOffset
cap.y = 10+yOffset
cap.z = newVec.y
end
I made the Orc inactive, to keep the screen more clear, and removed that irritating text that was there for debug. Here’s the ship, sailing, with the capsule (a very unusual bird type) flying near it:
Summing Up
It’s only 10:10 and I started about 9:15, so this hasn’t really taken very long. I’m feeling a bit off this morning, so I think I’ll stop here, commit the code, and work more on this next week.
The main learning of today is that we can make one object a child of another, by just saying something like:
thisEntity.parent = thatEntity
After that, all the references we make to our child are done in the context of the position, angle, and scale of the parent object. This is just as one would expect and hope. What’s interesting is that it actually works. :)
The other issue is my continuing confusion of Codea’s choice of y axis up and z axis depth. I suspect this is quite common in 3d, but not in the systems I habitually use and think about.
I’m not sure quite what to do about this yet, but I’ll keep reflecting. I’m sure that some helper functions, or a carefully chosen style of working, will help me prevent these mistakes. They show up quickly and are easy to fix, but they do slow me down, and someday, they might bite harder.
Finally, we can of course extract our “bird” into an object, and we’ll probably do that next time.
See you then!
Here’s all the code:
-- Orc-1
-- RJ 20200203
-- upper case variables are global
-- 20200203: Orc Class
function setup()
Scene = craft.scene()
setupSky(Scene)
setupOrc(Scene)
local ship = setupWatercraft(Scene)
print("ship", ship)
setupFollower(Scene, ship)
setupCamera(Scene)
end
function setupFollower(scene, shipEntity)
local f = scene:entity()
local cap = f:add(craft.renderer, craft.model("Primitives:Capsule"))
f.material = craft.material("Materials:Standard")
f.parent = shipEntity
f.position = vec3(0,10,0)
end
function setupWatercraft(scene)
local entity = scene:entity()
entity:add(Watercraft)
return entity
end
function setupSky(scene)
scene.sky.active = false
createGround(-1.125, scene)
end
function setupOrc(scene)
scene:entity():add(Orc)
end
function setupCamera(scene)
Scene.camera.z = -4
local cameraSettings = scene.camera:get(craft.camera)
local fieldOfView = 60
local ortho = false
local orthoSize = 5
cameraSettings.fieldOfView = fieldOfView
cameraSettings.ortho = ortho
cameraSettings.orthoSize = orthoSize
end
function update(dt, scene)
updateCamera(dt, scene)
scene:update(dt)
end
-- Called automatically by codea
function draw()
update(DeltaTime, Scene)
Scene:draw()
-- transform playground
local tvec = Ship:transformPoint(vec3(0,0,0))
local tstr = string.format("tvec is %f %f %f", tvec.x, tvec.y, tvec.z)
text(tstr, 500, 500)
end
-- Creates the ground using a box model and applies a simple textured material
function createGround(y, scene)
local ground = scene:entity()
ground.model = craft.model.cube(vec3(4,0.125,4))
ground.material = craft.material("Materials:Specular")
ground.material.map = readImage("Blocks:Dirt")
ground.material.specular = color(0, 0, 0, 255)
ground.material.offsetRepeat = vec4(0,0,5,5)
ground.y = y
return ground
end
function updateCamera(dt, scene)
if CurrentTouch.state == MOVING then
CameraX = (CameraX or 0) - CurrentTouch.deltaX * 0.25
CameraY = (CameraY or 0) - CurrentTouch.deltaY * 0.25
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5
end
end
Orc = class()
function Orc:init(entity)
self.entity = entity
self.step = 0.02
entity.model = craft.model("Blocky Characters:Orc")
entity.x = 0
entity.y = -1
entity.z = 0
entity.scale = vec3(1,1,1) / 8
entity.eulerAngles = vec3(0, 180, 0)
end
function Orc:update(dt)
local x = self.entity.x + self.step
if math.abs(x) >= 2 then self.step = -self.step end
self.entity.x = x
end
-- Watercraft
-- RJ 20200204
Watercraft = class()
function Watercraft:init(entity)
Ship = entity -- darn
self.entity = entity
self.radiusVector = vec2(1.5,0)
self.theta = 0
self.deltaTheta = 0.01 -- random guess
entity.model = craft.model("Watercraft:watercraftPack_003")
entity.position = vec3(0,-1,0)
entity.scale = vec3(1,1,1) / 8
entity.eulerAngles = vec3(0, 180, 0)
end
function Watercraft:update(dt)
local pos2d = self.radiusVector:rotate(self.theta)
self.theta = self.theta + self.deltaTheta
self.entity.x = pos2d.x
self.entity.z = pos2d.y
self.entity.eulerAngles = vec3(0, math.deg(-self.theta), 0)
-- flying capsule logic
local cap = self.entity.children[1]
local angle = ElapsedTime
local xAngle = ElapsedTime*1.2
local xOffset = math.sin(xAngle)*4
local yAngle = ElapsedTime*0.4
local yOffset = math.sin(yAngle)*2
local newVec = vec2(5,0):rotate(angle)
cap.x = newVec.x + xOffset
cap.y = 10+yOffset
cap.z = newVec.y
end