JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

Simpler MainRod?

Aug 24, 2025 • [designlinkagesluatesting]


Today I think we can find a simpler implementation of the Main Rod (formerly connecting rod). I think a few simple rotations should do the job.

The elements of the plan, as I see if vaguely but well enough to start following it are:

I will test this before building it in, because, this will surprise no one, I could be wrong.

OK, I have good news and not so good news. The good news is that this partial test is printing results that I agree with:

_:test("new main rod idea", function()
    function new_main_rod(wheel, tilt_angle, length, radius)
        local adjust_angle = tilt_angle*DEG_TO_RAD
        local wheel_angle = wheel:angle()*DEG_TO_RAD - adjust_angle
        local x = radius*math.cos(wheel_angle)
        local y = radius*math.sin(wheel_angle)
        -- print()
        -- print(wheel_angle, x, y)
        local x_proj_sq = length*length - y*y
        local x_proj = math.sqrt(x_proj_sq)
        local result_x = x + x_proj
        return vector(result_x, 0)
    end

    local wheel = DriveWheel{x=10, y=2}
    for _, angle in {0,45,90,135,180,225,270,315,360} do
        wheel._angle = 0 -- hack
        wheel:rotate_by(angle*DEG_TO_RAD)
        local test_end = new_main_rod(wheel, 90, 6, 1)
        print(wheel:angle(), test_end)
    end
end)

With tile angle set at 90, it prints this:

0   <5.916079783099616, 0, 0>
45  <6.66529442509304, 0, 0>
90  <7, 0, 0>
135 <6.66529442509304, 0, 0>
180 <5.916079783099616, 0, 0>
225 <5.251080862719945, 0, 0>
270 <5, 0, 0>
315 <5.251080862719944, 0, 0>
0   <5.916079783099616, 0, 0>

Those are the correct values for the intercept on the x axis. Now I need to rotate those values to the tilt angle:

function new_main_rod(wheel, tilt_angle, length, radius)
    local adjust_angle = tilt_angle*DEG_TO_RAD
    local wheel_angle = wheel:angle()*DEG_TO_RAD - adjust_angle
    local x = radius*math.cos(wheel_angle)
    local y = radius*math.sin(wheel_angle)
    -- print()
    -- print(wheel_angle, x, y)
    local x_proj_sq = length*length - y*y
    local x_proj = math.sqrt(x_proj_sq)
    local result_x = x + x_proj
    local unrotated = vector(result_x, 0)
    local rotated = unrotated:rotate_2d(adjust_angle)
    return rotated
end

Results:

0   <3.6225540849366565e-16, 5.916079783099616, 0>
45  <4.081315741532445e-16, 6.66529442509304, 0>
90  <4.286263797015736e-16, 7, 0>
135 <4.081315741532445e-16, 6.66529442509304, 0>
180 <3.6225540849366565e-16, 5.916079783099616, 0>
225 <3.2153596852969514e-16, 5.251080862719945, 0>
270 <3.061616997868383e-16, 5, 0>
315 <3.215359685296951e-16, 5.251080862719944, 0>
0   <3.6225540849366565e-16, 5.916079783099616, 0>

Those are correct, I believe. Those e-16 are rounding. Since our tilt angle is 90 in this example, Our x value for the end of the main rod is always zero.

The not so good news is that I do not see a good way to turn this into a test that actually checks the answers, short of calculating the values by hand. But if I did that, I’d use the same formula that I coded. Wouldn’t prove all that much: I might as well just save these values and check for them.

What if we do this much? We’ll hand calculate the results for tilt zero and verify those. Then we’ll see what to do next.

local wheel = DriveWheel{x=10, y=2}
for i, angle in {0,45,90,135,180,225,270,315,360} do
    local radians = angle*DEG_TO_RAD
    wheel._angle = 0 -- hack
    wheel:rotate_by(radians)
    local test_end = new_main_rod(wheel, 0, 6, 1)
    local dxdy = vector(1,0):rotate_2d(radians)
    local x_proj = math.sqrt(36-dxdy.y^2)
    local tot_x = dxdy.x+x_proj
    _:expect(test_end.x).is(tot_x)
end

I calculated the tot_x somewhat differently, differently enough that I got it wrong at first, but it’s still basically the same calculation.

Still, I’m confident that it’s working so far. I’ll pause for now: it is Sunday and time for our ritual Sunday brekkers and television. I’ll take this up:

Later, the Next Day

I’m sure enough that this new scheme works, and that it’s better, to go forward with it. But I do not feel that it is well-enough tested. How did I test the existing MainRod and GeneralMainRod classes?

Reviewing the tests for those classes, I experience - how can I put this - mystified confusion. Here are two tests for the horizontal MainRod:

local wheel
local main_rod
_:setup(function()
    wheel = DriveWheel{x=10, y=2}
    main_rod = MainRod{parent=wheel, length=6, radius=1}
end)

_:test("90", function() 
    wheel:move(2*math.pi/2)
    main_rod:calculate()
    local dx = math.sqrt(35)
    local ex = 10-dx
    local exp = (10+ex)/2
    _:expect(main_rod:position()).is(vector(exp, 1.5))
end)

_:test("270", function() 
    wheel:move(3*2*math.pi/2)
    main_rod:calculate()
    local dx = math.sqrt(35)
    local ex = 10-dx
    local exp = (10+ex)/2
    _:expect(main_rod:position()).is(vector(exp, 2.5))
end)

Surprise Ending

After two hours this morning, I cannot get this new thing to hook up. There is some assumption built into the calculate and tests that is eluding me. I’ve scrapped two hours of code and writing. I’ll hit this again later today, or maybe not until tomorrow.

I do think the new scheme works: I’ve just been unable to integrate it on my first try or two. We’ll get there.

Safe paths!