JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

Smaller Objects?

Jun 17, 2025 • [designluamoverstesting]


In which, we try a smaller object on for size. Doesn’t seem to fit.

In the previous article, we looked at two methods of L_Bezier, and observed that they could be improved by limiting each one to manipulating just one of the two tables in L_Bezier.

What if we pushed that idea even further? What if we had an object that only dealt with finding interpolation index and fraction, and another separate object, or several objects, that used that result and did actual interpolation of positions or, well, anything?

I think that’s an interesting notion. Let’s find out.

Let’s start with the one that has the table of lengths and that uses them to produce an index into the table and a fraction. This object doesn’t need to know that it is dealing with lengths. If could process anything that follows the rules.

I’m not sure what to call it. Let’s call it, oh, Interpolator, and let’s begin with a test. This is too much but not too much too much:

function Tests:test_interpolator()
    local values =  {0, 10, 20, 50, 70}
    local int = Interpolator(values)
    local index, fraction
    index, fraction = int:at(5)
    self:assert_equals(index, 2)
    self:assert_equals(fraction, 0.5)
end

Why too much? Well, I’m not even sure yet just what form the values should take. As shown, they’re increasing. But my experience with L_Bezier makes me think I want them to be the individual lengths of the segments between. Let’s try it that way.

    local values =  {0, 10, 10, 30, 20}

OK, and let’s have this much code:

Interpolator = class()
function Interpolator:init(values)
    self._values = values
end

Test will fail looking for at. It does. Code it:

function Interpolator:at(desired)
    local remaining_value = desired
    for i, value in self._values do
        if remaining_value < value then
            return i, remaining_value/value
        end
        remaining_value -= value
    end
    return nil
end

Obviously, I cribbed this from L_Bezier and massaged the names a bit.

Let’s add a test or check or two but I think we’re really done here.

function Tests:test_interpolator_more()
    local values =  {0, 10, 10, 30, 20}
    local int = Interpolator(values)
    local index, fraction
    index, fraction = int:at(3)
    self:assert_equals(index, 2)
    self:assert_equals(fraction, 0.3)
    index, fraction = int:at(60)
    self:assert_equals(index, 5)
    self:assert_equals(fraction, 0.5)
end

These pass. Ship it. We have a nice new tiny object, an interpolator.

For our next trick, we’ll try installing Interpolator into L_Bezier and see how it feels.

Installed

I found it desirable to provide an add method to Interpolate, so that I could easily plug it into L_Bezier:

Interpolator = class()
function Interpolator:init(values)
    self._values = values or {}
end

function Interpolator:add(value)
    table.insert(self._values, value)    
end

function Interpolator:at(desired)
    local remaining_value = desired
    for i, value in self._values do
        if remaining_value < value then
            return i, remaining_value/value
        end
        remaining_value -= value
    end
    return nil
end

And installed in L_Bezier:

L_Bezier = class()
function L_Bezier:init(beziers)
   self.length = 0
    self._interp = Interpolator() -- <---
   self._points = {}
   self.ready = false
   for _, b in beziers do
      self:_add(b.p0)
      self:_add(b.p1)
      self:_add(b.p2)
      -- self:_add(b.p3)
   end
   self:_add(beziers[#beziers].p3)
end

function L_Bezier:_add(point)
   local len
   local n = #self._points
   if n == 0 then
      len = 0
   else
      len = point:dist(self._points[n])
   end
   self.length += len
    self._interp:add(len) -- <---
   table.insert(self._points, point)
end

-- just here supporting some tests. 
function L_Bezier:find_distance(d)
    return self._interp:at(d)
end
-- to be removed. covered by Interpolator tests.

function L_Bezier:point_at_distance(d, debug)
   local idx, frac = self._interp:at(d) -- <---
   local p0 = self._points[idx-1]
   local p1 = self._points[idx]
   return  p0*(1 - frac) + p1*frac 
end

What I like about this is that we could use the interpolator in lots of places, if we had use for it, and it would be useful in all those places: we wouldn’t have to duplicate the find code.

What I do not like, as things stand, is that I needed to create and use the incremental add method, instead of just creating the interpreter once and for all, giving it all the values at once.

So my current, not at all final answer, is that I like the Interpolator, but it isn’t quite as useful, yet, as I had hoped. So we’ll keep on trying.

Safe paths!