Skip to content

Instantly share code, notes, and snippets.

@juaxix
Created July 9, 2013 11:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save juaxix/5956606 to your computer and use it in GitHub Desktop.
Save juaxix/5956606 to your computer and use it in GitHub Desktop.
Codea - Fish.lua
Boid = class()
-- constructor used by fish
function Boid:init( _location, _maxSpeed, _maxForce)
self.velocity = vec2( math.random( -maxSpeed, maxSpeed ), math.random( -maxSpeed, maxSpeed ) )
self:createBoid(_location, _maxSpeed, _maxForce)
end
-- constructor used by bubbles
--[[
function Boid:init( _location, _maxSpeed, _maxForce, _velocity)
self.velocity = _velocity
self:createBoid(_location, _maxSpeed, _maxForce)
end
]]--
function Boid:createBoid( _location, _maxSpeed, _maxForce)
self.location = _location
self.maxSpeed = _maxSpeed
self.maxForce = _maxForce
self.acceleration = vec2( 0, 0 )
self.wanderTheta = 0
self.hasArrive = false
end
function Boid:update()
if not self.velocity then return end
self.velocity = self.velocity + self.acceleration
self.velocity = vec2(
math.min( self.velocity.x, self.maxSpeed),
math.min( self.velocity.y, self.maxSpeed)
)
self.location = self.location + self.velocity
self.acceleration = self.acceleration * 0
end
function Boid:debugRender()
noStroke()
fill(255, 0, 0,255)
ellipse(self.location.x, self.location.y, 10, 10);
end
function Boid:steer( _target, _slowdown )
local steer
local desired = _target - self.location
local dist = desired:len()
if ( dist > 0 ) then
desired = desired:normalize()
if ( _slowdown and dist < 60 ) then
desired = desired * ( self.maxSpeed * (dist / 60) )
if ( dist < 10 ) then
self.hasArrive = true
end
else
desired = desired * self.maxSpeed
end
steer = desired - self.velocity
steer = vec2(
math.min(steer.x, self.maxForce ),
math.min(steer.y, self.maxForce)
)
else
steer = vec2( 0, 0 )
end
return steer
end
function Boid:seek( _target )
self.acceleration = self.acceleration + self:steer( _target, false )
end
function Boid:arrive( _target )
self.acceleration = self.acceleration + self:steer( _target, true )
end
function Boid:flee( _target )
acceleration = acceleration - self:steer( _target, false )
end
function Boid:wander()
local wanderR = 5
local wanderD = 100
local change = 0.05
self.wanderTheta = self.wanderTheta + self.wanderTheta math.random( -change, change )
local circleLocation = self.velocity
circleLocation = circleLocation:normalize()
circleLocation = (circleLocation * wanderD) + self.location
local circleOffset = vec2(
wanderR * math.cos(self.wanderTheta),
wanderR * math.sin(self.wanderTheta )
)
local target = circleLocation + circleOffset
self:seek( target )
end
function Boid:evade( _target )
local lookAhead = self.location:dist( _target ) / (self.maxSpeed * 2)
local predictedTarget = vec2( _target.x - lookAhead, _target.y - lookAhead )
self:flee( predictedTarget )
end
Flagellum = class()
--[[
int numNodes
float[][] spine
float MUSCLE_RANGE = 0.15
float muscleFreq = 0.08
float sizeW, sizeH
float spaceX, spaceY
float theta
float count
]]--
function Flagellum:init( _sizeW, _sizeH, _numNodes )
self.MUSCLE_RANGE = 0.15
self.muscleFreq = 0.08
self.sizeW = _sizeW
self.sizeH = _sizeH
self.numNodes = _numNodes
self.spine = {} --new float[numNodes][2]
self.spaceX = _sizeW / (_numNodes + 1.0)
self.spaceY = _sizeH / 2.0
self.count = 0
self.theta = math.pi
self.thetaVel = 0
-- Initialize spine positions
for n = 0, _numNodes-1 do
local x = self.spaceX * n
local y = self.spaceY
self.spine[n] = {}
self.spine[n][0] = x
self.spine[n][1] = y
end
-- create mesh
self.m = mesh()
end
function Flagellum:swim()
self.spine[0][0] = math.cos( self.theta )
self.spine[0][1] = math.sin( self.theta )
self.count = self.count + self.muscleFreq:len()
local thetaMuscle = self.MUSCLE_RANGE * math.sin( self.count )
self.spine[1][0] = -self.spaceX * math.cos( self.theta + thetaMuscle ) + self.spine[0][0]
self.spine[1][1] = -self.spaceX * math.sin( self.theta + thetaMuscle ) + self.spine[0][1]
for n = 2 , self.numNodes - 1 do
local x = self.spine[n][0] - self.spine[n - 2][0]
local y = self.spine[n][1] - self.spine[n - 2][1]
local l = math.sqrt( (x * x) + (y * y) )
if ( l > 0 ) then
self.spine[n][0] = self.spine[n - 1][0] + (x * self.spaceX) / l
self.spine[n][1] = self.spine[n - 1][1] + (y * self.spaceX) / l
end
end
end
function Flagellum:draw()
self.m:draw()
--self:debugRender()
end
function Flagellum:debugRender()
for n = 0 ,self.numNodes - 1 do
stroke( 0 )
if ( n < self.numNodes - 1 ) then
line( self.spine[n][0], self.spine[n][1], self.spine[n + 1][0], self.spine[n + 1][1] )
end
fill( 90 )
ellipse( self.spine[n][0], self.spine[n][1], 6, 6 )
end
end
Fish = class ( Boid )
-- Constructor to create your personal, local fish
function Fish:init(location, maxSpeed, maxForce)
self.stripeColor= color(math.random(255), math.random(255), math.random(255))
self.bodySizeW = math.random( 100, 200 )
self.bodySizeH = self.bodySizeW * 0.3 + math.random( 55 )
self.originalBodySizeH = bodySizeH
self.originalBodySizeW = bodySizeW
self.id = "" .. math.random(1000000)
self.mousePosition = vec2(WIDTH/2, HEIGHT/2)
self.canonicalUnModdedLocation = location
self.startFade = false
self.lastAteTimer = 0
self.velocity = vec2( math.random( -maxSpeed, maxSpeed ), math.random( -maxSpeed, maxSpeed ) )
self:createBoid(_location, _maxSpeed, _maxForce)
self:createFish(location, maxSpeed, maxForce)
end
-- Constructor to create your local representation of a remote fish
--[[
function Fish:init(
location, _mousePosition, _stripeColor,
_id, maxSpeed, maxForce, _bodySizeW, _bodySizeH,
_velocity, _canonicalUnModdedLocation, _originalBodySizeW,
_originalBodySizeH, _startFade
)
self.mousePosition = _mousePosition
self.stripeColor = _stripeColor
self.id = _id
self.startFade = _startFade
-- temporarily set bodysize to be original values so that fins, etc are rendered
-- to the corret initial size
self.bodySizeW = _originalBodySizeW
self.bodySizeH = _originalBodySizeH
self.velocity = _velocity
self.canonicalUnModdedLocation = _canonicalUnModdedLocation
self.unModdedLocation = _canonicalUnModdedLocation
self.canonicalVelocity = _velocity
self:createFish(location, maxSpeed, maxForce)
-- set body size to be the proper adjusted body size now that the fish body
-- has been created
self.bodySizeW = _bodySizeW
self.bodySizeH = _bodySizeH
end
]]--
function Fish:createFish( _location, _maxSpeed, _maxForce)
self.mousePositionOld = self.mousePosition
self.location = _location
self.maxSpeed = _maxSpeed
self.maxForce = _maxForce
self.mainColor = color(0)
self.outlineColor = color(216,216,192,255)
self.transparency = 255
self.numBodySegments = 10
self.numTailSegments = 10
self.tailSizeW = self.bodySizeW * 0.6
self.tailSizeH = self.bodySizeH * 0.25
self.body = Flagellum( self.bodySizeW, self.bodySizeH, self.numBodySegments )
self.tailR = Flagellum( self.tailSizeW, self.tailSizeH, self.numTailSegments )
self.tailL = Flagellum( self.tailSizeW * 0.8, self.tailSizeH * 0.8, self.numTailSegments )
self.numFinSegments = 9
self.finR = Flagellum( self.tailSizeW * 0.5, self.tailSizeH, self.numFinSegments )
self.finL = Flagellum( self.tailSizeW * 0.5, self.tailSizeH, self.numFinSegments )
self.lastUpdateTime = Ellapsedtime -- miliseconds... ?
end
function Fish:updateFish()
if(self.startFade and self.transparency > 0) then
self.transparency = self.transparency - 1
end
self:update() -- parent update
-- keep unModdedLocations in sync with location
self.canonicalUnModdedLocation = self.canonicalUnModdedLocation + self.velocity
--[[
if(id = myId))
canonicalUnModdedLocation.add(velocity)
else
unModdedLocation.add(velocity)
]]--
self:checkBorders()
-- self:wander() --?
--self.body.muscleFreq = norm(super.velocity.len(), 0, 1) * 0.05
self.body.muscleFreq = self.velocity:normalize() * 0.05
-- Align body to velocity
--self.body.theta = self.velocity.heading2D() -- ? angle of rotation
self.body.theta = -math.atan2(-self.velocity.y, self.velocity.x)
self.body:swim()
local diffX = self.body.spine[self.numBodySegments-1][0] - self.body.spine[self.numBodySegments-2][0]
local diffY = self.body.spine[self.numBodySegments-1][1] - self.body.spine[self.numBodySegments-2][1]
local angle = math.atan2( diffY, diffX )
--self.tailR.muscleFreq = norm( super.velocity.len(), 0, 1 ) * 0.08
self.tailR.muscleFreq = self.velocity:normalize() * 0.08
self.tailR.theta = angle + (math.pi * 0.95)
self.tailR:swim()
--self.tailL.muscleFreq = norm( super.velocity.len(), 0, 1 ) * 0.08
self.tailL.muscleFreq = self.velocity:normalize()* 0.08
self.tailL.theta = angle + (math.pi * 1.05)
self.tailL:swim()
--self.finR.muscleFreq = norm( super.velocity.len(), 0, 1 ) * 0.04
self.finR.muscleFreq = self.velocity:normalize() * 0.04
self.finR:swim()
--finL.muscleFreq = norm( super.velocity.len(), 0, 1 ) * 0.04
self.finL.muscleFreq = self.velocity:normalize() * 0.04
self.finL:swim()
-- only change fish velocity if the mouse moved. this makes the fish keep moving as opposed
-- to "seizuring up" when they reach the mouse
if(self.mousePosition.x ~= self.mousePositionOld.x or
self.mousePosition.y ~= self.mousePositionOld.y
) then
self:seek(self.mousePosition)
end
self.mousePositionOld = self.mousePosition
end
function Fish:render()
noStroke()
-- render fins
local finLLocation = vec2( self.location.x + self.body.spine[3][0], self.location.y + self.body.spine[3][1] )
local finRLocation = vec2( self.location.x + self.body.spine[3][0], self.location.y + self.body.spine[3][1] )
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
self:renderFin(self.finR, finLLocation, self.bodySizeH * 0.5, 1)
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
self:renderFin(self.finL, finRLocation, -self.bodySizeH * 0.5, -1)
-- render body
fill(self.outlineColor.r,self.outlineColor.g,self.outlineColor.b, self.transparency)
self:renderBody( self.body, self.location, 1.1, 0.1 )
fill(self.stripeColor.r,self.stripeColor.g,self.stripeColor.b, self.transparency)
self:renderBody( self.body, self.location, 0.8, 0.15 )
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
self:renderBody( self.body, self.location, 0.5, 0.25 )
-- render tails
local tailLocation = vec2(
self.location.x + self.body.spine[self.numBodySegments - 1][0],
self.location.y + self.body.spine[self.numBodySegments - 1][1]
)
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
self:renderTail( self.tailR, tailLocation, 0.75 )
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
self:renderTail( self.tailL, tailLocation, 0.75 )
-- render head
local headLocation = vec2(
self.location.x + self.body.spine[1][0],
self.location.y + self.body.spine[1][1]
)
self:renderHead( headLocation, self.bodySizeW * 0.1, self.bodySizeW * 0.06 )
-- render mouth if fish ate recently
fill(250, 128, 114,255)
if(self.lastAteTimer > 0) then
self.lastAteTimer = self.lastAteTimer - 1
-- DeltaTime -> frameTime
local mouthSize = (self.bodySizeW*.05) * self.lastAteTimer / (DeltaTime/2)
ellipse(self.location.x,self.location.y,mouthSize,mouthSize)
end
end
function Fish:renderHead( _location, _eyeSize, _eyeDist )
local diffX = self.body.spine[2][0] - self.body.spine[1][0]
local diffY = self.body.spine[2][1] - self.body.spine[1][1]
local angle = math.atan2( diffY, diffX )
pushMatrix()
translate( _location.x, _location.y )
rotate( angle )
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
ellipse( 0, _eyeDist, _eyeSize, _eyeSize )
fill(self.stripeColor.r,self.stripeColor.g,self.stripeColor.b, self.transparency)
ellipse( -3, _eyeDist, _eyeSize * 0.35, _eyeSize * 0.35 )
popMatrix()
pushMatrix()
translate( _location.x, _location.y )
rotate( angle )
fill(self.mainColor.r,self.mainColor.g,self.mainColor.b, self.transparency)
ellipse( 0, -_eyeDist, _eyeSize, _eyeSize )
fill(self.stripeColor.r,self.stripeColor.g,self.stripeColor.b, self.transparency)
ellipse( -3, -_eyeDist, _eyeSize * 0.35, _eyeSize * 0.35 )
popMatrix()
end
function Fish:renderBody( _flag, _location, _sizeOffsetA, _sizeOffsetB )
pushMatrix()
translate( _location.x, _location.y )
--beginShape( TRIANGLE_STRIP )
local tt= {}
for n = 0 ,_flag.numNodes-1 do
local dx, dy
if ( n == 0 ) then
dx = _flag.spine[1][0] - _flag.spine[0][0]
dy = _flag.spine[1][1] - _flag.spine[0][1]
else
dx = _flag.spine[n][0] - _flag.spine[n - 1][0]
dy = _flag.spine[n][1] - _flag.spine[n - 1][1]
end
local theta = -math.atan2( dy, dx )
local t = n / (_flag.numNodes - 1)
local b = self:bezierPoint( 3,
self.bodySizeH * _sizeOffsetA,
self.bodySizeH * _sizeOffsetB,
2, t
)
--local b = t*(self.bodySizeH)/theta
local x1 = _flag.spine[n][0] - math.sin( theta ) * b
local y1 = _flag.spine[n][1] - math.cos( theta ) * b
local x2 = _flag.spine[n][0] + math.sin( theta ) * b
local y2 = _flag.spine[n][1] + math.cos( theta ) * b
--vertex( x1, y1 )
--vertex( x2, y2 )
-- add vertex to mesh
table.insert( tt,
vec3(x1, y1,0))
table.insert(tt,
vec3(x2, y2,0)
)
end
_flag.m.vertices = tt
-- Set all vertex colors to white
_flag.m:setColors(self.outlineColor)
-- draw mesh
_flag:draw()
--endShape()
popMatrix()
end
function Fish:renderTail( _flag, _location, _sizeOffset )
pushMatrix()
translate( _location.x, _location.y )
local tt = {}
for n = 0 ,_flag.numNodes-1 do
local dx, dy
if ( n == 0 ) then
dx = _flag.spine[1][0] - _flag.spine[0][0]
dy = _flag.spine[1][1] - _flag.spine[0][1]
else
dx = _flag.spine[n][0] - _flag.spine[n - 1][0]
dy = _flag.spine[n][1] - _flag.spine[n - 1][1]
end
local theta = -math.atan2( dy, dx )
local t = n / (_flag.numNodes - 1)
local b = self:bezierPoint( 2, _flag.sizeH, _flag.sizeH * _sizeOffset, 0, t )
--local b = 2*math.cos(ElapsedTime)
local x1 = _flag.spine[n][0] - math.sin( theta ) * b
local y1 = _flag.spine[n][1] - math.cos( theta ) * b
local x2 = _flag.spine[n][0] + math.sin( theta ) * b
local y2 = _flag.spine[n][1] + math.cos( theta ) * b
--vertex( x1, y1 )
--vertex( x2, y2 )
-- add vertex to mesh
table.insert( tt,
vec3(x1, y1,0)
)
table.insert( tt,
vec3(x2, y2,0)
)
end
_flag.m.vertices = tt
-- Set all vertex colors to white
_flag.m:setColors(0,255,0,255)
-- draw mesh
_flag:draw()
popMatrix()
end
function Fish:renderFin( _flag, _location, _posOffset, _flip )
local diffX = self.body.spine[2][0] - self.body.spine[1][0]
local diffY = self.body.spine[2][1] - self.body.spine[1][1]
local angle = math.atan2( diffY, diffX )
pushMatrix()
translate( _location.x, _location.y )
rotate( angle )
pushMatrix()
translate( 0, _posOffset )
local tt = {}
--beginShape(TRIANGLE_STRIP)
for n = 0, _flag.numNodes-1 do
local dx, dy
if ( n == 0 ) then
dx = _flag.spine[1][0] - _flag.spine[0][0]
dy = _flag.spine[1][1] - _flag.spine[0][1]
else
dx = _flag.spine[n][0] - _flag.spine[n - 1][0]
dy = _flag.spine[n][1] - _flag.spine[n - 1][1]
end
local theta = -math.atan2( dy, dx )
local t = n / (_flag.numNodes - 1)
local b = self:bezierPoint( 0,
_flip * _flag.sizeH * 0.75,
_flip * _flag.sizeH * 0.75,
0, t
)
local v = self:bezierPoint( 0,
_flip * _flag.sizeH * 0.05,
_flip * _flag.sizeH * 0.65,
0, t
)
local x1 = _flag.spine[n][0] - math.sin( theta ) * v
local y1 = _flag.spine[n][1] - math.cos( theta ) * v
local x2 = _flag.spine[n][0] + math.sin( theta ) * b
local y2 = _flag.spine[n][1] + math.cos( theta ) * b
--vertex( x1, y1 )
--vertex( x2, y2 )
-- add vertex to mesh
table.insert( tt,
vec3(x1, y1,0)
)
table.insert( tt,
vec3(x2, y2,0)
)
end
_flag.m.vertices = tt
-- Set all vertex colors to white
_flag.m:setColors(255,0,0,255)
-- draw mesh
_flag.m:draw()
--endShape()
popMatrix()
popMatrix()
end
function Fish:bezierPoint(s,cp1,cp2,d,t )
local t0 = math.pow(1-t, 3)*s
local sq = t*t
local sq1= (1-t)*(1-t)
local t1 = 3 * sq1*t*cp1
local t2 = 3 * (1-t) * sq *cp2
local t3 = math.pow(t,3)*d
return t0 + t1 + t2 + t3
end
function Fish:draw()
self:updateFish()
self:render()
end
--[[
* client side correction to correct velocity/location discrepancies between the local
* representation of a remote fish. This is called from the updateRemoteFish method
* in the Pond.
]]--
function Fish:applyClientSideCorrection()
-- correct location discrepancies
local locationDiscrepancy = self.canonicalUnModdedLocation - self.unModdedLocation
local locationCorrection = locationDiscrepancy * 0.1
self.location = self.location + locationCorrection
self.unModdedLocation = self.unModdedLocation + locationCorrection
-- correct velocity discrepancies
local velocityDiscrepancy = self.canonicalVelocity - self.velocity
local velocityCorrection = velocityDiscrepancy * 0.5
self.velocity = self.velocity + velocityCorrection
end
function Fish:roundToNearestRightBoundary(currentX)
local nearestRightBoundary
local roundingWidth = WIDTH + 2 * self.bodySizeW
local adjustedCurrentX = currentX + 2 * self.bodySizeW
local multiple = adjustedCurrentX / roundingWidth
if(math.ceil(multiple) - multiple < multiple - math.floor(multiple)) then
nearestRightBoundary = math.ceil(multiple) * roundingWidth - 2 * self.bodySizeW
else
nearestRightBoundary = math.floor(multiple) * roundingWidth - 2 * self.bodySizeW
end
return nearestRightBoundary
end
function Fish:roundToNearestLeftBoundary(currentX)
local nearestLeftBoundary
local roundingWidth = WIDTH + 2 * self.bodySizeW
local adjustedCurrentX = currentX
local multiple = adjustedCurrentX / roundingWidth
if(math.ceil(multiple) - multiple < multiple - math.floor(multiple)) then
nearestLeftBoundary = math.ceil(multiple) * roundingWidth
else
nearestLeftBoundary = math.floor(multiple) * roundingWidth
end
return nearestLeftBoundary
end
function Fish:roundToNearestBottomBoundary(currentY)
local nearestBottomBoundary
local roundingHeight = HEIGHT + 2 * self.bodySizeW
local adjustedCurrentY = currentY + 2 * self.bodySizeW
local multiple = adjustedCurrentY / roundingHeight
if(math.ceil(multiple) - multiple < multiple - math.floor(multiple)) then
nearestBottomBoundary = math.ceil(multiple) * roundingHeight - 2 * self.bodySizeW
else
nearestBottomBoundary = math.floor(multiple) * roundingHeight - 2 * self.bodySizeW
end
return nearestBottomBoundary
end
function Fish:roundToNearestTopBoundary(currentY)
local nearestTopBoundary
local roundingHeight = HEIGHT + 2 * self.bodySizeW
local adjustedCurrentY = currentY
local multiple = adjustedCurrentY /roundingHeight
if(math.ceil(multiple) - multiple < multiple - math.floor(multiple)) then
nearestTopBoundary = math.ceil(multiple) * roundingHeight
else
nearestTopBoundary = math.floor(multiple) * roundingHeight
end
return nearestTopBoundary
end
--[[
* ensure that the fish wraps around the the other end of the pond
* if it goes past the edge. Also keeps unModdedLocations in sync with
* location
]]--
function Fish:checkBorders()
if ( self.location.x < -self.bodySizeW ) then
self.location.x = WIDTH
self.canonicalUnModdedLocation.x = self:roundToNearestRightBoundary(self.canonicalUnModdedLocation.x)
--[[if (id.equals(myId))
canonicalUnModdedLocation.x = roundToNearestRightBoundary(canonicalUnModdedLocation.x)
else
unModdedLocation.x = roundToNearestRightBoundary(unModdedLocation.x)
]]--
end
if ( self.location.x > WIDTH + self.bodySizeW ) then
self.location.x = 0
self.canonicalUnModdedLocation.x = self:roundToNearestLeftBoundary(self.canonicalUnModdedLocation.x)
--[[if (id.equals(myId))
canonicalUnModdedLocation.x = roundToNearestLeftBoundary(canonicalUnModdedLocation.x)
else
unModdedLocation.x = roundToNearestLeftBoundary(unModdedLocation.x)
]]--
end
if ( self.location.y < -self.bodySizeW ) then
self.location.y = HEIGHT
self.canonicalUnModdedLocation.y = self:roundToNearestBottomBoundary(self.canonicalUnModdedLocation.y)
--[[if (id.equals(myId))
canonicalUnModdedLocation.y = roundToNearestBottomBoundary(canonicalUnModdedLocation.y)
else
unModdedLocation.y = roundToNearestBottomBoundary(unModdedLocation.y)
]]--
end
if ( self.location.y > HEIGHT + self.bodySizeW ) then
self.location.y = 0
self.canonicalUnModdedLocation.y = self:roundToNearestTopBoundary(self.canonicalUnModdedLocation.y)
--[[if (id.equals(myId))
canonicalUnModdedLocation.y = roundToNearestTopBoundary(canonicalUnModdedLocation.y)
else
unModdedLocation.y = roundToNearestTopBoundary(unModdedLocation.y)
]]--
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment