Learning Gideros (and Lua) : Porting Advanced AS3 Animations code

Today I had a chance to play with Gideros SDK. In order to get a better understanding about the SDK and Lua in general, I decided to port some AS3 code. Here are some code from Keith Peter’s book, Advanced AS3 animations, in Lua.

I haven’t fully understood Lua so these code might look bad in the eyes of Lua experts 🙂 but at least it works.

[note color=”#FFCC00″]You can get the original, fully commented AS3 code from http://www.apress.com/9781430216087[/note]

First, the Vector2D. I don’t know if Lua has a built-in Vector class, maybe it does but as part of the learning process, I wrote one myself. Porting Vector2D.as to Vector2D.lua was almost as easy as copy-pasting.

--[[
Vector 2D class. Ported to Lua from Keith Peter's AS3 Vector2D
Author : Anggie Bratadinata | www.masputih.com
--]]

Vector2D = {}
Vector2D.__index = Vector2D

-- constructor
function Vector2D.new(initX,initY)
	local instance = {
		x = initX,
		y = initY
	}
	setmetatable(instance,Vector2D)
	return instance
end

--[[
	BASIC MATH OPS
--]]

function Vector2D:dotProduct(v2)
	return self.x * v2.x + self.y * v2.y
end

function Vector2D:angleFrom(v2)

	local v1 = self
	
	if self:isNormalized() == true 
	then
		v1 = self:clone()
		v1:normalize()
	end
	
	if v2:isNormalized() == true
	then
		v2 = v2:clone()
		v2:normalize()
	end
	
	return math.acos(v1:dotProd(v2))
	
end

function Vector2D:add(v2)
	self.x = self.x + v2.x
	self.y = self.y + v2.y
end

function Vector2D:subtract(v2)
	self.x = self.x - v2.x
	self.y = self.y - v2.y
end

function Vector2D:multiply(value)
	self.x = self.x * value
	self.y = self.y * value
end

function Vector2D:divide(value)
	self.x = self.x / value
	self.y = self.y / value
end

--[[
	LENGTH
--]]
function Vector2D:setLength(value)
	local a = self:getAngle()
	self.x = math.cos(a) * value
	self.y = math.sin(a) * value
end

function Vector2D:getLength()
	return math.sqrt(self:getSquareLength())
end

function Vector2D:getSquareLength()
	return self.x * self.x + self.y * self.y
end

-- normalization
function Vector2D:normalize()
	if self:getLength() == 0
	then
		self.x = 1
		self.y = 0
	else
		local l = self:getLength()
		self.x = self.x/l
		self.y = self.y/l
	end
	return self
end

function Vector2D:isNormalized()
	return self:getLength() == 1
end

--[[
	ANGLE
--]]
function Vector2D:getAngle()
	return math.atan2(self.y,self.x)
end

function Vector2D:setAngle(value)
	local l = self:getLength()
	self.x = math.cos(value) * l
	self.y = math.sin(value) * l
end

--[[
	DISTANCE
--]]

-- get the distance of the given vector from this vector
function Vector2D:getDistanceOf(v2)
	return math.sqrt(self:getSquareDistance(v2))
end

function Vector2D:getSquareDistance(v2)
	local dx = v2.x - self.x
	local dy = v2.y - self.y
	return dx * dx + dy * dy
end

--[[
	UTILITIES
--]]

-- determine if the given vector is to the right (+1) or to the left (-1)
-- of this vector
function Vector2D:getSignOf(v2)
	local perp = self:getPerpendicular()
	if perp:dotProduct(v2) < 0
	then
		return -1
	else
		return 1
	end
end

-- return a new vector that is perpendicular to this vector
function Vector2D:getPerpendicular()
	return Vector2D.new(-self.y,self.x)
end


function Vector2D:truncate(maxLength)
	self:setLength(math.min(maxLength,self:getLength()))
end

function Vector2D:zero()
	self.x = 0
	self.y = 0
end

function Vector2D:isZero()
	if self.x == 0 and self.y == 0
	then
		return true
	else
		return false
	end
	
end

function Vector2D:equals(v2)
	return self.x == v2.x and self.y == v2.y
end

function Vector2D:clone()
	return Vector2D.new(self.x,self.y)
end

function Vector2D:toString()
	print("Vector2D[",self.x,",",self.y,"]");
end

-- so that this class can be imported by "geom.Vector2D"
return Vector2D

Below is the Vehicle class which is the base class of all vehicles. Vehicles can be moved by updating their velocity vector.

require "geom.Vector2D"
-- subclass Gideros' Shape class
Vehicle = Core.class(Shape)

function Vehicle:init()
	--instance vars
	self.positionVector = Vector2D.new(0,0)
	self.mass = 1
	self.velocityVector = Vector2D.new(0,0)
	self.maxSpeed = 4
	
	self:draw(0x000000)
end

function Vehicle:draw(color)
	self:setFillStyle(Shape.SOLID,color)
	self:beginPath()
	self:moveTo(0,0)
	self:lineTo(10,5)
	self:lineTo(0,10)
	self:lineTo(0,0)
	self:endPath()
end

function Vehicle:update()
	
	local vel = self.velocityVector
	vel:truncate(self.maxSpeed)
	
	local pos = self.positionVector
	pos:add(vel)
	
	local w = application:getContentWidth()
	local h = application:getContentHeight()
	
	if pos.x > w 
	then
		pos.x = w
		vel.x = vel.x * -1
	elseif pos.x < 0
	then
		pos.x = 0
		vel.x = vel.x * -1
	end
	
	if pos.y > h
	then
		pos.y = h
		vel.y = vel.y * -1
	elseif pos.y < 0
	then
		pos.y = 0
		vel.y = vel.y * -1
		
	end
	
	self:setPosition(pos.x,pos.y)
	self:setRotation(vel:getAngle() * 180/math.pi)
	
end

function Vehicle:setPosition(x,y)
	-- call super class method
	Shape.setPosition(self,x,y)
	
	-- update position vector
	self.positionVector.x = x;
	self.positionVector.y = y;
	
end

return Vehicle

Here’s the SteeredVehicle class that I can steer by adding steering force (vector). This class has seek() and flee() methods, both take Vector2D as their parameters, which are used for setting the object’s behavior. If seek is called, the object will try to get to the designated vector. If flee is called, it will try to get away from the designated vector.

require "geom.Vector2D"
require "vehicles.Vehicle"
-- subclass the Vehicle class
SteeredVehicle = Core.class(Vehicle)

-- constructor
function SteeredVehicle:init()
	Vehicle.init(self)
	self.steeringForce = Vector2D.new(1,2)
	self.maxForce = 1		
	self.mass = 10
end

function SteeredVehicle:update()

	self.steeringForce:truncate(self.maxForce)
	self.steeringForce:divide(self.mass)
	self.velocityVector:add(self.steeringForce)
	self.steeringForce = Vector2D.new(0,0)
	
	Vehicle.update(self)
end

function SteeredVehicle:seek(target)

	local desiredVelocity = target:clone()
	desiredVelocity:subtract(self.positionVector)
	desiredVelocity:normalize()
	desiredVelocity:multiply(self.maxSpeed)

	local force = desiredVelocity:clone()
	force:subtract(self.velocityVector)
	self.steeringForce:add(force)
	
end

function SteeredVehicle:flee(target)

	local desiredVelocity = target:clone()
	desiredVelocity:subtract(self.positionVector)
	desiredVelocity:normalize()
	desiredVelocity:multiply(self.maxSpeed)

	local force = desiredVelocity:clone()
	force:subtract(self.velocityVector)
	self.steeringForce:subtract(force)
	
end

Finally, the main application file.

require "vehicles.SteeredVehicle"

local v = SteeredVehicle.new()
v:draw(0x00FF00)
v:setPosition(100,100)
v.velocityVector:setLength(5)
v.velocityVector:setAngle(-math.pi/4)

local v1 = SteeredVehicle.new()
v1:draw(0x0000FF)
v1:setPosition(300,300)
v1.velocityVector:setLength(5)
v1.velocityVector:setAngle(-math.pi/2)

local v2 = SteeredVehicle.new()
v2:draw(0xFF0000)
v2:setPosition(0,0)
v2.velocityVector:setLength(4)
v2.velocityVector:setAngle(math.pi/3)

-- fps
local fpsLabel = TextField.new(nil,"fps")
fpsLabel:setTextColor(0x000000)
fpsLabel:setPosition(10,10)


function onEnterFrame(event)
	
	v:seek(v2.positionVector)
	v:flee(v1.positionVector)
	
	v1:seek(v.positionVector)
	v1:flee(v2.positionVector)
	
	v2:seek(v1.positionVector)
	v2:flee(v.positionVector)
	
	v:update()
	v1:update()
	v2:update()
	
	fpsLabel:setText("fps ".. application:getFps())
end

stage:addChild(v)
stage:addChild(v1)
stage:addChild(v2)
stage:addChild(fpsLabel)
stage:addEventListener(Event.ENTER_FRAME,onEnterFrame)
[box title=”The source” color=”#fede9f”]→ Gideros animations seek and flee[/box]

Here’s is the animation running on Gideros player ( it actually runs smoothly, it looks stuttering only on the screencap).

Also in this category ...


One thought on “Learning Gideros (and Lua) : Porting Advanced AS3 Animations code”

Comments are closed.