Locomotive using joints

Here’s something I was playing with the last few days. It’s a locomotive demo using Revolute, Distance and Prismatic joints. The piston uses Prismatic, the wheels use Revolute, and the joints connecting the wheels and piston uses Distance. The only piece the gets any value to move is the piston. When the piston moves, it causes wheel number 3 to start rotating by the distance joint connecting them. Wheel 3 then rotates wheel 2 and wheel 4 with their connecting joints. When 2 rotates wheel 1 with their connecting joint. By increasing the pressure slider, the piston moves faster causing wheel 3 and the other wheels to move faster. You can slide the parameter “piston joint” to break the joint between the piston and wheel 3 causing them to slow down. Parameter “Joints 1_2, 2_3, and 3_4” breaks the joints between those wheels and they will slow down. Wheels 1, 2, and 3 have angularDamping, so they will slow down faster. Wheel 4 has no damping, so it will rotate for awhile.

PS. Run in landscape orientation.

viewer.mode=STANDARD

-- locomotive

function setup()
    rectMode(CENTER)
    parameter.integer("pressure",1,100,5)
    parameter.boolean("piston joint",false)
    parameter.boolean("joint1_2",false)
    parameter.boolean("joint2_3",false)
    parameter.boolean("joint3_4",false)
    
    -- create wheels
    w1=wheel(200,200,200,200,200,260)
    w2=wheel(380,200,380,200,380,260)
    w3=wheel(560,200,560,200,560,260)
    w4=wheel(740,200,740,200,740,260)

    createPiston()
    createJoints()
end

