#### 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 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?

Tagged:

• 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?

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

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

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

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

• 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)

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)
end

-- Restore style and transform
popMatrix()
popStyle()
end

``````
• 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”.

• 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

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)

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

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