Howdy, Stranger!

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

In this Discussion

Soft Bodies example - Modify 2d mesh in real time

edited July 2013 in Examples Posts: 437

It uses hidden control points (circle bodies) to modify the shape of the mesh, the function triangulate use all of these centers and then with a shader draw it.
Using some specific options of joints you can setup this behaviour, check out the code.

MyNode = class()
 
function MyNode:init(nSegments)
    touches = 0
    parameter.watch("touches")
    parameter.boolean("detach",false,detachInner)
    parameter.boolean("elastic",false,changeJoints)
    self.numSegments = nSegments or 12
    self.ptmRatio    = math.pi
    local center = vec2(WIDTH/2,HEIGHT/2)
    local springness = 4.0
    local deltaAngle = (2.0*math.pi)/self.numSegments
    local radius = 500
    self.bodies = {}
    local cos = math.cos
    local sin = math.sin
    for i=0, self.numSegments-1 do
        local theta = deltaAngle * i
        local x = radius * cos(theta)
        local y = radius * sin(theta)
        self.bodies[i] = self:createCircle(
            vec2( x/self.ptmRatio + math.random(-16,16), 
            y/self.ptmRatio + math.random(6) ) + center 
            ,37
        )
        self.bodies[i].info= {origin = vec2(x,y)}
    end
    -- inner circle
    self.inner = self:createCircle(center, 23)
    -- create joints
    self.inner.info = {
        joints = {}
    }
    self.inner.type = STATIC
    self.inner.fixedRotation = true
    for i=0, self.numSegments-1 do
        local neighborIndex = math.fmod (i+1, self.numSegments)
        local currentBody   = self.bodies[i]
        local neighborBody  = self.bodies[neighborIndex]
 
        currentBody.info.joint = physics.joint(
            DISTANCE, currentBody,neighborBody,
            currentBody.worldCenter,
            neighborBody.worldCenter
        )
        currentBody.info.color = color(
            math.random(10,80), math.random(20,60), math.random(166,255)
        )
 
        currentBody.info.joint.frequency = springness
        currentBody.info.joint.dampingRatio = 0.5
 
        -- connect center body with a joint
        self.inner.info.joints[i] = physics.joint(
            DISTANCE,
            currentBody, self.inner, 
            currentBody.worldCenter,
            center
        )
 
        self.inner.info.joints[i].frequency = springness
        self.inner.info.joints[i].dampingRatio = 0.1
    end
    -- create ground
    self.ground = physics.body(POLYGON,
        vec2(0,10),
        vec2(0,0),
        vec2(WIDTH,0),
        vec2(WIDTH,10)
    )
    self.ground.type = STATIC
    -- touches table
    self.touches = {}
 
    -- mesh
    self.m = mesh()
 
    self.m.shader = shader(
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
varying lowp vec4 vColor;
varying mediump vec4 vPos;
void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vPos = position;
 
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]],
[[
//Default precision qualifier
precision highp float;
 
//interpolated vertex color for this fragment
varying lowp vec4 vColor;
varying mediump vec4 vPos;
 
//Brick variables
uniform vec4 brickColor;
uniform vec4 mortarColor;
uniform float time;
uniform vec3 brickSize;
uniform vec3 brickPct;
 
void main()
{
    vec3 color;
    vec3 position, useBrick;
 
    position = vPos.xyz / brickSize.xyz;
 
    if( fract(position.y * 0.5) > 0.5 )
    {
        position.x += 0.5;
        position.z += 1.0;
    }
 
    position = fract(position);
    useBrick = step(position, brickPct.xyz);
 
    color = mix(mortarColor.rgb, brickColor.rgb, 
                useBrick.x * useBrick.y * useBrick.z);
    color *= vColor.rgb;
 
    //Set the output color to the texture color
    gl_FragColor = vec4(color, 1.0);
    vec2 uv = gl_FragColor.xy/vec3(1.0,1.0,0.0).xy;
    gl_FragColor = vec4(uv, 0.5 + 0.5 * sin(time), 1.0);
}
 
]])
    --self.m.shader = shader("Patterns:Bricks")
    self.m.shader.brickColor = vec4(0.1,0.3,0.8,1.0)  
    self.m.shader.mortarColor= vec4(0.8,0.8,0.8,1.0)
    self.m.shader.brickSize  = vec3(33,13,23) 
    self.m.shader.brickPct   = vec3(0.9,0.85,0.85)
    self.m.shader.time = ElapsedTime
    self.bg = readImage("physicsnotebook:background3")
end
 