function draw()
    background(142, 95, 31)
    fill(255)
    text("Angular velocity (1)   "..w1.wheel.angularVelocity//-1,WIDTH/2,HEIGHT-25)
    text("Angular velocity (2)   "..w2.wheel.angularVelocity//-1,WIDTH/2,HEIGHT-50)
    text("Angular velocity (3)   "..w3.wheel.angularVelocity//-1,WIDTH/2,HEIGHT-75)
    text("Angular velocity (4)   "..w4.wheel.angularVelocity//-1,WIDTH/2,HEIGHT-100)
    
    w1:draw()
    w2:draw()
    w3:draw()
    w4:draw()
    drawPiston()
    drawJoints() 
     
    if r1.linearVelocity.x>0 then
        r1.linearVelocity=vec2(pressure*10,0)
    else
        r1.linearVelocity=vec2(pressure*-10,0)
    end
    
    if piston_joint and j5~=nil then   
        j5:destroy()
        j5=nil
    end
    if joint1_2 and j61~=nil then
        j61:destroy()
        j61=nil
        w1.wheel.angularDamping=1
    end
    if joint2_3 and j62~=nil then
        j62:destroy()
        j62=nil
        w2.wheel.angularDamping=1
    end
    if joint3_4 and j63~=nil then
        j63:destroy()
        j63=nil
        w3.wheel.angularDamping=1
    end

end

function createPiston()
    p1=physics.body(CIRCLE,0)   -- anchor piston
    p1.x=100
    p1.y=350
    p1.type=STATIC
        
    r1=physics.body(CIRCLE,0)  -- piston rect
    r1.x=100
    r1.y=350
end

function drawPiston()
    if j5==nil then
        return
    end
    pushStyle()
    strokeWidth(8)
    fill(255)
    stroke(0)
    rect(110,350,200,50) -- piston cylinder
    fill(0)
    rect(r1.x,r1.y,20,50) -- piston
    strokeWidth(0)
    rectMode(CORNER)
    if pressure>0 then
        if r1.linearVelocity.x>0 then
            fill(255,0,0,pressure*2+55)
            rect(p1.x-84,r1.y-20,r1.x-24,40)   -- red pressure
        elseif r1.linearVelocity.x<0 then
            fill(255,0,0,pressure*2+55)
            rect(r1.x+8,r1.y-20,p1.x-r1.x+96,40)   -- red pressure
        end
    end
    popStyle()
end

function drawJoints()
    pushStyle()
    stroke(172)
    strokeWidth(4)
    if j61~=nil then
        line(j61.bodyA.x,j61.bodyA.y,j61.bodyB.x,j61.bodyB.y)
    end
    if j62~=nil then
        line(j62.bodyA.x,j62.bodyA.y,j62.bodyB.x,j62.bodyB.y)
    end
    if j63~=nil then
        line(j63.bodyA.x,j63.bodyA.y,j63.bodyB.x,j63.bodyB.y)
    end
    if j5~=nil then
        line(j5.bodyA.x,j5.bodyA.y,j5.bodyB.x,j5.bodyB.y)
    end
    popStyle()
end

function createJoints()
    j31=physics.joint(REVOLUTE,w1.anchor1,w1.wheel,vec2(w1.anchor1.x,w1.anchor1.y))
    j32=physics.joint(REVOLUTE,w2.anchor1,w2.wheel,vec2(w2.anchor1.x,w2.anchor1.y))
    j33=physics.joint(REVOLUTE,w3.anchor1,w3.wheel,vec2(w3.anchor1.x,w3.anchor1.y))
    j34=physics.joint(REVOLUTE,w4.anchor1,w4.wheel,vec2(w4.anchor1.x,w4.anchor1.y))
    
    j41=physics.joint(REVOLUTE,w1.wheel,w1.anchor2,vec2(w1.wheel.x,w1.wheel.y+60))
    j42=physics.joint(REVOLUTE,w2.wheel,w2.anchor2,vec2(w2.wheel.x,w2.wheel.y+60))
    j43=physics.joint(REVOLUTE,w3.wheel,w3.anchor2,vec2(w3.wheel.x,w3.wheel.y+60))
    j44=physics.joint(REVOLUTE,w4.wheel,w4.anchor2,vec2(w4.wheel.x,w4.wheel.y+60))
    
    j61=physics.joint(DISTANCE,w1.anchor2,w2.anchor2,
        vec2(w1.anchor2.x,w1.anchor2.y),vec2(w2.anchor2.x,w2.anchor2.y))
    j62=physics.joint(DISTANCE,w2.anchor2,w3.anchor2,
        vec2(w2.anchor2.x,w2.anchor2.y),vec2(w3.anchor2.x,w3.anchor2.y))
    j63=physics.joint(DISTANCE,w3.anchor2,w4.anchor2,
        vec2(w3.anchor2.x,w3.anchor2.y),vec2(w4.anchor2.x,w4.anchor2.y))
    
    j1=physics.joint(PRISMATIC,p1,r1,p1.position,vec2(1,0))
    j5=physics.joint(DISTANCE,r1,w3.anchor2,r1.position,w3.anchor2.position)
end

wheel=class()

function wheel:init(a1x,a1y,wx,wy,a2x,a2y)
    self.anchor1=physics.body(CIRCLE,0)
    self.anchor1.x=a1x
    self.anchor1.y=a1y
    self.anchor1.type=STATIC
    
    self.wheel=physics.body(CIRCLE,80)
    self.wheel.x=wx
    self.wheel.y=wy
    
    self.anchor2=physics.body(CIRCLE,0)
    self.anchor2.x=a2x
    self.anchor2.y=a2y
end

function wheel:draw()
    pushMatrix()
    pushStyle()
    stroke(255)
    strokeWidth(6)
    fill(46, 30, 29)
    translate(self.anchor1.x,self.anchor1.y)
    rotate(self.wheel.angle)
    ellipse(0,0,self.wheel.radius*2)       --large wheel
    fill(255)
    strokeWidth(3)
    stroke(103, 66, 64)
    for z=0,360,30 do
        x=math.cos(math.rad(z))*72
        y=math.sin(math.rad(z))*72
        line(0,0,x,y)
    end
    ellipse(0,0,30)                 -- center circle
    ellipse(0,60,20)                -- outer circle
    fill(0)
    ellipse(0,0,10)                -- small center circle
    popStyle()
    popMatrix()
end

neat. now i’ll have to figure out what you did :smile:

@RonJeffries Thanks, if you have questions about anything, just ask. It took a lot of trial and error writing this because I wasn’t sure how the joints actually worked. I spent a lot of time on small programs playing with a single joint until I understood what was happening.

yes, that’s what i’d do too. that’s a neat example, should be added to the examples that ship with codea

@dave1707 this is a great demo project!

Attached is a project that puts all the code into a stand-alone class so it can be used as a dependency if anyone wants to.

The class abstracts the code enough that it can make the exact same train as in the original example, but it can also make multiple pistons and wheels and connect them in any way you like—it won’t always work out, but you can try any configuration you want.

https://youtu.be/CmBHSjbbK5M

Did a little more refinement and renamed the project “dave1707 Locomotion API”. :slight_smile:

@dave1707’s code can now be used, with simple commands, to make copies of his train or to make something new out of the parts he made:

https://youtu.be/9a8LvCgKDEg

To make one of his trains at any size, use makeTrain(x, y, width), like this:


locoNator = LocoMotionInator()
locoNator:makeTrain(130, 400, 1200)

If called without parameters, it will make a train that has roughly the size and placement of Dave’s original train.

This “API” also lets you use his code piece-by-piece with four functions:

  • makeWheel(x, y, radius) makes a wheel with a connection point
  • makePiston(x, y, width, height) makes a piston with an adjustable pressure setting
  • attachPistonToWheel(piston, wheel) connects a piston to a wheel’s connection point
  • attachWheelToWheel(wheel1, wheel2) connects two wheels by their connection points

For example, this is the code that makes the “weirdDangThingy” contraption in the video:


    locoNator = LocoMotionInator()

    local p = locoNator:makePiston(WIDTH*0.75, HEIGHT*0.55, 200, 40)
    p.pressure = 25

    local w1 = locoNator:makeWheel(WIDTH*0.85, HEIGHT*0.95, 35)
    local w2 = locoNator:makeWheel(WIDTH*0.92, HEIGHT*0.25, 25)
    local w3 = locoNator:makeWheel(WIDTH*0.72, HEIGHT*0.8, 25)
    local w4 = locoNator:makeWheel(WIDTH*0.96, HEIGHT*0.96, 25)
    local w5 = locoNator:makeWheel(WIDTH*0.46, HEIGHT*0.82, 25)

    locoNator:attachPistonToWheel(p, w2)
    locoNator:attachWheelToWheel(w1, w2)
    locoNator:attachWheelToWheel(w2, w3)
    locoNator:attachWheelToWheel(w2, w5)
    locoNator:attachWheelToWheel(w3, w4)

…and also, of course, you’re free to go in the code and change the colors however you like. :slight_smile:

I hope someone has fun with the cool stuff dave1707 wrote!

@UberGoober Nice job, but you’re putting too much work into this. I did this just for kicks and to have an example for anyone who wanted to see how joints worked.

@dave1707 you have your kicks and I have mine. :slight_smile:

@UberGoober True.

Also, like I mentioned in another post, this is how I have to approach code if I want to understand it.

My brain grapes just can’t comprehend or retain the purpose of code with single-letter variables, and it’s similarly hard for me to understand or retain how the various parts of the code work together without separating them into discrete functions.

And I wanted to understand this code as well as I could, because you suggested it might be helpful with a different problem I was having— and once I had made it verbose and modular enough for me to understand and retain, it was pretty close to being an API anyway, so I just went all the way with it.

@UberGoober I guess I’m too used to using 1 letter variable names and not separating code into different functions. I guess that’s what happens when I’m half coding and half watching TV.

@dave1707 - guess I’m in your gang, I tend to abreviate my variables to give a hint of their purpose but I don’t like long winded descriptive names especially on an iPad. They tend to make lines wrap which I prefer to avoid. Also I prefer to edit in portrait and not landscape, so I see more lines of code (generally).

I do think it’s better with short variable names but commenting on their purpose in the text is a good practice. Shame I’m not good at that in practice !!

I can understand professional programmers, with reems of code in their projects, using long winded names.

I don’t have anything against short variable names, and I honestly admire people who can understand someone else’s code immediately without any of the variables being English words—I’m stunned at how much you guys can accomplish, and I assume your brains can just keep all that stuff in your head as you write it.

I simply do not have that skill, and I just literally can’t understand what code is doing—even my own!—unless I make the names something I can read.

It’s not a criticism, it’s that I personally can’t learn from anyone else’s code unless I make the variables describe what they are.

this is crazy awesome, if you wanted to recreate a combustion engine you could because Codea uses box2D and box2D is wicked good
https://youtu.be/8kZRpouZ3OQ

@Bri_G I used to believe that the best code is obsessively commented code, but then I came across someone who suggested that the best code is code that is obvious without comments.

And I think the code you guys write is obvious to you without comments, just not to me.

@uberguber - no probs, whatever your style we all just want to learn and create.

@UberGoober That’s not always true. A lot of times I look at old code I wrote and I haven’t the slightest idea what I was doing. I might know what’s happening while I write the code and I’ll try to condense it right after I write it, but later on it’s a mystery to me.