1. Danny Fritz
  2. blank LOVE

Commits

Danny Fritz  committed 39348b4

New blank LOVE project.

  • Participants
  • Parent commits 47d6545
  • Branches default

Comments (0)

Files changed (61)

File .hgignore

View file
  • Ignore whitespace
+syntax: glob
+
+*.sublime-project
+*.sublime-workspace
+.hg
+.git
+.svn
+*.luac

File CREDITS.README

View file
  • Ignore whitespace
-
 Programming Libraries
-*KIKITO MiddleClass https://github.com/kikito/middleclass
-*KIKITO Stateful https://github.com/kikito/middleclass-extras
-*KIKITO Tween.lua https://github.com/kikito/tween.lua/
+	*kikito MiddleClass https://github.com/kikito/middleclass
+	*kikito stateful.lua https://github.com/kikito/stateful.lua
+	*kikito beholder.lua https://github.com/kikito/beholder.lua
+	*kikito tween.lua https://github.com/kikito/beholder.lua
+	*kikito cron.lua https://github.com/kikito/cron.lua
+	*kikito anim8 https://github.com/kikito/anim8
+	*vrld Hardon Collider http://vrld.github.com/HardonCollider/
 
 Art Assets
-
+	

File LICENSE.README

View file
  • Ignore whitespace
-Copyright (c) 2011, Danny Fritz <dannyfritz@gmail.com>
-http://creativecommons.org/licenses/by/3.0/
-Licensed under the Creative Commons Attribution 3.0 Unported License.
+Free Attribution License (FAL) 1.0
+The FAL applies to the software's source and object code and comes with any
+rights that I have in it (other than trademarks). You agree to the FAL by
+copying, distributing, or making a derivative work of the software.
+
+You get the royalty free right:
+	* To use the software for any purpose including commercial.
+	* To make derivative works of it.
+	* To copy and distribute it.
+
+If the Derived Work is distributed open source, you must:
+	* Leave other people's copyright notices and license terms in place.
+	* You must use the FAL or similar license without adding further restrictions
+	  to the rights provided.
+
+If the Derived Work is distributed closed source:
+	* You must Attribute credit to the original authors.
+	* You must use a license that does not allow derivative works.
+
+There are some things that you must shoulder:
+	* You get NO WARRANTIES. None of any kind;
+	* If the software damages you in any way, you may only recover direct damages
+		up to the amount you paid for it (that is zero if you did not pay anything).
+		You may not recover any other damages, including those called "consequential
+		damages." (The state or country where you live may not allow you to limit
+		your liability in this way, so this may not apply to you);

File conf.lua

View file
  • Ignore whitespace
+--[[----------------------------------------------------------------------------
+Copyright (c) 2012, Danny Fritz <dannyfritz@gmail.com>
+Free Attribution License (FAL) 1.0
+--]]----------------------------------------------------------------------------
 
 function love.conf(t)
-  t.title = 'TechnoCat Splash'
-  t.author = 'Danny \'TechnoCat\' Fritz dannyfritz@gmail.com'
-  t.identity = nil
+  t.title = 'Bombergirl'
+  t.author = 'Pindie Studios'
+  t.identity = "Bombergirl"
   t.release = false
   t.version = '0.8.0'
-  t.console = false
+  t.console = true
   t.screen.width = 800
   t.screen.height = 600
   t.screen.fullscreen = false
   t.modules.timer = true
   t.modules.mouse = true
   t.modules.sound = true
-  t.modules.physics = false 
+  t.modules.physics = false
 end

File img/bomb.png

  • Ignore whitespace
Added
New image

File img/box.png

  • Ignore whitespace
Added
New image

File img/fire_up.png

  • Ignore whitespace
Added
New image

File img/player.png

  • Ignore whitespace
Added
New image

File lib/HardonCollider/README

View file
  • Ignore whitespace
+General Purpose 2D Collision Detection System
+
+Documentation and examples here:
+http://vrld.github.com/HardonCollider

File lib/HardonCollider/class.lua

View file
  • Ignore whitespace
+--[[
+Copyright (c) 2010-2011 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]--
+
+local function __NULL__() end
+
+-- class "inheritance" by copying functions
+local function inherit(class, interface, ...)
+	if not interface then return end
+	assert(type(interface) == "table", "Can only inherit from other classes.")
+
+	-- __index and construct are not overwritten as for them class[name] is defined
+	for name, func in pairs(interface) do
+		if not class[name] then
+			class[name] = func
+		end
+	end
+	for super in pairs(interface.__is_a or {}) do
+		class.__is_a[super] = true
+	end
+
+	return inherit(class, ...)
+end
+
+-- class builder
+local function new(args)
+	local super = {}
+	local name = '<unnamed class>'
+	local constructor = args or __NULL__
+	if type(args) == "table" then
+		-- nasty hack to check if args.inherits is a table of classes or a class or nil
+		super = (args.inherits or {}).__is_a and {args.inherits} or args.inherits or {}
+		name = args.name or name
+		constructor = args[1] or __NULL__
+	end
+	assert(type(constructor) == "function", 'constructor has to be nil or a function')
+
+	-- build class
+	local class = {}
+	class.__index = class
+	class.__tostring = function() return ("<instance of %s>"):format(tostring(class)) end
+	class.construct = constructor or __NULL__
+	class.inherit = inherit
+	class.__is_a = {[class] = true}
+	class.is_a = function(self, other) return not not self.__is_a[other] end
+
+	-- intercept assignment in global environment to infer the class name
+	if not (type(args) == "table" and args.name) then
+		local env, env_meta, interceptor = getfenv(0), getmetatable(getfenv(0)), {}
+		function interceptor:__newindex(key, value)
+			if value == class then
+				local name = tostring(key)
+				getmetatable(class).__tostring = function() return name end
+			end
+			-- restore old metatable and insert value
+			setmetatable(env, env_meta)
+			if env.global then env.global(key) end -- handle 'strict' module
+			env[key] = value
+		end
+		setmetatable(env, interceptor)
+	end
+
+	-- inherit superclasses (see above)
+	inherit(class, unpack(super))
+
+	-- syntactic sugar
+	local meta = {
+		__call = function(self, ...)
+			local obj = {}
+			setmetatable(obj, self)
+			self.construct(obj, ...)
+			return obj
+		end,
+		__tostring = function() return name end
+	}
+	return setmetatable(class, meta)
+end
+
+-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons).
+if class_commons ~= false and not common then
+	common = {}
+	function common.class(name, prototype, parent)
+		local init = prototype.init or (parent or {}).init
+		return new{name = name, inherits = {prototype, parent}, init}
+	end
+	function common.instance(class, ...)
+		return class(...)
+	end
+end
+
+
+-- the module
+return setmetatable({new = new, inherit = inherit},
+	{__call = function(_,...) return new(...) end})

File lib/HardonCollider/init.lua

View file
  • Ignore whitespace
