It’s time to try a serious Bezier mover in SLua. The result, in my biased view, is quite nice. Lurvely. Added: Caveat.
I begin by defining a figure-eight path of beziers, 2 meters max height, 3 meters width, in each direction. I laid out the points on an index card.
function define_path()
Path = {}
local w = 3
local h = 2
local p0, p1, p2, p3
p0 = vector(0,0,0)
p1 = vector(0, h, 0)
p2 = vector(w, h, 0)
p3 = vector(w, 0, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
p0, p3 = p3, p0
p1 = vector(w, -h, 0)
p2 = vector(0, -h, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
p0 = vector(0,0,0)
p1 = vector(0, h, 0)
p2 = vector(-w, h, 0)
p3 = vector(-w, 0, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
p0, p3 = p3, p0
p1 = vector(-w, -h, 0)
p2 = vector(0, -h, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
end
The basic plan is this:
We’ll look at the coroutine last. Here’s the rest:
function state_entry()
--Tests:run_tests()
Home = ll.GetPos()
define_path()
print("ok")
end
Nothing much, just a bit of init. Then:
function touch_start(number_of_touches)
ll.Sensor("Base", "", bit32.bor(ACTIVE, PASSIVE), 10, PI)
end
Look for the Base. When we find it:
function sensor(number_of_prims)
Base = ll.DetectedPos(0)
move_safely_to(Base)
Provider = coroutine.create(provide)
ll.SetTimerEvent(0.1)
end
Move to the Base, create the coroutine, set the timer. In the timer:
function timer()
ok, vec = coroutine.resume(Provider)
if ok ~= true or vec == nil then
ll.SetTimerEvent(0)
else
move_safely_to(Base + vec)
end
end
I wrote move_safely_to
because things can go wrong and I didn’t want the prim moving out of range, since it has my script in it.
local SafeDistance = 10
function move_safely_to(pos)
local dist = ll.VecDist(pos, Home)
if dist < SafeDistance then
local move = {PRIM_POSITION, pos}
ll.SetLinkPrimitiveParamsFast(0, move)
else
print("unsafe move to ", pos)
end
end
So long as the move is within SafeDistance, we move. Otherwise we do not and we complain.
All that’s left, I guess, is the coroutine that provides points. It looks like this:
function provide()
for _, bezier in ipairs(Path) do
for t = 0, 1, Step do
coroutine.yield(bezier:at(t))
end
end
end
Look at that! Loop over however many beziers there are, four in our case. Loop over t
by some Step
. yield
the bezier value at that t
.
What makes this so simple and nice is that there are no globals keeping track of which bezier we’re in or what our current t
value is. It’s all inside the coroutine.
This is a small example of why I think our SLua scripts will be able to be much simpler and more clear than what we have to do in LSL.
This scheme will not serve as-is for practical movers for trains. It might serve for single-bogie vehicles such as short trams. More complex vehicles need a series of points spaced at fixed distances along the curve. At the junctions between curves, they even call for points on different curves.
Will we still want to use the coroutine style? Too soon to tell. But even if not, the timer code should wind up being nearly as simple as we have here. It’s still early days, we are still learning.
Safe paths!
Here’s the whole program:
local Home = nil
local Base = nil
local SafeDistance = 10
local Path = nil
local Provider = nil
local Step = 1/32
function provide()
-- while true do -- uncomment this and it runs forever
for _, bezier in ipairs(Path) do
for t = 0, 1, Step do
coroutine.yield(bezier:at(t))
end
end
-- end
end
function move_safely_to(pos)
local dist = ll.VecDist(pos, Home)
if dist < SafeDistance then
local move = {PRIM_POSITION, pos}
ll.SetLinkPrimitiveParamsFast(0, move)
else
print("unsafe move to ", pos)
end
end
function define_path()
Path = {}
local w = 3
local h = 2
local p0, p1, p2, p3
p0 = vector(0,0,0)
p1 = vector(0, h, 0)
p2 = vector(w, h, 0)
p3 = vector(w, 0, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
p0, p3 = p3, p0
p1 = vector(w, -h, 0)
p2 = vector(0, -h, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
p0 = vector(0,0,0)
p1 = vector(0, h, 0)
p2 = vector(-w, h, 0)
p3 = vector(-w, 0, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
p0, p3 = p3, p0
p1 = vector(-w, -h, 0)
p2 = vector(0, -h, 0)
table.insert(Path, Bezier(p0, p1, p2, p3))
end
function state_entry()
Home = ll.GetPos()
define_path()
end
function sensor(number_of_prims)
Base = ll.DetectedPos(0)
move_safely_to(Base)
Provider = coroutine.create(provide)
ll.SetTimerEvent(0.1)
end
function timer()
ok, vec = coroutine.resume(Provider)
if ok ~= true or vec == nil then
ll.SetTimerEvent(0)
else
move_safely_to(Base + vec)
end
end
function no_sensor()
print("base not found)")
end
function touch_start(number_of_touches)
ll.Sensor("Base", "", bit32.bor(ACTIVE, PASSIVE), 10, PI)
end