Braitenberg Vehicles - 7
Friday
On my way to the coffee shop, it occurred to me that, perhaps because I’m a programmer, I’ve probably made a mistake in dealing with wall avoidance. The problem we see is that if the poor little devils get headed into a corner, one eye sees the wall first, so turns away, then the other eye soon sees the wall first, and so on, resulting in steering directly toward the corner. The key to my insight today is in the word “avoidance”. I was thinking about an added behavior, such as “if really close to the wall, turn around”, and then this morning I thought what if, instead of driving the near wheel faster, why not drive the off wheel slower? Why not, in other words, program avoidance?
Pushing this thought further, I’m reminded that Braitenberg’s vehicles are more analog than mine. They send a variable signal, depending on what they sense. So, if we had a propensity to avoid walls, if both eyes could see the wall, they might both send a negative signal to the wheel they control. If the sight of the wall gets increasingly scary, they’d wind up backing away.
This is a new model almost entirely from my current one, where sensors just add a value to one wheel, depending which eye gets the stronger signal. With this scheme, we’d add a value to each eye’s wheel. It’s just that one of those values would be larger than the other.
I think this notion will also get me closer to the original Braitenberg idea, and might lead to a simpler way of building behaviors, by just linking sensors, through weighting functions, to wheels. If development goes as I think it might, we may wind up with a set of bugs whose programming is more analog in nature and they may look less like a god-scientist created them.
Let’s find out.
Strength of Signal
I think I’ll start with the notion of the strength of the signal received at an eye. It could be constant, but more likely it will vary with the distance of the thing seen. Perhaps it’ll vary linearly, perhaps inverse square, perhaps some other function. Braitenberg postulates a number of interesting functions, graphs with steps, holes, and so on. For the wall avoidance, we want fear of the wall to increase as distance decreases (and in fact it does now, since we adjust the wheels by minDist/distToEye
. Let’s try a few steps in the new direction. First, I’ll change the wheel adjustment to be negative, and apply it to the other wheel. Yesterday, we had this:
function computeWheelIncrement(value, left, right)
if right < left then
return vec2(0, value)
else
return vec2(value, 0)
end
end
This is driven by lines like these:
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(1000, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
Today, let’s just get those distances and apply those two fractions, negated, to the appropriate wheels. I’ll leave the computation of the two distances alone, though I think both these functions are probably not quite what we want. Here’s what I tried:
function BVehicle1:avoidWall()
local minDist = 50
local wheels = vec2(0,0)
local distLeftEye, distRightEye
local adjLeft, adjRight
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(1000, self.position.y))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(eyeLeft,eyeRight)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(0, self.position.y))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(eyeLeft,eyeRight)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 0))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(eyeLeft,eyeRight)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 1000))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(eyeLeft,eyeRight)
return wheels.x, wheels.y
end
I get the two eye distances, and I get the “fear power” of that distance by dividing 50 (changed from 10) by the distance. The shorter the distance, the greater the power. Then I apply the negative of that power to the opposite wheel, causing that wheel to brake and turn us away from the wall.
It doesn’t seem to work. They don’t seem to go into corners any more, and they turn away from walls they are nearly parallel to, but they can head directly at a wall and crash right into it. I’m confused. And I’m tempted to debug this and maybe to hammer on it. Truth is, I believe the code above does what I want, but I don’t know that it does. Anyway, it’s valid behavior, so I’ll commit.
And I decided not to revert, and I’m glad I didn’t. Instead, I printed the output from the avoidWalls
function and it was always zero. This made me notice, finally, that I was referring to eyeLeft
and eyeRight
, which aren’t even defined. It turns out, I guess, that vec2(nil,nil)
is a perfectly good zero vector. This code works much better:
function BVehicle1:avoidWall()
local minDist = 50
local wheels = vec2(0,0)
local distLeftEye, distRightEye
local adjLeft, adjRight
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(1000, self.position.y))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(adjLeft, adjRight)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(0, self.position.y))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(adjLeft, adjRight)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 0))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(adjLeft, adjRight)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 1000))
adjLeft = -minDist/distRightEye
adjRight = -minDist/distLeftEye
wheels = wheels + vec2(adjLeft, adjRight)
return wheels.x, wheels.y
end
Commit this.
And for next time, when it finally comes: what could we do to avoid the error caused by a simple “typo” reference to an undefined variable?