+--[[
+Copyright (c) 2011 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]--
+
+local _NAME = (...)
+if not (common and common.class and common.instance) then
+	class_commons = true
+	require(_NAME .. '.class')
+end
+local Shapes      = require(_NAME .. '.shapes')
+local Spatialhash = require(_NAME .. '.spatialhash')
+local vector      = require(_NAME .. '.vector')
+
+local newPolygonShape = Shapes.newPolygonShape
+local newCircleShape  = Shapes.newCircleShape
+local newPointShape   = Shapes.newPointShape
+
+local function __NULL__() end
+
+local HC = {}
+function HC:init(cell_size, callback_collide, callback_stop)
+	self._active_shapes  = {}
+	self._passive_shapes = {}
+	self._ghost_shapes   = {}
+	self._current_shape_id = 0
+	self._shape_ids      = setmetatable({}, {__mode = "k"}) -- reverse lookup
+	self.groups          = {}
+	self._colliding_last_frame = {}
+
+	self.on_collide = callback_collide or __NULL__
+	self.on_stop    = callback_stop    or __NULL__
+	self._hash      = common.instance(Spatialhash, cell_size)
+end
+
+function HC:clear()
+	self._active_shapes  = {}
+	self._passive_shapes = {}
+	self._ghost_shapes   = {}
+	self._current_shape_id = 0
+	self._shape_ids      = setmetatable({}, {__mode = "k"}) -- reverse lookup
+	self.groups          = {}
+	self._colliding_last_frame = {}
+	self._hash           = common.instance(Spatialhash, self._hash.cell_size)
+	return self
+end
+
+function HC:setCallbacks(collide, stop)
+	if type(collide) == "table" and not (getmetatable(collide) or {}).__call then
+		stop = collide.stop
+		collide = collide.collide
+	end
+
+	if collide then
+		assert(type(collide) == "function" or (getmetatable(collide) or {}).__call,
+			"collision callback must be a function or callable table")
+		self.on_collide = collide
+	end
+
+	if stop then
+		assert(type(stop) == "function" or (getmetatable(stop) or {}).__call,
+			"stop callback must be a function or callable table")
+		self.on_stop = stop
+	end
+
+	return self
+end
+
+local function new_shape(self, shape)
+	local x1,y1,x2,y2 = shape:bbox()
+
+	self._current_shape_id = self._current_shape_id + 1
+	self._active_shapes[self._current_shape_id] = shape
+	self._shape_ids[shape] = self._current_shape_id
+	self._hash:insert(shape, {x=x1,y=y1}, {x=x2,y=y2})
+	shape._groups = {}
+
+	local hash = self._hash
+	local move, rotate = shape.move, shape.rotate
+	function shape:move(...)
+		local x1,y1,x2,y2 = self:bbox()
+		move(self, ...)
+		local x3,y3,x4,y4 = self:bbox()
+		hash:update(self, {x=x1,y=y1}, {x=x2,y=y2}, {x=x3,y=y3}, {x=x4,y=y4})
+	end
+
+	function shape:rotate(...)
+		local x1,y1,x2,y2 = self:bbox()
+		rotate(self, ...)
+		local x3,y3,x4,y4 = self:bbox()
+		hash:update(self, {x=x1,y=y1}, {x=x2,y=y2}, {x=x3,y=y3}, {x=x4,y=y4})
+	end
+
+	function shape:_getNeighbors()
+		local x1,y1, x2,y2 = self:bbox()
+		return hash:getNeighbors(self, {x=x1,y=y1}, {x=x2,y=y2})
+	end
+
+	function shape:_removeFromHash()
+		local x1,y1, x2,y2 = self:bbox()
+		hash:remove(shape, {x=x1,y=y1}, {x=x2,y=y2})
+	end
+
+	return shape
+end
+
+function HC:addPolygon(...)
+	return new_shape(self, newPolygonShape(...))
+end
+
+function HC:addRectangle(x,y,w,h)
+	return self:addPolygon(x,y, x+w,y, x+w,y+h, x,y+h)
+end
+
+function HC:addCircle(cx, cy, radius)
+	return new_shape(self, newCircleShape(cx,cy, radius))
+end
+
+function HC:addPoint(x,y)
+	return new_shape(self, newPointShape(x,y))
+end
+
+function HC:share_group(shape, other)
+	for name,group in pairs(shape._groups) do
+		if group[other] then return true end
+	end
+	return false
+end
+
+
+-- get unique indentifier for an unordered pair of shapes, i.e.:
+-- collision_id(s,t) = collision_id(t,s)
+local function collision_id(self,s,t)
+	local i,k = self._shape_ids[s], self._shape_ids[t]
+	if i < k then i,k = k,i end
+	return string.format("%d,%d", i,k)
+end
+
+-- check for collisions
+function HC:update(dt)
+	-- collect colliding shapes
+	local tested, colliding = {}, {}
+	for _,shape in pairs(self._active_shapes) do
+		local neighbors = shape:_getNeighbors()
+		for _,other in pairs(neighbors) do
+			local id = collision_id(self, shape,other)
+			if not tested[id] then
+				if not (self._ghost_shapes[other] or self:share_group(shape, other)) then
+					local collide, sep = shape:collidesWith(other)
+					if collide then
+						colliding[id] = {shape, other, sep.x, sep.y}
+					end
+					tested[id] = true
+				end
+			end
+		end
+	end
+
+	-- call colliding callbacks on colliding shapes
+	for id,info in pairs(colliding) do
+		self._colliding_last_frame[id] = nil
+		self.on_collide( dt, unpack(info) )
+	end
+
+	-- call stop callback on shapes that do not collide anymore
+	for _,info in pairs(self._colliding_last_frame) do
+		self.on_stop( dt, unpack(info) )
+	end
+
+	self._colliding_last_frame = colliding
+end
+
+-- Test point for collision with active objects
+function HC:testPoint(x, y)
+	-- collect colliding shapes
+	local point = newPointShape(x,y);
+	new_shape(self, point);
+
+	local colliding = {};
+	for _,shape in pairs(point:_getNeighbors()) do
+		if shape:collidesWith(point) then
+			table.insert(colliding, shape);
+		end
+	end
+
+	self:remove(point);
+	
+	if #colliding == 0 then
+		return false;
+	end
+	return colliding;
+end
+
+-- remove shape from internal tables and the hash
+function HC:remove(shape, ...)
+	if not shape then return end
+	local id = self._shape_ids[shape]
+	if id then
+		self._active_shapes[id] = nil
+		self._passive_shapes[id] = nil
+	end
+	self._ghost_shapes[shape] = nil
+	self._shape_ids[shape] = nil
+	shape:_removeFromHash()
+
+	return self:remove(...)
+end
+
+-- group support
+function HC:addToGroup(group, shape, ...)
+	if not shape then return end
+	assert(self._shape_ids[shape], "Shape not registered!")
+
+	if not self.groups[group] then self.groups[group] = {} end
+	self.groups[group][shape] = true
+	shape._groups[group] = self.groups[group]
+	return self:addToGroup(group, ...)
+end
+
+function HC:removeFromGroup(group, shape, ...)
+	if not shape or not self.groups[group] then return end
+	assert(self._shape_ids[shape], "Shape not registered!")
+
+	self.groups[group][shape] = nil
+	shape._groups[group] = nil
+	return self:removeFromGroup(group, ...)
+end
+
+function HC:setPassive(shape, ...)
+	if not shape then return end
+	assert(self._shape_ids[shape], "Shape not registered!")
+
+	local id = self._shape_ids[shape]
+	if not id or self._ghost_shapes[shape] then return end
+
+	self._active_shapes[id] = nil
+	self._passive_shapes[id] = shape
+
+	return self:setPassive(...)
+end
+
+function HC:setActive(shape, ...)
+	if not shape then return end
+	assert(self._shape_ids[shape], "Shape not registered!")
+
+	local id = self._shape_ids[shape]
+	if not id or self._ghost_shapes[shape] then return end
+
+	self._active_shapes[id] = shape
+	self._passive_shapes[id] = nil
+
+	return self:setActive(...)
+end
+
+function HC:setGhost(shape, ...)
+	if not shape then return end
+	local id = self._shape_ids[shape]
+	assert(id, "Shape not registered!")
+
+	self._active_shapes[id] = nil
+	-- dont remove from passive shapes, see below
+	self._ghost_shapes[shape] = shape
+	return self:setGhost(...)
+end
+
+function HC:setSolid(shape, ...)
+	if not shape then return end
+	local id = self._shape_ids[shape]
+	assert(id, "Shape not registered!")
+
+	-- re-register shape. passive shapes were not unregistered above, so if a shape
+	-- is not passive, it must be registered as active again.
+	if not self._passive_shapes[id] then
+		self._active_shapes[id] = shape
+	end
+	self._ghost_shapes[shape] = nil
+	return self:setSolid(...)
+end
+
+-- the module
+HC = common.class("HardonCollider", HC)
+local function new(...)
+	return common.instance(HC, ...)
+end
+
+return setmetatable({HardonCollider = HC, new = new},
+	{__call = function(_,...) return new(...) end})

File lib/HardonCollider/polygon.lua

View file
  • Ignore whitespace
+--[[
+Copyright (c) 2011 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]--
+
+local _PACKAGE = (...):match("^(.+)%.[^%.]+")
+if not (common and common.class and common.instance) then
+	class_commons = true
+	require(_PACKAGE .. '.class')
+end
+local vector = require(_PACKAGE .. '.vector')
+
+----------------------------
+-- Private helper functions
+--
+-- create vertex list of coordinate pairs
+local function toVertexList(vertices, x,y, ...)
+	if not x or not y then return vertices end -- no more arguments
+
+	vertices[#vertices + 1] = vector(x, y)     -- set vertex
+	return toVertexList(vertices, ...)         -- recurse
+end
+
+-- returns true if three points lie on a line
+local function areCollinear(p,q,r)
+	return (q - p):cross(r - p) == 0
+end
+-- remove vertices that lie on a line
+local function removeCollinear(vertices)
+	local ret = {}
+	local i,k = #vertices - 1, #vertices
+	for l=1,#vertices do
+		if not areCollinear(vertices[i], vertices[k], vertices[l]) then
+			ret[#ret+1] = vertices[k]
+		end
+		i,k = k,l
+	end
+	return ret
+end
+
+-- get index of rightmost vertex (for testing orientation)
+local function getIndexOfleftmost(vertices)
+	local idx = 1
+	for i = 2,#vertices do
+		if vertices[i].x < vertices[idx].x then
+			idx = i
+		end
+	end
+	return idx
+end
+
+-- returns true if three points make a counter clockwise turn
+local function ccw(p, q, r)
+	return (q - p):cross(r - p) >= 0
+end
+
+-- unpack vertex coordinates, i.e. {x=p, y=q}, ... -> p,q, ...
+local function unpackHelper(v, ...)
+	if not v then return end
+	return v.x,v.y,unpackHelper(...)
+end
+
+-- test if a point lies inside of a triangle using cramers rule
+local function pointInTriangle(q, p1,p2,p3)
+	local v1,v2 = p2 - p1, p3 - p1
+	local qp = q - p1
+	local dv = v1:cross(v2)
+	local l = qp:cross(v2)
+	if l <= 0 then return false end
+	local m = v1:cross(qp)
+	if m <= 0 then return false end
+	return (l+m)/dv < 1
+end
+
+-- returns starting indices of shared edge, i.e. if p and q share the
+-- edge with indices p1,p2 of p and q1,q2 of q, the return value is p1,q1
+local function getSharedEdge(p,q)
+	local vertices = {}
+	for i,v in ipairs(q) do vertices[ tostring(v) ] = i end
+	for i,v in ipairs(p) do
+		local w = (i == #p) and p[1] or p[i+1]
+		if vertices[ tostring(v) ] and vertices[ tostring(w) ] then
+			return i, vertices[ tostring(v) ]
+		end
+	end
+end
+
+-----------------
+-- Polygon class
+--
+local Polygon = {}
+function Polygon:init(...)
+	local vertices = removeCollinear( toVertexList({}, ...) )
+	assert(#vertices >= 3, "Need at least 3 non collinear points to build polygon (got "..#vertices..")")
+
+	-- assert polygon is oriented counter clockwise
+	local r = getIndexOfleftmost(vertices)
+	local q = r > 1 and r - 1 or #vertices
+	local s = r < #vertices and r + 1 or 1
+	if not ccw(vertices[q], vertices[r], vertices[s]) then -- reverse order if polygon is not ccw
+		local tmp = {}
+		for i=#vertices,1,-1 do
+			tmp[#tmp + 1] = vertices[i]
+		end
+		vertices = tmp
+	end
+	self.vertices = vertices
+	-- make vertices immutable
+	setmetatable(self.vertices, {__newindex = function() error("Thou shall not change a polygon's vertices!") end})
+
+	-- compute polygon area and centroid
+	self.area = vertices[#vertices]:cross(vertices[1])
+	for i = 1,#vertices-1 do
+		self.area = self.area + vertices[i]:cross(vertices[i+1])
+	end
+	self.area = self.area / 2
+
+	local p,q = vertices[#vertices], vertices[1]
+	local det = p:cross(q)
+	self.centroid = vector((p.x+q.x) * det, (p.y+q.y) * det)
+	for i = 1,#vertices-1 do
+		p,q = vertices[i], vertices[i+1]
+		det = p:cross(q)
+		self.centroid.x = self.centroid.x + (p.x+q.x) * det
+		self.centroid.y = self.centroid.y + (p.y+q.y) * det
+	end
+	self.centroid = self.centroid / (6 * self.area)
+
+	-- get outcircle
+	self._radius = 0
+	for i = 1,#vertices do
+		self._radius = math.max(vertices[i]:dist(self.centroid), self._radius)
+	end
+end
+local newPolygon
+
+-- return vertices as x1,y1,x2,y2, ..., xn,yn
+function Polygon:unpack()
+	return unpackHelper( unpack(self.vertices) )
+end
+
+-- deep copy of the polygon
+function Polygon:clone()
+	return Polygon( self:unpack() )
+end
+
+-- get bounding box
+function Polygon:getBBox()
+	local ul = self.vertices[1]:clone()
+	local lr = ul:clone()
+	for i=2,#self.vertices do
+		local p = self.vertices[i]
+		if ul.x > p.x then ul.x = p.x end
+		if ul.y > p.y then ul.y = p.y end
+
+		if lr.x < p.x then lr.x = p.x end
+		if lr.y < p.y then lr.y = p.y end
+	end
+
+	return ul.x,ul.y, lr.x,lr.y
+end
+
+-- a polygon is convex if all edges are oriented ccw
+function Polygon:isConvex()
+	local function isConvex()
+		local v = self.vertices
+		if #v == 3 then return true end
+
+		if not ccw(v[#v], v[1], v[2]) then
+			return false
+		end
+		for i = 2,#v-1 do
+			if not ccw(v[i-1], v[i], v[i+1]) then
+				return false
+			end
+		end
+		if not ccw(v[#v-1], v[#v], v[1]) then
+			return false
+		end
+		return true
+	end
+
+	-- replace function so that this will only be computed once
+	local status = isConvex()
+	self.isConvex = function() return status end
+	return status
+end
+
+function Polygon:move(dx, dy)
+	if not dy then
+		dx, dy = dx:unpack()
+	end
+	for i,v in ipairs(self.vertices) do
+		v.x = v.x + dx
+		v.y = v.y + dy
+	end
+	self.centroid.x = self.centroid.x + dx
+	self.centroid.y = self.centroid.y + dy
+end
+
+function Polygon:rotate(angle, center, cy)
+	local center = center or self.centroid
+	if cy then center = vector(center, cy) end
+	for i,v in ipairs(self.vertices) do
+		self.vertices[i] = (self.vertices[i] - center):rotate_inplace(angle) + center
+	end
+	self.centroid = (self.centroid - center):rotate_inplace(angle) + center
+end
+
+-- triangulation by the method of kong
+function Polygon:triangulate()
+	if #self.vertices == 3 then return {self:clone()} end
+	local triangles = {} -- list of triangles to be returned
+	local concave = {}   -- list of concave edges
+	local adj = {}       -- vertex adjacencies
+	local vertices = self.vertices
+
+	-- retrieve adjacencies as the rest will be easier to implement
+	for i,p in ipairs(vertices) do
+		local l = (i == 1) and vertices[#vertices] or vertices[i-1]
+		local r = (i == #vertices) and vertices[1] or vertices[i+1]
+		adj[p] = {p = p, l = l, r = r} -- point, left and right neighbor
+		-- test if vertex is a concave edge
+		if not ccw(l,p,r) then concave[p] = p end
+	end
+
+	-- and ear is an edge of the polygon that contains no other
+	-- vertex of the polygon
+	local function isEar(p1,p2,p3)
+		if not ccw(p1,p2,p3) then return false end
+		for q,_ in pairs(concave) do
+			if pointInTriangle(q, p1,p2,p3) then return false end
+		end
+		return true
+	end
+
+	-- main loop
+	local nPoints, skipped = #vertices, 0
+	local p = adj[ vertices[2] ]
+	while nPoints > 3 do
+		if not concave[p.p] and isEar(p.l, p.p, p.r) then
+			-- polygon may be a 'collinear triangle', i.e.
+			-- all three points are on a line. In that case
+			-- the polygon constructor throws an error.
+			if not areCollinear(p.l, p.p, p.r) then
+				triangles[#triangles+1] = newPolygon(unpackHelper(p.l, p.p, p.r))
+			end
+
+			if concave[p.l] and ccw(adj[p.l].l, p.l, p.r) then
+				concave[p.l] = nil
+			end
+			if concave[p.r] and ccw(p.l, p.r, adj[p.r].r) then
+				concave[p.r] = nil
+			end
+			-- remove point from list
+			adj[p.p] = nil
+			adj[p.l].r = p.r
+			adj[p.r].l = p.l
+			nPoints = nPoints - 1
+			skipped = 0
+			p = adj[p.l]
+		else
+			p = adj[p.r]
+			skipped = skipped + 1
+			assert(skipped <= nPoints, "Cannot triangulate polygon (is the polygon intersecting itself?)")
+		end
+	end
+
+	if not areCollinear(p.l, p.p, p.r) then
+		triangles[#triangles+1] = newPolygon(unpackHelper(p.l, p.p, p.r))
+	end
+
+	return triangles
+end
+
+-- return merged polygon if possible or nil otherwise
+function Polygon:mergedWith(other)
+	local p,q = getSharedEdge(self.vertices, other.vertices)
+	if not (p and q) then return nil end
+
+	local ret = {}
+	for i = 1, p do ret[#ret+1] = self.vertices[i] end
+	for i = 2, #other.vertices-1 do
+		local k = i + q - 1
+		if k > #other.vertices then k = k - #other.vertices end
+		ret[#ret+1] = other.vertices[k]
+	end
+	for i = p+1,#self.vertices do ret[#ret+1] = self.vertices[i] end
+	return newPolygon( unpackHelper( unpack(ret) ) )
+end
+
+-- split polygon into convex polygons.
+-- note that this won't be the optimal split in most cases, as
+-- finding the optimal split is a really hard problem.
+-- the method is to first triangulate and then greedily merge
+-- the triangles.
+function Polygon:splitConvex()
+	-- edge case: polygon is a triangle or already convex
+	if #self.vertices <= 3 or self:isConvex() then return {self:clone()} end
+
+	local convex = self:triangulate()
+	local i = 1
+	repeat
+		local p = convex[i]
+		local k = i + 1
+		while k <= #convex do
+			local _, merged = pcall(function() return p:mergedWith(convex[k]) end)
+			if merged and merged:isConvex() then
+				convex[i] = merged
+				p = convex[i]
+				table.remove(convex, k)
+			else
+				k = k + 1
+			end
+		end
+		i = i + 1
+	until i >= #convex
+	
+	return convex
+end
+
+function Polygon:contains(x,y)
+	-- test if an edge cuts the ray
+	local function cut_ray(p,q)
+		return ((p.y > y and q.y < y) or (p.y < y and q.y > y)) -- possible cut
+			and (x - p.x < (y - p.y) * (q.x - p.x) / (q.y - p.y)) -- x < cut.x
+	end
+
+	-- test if the ray crosses boundary from interior to exterior.
+	-- this is needed due to edge cases, when the ray passes through
+	-- polygon corners
+	local function cross_boundary(p,q)
+		return (p.y == y and p.x > x and q.y < y)
+			or (q.y == y and q.x > x and p.y < y)
+	end
+
+	local v = self.vertices
+	local in_polygon = false
+	for i = 1, #v do
+		local p, q = v[i], v[(i % #v) + 1]
+		if cut_ray(p,q) or cross_boundary(p,q) then
+			in_polygon = not in_polygon
+		end
+	end
+	return in_polygon
+end
+
+function Polygon:intersectsRay(x,y, dx,dy)
+	local p = vector(x,y)
+	local v = vector(dx,dy)
+	local n = v:perpendicular()
+	local w,det
+
+	local tmin = math.huge
+	local q1,q2 = nil, self.vertices[#self.vertices]
+	for i = 1, #self.vertices do
+		q1,q2 = q2,self.vertices[i]
+		w = q2 - q1
+		det = v:cross(w)
+
+		if det ~= 0 then
+			-- there is an intersection point. check if it lies on both
+			-- the ray and the segment.
+			local r = q2 - p
+			local l = r:cross(w)/det
+			local m = v:cross(r)/det
+			if l >= 0 and m >= 0 and m <= 1 then
+				-- we cannot jump out early here (i.e. when l > tmin) because
+				-- the polygon might be concave
+				tmin = math.min(tmin, l)
+			end
+		else
+			-- lines parralel or incident. get distance of line to
+			-- anchor point. if they are incident, check if an endpoint
+			-- lies on the ray
+			local dist = (q1 - p) * n
+			if dist == 0 then
+				local l,m = v * (q1 - p), v * (q2 - p)
+				if l >= 0 and l >= m then
+					tmin = math.min(tmin, l)
+				elseif m >= 0 then
+					tmin = math.min(tmin, m)
+				end
+			end
+		end
+	end
+	return tmin ~= math.huge, tmin
+end
+
+Polygon = common.class('Polygon', Polygon)
+newPolygon = function(...) return common.instance(Polygon, ...) end
+return Polygon

File lib/HardonCollider/shapes.lua

View file
  • Ignore whitespace
+--[[
+Copyright (c) 2011 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]--
+
+local math_abs, math_floor, math_min, math_max = math.abs, math.floor, math.min, math.max
+local math_sqrt, math_log, math_pi, math_huge = math.sqrt, math.log, math.pi, math.huge
+
+local _PACKAGE = (...):match("^(.+)%.[^%.]+")
+if not common and common.class then
+	class_commons = true
+	require(_PACKAGE .. '.class')
+end
+local vector  = require(_PACKAGE .. '.vector')
+local Polygon = require(_PACKAGE .. '.polygon')
+
+local function math_absmin(a,b) return math_abs(a) < math_abs(b) and a or b end
+local function test_axes(axes, shape_one, shape_two, sep, min_overlap)
+	for _,axis in ipairs(axes) do
+		local l1,r1 = shape_one:projectOn(axis)
+		local l2,r2 = shape_two:projectOn(axis)
+		-- do the intervals overlap?
+		if r1 < l2 or r2 < l1 then return false end
+
+		-- get the smallest absolute overlap
+		local overlap = math_absmin(l2-r1, r2-l1)
+		if math_abs(overlap) < min_overlap then
+			sep, min_overlap = overlap * axis, math_abs(overlap)
+		end
+	end
+	return true, sep, min_overlap
+end
+
+local function SAT(shape_one, axes_one, shape_two, axes_two)
+	local collide, sep, overlap = false, vector(0,0), math_huge
+	collide, sep, overlap = test_axes(axes_one, shape_one, shape_two, sep, overlap)
+	if not collide then return false end
+	collide, sep = test_axes(axes_two, shape_one, shape_two, sep, overlap)
+	return collide, sep
+end
+
+local function outcircles_intersect(shape_one, shape_two)
+	local x1,y1,r1 = shape_one:outcircle()
+	local x2,y2,r2 = shape_two:outcircle()
+	return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) <= (r1+r2)*(r1+r2)
+end
+
+--
+-- base class
+--
+local Shape = {}
+function Shape:init(t)
+	self._type = t
+	self._rotation = 0
+end
+
+function Shape:moveTo(x,y)
+	local cx,cy = self:center()
+	self:move(x - cx, y - cy)
+end
+
+function Shape:rotation()
+	return self._rotation
+end
+
+function Shape:rotate(angle)
+	self._rotation = self._rotation + angle
+end
+
+function Shape:setRotation(angle, x,y)
+	return self:rotate(angle - self._rotation, x,y)
+end
+
+-- supported shapes
+Shape.POLYGON  = setmetatable({}, {__tostring = function() return 'POLYGON'  end})
+Shape.COMPOUND = setmetatable({}, {__tostring = function() return 'COMPOUND' end})
+Shape.CIRCLE   = setmetatable({}, {__tostring = function() return 'CIRCLE' end})
+Shape.POINT    = setmetatable({}, {__tostring = function() return 'POINT' end})
+
+--
+-- class definitions
+--
+local ConvexPolygonShape = {}
+function ConvexPolygonShape:init(polygon)
+	Shape.init(self, Shape.POLYGON)
+	assert(polygon:isConvex(), "Polygon is not convex.")
+	self._polygon = polygon
+end
+
+local ConcavePolygonShape = {}
+function ConcavePolygonShape:init(poly)
+	Shape.init(self, Shape.COMPOUND)
+	self._polygon = poly
+	self._shapes = poly:splitConvex()
+	for i,s in ipairs(self._shapes) do
+		self._shapes[i] = common.instance(ConvexPolygonShape, s)
+	end
+end
+
+local CircleShape = {}
+function CircleShape:init(cx,cy, radius)
+	Shape.init(self, Shape.CIRCLE)
+	self._center = vector(cx,cy)
+	self._radius = radius
+end
+
+local PointShape = {}
+function PointShape:init(x,y)
+	Shape.init(self, Shape.POINT)
+	self._pos = vector(x,y)
+end
+
+--
+-- collision functions
+--
+function ConvexPolygonShape:getAxes()
+	local axes = {}
+	local vert = self._polygon.vertices
+	for i = 1,#vert do
+		axes[#axes+1] = (vert[i]-vert[(i%#vert)+1]):perpendicular():normalize_inplace()
+	end
+	return axes
+end
+
+function ConvexPolygonShape:projectOn(axis)
+	local vertices = self._polygon.vertices
+	local projection = {}
+	for i = 1,#vertices do
+		projection[i] = vertices[i] * axis -- same as vertices[i]:projectOn(axis) * axis
+	end
+	return math_min(unpack(projection)), math_max(unpack(projection))
+end
+
+function CircleShape:projectOn(axis)
+	-- v:projectOn(a) * a = v * a (see ConvexPolygonShape)
+	-- therefore: (c +- a*r) * a = c*a +- |a|^2 * r
+	local center = self._center * axis
+	local shift = self._radius * axis:len2()
+	return center - shift, center + shift
+end
+
+-- collision dispatching:
+-- let circle shape or compund shape handle the collision
+function ConvexPolygonShape:collidesWith(other)
+	if other._type ~= Shape.POLYGON then
+		local collide, sep = other:collidesWith(self)
+		return collide, sep and -sep
+	end
+
+	-- else: type is POLYGON, use the SAT
+	if not outcircles_intersect(self, other) then return false end
+	return SAT(self, self:getAxes(), other, other:getAxes())
+end
+
+function ConcavePolygonShape:collidesWith(other)
+	if other._type == Shape.POINT then
+		return other:collidesWith(self)
+	end
+
+	if not outcircles_intersect(self, other) then return false end
+
+	local sep, collide, collisions = vector(0,0), false, 0
+	for _,s in ipairs(self._shapes) do
+		local status, separating_vector = s:collidesWith(other)
+		collide = collide or status
+		if status then
+			sep, collisions = sep + separating_vector, collisions + 1
+		end
+	end
+	return collide, sep / collisions
+end
+
+function CircleShape:collidesWith(other)
+	if other._type == Shape.CIRCLE then
+		local d = self._center:dist(other._center)
+		local radii = self._radius + other._radius
+		if d < radii then
+			-- if circles overlap, push it out upwards
+			if d == 0 then return true, radii * vector(0,1) end
+			-- otherwise push out in best direction
+			return true, (radii - d) * (self._center - other._center):normalize_inplace()
+		end
+		return false
+	elseif other._type == Shape.COMPOUND then
+		local collide, sep = other:collidesWith(self)
+		return collide, sep and -sep
+	elseif other._type == Shape.POINT then
+		return other:collidesWith(self)
+	end
+
+	-- else: other._type == POLYGON
+	if not outcircles_intersect(self, other) then return false end
+	-- retrieve closest edge to center
+	local points = other._polygon.vertices
+	local closest, dist = points[1], (self._center - points[1]):len2()
+	for i = 2,#points do
+		local d = (self._center - points[i]):len2()
+		if d < dist then
+			closest, dist = points[i], d
+		end
+	end
+	local axis = vector(0,1)
+	if dist ~= 0 then axis = (closest - self._center):normalize_inplace() end
+	return SAT(self, {axis}, other, other:getAxes())
+end
+
+function PointShape:collidesWith(other)
+	if other._type == Shape.POINT then
+		return (self._pos == other._pos), vector(0,0)
+	end
+	return other:contains(self._pos.x, self._pos.y), vector(0,0)
+end
+
+--
+-- point location/ray intersection
+--
+function ConvexPolygonShape:contains(x,y)
+	return self._polygon:contains(x,y)
+end
+
+function ConcavePolygonShape:contains(x,y)
+	return self._polygon:contains(x,y)
+end
+
+function CircleShape:contains(x,y)
+	return (vector(x,y) - self._center):len2() < self._radius * self._radius
+end
+
+function PointShape:contains(x,y)
+	return x == self._pos.x and y == self._pos.y
+end
+
+
+function ConcavePolygonShape:intersectsRay(x,y, dx,dy)
+	return self._polygon:intersectsRay(x,y, dx,dy)
+end
+
+function ConvexPolygonShape:intersectsRay(x,y, dx,dy)
+	return self._polygon:intersectsRay(x,y, dx,dy)
+end
+
+-- circle intersection if distance of ray/center is smaller
+-- than radius.
+-- with r(s) = p + d*s = (x,y) + (dx,dy) * s defining the ray and
+-- (x - cx)^2 + (y - cy)^2 = r^2, this problem is eqivalent to
+-- solving [with c = (cx,cy)]:
+--
+--     d*d s^2 + 2 d*(p-c) s + (p-c)*(p-c)-r^2 = 0
+function CircleShape:intersectsRay(x,y, dx,dy)
+	local pc = vector(x,y) - self._center
+	local d = vector(dx,dy)
+
+	local a = d * d
+	local b = 2 * d * pc
+	local c = pc * pc - self._radius * self._radius
+	local discr = b*b - 4*a*c
+	if discr < 0 then return false end
+
+	discr = math_sqrt(discr)
+	local s1,s2 = discr-b, -discr-b
+	if s1 < 0 then -- first solution is off the ray
+		return s2 >= 0, s2/(2*a)
+	elseif s2 < 0 then -- second solution is off the ray
+		return s1 >= 0, s1/(2*a)
+	end
+	-- both solutions on the ray
+	return true, math_min(s1,s2)/(2*a)
+end
+
+-- point shape intersects ray if it lies on the ray
+function PointShape:intersectsRay(x,y,dx,dy)
+	local p = self._pos - vector(x,y)
+	local d = vector(dx,dy)
+	local t = p * d / d:len2()
+	return t >= 0, t
+end
+
+--
+-- auxiliary
+--
+function ConvexPolygonShape:center()
+	return self._polygon.centroid:unpack()
+end
+
+function ConcavePolygonShape:center()
+	return self._polygon.centroid:unpack()
+end
+
+function CircleShape:center()
+	return self._center:unpack()
+end
+
+function PointShape:center()
+	return self._pos:unpack()
+end
+
+function ConvexPolygonShape:outcircle()
+	local cx,cy = self:center()
+	return cx,cy, self._polygon._radius
+end
+
+function ConcavePolygonShape:outcircle()
+	local cx,cy = self:center()
+	return cx,cy, self._polygon._radius
+end
+
+function CircleShape:outcircle()
+	local cx,cy = self:center()
+	return cx,cy, self._radius
+end
+
+function PointShape:outcircle()
+	return self._pos.x, self._pos.y, 0
+end
+
+function ConvexPolygonShape:bbox()
+	return self._polygon:getBBox()
+end
+
+function ConcavePolygonShape:bbox()
+	return self._polygon:getBBox()
+end
+
+function CircleShape:bbox()
+	local cx,cy = self._center:unpack()
+	local r = self._radius
+	return cx-r,cy-r, cx+r,cy+r
+end
+
+function PointShape:bbox()
+	local x,y = self._pos:unpack()
+	return x,y,x,y
+end
+
+
+function ConvexPolygonShape:move(x,y)
+	self._polygon:move(x,y)
+end
+
+function ConcavePolygonShape:move(x,y)
+	self._polygon:move(x,y)
+	for _,p in ipairs(self._shapes) do
+		p:move(x,y)
+	end
+end
+
+function CircleShape:move(x,y)
+	self._center = self._center + vector(x,y)
+end
+
+function PointShape:move(x,y)
+	self._pos.x = self._pos.x + x
+	self._pos.y = self._pos.y + y
+end
+
+
+function ConcavePolygonShape:rotate(angle,cx,cy)
+	Shape.rotate(self, angle)
+	self._polygon:rotate(angle,cx)
+	for _,p in ipairs(self._shapes) do
+		p:rotate(angle, cx and vector(cx,cy) or self._polygon.centroid)
+	end
+end
+
+function ConvexPolygonShape:rotate(angle, cx,cy)
+	Shape.rotate(self, angle)
+	self._polygon:rotate(angle, cx, cy)
+end
+
+function CircleShape:rotate(angle, cx,cy)
+	Shape.rotate(self, angle)
+	if not cx then return end
+	local c = vector(cx,cy)
+	self._center = (self._center - c):rotate_inplace(angle) + c
+end
+
+function PointShape:rotate(angle, cx,cy)
+	Shape.rotate(self, angle)
+	if not cx then return end
+	local c = vector(cx,cy)
+	self._pos = (self._pos - c):rotate_inplace(angle) + c
+end
+
+
+function ConvexPolygonShape:draw(mode)
+	local mode = mode or 'line'
+	love.graphics.polygon(mode, self._polygon:unpack())
+end
+
+function ConcavePolygonShape:draw(mode)
+	local mode = mode or 'line'
+	if mode == 'line' then
+		love.graphics.polygon('line', self._polygon:unpack())
+	else
+		for _,p in ipairs(self._shapes) do
+			love.graphics.polygon(mode, p._polygon:unpack())
+		end
+	end
+end
+
+function CircleShape:draw(mode, segments)
+	love.graphics.circle(mode or 'line', self._center.x, self._center.y, self._radius)
+end
+
+function PointShape:draw()
+	love.graphics.point(self._pos.x, self._pos.y)
+end
+
+
+Shape = common.class('Shape', Shape)
+ConvexPolygonShape  = common.class('ConvexPolygonShape',  ConvexPolygonShape,  Shape)
+ConcavePolygonShape = common.class('ConcavePolygonShape', ConcavePolygonShape, Shape)
+CircleShape         = common.class('CircleShape',         CircleShape,         Shape)
+PointShape          = common.class('PointShape',          PointShape,          Shape)
+
+local function newPolygonShape(polygon, ...)
+	-- create from coordinates if needed
+	if type(polygon) == "number" then
+		polygon = common.instance(Polygon, polygon, ...)
+	else
+		polygon = polygon:clone()
+	end
+
+	if polygon:isConvex() then
+		return common.instance(ConvexPolygonShape, polygon)
+	end
+	return common.instance(ConcavePolygonShape, polygon)
+end
+
+local function newCircleShape(...)
+	return common.instance(CircleShape, ...)
+end
+
+local function newPointShape(...)
+	return common.instance(PointShape, ...)
+end
+
+return {
+	ConcavePolygonShape = ConcavePolygonShape,
+	ConvexPolygonShape  = ConvexPolygonShape,
+	CircleShape         = CircleShape,
+	PointShape          = PointShape,
+	newPolygonShape     = newPolygonShape,
+	newCircleShape      = newCircleShape,
+	newPointShape       = newPointShape,
+}
+

File lib/HardonCollider/spatialhash.lua

View file
  • Ignore whitespace
+--[[
+Copyright (c) 2011 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]--
+
+local floor = math.floor
+local min, max = math.min, math.max
+
+local _PACKAGE = (...):match("^(.+)%.[^%.]+")
+if not (common and common.class and common.instance) then
+	class_commons = true
+	require(_PACKAGE .. '.class')
+end
+local vector = require(_PACKAGE .. '.vector')
+
+-- special cell accesor metamethods, so vectors are converted
+-- to a string before using as keys
+local cell_meta = {}
+function cell_meta.__newindex(tbl, key, val)
+	return rawset(tbl, key.x..","..key.y, val)
+end
+function cell_meta.__index(tbl, key)
+	local key = key.x..","..key.y
+	local ret = rawget(tbl, key)
+	if not ret then
+		ret = setmetatable({}, {__mode = "kv"})
+		rawset(tbl, key, ret)
+	end
+	return ret
+end
+
+local Spatialhash = {}
+function Spatialhash:init(cell_size)
+	self.cell_size = cell_size or 100
+	self.cells = setmetatable({}, cell_meta)
+end
+
+function Spatialhash:cellCoords(v)
+	return {x=floor(v.x / self.cell_size), y=floor(v.y / self.cell_size)}
+end
+
+function Spatialhash:cell(v)
+	return self.cells[ self:cellCoords(v) ]
+end
+
+function Spatialhash:insert(obj, ul, lr)
+	local ul = self:cellCoords(ul)
+	local lr = self:cellCoords(lr)
+	for i = ul.x,lr.x do
+		for k = ul.y,lr.y do
+			rawset(self.cells[ {x=i,y=k} ], obj, obj)
+		end
+	end
+end
+
+function Spatialhash:remove(obj, ul, lr)
+	-- no bbox given. => must check all cells
+	if not ul or not lr then
+		for _,cell in pairs(self.cells) do
+			rawset(cell, obj, nil)
+		end
+		return
+	end
+
+	local ul = self:cellCoords(ul)
+	local lr = self:cellCoords(lr)
+	-- else: remove only from bbox
+	for i = ul.x,lr.x do
+		for k = ul.y,lr.y do
+			rawset(self.cells[{x=i,y=k}], obj, nil)
+		end
+	end
+end
+
+-- update an objects position
+function Spatialhash:update(obj, ul_old, lr_old, ul_new, lr_new)
+	local ul_old, lr_old = self:cellCoords(ul_old), self:cellCoords(lr_old)
+	local ul_new, lr_new = self:cellCoords(ul_new), self:cellCoords(lr_new)
+
+	if ul_old.x == ul_new.x and ul_old.y == ul_new.y and
+	   lr_old.x == lr_new.x and lr_old.y == lr_new.y then
+		return
+	end
+
+	for i = ul_old.x,lr_old.x do
+		for k = ul_old.y,lr_old.y do
+			rawset(self.cells[{x=i,y=k}], obj, nil)
+		end
+	end
+	for i = ul_new.x,lr_new.x do
+		for k = ul_new.y,lr_new.y do
+			rawset(self.cells[{x=i,y=k}], obj, obj)
+		end
+	end
+end
+
+function Spatialhash:getNeighbors(obj, ul, lr)
+	local ul = self:cellCoords(ul)
+	local lr = self:cellCoords(lr)
+	local set = {}
+	for i = ul.x,lr.x do
+		for k = ul.y,lr.y do
+			local cell = self.cells[{x=i,y=k}] or {}
+			for other,_ in pairs(cell) do
+				rawset(set, other, other)
+			end
+		end
+	end
+	rawset(set, obj, nil)
+	return set
+end
+
+function Spatialhash:draw(how, show_empty, print_key)
+	if show_empty == nil then show_empty = true end
+	for k,cell in pairs(self.cells) do
+		local empty = true
+		(function() for _ in pairs(cell) do empty = false; return end end)()
+		if show_empty or not empty then
+			local x,y = k:match("([^,]+),([^,]+)")
+			x = x * self.cell_size
+			y = y * self.cell_size
+			love.graphics.rectangle(how, x,y, self.cell_size, self.cell_size)
+
+			if print_key then
+				love.graphics.print(k, x+3,y+3)
+			end
+		end
+	end
+end
+
+return common.class('Spatialhash', Spatialhash)

File lib/HardonCollider/vector.lua

View file
  • Ignore whitespace
+--[[
+Copyright (c) 2010 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+]]--
+
+-- somewhat speed optimized version of hump.vector
+
+local sqrt, cos, sin = math.sqrt, math.cos, math.sin
+
+local vector = {}
+vector.__index = vector
+
+local function new(x,y)
+	local v = {x = x or 0, y = y or 0}
+	setmetatable(v, vector)
+	return v
+end
+
+function vector:clone()
+	return new(self.x, self.y)
+end
+
+function vector:unpack()
+	return self.x, self.y
+end
+
+function vector:__tostring()
+	return "("..tonumber(self.x)..","..tonumber(self.y)..")"
+end
+
+function vector.__unm(a)
+	return new(-a.x, -a.y)
+end
+
+function vector.__add(a,b)
+	return new(a.x+b.x, a.y+b.y)
+end
+
+function vector.__sub(a,b)
+	return new(a.x-b.x, a.y-b.y)
+end
+
+function vector.__mul(a,b)
+	if type(a) == "number" then
+		return new(a*b.x, a*b.y)
+	elseif type(b) == "number" then
+		return new(b*a.x, b*a.y)
+	else
+		return a.x*b.x + a.y*b.y
+	end
+end
+
+function vector.__div(a,b)
+	return new(a.x / b, a.y / b)
+end
+
+function vector.__eq(a,b)
+	return a.x == b.x and a.y == b.y
+end
+
+function vector.__lt(a,b)
+	return a.x < b.x or (a.x == b.x and a.y < b.y)
+end
+
+function vector.__le(a,b)
+	return a.x <= b.x and a.y <= b.y
+end
+
+function vector.permul(a,b)
+	return new(a.x*b.x, a.y*b.y)
+end
+
+function vector:len2()
+	return self.x * self.x + self.y * self.y
+end
+
+function vector:len()
+	return sqrt(self.x * self.x + self.y * self.y)
+end
+
+function vector.dist(a, b)
+	local dx = a.x - b.x
+	local dy = a.y - b.y
+	return sqrt( dx*dx + dy*dy )
+end
+
+function vector:normalize_inplace()
+	local l = sqrt(self.x * self.x + self.y * self.y)
+	self.x, self.y = self.x / l, self.y / l
+	return self
+end
+
+function vector:normalized()
+	return self / sqrt(self.x * self.x + self.y * self.y)
+end
+
+function vector:rotate_inplace(phi)
+	local c, s = cos(phi), sin(phi)
+	self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y
+	return self
+end
+
+function vector:rotated(phi)
+	local c, s = cos(phi), sin(phi)
+	return new(c * self.x - s * self.y, s * self.x + c * self.y)
+end
+
+function vector:perpendicular()
+	return new(-self.y, self.x)
+end
+
+function vector:projectOn(v)
+	-- (self * v) * v / v:len2()
+	local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
+	return new(s * v.x, s * v.y)
+end
+
+function vector:mirrorOn(v)
+	-- 2 * self:projectOn(other) - self
+	local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
+	return new(s * v.x - self.x, s * v.y - self.y)
+end
+
+function vector:cross(other)
+	return self.x * other.y - self.y * other.x
+end
+
+
+-- the module
+return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})

File lib/LICENSE.txt

View file
  • Ignore whitespace
+Copyright (c) 2011, Enrique García Cota
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright notice, 
+     this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright notice, 
+     this list of conditions and the following disclaimer in the documentation 
+     and/or other materials provided with the distribution.
+  3. Neither the name of tween.lua nor the names of its contributors 
+     may be used to endorse or promote products derived from this software 
+     without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=====================================================================================
+
+The easing functions were taken from emmanuelOga's easing functions
+(https://github.com/EmmanuelOga/easing).
+
+Here's its license:
+
+Tweener authors,
+Yuichi Tateno,
+Emmanuel Oga
+
+The MIT License
+--------
+Copyright (c) 2010, Emmanuel Oga.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+=====================================================================================
+
+Emmanuel Oga's functions, in turn, were adapted from Penner's Easing Equations 
+and http://code.google.com/p/tweener/ (jstweener javascript version)
+
+Disclaimer for Robert Penner's Easing Equations license:
+
+TERMS OF USE - EASING EQUATIONS
+
+Open source under the BSD License.
+
+Copyright © 2001 Robert Penner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+    * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+

File lib/MIT-LICENSE.txt

View file
  • Ignore whitespace
+Copyright (c) 2011 Enrique García Cota
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File lib/anim8.lua

View file
  • Ignore whitespace
+-- anim8 v1.0.0 - 2012-02
+-- Copyright (c) 2011 Enrique García Cota
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local Grid = {}
+
+local _frames = {}
+
+local function assertPositiveInteger(value, name)
+  if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end
+  if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end
+  if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end
+end
+
+local function createFrame(self, x, y)
+  local fw, fh = self.frameWidth, self.frameHeight
+  return love.graphics.newQuad(
+    self.left + (x-1) * fw + x * self.border,
+    self.top  + (y-1) * fh + y * self.border,
+    fw,
+    fh,
+    self.imageWidth,
+    self.imageHeight
+  )
+end
+
+local function getGridKey(...)
+  return table.concat( {...} ,'-' )
+end
+
+
+local function getOrCreateFrame(self, x, y)
+  if x < 1 or x > self.width or y < 1 or y > self.height then
+    error(("There is no frame for x=%d, y=%d"):format(x, y))
+  end
+  local key = self._key
+  _frames[key]       = _frames[key]       or {}
+  _frames[key][x]    = _frames[key][x]    or {}
+  _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y)
+  return _frames[key][x][y]
+end
+
+local function parseInterval(str)
+  str = str:gsub(' ', '')
+  local min, max = str:match("^(%d+)-(%d+)$")
+  if not min then
+    min = str:match("^%d+$")
+    max = min
+  end
+  assert(min and max, ("Could not parse interval from %q"):format(str))
+  return tonumber(min), tonumber(max)
+end
+
+local function parseIntervals(str)
+  local left, right = str:match("(.+),(.+)")
+  assert(left and right, ("Could not parse intervals from %q"):format(str))
+  local minx, maxx = parseInterval(left)
+  local miny, maxy = parseInterval(right)
+  return minx, miny, maxx, maxy
+end
+
+local function parseFrames(self, args, result, position)
+  local current = args[position]
+  local kind = type(current)
+
+  if kind == 'number' then
+
+    result[#result + 1] = getOrCreateFrame(self, current, args[position + 1])
+    return position + 2
+
+  elseif kind == 'string' then
+
+    local minx, miny, maxx, maxy = parseIntervals(current)
+    for x = minx, maxx do
+      for y = miny, maxy do
+        result[#result+1] = getOrCreateFrame(self,x,y)
+      end
+    end
+
+    return position + 1
+
+  else
+
+    error(("Invalid type: %q (%s)"):format(kind, tostring(args[position])))
+
+  end
+end
+
+function Grid:getFrames(...)
+  local args = {...}
+  local length = #args
+  local result = {}
+  local position = 1
+
+  while position <= length do
+    position = parseFrames(self, args, result, position)
+  end
+
+  return result
+end
+
+local Gridmt = {
+  __index = Grid,
+  __call  = Grid.getFrames
+}
+
+local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
+  assertPositiveInteger(frameWidth,  "frameWidth")
+  assertPositiveInteger(frameHeight, "frameHeight")
+  assertPositiveInteger(imageWidth,  "imageWidth")
+  assertPositiveInteger(imageHeight, "imageHeight")
+
+  left   = left   or 0
+  top    = top    or 0
+  border = border or 0
+
+  local key  = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
+
+  local grid = setmetatable(
+    { frameWidth  = frameWidth,
+      frameHeight = frameHeight,
+      imageWidth  = imageWidth,
+      imageHeight = imageHeight,
+      left        = left,
+      top         = top,
+      border      = border,
+      width       = math.floor(imageWidth/frameWidth),
+      height      = math.floor(imageHeight/frameHeight),
+      _key        = key
+    },
+    Gridmt
+  )
+  return grid
+end
+
+-----------------------------------------------------------
+
+local Animation = {}
+
+local function cloneArray(arr)
+  local result = {}
+  for i=1,#arr do result[i] = arr[i] end
+  return result
+end
+
+local function parseDelays(delays)
+  local parsedDelays = {}
+  local tk,min,max
+  for k,v in pairs(delays) do
+    tk = type(k)
+    if     tk == "number"