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