Braitenberg Vehicles - 1
Apologia
I’m going to play a bit in Codea, and perhaps elsewhere, with the ideas that come to mind when I think about “Braitenberg Vehicles”. My purposes? To play, to learn about Codea, maybe even its newer 3D-related capabilities. To write something. To learn about software development. To explore; to share; to mess around.
I’m not going to follow Braitenberg’s path, just to start off with some vehicles and see where they take me. Follow along, if you wish.
Starting off
I’m going to build a little playing field for my vehicles, put down a single vehicle, and play a bit with driving it around. The B-vehicles (please permit me this abbreviation) have two driven wheels, and they turn by driving one faster than the other. I’m planning just now to get to a point where I can decide if I like that model well enough to follow it. It has some nice properties, in my mind, so let’s find out.
I begin with a rectangle: rect(0,0,1000,1000)
, whose parameters are x, y, width, and height. That seems to encompass the whole screen. I decide to verify that by printing WIDTH and HEIGHT. They display as 1091, and 1024. So I decide to draw another rectangle of that size, and then my 1000 by 1000 on top. . That teaches me two things. First, it shows me where the screen edges are, and second it reminds me that rectangles are filled. The default is some grey color, as shown below. I’ll commit that, and go back to a darker fill or maybe none at all if that’s possible.
That worked. I used an alpha of 0 for the color of the larger rectangle, and the general background shows through.
Now for my vehicle, I’m just drawing a little rectangle in yellow. The code, so far, looks like this:
— Br1
— Use this function to perform your initial setup
function setup()
print("Braitenberg Vehicle Experiment")
print(WIDTH, HEIGHT)
rectx = 100
recty = 100
end
— This function gets called once every frame
function draw()
— This sets a dark background color
background(40, 40, 50)
— This sets the line thickness
strokeWidth(5)
stroke(255,0,0)
fill(0,0,0,0)
rect(0,0, 1091, 1024)
stroke(255,255,255)
fill(50,50,50)
rect(0,0,1000,1000)
— draw the vehicle
stroke(255,255,0)
fill(255,255,0)
rect(rectx, recty, 20, 10)
end
And the static picture looks like this:
I defined the coordinates of the “vehicle” as variables, since I plan to move it across the screen, to get a sense of how fast things change. My first notion is to increment x each time the draw() function is called. And I’m going to check to be sure X doesn’t get too big. Here goes:
— update vehicle
rectx = rectx + 1
if rectx > 1000 then rectx = 0 end
This isn’t bad: he moves across the screen in about 12 seconds. I’m a little surprised by this. I thought the Codea clock was 50x a second, so 1000/50 is 20. Maybe my Mississippi’s were fast. Anyway that’s how long it takes. Time to commit.
What about turning? The b-vehicles have two wheels, and they turn by driving one faster than the other. How can I best emulate that? It would be much easier, I think, to rotate the vehicle a little bit, and drive him forward along his line of rotation. Even that’s a bit tricky in Codea. I think one does rotations by rotating the world and then drawing. Let’s actually try that. I’ll change my current program to rotate before moving. We have to rotate back of course. Codea helps with that.
— draw the vehicle
stroke(255,255,0)
fill(255,255,0)
pushMatrix()
rotate(45)
rect(rectx, recty, 20, 10)
popMatrix()
We learn a few things, but the little guy does now move upward across the screen as we see below. What do we learn?
I’m rotating is around the zero coordinate. So our guy doesn’t turn in place, he is also moved upward and to the right. And he still thinks he’s going across the screen, so he pops back to zero when x is 1000, which isn’t all the way across any more. He’s 1000 away from x = 0 but that doesn’t take him all the way to either edge.
Also, we need to decide whether to follow B-vehicles’ model of moving one wheel faster than another, or something more simple relative to our coding world. To decide how much a vehicle turns if one wheel is at speed S and the other at S + x, we’d need to know how big the wheels are, and how far apart they are. Clearly if S is zero, he’ll turn in place. If S is large, he’ll describe some large arc.
I don’t want to model vehicle width, and I don’t want to do the math to decide direction that way. After a chat with Chet, I decide that my vehicles are going to have an angle they’re moving, relative to the world, and that they will change that angle incrementally, rather than adjust the speed of their wheels. It’s “clear” that we can do the calculus (literally) to determine the angular rate of change given S and x, so it’s also “clear” that we can get away with just modeling that angular change directly, with perhaps some more complex calculations in the future having to do with complex behavior. In the end, since the two are related mathematically, we can model the angle now and be assured that if our vehicles are modular, we can change that aspect later.
They’re definitely not modular yet. Here’s our whole program:
— Br1
— Use this function to perform your initial setup
function setup()
print("Braitenberg Vehicle Experiment")
print(WIDTH, HEIGHT)
rectx = 100
recty = 100
end
— This function gets called once every frame
function draw()
— update vehicle
rectx = rectx + 1
if rectx > 1000 then rectx = 0 end
— This sets a dark background color
background(40, 40, 50)
— This sets the line thickness
strokeWidth(5)
stroke(255,0,0)
fill(0,0,0,0)
rect(0,0, 1091, 1024)
stroke(255,255,255)
fill(50,50,50)
rect(0,0,1000,1000)
— draw the vehicle
stroke(255,255,0)
fill(255,255,0)
pushMatrix()
rotate(45)
rect(rectx, recty, 20, 10)
popMatrix()
end
It’s probably time to build a vehicle class and do the updating inside it. We have another issue, though, which is that our current rotation scheme is rotating around zero and that won’t do in the long run. I shouldn’t refactor and implement at the same time. I’m going to do it anyway, and you can hold my beer. “Watch this!”
— Br1
— Use this function to perform your initial setup
function setup()
print("Braitenberg Vehicle Experiment")
vehicle = BVehicle1(100,100)
end
— This function gets called once every frame
function draw()
vehicle:update()
— This sets a dark background color
background(40, 40, 50)
— Draw the playground
strokeWidth(5)
stroke(255,0,0)
fill(0,0,0,0)
rect(0,0, 1091, 1024)
stroke(255,255,255)
fill(50,50,50)
rect(0,0,1000,1000)
— draw the vehicle
vehicle:draw()
end
BVehicle1 = class()
function BVehicle1:init(x,y)
self.x = x
self.y = y
end
function BVehicle1:update()
self.x = self.x + 1
if self.x > 1000 then self.x = 0 end
end
function BVehicle1:draw()
stroke(255,255,0)
fill(255,255,0)
pushMatrix()
rotate(45)
rect(self.x, self.y, 20, 10)
popMatrix()
end
This actually works, replicating the prior behavior exactly, including everything I don’t like, like rotating the little guy off the page, and stopping before he reaches the edge. Time to commit.
OK, let’s think a moment. This little vehicle moves in its own x direction, one unit per call to update. It has a known rotation, 45. But it’s transforming the space, not around itself, as one does, but around zero. We can “fix” that by translating to our real location, rotating, then (if necessary) translating back. (The popMatrix function handles restoring everything we’ve messed with, and that may be sufficient.) I’m inclined to make that change and see what happens, but I’m stating clearly here that I see the need for our little guy to know where he is in the real XY space, not just his own local model of moving from <100,100> to <1000,100>. We’re going to need to handle that somehow. First, though, let’s see what happens if we rotate around ourself. Ohhh I think this’ll be ugly.
function BVehicle1:draw()
stroke(255,255,0)
fill(255,255,0)
pushMatrix()
translate(self.x, self.y)
rotate(45)
rect(self.x, self.y, 20, 10)
popMatrix()
end
What this does, to my surprise about the details, is it crabs across the screen at less than a 45 degree angle, and it doesn’t wrap back any time soon. I want to see some info.
function BVehicle1:draw()
stroke(255,255,0)
fill(255,255,0)
pushMatrix()
translate(self.x, self.y)
rotate(45)
rect(0, 0, 20, 10)
local s = string.format("%d %d", self.x, self.y)
text(s,0, 10)
popMatrix()
end
Displaying that string, I realized that translating to my supposed position, and then drawing offset from that, makes no sense. Once I translate the word to my location, I should draw myself at 0,0. (We should also consider drawing the rectangle around its center, but that’s a detail I don’t even want to explain right now. But, if I do draw myself at 0,0, I’ll rotate OK but then go straight across the screen. And that’s what happens, my guy now rotates to 45, but goes straight across.
Our guy needs to adjust his position in the world based on his rotation. If he moves 1 unit forward, and he’s at angle Theta, then his real-world coordinates change by something like 1*cos(Theta), 1*sin(Theta). Or we can do it by rotating a vector, which will be better.
BVehicle1 = class()
function BVehicle1:init(x,y)
self.x = x
self.y = y
end
function BVehicle1:update()
local step = vec2(1,0)
local move = step:rotate(math.rad(45))
self.x = self.x + move.x
self.y = self.y + move.y
if self.x > 1000 then self.x = 100; self.y = 100 end
end
function BVehicle1:draw()
stroke(255,255,0)
fill(255,255,0)
pushMatrix()
translate(self.x, self.y)
rotate(45)
rect(0, 0, 20, 10)
local s = string.format("%d %d", math.floor(self.x), math.floor(self.y))
text(s,0, 30)
popMatrix()
end
That’s what I came up with. We’ve begun to get our little guy’s behavior in the right place. He draws himself. That can be improved at will. Maybe I’ll do that just for fun. He updates himself before we draw him, deciding where to go given where he is. Right now he makes a very simple decision: go straight ahead. We can enhance that decision, and real soon now. We will. First, though, I want to work on his turning. And to start a separate “chapter”.