# Making CHAIN physics bodies follow sine curves from any point to any other

I’ve been trying to understand the “test edges and chains“ example in PhysicsLab.

I tried to pick the code apart and explicate the variables, but I’m not sure I did it right:

``````
-- test edges & chains
local points = {}
local overallY = WIDTH * 0.08
local peaksMultiple = WIDTH * 0.00003
local waveHeight = HEIGHT * 0.025
local segmentLength = WIDTH/150
for i = 0, WIDTH, segmentLength do
table.insert(points, vec2(i,(math.sin(i*peaksMultiple) * waveHeight) + overallY))
end

local ground = physics.body(CHAIN, false, table.unpack(points))

``````

What I would like to do is create a gently sloping curve, just one, with its peak in the middle of the screen, kind of like the curve in the attached picture.

I’ve been able to make that happen on a single device in a single orientation, just by fiddling around with the variables until it looked right, but I can’t figure out how to do it on any device in any orientation.

``````viewer.mode=STANDARD

function setup()
parameter.integer("high",0,500,200)
parameter.integer("step",45,720,280)
fill(255)
end

function draw()
background()
for x=0,WIDTH do
ellipse(x,y+HEIGHT/2,4)
end
end
``````

@dave1707 thanks, that gave me what I needed to figure this out.

The adapted version below shows the solution I needed.

In the screen shots you can see that, in portrait mode, both waves look the same, because they both use the same height and step values—but the difference is that the top one is using hard-coded values and the bottom one is calculating those values relative to screen width.

That’s why (I think) in landscape mode the curves look different. The hard-coded values result in a different width-to-height ratio for some reason.

But the lower curve keeps the same ratio as in portrait because (I think) it uses a ratio to calculate its dimensions to begin with.

``````
viewer.mode=OVERLAY
function setup()

parameter.integer("high",0,500,50)
parameter.integer("step",45,720,180)

local widthRatio = 50 / math.min(WIDTH, HEIGHT)
waveHeight = WIDTH * widthRatio
local points = {}
local overallY = 130
local thisStep = 180
for i = 0, WIDTH do
table.insert(points, vec2(i, overallY + math.sin(math.rad(i/(WIDTH/thisStep))) * waveHeight))
end
ground = physics.body(CHAIN, false, table.unpack(points))
end

function draw()
background()
stroke(255)
for x=0,WIDTH do
ellipse(x,y+HEIGHT/2,4)
end
strokeWidth(4)
stroke(167, 236, 67)
local points = ground.points
for j = 1,#points-1 do
a, b = points[j], points[j+1]
line(a.x, a.y, b.x, b.y)
end
end

``````

@dave1707 kind of tangentially (heh?) I wonder if you know how to draw the curve between any two points. Like to go from the lower left of the screen to the upper right, for example. I’ve been trying to figure it out but I don’t think my code’s even worth posting. Thanks for your help always.

if you can draw it from, say, x=0 to x-n, you can scale by desired/n to get it 0-desired. you can translate (x,y) to get it to start at x,y. you can rotate to get it to any desired angle. don’t forget to push and pop the matrix.

@UberGoober I guess that would depend on how much of the sine curve you want between the 2 points. In my example above, you can adjust the curve so the right side of the curve would pass through some point on the right side of the screen.

@RonJeffries I am working with Physics bodies, and `translate` easily changes where something is drawn (like in your article), but with Physics bodies I need to change where it actually is.

The equivalent of using translate, I guess, would be to make the wave horizontally, like normal, move its origin to the midpoint of the desired points, and then change its angle so each end touches the desired points—that’s a lot of math, though, and if there’s a way to just directly calculate the same points using `math.sin` that would be better, I think.

@dave1707 I’m trying to write a method along the lines of `makeWaveBetweenPoints(pointA, pointB, numPeaks, heightOfPeaks)` so I need to control start point, end point, and the wave properties separately.

I’ve found this formula on Stack Overflow:

``````
ParametricPlot[{t/Sqrt - Sin[t]/Sqrt, t/Sqrt + Sin[t]/Sqrt},{t,-10,10}]

``````

…I’m trying to figure out how to apply it….

@UberGoober Try this for starters.

``````viewer.mode=STANDARD

function setup()
parameter.integer("ang",0,90,30)
parameter.number("size",.1,5,1)
parameter.integer("high",50,300)
parameter.integer("repeats",1,20,3)
fill(255)
end

function draw()
background(0)
for z=0,360*repeats do
x2=z
ellipse(x1,y1,3)
end
end
``````

@dave1707 that’s fantastic! I’ve been playing with that formula I found for hours and I’ve been getting only hideous results.

@dave1707 ok I think this does everything I need, though I’m not sure my modifications are done in the best way:

``````
viewer.mode=OVERLAY

function setup()
ang = 30
parameter.number("size",.1,5,1)
parameter.integer("high",0,300, 150)
parameter.number("repeats",0,20,3, function()
if not lastTouch then
lastTouch = vec2(WIDTH, HEIGHT)
end
calculateSizeFromLastTouch()
end)
parameter.number("startX", 0, WIDTH)
parameter.number("startY", 0, HEIGHT)
fill(255)
end

function angleBetween(x1, y1, x2, y2)
local dx, dy, radsToDegrees
dx = x2 - x1
dy = y2 - y1
radsToDegrees = 180 / math.pi
return math.atan2(dy, dx) * radsToDegrees
end

function draw()
background(0)
fill(80, 161, 233)
for z=0,360*repeats do
x2=z
ellipse(x1 + startX,y1 + startY,3)
end
end

function calculateSizeFromLastTouch()
size = vec2(startX, startY):dist(lastTouch) * 0.0027 / repeats
end

function touched(touch)
lastTouch = touch.pos
ang = angleBetween(startX, startY, touch.x, touch.y)
calculateSizeFromLastTouch()
end

``````

In particular the fact that I’m using the decimal 0.0027 as a multiplier in `calculateSizeFromLastTouch` seems completely arbitrary.

There must be a way to make that calculation based only on known quantities.

!!!

This is functionally exactly the same code as above, except I attempted to actually use it to create CHAIN-type physics bodies.

It seems to work fine… except it crashes Codea really quickly.

—any idea why??

``````
viewer.mode=OVERLAY

function setup()
ang = 30
parameter.number("size",.1,5,1)
parameter.integer("high",0,300, 150)
parameter.number("repeats",0,20,3, function()
if not lastTouch then
lastTouch = vec2(WIDTH, HEIGHT)
end
calculateSizeFromLastTouch()
end)
parameter.number("startX", 0, WIDTH)
parameter.number("startY", 0, HEIGHT)
fill(255)
lastSize, lastHigh, lastRepeats, lastStartX, lastStartY = 0,0,0,0,0
end

function makePhysicsBodyFromCurve()
if physCurve then physCurve:destroy() end
physCurve = nil
if points then
physCurve = physics.body(CHAIN, false, table.unpack(points))
end
collectgarbage()
end

function angleBetween(x1, y1, x2, y2)
local dx, dy, radsToDegrees
dx = x2 - x1
dy = y2 - y1
radsToDegrees = 180 / math.pi
return math.atan2(dy, dx) * radsToDegrees
end

function draw()
background(0)
fill(80, 161, 233)
points = {}
for z=0,360*repeats do
x2=z
x1= x1 + startX
y1 = y1 + startY
ellipse(x1,y1,3)
table.insert(points, vec2(x1,y1))
end
if lastSize ~= size or
lastHigh ~= high or
lastRepeats ~= repeats or
lastStartX ~= startX or
lastStartY ~= startY then
makePhysicsBodyFromCurve()
lastSize = size
lastHigh = high
lastRepeats = repeats
lastStartX = startX
lastStartY = startY
end
pushStyle()
strokeWidth(3.0)
stroke(233, 80, 193)
for j = 1,#physCurve.points-1 do
a = points[j]
b = points[j+1]
line(a.x, a.y, b.x, b.y)
end
popStyle()
end

function calculateSizeFromLastTouch()
size = vec2(startX, startY):dist(lastTouch) * 0.0027 / repeats
end

function touched(touch)
lastTouch = touch.pos
ang = angleBetween(startX, startY, touch.x, touch.y)
calculateSizeFromLastTouch()
end

``````

ok, but why do the math for yourself when codea will do it for you?

@RonJeffries not sure what you mean—there’s math I gotta do either way, right?

I have to calculate the sine wave either way, Codea won’t do that, and then to rotate it into the right position I have to manually calculate the midpoint between the target points, and calculate the angle, and yadda yadda yadda.

But the separation of the creation and placement of the physics body is kind of weird, to me, in that it seems odd to open myself to an opportunity for a whole separate category of errors—errors rooted in creating the thing separately from placing it.

I have to do the sine calculations no matter what (don’t I?) so why add an extra layer of opportunities for failure when I could just do all the calculations in the same formula that calculates the wave?

@John, @Simeon, I think this may be a bug in 2D physics CHAIN handling.

This code boils it down: all it takes to cause a crash is to put your finger down anywhere and then drag it towards the start of the curve—at least, that’s what’s been crashing my iPad twenty times an hour this evening.

The reason I think it’s a bug is that if you make the body a POLYGON instead of a CHAIN the crash does not happen—which makes it seem likely there’s something going wrong with CHAINs.

``````
viewer.mode=OVERLAY

function setup()
ang, size, shouldRemake = 30, 1, true
stroke(255, 0, 174)
strokeWidth(3.0)
end

function draw()
background(0)
points = {}
for z=0,360 do
x2=z
x1= x1 + 300
y1 = y1 + 300
table.insert(points, vec2(x1,y1))
end
if shouldRemake then
if physCurve then physCurve:destroy() end
physCurve = physics.body(CHAIN, false, table.unpack(points))
-- if you comment out the CHAIN version above and uncomment
-- the POLYGON version below, the crash does not happen
-- physCurve = physics.body(POLYGON, table.unpack(points))
shouldRemake = false
end
for j = 1,#physCurve.points-1 do
a = points[j]
b = points[j+1]
if a and b then
line(a.x, a.y, b.x, b.y)
end
end
end

