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

@UberGoober Not sure why yours is crashing. Maybe there’s something else that’s happening that we don’t see.

@dave1707 can you explain easily how you got those equations? thanks!

i changed @UberGoober 's to just check the last (prev) element, still crashes. no idea why yet.

@RonJeffries Read your latest article, The Sines of the Father. You mentioned that nobody wanted to use the translate, rotate, and scale functions. Using those functions would be an easy way to draw a sine curve of any size and angle. I think you missed what was needed. @UberGoober wanted a sine curve of any size and angle to create a physics CHAIN object of the sine curve. To create the CHAIN, a table of points are needed. To create the table, the points of the sine curve had to be calculated based on the size and angle of the sine curve. So each time the angle or size was changed, a new table was needed. Using the translate, rotate, and scale functions only altered what was being drawn and not the actual points.

@RonJeffries Just saw your question. That appeared while I was writing the above post. Anyways, I did a Google search for rotating a point around the origin. I’ll see if I can find the page I found.

.0027 is about 1/360. (370 really) so i think it’s compensating for the width scale somehow.

@RonJeffries That didn’t take long, first try. Here’s the link.

https://academo.org/demos/rotation-about-point/

yes, i missed the chain thing. still no idea why it crashes. i’ll see if i can come up with anything interesting.

@RonJeffries I would calculate the points for the sine curve along the x axis. For each point I would then recalculate the rotated point based on the angle. That would give me a table for the CHAIN function and for drawing the curve.

this is equivalent to @UberGoober 's (and still crashes). Just showing relevant bits in context.

if shouldRemake then
        prev=nil
        for z=0,360 do
            r = math.rad(z)
            pt = vec2(size*z,math.sin(r)*150)      
            curr = pt:rotate(math.rad(ang)) + vec2(300,300)
            if prev~=curr then
                table.insert(points, curr) 
                prev=curr
            else
                print("discarding duplicate")
            end
        end
        if physCurve then physCurve:destroy() end
        physCurve = physics.body(CHAIN, false, table.unpack(points))
        shouldRemake = false
    end

mine also calcs sin on x axis, then rotates, then translates to 300,300.

@RonJeffries Let me play with your code. It might do the exact same thing mine did. If that’s the case then I didn’t need those 2 calculations.

With this iteration I think I have exactly what I need.

@RonJeffries, turns out you’re exactly right—360 is the key number.

This uses POLYGON instead of CHAIN to avoid the crash.


viewer.mode=OVERLAY

function setup()
    parameter.number("spaceBetweenPeaks",.1,5,0.5)
    parameter.integer("waveHeight",1,300, 90)
    parameter.number("numWaves",0,10,4)
    ang = 30
    start = physics.body(CIRCLE, 30)
    start.type = STATIC
    start.position = vec2(300, 300)
    target = physics.body(CIRCLE, 30)
    target.type = STATIC
    target.position = vec2(600, 600)
    waveNeedsMaking = true
end

function makePhysicsWaveBetween(x1, y1, x2, y2)
    calculateSpacingToTarget()
    points = {}
    --angle between points:
    ang = math.atan2(y2 - y1, x2 - x1) * 180 / math.pi
    for z=0, 360*numWaves, 22 do
        x2=z
        y2=math.sin(math.rad(z))*waveHeight
        x1=x2*spaceBetweenPeaks*math.cos(math.rad(ang))-y2*math.sin(math.rad(ang))
        y1=y2*math.cos(math.rad(ang))+x2*spaceBetweenPeaks*math.sin(math.rad(ang))
        x1= x1 + start.x
        y1 = y1 + start.y
        ellipse(x1,y1,3)
        table.insert(points, vec2(x1,y1))
    end
    if physCurve then physCurve:destroy() end
    physCurve = nil
    if points then
        --physCurve = physics.body(CHAIN, false, table.unpack(points))
        physCurve = physics.body(POLYGON, table.unpack(points))
    end   
end

function draw()
    background(0)
    makePhysicsWaveBetween(start.x, start.y, target.x, target.y)
    fill(80, 233, 126)
    ellipse(start.x, start.y, 30)
    fill(80, 161, 233)
    ellipse(target.x, target.y, 30)
    pushStyle()
    strokeWidth(3.0)
    stroke(233, 80, 193)
    if physCurve then
        for j = 1,#physCurve.points-1 do
            a = points[j]
            b = points[j+1]
            line(a.x, a.y, b.x, b.y)
        end 
    end  
    popStyle()  
end

function calculateSpacingToTarget()
    --this is: length / (size * repeats)) = 360
    --so: size = length / repeats * 360
    local dist = start.position:dist(target.position)
    spaceBetweenPeaks = dist / (numWaves * 360)
end

-- this function makes the project stop with an error, not sure why
-- so it’s not actually used here
function calculateNumWavesToTarget()
    --this is: length / (size * numWaves)) = 360
    --so: length / size * 360 = numWaves
    local dist = start.position:dist(target.position)
    numWaves = dist / (spaceBetweenPeaks * 360)
end

function touched(touch)
    if target:testPoint(touch.pos) then
        target.position = touch.pos
    elseif start:testPoint(touch.pos) then
        start.position = touch.pos
    end
end

@RonJeffries In your rotate function below, you’re converting ang to radians. The rotate command uses degrees, not radians.

curr = pt:rotate(math.rad(ang)) + vec2(300,300)

PS. The documentation says rotate uses degrees, but it looks like it really uses radians, so your calculation is correct.

@Simeon Something is wrong with trying to copy code from the forum. Sometimes I can’t even select code, other times I can select some code but when I try to expand to highlighted copy area, as soon as I move the drag icon, the screen just scrolls halfway up the discussion over several posts and I can’t copy anything.

vec2 rotate takes radians. screen rotate takes degrees. codea is weird. :wink:

i’m sure it does same as yours. could be wrong, but i’d bet a coke.

@RonJeffries I don’t like it when something similar takes different arguments. I tried some calculations and the rotate was the same as the 2 calculations I was using. I totally forgot about the vec2 rotate and when I did the Google search and found the 2 simple calculations on the first web page, I just used them. It’s amazing how much stuff I forget until someone reminds me about them.

just messing around to understand, i might do this. not entirely happy with the remake flag, but don’t see anything nicer.

at my real keyboard i might try computing the wave just once, but i think there’s little reason to do it other than just because, even if i can. :smile:


viewer.mode=OVERLAY

function setup()
    ang, size, shouldRemake = 0, 1, true
    stroke(255, 0, 174)
    strokeWidth(3.0)
    points = {}
    physCurve = physics.body(CIRCLE,10)
end

function draw()
    background(0)
    make()
    polyline(physCurve.points) 
end

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

function polyline(pts)
    for i = 2,#pts do
        a = pts[i-1]
        b = pts[i]
        line(a.x,a.y, b.x,b.y)
    end
end

function make()
    if not shouldRemake then return end
    points = {}
    for z=0,360 do
        r = math.rad(z)
        pt = vec2(size*z,math.sin(r)*150)      
        curr = pt:rotate(ang) + vec2(300,300)
        table.insert(points, curr) 
    end
    physCurve:destroy()
    physCurve = physics.body(POLYGON, table.unpack(points)) 
    shouldRemake = false
end

no reason to make points up top, it turns out.

@dave1707 yes, i think it’s a mistake. don’t know if it’s unique to codea, or in lua. suspect the former, but i think we’re stuck with it.