Add collisions
This commit is contained in:
parent
7bf419249c
commit
2af855e88a
BIN
android/game.apk
BIN
android/game.apk
Binary file not shown.
Binary file not shown.
@ -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>
|
||||
|
Binary file not shown.
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
Binary file not shown.
@ -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 \
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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>(...); }
|
||||
|
||||
|
@ -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 \
|
||||
|
@ -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;
|
@ -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 {
|
@ -1,4 +1,4 @@
|
||||
package love.to.android1127134907;
|
||||
package love.to.android1208185535;
|
||||
import org.love2d.android.GameActivity;
|
||||
|
||||
public class LtaActivity extends GameActivity {}
|
@ -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
|
@ -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
|
@ -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
|
@ -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,24 +33,27 @@ 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)
|
||||
-- Spaceship
|
||||
@ -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
21
assets/scripts/vendor/HC/HC-0.1-1.rockspec
vendored
Executable 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
4
assets/scripts/vendor/HC/README
vendored
Executable 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
99
assets/scripts/vendor/HC/class.lua
vendored
Executable 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
8
assets/scripts/vendor/HC/docs/Class.rst
vendored
Executable 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
250
assets/scripts/vendor/HC/docs/MainModule.rst
vendored
Executable 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
192
assets/scripts/vendor/HC/docs/Makefile
vendored
Executable 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
213
assets/scripts/vendor/HC/docs/Polygon.rst
vendored
Executable 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
303
assets/scripts/vendor/HC/docs/Shapes.rst
vendored
Executable 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
164
assets/scripts/vendor/HC/docs/SpatialHash.rst
vendored
Executable 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
8
assets/scripts/vendor/HC/docs/Vector.rst
vendored
Executable 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
296
assets/scripts/vendor/HC/docs/conf.py
vendored
Executable 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
115
assets/scripts/vendor/HC/docs/index.rst
vendored
Executable 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
26
assets/scripts/vendor/HC/docs/license.rst
vendored
Executable 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
16
assets/scripts/vendor/HC/docs/reference.rst
vendored
Executable 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
4
assets/scripts/vendor/HC/docs/tutorial.rst
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
Tutorial
|
||||
========
|
||||
|
||||
To be rewritten.
|
193
assets/scripts/vendor/HC/gjk.lua
vendored
Executable file
193
assets/scripts/vendor/HC/gjk.lua
vendored
Executable 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
142
assets/scripts/vendor/HC/init.lua
vendored
Executable 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
474
assets/scripts/vendor/HC/polygon.lua
vendored
Executable 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
466
assets/scripts/vendor/HC/shapes.lua
vendored
Executable 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
202
assets/scripts/vendor/HC/spatialhash.lua
vendored
Executable 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
138
assets/scripts/vendor/HC/vector-light.lua
vendored
Executable 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,
|
||||
}
|
770
assets/scripts/vendor/bump.lua
vendored
770
assets/scripts/vendor/bump.lua
vendored
@ -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 |
Loading…
Reference in New Issue
Block a user