Add collisions

This commit is contained in:
Andros Fenollosa 2016-12-08 20:02:27 +01:00
parent 7bf419249c
commit 2af855e88a
50 changed files with 3428 additions and 846 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="love.to.android1127134907"
<manifest package="love.to.android1208185535"
android:versionCode="15"
android:versionName="0.9.2"
android:installLocation="auto" xmlns:android="http://schemas.android.com/apk/res/android">
@ -12,13 +12,13 @@
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="Game 1127134907"
android:label="Game 1208185535"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
<service android:name=".DownloadService" />
<activity
android:name="LtaActivity"
android:configChanges="orientation|screenSize"
android:label="Game 1127134907"
android:label="Game 1208185535"
android:launchMode="singleTop"
android:screenOrientation="landscape" >
<intent-filter>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="love.to.android1127134907"
<manifest package="love.to.android1208185535"
android:versionCode="15"
android:versionName="0.9.2"
android:installLocation="auto" xmlns:android="http://schemas.android.com/apk/res/android">
@ -12,13 +12,13 @@
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="Game 1127134907"
android:label="Game 1208185535"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
<service android:name=".DownloadService" />
<activity
android:name="LtaActivity"
android:configChanges="orientation|screenSize"
android:label="Game 1127134907"
android:label="Game 1208185535"
android:launchMode="singleTop"
android:screenOrientation="landscape" >
<intent-filter>

View File

@ -1,5 +1,5 @@
#Last build type
#Sun, 27 Nov 2016 13:49:13 +0100
#Thu, 08 Dec 2016 18:55:42 +0100
build.last.target=debug

View File

@ -1,9 +1,9 @@
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes.dex : \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1127134907/BuildConfig.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1127134907/LtaActivity.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1127134907/R$attr.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1127134907/R$drawable.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1127134907/R.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1208185535/BuildConfig.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1208185535/LtaActivity.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1208185535/R$attr.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1208185535/R$drawable.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/love/to/android1208185535/R.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/org/libsdl/app/DummyEdit.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/org/libsdl/app/SDLActivity$1.class \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/classes/org/libsdl/app/SDLActivity$2.class \

View File

@ -1,9 +1,9 @@
# view AndroidManifest.xml #generated:45
-keep class love.to.android1127134907.DownloadActivity { <init>(...); }
-keep class love.to.android1208185535.DownloadActivity { <init>(...); }
# view AndroidManifest.xml #generated:17
-keep class love.to.android1127134907.DownloadService { <init>(...); }
-keep class love.to.android1208185535.DownloadService { <init>(...); }
# view AndroidManifest.xml #generated:18
-keep class love.to.android1127134907.LtaActivity { <init>(...); }
-keep class love.to.android1208185535.LtaActivity { <init>(...); }

View File

@ -1,3 +1,3 @@
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/gen/love/to/android1127134907/R.java \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/gen/love/to/android1208185535/R.java \
: /Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/res/drawable-xxhdpi/ic_launcher.png \
/Users/androsfenollosa/www/alunizaje/android/tools/love-android-sdl2/bin/AndroidManifest.xml \

View File