function touched(touch)
ang = math.atan2(touch.y - 300, touch.x - 300) * (180 / math.pi)
size = vec2(300, 300):dist(touch.pos) * 0.0027
shouldRemake = true
end

``````

I think the reason is that CHAIN can’t handle duplicate points in a row. Run this code and drag your finger on the screen. When cnt reaches 80, I create a duplicate occurrence in the table tab. As long as you don’t exceed 80 it doesn’t crash. When you lift your finger, I create a chain.

``````viewer.mode=STANDARD

function setup()
parameter.watch("cnt")
fill(255)
tab={}
end

function draw()
background(0)
for a,b in pairs(tab) do
ellipse(b.x,b.y,4)
end
end

function touched(t)
if t.state==BEGAN then
tab={}
cnt=0
end
if t.state==CHANGED and t.x>0 then
cnt=cnt+1
table.insert(tab,vec2(t.x,t.y))
if cnt==80 then
print("duplicate")
table.insert(tab,vec2(t.x,t.y))
end
end
if t.state==ENDED then
s=physics.body(CHAIN, false, table.unpack(tab))
end
end
``````

@dave1707 your example looks solid, what’s wrong with mine, below?

I’m trying to detect duplicate points and discard them, but it still crashes.

Am I doing it wrong?

``````
viewer.mode=OVERLAY

function setup()
ang, size, shouldRemake = 30, 1, true
stroke(255, 0, 174)
strokeWidth(3.0)
end

function draw()
background(0)
points = {}
if shouldRemake then
for z=0,360 do
x2=z
x1= x1 + 300
y1 = y1 + 300
local notDuplicate = true
for _, pnt in ipairs(points) do
if pnt.x == x1 and pnt.y == y1 then
notDuplicate = false
end
end
if notDuplicate then
table.insert(points, vec2(x1,y1))
else
end
end
if physCurve then physCurve:destroy() end
physCurve = physics.body(CHAIN, false, table.unpack(points))
shouldRemake = false
end
for j = 1,#physCurve.points-1 do
a = physCurve.points[j]
b = physCurve.points[j+1]
if a and b then
line(a.x, a.y, b.x, b.y)
end
end
end

function touched(touch)
ang = math.atan2(touch.y - 300, touch.x - 300) * (180 / math.pi)
size = vec2(300, 300):dist(touch.pos) * 0.0027
shouldRemake = true
end

``````

Here’s an article I wrote on using the translate, rotate, and scale capabilities of Codea to draw a sine wave at various sizes and angles. It’s not solving the problem here, but the techniques are those I would use to solve this one too.

Note that it only calculates the curve once, in setup.

Questions welcome. Code follows.

Article: The Sines of the Fathers

``````-- Demonstrate translate, rotate, scale
-- RJ 20211010

function setup()
print("This example draws a two-cycle sine wave between the first two touches on the screen.")
touches = {}
sine = {}
size = 50
local step = 0.1
for x = 0,4*math.pi,step do
table.insert(sine, vec2(size*x,size*math.sin(x)))
end
end

function touched(touch)
if touch.state == ENDED or touch.state == CANCELLED then
touches[touch.id] = nil
else
touches[touch.id] = touch
end
end

function draw()
background(0, 0, 0, 255)
stroke(255)
strokeWidth(3)
for k,touch in pairs(touches) do
math.randomseed(touch.id)
fill(math.random(255),math.random(255),math.random(255))
ellipse(touch.pos.x, touch.pos.y, 100, 100)
end
local myTouches = {}
for k,touch in pairs(touches) do
table.insert(myTouches,touch)
end
pushMatrix()
local pos1, pos2
if #myTouches == 0 then
translate(WIDTH/4, HEIGHT/2)
else
pos1 = myTouches.pos
translate(pos1.x,pos1.y)
end
if #myTouches >= 2 then
local pos2 = myTouches.pos
local ang = math.atan(pos2.y-pos1.y, pos2.x-pos1.x)
rotate(math.deg(ang))
local dist = pos2:dist(pos1)
scale(dist/(4*size*math.pi))
end
drawSine()
popMatrix()
end

function drawSine()
for i = 2,#sine do
local p0 = sine[i-1]
local p1 = sine[i]
line(p0.x,p0.y, p1.x,p1.y)
end
left = vec2(-10,0)
right = vec2(10 + size*4*math.pi, 0)
line(left.x,left.y, right.x,right.y)
end
``````

I see the thread name changed. Confused me I’m not sure how one would do the same thing with physics, certainly not with the screen functions. Different problem, different tools. Sorry for any confusion, HTH as always.

@UberGoober You only need to check the current set of points with the last set of points. There can be duplicate points, just not 2 in a row. I don’t know if CHAIN subtracts the last 2 sets of points and if it’s 0 then it crashes.