Physics2D rotation question maybe solvable with joints I think (SIMPLIFIED-ER)

I have two boxes, each with one black edge, each potentially rotated any amount in any direction.

I need to make box A rotate to match box B using the least movement possible.

Is a joint the best way to control rotation like this?

This is an attempt I’ve made, but it doesn’t completely work, maybe because it doesn’t use joints:


                --get this body angle as modulo of 360
                local thisAngleMod = thisBody.angle % 360
                --get the other body angle as modulo of 360
                local otherAngleMod = otherBody.angle % 360
                --drift towards other body angle
                local angleDiff = thisAngleMod - otherAngleMod
                if angleDiff > 3 then
                    thisBody:applyTorque(math.random(-4800,-4800))
                elseif angleDiff < -3 then
                    thisBody:applyTorque(math.random(4800, 4800))
                end

The problem is that the object rotates a little past the desired direction and then bounces back and forth a little before stopping, and when it stops it’s not completely rotated correctly.

Could a joint achieve this better?

@UberGoober See this discussion using joints. Instead of joining boxes, I’m joining circles.

https://codea.io/talk/discussion/11530/locomotive-using-joints#latest

@dave1707 thats a cool project, and to understand it I made it into a stand-alone class, and added that version to the bottom of that thread.

So I think I get pretty well what that code is doing, and I don’t think it applies to my question, because I’m trying to directly rotate objects and your project only rotates them when they’re attached to something else moving.

@UberGoober In your first post, you said you needed a joint to connect the 2 boxes. The example I gave above shows how to connect objects with joints.

@dave1707 I understand, and the locomotion example is a really great demonstration of how joints work.

My first explanation was over-complicated, and maybe I hadn’t simplified my question when you read it; it now boils down to “I need to make two objects rotate to face the same direction and I think I need a joint to do it, does anyone know what kind and how?”

To remove confusion maybe I should edit the post and title to emphasize rotation more.

@UberGoober If you know the x,y,z angles of box B, then rotate box A’s x,y,z angles until it has the same values. Why connect them with joints.

@dave1707 sounds simple enough, right?

But unless I’m missing something it’s not—as I understand it, if you want to move something the right way, you can’t just say “move to x, y, z” you have to apply the right torque to get it to x, y, z.

I brought up joints because I saw where certain joints can constrain motion to certain angles, but I didn’t understand it, and the locomotive doesn’t use that ability as far as I could tell.

@UberGoober I haven’t played around with the physics body for awhile. Seems like you should be able to apply torque to each x or y or z until they match. I’ll have to try it and see what’s involved.

@dave1707 my lack of ability to exactly calculate the torque is why the example above can only get the angle within a certain range of precision—it applies torque only when the body is rotated too much one way or another by 3.

@UberGoober Are you talking about 2D squares or 3D cubes.

@UberGoober

Here is a non-joint solution which may or may not be what your after - based on simple physics example so might be a bit bloated. Basically compare the angles of both boxes and rotate the “follower” by a percentage of the difference in angles between the two.


-- Simple Physics

-- Use this function to perform your initial setup
function setup()
    print("Hello Physics!")
    
    print("Touch the screen to rotate nearest box - other will follow")
    parameter.number("delay",0,0.5,0.05)
    parameter.number("rotspd",1,5,1)
    -- Table to store our physics bodies
    bodies = {}
    
    -- Create some static boxes (not effected by gravity or collisions)
    local left = makeBox(WIDTH/4, HEIGHT/2, 50, 50, -30)
    left.type = STATIC
    
    local right = makeBox(WIDTH - WIDTH/4, HEIGHT/2, 50, 50, 30)
    right.type = STATIC
    
    local floor = makeBox(WIDTH/2, 10, WIDTH, 20, 0)
    floor.type = STATIC
    
    table.insert(bodies, left)
    table.insert(bodies, right)
    table.insert(bodies, floor)
    
    master=1 --set the left box as the lead to start with 
    slave=2 --slave rotates to master
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color
    background(40, 40, 50)
    
    -- Draw all our physics bodies
    for k,body in pairs(bodies) do
        drawBody(body)
    end
    
    
    diff=bodies[master].angle-bodies[slave].angle
    if diff~=0 then
        bodies[slave].angle = bodies[slave].angle + diff*delay
    end
    
end

function touched(touch)
    -- When you touch the screen, create a random box
    if touch.state == BEGAN  or touch.state==MOVING then
        local ang=rotspd
        if touch.x<WIDTH/2 then
            master=1
            slave=2
            if touch.x<WIDTH/4 then
                ang=-ang
            end
        else
            master=2
            slave=1
            if touch.x>0.75*WIDTH then
                ang=-ang
            end
        end
        
        bodies[master].angle = bodies[master].angle + ang
    end
end

-- Helper function to create a box using a polygon body
function makeBox(x,y,w,h,r)
    -- Points are defined in counter-clockwise order
    local body = physics.body(POLYGON,vec2(-w/2, h/2),
    vec2(-w/2, -h/2), vec2(w/2, -h/2), vec2(w/2, h/2))
    
    -- Set the body's transform (position, angle)
    body.x = x
    body.y = y
    body.angle = r
    
    -- Make movement smoother regardless of framerate
    body.interpolate = true
    
    return body
end

