Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

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

edited August 29 in Questions Posts: 1,291

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?

Comments

  • edited August 29 Posts: 1,291

    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?

  • dave1707dave1707 Mod
    Posts: 9,441

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

    https://codea.io/talk/discussion/11530/locomotive-using-joints#latest
    
  • Posts: 1,291

    @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.

  • dave1707dave1707 Mod
    Posts: 9,441

    @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.

  • Posts: 1,291

    @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.

  • dave1707dave1707 Mod
    Posts: 9,441

    @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.

  • edited August 29 Posts: 1,291

    @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.

  • dave1707dave1707 Mod
    Posts: 9,441

    @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.

  • Posts: 1,291

    @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.

  • dave1707dave1707 Mod
    edited August 29 Posts: 9,441

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

  • edited August 31 Posts: 887

    @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
  • dave1707dave1707 Mod
    Posts: 9,441

    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
    
  • Posts: 1,291
    @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?
  • Posts: 887
    @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.
  • Posts: 1,291

    @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”.

  • dave1707dave1707 Mod
    edited August 31 Posts: 9,441

    @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.

  • edited August 31 Posts: 1,291

    @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.

  • Posts: 1,291
    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?
  • edited September 2 Posts: 1,291
    deleted because of formatting bug
  • Posts: 1,291

    @dave1707 , @West this works Ok:


    viewer.mode=STANDARD function setup() parameter.integer("angle",-360,360,0) parameter.integer("speed",0,30,1) parameter.watch("e_angle") parameter.watch("e1_angle") parameter.watch("absDiff") 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) --[[ adapted from the following code from http://www.iforce2d.net/b2dtut/rotate-to-angle: float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0; float totalRotation = desiredAngle - nextAngle;//use angle in next time step body->ApplyTorque( totalRotation < 0 ? -10 : 10 ); ]] diff = ((angle - e1.angle) % 360) absDiff = math.floor(math.abs(diff)) if absDiff > 180 then absDiff = 180 - absDiff end if absDiff ~= 0 then local nextAngle = e1.angle + (e1.angularVelocity / 3) local totalRotation if diff > 0 then totalRotation = angle - nextAngle else totalRotation = nextAngle - angle end local torque = 1000 if totalRotation < 0 then torque = torque * -1 end e1:applyTorque(torque*speed) end rotate(-e1.angle) rect(-80,-80,160,160) ellipse(0,80,20) popMatrix() e_angle = angle e1_angle = e1.angle angularVelocity = e1.angularVelocity end

    I adapted it from the website that is mentioned in the comments in the code. It’s not perfect, but I think it avoids the situation where it rotates forever it never stops.

  • dave1707dave1707 Mod
    Posts: 9,441

    @UberGoober I modified my above code to this. It moves the second square in the direction of the smallest rotation.

    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%360~=e1.angle%360 then
            vel=angle%360-e1.angle%360
            if math.abs(vel)>=180 then
                vel=vel*-1
            end
            vel=math.min(50,math.max(-50,vel))
            e1.angularVelocity=vel*speed
        else
            e1.angularVelocity=0
        end
        rotate(-e1.angle)
        rect(-80,-80,160,160)
        ellipse(0,80,20)
        popMatrix()
    end
    
  • Posts: 1,291

    @dave1707 that's some sweet code, I tried to figure that trick out myself and couldn’t do it.

    I incorporated it into my version, which uses dynamic bodies and torque, and I think it improved it.


    viewer.mode=STANDARD function setup() parameter.integer("angle",-360,360,0) parameter.integer("speed",0,30,1) 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 local nextAngle = e1.angle + (e1.angularVelocity / 3) diff = angle % 360 - nextAngle % 360 if math.abs(diff) >= 180 then diff = diff * -1 end diff=math.min(50,math.max(-50,diff)) local torque = 1000 if diff < 0 then torque = torque * -1 end e1:applyTorque(torque*speed) end rotate(-e1.angle) rect(-80,-80,160,160) ellipse(0,80,20) popMatrix() end

    Of course, it’s not really my version, it’s a hybrid of your version and the code I got from that other website.

    I have to admit I’m not totally sure how this hybrid version works, because it seems it does all these calculations, and the only result is it either applies 1000 or -1000 as torque.

    Your version I understand, because it changes the velocity based on the calculations, but this version doesn’t seem to do much at all with the calculations, it just sometimes adds a negative sign, but still, somehow, it works.

Sign In or Register to comment.