function MyNode:createCircle(pos,r)
    local circle = physics.body(CIRCLE, r)
    circle.density = 0.1
    circle.restitution = 0.05
    circle.friction = 1.0
    circle.type = DYNAMIC
    circle.x = pos.x
    circle.y = pos.y
    circle.fixedRotation = true
    return circle
end
 
function MyNode:draw()
    sprite(self.bg, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
 
    fill(255)
    -- draw ground
    rectMode(CENTER)
    rect(
        self.ground.points[1].x/2, self.ground.points[1].y/2,
        self.ground.points[1].x, self.ground.points[1].y
    )
    strokeWidth(1)
    lineCapMode(ROUND)
    ellipseMode(RADIUS)
 
    -- draw inner joints
    for i=0,#self.inner.info.joints do
        local joint = self.inner.info.joints[i]
       --[[ line(
                joint.anchorA.x, joint.anchorA.y,
                joint.anchorB.x, joint.anchorB.y
        )]]--
    end
    local vs = {}
    for i=0, self.numSegments-1 do
        local body = self.bodies[i]
        -- draw joint also
        if body.info.joint then
           --[[ line(
                body.info.joint.anchorA.x, body.info.joint.anchorA.y,
                body.info.joint.anchorB.x, body.info.joint.anchorB.y
            )]]--
        end
        fill(body.info.color)
        ellipse(body.x, body.y, body.radius) 
        table.insert(vs, vec2(body.x,body.y))
    end
    fill(0)
    ellipse(self.inner.x, self.inner.y,self.inner.radius)
    --table.sort(vs, compareV2)
    self.m.vertices = triangulate(vs)
    self.m:setColors(127,127,127,155) 
    self.m.shader.time = ElapsedTime
    blendMode(MULTIPLY)
    self.m:draw()
    blendMode(NORMAL)
end
 
function MyNode:isTouchingBody(touch)
    local tpoint = vec2(touch.x, touch.y)
    for i=0, self.numSegments-1 do
        local body = self.bodies[i]
        if body:testPoint(tpoint) then 
           return body
        end
    end
    return nil
end
 
function MyNode:isDraggingKey(key)
    for i,b in ipairs(self.touches) do
        if key==b.key then return i end
    end
    return -1
end
 
function MyNode:touched(touch)
    -- check if it is touching a body:
    local index = self:isDraggingKey(touch.id)
    if index>-1 then
        local tpoint = vec2(touch.x,touch.y)
        if self.touches[index].body then
            local t =  self.touches[index]
           -- if t.body.info.origin:dist(tpoint)<t.body.radius*12 then
            if t.origin:dist(tpoint)<t.body.radius*12 then
                t.body.x = touch.x
                t.body.y = touch.y
            else
                t.body.x = t.origin.x
                t.body.y = t.origin.y
            end
 
            if touch.state == ENDED then
                --t.body.info.joint.
                --t.body.active = false
                table.remove(self.touches, index)
 
            end
        else
 
            table.remove(self.touches, index)
        end
    else
       local tbody = self:isTouchingBody(touch)
       if tbody~=nil then
        table.insert(self.touches,  {
            body = tbody,
            key  = touch.id,
            origin = vec2(tbody.x, tbody.y)
        })
 
        tbody.x = touch.x
        tbody.y = touch.y
        --print("created "..(#self.touches))
       end
    end
 
    touches = #self.touches   
end
 
function changeJoints(v)
    if not node then return end 
 
    if v then
        for i=0, node.numSegments-1 do
           node.bodies[i].info.joint:destroy()
        --node.bodies[i].info.joint.type = type
        end
    else
        for i =0, node.numSegments-1 do
            node.bodies[i].info.joint = physics.joint(
                REVOLUTE, node.bodies[i],node.bodies[math.fmod (i+1, node.numSegments)],
                node.bodies[i].worldCenter,
                node.bodies[math.fmod (i+1, node.numSegments)].worldCenter
            )
        end
    end
end 
 
function detachInner(v)
    if not node then return end 
    if v then
        node.inner.type = DYNAMIC
    else
        node.inner.type = STATIC
    end
end
 
function compareV2(v1,v2) 
    --return (v1.x>v2.x and v1.y>v2.y)
    return v1.x<v2.x and v1.y<v2.y
end
 
-- SoftBody
node = nil
-- Use this function to perform your initial setup
function setup()
    node = MyNode()
end
 
-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
 
    -- This sets the line thickness
    strokeWidth(5)
 
    -- Do your drawing here
    node:draw()
end
 
function touched(touch)
    node:touched(touch)
end

Video:

Comments

  • Posts: 2,820

    Haha. Thanks. This was on my todo list and I was about to start on it.

  • Posts: 494

    This is amazing! I love soft bodies)))

Sign In or Register to comment.