-- Helper function to draw a physics body
function drawBody(body)
    -- Push style and transform matrix so we can restore them after
    pushStyle()
    pushMatrix()
    
    strokeWidth(5)
    stroke(148, 224, 135, 255)
    translate(body.x, body.y)
    rotate(body.angle)
    
    -- Draw body based on shape type
    if body.shapeType == POLYGON then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CHAIN or body.shapeType == EDGE then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[j+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CIRCLE then
        strokeWidth(3.0)
        line(0,0,body.radius-3,0)
        ellipse(0,0,body.radius*2)
    end
    
    -- Restore style and transform
    popMatrix()
    popStyle()
end

Here’s my version. Slide the angle parameter to rotate the bottom square and the top square will follow. Slide the speed parameter to increase the speed of the top square.

viewer.mode=STANDARD

function setup()
    parameter.integer("angle",-360,360,0)
    parameter.integer("speed",1,30)
    e=physics.body(POLYGON,vec2(-80,80),vec2(-80,-80),vec2(80,-80),vec2(80,80))
    e.type=KINEMATIC 
    e.x=WIDTH/2
    e.y=200    
    e1=physics.body(POLYGON,vec2(-80,80),vec2(-80,-80),vec2(80,-80),vec2(80,80)) 
    e1.type=KINEMATIC 
    e1.x=WIDTH/2
    e1.y=500
end

function draw()
    background(40, 40, 50)
    stroke(255)
    strokeWidth(2)
    noFill()    
    pushMatrix()
    translate(e.x,e.y)
    rotate(-angle)
    rect(-80,-80,160,160)
    ellipse(0,80,20)
    popMatrix()   
    pushMatrix()
    translate(e1.x,e1.y)
    if angle~=e1.angle then
        vel=angle-e1.angle
        e1.angularVelocity=vel*speed
    else
        e1.angularVelocity=0
    end
    rotate(-e1.angle)
    rect(-80,-80,160,160)
    ellipse(0,80,20)
    popMatrix()
end

@West that’s almost exactly what I need! Question though: on my iPhone 8 the rotating square moves very slowly—is it supposed to be that way?

@dave1707 aren’t you cheating though, by using matrix rotation instead of physics engine forces?

@UberGoober I had set the boxes to rotate by one degree each time the touch is detected or a touch moves. I’ve edited the code to make the rotation a watch parameter now. I don’t know how you intended to make the boxes rotate in the first place so went for a simple touch nearest increments the angle by one degree.

@dave1707 I think I might have been misunderstanding your code. You’re only using the matrix for drawing, and you are accomplishing the rotation by changing angular velocity, whereas @West is forcing an angle value. I think your approach might be more “Box2D-ish”.

@UberGoober I’m using the e1.angularVelocity to rotate the square and then using the e1.angle and rotate to draw the new position of the square.

@dave1707, @West

Okay you guys gave me great pointers and this is almost there.

I need the bodies to be DYNAMIC, and I want to do it by applying forces not setting parameters, and the following adaptation of dave’s code works very well mostly.

The problem is that sometimes the follower will get into a pattern where it rocks back-and-forth and never stops, and I don’t know how to prevent that.


viewer.mode=STANDARD

function setup()
    parameter.integer("angle",-360,360,0)
    parameter.integer("speed",0,30,1)
    parameter.integer("linearDamping",0,300,10)
    parameter.integer("angularDamping",0,300,10)
    e=physics.body(POLYGON,vec2(-80,80),vec2(-80,-80),vec2(80,-80),vec2(80,80))
    e.gravityScale = 0
    e.type=DYNAMIC 
    e.x=WIDTH/2
    e.y=200    
    e1=physics.body(POLYGON,vec2(-80,80),vec2(-80,-80),vec2(80,-80),vec2(80,80)) 
    e1.gravityScale = 0
    e1.type=DYNAMIC 
    e1.x=WIDTH/2
    e1.y=500
end

function draw()
    background(40, 40, 50)
    stroke(255)
    strokeWidth(2)
    noFill()    
    pushMatrix()
    translate(e.x,e.y)
    rotate(-angle)
    rect(-80,-80,160,160)
    ellipse(0,80,20)
    popMatrix()   
    pushMatrix()
    translate(e1.x,e1.y)
    if angle % 360 ~= e1.angle % 360 then
        vel=(angle % 360 - e1.angle % 360)
        if vel ~= 0 and not e1.isRotating then
            e1.linearDamping = linearDamping
            e1.angularDamping = angularDamping
            e1.isRotating = true
            e1:applyTorque(vel*speed*100)
        else
            e1.linearDamping = 0
            e1.angularDamping = 0
            e1.isRotating = false
        end
    end
    rotate(-e1.angle)
    rect(-80,-80,160,160)
    ellipse(0,80,20)
    popMatrix()
end 

UPDATE

I modified it to evaluate the angles as modulos of 360°, so that it always takes the shortest route to match the angle, instead of spinning around a ton of times if the angles are different by huge numbers, because the physics engine remembers angles as cumulative rotations, meaning an angle value can be in the thousands or more.

The problem with my code seems to be that once the follower is moving it goes nuts if the angle slider gets set to 360, -360, or zero.

I can figure out how to detect for that situation, but can anybody tell me how to handle it when it happens?

deleted because of formatting bug