@ -1,5 +1,5 @@
/** Automatically generated file. DO NOT MODIFY */
package love.to.android1127134907;
package love.to.android1208185535;
public final class BuildConfig {
public final static boolean DEBUG = true;

View File

@ -5,7 +5,7 @@
* should not be modified by hand.
*/
package love.to.android1127134907;
package love.to.android1208185535;
public final class R {
public static final class attr {

View File

@ -1,4 +1,4 @@
package love.to.android1127134907;
package love.to.android1208185535;
import org.love2d.android.GameActivity;
public class LtaActivity extends GameActivity {}

View File

@ -1,6 +1,8 @@
local HC = require 'assets/scripts/vendor/HC'
local tools = require 'assets/scripts/tools'
local asteroids = {}
local collision_debug = false
local imgs = nil
function asteroids.load(game)
@ -12,6 +14,8 @@ function asteroids.load(game)
}
asteroids.num = game.level * 5
asteroids.max_speed = 5
asteroids.collision_size = 50
-- Make asteroids
asteroids.bodys = {}
for i = 1, asteroids.num do
@ -20,16 +24,20 @@ function asteroids.load(game)
end
function asteroids.update(dt, game)
-- Rotate asteroids
-- Move asteroids
for key, asteroid in pairs(asteroids.bodys) do
-- Rotate
asteroid.angle = asteroid.angle + (dt * math.pi / 10)
-- Move
asteroid.x = asteroid.x - asteroid.speed
-- Collisions
asteroid.collision:moveTo(asteroid.x, asteroid.y)
end
-- Destroy asteroids
for key, asteroid in pairs(asteroids.bodys) do
if asteroid.x + asteroid.img:getWidth() < 0 then
HC.remove(asteroid.collision)
table.remove(asteroids.bodys, key)
remove_collision(key, game)
end
end
-- Create asteroids
@ -41,6 +49,9 @@ end
function asteroids.draw()
for key, asteroid in pairs(asteroids.bodys) do
love.graphics.draw(asteroid.img, asteroid.x, asteroid.y, asteroid.angle, 1, 1, asteroid.img:getWidth() / 2, asteroid.img:getHeight() / 2)
if collision_debug then
asteroid.collision:draw('fill')
end
end
end
@ -51,26 +62,12 @@ function make_asteroid(pos, game, x_random)
y = math.random(game.window.height / 2, game.canvas.height - temp_img:getHeight()),
speed = math.random(1, asteroids.max_speed),
img = temp_img,
angle = math.random(0, 90)
angle = math.random(0, 90),
collision = HC.circle(0, 0, asteroids.collision_size)
}
if x_random then
asteroids.bodys[pos].x = math.random(0, game.canvas.width - temp_img:getWidth())
end
-- Collision
remove_collision(pos, game)
game.collisions:add(
'asteroid' .. pos,
asteroids.bodys[pos].x,
asteroids.bodys[pos].y,
asteroids.bodys[pos].img:getWidth(),
asteroids.bodys[pos].img:getHeight()
)
end
function remove_collision(pos, game)
if game.collisions:hasItem('asteroid' .. pos) then
game.collisions:remove('asteroid' .. pos)
end
end
return asteroids

View File

@ -1,5 +1,12 @@
local controls = {}
function controls.load(game)
controls.padding = 50
controls.img_right = love.graphics.newImage('assets/sprites/hui/button.png')
controls.right_x = game.window.width - controls.img_right:getWidth() - controls.padding
controls.right_y = game.window.height - controls.img_right:getHeight() - controls.padding
end
function controls.update(dt)
control_up, control_right, control_left, control_quit = false, false, false, false
-- Keyboard
@ -30,4 +37,8 @@ function controls.update(dt)
end
end
function controls.draw()
love.graphics.draw(controls.img_right, controls.right_x, controls.right_y)
end
return controls

View File

@ -1,5 +1,4 @@
local game = {}
local bump = require 'assets/scripts/vendor/bump'
function game.load()
-- Configuration
@ -15,7 +14,7 @@ function game.load()
love.physics.setMeter(world_meter) -- Height earth in meters
game.world = love.physics.newWorld(0, gravity * world_meter, true) -- Make earth
-- Collisions
game.collisions = bump.newWorld(50)
--game.collisions = HC.new(150)
end
return game

View File

@ -1,7 +1,8 @@
local anim8 = require 'assets/scripts/vendor/anim8'
local HC = require 'assets/scripts/vendor/HC'
local spaceship = {}
local collision_debug = true
local collision_debug = false
spaceship.y = 0
local press_button = false
@ -11,13 +12,14 @@ function spaceship.load(game)
body.width = 156
body.height = 143
body.img = love.graphics.newImage('assets/sprites/spaceship/body.png')
body.num_frames = 5
body.body = love.physics.newBody(game.world, (game.canvas.width / 2) - (body.img:getWidth() / 2) , body.y, 'dynamic')
body.shape = love.physics.newCircleShape(20)
body.fixture = love.physics.newFixture(body.body, body.shape, 1)
body.fixture:setRestitution(0.9)
local g = anim8.newGrid(body.width, body.height, body.img:getWidth(), body.img:getHeight())
body.animation_stop = anim8.newAnimation(g('1-1', 1), 0.1)
body.animation_fire = anim8.newAnimation(g('2-5', 1), 0.01)
body.animation_fire = anim8.newAnimation(g('2-' .. body.num_frames, 1), 0.01)
-- Light
light = {
img = love.graphics.newImage('assets/sprites/spaceship/light.png'),
@ -31,23 +33,26 @@ function spaceship.load(game)
light.animation = anim8.newAnimation(g('1-' .. light.num_frames, 1), 0.05)
-- Collision
body.collision = {}
body.collision[1] = {x=66, y=35, width=28, height=5, name={name='spaceship_1'}}
body.collision[2] = {x=56, y=40, width=48, height=5, name={name='spaceship_2'}}
body.collision[3] = {x=54, y=45, width=54, height=10, name={name='spaceship_3'}}
body.collision[4] = {x=50, y=55, width=60, height=10, name={name='spaceship_4'}}
body.collision[5] = {x=46, y=65, width=66, height=10, name={name='spaceship_5'}}
body.collision[6] = {x=44, y=75, width=70, height=10, name={name='spaceship_6'}}
body.collision[7] = {x=40, y=85, width=78, height=20, name={name='spaceship_7'}}
body.collision[8] = {x=53, y=105, width=50, height=4, name={name='spaceship_8'}}
for key, value in pairs(body.collision) do
game.collisions:add(
body.collision[key].name,
body.body:getX() + body.collision[key].x,
body.body:getY() + body.collision[key].y,
body.collision[key].width,
body.collision[key].height
body.collision.claim = 79
body.collision.vertices = {
104, 40,
55, 40,
35, 100,
78, 110,
119, 100
}
body.collision.hc = HC.polygon(
body.collision.vertices[1],
body.collision.vertices[2],
body.collision.vertices[3],
body.collision.vertices[4],
body.collision.vertices[5],
body.collision.vertices[6],
body.collision.vertices[7],
body.collision.vertices[8],
body.collision.vertices[9],
body.collision.vertices[10]
)
end
end
function spaceship.update(dt, game)
@ -75,12 +80,23 @@ function spaceship.update(dt, game)
love.event.push('quit')
end
-- Collision
for key, value in pairs(body.collision) do
game.collisions:move(
body.collision[key].name,
body.body:getX() + body.collision[key].x,
body.body:getY() + body.collision[key].y
)
-- Move polygon
body.collision.hc:moveTo(body.body:getX() + body.collision.claim, body.body:getY() + body.collision.claim)
-- Check for collisions
for shape, delta in pairs(HC.collisions(body.collision.hc)) do
end
-- Check game limits
if body.body:getY() <= 0 then -- Top game
x, y = body.body:getLinearVelocity()
body.body:setLinearVelocity(x, -y)
end
if body.body:getX() + (body.img:getWidth() / body.num_frames) - (body.collision.claim / 2) > game.canvas.width then -- Right game
x, y = body.body:getLinearVelocity()
body.body:setLinearVelocity(-x, y)
end
if body.body:getX() + (body.collision.claim / 2) < 0 then -- Left game
x, y = body.body:getLinearVelocity()
body.body:setLinearVelocity(-x, y)
end
end
@ -91,16 +107,9 @@ function spaceship.draw()
else
body.animation_stop:draw(body.img, body.body:getX(), body.body:getY())
end
-- Collision
if collision_debug then
for key, value in pairs(body.collision) do
love.graphics.rectangle(
'fill',
body.body:getX() + body.collision[key].x,
body.body:getY() + body.collision[key].y,
body.collision[key].width,
body.collision[key].height
)
end
body.collision.hc:draw('fill')
end
end

21
assets/scripts/vendor/HC/HC-0.1-1.rockspec vendored Executable file
View File

@ -0,0 +1,21 @@
package = "HC"
version = "0.1-1"
source = {
url = "git://github.com/vrld/HC.git"
}
description = {"General purpose 2D collision detection in pure Lua"}
dependencies = {
"lua = 5.1"
}
build = {
type = "builtin",
modules = {
["hardoncollider"] = "init.lua",
["hardoncollider.class"] = "class.lua",
["hardoncollider.gjk"] = "gjk.lua",
["hardoncollider.polygon"] = "polygon.lua",
["hardoncollider.shapes"] = "shapes.lua",
["hardoncollider.spatialhash"] = "spatialhash.lua",
["hardoncollider.vector-light"] = "vector-light.lua",
}
}

4
assets/scripts/vendor/HC/README vendored Executable file
View File

@ -0,0 +1,4 @@
General Purpose 2D Collision Detection System
Documentation and examples here:
http://hc.readthedocs.org/

99
assets/scripts/vendor/HC/class.lua vendored Executable file
View File

@ -0,0 +1,99 @@
--[[
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
-- 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 common_class ~= 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})

8
assets/scripts/vendor/HC/docs/Class.rst vendored Executable file
View File

@ -0,0 +1,8 @@
HC.class
========
::
class = require 'HC.class'
See ``hump.class``.

250
assets/scripts/vendor/HC/docs/MainModule.rst vendored Executable file
View File

@ -0,0 +1,250 @@
Main Module
===========
::
HC = require 'HC'
The purpose of the main modules is to connect shapes with the spatial hash -- a
data structure to quickly look up neighboring shapes -- and to provide
utilities to tell which shapes intersect (collide) with each other.
Most of the time, HC will be run as a singleton; you can, however, also create
several instances, that each hold their own little worlds.
Initialization
--------------
.. function:: HC.new([cell_size = 100])
:param number cell_size: Resolution of the internal search structure (optional).
:returns: Collider instance.
Creates a new collider instance that holds a separate spatial hash.
Collider instances carry the same methods as the main module.
The only difference is that function calls must use the colon-syntax (see
below).
Useful if you want to maintain several collision layers or several separate
game worlds.
The ``cell_size`` somewhat governs the performance of :func:`HC.neighbors` and
:func:`HC.collisions`. How this parameter affects the performance depends on
how many shapes there are, how big these shapes are and (somewhat) how the
shapes are distributed.
A rule of thumb would be to set ``cell_size`` to be about four times the size
of the average object.
Or just leave it as is and worry about it only if you run into performance
problems that can be traced back to the spatial hash.
**Example**::
collider = HC.new(150) -- create separate world
-- method calls with colon syntax
ball = collider:circle(100,100,20)
rect = collider:rectangle(110,90,20,100)
for shape, delta in pairs(collider:collisions(ball)) do
shape:move(delta.x, delta.y)
end
.. function:: HC.resetHash([cell_size = 100])
:param number cell_size: Resolution of the internal search structure (optional).
Reset the internal search structure, the spatial hash.
This clears *all* shapes that were registered beforehand, meaning that HC will
not be able to find any collisions with those shapes anymore.
**Example**::
function new_stage()
actors = {} -- clear the stage on our side
HC.resetHash() -- as well as on HC's side
end
Shapes
------
See also the :doc:`Shapes` sub-module.
.. note::
HC will only keep `weak references
<https://www.lua.org/manual/5.1/manual.html#2.10.2>`_ to the shapes you add
to the world. This means that if you don't store the shapes elsewhere, the
garbage collector will eventually come around and remove these shapes.See
also `this issue <https://github.com/vrld/HC/issues/44>`_ on github.
.. function:: HC.rectangle(x, y, w, h)
:param numbers x,y: Upper left corner of the rectangle.
:param numbers w,h: Width and height of the rectangle.
:returns: The rectangle :class:`Shape` added to the scene.
Add a rectangle shape to the scene.
.. note::
:class:`Shape` transformations, e.g. :func:`Shape.moveTo` and
:func:`Shape.rotate` will be with respect to the *center, not* the upper left
corner of the rectangle!
**Example**::
rect = HC.rectangle(100, 120, 200, 40)
rect:rotate(23)
.. function:: HC.polygon(x1,y1,...,xn,yn)
:param numbers x1,y1,...,xn,yn: The corners of the polygon. At least three
corners that do not lie on a straight line
are required.
:returns: The polygon :class:`Shape` added to the scene.
Add a polygon to the scene. Any non-self-intersection polygon will work.
The polygon will be closed; the first and the last point do not need to be the
same.
.. note::
If three consecutive points lie on a line, the middle point will be discarded.
This means you cannot construct polygon shapes that are lines.
.. note::
:class:`Shape` transformations, e.g. :func:`Shape.moveTo` and
:func:`Shape.rotate` will be with respect to the center of the polygon.
**Example**::
shape = HC.polygon(10,10, 40,50, 70,10, 40,30)
shape:move(42, 5)
.. function:: HC.circle(cx, cy, radius)
:param numbers cx,cy: Center of the circle.
:param number radius: Radius of the circle.
:returns: The circle :class:`Shape` added to the scene.
Add a circle shape to the scene.
**Example**::
circle = HC.circle(400, 300, 100)
.. function:: HC.point(x, y)
:param numbers x, y: Position of the point.
:returns: The point :class:`Shape` added to the scene.
Add a point shape to the scene.
Point shapes are most useful for bullets and such, because detecting collisions
between a point and any other shape is a little faster than detecting collision
between two non-point shapes. In case of a collision, the separating vector
will not be valid.
**Example**::
bullets[#bulltes+1] = HC.point(player.pos.x, player.pos.y)
.. function:: HC.register(shape)
:param Shape shape: The :class:`Shape` to add to the spatial hash.
Add a shape to the bookkeeping system.
:func:`HC.neighbors` and :func:`Hc.collisions` works only with registered
shapes.
You don't need to (and should not) register any shapes created with the above
functions.
Overwrites :func:`Shape.move`, :func:`Shape.rotate`, and :func:`Shape.scale`
with versions that update the :doc:`SpatialHash`.
This function is mostly only useful if you provide a custom shape.
See :ref:`custom-shapes`.
.. function:: HC.remove(shape)
:param Shape shape: The :class:`Shape` to remove from the spatial hash.
Remove a shape to the bookkeeping system.
.. warning::
This will also invalidate the functions :func:`Shape.move`,
:func:`Shape.rotate`, and :func:`Shape.scale`.
Make sure you delete the shape from your own actor list(s).
**Example**::
for i = #bullets,1,-1 do
if bullets[i]:collidesWith(player)
player:takeDamage()
HC.remove(bullets[i]) -- remove bullet from HC
table.remove(bullets, i) -- remove bullet from own actor list
end
end
Collision Detection
-------------------
.. function:: HC.collisions(shape)
:param Shape shape: Query shape.
:returns: Table of colliding shapes and separating vectors.
Get shapes that are colliding with ``shape`` and the vector to separate the shapes.
The separating vector points away from ``shape``.
The table is a *set*, meaning that the shapes are stored in *keys* of the table.
The *values* are the separating vector.
You can iterate over the shapes using ``pairs`` (see example).
**Example**::
local collisions = HC.collisions(shape)
for other, separating_vector in pairs(collisions)
shape:move(-separating_vector.x/2, -separating_vector.y/2)
other:move( separating_vector.x/2, separating_vector.y/2)
end
.. function:: HC.neighbors(shape)
:param Shape shape: Query shape.
:returns: Table of neighboring shapes, where the keys of the table are the shape.
Get other shapes in that are close to ``shape``.
The table is a *set*, meaning that the shapes are stored in *keys* of the table.
You can iterate over the shapes using ``pairs`` (see example).
.. note::
The result depends on the size and position of ``shape`` as well as the
grid size of the spatial hash: :func:`HC.neighbors` returns the shapes that
are in the same cell(s) as ``shape``.
**Example**::
local candidates = HC.neighbors(shape)
for other in pairs(candidates)
local collides, dx, dy = shape:collidesWith(other)
if collides then
other:move(dx, dy)
end
end
.. attribute:: HC.hash
Reference to the :class:`SpatialHash` instance.

192
assets/scripts/vendor/HC/docs/Makefile vendored Executable file
View File

@ -0,0 +1,192 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/HC.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/HC.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/HC"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/HC"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

213
assets/scripts/vendor/HC/docs/Polygon.rst vendored Executable file
View File

@ -0,0 +1,213 @@
HC.polygon
==============
::
polygon = require 'HC.polygon'
Polygon class with some handy algorithms. Does not provide collision detection
- this functionality is provided by :func:`newPolygonShape` instead.
.. class:: Polygon(x1,y1, ..., xn,yn)
:param numbers x1,y1, ..., xn,yn: The corners of the polygon. At least three corners are needed.
:returns: The polygon object.
Construct a polygon.
At least three points that are not collinear (i.e. not lying on a straight
line) are needed to construct the polygon. If there are collinear points, these
points will be removed. The shape of the polygon is not changed.
.. note::
The syntax depends on used class system. The shown syntax works when using
the bundled `hump.class <http://vrld.github.com/hump/#hump.class>`_ or
`slither <https://bitbucket.org/bartbes/slither>`_.
**Example**::
Polygon = require 'HC.polygon'
poly = Polygon(10,10, 40,50, 70,10, 40,30)
.. function:: Polygon:unpack()
:returns: ``x1,y1, ..., xn,yn`` - The vertices of the polygon.
Get the polygon's vertices. Useful for drawing with ``love.graphics.polygon()``.
**Example**::
love.graphics.draw('line', poly:unpack())
.. function:: Polygon:clone()
:returns: A copy of the polygon.
Get a copy of the polygon.
.. note::
Since Lua uses references when simply assigning an existing polygon to a
variable, unexpected things can happen when operating on the variable. Consider
this code::
p1 = Polygon(10,10, 40,50, 70,10, 40,30)
p2 = p1 -- p2 is a reference
p3 = p1:clone() -- p3 is a clone
p2:rotate(math.pi) -- p1 will be rotated, too!
p3:rotate(-math.pi) -- only p3 will be rotated
**Example**::
copy = poly:clone()
copy:move(10,20)
.. function:: Polygon:bbox()
:returns: ``x1, y1, x2, y2`` - Corners of the counding box.
Get axis aligned bounding box.
``x1, y1`` defines the upper left corner, while ``x2, y2`` define the lower
right corner.
**Example**::
x1,y1,x2,y2 = poly:bbox()
-- draw bounding box
love.graphics.rectangle('line', x1,y2, x2-x1, y2-y1)
.. function:: Polygon:isConvex()
:returns: ``true`` if the polygon is convex, ``false`` otherwise.
Test if a polygon is convex, i.e. a line line between any two points inside the
polygon will lie in the interior of the polygon.
**Example**::
-- split into convex sub polygons
if not poly:isConvex() then
list = poly:splitConvex()
else
list = {poly:clone()}
end
.. function:: Polygon:move(x,y)
:param numbers x, y: Coordinates of the direction to move.
Move a polygon in a direction..
**Example**::
poly:move(10,-5) -- move 10 units right and 5 units up
.. function:: Polygon:rotate(angle[, cx, cy])
:param number angle: The angle to rotate in radians.
:param numbers cx, cy: The rotation center (optional).
Rotate the polygon. You can define a rotation center. If it is omitted, the
polygon will be rotated around it's centroid.
**Example**::
p1:rotate(math.pi/2) -- rotate p1 by 90° around it's center
p2:rotate(math.pi/4, 100,100) -- rotate p2 by 45° around the point 100,100
.. function:: Polygon:triangulate()
:returns: ``table`` of Polygons: Triangles that the polygon is composed of.
Split the polygon into triangles.
**Example**::
triangles = poly:triangulate()
for i,triangle in ipairs(triangles) do
triangles.move(math.random(5,10), math.random(5,10))
end
.. function:: Polygon:splitConvex()
:returns: ``table`` of Polygons: Convex polygons that form the original polygon.
Split the polygon into convex sub polygons.
**Example**::
convex = concave_polygon:splitConvex()
function love.draw()
for i,poly in ipairs(convex) do
love.graphics.polygon('fill', poly:unpack())
end
end
.. function:: Polygon:mergedWith(other)
:param Polygon other: The polygon to merge with.
:returns: The merged polygon, or nil if the two polygons don't share an edge.
Create a merged polygon of two polygons **if, and only if** the two polygons
share one complete edge. If the polygons share more than one edge, the result
may be erroneous.
This function does not change either polygon, but rather creates a new one.
**Example**::
merged = p1:mergedWith(p2)
.. function:: Polygon:contains(x, y)
:param numbers x, y: Point to test.
:returns: ``true`` if ``x,y`` lies in the interior of the polygon.
Test if the polygon contains a given point.
**Example**::
if button:contains(love.mouse.getPosition()) then
button:setHovered(true)
end
.. function:: Polygon:intersectionsWithRay(x, y, dx, dy)
:param numbers x, y: Starting point of the ray.
:param numbers dx, dy: Direction of the ray.
:returns: Table of ray parameters.
Test if the polygon intersects the given ray.
The ray parameters of the intersections are returned as a table.
The position of the intersections can be computed as
``(x,y) + ray_parameter * (dx, dy)``.
.. function:: Polygon:intersectsRay(x, y, dx, dy)
:param numbers x, y: Starting point of the ray.
:param numbers dx, dy: Direction of the ray.
:returns: ``intersects, ray_parameter`` - intersection indicator and ray paremter.
Test if the polygon intersects a ray.
If the shape intersects the ray, the point of intersection can be computed by
``(x,y) + ray_parameter * (dx, dy)``.
**Example**::
if poly:intersectsRay(400,300, dx,dy) then
love.graphics.setLine(2) -- highlight polygon
end

303
assets/scripts/vendor/HC/docs/Shapes.rst vendored Executable file
View File

@ -0,0 +1,303 @@
HC.shapes
=========
::
shapes = require 'HC.shapes'
Shape classes with collision detection methods.
This module defines methods to move, rotate and draw shapes created with the
main module.
As each shape is at it's core a Lua table, you can attach values and add
functions to it. Be careful not to use keys that name a function or start with
an underscore, e.g. `move` or `_rotation`, since these are used internally.
Everything else is fine.
If you don't want to use the full blown module, you can still use these classes
to test for colliding shapes.
This may be useful for scenes where the shapes don't move very much and only
few collisions are of interest - for example graphical user interfaces.
.. _shape-baseclass:
Base Class
----------
.. class:: Shape(type)
:param any type: Arbitrary type identifier of the shape's type.
Base class for all shapes. All shapes must conform to the interface defined below.
.. function:: Shape:move(dx, dy)
:param numbers dx,dy: Coordinates to move the shape by.
Move the shape *by* some distance.
**Example**::
circle:move(10, 15) -- move the circle 10 units right and 15 units down.
.. function:: Shape:moveTo(x, y)
:param numbers x,y: Coordinates to move the shape to.
Move the shape *to* some point. Most shapes will be *centered* on the point
``(x,y)``.
.. note::
Equivalent to::
local cx,cy = shape:center()
shape:move(x-cx, y-cy)
**Example**::
local x,y = love.mouse.getPosition()
circle:moveTo(x, y) -- move the circle with the mouse
.. function:: Shape:center()
:returns: ``x, y`` - The center of the shape.
Get the shape's center.
**Example**::
local cx, cy = circle:center()
print("Circle at:", cx, cy)
.. function:: Shape:rotate(angle[, cx, cy])
:param number angle: Amount of rotation in radians.
:param numbers cx, cy: Rotation center; defaults to the shape's center if omitted (optional).
Rotate the shape *by* some angle. A rotation center can be specified. If no
center is given, rotate around the center of the shape.
**Example**::
rectangle:rotate(math.pi/4)
.. function:: Shape:setRotation(angle[, cx, cy])
:param number angle: Amount of rotation in radians.
:param numbers cx, cy: Rotation center; defaults to the shape's center if omitted (optional).
Set the rotation of a shape. A rotation center can be specified. If no center
is given, rotate around the center of the shape.
.. note::
Equivalent to::
shape:rotate(angle - shape.rotation(), cx,cy)
**Example**::
rectangle:setRotation(math.pi, 100,100)
.. function:: Shape:rotation()
:returns: The shape's rotation in radians.
Get the rotation of the shape in radians.
.. function:: Shape:scale(s)
:param number s: Scale factor; must be > 0.
Scale the shape relative to it's center.
.. note::
There is no way to query the scale of a shape.
**Example**::
circle:scale(2) -- double the size
.. function:: Shape:outcircle()
:returns: ``x, y, r`` - Parameters of the outcircle.
Get parameters of a circle that fully encloses the shape.
**Example**::
if player:hasShield() then
love.graphics.circle('line', player:outcircle())
end
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
.. function:: Shape:bbox()
:returns: ``x1, y1, x2, y2`` - Corners of the counding box.
Get axis aligned bounding box.
``x1, y1`` defines the upper left corner, while ``x2, y2`` define the lower
right corner.
**Example**::
-- draw bounding box
local x1,y1, x2,y2 = shape:bbox()
love.graphics.rectangle('line', x1,y1, x2-x1,y2-y1)
.. function:: Shape:draw(mode)
:param DrawMode mode: How to draw the shape. Either 'line' or 'fill'.
Draw the shape either filled or as outline. Mostly for debug-purposes.
**Example**::
circle:draw('fill')
.. function:: Shape:support(dx,dy)
:param numbers dx, dy: Search direction.
:returns: The furthest vertex in direction `dx, dy`.
Get furthest vertex of the shape with respect to the direction ``dx, dy``.
Used in the collision detection algorithm, but may be useful for other things -
e.g. lighting - too.
**Example**::
-- get vertices that produce a shadow volume
local x1,y1 = circle:support(lx, ly)
local x2,y2 = circle:support(-lx, -ly)
.. function:: Shape:collidesWith(other)
:param Shape other: Test for collision with this shape.
:returns: ``collide, dx, dy`` - Collision indicator and separating vector.
Test if two shapes collide.
The separating vector ``dx, dy`` will only be defined if ``collide`` is ``true``.
If defined, the separating vector will point in the direction of ``other``,
i.e. ``dx, dy`` is the direction and magnitude to move ``other`` so that the
shapes do not collide anymore.
**Example**::
if circle:collidesWith(rectangle) then
print("collision detected!")
end
.. function:: Shape:contains(x, y)
:param numbers x, y: Point to test.
:returns: ``true`` if ``x,y`` lies in the interior of the shape.
Test if the shape contains a given point.
**Example**::
if unit.shape:contains(love.mouse.getPosition) then
unit:setHovered(true)
end
.. function:: Shape:intersectionsWithRay(x, y, dx, dy)
:param numbers x, y: Starting point of the ray.
:param numbers dx, dy: Direction of the ray.
:returns: Table of ray parameters.
Test if the shape intersects the given ray.
The ray parameters of the intersections are returned as a table.
The position of the intersections can be computed as
``(x,y) + ray_parameter * (dx, dy)``.
**Example**::
local ts = player:intersectionsWithRay(x,y, dx,dy)
for _, t in ipairs(t) do
-- find point of intersection
local vx,vy = vector.add(x, y, vector.mul(t, dx, dy))
player:addMark(vx,vy)
end
.. function:: Shape:intersectsRay(x, y, dx, dy)
:param numbers x, y: Starting point of the ray.
:param numbers dx, dy: Direction of the ray.
:returns: ``intersects, ray_parameter`` - intersection indicator and ray paremter.
Test if the shape intersects the given ray.
If the shape intersects the ray, the point of intersection can be computed by
``(x,y) + ray_parameter * (dx, dy)``.
**Example**::
local intersecting, t = player:intersectsRay(x,y, dx,dy)
if intersecting then
-- find point of intersection
local vx,vy = vector.add(x, y, vector.mul(t, dx, dy))
player:addMark(vx,vy)
end
.. _custom-shapes:
Custom Shapes
-------------
Custom shapes must implement at least the following methods (as defined above)
- :func:`Shape:move`
- :func:`Shape:rotate`
- :func:`Shape:scale`
- :func:`Shape:bbox`
- :func:`Shape:collidesWith`
.. _builtin-shapes:
Built-in Shapes
---------------
.. class:: ConcavePolygonShape
.. class:: ConvexPolygonShape
.. class:: CircleShape
.. class:: PointShape
.. function:: newPolygonShape(...)
:param numbers ...: Vertices of the :class:`Polygon`.
:returns: :class:`ConcavePolygonShape` or :class:`ConvexPolygonShape`.
.. function:: newCircleShape(cx, cy, radius)
:param numbers cx,cy: Center of the circle.
:param number radius: Radius of the circle.
:returns: :class:`CircleShape`.
.. function:: newPointShape
:param numbers x, y: Position of the point.
:returns: :class:`PointShape`.

164
assets/scripts/vendor/HC/docs/SpatialHash.rst vendored Executable file
View File

@ -0,0 +1,164 @@
HC.spatialhash
==============
::
spatialhash = require 'HC.spatialhash'
A spatial hash implementation that supports scenes of arbitrary size. The hash
is sparse, which means that cells will only be created when needed.
.. class:: Spatialhash([cellsize = 100])
:param number cellsize: Width and height of a cell (optional).
Create a new spatial hash with a given cell size.
Choosing a good cell size depends on your application. To get a decent speedup,
the average cell should not contain too many objects, nor should a single
object occupy too many cells. A good rule of thumb is to choose the cell size
so that the average object will occupy only one cell.
.. note::
The syntax depends on used class system. The shown syntax works when using
the bundled `hump.class <http://vrld.github.com/hump/#hump.class>`_ or
`slither <https://bitbucket.org/bartbes/slither>`_.
**Example**::
Spatialhash = require 'hardoncollider.spatialhash'
hash = Spatialhash(150)
.. function:: Spatialhash:cellCoords(x,y)
:param numbers x, y: The position to query.
:returns: Coordinates of the cell which would contain ``x,y``.
Get coordinates of a given value, i.e. the cell index in which a given point
would be placed.
**Example**::
local mx,my = love.mouse.getPosition()
cx, cy = hash:cellCoords(mx, my)
.. function:: Spatialhash:cell(i,k)
:param numbers i, k: The cell index.
:returns: Set of objects contained in the cell.
Get the cell with given coordinates.
A cell is a table which's keys and value are the objects stored in the cell,
i.e.::
cell = {
[obj1] = obj1,
[obj2] = obj2,
...
}
You can iterate over the objects in a cell using ``pairs()``::
for object in pairs(cell) do stuff(object) end
**Example**::
local mx,my = love.mouse.getPosition()
cx, cy = hash:cellCoords(mx, my)
cell = hash:cell(cx, cy)
.. function:: Spatialhash:cellAt(x,y)
:param numbers x, y: The position to query.
:returns: Set of objects contained in the cell.
Get the cell that contains point x,y.
Same as ``hash:cell(hash:cellCoords(x,y))``
**Example**::
local mx,my = love.mouse.getPosition()
cell = hash:cellAt(mx, my)
.. function:: Spatialhash:shapes()
:returns: Set of all shapes in the hash.
Get *all* shapes that are recorded in the hash.
.. function:: Spatialhash:inSameCells(x1,y1, x2,y2)
:param numbers x1,y1: Upper left corner of the query bounding box.
:param numbers x2,y2: Lower right corner of the query bounding box.
:returns: Set of all shapes in the same cell as the bbox.
Get the shapes that are in the same cell as the defined bounding box.
.. function:: Spatialhash:register(obj, x1,y1, x2,y2)
:param mixed obj: Object to place in the hash. It can be of any type except `nil`.
:param numbers x1,y1: Upper left corner of the bounding box.
:param numbers x2,y2: Lower right corner of the bounding box.
Insert an object into the hash using a given bounding box.
**Example**::
hash:register(shape, shape:bbox())
.. function:: Spatialhash:remove(obj[, x1,y1[, x2,y2]])
:param mixed obj: The object to delete
:param numbers x1,y1: Upper left corner of the bounding box (optional).
:param numbers x2,y2: Lower right corner of the bounding box (optional).
Remove an object from the hash using a bounding box.
If no bounding box is given, search the whole hash to delete the object.
**Example**::
hash:remove(shape, shape:bbox())
hash:remove(object_with_unknown_position)
.. function:: Spatialhash:update(obj, x1,y1, x2,y2, x3,y3, x4,y4)
:param mixed obj: The object to be updated.
:param numbers x1,y1: Upper left corner of the bounding box before the object was moved.
:param numbers x2,y2: Lower right corner of the bounding box before the object was moved.
:param numbers x3,y3: Upper left corner of the bounding box after the object was moved.
:param numbers x4,y4: Lower right corner of the bounding box after the object was moved.
Update an objects position given the old bounding box and the new bounding box.
**Example**::
hash:update(shape, -100,-30, 0,60, -100,-70, 0,20)
.. function:: Spatialhash:draw(draw_mode[, show_empty = true[, print_key = false]])
:param string draw_mode: Either 'fill' or 'line'. See the LÖVE wiki.
:param boolean show_empty: Wether to draw empty cells (optional).
:param boolean print_key: Wether to print cell coordinates (optional).
Draw hash cells on the screen, mostly for debug purposes
**Example**::
love.graphics.setColor(160,140,100,100)
hash:draw('line', true, true)
hash:draw('fill', false)

8
assets/scripts/vendor/HC/docs/Vector.rst vendored Executable file
View File

@ -0,0 +1,8 @@
HC.vector
=========
::
vector = require 'HC.vector'
See ``hump.vector_light``.

296
assets/scripts/vendor/HC/docs/conf.py vendored Executable file
View File

@ -0,0 +1,296 @@
# -*- coding: utf-8 -*-
#
# HC documentation build configuration file, created by
# sphinx-quickstart on Thu Oct 8 20:31:43 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
import shlex
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.mathjax',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'HC'
copyright = u'2015, Matthias Richter'
author = u'Matthias Richter'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1-1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'HCdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'HC.tex', u'HC Documentation',
u'Matthias Richter', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'hc', u'HC Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'HC', u'HC Documentation',
author, 'HC', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
primary_domain = "js"
highlight_language = "lua"
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
#import sphinx_bootstrap_theme
#html_theme = 'bootstrap'
#html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()

115
assets/scripts/vendor/HC/docs/index.rst vendored Executable file
View File

@ -0,0 +1,115 @@
.. HC documentation master file, created by
sphinx-quickstart on Thu Oct 8 20:31:43 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
HC - General purpose collision detection with LÖVE_
===================================================
HC is a Lua module to simplify one important aspect in computer games:
Collision detection.
It can detect collisions between arbitrary positioned and rotated shapes.
Built-in shapes are points, circles and polygons.
Any non-intersecting polygons are supported, even concave ones.
You can add other types of shapes if you need them.
The main interface is simple:
1. Set up your scene,
2. Check for collisions,
3. React to collisions.
First steps
-----------
This is an example on how to use HC. One shape will stick to the mouse
position, while the other will stay in the same place::
HC = require 'HC'
-- array to hold collision messages
local text = {}
function love.load()
-- add a rectangle to the scene
rect = HC.rectangle(200,400,400,20)
-- add a circle to the scene
mouse = HC.circle(400,300,20)
mouse:moveTo(love.mouse.getPosition())
end
function love.update(dt)
-- move circle to mouse position
mouse:moveTo(love.mouse.getPosition())
-- rotate rectangle
rect:rotate(dt)
-- check for collisions
for shape, delta in pairs(HC.collisions(mouse)) do
text[#text+1] = string.format("Colliding. Separating vector = (%s,%s)",
delta.x, delta.y)
end
while #text > 40 do
table.remove(text, 1)
end
end
function love.draw()
-- print messages
for i = 1,#text do
love.graphics.setColor(255,255,255, 255 - (i-1) * 6)
love.graphics.print(text[#text - (i-1)], 10, i * 15)
end
-- shapes can be drawn to the screen
love.graphics.setColor(255,255,255)
rect:draw('fill')
mouse:draw('fill')
end
Get HC
------
You can download the latest packaged version as `zip <https://github.com/vrld/HC/zipball/master>`_- or `tar <https://github.com/vrld/HC/tarball/master>`_-archive directly
from github_.
You can also have a look at the sourcecode online `here <http://github.com/vrld/HC>`_.
If you use the Git command line client, you can clone the repository by
running::
git clone git://github.com/vrld/HC.git
Once done, you can check for updates by running::
git pull
from inside the directory.
Read on
-------
.. toctree::
:maxdepth: 2
reference
tutorial
license
Indices and tables
^^^^^^^^^^^^^^^^^^
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _LÖVE: http://love2d.org
.. _github: https://github.com

26
assets/scripts/vendor/HC/docs/license.rst vendored Executable file
View File

@ -0,0 +1,26 @@
License
=======
Copyright (c) 2011-2015 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.

16
assets/scripts/vendor/HC/docs/reference.rst vendored Executable file
View File

@ -0,0 +1,16 @@
Reference
=========
HC is composed of several parts. Most of the time, you will only have to deal
with the main module and the `Shapes` sub-module, but the other modules are
at your disposal if you need them.
.. toctree::
:maxdepth: 2
HC <MainModule>
HC.shapes <Shapes>
HC.polygon <Polygon>
HC.spatialhash <SpatialHash>
HC.vector <Vector>
HC.class <Class>

4
assets/scripts/vendor/HC/docs/tutorial.rst vendored Executable file
View File

@ -0,0 +1,4 @@
Tutorial
========
To be rewritten.

193
assets/scripts/vendor/HC/gjk.lua vendored Executable file
View File

@ -0,0 +1,193 @@
--[[
Copyright (c) 2012 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("^(.+)%.[^%.]+")
local vector = require(_PACKAGE .. '.vector-light')
local huge, abs = math.huge, math.abs
local function support(shape_a, shape_b, dx, dy)
local x,y = shape_a:support(dx,dy)
return vector.sub(x,y, shape_b:support(-dx, -dy))
end
-- returns closest edge to the origin
local function closest_edge(simplex)
local e = {dist = huge}
local i = #simplex-1
for k = 1,#simplex-1,2 do
local ax,ay = simplex[i], simplex[i+1]
local bx,by = simplex[k], simplex[k+1]
i = k
local ex,ey = vector.perpendicular(bx-ax, by-ay)
local nx,ny = vector.normalize(ex,ey)
local d = vector.dot(ax,ay, nx,ny)
if d < e.dist then
e.dist = d
e.nx, e.ny = nx, ny
e.i = k
end
end
return e
end
local function EPA(shape_a, shape_b, simplex)
-- make sure simplex is oriented counter clockwise
local cx,cy, bx,by, ax,ay = unpack(simplex)
if vector.dot(ax-bx,ay-by, cx-bx,cy-by) < 0 then
simplex[1],simplex[2] = ax,ay
simplex[5],simplex[6] = cx,cy
end
-- the expanding polytype algorithm
local is_either_circle = shape_a._center or shape_b._center
local last_diff_dist = huge
while true do
local e = closest_edge(simplex)
local px,py = support(shape_a, shape_b, e.nx, e.ny)
local d = vector.dot(px,py, e.nx, e.ny)
local diff_dist = d - e.dist
if diff_dist < 1e-6 or (is_either_circle and abs(last_diff_dist - diff_dist) < 1e-10) then
return -d*e.nx, -d*e.ny
end
last_diff_dist = diff_dist
-- simplex = {..., simplex[e.i-1], px, py, simplex[e.i]
table.insert(simplex, e.i, py)
table.insert(simplex, e.i, px)
end
end
-- : : origin must be in plane between A and B
-- B o------o A since A is the furthest point on the MD
-- : : in direction of the origin.
local function do_line(simplex)
local bx,by, ax,ay = unpack(simplex)
local abx,aby = bx-ax, by-ay
local dx,dy = vector.perpendicular(abx,aby)
if vector.dot(dx,dy, -ax,-ay) < 0 then
dx,dy = -dx,-dy
end
return simplex, dx,dy
end
-- B .'
-- o-._ 1
-- | `-. .' The origin can only be in regions 1, 3 or 4:
-- | 4 o A 2 A lies on the edge of the MD and we came
-- | _.-' '. from left of BC.
-- o-' 3
-- C '.
local function do_triangle(simplex)
local cx,cy, bx,by, ax,ay = unpack(simplex)
local aox,aoy = -ax,-ay
local abx,aby = bx-ax, by-ay
local acx,acy = cx-ax, cy-ay
-- test region 1
local dx,dy = vector.perpendicular(abx,aby)
if vector.dot(dx,dy, acx,acy) > 0 then
dx,dy = -dx,-dy
end
if vector.dot(dx,dy, aox,aoy) > 0 then
-- simplex = {bx,by, ax,ay}
simplex[1], simplex[2] = bx,by
simplex[3], simplex[4] = ax,ay
simplex[5], simplex[6] = nil, nil
return simplex, dx,dy
end
-- test region 3
dx,dy = vector.perpendicular(acx,acy)
if vector.dot(dx,dy, abx,aby) > 0 then
dx,dy = -dx,-dy
end
if vector.dot(dx,dy, aox, aoy) > 0 then
-- simplex = {cx,cy, ax,ay}
simplex[3], simplex[4] = ax,ay
simplex[5], simplex[6] = nil, nil
return simplex, dx,dy
end
-- must be in region 4
return simplex
end
local function GJK(shape_a, shape_b)
local ax,ay = support(shape_a, shape_b, 1,0)
if ax == 0 and ay == 0 then
-- only true if shape_a and shape_b are touching in a vertex, e.g.
-- .--- .---.
-- | A | .-. | B | support(A, 1,0) = x
-- '---x---. or : A :x---' support(B, -1,0) = x
-- | B | `-' => support(A,B,1,0) = x - x = 0
-- '---'
-- Since CircleShape:support(dx,dy) normalizes dx,dy we have to opt
-- out or the algorithm blows up. In accordance to the cases below
-- choose to judge this situation as not colliding.
return false
end
local simplex = {ax,ay}
local n = 2
local dx,dy = -ax,-ay
-- first iteration: line case
ax,ay = support(shape_a, shape_b, dx,dy)
if vector.dot(ax,ay, dx,dy) <= 0 then
return false
end
simplex[n+1], simplex[n+2] = ax,ay
simplex, dx, dy = do_line(simplex, dx, dy)
n = 4
-- all other iterations must be the triangle case
while true do
ax,ay = support(shape_a, shape_b, dx,dy)
if vector.dot(ax,ay, dx,dy) <= 0 then
return false
end
simplex[n+1], simplex[n+2] = ax,ay
simplex, dx, dy = do_triangle(simplex, dx,dy)
n = #simplex
if n == 6 then
return true, EPA(shape_a, shape_b, simplex)
end
end
end
return GJK

142
assets/scripts/vendor/HC/init.lua vendored Executable file
View File

@ -0,0 +1,142 @@
--[[
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, common_local = ..., common
if not (type(common) == 'table' and common.class and common.instance) then
assert(common_class ~= false, 'No class commons specification available.')
require(_NAME .. '.class')
end
local Shapes = require(_NAME .. '.shapes')
local Spatialhash = require(_NAME .. '.spatialhash')
-- reset global table `common' (required by class commons)
if common_local ~= common then
common_local, common = common, common_local
end
local newPolygonShape = Shapes.newPolygonShape
local newCircleShape = Shapes.newCircleShape
local newPointShape = Shapes.newPointShape
local HC = {}
function HC:init(cell_size)
self.hash = common_local.instance(Spatialhash, cell_size or 100)
end
-- spatial hash management
function HC:resetHash(cell_size)
local hash = self.hash
self.hash = common_local.instance(Spatialhash, cell_size or 100)
for shape in pairs(hash:shapes()) do
self.hash:register(shape, shape:bbox())
end
return self
end
function HC:register(shape)
self.hash:register(shape, shape:bbox())
-- keep track of where/how big the shape is
for _, f in ipairs({'move', 'rotate', 'scale'}) do
local old_function = shape[f]
shape[f] = function(this, ...)
local x1,y1,x2,y2 = this:bbox()
old_function(this, ...)
self.hash:update(this, x1,y1,x2,y2, this:bbox())
return this
end
end
return shape
end
function HC:remove(shape)
self.hash:remove(shape, shape:bbox())
for _, f in ipairs({'move', 'rotate', 'scale'}) do
shape[f] = function()
error(f.."() called on a removed shape")
end
end
return self
end
-- shape constructors
function HC:polygon(...)
return self:register(newPolygonShape(...))
end
function HC:rectangle(x,y,w,h)
return self:polygon(x,y, x+w,y, x+w,y+h, x,y+h)
end
function HC:circle(x,y,r)
return self:register(newCircleShape(x,y,r))
end
function HC:point(x,y)
return self:register(newPointShape(x,y))
end
-- collision detection
function HC:neighbors(shape)
local neighbors = self.hash:inSameCells(shape:bbox())
rawset(neighbors, shape, nil)
return neighbors
end
function HC:collisions(shape)
local candidates = self:neighbors(shape)
for other in pairs(candidates) do
local collides, dx, dy = shape:collidesWith(other)
if collides then
rawset(candidates, other, {dx,dy, x=dx, y=dy})
else
rawset(candidates, other, nil)
end
end
return candidates
end
-- the class and the instance
HC = common_local.class('HardonCollider', HC)
local instance = common_local.instance(HC)
-- the module
return setmetatable({
new = function(...) return common_local.instance(HC, ...) end,
resetHash = function(...) return instance:resetHash(...) end,
register = function(...) return instance:register(...) end,
remove = function(...) return instance:remove(...) end,
polygon = function(...) return instance:polygon(...) end,
rectangle = function(...) return instance:rectangle(...) end,
circle = function(...) return instance:circle(...) end,
point = function(...) return instance:point(...) end,
neighbors = function(...) return instance:neighbors(...) end,
collisions = function(...) return instance:collisions(...) end,
hash = function() return instance.hash end,
}, {__call = function(_, ...) return common_local.instance(HC, ...) end})

474
assets/scripts/vendor/HC/polygon.lua vendored Executable file
View File

@ -0,0 +1,474 @@
--[[
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, common_local = (...):match("^(.+)%.[^%.]+"), common
if not (type(common) == 'table' and common.class and common.instance) then
assert(common_class ~= false, 'No class commons specification available.')
require(_PACKAGE .. '.class')
common_local, common = common, common_local
end
local vector = require(_PACKAGE .. '.vector-light')
----------------------------
-- Private helper functions
--
-- create vertex list of coordinate pairs
local function toVertexList(vertices, x,y, ...)
if not (x and y) then return vertices end -- no more arguments
vertices[#vertices + 1] = {x = x, y = y} -- set vertex
return toVertexList(vertices, ...) -- recurse
end
-- returns true if three vertices lie on a line
local function areCollinear(p, q, r, eps)
return math.abs(vector.det(q.x-p.x, q.y-p.y, r.x-p.x,r.y-p.y)) <= (eps or 1e-32)
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 vector.det(q.x-p.x, q.y-p.y, r.x-p.x, r.y-p.y) >= 0
end
-- test wether a and b lie on the same side of the line c->d
local function onSameSide(a,b, c,d)
local px, py = d.x-c.x, d.y-c.y
local l = vector.det(px,py, a.x-c.x, a.y-c.y)
local m = vector.det(px,py, b.x-c.x, b.y-c.y)
return l*m >= 0
end
local function pointInTriangle(p, a,b,c)
return onSameSide(p,a, b,c) and onSameSide(p,b, a,c) and onSameSide(p,c, a,b)
end
-- test whether any point in vertices (but pqr) lies in the triangle pqr
-- note: vertices is *set*, not a list!
local function anyPointInTriangle(vertices, p,q,r)
for v in pairs(vertices) do
if v ~= p and v ~= q and v ~= r and pointInTriangle(v, p,q,r) then
return true
end
end
return false
end
-- test is the triangle pqr is an "ear" of the polygon
-- note: vertices is *set*, not a list!
local function isEar(p,q,r, vertices)
return ccw(p,q,r) and not anyPointInTriangle(vertices, p,q,r)
end
local function segmentsInterset(a,b, p,q)
return not (onSameSide(a,b, p,q) or onSameSide(p,q, a,b))
end
-- returns starting/ending 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,q2
local function getSharedEdge(p,q)
local pindex = setmetatable({}, {__index = function(t,k)
local s = {}
t[k] = s
return s
end})
-- record indices of vertices in p by their coordinates
for i = 1,#p do
pindex[p[i].x][p[i].y] = i
end
-- iterate over all edges in q. if both endpoints of that
-- edge are in p as well, return the indices of the starting
-- vertex
local i,k = #q,1
for k = 1,#q do
local v,w = q[i], q[k]
if pindex[v.x][v.y] and pindex[w.x][w.y] then
return pindex[w.x][w.y], k
end
i = k
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
-- assert polygon is not self-intersecting
-- outer: only need to check segments #vert;1, 1;2, ..., #vert-3;#vert-2
-- inner: only need to check unconnected segments
local q,p = vertices[#vertices]
for i = 1,#vertices-2 do
p, q = q, vertices[i]
for k = i+1,#vertices-1 do
local a,b = vertices[k], vertices[k+1]
assert(not segmentsInterset(p,q, a,b), 'Polygon may not intersect itself')
end
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
local p,q = vertices[#vertices], vertices[1]
local det = vector.det(p.x,p.y, q.x,q.y) -- also used below
self.area = det
for i = 2,#vertices do
p,q = q,vertices[i]
self.area = self.area + vector.det(p.x,p.y, q.x,q.y)
end
self.area = self.area / 2
p,q = vertices[#vertices], vertices[1]
self.centroid = {x = (p.x+q.x)*det, y = (p.y+q.y)*det}
for i = 2,#vertices do
p,q = q,vertices[i]
det = vector.det(p.x,p.y, q.x,q.y)
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.x = self.centroid.x / (6 * self.area)
self.centroid.y = self.centroid.y / (6 * self.area)
-- get outcircle
self._radius = 0
for i = 1,#vertices do
self._radius = math.max(self._radius,
vector.dist(vertices[i].x,vertices[i].y, self.centroid.x,self.centroid.y))
end
end
local newPolygon
-- return vertices as x1,y1,x2,y2, ..., xn,yn
function Polygon:unpack()
local v = {}
for i = 1,#self.vertices do
v[2*i-1] = self.vertices[i].x
v[2*i] = self.vertices[i].y
end
return unpack(v)
end
-- deep copy of the polygon
function Polygon:clone()
return Polygon( self:unpack() )
end
-- get bounding box
function Polygon:bbox()
local ulx,uly = self.vertices[1].x, self.vertices[1].y
local lrx,lry = ulx,uly
for i=2,#self.vertices do
local p = self.vertices[i]
if ulx > p.x then ulx = p.x end
if uly > p.y then uly = p.y end
if lrx < p.x then lrx = p.x end
if lry < p.y then lry = p.y end
end
return ulx,uly, lrx,lry
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, cx, cy)
if not (cx and cy) then
cx,cy = self.centroid.x, self.centroid.y
end
for i,v in ipairs(self.vertices) do
-- v = (v - center):rotate(angle) + center
v.x,v.y = vector.add(cx,cy, vector.rotate(angle, v.x-cx, v.y-cy))
end
local v = self.centroid
v.x,v.y = vector.add(cx,cy, vector.rotate(angle, v.x-cx, v.y-cy))
end
function Polygon:scale(s, cx,cy)
if not (cx and cy) then
cx,cy = self.centroid.x, self.centroid.y
end
for i,v in ipairs(self.vertices) do
-- v = (v - center) * s + center
v.x,v.y = vector.add(cx,cy, vector.mul(s, v.x-cx, v.y-cy))
end
self._radius = self._radius * s
end
-- triangulation by the method of kong
function Polygon:triangulate()
if #self.vertices == 3 then return {self:clone()} end
local vertices = self.vertices
local next_idx, prev_idx = {}, {}
for i = 1,#vertices do
next_idx[i], prev_idx[i] = i+1,i-1
end
next_idx[#next_idx], prev_idx[1] = 1, #prev_idx
local concave = {}
for i, v in ipairs(vertices) do
if not ccw(vertices[prev_idx[i]], v, vertices[next_idx[i]]) then
concave[v] = true
end
end
local triangles = {}
local n_vert, current, skipped, next, prev = #vertices, 1, 0
while n_vert > 3 do
next, prev = next_idx[current], prev_idx[current]
local p,q,r = vertices[prev], vertices[current], vertices[next]
if isEar(p,q,r, concave) then
triangles[#triangles+1] = newPolygon(p.x,p.y, q.x,q.y, r.x,r.y)
next_idx[prev], prev_idx[next] = next, prev
concave[q] = nil
n_vert, skipped = n_vert - 1, 0
else
skipped = skipped + 1
assert(skipped <= n_vert, "Cannot triangulate polygon")
end
current = next
end
next, prev = next_idx[current], prev_idx[current]
local p,q,r = vertices[prev], vertices[current], vertices[next]
triangles[#triangles+1] = newPolygon(p.x,p.y, q.x,q.y, r.x,r.y)
return triangles
end
-- return merged polygon if possible or nil otherwise
function Polygon:mergedWith(other)
local p,q = getSharedEdge(self.vertices, other.vertices)
assert(p and q, "Polygons do not share an edge")
local ret = {}
for i = 1,p-1 do
ret[#ret+1] = self.vertices[i].x
ret[#ret+1] = self.vertices[i].y
end
for i = 0,#other.vertices-2 do
i = ((i-1 + q) % #other.vertices) + 1
ret[#ret+1] = other.vertices[i].x
ret[#ret+1] = other.vertices[i].y
end
for i = p+1,#self.vertices do
ret[#ret+1] = self.vertices[i].x
ret[#ret+1] = self.vertices[i].y
end
return newPolygon(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 success, merged = pcall(function() return p:mergedWith(convex[k]) end)
if success 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
local p,q = v[#v],v[#v]
for i = 1, #v do
p,q = q,v[i]
if cut_ray(p,q) or cross_boundary(p,q) then
in_polygon = not in_polygon
end
end
return in_polygon
end
function Polygon:intersectionsWithRay(x,y, dx,dy)
local nx,ny = vector.perpendicular(dx,dy)
local wx,wy,det
local ts = {} -- ray parameters of each intersection
local q1,q2 = nil, self.vertices[#self.vertices]
for i = 1, #self.vertices do
q1,q2 = q2,self.vertices[i]
wx,wy = q2.x - q1.x, q2.y - q1.y
det = vector.det(dx,dy, wx,wy)
if det ~= 0 then
-- there is an intersection point. check if it lies on both
-- the ray and the segment.
local rx,ry = q2.x - x, q2.y - y
local l = vector.det(rx,ry, wx,wy) / det
local m = vector.det(dx,dy, rx,ry) / det
if m >= 0 and m <= 1 then
-- we cannot jump out early here (i.e. when l > tmin) because
-- the polygon might be concave
ts[#ts+1] = 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 = vector.dot(q1.x-x,q1.y-y, nx,ny)
if dist == 0 then
local l = vector.dot(dx,dy, q1.x-x,q1.y-y)
local m = vector.dot(dx,dy, q2.x-x,q2.y-y)
if l >= m then
ts[#ts+1] = l
else
ts[#ts+1] = m
end
end
end
end
return ts
end
function Polygon:intersectsRay(x,y, dx,dy)
local tmin = math.huge
for _, t in ipairs(self:intersectionsWithRay(x,y,dx,dy)) do
tmin = math.min(tmin, t)
end
return tmin ~= math.huge, tmin
end
Polygon = common_local.class('Polygon', Polygon)
newPolygon = function(...) return common_local.instance(Polygon, ...) end
return Polygon

466
assets/scripts/vendor/HC/shapes.lua vendored Executable file
View File

@ -0,0 +1,466 @@
--[[
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_min, math_sqrt, math_huge = math.min, math.sqrt, math.huge
local _PACKAGE, common_local = (...):match("^(.+)%.[^%.]+"), common
if not (type(common) == 'table' and common.class and common.instance) then
assert(common_class ~= false, 'No class commons specification available.')
require(_PACKAGE .. '.class')
end
local vector = require(_PACKAGE .. '.vector-light')
local Polygon = require(_PACKAGE .. '.polygon')
local GJK = require(_PACKAGE .. '.gjk') -- actual collision detection
-- reset global table `common' (required by class commons)
if common_local ~= common then
common_local, common = common, common_local
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
--
-- class definitions
--
local ConvexPolygonShape = {}
function ConvexPolygonShape:init(polygon)
Shape.init(self, 'polygon')
assert(polygon:isConvex(), "Polygon is not convex.")
self._polygon = polygon
end
local ConcavePolygonShape = {}
function ConcavePolygonShape:init(poly)
Shape.init(self, 'compound')
self._polygon = poly
self._shapes = poly:splitConvex()
for i,s in ipairs(self._shapes) do
self._shapes[i] = common_local.instance(ConvexPolygonShape, s)
end
end
local CircleShape = {}
function CircleShape:init(cx,cy, radius)
Shape.init(self, 'circle')
self._center = {x = cx, y = cy}
self._radius = radius
end
local PointShape = {}
function PointShape:init(x,y)
Shape.init(self, 'point')
self._pos = {x = x, y = y}
end
--
-- collision functions
--
function ConvexPolygonShape:support(dx,dy)
local v = self._polygon.vertices
local max, vmax = -math_huge
for i = 1,#v do
local d = vector.dot(v[i].x,v[i].y, dx,dy)
if d > max then
max, vmax = d, v[i]
end
end
return vmax.x, vmax.y
end
function CircleShape:support(dx,dy)
return vector.add(self._center.x, self._center.y,
vector.mul(self._radius, vector.normalize(dx,dy)))
end
-- collision dispatching:
-- let circle shape or compund shape handle the collision
function ConvexPolygonShape:collidesWith(other)
if self == other then return false end
if other._type ~= 'polygon' then
local collide, sx,sy = other:collidesWith(self)
return collide, sx and -sx, sy and -sy
end
-- else: type is POLYGON
return GJK(self, other)
end
function ConcavePolygonShape:collidesWith(other)
if self == other then return false end
if other._type == 'point' then
return other:collidesWith(self)
end
-- TODO: better way of doing this. report all the separations?
local collide,dx,dy = false,0,0
for _,s in ipairs(self._shapes) do
local status, sx,sy = s:collidesWith(other)
collide = collide or status
if status then
if math.abs(dx) < math.abs(sx) then
dx = sx
end
if math.abs(dy) < math.abs(sy) then
dy = sy
end
end
end
return collide, dx, dy
end
function CircleShape:collidesWith(other)
if self == other then return false end
if other._type == 'circle' then
local px,py = self._center.x-other._center.x, self._center.y-other._center.y
local d = vector.len2(px,py)
local radii = self._radius + other._radius
if d < radii*radii then
-- if circles overlap, push it out upwards
if d == 0 then return true, 0,radii end
-- otherwise push out in best direction
return true, vector.mul(radii - math_sqrt(d), vector.normalize(px,py))
end
return false
elseif other._type == 'polygon' then
return GJK(self, other)
end
-- else: let the other shape decide
local collide, sx,sy = other:collidesWith(self)
return collide, sx and -sx, sy and -sy
end
function PointShape:collidesWith(other)
if self == other then return false end
if other._type == 'point' then
return (self._pos == other._pos), 0,0
end
return other:contains(self._pos.x, self._pos.y), 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.len2(x-self._center.x, y-self._center.y) < 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
function ConcavePolygonShape:intersectionsWithRay(x,y, dx,dy)
return self._polygon:intersectionsWithRay(x,y, dx,dy)
end
function ConvexPolygonShape:intersectionsWithRay(x,y, dx,dy)
return self._polygon:intersectionsWithRay(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:intersectionsWithRay(x,y, dx,dy)
local pcx,pcy = x-self._center.x, y-self._center.y
local a = vector.len2(dx,dy)
local b = 2 * vector.dot(dx,dy, pcx,pcy)
local c = vector.len2(pcx,pcy) - self._radius * self._radius
local discr = b*b - 4*a*c
if discr < 0 then return {} end
discr = math_sqrt(discr)
local ts, t1, t2 = {}, discr-b, -discr-b
if t1 >= 0 then ts[#ts+1] = t1/(2*a) end
if t2 >= 0 then ts[#ts+1] = t2/(2*a) end
return ts
end
function CircleShape:intersectsRay(x,y, dx,dy)
local tmin = math_huge
for _, t in ipairs(self:intersectionsWithRay(x,y,dx,dy)) do
tmin = math_min(t, tmin)
end
return tmin ~= math_huge, tmin
end
-- point shape intersects ray if it lies on the ray
function PointShape:intersectsRay(x,y, dx,dy)
local px,py = self._pos.x-x, self._pos.y-y
local t = vector.dot(px,py, dx,dy) / vector.len2(dx,dy)
return t >= 0, t
end
function PointShape:intersectionsWithRay(x,y, dx,dy)
local intersects, t = self:intersectsRay(x,y, dx,dy)
return intersects and {t} or {}
end
--
-- auxiliary
--
function ConvexPolygonShape:center()
return self._polygon.centroid.x, self._polygon.centroid.y
end
function ConcavePolygonShape:center()
return self._polygon.centroid.x, self._polygon.centroid.y
end
function CircleShape:center()
return self._center.x, self._center.y
end
function PointShape:center()
return self._pos.x, self._pos.y
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:bbox()
end
function ConcavePolygonShape:bbox()
return self._polygon:bbox()
end
function CircleShape:bbox()
local cx,cy = self:center()
local r = self._radius
return cx-r,cy-r, cx+r,cy+r
end
function PointShape:bbox()
local x,y = self:center()
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.x = self._center.x + x
self._center.y = self._center.y + 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)
if not (cx and cy) then
cx,cy = self:center()
end
self._polygon:rotate(angle,cx,cy)
for _,p in ipairs(self._shapes) do
p:rotate(angle, cx,cy)
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 and cy) then return end
self._center.x,self._center.y = vector.add(cx,cy, vector.rotate(angle, self._center.x-cx, self._center.y-cy))
end
function PointShape:rotate(angle, cx,cy)
Shape.rotate(self, angle)
if not (cx and cy) then return end
self._pos.x,self._pos.y = vector.add(cx,cy, vector.rotate(angle, self._pos.x-cx, self._pos.y-cy))
end
function ConcavePolygonShape:scale(s)
assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
local cx,cy = self:center()
self._polygon:scale(s, cx,cy)
for _, p in ipairs(self._shapes) do
local dx,dy = vector.sub(cx,cy, p:center())
p:scale(s)
p:moveTo(cx-dx*s, cy-dy*s)
end
end
function ConvexPolygonShape:scale(s)
assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
self._polygon:scale(s, self:center())
end
function CircleShape:scale(s)
assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
self._radius = self._radius * s
end
function PointShape:scale()
-- nothing
end
function ConvexPolygonShape:draw(mode)
mode = mode or 'line'
love.graphics.polygon(mode, self._polygon:unpack())
end
function ConcavePolygonShape:draw(mode, wireframe)
local mode = mode or 'line'
if mode == 'line' then
love.graphics.polygon('line', self._polygon:unpack())
if not wireframe then return end
end
for _,p in ipairs(self._shapes) do
love.graphics.polygon(mode, p._polygon:unpack())
end
end
function CircleShape:draw(mode, segments)
love.graphics.circle(mode or 'line', self:outcircle())
end
function PointShape:draw()
love.graphics.point(self:center())
end
Shape = common_local.class('Shape', Shape)
ConvexPolygonShape = common_local.class('ConvexPolygonShape', ConvexPolygonShape, Shape)
ConcavePolygonShape = common_local.class('ConcavePolygonShape', ConcavePolygonShape, Shape)
CircleShape = common_local.class('CircleShape', CircleShape, Shape)
PointShape = common_local.class('PointShape', PointShape, Shape)
local function newPolygonShape(polygon, ...)
-- create from coordinates if needed
if type(polygon) == "number" then
polygon = common_local.instance(Polygon, polygon, ...)
else
polygon = polygon:clone()
end
if polygon:isConvex() then
return common_local.instance(ConvexPolygonShape, polygon)
end
return common_local.instance(ConcavePolygonShape, polygon)
end
local function newCircleShape(...)
return common_local.instance(CircleShape, ...)
end
local function newPointShape(...)
return common_local.instance(PointShape, ...)
end
return {
ConcavePolygonShape = ConcavePolygonShape,
ConvexPolygonShape = ConvexPolygonShape,
CircleShape = CircleShape,
PointShape = PointShape,
newPolygonShape = newPolygonShape,
newCircleShape = newCircleShape,
newPointShape = newPointShape,
}

202
assets/scripts/vendor/HC/spatialhash.lua vendored Executable file
View File

@ -0,0 +1,202 @@
--[[
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, common_local = (...):match("^(.+)%.[^%.]+"), common
if not (type(common) == 'table' and common.class and common.instance) then
assert(common_class ~= false, 'No class commons specification available.')
require(_PACKAGE .. '.class')
common_local, common = common, common_local
end
local Spatialhash = {}
function Spatialhash:init(cell_size)
self.cell_size = cell_size or 100
self.cells = {}
end
function Spatialhash:cellCoords(x,y)
return floor(x / self.cell_size), floor(y / self.cell_size)
end
function Spatialhash:cell(i,k)
local row = rawget(self.cells, i)
if not row then
row = {}
rawset(self.cells, i, row)
end
local cell = rawget(row, k)
if not cell then
cell = setmetatable({}, {__mode = "kv"})
rawset(row, k, cell)
end
return cell
end
function Spatialhash:cellAt(x,y)
return self:cell(self:cellCoords(x,y))
end
-- get all shapes
function Spatialhash:shapes()
local set = {}
for i,row in pairs(self.cells) do
for k,cell in pairs(row) do
for obj in pairs(cell) do
rawset(set, obj, obj)
end
end
end
return set
end
-- get all shapes that are in the same cells as the bbox x1,y1 '--. x2,y2
function Spatialhash:inSameCells(x1,y1, x2,y2)
local set = {}
x1, y1 = self:cellCoords(x1, y1)
x2, y2 = self:cellCoords(x2, y2)
for i = x1,x2 do
for k = y1,y2 do
for obj in pairs(self:cell(i,k)) do
rawset(set, obj, obj)
end
end
end
return set
end
function Spatialhash:register(obj, x1, y1, x2, y2)
x1, y1 = self:cellCoords(x1, y1)
x2, y2 = self:cellCoords(x2, y2)
for i = x1,x2 do
for k = y1,y2 do
rawset(self:cell(i,k), obj, obj)
end
end
end
function Spatialhash:remove(obj, x1, y1, x2,y2)
-- no bbox given. => must check all cells
if not (x1 and y1 and x2 and y2) then
for _,row in pairs(self.cells) do
for _,cell in pairs(row) do
rawset(cell, obj, nil)
end
end
return
end
-- else: remove only from bbox
x1,y1 = self:cellCoords(x1,y1)
x2,y2 = self:cellCoords(x2,y2)
for i = x1,x2 do
for k = y1,y2 do
rawset(self:cell(i,k), obj, nil)
end
end
end
-- update an objects position
function Spatialhash:update(obj, old_x1,old_y1, old_x2,old_y2, new_x1,new_y1, new_x2,new_y2)
old_x1, old_y1 = self:cellCoords(old_x1, old_y1)
old_x2, old_y2 = self:cellCoords(old_x2, old_y2)
new_x1, new_y1 = self:cellCoords(new_x1, new_y1)
new_x2, new_y2 = self:cellCoords(new_x2, new_y2)
if old_x1 == new_x1 and old_y1 == new_y1 and
old_x2 == new_x2 and old_y2 == new_y2 then
return
end
for i = old_x1,old_x2 do
for k = old_y1,old_y2 do
rawset(self:cell(i,k), obj, nil)
end
end
for i = new_x1,new_x2 do
for k = new_y1,new_y2 do
rawset(self:cell(i,k), obj, obj)
end
end
end
function Spatialhash:intersectionsWithSegment(x1, y1, x2, y2)
local odx, ody = x2 - x1, y2 - y1
local len, cur = vector.len(odx, ody), 0
local dx, dy = vector.normalize(odx, ody)
local step = self.cell_size / 2
local visited = {}
local points = {}
local mt = math.huge
while (cur + step < len) do
local cx, cy = x1 + dx * cur, y1 + dy * cur
local shapes = self:cellAt(cx, cy)
cur = cur + step
for _, shape in pairs(shapes) do
if (not visited[shape]) then
local ints = shape:intersectionsWithRay(x1, y1, dx, dy)
for _, t in ipairs(ints) do
if (t >= 0 and t <= len) then
local px, py = vector.add(x1, y1, vector.mul(t, dx, dy))
table.insert(points, {shape, t, px, py})
end
end
visited[shape] = true
end
end
end
table.sort(points, function(a, b)
return a[2] < b[2]
end)
return points
end
function Spatialhash:draw(how, show_empty, print_key)
if show_empty == nil then show_empty = true end
for k1,v in pairs(self.cells) do
for k2,cell in pairs(v) do
local is_empty = (next(cell) == nil)
if show_empty or not is_empty then
local x = k1 * self.cell_size
local y = k2 * self.cell_size
love.graphics.rectangle(how or 'line', x,y, self.cell_size, self.cell_size)
if print_key then
love.graphics.print(("%d:%d"):format(k1,k2), x+3,y+3)
end
end
end
end
end
return common_local.class('Spatialhash', Spatialhash)

138
assets/scripts/vendor/HC/vector-light.lua vendored Executable file
View File

@ -0,0 +1,138 @@
--[[
Copyright (c) 2012 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 sqrt, cos, sin = math.sqrt, math.cos, math.sin
local function str(x,y)
return "("..tonumber(x)..","..tonumber(y)..")"
end
local function mul(s, x,y)
return s*x, s*y
end
local function div(s, x,y)
return x/s, y/s
end
local function add(x1,y1, x2,y2)
return x1+x2, y1+y2
end
local function sub(x1,y1, x2,y2)
return x1-x2, y1-y2
end
local function permul(x1,y1, x2,y2)
return x1*x2, y1*y2
end
local function dot(x1,y1, x2,y2)
return x1*x2 + y1*y2
end
local function det(x1,y1, x2,y2)
return x1*y2 - y1*x2
end
local function eq(x1,y1, x2,y2)
return x1 == x2 and y1 == y2
end
local function lt(x1,y1, x2,y2)
return x1 < x2 or (x1 == x2 and y1 < y2)
end
local function le(x1,y1, x2,y2)
return x1 <= x2 and y1 <= y2
end
local function len2(x,y)
return x*x + y*y
end
local function len(x,y)
return sqrt(x*x + y*y)
end
local function dist(x1,y1, x2,y2)
return len(x1-x2, y1-y2)
end
local function normalize(x,y)
local l = len(x,y)
return x/l, y/l
end
local function rotate(phi, x,y)
local c, s = cos(phi), sin(phi)
return c*x - s*y, s*x + c*y
end
local function perpendicular(x,y)
return -y, x
end
local function project(x,y, u,v)
local s = (x*u + y*v) / (u*u + v*v)
return s*u, s*v
end
local function mirror(x,y, u,v)
local s = 2 * (x*u + y*v) / (u*u + v*v)
return s*u - x, s*v - y
end
-- the module
return {
str = str,
-- arithmetic
mul = mul,
div = div,
add = add,
sub = sub,
permul = permul,
dot = dot,
det = det,
cross = det,
-- relation
eq = eq,
lt = lt,
le = le,
-- misc operations
len2 = len2,
len = len,
dist = dist,
normalize = normalize,
rotate = rotate,
perpendicular = perpendicular,
project = project,
mirror = mirror,
}

View File

@ -1,770 +0,0 @@
local bump = {
_VERSION = 'bump v3.1.6',
_URL = 'https://github.com/kikito/bump.lua',
_DESCRIPTION = 'A collision detection library for Lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 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.
]]
}
------------------------------------------
-- Auxiliary functions
------------------------------------------
local DELTA = 1e-10 -- floating-point margin of error
local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
local function sign(x)
if x > 0 then return 1 end
if x == 0 then return 0 end
return -1
end
local function nearest(x, a, b)
if abs(a - x) < abs(b - x) then return a else return b end
end
local function assertType(desiredType, value, name)
if type(value) ~= desiredType then
error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
end
end
local function assertIsPositiveNumber(value, name)
if type(value) ~= 'number' or value <= 0 then
error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
end
end
local function assertIsRect(x,y,w,h)
assertType('number', x, 'x')
assertType('number', y, 'y')
assertIsPositiveNumber(w, 'w')
assertIsPositiveNumber(h, 'h')
end
local defaultFilter = function()
return 'slide'
end
------------------------------------------
-- Rectangle functions
------------------------------------------
local function rect_getNearestCorner(x,y,w,h, px, py)
return nearest(px, x, x+w), nearest(py, y, y+h)
end
-- This is a generalized implementation of the liang-barsky algorithm, which also returns
-- the normals of the sides where the segment intersects.
-- Returns nil if the segment never touches the rect
-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2)
ti1, ti2 = ti1 or 0, ti2 or 1
local dx, dy = x2-x1, y2-y1
local nx, ny
local nx1, ny1, nx2, ny2 = 0,0,0,0
local p, q, r
for side = 1,4 do
if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left
elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right
elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top
else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom
end
if p == 0 then
if q <= 0 then return nil end
else
r = q / p
if p < 0 then
if r > ti2 then return nil
elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny
end
else -- p > 0
if r < ti1 then return nil
elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny
end
end
end
end
return ti1,ti2, nx1,ny1, nx2,ny2
end
-- Calculates the minkowsky difference between 2 rects, which is another rect
local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
return x2 - x1 - w1,
y2 - y1 - h1,
w1 + w2,
h1 + h2
end
local function rect_containsPoint(x,y,w,h, px,py)
return px - x > DELTA and py - y > DELTA and
x + w - px > DELTA and y + h - py > DELTA
end
local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and x2 < x1+w1 and
y1 < y2+h2 and y2 < y1+h1
end
local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2)
local dx = x1 - x2 + (w1 - w2)/2
local dy = y1 - y2 + (h1 - h2)/2
return dx*dx + dy*dy
end
local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY)
goalX = goalX or x1
goalY = goalY or y1
local dx, dy = goalX - x1, goalY - y1
local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
local overlaps, ti, nx, ny
if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other
local px, py = rect_getNearestCorner(x,y,w,h, 0, 0)
local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection
ti = -wi * hi -- ti is the negative area of intersection
overlaps = true
else
local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge)
-- item tunnels into other
if ti1 and ti1 < 1 and (0 < ti1 + DELTA or 0 == ti1 and ti2 > 0) then
ti, nx, ny = ti1, nx1, ny1
overlaps = false
end
end
if not ti then return end
local tx, ty
if overlaps then
if dx == 0 and dy == 0 then
-- intersecting and not moving - use minimum displacement vector
local px, py = rect_getNearestCorner(x,y,w,h, 0,0)
if abs(px) < abs(py) then py = 0 else px = 0 end
nx, ny = sign(px), sign(py)
tx, ty = x1 + px, y1 + py
else
-- intersecting and moving - move in the opposite direction
local ti1, _
ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1)
if not ti1 then return end
tx, ty = x1 + dx * ti1, y1 + dy * ti1
end
else -- tunnel
tx, ty = x1 + dx * ti, y1 + dy * ti
end
return {
overlaps = overlaps,
ti = ti,
move = {x = dx, y = dy},
normal = {x = nx, y = ny},
touch = {x = tx, y = ty},
itemRect = {x = x1, y = y1, w = w1, h = h1},
otherRect = {x = x2, y = y2, w = w2, h = h2}
}
end
------------------------------------------
-- Grid functions
------------------------------------------
local function grid_toWorld(cellSize, cx, cy)
return (cx - 1)*cellSize, (cy-1)*cellSize
end
local function grid_toCell(cellSize, x, y)
return floor(x / cellSize) + 1, floor(y / cellSize) + 1
end
-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
-- It has been modified to include both cells when the ray "touches a grid corner",
-- and with a different exit condition
local function grid_traverse_initStep(cellSize, ct, t1, t2)
local v = t2 - t1
if v > 0 then
return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
elseif v < 0 then
return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
else
return 0, math.huge, math.huge
end
end
local function grid_traverse(cellSize, x1,y1,x2,y2, f)
local cx1,cy1 = grid_toCell(cellSize, x1,y1)
local cx2,cy2 = grid_toCell(cellSize, x2,y2)
local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
local cx,cy = cx1,cy1
f(cx, cy)
-- The default implementation had an infinite loop problem when
-- approaching the last cell in some occassions. We finish iterating
-- when we are *next* to the last cell
while abs(cx - cx2) + abs(cy - cy2) > 1 do
if tx < ty then
tx, cx = tx + dx, cx + stepX
f(cx, cy)
else
-- Addition: include both cells when going through corners
if tx == ty then f(cx + stepX, cy) end
ty, cy = ty + dy, cy + stepY
f(cx, cy)
end
end
-- If we have not arrived to the last cell, use it
if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end
end
local function grid_toCellRect(cellSize, x,y,w,h)
local cx,cy = grid_toCell(cellSize, x, y)
local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize)
return cx, cy, cr - cx + 1, cb - cy + 1
end
------------------------------------------
-- Responses
------------------------------------------
local touch = function(world, col, x,y,w,h, goalX, goalY, filter)
return col.touch.x, col.touch.y, {}, 0
end
local cross = function(world, col, x,y,w,h, goalX, goalY, filter)
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
local slide = function(world, col, x,y,w,h, goalX, goalY, filter)
goalX = goalX or x
goalY = goalY or y
local tch, move = col.touch, col.move
local sx, sy = tch.x, tch.y
if move.x ~= 0 or move.y ~= 0 then
if col.normal.x == 0 then
sx = goalX
else
sy = goalY
end
end
col.slide = {x = sx, y = sy}
x,y = tch.x, tch.y
goalX, goalY = sx, sy
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
local bounce = function(world, col, x,y,w,h, goalX, goalY, filter)
goalX = goalX or x
goalY = goalY or y
local tch, move = col.touch, col.move
local tx, ty = tch.x, tch.y
local bx, by = tx, ty
if move.x ~= 0 or move.y ~= 0 then
local bnx, bny = goalX - tx, goalY - ty
if col.normal.x == 0 then bny = -bny else bnx = -bnx end
bx, by = tx + bnx, ty + bny
end
col.bounce = {x = bx, y = by}
x,y = tch.x, tch.y
goalX, goalY = bx, by
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
------------------------------------------
-- World
------------------------------------------
local World = {}
local World_mt = {__index = World}
-- Private functions and methods
local function sortByWeight(a,b) return a.weight < b.weight end
local function sortByTiAndDistance(a,b)
if a.ti == b.ti then
local ir, ar, br = a.itemRect, a.otherRect, b.otherRect
local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
return ad < bd
end
return a.ti < b.ti
end
local function addItemToCell(self, item, cx, cy)
self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'})
local row = self.rows[cy]
row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})}
local cell = row[cx]
self.nonEmptyCells[cell] = true
if not cell.items[item] then
cell.items[item] = true
cell.itemCount = cell.itemCount + 1
end
end
local function removeItemFromCell(self, item, cx, cy)
local row = self.rows[cy]
if not row or not row[cx] or not row[cx].items[item] then return false end
local cell = row[cx]
cell.items[item] = nil
cell.itemCount = cell.itemCount - 1
if cell.itemCount == 0 then
self.nonEmptyCells[cell] = nil
end
return true
end
local function getDictItemsInCellRect(self, cl,ct,cw,ch)
local items_dict = {}
for cy=ct,ct+ch-1 do
local row = self.rows[cy]
if row then
for cx=cl,cl+cw-1 do
local cell = row[cx]
if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
for item,_ in pairs(cell.items) do
items_dict[item] = true
end
end
end
end
end
return items_dict
end
local function getCellsTouchedBySegment(self, x1,y1,x2,y2)
local cells, cellsLen, visited = {}, 0, {}
grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy)
local row = self.rows[cy]
if not row then return end
local cell = row[cx]
if not cell or visited[cell] then return end
visited[cell] = true
cellsLen = cellsLen + 1
cells[cellsLen] = cell
end)
return cells, cellsLen
end
local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter)
local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2)
local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1
local visited, itemInfo, itemInfoLen = {},{},0
for i=1,len do
cell = cells[i]
for item in pairs(cell.items) do
if not visited[item] then
visited[item] = true
if (not filter or filter(item)) then
rect = self.rects[item]
l,t,w,h = rect.x,rect.y,rect.w,rect.h
ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1)
if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
-- the sorting is according to the t of an infinite line, not the segment
tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge)
itemInfoLen = itemInfoLen + 1
itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)}
end
end
end
end
end
table.sort(itemInfo, sortByWeight)
return itemInfo, itemInfoLen
end
local function getResponseByName(self, name)
local response = self.responses[name]
if not response then
error(('Unknown collision type: %s (%s)'):format(name, type(name)))
end
return response
end
-- Misc Public Methods
function World:addResponse(name, response)
self.responses[name] = response
end
function World:project(item, x,y,w,h, goalX, goalY, filter)
assertIsRect(x,y,w,h)
goalX = goalX or x
goalY = goalY or y
filter = filter or defaultFilter
local collisions, len = {}, 0
local visited = {}
if item ~= nil then visited[item] = true end
-- This could probably be done with less cells using a polygon raster over the cells instead of a
-- bounding rect of the whole movement. Conditional to building a queryPolygon method
local tl, tt = min(goalX, x), min(goalY, y)
local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h)
local tw, th = tr-tl, tb-tt
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th)
local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
for other,_ in pairs(dictItemsInCellRect) do
if not visited[other] then
visited[other] = true
local responseName = filter(item, other)
if responseName then
local ox,oy,ow,oh = self:getRect(other)
local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY)
if col then
col.other = other
col.item = item
col.type = responseName
len = len + 1
collisions[len] = col
end
end
end
end
table.sort(collisions, sortByTiAndDistance)
return collisions, len
end
function World:countCells()
local count = 0
for _,row in pairs(self.rows) do
for _,_ in pairs(row) do
count = count + 1
end
end
return count
end
function World:hasItem(item)
return not not self.rects[item]
end
function World:getItems()
local items, len = {}, 0
for item,_ in pairs(self.rects) do
len = len + 1
items[len] = item
end
return items, len
end
function World:countItems()
local len = 0
for _ in pairs(self.rects) do len = len + 1 end
return len
end
function World:getRect(item)
local rect = self.rects[item]
if not rect then
error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.')
end
return rect.x, rect.y, rect.w, rect.h
end
function World:toWorld(cx, cy)
return grid_toWorld(self.cellSize, cx, cy)
end
function World:toCell(x,y)
return grid_toCell(self.cellSize, x, y)
end
--- Query methods
function World:queryRect(x,y,w,h, filter)
assertIsRect(x,y,w,h)
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
local items, len = {}, 0
local rect
for item,_ in pairs(dictItemsInCellRect) do
rect = self.rects[item]
if (not filter or filter(item))
and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:queryPoint(x,y, filter)
local cx,cy = self:toCell(x,y)
local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1)
local items, len = {}, 0
local rect
for item,_ in pairs(dictItemsInCellRect) do
rect = self.rects[item]
if (not filter or filter(item))
and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:querySegment(x1, y1, x2, y2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
local items = {}
for i=1, len do
items[i] = itemInfo[i].item
end
return items, len
end
function World:querySegmentWithCoords(x1, y1, x2, y2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
local dx, dy = x2-x1, y2-y1
local info, ti1, ti2
for i=1, len do
info = itemInfo[i]
ti1 = info.ti1
ti2 = info.ti2
info.weight = nil
info.x1 = x1 + dx * ti1
info.y1 = y1 + dy * ti1
info.x2 = x1 + dx * ti2
info.y2 = y1 + dy * ti2
end
return itemInfo, len
end
--- Main methods
function World:add(item, x,y,w,h)
local rect = self.rects[item]
if rect then
error('Item ' .. tostring(item) .. ' added to the world twice.')
end
assertIsRect(x,y,w,h)
self.rects[item] = {x=x,y=y,w=w,h=h}
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
for cy = ct, ct+ch-1 do
for cx = cl, cl+cw-1 do
addItemToCell(self, item, cx, cy)
end
end
return item
end
function World:remove(item)
local x,y,w,h = self:getRect(item)
self.rects[item] = nil
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
for cy = ct, ct+ch-1 do
for cx = cl, cl+cw-1 do
removeItemFromCell(self, item, cx, cy)
end
end
end
function World:update(item, x2,y2,w2,h2)
local x1,y1,w1,h1 = self:getRect(item)
w2,h2 = w2 or w1, h2 or h1
assertIsRect(x2,y2,w2,h2)
if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then
local cellSize = self.cellSize
local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1)
local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2)
if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then
local cr1, cb1 = cl1+cw1-1, ct1+ch1-1
local cr2, cb2 = cl2+cw2-1, ct2+ch2-1
local cyOut
for cy = ct1, cb1 do
cyOut = cy < ct2 or cy > cb2
for cx = cl1, cr1 do
if cyOut or cx < cl2 or cx > cr2 then
removeItemFromCell(self, item, cx, cy)
end
end
end
for cy = ct2, cb2 do
cyOut = cy < ct1 or cy > cb1
for cx = cl2, cr2 do
if cyOut or cx < cl1 or cx > cr1 then
addItemToCell(self, item, cx, cy)
end
end
end
end
local rect = self.rects[item]
rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2
end
end
function World:move(item, goalX, goalY, filter)
local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter)
self:update(item, actualX, actualY)
return actualX, actualY, cols, len
end
function World:check(item, goalX, goalY, filter)
filter = filter or defaultFilter
local visited = {[item] = true}
local visitedFilter = function(itm, other)
if visited[other] then return false end
return filter(itm, other)
end
local cols, len = {}, 0
local x,y,w,h = self:getRect(item)
local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter)
while projected_len > 0 do
local col = projected_cols[1]
len = len + 1
cols[len] = col
visited[col.other] = true
local response = getResponseByName(self, col.type)
goalX, goalY, projected_cols, projected_len = response(
self,
col,
x, y, w, h,
goalX, goalY,
visitedFilter
)
end
return goalX, goalY, cols, len
end
-- Public library functions
bump.newWorld = function(cellSize)
cellSize = cellSize or 64
assertIsPositiveNumber(cellSize, 'cellSize')
local world = setmetatable({
cellSize = cellSize,
rects = {},
rows = {},
nonEmptyCells = {},
responses = {}
}, World_mt)
world:addResponse('touch', touch)
world:addResponse('cross', cross)
world:addResponse('slide', slide)
world:addResponse('bounce', bounce)
return world
end
bump.rect = {
getNearestCorner = rect_getNearestCorner,
getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices,
getDiff = rect_getDiff,
containsPoint = rect_containsPoint,
isIntersecting = rect_isIntersecting,
getSquareDistance = rect_getSquareDistance,
detectCollision = rect_detectCollision
}
bump.responses = {
touch = touch,
cross = cross,
slide = slide,
bounce = bounce
}
return bump

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -14,6 +14,7 @@ function love.load()
asteroids.load(game)
moon.load(game)
spaceship.load(game)
controls.load(game)
end
-- UPDATE
@ -35,6 +36,7 @@ function love.draw()
spaceship.draw()
asteroids.draw()
end)
controls.draw()
end
-- CONTROLS