added vendored files
@@ -0,0 +1,47 @@
|
||||
HUMP - Helper Utilities for Massive Progression
|
||||
===============================================
|
||||
|
||||
__HUMP__ is a small collection of tools for developing games with LÖVE.
|
||||
|
||||
Contents:
|
||||
------------
|
||||
|
||||
* *gamestate.lua*: Easy gamestate management.
|
||||
* *timer.lua*: Delayed and time-limited function calls and tweening.
|
||||
* *vector.lua*: 2D vector math.
|
||||
* *vector-light.lua*: Lightweight 2D vector math (for optimisation purposes - leads to potentially ugly code).
|
||||
* *class.lua*: Lightweight object orientation (class or prototype based).
|
||||
* *signal.lua*: Simple Signal/Slot (aka. Observer) implementation.
|
||||
* *camera.lua*: Move-, zoom- and rotatable camera with camera locking and movement smoothing.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can find the documentation here: [hump.readthedocs.org](http://hump.readthedocs.org)
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
> Copyright (c) 2010-2013 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.
|
||||
@@ -0,0 +1,216 @@
|
||||
--[[
|
||||
Copyright (c) 2010-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.
|
||||
]]--
|
||||
|
||||
local _PATH = (...):match('^(.*[%./])[^%.%/]+$') or ''
|
||||
local cos, sin = math.cos, math.sin
|
||||
|
||||
local camera = {}
|
||||
camera.__index = camera
|
||||
|
||||
-- Movement interpolators (for camera locking/windowing)
|
||||
camera.smooth = {}
|
||||
|
||||
function camera.smooth.none()
|
||||
return function(dx,dy) return dx,dy end
|
||||
end
|
||||
|
||||
function camera.smooth.linear(speed)
|
||||
assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed))
|
||||
return function(dx,dy, s)
|
||||
-- normalize direction
|
||||
local d = math.sqrt(dx*dx+dy*dy)
|
||||
local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal
|
||||
if d > 0 then
|
||||
dx,dy = dx/d, dy/d
|
||||
end
|
||||
|
||||
return dx*dts, dy*dts
|
||||
end
|
||||
end
|
||||
|
||||
function camera.smooth.damped(stiffness)
|
||||
assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness))
|
||||
return function(dx,dy, s)
|
||||
local dts = love.timer.getDelta() * (s or stiffness)
|
||||
return dx*dts, dy*dts
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function new(x,y, zoom, rot, smoother)
|
||||
x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2
|
||||
zoom = zoom or 1
|
||||
rot = rot or 0
|
||||
smoother = smoother or camera.smooth.none() -- for locking, see below
|
||||
return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera)
|
||||
end
|
||||
|
||||
function camera:lookAt(x,y)
|
||||
self.x, self.y = x, y
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:move(dx,dy)
|
||||
self.x, self.y = self.x + dx, self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:position()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
function camera:rotate(phi)
|
||||
self.rot = self.rot + phi
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:rotateTo(phi)
|
||||
self.rot = phi
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:zoom(mul)
|
||||
self.scale = self.scale * mul
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:zoomTo(zoom)
|
||||
self.scale = zoom
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:attach(x,y,w,h, noclip)
|
||||
x,y = x or 0, y or 0
|
||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||
|
||||
self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor()
|
||||
if not noclip then
|
||||
love.graphics.setScissor(x,y,w,h)
|
||||
end
|
||||
|
||||
local cx,cy = x+w/2, y+h/2
|
||||
love.graphics.push()
|
||||
love.graphics.translate(cx, cy)
|
||||
love.graphics.scale(self.scale)
|
||||
love.graphics.rotate(self.rot)
|
||||
love.graphics.translate(-self.x, -self.y)
|
||||
end
|
||||
|
||||
function camera:detach()
|
||||
love.graphics.pop()
|
||||
love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh)
|
||||
end
|
||||
|
||||
function camera:draw(...)
|
||||
local x,y,w,h,noclip,func
|
||||
local nargs = select("#", ...)
|
||||
if nargs == 1 then
|
||||
func = ...
|
||||
elseif nargs == 5 then
|
||||
x,y,w,h,func = ...
|
||||
elseif nargs == 6 then
|
||||
x,y,w,h,noclip,func = ...
|
||||
else
|
||||
error("Invalid arguments to camera:draw()")
|
||||
end
|
||||
|
||||
self:attach(x,y,w,h,noclip)
|
||||
func()
|
||||
self:detach()
|
||||
end
|
||||
|
||||
-- world coordinates to camera coordinates
|
||||
function camera:cameraCoords(x,y, ox,oy,w,h)
|
||||
ox, oy = ox or 0, oy or 0
|
||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||
|
||||
-- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center
|
||||
local c,s = cos(self.rot), sin(self.rot)
|
||||
x,y = x - self.x, y - self.y
|
||||
x,y = c*x - s*y, s*x + c*y
|
||||
return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy
|
||||
end
|
||||
|
||||
-- camera coordinates to world coordinates
|
||||
function camera:worldCoords(x,y, ox,oy,w,h)
|
||||
ox, oy = ox or 0, oy or 0
|
||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||
|
||||
-- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y)
|
||||
local c,s = cos(-self.rot), sin(-self.rot)
|
||||
x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale
|
||||
x,y = c*x - s*y, s*x + c*y
|
||||
return x+self.x, y+self.y
|
||||
end
|
||||
|
||||
function camera:mousePosition(ox,oy,w,h)
|
||||
local mx,my = love.mouse.getPosition()
|
||||
return self:worldCoords(mx,my, ox,oy,w,h)
|
||||
end
|
||||
|
||||
-- camera scrolling utilities
|
||||
function camera:lockX(x, smoother, ...)
|
||||
local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...)
|
||||
self.x = self.x + dx
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:lockY(y, smoother, ...)
|
||||
local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...)
|
||||
self.y = self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:lockPosition(x,y, smoother, ...)
|
||||
return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...))
|
||||
end
|
||||
|
||||
function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...)
|
||||
-- figure out displacement in camera coordinates
|
||||
x,y = self:cameraCoords(x,y)
|
||||
local dx, dy = 0,0
|
||||
if x < x_min then
|
||||
dx = x - x_min
|
||||
elseif x > x_max then
|
||||
dx = x - x_max
|
||||
end
|
||||
if y < y_min then
|
||||
dy = y - y_min
|
||||
elseif y > y_max then
|
||||
dy = y - y_max
|
||||
end
|
||||
|
||||
-- transform displacement to movement in world coordinates
|
||||
local c,s = cos(-self.rot), sin(-self.rot)
|
||||
dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale
|
||||
|
||||
-- move
|
||||
self:move((smoother or self.smoother)(dx,dy,...))
|
||||
end
|
||||
|
||||
-- the module
|
||||
return setmetatable({new = new, smooth = camera.smooth},
|
||||
{__call = function(_, ...) return new(...) end})
|
||||
@@ -0,0 +1,98 @@
|
||||
--[[
|
||||
Copyright (c) 2010-2013 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 include_helper(to, from, seen)
|
||||
if from == nil then
|
||||
return to
|
||||
elseif type(from) ~= 'table' then
|
||||
return from
|
||||
elseif seen[from] then
|
||||
return seen[from]
|
||||
end
|
||||
|
||||
seen[from] = to
|
||||
for k,v in pairs(from) do
|
||||
k = include_helper({}, k, seen) -- keys might also be tables
|
||||
if to[k] == nil then
|
||||
to[k] = include_helper({}, v, seen)
|
||||
end
|
||||
end
|
||||
return to
|
||||
end
|
||||
|
||||
-- deeply copies `other' into `class'. keys in `other' that are already
|
||||
-- defined in `class' are omitted
|
||||
local function include(class, other)
|
||||
return include_helper(class, other, {})
|
||||
end
|
||||
|
||||
-- returns a deep copy of `other'
|
||||
local function clone(other)
|
||||
return setmetatable(include({}, other), getmetatable(other))
|
||||
end
|
||||
|
||||
local function new(class)
|
||||
-- mixins
|
||||
class = class or {} -- class can be nil
|
||||
local inc = class.__includes or {}
|
||||
if getmetatable(inc) then inc = {inc} end
|
||||
|
||||
for _, other in ipairs(inc) do
|
||||
if type(other) == "string" then
|
||||
other = _G[other]
|
||||
end
|
||||
include(class, other)
|
||||
end
|
||||
|
||||
-- class implementation
|
||||
class.__index = class
|
||||
class.init = class.init or class[1] or function() end
|
||||
class.include = class.include or include
|
||||
class.clone = class.clone or clone
|
||||
|
||||
-- constructor call
|
||||
return setmetatable(class, {__call = function(c, ...)
|
||||
local o = setmetatable({}, c)
|
||||
o:init(...)
|
||||
return o
|
||||
end})
|
||||
end
|
||||
|
||||
-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons).
|
||||
if class_commons ~= false and not common then
|
||||
common = {}
|
||||
function common.class(name, prototype, parent)
|
||||
return new{__includes = {prototype, parent}}
|
||||
end
|
||||
function common.instance(class, ...)
|
||||
return class(...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- the module
|
||||
return setmetatable({new = new, include = include, clone = clone},
|
||||
{__call = function(_,...) return new(...) end})
|
||||
@@ -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/hump.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hump.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/hump"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hump"
|
||||
@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."
|
||||
@@ -0,0 +1,218 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// DISCLAIMER: I just started learning d3, so this is certainly not good
|
||||
// idiomatic d3 code. But hey, it works (kinda).
|
||||
|
||||
var tweens = {
|
||||
'out': function(f) { return function(s) { return 1-f(1-s) } },
|
||||
'chain': function(f1, f2) { return function(s) { return ((s<.5) ? f1(2*s) : 1+f2(2*s-1)) * .5 } },
|
||||
'linear': function(s) { return s },
|
||||
'quad': function(s) { return s*s },
|
||||
'cubic': function(s) { return s*s*s },
|
||||
'quart': function(s) { return s*s*s*s },
|
||||
'quint': function(s) { return s*s*s*s*s },
|
||||
'sine': function(s) { return 1 - Math.cos(s*Math.PI/2) },
|
||||
'expo': function(s) { return Math.pow(2, 10*(s-1)) },
|
||||
'circ': function(s) { return 1 - Math.sqrt(Math.max(0,1-s*s)) },
|
||||
'back': function(s) { var b = 1.70158; return s*s*((b+1)*s - b) },
|
||||
'bounce': function(s) {
|
||||
return Math.min(
|
||||
7.5625 * Math.pow(s, 2),
|
||||
7.5625 * Math.pow((s - .545455), 2) + .75,
|
||||
7.5625 * Math.pow((s - .818182), 2) + .90375,
|
||||
7.5625 * Math.pow((s - .954546), 2) + .984375)
|
||||
},
|
||||
'elastic': function(s) {
|
||||
return -Math.sin(2/0.3 * Math.PI * (s-1) - Math.asin(1)) * Math.pow(2, 10*(s-1))
|
||||
},
|
||||
};
|
||||
var tweenfunc = tweens.linear;
|
||||
|
||||
|
||||
var width_graph = 320,
|
||||
width_anim_move = 110,
|
||||
width_anim_rotate = 110,
|
||||
width_anim_size = 110,
|
||||
height = 250;
|
||||
|
||||
// "UI"
|
||||
var graph_ui = d3.select("#tween-graph").append("div")
|
||||
.attr("id", "tween-graph-ui");
|
||||
// rest see below
|
||||
|
||||
// the graph
|
||||
var graph = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_graph).attr("height", height);
|
||||
|
||||
// background
|
||||
graph.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
var y_zero = height * .78, y_one = height * .22;
|
||||
graph.append("rect")
|
||||
.attr("y", y_one)
|
||||
.attr("width", "100%").attr("height", y_zero - y_one)
|
||||
.attr("style", "fill:steelblue;fill-opacity:.3;stroke-width:1;stroke:rgba(100,100,100,.7)");
|
||||
|
||||
// time arrow
|
||||
graph.append("defs")
|
||||
.append("marker")
|
||||
.attr("id", "triangle")
|
||||
.attr("viewBox", "0 0 10 10")
|
||||
.attr("refX", 1).attr("refY", 5)
|
||||
.attr("markerWidth", 4)
|
||||
.attr("markerHeight", 4)
|
||||
.attr("orient", "auto")
|
||||
.attr("style", "fill:rgba(0,0,0,.5)")
|
||||
.append("path").attr("d", "M 0 0 L 10 5 L 0 10 z");
|
||||
|
||||
graph.append("line")
|
||||
.attr("x1", width_graph/2-80)
|
||||
.attr("x2", width_graph/2+80)
|
||||
.attr("y1", y_zero + 40).attr("y2", y_zero + 40)
|
||||
.attr("style", "stroke-width:2;stroke:rgba(0,0,0,.5)")
|
||||
.attr("marker-end", "url(#triangle)");
|
||||
|
||||
graph.append("text")
|
||||
.text("Time")
|
||||
.attr("x", width_graph/2).attr("y", y_zero + 55)
|
||||
.attr("style", "text-anchor:middle;fill:rgba(0,0,0,.5);font-size:15px");
|
||||
|
||||
// the actual graph
|
||||
var curve = d3.svg.line()
|
||||
.x(function(x) { return x*width_graph; })
|
||||
.y(function(x) { return tweenfunc(x) * (y_one - y_zero) + y_zero; })
|
||||
|
||||
var graph_curve = graph.append("path").attr("d", curve(d3.range(0,1.05,.005)))
|
||||
.attr("style", "fill:none;stroke-width:2;stroke:seagreen;");
|
||||
|
||||
var graph_marker = graph.append("circle")
|
||||
.attr("r", 5)
|
||||
.attr("style", "stroke:goldenrod;fill:none;stroke-width:3");
|
||||
|
||||
// finally, a label
|
||||
var graph_label = graph.append("text")
|
||||
.text("linear")
|
||||
.attr("x", width_graph/2).attr("y", 20)
|
||||
.attr("style", "text-anchor:middle;font-weight:bold;font-size:15px;");
|
||||
|
||||
|
||||
// animation examples - moving ball
|
||||
var anim_move = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_anim_move).attr("height", height);
|
||||
|
||||
anim_move.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
anim_move.append("rect")
|
||||
.attr("width", 10).attr("height", (y_zero - y_one))
|
||||
.attr("x", width_anim_move/2-5).attr("y", y_one)
|
||||
.attr("style", "fill:black;opacity:.1");
|
||||
|
||||
var anim_move_ball = anim_move.append("circle")
|
||||
.attr("cx", width_anim_move/2).attr("cy", y_one)
|
||||
.attr("r", 17)
|
||||
.attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;");
|
||||
|
||||
// animation examples - rotating square
|
||||
var anim_rotate = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_anim_size).attr("height", height);
|
||||
|
||||
anim_rotate.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
var w = width_anim_size/2;
|
||||
var anim_rotate_square = anim_rotate.append("rect")
|
||||
.attr("x", -w/2).attr("y", -w/4)
|
||||
.attr("width", w).attr("height", w/2)
|
||||
.attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;");
|
||||
|
||||
// animation examples - resizing ellipse
|
||||
var anim_size = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_anim_size).attr("height", height);
|
||||
|
||||
anim_size.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
anim_size.append("ellipse")
|
||||
.attr("cx", width_anim_size/2).attr("cy", height/2)
|
||||
.attr("rx", 40).attr("ry", 120)
|
||||
.attr("style", "fill:rgb(150,150,150);stroke:black;stroke-width:2;opacity:.1");
|
||||
|
||||
var anim_size_ellipse = anim_size.append("ellipse")
|
||||
.attr("cx", width_anim_size/2).attr("cy", height/2)
|
||||
.attr("rx", 40).attr("ry", 40)
|
||||
.attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;");
|
||||
|
||||
|
||||
// make it move!
|
||||
var t = 0;
|
||||
window.setInterval(function() {
|
||||
t = (t + .025 / 3);
|
||||
if (t > 1.3) { t = -.3; }
|
||||
var tt = Math.max(Math.min(t, 1), 0);
|
||||
|
||||
var s = tweenfunc(tt)
|
||||
var yy = s * (y_one - y_zero) + y_zero;
|
||||
var translate = "translate("+(width_anim_size/2)+" "+(height/2)+")";
|
||||
var rotate = "rotate(" + (s * 360) + ")";
|
||||
|
||||
graph_marker.attr("cx", tt*width_graph).attr("cy", yy);
|
||||
anim_move_ball.attr("cy", y_one + y_zero - yy);
|
||||
anim_rotate_square.attr("transform", translate + " " + rotate);
|
||||
anim_size_ellipse.attr("ry", s * 80 + 40);
|
||||
}, 25);
|
||||
|
||||
|
||||
// ui continued
|
||||
graph_ui.append("strong").text("Function: ");
|
||||
var select_modifier = graph_ui.append("select");
|
||||
select_modifier.append("option").text("in");
|
||||
select_modifier.append("option").text("out");
|
||||
select_modifier.append("option").text("in-out");
|
||||
select_modifier.append("option").text("out-in");
|
||||
graph_ui.append("strong").text("-")
|
||||
|
||||
var select_func = graph_ui.append("select")
|
||||
var funcs = [];
|
||||
for (var k in tweens)
|
||||
{
|
||||
if (k != "out" && k != "chain")
|
||||
{
|
||||
select_func.append("option").text(k);
|
||||
funcs.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
var change_tweenfunc = function()
|
||||
{
|
||||
var fname = funcs[select_func.node().selectedIndex];
|
||||
var mod = select_modifier.node().selectedIndex;
|
||||
|
||||
tweenfunc = tweens[fname];
|
||||
if (mod == 1) // out
|
||||
tweenfunc = tweens.out(tweenfunc);
|
||||
else if (mod == 2) // in-out
|
||||
tweenfunc = tweens.chain(tweenfunc, tweens.out(tweenfunc));
|
||||
else if (mod == 3) // out-in
|
||||
tweenfunc = tweens.chain(tweens.out(tweenfunc), tweenfunc);
|
||||
|
||||
// update curve
|
||||
graph_curve.attr("d", curve(d3.range(0,1.05,.005)))
|
||||
|
||||
// update label
|
||||
if (mod != 0)
|
||||
graph_label.text((['in','out','in-out','out-in'])[mod] + "-" + fname);
|
||||
else
|
||||
graph_label.text(fname);
|
||||
}
|
||||
|
||||
select_func.on("change", change_tweenfunc);
|
||||
select_modifier.on("change", change_tweenfunc);
|
||||
|
||||
})();
|
||||
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,537 @@
|
||||
hump.camera
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
Camera = require "hump.camera"
|
||||
|
||||
A camera utility for LÖVE. A camera can "look" at a position. It can zoom in
|
||||
and out and it can rotate it's view. In the background, this is done by
|
||||
actually moving, scaling and rotating everything in the game world. But don't
|
||||
worry about that.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.load()
|
||||
camera = Camera(player.pos.x, player.pos.y)
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
local dx,dy = player.x - cam.x, player.y - cam.y
|
||||
camera:move(dx/2, dy/2)
|
||||
end
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Camera.new(x,y, zoom, rot)
|
||||
|
||||
:param numbers x,y: Point for the camera to look at. (optional)
|
||||
:param number zoom: Camera zoom. (optional)
|
||||
:param number rot: Camera rotation in radians. (optional)
|
||||
:returns: A new camera.
|
||||
|
||||
|
||||
Creates a new camera. You can access the camera position using ``camera.x,
|
||||
camera.y``, the zoom using ``camera.scale`` and the rotation using ``camera.rot``.
|
||||
|
||||
The module variable name can be used at a shortcut to ``new()``.
|
||||
|
||||
**Example**::
|
||||
|
||||
camera = require 'hump.camera'
|
||||
-- camera looking at (100,100) with zoom 2 and rotated by 45 degrees
|
||||
cam = camera(100,100, 2, math.pi/2)
|
||||
|
||||
|
||||
.. function:: camera:move(dx,dy)
|
||||
|
||||
:param numbers dx,dy: Direction to move the camera.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Move the camera *by* some vector. To set the position, use
|
||||
:func:`camera:lookAt`.
|
||||
|
||||
This function is shortcut to ``camera.x,camera.y = camera.x+dx, camera.y+dy``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
camera:move(dt * 5, dt * 6)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.update(dt)
|
||||
camera:move(dt * 5, dt * 6):rotate(dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:lookAt(x,y)
|
||||
|
||||
:param numbers x,y: Position to look at.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Let the camera look at a point. In other words, it sets the camera position. To
|
||||
move the camera *by* some amount, use :func:`camera:move`.
|
||||
|
||||
This function is shortcut to ``camera.x,camera.y = x, y``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
camera:lookAt(player.pos:unpack())
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.update(dt)
|
||||
camera:lookAt(player.pos:unpack()):rotate(player.rot)
|
||||
end
|
||||
|
||||
.. function:: camera:position()
|
||||
|
||||
:returns: ``x,y`` -- Camera position.
|
||||
|
||||
|
||||
Returns ``camera.x, camera.y``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- let the camera fly!
|
||||
local cam_dx, cam_dy = 0, 0
|
||||
|
||||
function love.mousereleased(x,y)
|
||||
local cx,cy = camera:position()
|
||||
dx, dy = x-cx, y-cy
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
camera:move(dx * dt, dy * dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:rotate(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Rotate the camera by some angle. To set the angle use :func:`camera:rotateTo`.
|
||||
|
||||
This function is shortcut to ``camera.rot = camera.rot + angle``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
camera:rotate(dt)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.update(dt)
|
||||
camera:rotate(dt):move(dt,dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:rotateTo(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians
|
||||
:returns: The camera.
|
||||
|
||||
Set rotation: ``camera.rot = angle``.
|
||||
|
||||
**Example**::
|
||||
|
||||
camera:rotateTo(math.pi/2)
|
||||
|
||||
|
||||
.. function:: camera:zoom(mul)
|
||||
|
||||
:param number mul: Zoom change. Should be > 0.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
*Multiply* zoom: ``camera.scale = camera.scale * mul``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
camera:zoom(2) -- make everything twice as big
|
||||
|
||||
::
|
||||
|
||||
camera:zoom(0.5) -- ... and back to normal
|
||||
|
||||
::
|
||||
|
||||
camera:zoom(-1) -- mirror and flip everything upside down
|
||||
|
||||
|
||||
.. function:: camera:zoomTo(zoom)
|
||||
|
||||
:param number zoom: New zoom.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Set zoom: ``camera.scale = zoom``.
|
||||
|
||||
**Example**::
|
||||
|
||||
camera:zoomTo(1) -- reset zoom
|
||||
|
||||
|
||||
.. function:: camera:attach()
|
||||
|
||||
Start looking through the camera.
|
||||
|
||||
Apply camera transformations, i.e. move, scale and rotate everything until
|
||||
``camera:detach()`` as if looking through the camera.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
camera:attach()
|
||||
draw_world()
|
||||
camera:detach()
|
||||
|
||||
draw_hud()
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:detach()
|
||||
|
||||
Stop looking through the camera.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
camera:attach()
|
||||
draw_world()
|
||||
camera:detach()
|
||||
|
||||
draw_hud()
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:draw(func)
|
||||
|
||||
:param function func: Drawing function to be wrapped.
|
||||
|
||||
Wrap a function between a ``camera:attach()``/``camera:detach()`` pair.
|
||||
Equivalent to::
|
||||
|
||||
camera:attach()
|
||||
func()
|
||||
camera:detach()
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
camera:draw(draw_world)
|
||||
draw_hud()
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:worldCoords(x, y)
|
||||
|
||||
:param numbers x, y: Point to transform.
|
||||
:returns: ``x,y`` -- Transformed point.
|
||||
|
||||
Because a camera has a point it looks at, a rotation and a zoom factor, it
|
||||
defines a coordinate system. A point now has two sets of coordinates: One
|
||||
defines where the point is to be found in the game world, and the other
|
||||
describes the position on the computer screen. The first set of coordinates is
|
||||
called world coordinates, the second one camera coordinates. Sometimes it is
|
||||
needed to convert between the two coordinate systems, for example to get the
|
||||
position of a mouse click in the game world in a strategy game, or to see if an
|
||||
object is visible on the screen.
|
||||
|
||||
:func:`camera:worldCoords` and :func:`camera:cameraCoords` transform points
|
||||
between these two coordinate systems.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = camera:worldCoords(love.mouse.getPosition())
|
||||
selectedUnit:plotPath(x,y)
|
||||
|
||||
|
||||
.. function:: camera:cameraCoords(x, y)
|
||||
|
||||
:param numbers x, y: Point to transform.
|
||||
:returns: ``x,y`` -- Transformed point.
|
||||
|
||||
|
||||
Because a camera has a point it looks at, a rotation and a zoom factor, it
|
||||
defines a coordinate system. A point now has two sets of coordinates: One
|
||||
defines where the point is to be found in the game world, and the other
|
||||
describes the position on the computer screen. The first set of coordinates is
|
||||
called world coordinates, the second one camera coordinates. Sometimes it is
|
||||
needed to convert between the two coordinate systems, for example to get the
|
||||
position of a mouse click in the game world in a strategy game, or to see if an
|
||||
object is visible on the screen.
|
||||
|
||||
:func:`camera:worldCoords` and :func:`camera:cameraCoords` transform points
|
||||
between these two coordinate systems.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = camera:cameraCoords(player.pos.x, player.pos.y)
|
||||
love.graphics.line(x, y, love.mouse.getPosition())
|
||||
|
||||
|
||||
.. function:: camera:mousePosition()
|
||||
|
||||
:returns: Mouse position in world coordinates.
|
||||
|
||||
|
||||
Shortcut to ``camera:worldCoords(love.mouse.getPosition())``.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = camera:mousePosition()
|
||||
selectedUnit:plotPath(x,y)
|
||||
|
||||
|
||||
Camera Movement Control
|
||||
-----------------------
|
||||
|
||||
Camera movement is one of these things that go almost unnoticed when done well,
|
||||
but add a lot to the overall experience.
|
||||
The article `Scroll Back: The Theory and Practice of Cameras in SideScrollers
|
||||
<http://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php>`_
|
||||
by Itay Keren gives a lot of insight into how to design good camera systems.
|
||||
|
||||
**hump.camera** offers functions that help to implement most of the techniques
|
||||
discussed in the article. The functions :func:`camera:lockX`,
|
||||
:func:`camera:lockY`, :func:`camera:lockPosition`, and :func:`camera:lockWindow`
|
||||
move the camera so that the interesting content stays in frame.
|
||||
Note that the functions must be called every frame::
|
||||
|
||||
function love.update()
|
||||
-- vertical locking
|
||||
camera:lockX(player.pos.x)
|
||||
end
|
||||
|
||||
|
||||
All movements are subject to smoothing (see :ref:`Movement Smoothers
|
||||
<movement-smoothers>`).
|
||||
You can specify a default movement smoother by assigning the variable
|
||||
:attr:`camera.smoother`::
|
||||
|
||||
cam.smoother = Camera.smooth.linear(100)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockX(x, smoother, ...)
|
||||
|
||||
:param number x: X coordinate (in world coordinates) to lock to.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
Horizontal camera locking: Keep the camera locked on the defined ``x``-position
|
||||
(in *world coordinates*). The ``y``-position is not affected.
|
||||
|
||||
You can define an off-center locking position by "aiming" the camera left or
|
||||
right of your actual target. For example, to center the player 20 pixels to the
|
||||
*left* of the screen, aim 20 pixels to it's *right* (see examples).
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player vertically
|
||||
camera:lockX(player.x)
|
||||
|
||||
::
|
||||
|
||||
-- ... with linear smoothing at 25 px/s
|
||||
camera:lockX(player.x, Camera.smooth.linear(25))
|
||||
|
||||
::
|
||||
|
||||
-- lock player 20px left of center
|
||||
camera:lockX(player.x + 20)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockY(y, smoother, ...)
|
||||
|
||||
:param number y: Y coordinate (in world coordinates) to lock to.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
Vertical camera locking: Keep the camera locked on the defined ``y``-position
|
||||
(in *world coordinates*). The ``x``-position is not affected.
|
||||
|
||||
You can define an off-center locking position by "aiming" the camera above or
|
||||
below your actual target. For example, to center the player 20 pixels *below* the
|
||||
screen center, aim 20 pixels *above* it (see examples).
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player horizontally
|
||||
camera:lockY(player.y)
|
||||
|
||||
::
|
||||
|
||||
-- ... with damped smoothing with a stiffness of 10
|
||||
camera:lockY(player.y, Camera.smooth.damped(10))
|
||||
|
||||
::
|
||||
|
||||
-- lock player 20px below the screen center
|
||||
camera:lockY(player.y - 20)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockPosition(x,y, smoother, ...)
|
||||
|
||||
:param numbers x,y: Position (in world coordinates) to lock to.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
Horizontal and vertical camera locking: Keep the camera locked on the defined
|
||||
position (in *world coordinates*).
|
||||
|
||||
You can define an off-center locking position by "aiming" the camera to the
|
||||
opposite direction away from your real target.
|
||||
For example, to center the player 10 pixels to the *left* and 20 pixels *above*
|
||||
the screen center, aim 10 pixels to the *right* and 20 pixels *below*.
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player
|
||||
camera:lockPosition(player.x, player.y)
|
||||
|
||||
::
|
||||
|
||||
-- lock 50 pixels into player's aiming direction
|
||||
camera:lockPosition(player.x - player.aiming.x * 50, player.y - player.aiming.y * 50)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockWindow(x,y, x_min, x_max, y_min, y_max, smoother, ...)
|
||||
|
||||
:param numbers x,y: Position (in world coordinates) to lock to.
|
||||
:param numbers x_min: Upper left X coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param numbers x_max: Lower right X coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param numbers y_min: Upper left Y coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param numbers y_max: Lower right Y coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
The most powerful locking method: Lock camera to ``x,y``, but only move the
|
||||
camera if the position would be out of the screen-rectangle defined by ``x_min``,
|
||||
``x_max``, ``y_min``, ``y_max``.
|
||||
|
||||
.. note::
|
||||
The locking window is defined in camera coordinates, whereas the position to
|
||||
lock to is defined in world coordinates!
|
||||
|
||||
All of the other locking methods can be implemented by window locking. For
|
||||
position locking, set ``x_min = x_max`` and ``y_min = y_max``.
|
||||
Off-center locking can be done by defining the locking window accordingly.
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player
|
||||
camera:lock(player.x, player.y)
|
||||
|
||||
.. attribute:: camera.smoother
|
||||
|
||||
The default smoothing operator. Must be a ``function`` with the following
|
||||
prototype::
|
||||
|
||||
function customSmoother(dx,dy, ...)
|
||||
do_stuff()
|
||||
return new_dx,new_dy
|
||||
end
|
||||
|
||||
where ``dx,dy`` is the offset the camera would move before smoothing and
|
||||
``new_dx, new_dy`` is the offset the camera should move after smoothing.
|
||||
|
||||
|
||||
.. _movement-smoothers:
|
||||
|
||||
Movement Smoothers
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is not always desirable that the camera instantly locks on a target.
|
||||
`Platform snapping
|
||||
<http://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php#h.rncuomopycy0>`_,
|
||||
for example, would look terrible if the camera would instantly jump to the
|
||||
focussed platform.
|
||||
Smoothly moving the camera to the locked position can also give the illusion of
|
||||
a camera operator an add to the overall feel of your game.
|
||||
|
||||
**hump.camera** allows to smooth the movement by either passing movement
|
||||
smoother functions to the locking functions or by setting a default smoother
|
||||
(see :attr:`camera.smoother`).
|
||||
|
||||
Smoothing functions must have the following prototype::
|
||||
|
||||
function customSmoother(dx,dy, ...)
|
||||
do_stuff()
|
||||
return new_dx,new_dy
|
||||
end
|
||||
|
||||
where ``dx,dy`` is the offset the camera would move before smoothing and
|
||||
``new_dx, new_dy`` is the offset the camera should move after smoothing.
|
||||
|
||||
This is a simple "rubber-band" smoother::
|
||||
|
||||
function rubber_band(dx,dy)
|
||||
local dt = love.timer.getDelta()
|
||||
return dx*dt, dy*dt
|
||||
end
|
||||
|
||||
**hump.camera** defines generators for the most common smoothers:
|
||||
|
||||
.. function:: Camera.smooth.none()
|
||||
|
||||
:returns: Smoothing function.
|
||||
|
||||
Dummy smoother: does not smooth the motion.
|
||||
|
||||
**Example**::
|
||||
|
||||
cam.smoother = Camera.smooth.none()
|
||||
|
||||
|
||||
.. function:: Camera.smooth.linear(speed)
|
||||
|
||||
:param number speed: Smoothing speed.
|
||||
:returns: Smoothing function.
|
||||
|
||||
Smoothly moves the camera towards to snapping goal with constant speed.
|
||||
|
||||
**Examples**::
|
||||
|
||||
cam.smoother = Camera.smooth.linear(100)
|
||||
|
||||
::
|
||||
|
||||
-- warning: creates a function every frame!
|
||||
camera:lockX(player.x, Camera.smooth.linear(25))
|
||||
|
||||
|
||||
.. function:: Camera.smooth.damped(stiffness)
|
||||
|
||||
:param number stiffness: Speed of the camera movement.
|
||||
:returns: Smoothing function.
|
||||
|
||||
Smoothly moves the camera towards the goal with a speed proportional to the
|
||||
distance to the target.
|
||||
Stiffness defines the speed of the motion: Higher values mean that the camera
|
||||
moves more quickly.
|
||||
|
||||
**Examples**::
|
||||
|
||||
cam.smoother = Camera.smooth.damped(10)
|
||||
|
||||
::
|
||||
|
||||
-- warning: creates a function every frame!
|
||||
camera:lockPosition(player.x, player.y, Camera.smooth.damped(2))
|
||||
@@ -0,0 +1,345 @@
|
||||
hump.class
|
||||
==========
|
||||
|
||||
::
|
||||
|
||||
Class = require "hump.class"
|
||||
|
||||
A small, fast class/prototype implementation with multiple inheritance.
|
||||
|
||||
Implements `class commons <https://github.com/bartbes/Class-Commons>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
Critter = Class{
|
||||
init = function(self, pos, img)
|
||||
self.pos = pos
|
||||
self.img = img
|
||||
end,
|
||||
speed = 5
|
||||
}
|
||||
|
||||
function Critter:update(dt, player)
|
||||
-- see hump.vector
|
||||
local dir = (player.pos - self.pos):normalize_inplace()
|
||||
self.pos = self.pos + dir * Critter.speed * dt
|
||||
end
|
||||
|
||||
function Critter:draw()
|
||||
love.graphics.draw(self.img, self.pos.x, self.pos.y)
|
||||
end
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Class.new()
|
||||
.. function:: Class.new({init = constructor, __includes = parents, ...})
|
||||
|
||||
:param function constructor: Class constructor. Can be accessed with ``theclass.init(object, ...)``. (optional)
|
||||
:param class or table of classes parents: Classes to inherit from. Can either be a single class or a table of classes. (optional)
|
||||
:param mixed ...: Any other fields or methods common to all instances of this class. (optional)
|
||||
:returns: The class.
|
||||
|
||||
|
||||
Declare a new class.
|
||||
|
||||
``init()`` will receive the new object instance as first argument. Any other
|
||||
arguments will also be forwarded (see examples), i.e. ``init()`` has the
|
||||
following signature::
|
||||
|
||||
function init(self, ...)
|
||||
|
||||
If you do not specify a constructor, an empty constructor will be used instead.
|
||||
|
||||
The name of the variable that holds the module can be used as a shortcut to
|
||||
``new()`` (see example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
Class = require 'hump.class' -- `Class' is now a shortcut to new()
|
||||
|
||||
-- define a class class
|
||||
Feline = Class{
|
||||
init = function(self, size, weight)
|
||||
self.size = size
|
||||
self.weight = weight
|
||||
end;
|
||||
-- define a method
|
||||
stats = function(self)
|
||||
return string.format("size: %.02f, weight: %.02f", self.size, self.weight)
|
||||
end;
|
||||
}
|
||||
|
||||
-- create two objects
|
||||
garfield = Feline(.7, 45)
|
||||
felix = Feline(.8, 12)
|
||||
|
||||
print("Garfield: " .. garfield:stats(), "Felix: " .. felix:stats())
|
||||
|
||||
::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
-- same as above, but with 'external' function definitions
|
||||
Feline = Class{}
|
||||
|
||||
function Feline:init(size, weight)
|
||||
self.size = size
|
||||
self.weight = weight
|
||||
end
|
||||
|
||||
function Feline:stats()
|
||||
return string.format("size: %.02f, weight: %.02f", self.size, self.weight)
|
||||
end
|
||||
|
||||
garfield = Feline(.7, 45)
|
||||
print(Feline, garfield)
|
||||
|
||||
::
|
||||
|
||||
Class = require 'hump.class'
|
||||
A = Class{
|
||||
foo = function() print('foo') end
|
||||
}
|
||||
|
||||
B = Class{
|
||||
bar = function() print('bar') end
|
||||
}
|
||||
|
||||
-- single inheritance
|
||||
C = Class{__includes = A}
|
||||
instance = C()
|
||||
instance:foo() -- prints 'foo'
|
||||
instance:bar() -- error: function not defined
|
||||
|
||||
-- multiple inheritance
|
||||
D = Class{__includes = {A,B}}
|
||||
instance = D()
|
||||
instance:foo() -- prints 'foo'
|
||||
instance:bar() -- prints 'bar'
|
||||
|
||||
::
|
||||
|
||||
-- class attributes are shared across instances
|
||||
A = Class{ foo = 'foo' } -- foo is a class attribute/static member
|
||||
|
||||
one, two, three = A(), A(), A()
|
||||
print(one.foo, two.foo, three.foo) --> prints 'foo foo foo'
|
||||
|
||||
one.foo = 'bar' -- overwrite/specify for instance `one' only
|
||||
print(one.foo, two.foo, three.foo) --> prints 'bar foo foo'
|
||||
|
||||
A.foo = 'baz' -- overwrite for all instances without specification
|
||||
print(one.foo, two.foo, three.foo) --> prints 'bar baz baz'
|
||||
|
||||
|
||||
.. function:: class.init(object, ...)
|
||||
|
||||
:param Object object: The object. Usually ``self``.
|
||||
:param mixed ...: Arguments to pass to the constructor.
|
||||
:returns: Whatever the parent class constructor returns.
|
||||
|
||||
|
||||
Calls class constructor of a class on an object.
|
||||
|
||||
Derived classes should use this function their constructors to initialize the
|
||||
parent class(es) portions of the object.
|
||||
|
||||
**Example**::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
Shape = Class{
|
||||
init = function(self, area)
|
||||
self.area = area
|
||||
end;
|
||||
__tostring = function(self)
|
||||
return "area = " .. self.area
|
||||
end
|
||||
}
|
||||
|
||||
Rectangle = Class{__includes = Shape,
|
||||
init = function(self, width, height)
|
||||
Shape.init(self, width * height)
|
||||
self.width = width
|
||||
self.height = height
|
||||
end;
|
||||
__tostring = function(self)
|
||||
local strs = {
|
||||
"width = " .. self.width,
|
||||
"height = " .. self.height,
|
||||
Shape.__tostring(self)
|
||||
}
|
||||
return table.concat(strs, ", ")
|
||||
end
|
||||
}
|
||||
|
||||
print( Rectangle(2,4) ) -- prints 'width = 2, height = 4, area = 8'
|
||||
|
||||
|
||||
.. function:: Class:include(other)
|
||||
|
||||
:param tables other: Parent classes/mixins.
|
||||
:returns: The class.
|
||||
|
||||
|
||||
Inherit functions and variables of another class, but only if they are not
|
||||
already defined. This is done by (deeply) copying the functions and variables
|
||||
over to the subclass.
|
||||
|
||||
.. note::
|
||||
``class:include()`` doesn't actually care if the arguments supplied are
|
||||
hump classes. Just any table will work.
|
||||
|
||||
.. note::
|
||||
You can use ``Class.include(a, b)`` to copy any fields from table ``a``
|
||||
to table ``b`` (see second example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
Entity = Class{
|
||||
init = function(self)
|
||||
GameObjects.register(self)
|
||||
end
|
||||
}
|
||||
|
||||
Collidable = {
|
||||
dispatch_collision = function(self, other, dx, dy)
|
||||
if self.collision_handler[other.type])
|
||||
return collision_handler[other.type](self, other, dx, dy)
|
||||
end
|
||||
return collision_handler["*"](self, other, dx, dy)
|
||||
end,
|
||||
|
||||
collision_handler = {["*"] = function() end},
|
||||
}
|
||||
|
||||
Spaceship = Class{
|
||||
init = function(self)
|
||||
self.type = "Spaceship"
|
||||
-- ...
|
||||
end
|
||||
}
|
||||
|
||||
-- make Spaceship collidable
|
||||
Spaceship:include(Collidable)
|
||||
|
||||
Spaceship.collision_handler["Spaceship"] = function(self, other, dx, dy)
|
||||
-- ...
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
-- using Class.include()
|
||||
Class = require 'hump.class'
|
||||
a = {
|
||||
foo = 'bar',
|
||||
bar = {one = 1, two = 2, three = 3},
|
||||
baz = function() print('baz') end,
|
||||
}
|
||||
b = {
|
||||
foo = 'nothing to see here...'
|
||||
}
|
||||
|
||||
Class.include(b, a) -- copy values from a to b
|
||||
-- note that neither a nor b are hump classes!
|
||||
|
||||
print(a.foo, b.foo) -- prints 'bar nothing to see here...'
|
||||
|
||||
b.baz() -- prints 'baz'
|
||||
|
||||
b.bar.one = 10 -- changes only values in b
|
||||
print(a.bar.one, b.bar.one) -- prints '1 10'
|
||||
|
||||
|
||||
.. function:: class:clone()
|
||||
|
||||
:returns: A deep copy of the class/table.
|
||||
|
||||
|
||||
Create a clone/deep copy of the class.
|
||||
|
||||
.. note::
|
||||
You can use ``Class.clone(a)`` to create a deep copy of any table (see
|
||||
second example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
point = Class{ x = 0, y = 0 }
|
||||
|
||||
a = point:clone()
|
||||
a.x, a.y = 10, 10
|
||||
print(a.x, a.y) --> prints '10 10'
|
||||
|
||||
b = point:clone()
|
||||
print(b.x, b.y) --> prints '0 0'
|
||||
|
||||
c = a:clone()
|
||||
print(c.x, c.y) --> prints '10 10'
|
||||
|
||||
::
|
||||
|
||||
-- using Class.clone() to copy tables
|
||||
Class = require 'hump.class'
|
||||
a = {
|
||||
foo = 'bar',
|
||||
bar = {one = 1, two = 2, three = 3},
|
||||
baz = function() print('baz') end,
|
||||
}
|
||||
b = Class.clone(a)
|
||||
|
||||
b.baz() -- prints 'baz'
|
||||
b.bar.one = 10
|
||||
print(a.bar.one, b.bar.one) -- prints '1 10'
|
||||
|
||||
|
||||
|
||||
Caveats
|
||||
-------
|
||||
|
||||
Be careful when using metamethods like ``__add`` or ``__mul``: If a subclass
|
||||
inherits those methods from a superclass, but does not overwrite them, the
|
||||
result of the operation may be of the type superclass. Consider the following::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
A = Class{init = function(self, x) self.x = x end}
|
||||
function A:__add(other) return A(self.x + other.x) end
|
||||
function A:show() print("A:", self.x) end
|
||||
|
||||
B = Class{init = function(self, x, y) A.init(self, x) self.y = y end}
|
||||
function B:show() print("B:", self.x, self.y) end
|
||||
function B:foo() print("foo") end
|
||||
B:include(A)
|
||||
|
||||
one, two = B(1,2), B(3,4)
|
||||
result = one + two -- result will be of type A, *not* B!
|
||||
result:show() -- prints "A: 4"
|
||||
result:foo() -- error: method does not exist
|
||||
|
||||
Note that while you can define the ``__index`` metamethod of the class, this is
|
||||
not a good idea: It will break the class mechanism. To add a custom ``__index``
|
||||
metamethod without breaking the class system, you have to use ``rawget()``. But
|
||||
beware that this won't affect subclasses::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
A = Class{}
|
||||
function A:foo() print('bar') end
|
||||
|
||||
function A:__index(key)
|
||||
print(key)
|
||||
return rawget(A, key)
|
||||
end
|
||||
|
||||
instance = A()
|
||||
instance:foo() -- prints foo bar
|
||||
|
||||
B = Class{__includes = A}
|
||||
instance = B()
|
||||
instance:foo() -- prints only foo
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# hump documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Oct 10 13:10:12 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'hump'
|
||||
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 = '1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
|
||||
# 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.
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# 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.
|
||||
#html_theme_path = []
|
||||
|
||||
# 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 = 'humpdoc'
|
||||
|
||||
# -- 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, 'hump.tex', u'hump 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, 'hump', u'hump 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, 'hump', u'hump Documentation',
|
||||
author, 'hump', '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()]
|
||||
@@ -0,0 +1,350 @@
|
||||
hump.gamestate
|
||||
==============
|
||||
|
||||
::
|
||||
|
||||
Gamestate = require "hump.gamestate"
|
||||
|
||||
A gamestate encapsulates independent data an behaviour in a single table.
|
||||
|
||||
A typical game could consist of a menu-state, a level-state and a game-over-state.
|
||||
|
||||
**Example**::
|
||||
|
||||
local menu = {} -- previously: Gamestate.new()
|
||||
local game = {}
|
||||
|
||||
function menu:draw()
|
||||
love.graphics.print("Press Enter to continue", 10, 10)
|
||||
end
|
||||
|
||||
function menu:keyreleased(key, code)
|
||||
if key == 'return' then
|
||||
Gamestate.switch(game)
|
||||
end
|
||||
end
|
||||
|
||||
function game:enter()
|
||||
Entities.clear()
|
||||
-- setup entities here
|
||||
end
|
||||
|
||||
function game:update(dt)
|
||||
Entities.update(dt)
|
||||
end
|
||||
|
||||
function game:draw()
|
||||
Entities.draw()
|
||||
end
|
||||
|
||||
function love.load()
|
||||
Gamestate.registerEvents()
|
||||
Gamestate.switch(menu)
|
||||
end
|
||||
|
||||
|
||||
.. _callbacks:
|
||||
|
||||
Gamestate Callbacks
|
||||
-------------------
|
||||
|
||||
A gamestate can define all callbacks that LÖVE defines. In addition, there are
|
||||
callbacks for initalizing, entering and leaving a state:
|
||||
|
||||
``init()``
|
||||
Called once, and only once, before entering the state the first time. See
|
||||
:func:`Gamestate.switch`.
|
||||
|
||||
``enter(previous, ...)``
|
||||
Called every time when entering the state. See :func:`Gamestate.switch`.
|
||||
|
||||
``leave()``
|
||||
Called when leaving a state. See :func:`Gamestate.switch` and :func:`Gamestate.pop`.
|
||||
|
||||
``resume()``
|
||||
Called when re-entering a state by :func:`Gamestate.pop`-ing another state.
|
||||
|
||||
``update()``
|
||||
Update the game state. Called every frame.
|
||||
|
||||
``draw()``
|
||||
Draw on the screen. Called every frame.
|
||||
|
||||
``focus()``
|
||||
Called if the window gets or looses focus.
|
||||
|
||||
``keypressed()``
|
||||
Triggered when a key is pressed.
|
||||
|
||||
``keyreleased()``
|
||||
Triggered when a key is released.
|
||||
|
||||
``mousepressed()``
|
||||
Triggered when a mouse button is pressed.
|
||||
|
||||
``mousereleased()``
|
||||
Triggered when a mouse button is released.
|
||||
|
||||
``joystickpressed()``
|
||||
Triggered when a joystick button is pressed.
|
||||
|
||||
``joystickreleased()``
|
||||
Triggered when a joystick button is released.
|
||||
|
||||
``quit()``
|
||||
Called on quitting the game. Only called on the active gamestate.
|
||||
|
||||
When using :func:`Gamestate.registerEvents`, all these callbacks will be called by the
|
||||
corresponding LÖVE callbacks and receive receive the same arguments (e.g.
|
||||
``state:update(dt)`` will be called by ``love.update(dt)``).
|
||||
|
||||
**Example**::
|
||||
|
||||
menu = {} -- previously: Gamestate.new()
|
||||
|
||||
function menu:init()
|
||||
self.background = love.graphics.newImage('bg.jpg')
|
||||
Buttons.initialize()
|
||||
end
|
||||
|
||||
function menu:enter(previous) -- runs every time the state is entered
|
||||
Buttons.setActive(Buttons.start)
|
||||
end
|
||||
|
||||
function menu:update(dt) -- runs every frame
|
||||
Buttons.update(dt)
|
||||
end
|
||||
|
||||
function menu:draw()
|
||||
love.graphics.draw(self.background, 0, 0)
|
||||
Buttons.draw()
|
||||
end
|
||||
|
||||
function menu:keyreleased(key)
|
||||
if key == 'up' then
|
||||
Buttons.selectPrevious()
|
||||
elseif key == 'down' then
|
||||
Buttons.selectNext()
|
||||
elseif
|
||||
Buttons.active:onClick()
|
||||
end
|
||||
end
|
||||
|
||||
function menu:mousereleased(x,y, mouse_btn)
|
||||
local button = Buttons.hovered(x,y)
|
||||
if button then
|
||||
Button.select(button)
|
||||
if mouse_btn == 'l' then
|
||||
button:onClick()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Gamestate.new()
|
||||
|
||||
:returns: An empty table.
|
||||
|
||||
|
||||
**Deprecated: Use the table constructor instead (see example)**
|
||||
|
||||
Declare a new gamestate (just an empty table). A gamestate can define several
|
||||
callbacks.
|
||||
|
||||
**Example**::
|
||||
|
||||
menu = {}
|
||||
-- deprecated method:
|
||||
menu = Gamestate.new()
|
||||
|
||||
|
||||
.. function:: Gamestate.switch(to, ...)
|
||||
|
||||
:param Gamestate to: Target gamestate.
|
||||
:param mixed ...: Additional arguments to pass to ``to:enter(current, ...)``.
|
||||
:returns: The results of ``to:enter(current, ...)``.
|
||||
|
||||
|
||||
Switch to a gamestate, with any additional arguments passed to the new state.
|
||||
|
||||
Switching a gamestate will call the ``leave()`` callback on the current
|
||||
gamestate, replace the current gamestate with ``to``, call the ``init()`` function
|
||||
if, and only if, the state was not yet inialized and finally call
|
||||
``enter(old_state, ...)`` on the new gamestate.
|
||||
|
||||
.. note::
|
||||
Processing of callbacks is suspended until ``update()`` is called on the new
|
||||
gamestate, but the function calling :func:`Gamestate.switch` can still continue - it is
|
||||
your job to make sure this is handled correctly. See also the examples below.
|
||||
|
||||
|
||||
**Examples**::
|
||||
|
||||
Gamestate.switch(game, level_two)
|
||||
|
||||
::
|
||||
|
||||
-- stop execution of the current state by using return
|
||||
if player.has_died then
|
||||
return Gamestate.switch(game, level_two)
|
||||
end
|
||||
|
||||
-- this will not be called when the state is switched
|
||||
player:update()
|
||||
|
||||
|
||||
|
||||
.. function:: Gamestate.Gamestate.current()
|
||||
|
||||
:returns: The active gamestate.
|
||||
|
||||
Returns the currently activated gamestate.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.keypressed(key)
|
||||
if Gamestate.current() ~= menu and key == 'p' then
|
||||
Gamestate.push(pause)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.push(to, ...)
|
||||
|
||||
:param Gamestate to: Target gamestate.
|
||||
:param mixed ...: Additional arguments to pass to ``to:enter(current, ...)``.
|
||||
:returns: The results of ``to:enter(current, ...)``.
|
||||
|
||||
Pushes the ``to`` on top of the state stack, i.e. makes it the active state.
|
||||
Semantics are the same as ``switch(to, ...)``, except that ``leave()`` is *not*
|
||||
called on the previously active state.
|
||||
|
||||
Useful for pause screens, menus, etc.
|
||||
|
||||
.. note::
|
||||
Processing of callbacks is suspended until ``update()`` is called on the
|
||||
new gamestate, but the function calling ``GS.push()`` can still continue -
|
||||
it is your job to make sure this is handled correctly. See also the
|
||||
example below.
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
-- pause gamestate
|
||||
Pause = Gamestate.new()
|
||||
function Pause:enter(from)
|
||||
self.from = from -- record previous state
|
||||
end
|
||||
|
||||
function Pause:draw()
|
||||
local W, H = love.graphics.getWidth(), love.graphics.getHeight()
|
||||
-- draw previous screen
|
||||
self.from:draw()
|
||||
-- overlay with pause message
|
||||
love.graphics.setColor(0,0,0, 100)
|
||||
love.graphics.rectangle('fill', 0,0, W,H)
|
||||
love.graphics.setColor(255,255,255)
|
||||
love.graphics.printf('PAUSE', 0, H/2, W, 'center')
|
||||
end
|
||||
|
||||
-- [...]
|
||||
function love.keypressed(key)
|
||||
if Gamestate.current() ~= menu and key == 'p' then
|
||||
return Gamestate.push(pause)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.pop(...)
|
||||
|
||||
:returns: The results of ``new_state:resume(...)``.
|
||||
|
||||
Calls ``leave()`` on the current state and then removes it from the stack, making
|
||||
the state below the current state and calls ``resume(...)`` on the activated state.
|
||||
Does *not* call ``enter()`` on the activated state.
|
||||
|
||||
.. note::
|
||||
Processing of callbacks is suspended until ``update()`` is called on the
|
||||
new gamestate, but the function calling ``GS.pop()`` can still continue -
|
||||
it is your job to make sure this is handled correctly. See also the
|
||||
example below.
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
-- extending the example of Gamestate.push() above
|
||||
function Pause:keypressed(key)
|
||||
if key == 'p' then
|
||||
return Gamestate.pop() -- return to previous state
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.<callback>(...)
|
||||
|
||||
:param mixed ...: Arguments to pass to the corresponding function.
|
||||
:returns: The result of the callback function.
|
||||
|
||||
Calls a function on the current gamestate. Can be any function, but is intended
|
||||
to be one of the :ref:`callbacks`. Mostly useful when not using
|
||||
:func:`Gamestate.registerEvents`.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
Gamestate.draw() -- <callback> is `draw'
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Gamestate.update(dt) -- pass dt to currentState:update(dt)
|
||||
end
|
||||
|
||||
function love.keypressed(key, code)
|
||||
Gamestate.keypressed(key, code) -- pass multiple arguments
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.registerEvents([callbacks])
|
||||
|
||||
:param table callbacks: Names of the callbacks to register. If omitted,
|
||||
register all love callbacks (optional).
|
||||
|
||||
Overwrite love callbacks to call ``Gamestate.update()``, ``Gamestate.draw()``,
|
||||
etc. automatically. ``love`` callbacks (e.g. ``love.update()``) are still
|
||||
invoked as usual.
|
||||
|
||||
This is by done by overwriting the love callbacks, e.g.::
|
||||
|
||||
local old_update = love.update
|
||||
function love.update(dt)
|
||||
old_update(dt)
|
||||
return Gamestate.current:update(dt)
|
||||
end
|
||||
|
||||
.. note::
|
||||
Only works when called in love.load() or any other function that is
|
||||
executed *after* the whole file is loaded.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.load()
|
||||
Gamestate.registerEvents()
|
||||
Gamestate.switch(menu)
|
||||
end
|
||||
|
||||
-- love callback will still be invoked
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
-- no need for Gamestate.update(dt)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.load()
|
||||
-- only register draw, update and quit
|
||||
Gamestate.registerEvents{'draw', 'update', 'quit'}
|
||||
Gamestate.switch(menu)
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
**hump** - Helper Utilities for a Multitude of Problems
|
||||
=======================================================
|
||||
|
||||
**hump** is a set of lightweight helpers for the awesome `LÖVE
|
||||
<http://love2d.org>`_ game framework. It will help to get you over the initial
|
||||
hump when starting to build a new game.
|
||||
|
||||
**hump** differs from many other libraries in that every component is
|
||||
independent of the remaining ones.
|
||||
The footprint is very small, so the library should fit nicely into your
|
||||
projects.
|
||||
|
||||
Read on
|
||||
-------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
hump.gamestate <gamestate>
|
||||
hump.timer <timer>
|
||||
hump.vector <vector>
|
||||
hump.vector-light <vector-light>
|
||||
hump.class <class>
|
||||
hump.signal <signal>
|
||||
hump.camera <camera>
|
||||
license
|
||||
|
||||
|
||||
Get hump
|
||||
--------
|
||||
|
||||
You can view and download the individual modules on github: `vrld/hump
|
||||
<http://github.com/vrld/hump>`_.
|
||||
You may also download the whole packed sourcecode either in the `zip
|
||||
<http://github.com/vrld/hump/zipball/master>`_ or `tar
|
||||
<http://github.com/vrld/hump/tarball/master>`_ format.
|
||||
|
||||
Using `Git <http://git-scm.com>`_, you can clone the project by running:
|
||||
|
||||
git clone git://github.com/vrld/hump
|
||||
|
||||
Once done, tou can check for updates by running
|
||||
|
||||
git pull
|
||||
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
@@ -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.
|
||||
@@ -0,0 +1,185 @@
|
||||
hump.signal
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
Signal = require 'hump.signal'
|
||||
|
||||
A simple yet effective implementation of `Signals and Slots
|
||||
<http://en.wikipedia.org/wiki/Signals_and_slots>`_, aka the `Observer pattern
|
||||
<http://en.wikipedia.org/wiki/Observer_pattern>`_: Functions can be dynamically
|
||||
bound to signals. When a *signal* is *emitted*, all registered functions will
|
||||
be invoked. Simple as that.
|
||||
|
||||
``hump.signal`` makes things a little more interesing by allowing to emit all
|
||||
signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- in AI.lua
|
||||
Signal.register('shoot', function(x,y, dx,dy)
|
||||
-- for every critter in the path of the bullet:
|
||||
-- try to avoid being hit
|
||||
for critter in pairs(critters) do
|
||||
if critter:intersectsRay(x,y, dx,dy) then
|
||||
critter:setMoveDirection(-dy, dx)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- in sounds.lua
|
||||
Signal.register('shoot', function()
|
||||
Sounds.fire_bullet:play()
|
||||
end)
|
||||
|
||||
-- in main.lua
|
||||
function love.keypressed(key)
|
||||
if key == ' ' then
|
||||
local x,y = player.pos:unpack()
|
||||
local dx,dy = player.direction:unpack()
|
||||
Signal.emit('shoot', x,y, dx,dy)
|
||||
end
|
||||
end
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Signal.new()
|
||||
|
||||
:returns: A new signal registry.
|
||||
|
||||
Creates a new signal registry that is independent of the default registry: It
|
||||
will manage it's own list of signals and does not in any way affect the the
|
||||
global registry. Likewise, the global registry does not affect the instance.
|
||||
|
||||
.. note::
|
||||
If you don't need multiple independent registries, you can use the
|
||||
global/default registry (see examples).
|
||||
|
||||
.. note::
|
||||
Unlike the default one, signal registry instances use the colon-syntax,
|
||||
i.e., you need to call ``instance:emit('foo', 23)`` instead of
|
||||
``Signal.mit('foo', 23)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
player.signals = Signal.new()
|
||||
|
||||
|
||||
.. function:: Signal.register(s, f)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
:param function f: The function to register.
|
||||
:returns: A function handle to use in :func:`Signal.remove()`.
|
||||
|
||||
|
||||
Registers a function ``f`` to be called when signal ``s`` is emitted.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Signal.register('level-complete', function() self.fanfare:play() end)
|
||||
|
||||
::
|
||||
|
||||
handle = Signal.register('level-load', function(level) level.show_help() end)
|
||||
|
||||
::
|
||||
|
||||
menu:register('key-left', select_previous_item)
|
||||
|
||||
|
||||
.. function:: Signal.emit(s, ...)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
:param mixed ...: Arguments to pass to the bound functions. (optional)
|
||||
|
||||
|
||||
Calls all functions bound to signal ``s`` with the supplied arguments.
|
||||
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.keypressed(key)
|
||||
-- using a signal instance
|
||||
if key == 'left' then menu:emit('key-left') end
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
if level.is_finished() then
|
||||
-- adding arguments
|
||||
Signal.emit('level-load', level.next_level)
|
||||
end
|
||||
|
||||
|
||||
.. function:: Signal.remove(s, ...)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
:param functions ...: Functions to unbind from the signal.
|
||||
|
||||
|
||||
Unbinds (removes) functions from signal ``s``.
|
||||
|
||||
**Example**::
|
||||
|
||||
Signal.remove('level-load', handle)
|
||||
|
||||
|
||||
.. function:: Signal.clear(s)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
|
||||
|
||||
Removes all functions from signal ``s``.
|
||||
|
||||
**Example**::
|
||||
|
||||
Signal.clear('key-left')
|
||||
|
||||
|
||||
.. function:: Signal.emitPattern(p, ...)
|
||||
|
||||
:param string p: The signal identifier pattern.
|
||||
:param mixed ...: Arguments to pass to the bound functions. (optional)
|
||||
|
||||
|
||||
Emits all signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- emit all update signals
|
||||
Signal.emitPattern('^update%-.*', dt)
|
||||
|
||||
|
||||
.. function:: Signal.removePattern(p, ...)
|
||||
|
||||
:param string p: The signal identifier pattern.
|
||||
:param functions ...: Functions to unbind from the signals.
|
||||
|
||||
|
||||
Removes functions from all signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
Signal.removePattern('key%-.*', play_click_sound)
|
||||
|
||||
|
||||
.. function:: Signal.clearPattern(p)
|
||||
|
||||
:param string p: The signal identifier pattern.
|
||||
|
||||
|
||||
Removes **all** functions from all signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Signal.clearPattern('sound%-.*')
|
||||
|
||||
::
|
||||
|
||||
player.signals:clearPattern('.*') -- clear all signals
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
hump.timer
|
||||
==========
|
||||
|
||||
::
|
||||
|
||||
Timer = require "hump.timer"
|
||||
|
||||
hump.timer offers a simple interface to schedule the execution of functions. It
|
||||
is possible to run functions *after* and *for* some amount of time. For
|
||||
example, a timer could be set to move critters every 5 seconds or to make the
|
||||
player invincible for a short amount of time.
|
||||
|
||||
In addition to that, ``hump.timer`` offers various `tweening
|
||||
<http://en.wikipedia.org/wiki/Inbetweening>`_ functions that make it
|
||||
easier to produce `juicy games <http://www.youtube.com/watch?v=Fy0aCDmgnxg>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.keypressed(key)
|
||||
if key == ' ' then
|
||||
Timer.after(1, function() print("Hello, world!") end)
|
||||
end
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Timer.new()
|
||||
|
||||
:returns: A timer instance.
|
||||
|
||||
|
||||
Creates a new timer instance that is independent of the global timer: It will
|
||||
manage it's own list of scheduled functions and does not in any way affect the
|
||||
the global timer. Likewise, the global timer does not affect timer instances.
|
||||
|
||||
.. note::
|
||||
If you don't need multiple independent schedulers, you can use the
|
||||
global/default timer (see examples).
|
||||
|
||||
.. note::
|
||||
Unlike the default timer, timer instances use the colon-syntax, i.e.,
|
||||
you need to call ``instance:after(1, foo)`` instead of ``Timer.after(1,
|
||||
foo)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
menuTimer = Timer.new()
|
||||
|
||||
|
||||
.. function:: Timer.after(delay, func)
|
||||
|
||||
:param number delay: Number of seconds the function will be delayed.
|
||||
:param function func: The function to be delayed.
|
||||
:returns: The timer handle. See also :func:`Timer.cancel`.
|
||||
|
||||
|
||||
Schedule a function. The function will be executed after ``delay`` seconds have
|
||||
elapsed, given that ``update(dt)`` is called every frame.
|
||||
|
||||
.. note::
|
||||
There is no guarantee that the delay will not be exceeded, it is only
|
||||
guaranteed that the function will *not* be executed *before* the delay has
|
||||
passed.
|
||||
|
||||
``func`` will receive itself as only parameter. This is useful to implement
|
||||
periodic behavior (see the example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- grant the player 5 seconds of immortality
|
||||
player.isInvincible = true
|
||||
Timer.after(5, function() player.isInvincible = false end)
|
||||
|
||||
::
|
||||
|
||||
-- print "foo" every second. See also every()
|
||||
Timer.after(1, function(func) print("foo") Timer.after(1, func) end)
|
||||
|
||||
::
|
||||
|
||||
--Using a timer instance:
|
||||
menuTimer:after(1, finishAnimation)
|
||||
|
||||
|
||||
.. function:: Timer.script(func)
|
||||
|
||||
:param function func: Script to execute.
|
||||
|
||||
Execute a function that can be paused without causing the rest of the program to
|
||||
be suspended. ``func`` will receive a function - ``wait`` - to do interrupt the
|
||||
script (but not the whole program) as only argument. The function prototype of
|
||||
wait is: ``wait(delay)``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Timer.script(function(wait)
|
||||
print("Now")
|
||||
wait(1)
|
||||
print("After one second")
|
||||
wait(1)
|
||||
print("Bye!")
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
-- useful for splash screens
|
||||
Timer.script(function(wait)
|
||||
Timer.tween(0.5, splash.pos, {x = 300}, 'in-out-quad')
|
||||
wait(5) -- show the splash for 5 seconds
|
||||
Timer.tween(0.5, slpash.pos, {x = 800}, 'in-out-quad')
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
-- repeat something with a varying delay
|
||||
Timer.script(function(wait)
|
||||
while true do
|
||||
spawn_ship()
|
||||
wait(1 / (1-production_speed))
|
||||
end
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
-- jumping with timer.script
|
||||
self.timers:script(function(wait)
|
||||
local w = 1/12
|
||||
self.jumping = true
|
||||
Timer.tween(w*2, self, {z = -8}, "out-cubic", function()
|
||||
Timer.tween(w*2, self, {z = 0},"in-cubic")
|
||||
end)
|
||||
|
||||
self.quad = self.quads.jump[1]
|
||||
wait(w)
|
||||
|
||||
self.quad = self.quads.jump[2]
|
||||
wait(w)
|
||||
|
||||
self.quad = self.quads.jump[3]
|
||||
wait(w)
|
||||
|
||||
self.quad = self.quads.jump[4]
|
||||
wait(w)
|
||||
|
||||
self.jumping = false
|
||||
self.z = 0
|
||||
end)
|
||||
|
||||
|
||||
.. function:: Timer.every(delay, func[, count])
|
||||
|
||||
:param number delay: Number of seconds between two consecutive function calls.
|
||||
:param function func: The function to be called periodically.
|
||||
:param number count: Number of times the function is to be called (optional).
|
||||
:returns: The timer handle. See also :func:`Timer.cancel`.
|
||||
|
||||
|
||||
Add a function that will be called ``count`` times every ``delay`` seconds.
|
||||
|
||||
If ``count`` is omitted, the function will be called until it returns ``false``
|
||||
or :func:`Timer.cancel` or :func:`Timer.clear` is called on the timer instance.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- toggle light on and off every second
|
||||
Timer.every(1, function() lamp:toggleLight() end)
|
||||
|
||||
::
|
||||
|
||||
-- launch 5 fighters in quick succession (using a timer instance)
|
||||
mothership_timer:every(0.3, function() self:launchFighter() end, 5)
|
||||
|
||||
::
|
||||
|
||||
-- flicker player's image as long as he is invincible
|
||||
Timer.every(0.1, function()
|
||||
player:flipImage()
|
||||
return player.isInvincible
|
||||
end)
|
||||
|
||||
|
||||
.. function:: Timer.during(delay, func[, after])
|
||||
|
||||
:param number delay: Number of seconds the func will be called.
|
||||
:param function func: The function to be called on ``update(dt)``.
|
||||
:param function after: A function to be called after delay seconds (optional).
|
||||
:returns: The timer handle. See also :func:`Timer.cancel`.
|
||||
|
||||
|
||||
Run ``func(dt)`` for the next ``delay`` seconds. The function is called every
|
||||
time ``update(dt)`` is called. Optionally run ``after()`` once ``delay``
|
||||
seconds have passed.
|
||||
|
||||
``after()`` will receive itself as only parameter.
|
||||
|
||||
.. note::
|
||||
You should not add new timers in ``func(dt)``, as this can lead to random
|
||||
crashes.
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- play an animation for 5 seconds
|
||||
Timer.during(5, function(dt) animation:update(dt) end)
|
||||
|
||||
::
|
||||
|
||||
-- shake the camera for one second
|
||||
local orig_x, orig_y = camera:pos()
|
||||
Timer.during(1, function()
|
||||
camera:lookAt(orig_x + math.random(-2,2), orig_y + math.random(-2,2))
|
||||
end, function()
|
||||
-- reset camera position
|
||||
camera:lookAt(orig_x, orig_y)
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
player.isInvincible = true
|
||||
-- flash player for 3 seconds
|
||||
local t = 0
|
||||
player.timer:during(3, function(dt)
|
||||
t = t + dt
|
||||
player.visible = (t % .2) < .1
|
||||
end, function()
|
||||
-- make sure the player is visible after three seconds
|
||||
player.visible = true
|
||||
player.isInvincible = false
|
||||
end)
|
||||
|
||||
|
||||
.. function:: Timer.cancel(handle)
|
||||
|
||||
:param table handle: The function to be canceled.
|
||||
|
||||
Prevent a timer from being executed in the future.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function tick()
|
||||
print('tick... tock...')
|
||||
end
|
||||
handle = Timer.every(1, tick)
|
||||
-- later
|
||||
Timer.cancel(handle) -- NOT: Timer.cancel(tick)
|
||||
|
||||
::
|
||||
|
||||
-- using a timer instance
|
||||
function tick()
|
||||
print('tick... tock...')
|
||||
end
|
||||
handle = menuTimer:every(1, tick)
|
||||
-- later
|
||||
menuTimer:cancel(handle)
|
||||
|
||||
|
||||
.. function:: Timer.clear()
|
||||
|
||||
Remove all timed and periodic functions. Functions that have not yet been
|
||||
executed will discarded.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Timer.clear()
|
||||
|
||||
::
|
||||
|
||||
menuTimer:clear()
|
||||
|
||||
|
||||
.. function:: Timer.update(dt)
|
||||
|
||||
:param number dt: Time that has passed since the last ``update()``.
|
||||
|
||||
Update timers and execute functions if the deadline is reached. Call in
|
||||
``love.update(dt)``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
do_stuff()
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
-- using hump.gamestate and a timer instance
|
||||
function menuState:update(dt)
|
||||
self.timers:update(dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: Timer.tween(duration, subject, target, method, after, ...)
|
||||
|
||||
:param number duration: Duration of the tween.
|
||||
:param table subject: Object to be tweened.
|
||||
:param table target: Target values.
|
||||
:param string method: Tweening method, defaults to 'linear' (:ref:`see here
|
||||
<tweening-methods>`, optional).
|
||||
:param function after: Function to execute after the tween has finished
|
||||
(optiona).
|
||||
:param mixed ...: Additional arguments to the *tweening* function.
|
||||
:returns: A timer handle.
|
||||
|
||||
|
||||
`Tweening <http://en.wikipedia.org/wiki/Inbetweening>`_ (short for
|
||||
in-betweening) is the process that happens between two defined states. For
|
||||
example, a tween can be used to gradually fade out a graphic or move a text
|
||||
message to the center of the screen. For more information why tweening should
|
||||
be important to you, check out this great talk on `juicy games
|
||||
<http://www.youtube.com/watch?v=Fy0aCDmgnxg>`_.
|
||||
|
||||
``hump.timer`` offers two interfaces for tweening: the low-level
|
||||
:func:`Timer.during` and the higher level interface :func:`Timer.tween`.
|
||||
|
||||
To see which tweening methods hump offers, :ref:`see below <tweening-methods>`.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.load()
|
||||
color = {0, 0, 0}
|
||||
Timer.tween(10, color, {255, 255, 255}, 'in-out-quad')
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.setBackgroundColor(color)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.load()
|
||||
circle = {rad = 10, pos = {x = 400, y = 300}}
|
||||
-- multiple tweens can work on the same subject
|
||||
-- and nested values can be tweened, too
|
||||
Timer.tween(5, circle, {rad = 50}, 'in-out-quad')
|
||||
Timer.tween(2, circle, {pos = {y = 550}}, 'out-bounce')
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.circle('fill', circle.pos.x, circle.pos.y, circle.rad)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.load()
|
||||
-- repeated tweening
|
||||
|
||||
circle = {rad = 10, x = 100, y = 100}
|
||||
local grow, shrink, move_down, move_up
|
||||
grow = function()
|
||||
Timer.tween(1, circle, {rad = 50}, 'in-out-quad', shrink)
|
||||
end
|
||||
shrink = function()
|
||||
Timer.tween(2, circle, {rad = 10}, 'in-out-quad', grow)
|
||||
end
|
||||
|
||||
move_down = function()
|
||||
Timer.tween(3, circle, {x = 700, y = 500}, 'bounce', move_up)
|
||||
end
|
||||
move_up = function()
|
||||
Timer.tween(5, circle, {x = 200, y = 200}, 'out-elastic', move_down)
|
||||
end
|
||||
|
||||
grow()
|
||||
move_down()
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.circle('fill', circle.x, circle.y, circle.rad)
|
||||
end
|
||||
|
||||
|
||||
|
||||
.. _tweening-methods:
|
||||
|
||||
Tweening methods
|
||||
----------------
|
||||
|
||||
At the core of tweening lie interpolation methods. These methods define how the
|
||||
output should look depending on how much time has passed. For example, consider
|
||||
the following tween::
|
||||
|
||||
-- now: player.x = 0, player.y = 0
|
||||
Timer.tween(2, player, {x = 2})
|
||||
Timer.tween(4, player, {y = 8})
|
||||
|
||||
At the beginning of the tweens (no time passed), the interpolation method would
|
||||
place the player at ``x = 0, y = 0``. After one second, the player should be at
|
||||
``x = 1, y = 2``, and after two seconds the output is ``x = 2, y = 4``.
|
||||
|
||||
The actual duration of and time since starting the tween is not important, only
|
||||
the fraction of the two. Similarly, the starting value and output are not
|
||||
important to the interpolation method, since it can be calculated from the
|
||||
start and end point. Thus an interpolation method can be fully characterized by
|
||||
a function that takes a number between 0 and 1 and returns a number that
|
||||
defines the output (usually also between 0 and 1). The interpolation function
|
||||
must hold that the output is 0 for input 0 and 1 for input 1.
|
||||
|
||||
**hump** predefines several commonly used interpolation methods, which are
|
||||
generalized versions of `Robert Penner's easing
|
||||
functions <http://www.robertpenner.com/easing/>`_. Those are:
|
||||
|
||||
``'linear'``,
|
||||
``'quad'``,
|
||||
``'cubic'``,
|
||||
``'quart'``,
|
||||
``'quint'``,
|
||||
``'sine'``,
|
||||
``'expo'``,
|
||||
``'circ'``,
|
||||
``'back'``,
|
||||
``'bounce'``, and
|
||||
``'elastic'``.
|
||||
|
||||
It's hard to understand how these functions behave by staring at a graph, so
|
||||
below are some animation examples. You can change the type of the tween by
|
||||
changing the selections.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div id="tween-graph"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
|
||||
<script src="_static/graph-tweens.js"></script>
|
||||
|
||||
Note that while the animations above show tweening of shapes, other attributes
|
||||
(color, opacity, volume of a sound, ...) can be changed as well.
|
||||
|
||||
|
||||
Custom interpolators
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. warning:
|
||||
This is a stub
|
||||
|
||||
You can add custom interpolation methods by adding them to the `tween` table::
|
||||
|
||||
Timer.tween.sqrt = function(t) return math.sqrt(t) end
|
||||
-- or just Timer.tween.sqrt = math.sqrt
|
||||
|
||||
Access the your method like you would the predefined ones. You can even use the
|
||||
modyfing prefixes::
|
||||
|
||||
Timer.tween(5, 'in-out-sqrt', circle, {radius = 50})
|
||||
|
||||
You can also invert and chain functions::
|
||||
|
||||
outsqrt = Timer.tween.out(math.sqrt)
|
||||
inoutsqrt = Timer.tween.chain(math.sqrt, outsqrt)
|
||||
@@ -0,0 +1,431 @@
|
||||
hump.vector-light
|
||||
=================
|
||||
|
||||
::
|
||||
|
||||
vector = require "hump.vector-light"
|
||||
|
||||
An table-free version of :doc:`hump.vector <vector>`. Instead of a vector type,
|
||||
``hump.vector-light`` provides functions that operate on numbers.
|
||||
|
||||
.. note::
|
||||
|
||||
Using this module instead of :doc:`hump.vector <vector>` may result in
|
||||
faster code, but does so at the expense of speed of development and code
|
||||
readability. Unless you are absolutely sure that your code is
|
||||
significantly slowed down by :doc:`hump.vector <vector>`, I recommend using
|
||||
it instead.
|
||||
|
||||
**Example**::
|
||||
|
||||
function player:update(dt)
|
||||
local dx,dy = 0,0
|
||||
if love.keyboard.isDown('left') then
|
||||
dx = -1
|
||||
elseif love.keyboard.isDown('right') then
|
||||
dx = 1
|
||||
end
|
||||
if love.keyboard.isDown('up') then
|
||||
dy = -1
|
||||
elseif love.keyboard.isDown('down') then
|
||||
dy = 1
|
||||
end
|
||||
dx,dy = vector.normalize(dx, dy)
|
||||
|
||||
player.velx, player.vely = vector.add(player.velx, player.vely,
|
||||
vector.mul(dy, dx, dy))
|
||||
|
||||
if vector.len(player.velx, player.vely) > player.max_velocity then
|
||||
player.velx, player.vely = vector.mul(player.max_velocity,
|
||||
vector.normalize(player.velx, player.vely)
|
||||
end
|
||||
|
||||
player.x = player.x + dt * player.velx
|
||||
player.y = player.y + dt * player.vely
|
||||
end
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: vector.str(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: The string representation.
|
||||
|
||||
|
||||
Produce a human-readable string of the form ``(x,y)``.
|
||||
Useful for debugging.
|
||||
|
||||
**Example**::
|
||||
|
||||
print(vector.str(love.mouse.getPosition()))
|
||||
|
||||
|
||||
.. function:: vector.fromPolar(angle, radius)
|
||||
|
||||
:param number angle: Angle of the vector in radians.
|
||||
:param number radius: Length of the vector.
|
||||
:returns: ``x``, ``y``: The vector in cartesian coordinates.
|
||||
|
||||
|
||||
Convert polar coordinates to cartesian coordinates.
|
||||
The ``angle`` is measured against the vector (1,0), i.e., the x axis.
|
||||
|
||||
**Examples**::
|
||||
|
||||
x,y = vector.polar(math.pi,10)
|
||||
|
||||
|
||||
.. function:: vector.toPolar(x, y)
|
||||
|
||||
:param numbers x,y: A vector.
|
||||
:returns: ``angle``, ``radius``: The vector in polar coordinates.
|
||||
|
||||
Convert the vector to polar coordinates, i.e., the angle and the radius/lenth.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- complex multiplication
|
||||
phase1, abs1 = vector.toPolar(re1, im1)
|
||||
phase2, abs2 = vector.toPolar(re2, im2)
|
||||
|
||||
vector.fromPolar(phase1+phase2, abs1*abs2)
|
||||
|
||||
|
||||
.. function:: vector.mul(s, x,y)
|
||||
|
||||
:param number s: A scalar.
|
||||
:param numbers x,y: A vector.
|
||||
:returns: ``x*s, y*s``.
|
||||
|
||||
|
||||
Computes ``x*s,y*s``. The order of arguments is chosen so that it's possible to
|
||||
chain operations (see example).
|
||||
|
||||
**Example**::
|
||||
|
||||
velx,vely = vec.mul(dt, vec.add(velx,vely, accx,accy))
|
||||
|
||||
|
||||
.. function:: vector.div(s, x,y)
|
||||
|
||||
:param number s: A scalar.
|
||||
:param numbers x,y: A vector.
|
||||
:returns: ``x/s, y/s``.
|
||||
|
||||
|
||||
Computes ``x/s,y/s``. The order of arguments is chosen so that it's possible to
|
||||
chain operations (see example).
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = vec.div(self.zoom, x-w/2, y-h/2)
|
||||
|
||||
|
||||
.. function:: vector.add(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1+x2, x1+x2``.
|
||||
|
||||
|
||||
Computes the sum \\((x1+x2, y1+y2)\\)`` of two vectors. Meant to be used in
|
||||
conjunction with other functions like :func:`vector.mul`.
|
||||
|
||||
**Example**::
|
||||
|
||||
player.x,player.y = vector.add(player.x,player.y, vector.mul(dt, dx,dy))
|
||||
|
||||
|
||||
.. function:: vector.sub(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1-x2, x1-x2``.
|
||||
|
||||
|
||||
Computes the difference \\((x1-x2, y1-y2)\\) of two vectors. Meant to be used in
|
||||
conjunction with other functions like :func:`vector.mul`.
|
||||
|
||||
**Example**::
|
||||
|
||||
dx,dy = vector.sub(400,300, love.mouse.getPosition())
|
||||
|
||||
|
||||
.. function:: vector.permul(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*x2, y1*y2``.
|
||||
|
||||
|
||||
Component-wise multiplication, i.e.: ``x1*x2, y1*y2``.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = vector.permul(x,y, 1,1.5)
|
||||
|
||||
|
||||
.. function:: vector.dot(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*x2 + y1*y2``.
|
||||
|
||||
|
||||
Computes the `dot product <http://en.wikipedia.org/wiki/Dot_product>`_ of two
|
||||
vectors: ``x1*x2 + y1*y2``.
|
||||
|
||||
**Example**::
|
||||
|
||||
cosphi = vector.dot(rx,ry, vx,vy)
|
||||
|
||||
|
||||
.. function:: vector.cross(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*y2 - y1*x2``.
|
||||
|
||||
|
||||
Computes the `cross product <http://en.wikipedia.org/wiki/Cross_product>`_ of
|
||||
two vectors: ``x1*y2 - y1*x2``.
|
||||
|
||||
**Example**::
|
||||
|
||||
parallelogram_area = vector.cross(ax,ay, bx,by)
|
||||
|
||||
|
||||
.. function:: vector.vector.det(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*y2 - y1*x2``.
|
||||
|
||||
|
||||
Alias to :func:`vector.cross`.
|
||||
|
||||
**Example**::
|
||||
|
||||
parallelogram_area = vector.det(ax,ay, bx,by)
|
||||
|
||||
|
||||
.. function:: vector.eq(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1 == x2 and y1 == y2``
|
||||
|
||||
Test for equality.
|
||||
|
||||
**Example**::
|
||||
|
||||
if vector.eq(x1,y1, x2,y2) then be.happy() end
|
||||
|
||||
|
||||
.. function:: vector.le(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1 <= x2 and y1 <= y2``.
|
||||
|
||||
Test for partial lexicographical order, ``<=``.
|
||||
|
||||
**Example**::
|
||||
|
||||
if vector.le(x1,y1, x2,y2) then be.happy() end
|
||||
|
||||
|
||||
.. function:: vector.lt(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1 < x2 or (x1 == x2) and y1 <= y2``.
|
||||
|
||||
|
||||
Test for strict lexicographical order, ``<``.
|
||||
|
||||
**Example**::
|
||||
|
||||
if vector.lt(x1,y1, x2,y2) then be.happy() end
|
||||
|
||||
|
||||
.. function:: vector.len(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: Length of the vector.
|
||||
|
||||
Get length of a vector, i.e. ``math.sqrt(x*x + y*y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
distance = vector.len(love.mouse.getPosition())
|
||||
|
||||
|
||||
.. function:: vector.len2(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: Squared length of the vector.
|
||||
|
||||
Get squared length of a vector, i.e. ``x*x + y*y``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
closest, dsq = vertices[1], vector.len2(px-vertices[1].x, py-vertices[1].y)
|
||||
for i = 2,#vertices do
|
||||
local temp = vector.len2(px-vertices[i].x, py-vertices[i].y)
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.dist(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: The distance of the points.
|
||||
|
||||
|
||||
Get distance of two points. The same as ``vector.len(x1-x2, y1-y2)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
-- slightly slower than the example using len2()
|
||||
closest, dist = vertices[1], vector.dist(px,py, vertices[1].x,vertices[1].y)
|
||||
for i = 2,#vertices do
|
||||
local temp = vector.dist(px,py, vertices[i].x,vertices[i].y)
|
||||
if temp < dist then
|
||||
closest, dist = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.dist2(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: The squared distance of two points.
|
||||
|
||||
Get squared distance of two points. The same as ``vector.len2(x1-x2, y1-y2)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
closest, dsq = vertices[1], vector.dist2(px,py, vertices[1].x,vertices[1].y)
|
||||
for i = 2,#vertices do
|
||||
local temp = vector.dist2(px,py, vertices[i].x,vertices[i].y)
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.normalize(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: Vector with same direction as the input vector, but length 1.
|
||||
|
||||
|
||||
Get normalized vector, i.e. a vector with the same direction as the input
|
||||
vector, but with length 1.
|
||||
|
||||
**Example**::
|
||||
|
||||
dx,dy = vector.normalize(vx,vy)
|
||||
|
||||
|
||||
.. function:: vector.rotate(phi, x,y)
|
||||
|
||||
:param number phi: Rotation angle in radians.
|
||||
:param numbers x,y: The vector.
|
||||
:returns: The rotated vector
|
||||
|
||||
|
||||
Get a rotated vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- approximate a circle
|
||||
circle = {}
|
||||
for i = 1,30 do
|
||||
local phi = 2 * math.pi * i / 30
|
||||
circle[i*2-1], circle[i*2] = vector.rotate(phi, 0,1)
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.perpendicular(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: A vector perpendicular to the input vector
|
||||
|
||||
|
||||
Quick rotation by 90°. The same (but faster) as ``vector.rotate(math.pi/2, x,y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
nx,ny = vector.normalize(vector.perpendicular(bx-ax, by-ay))
|
||||
|
||||
|
||||
.. function:: vector.project(x,y, u,v)
|
||||
|
||||
:param numbers x,y: The vector to project.
|
||||
:param numbers u,v: The vector to project onto.
|
||||
:returns: The projected vector.
|
||||
|
||||
|
||||
Project vector onto another vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
vx_p,vy_p = vector.project(vx,vy, ax,ay)
|
||||
|
||||
|
||||
.. function:: vector.mirror(x,y, u,v)
|
||||
|
||||
:param numbers x,y: The vector to mirror.
|
||||
:param numbers u,v: The vector defining the axis.
|
||||
:returns: The mirrored vector.
|
||||
|
||||
|
||||
Mirrors vector on the axis defined by the other vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
vx,vy = vector.mirror(vx,vy, surface.x,surface.y)
|
||||
|
||||
|
||||
.. function:: vector.angleTo(ox,y, u,v)
|
||||
|
||||
:param numbers x,y: Vector to measure the angle.
|
||||
:param numbers u,v (optional): Reference vector.
|
||||
:returns: Angle in radians.
|
||||
|
||||
|
||||
Measures the angle between two vectors. ``u`` and ``v`` default to ``0`` if omitted,
|
||||
i.e. the function returns the angle to the coordinate system.
|
||||
|
||||
**Example**::
|
||||
|
||||
lean = vector.angleTo(self.upx, self.upy, 0,1)
|
||||
if lean > .1 then self:fallOver() end
|
||||
|
||||
|
||||
.. function:: vector.trim(max_length, x,y)
|
||||
|
||||
:param number max_length: Maximum allowed length of the vector.
|
||||
:param numbers x,y: Vector to trum.
|
||||
:returns: The trimmed vector.
|
||||
|
||||
Trim the vector to ``max_length``, i.e. return a vector that points in the same
|
||||
direction as the source vector, but has a magnitude smaller or equal to
|
||||
``max_length``.
|
||||
|
||||
**Example**::
|
||||
|
||||
vel_x, vel_y = vector.trim(299792458,
|
||||
vector.add(vel_x, vel_y,
|
||||
vector.mul(mass * dt, force_x, force_y)))
|
||||
@@ -0,0 +1,450 @@
|
||||
hump.vector
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
vector = require "hump.vector"
|
||||
|
||||
A handy 2D vector class providing most of the things you do with vectors.
|
||||
|
||||
You can access the individual coordinates by ``vec.x`` and ``vec.y``.
|
||||
|
||||
.. note::
|
||||
|
||||
The vectors are stored as tables. Most operations create new vectors and
|
||||
thus new tables, which *may* put the garbage collector under stress.
|
||||
If you experience slowdowns that are caused by hump.vector, try the
|
||||
table-less version :doc:`hump.vector-light <vector-light>`.
|
||||
|
||||
**Example**::
|
||||
|
||||
function player:update(dt)
|
||||
local delta = vector(0,0)
|
||||
if love.keyboard.isDown('left') then
|
||||
delta.x = -1
|
||||
elseif love.keyboard.isDown('right') then
|
||||
delta.x = 1
|
||||
end
|
||||
if love.keyboard.isDown('up') then
|
||||
delta.y = -1
|
||||
elseif love.keyboard.isDown('down') then
|
||||
delta.y = 1
|
||||
end
|
||||
delta:normalizeInplace()
|
||||
|
||||
player.velocity = player.velocity + delta * player.acceleration * dt
|
||||
|
||||
if player.velocity:len() > player.max_velocity then
|
||||
player.velocity = player.velocity:normalized() * player.max_velocity
|
||||
end
|
||||
|
||||
player.position = player.position + player.velocity * dt
|
||||
end
|
||||
|
||||
|
||||
Vector arithmetic
|
||||
-----------------
|
||||
|
||||
**hump** provides vector arithmetic by implement the corresponding metamethods
|
||||
(``__add``, ``__mul``, etc.). Here are the semantics:
|
||||
|
||||
``vector + vector = vector``
|
||||
Component wise sum: \\((a,b) + (x,y) = (a+x, b+y)\\)
|
||||
``vector - vector = vector``
|
||||
Component wise difference: \\((a,b) - (x,y) = (a-x, b-y)\\)
|
||||
``vector * vector = number``
|
||||
Dot product: \\((a,b) \\cdot (x,y) = a\\cdot x + b\\cdot y\\)
|
||||
``number * vector = vector``
|
||||
Scalar multiplication/scaling: \\((a,b) \\cdot s = (s\\cdot a, s\\cdot b)\\)
|
||||
``vector * number = vector``
|
||||
Scalar multiplication/scaling: \\(s \\cdot (x,y) = (s\\cdot x, s\\cdot y)\\)
|
||||
``vector / number = vector``
|
||||
Scalar multiplication/scaling: \\((a,b) / s = (a/s, b/s)\\).
|
||||
|
||||
Common relations are also defined:
|
||||
|
||||
``a == b``
|
||||
Same as ``a.x == b.x and a.y == b.y``.
|
||||
``a <= b``
|
||||
Same as ``a.x <= b.x and a.y <= b.y``.
|
||||
``a < b``
|
||||
Lexicographical order: ``a.x < b.x or (a.x == b.x and a.y < b.y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- acceleration, player.velocity and player.position are vectors
|
||||
acceleration = vector(0,-9)
|
||||
player.velocity = player.velocity + acceleration * dt
|
||||
player.position = player.position + player.velocity * dt
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: vector.new(x,y)
|
||||
|
||||
:param numbers x,y: Coordinates.
|
||||
:returns: The vector.
|
||||
|
||||
|
||||
Create a new vector.
|
||||
|
||||
**Examples**::
|
||||
|
||||
a = vector.new(10,10)
|
||||
|
||||
::
|
||||
|
||||
-- as a shortcut, you can call the module like a function:
|
||||
vector = require "hump.vector"
|
||||
a = vector(10,10)
|
||||
|
||||
|
||||
.. function:: vector.fromPolar(angle, radius)
|
||||
|
||||
:param number angle: Angle of the vector in radians.
|
||||
:param number radius: Length of the vector.
|
||||
:returns: The vector in cartesian coordinates.
|
||||
|
||||
|
||||
Create a new vector from polar coordinates.
|
||||
The ``angle`` is measured against the vector (1,0), i.e., the x axis.
|
||||
|
||||
**Examples**::
|
||||
|
||||
a = vector.polar(math.pi,10)
|
||||
|
||||
|
||||
.. function:: vector.isvector(v)
|
||||
|
||||
:param mixed v: The variable to test.
|
||||
:returns: ``true`` if ``v`` is a vector, ``false`` otherwise.
|
||||
|
||||
Test whether a variable is a vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
if not vector.isvector(v) then
|
||||
v = vector(v,0)
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:clone()
|
||||
|
||||
:returns: Copy of the vector.
|
||||
|
||||
Copy a vector. Assigning a vector to a variable will create a *reference*, so
|
||||
when modifying the vector referenced by the new variable would also change the
|
||||
old one::
|
||||
|
||||
a = vector(1,1) -- create vector
|
||||
b = a -- b references a
|
||||
c = a:clone() -- c is a copy of a
|
||||
b.x = 0 -- changes a,b and c
|
||||
print(a,b,c) -- prints '(1,0), (1,0), (1,1)'
|
||||
|
||||
**Example**::
|
||||
|
||||
copy = original:clone()
|
||||
|
||||
|
||||
.. function:: vector:unpack()
|
||||
|
||||
:returns: The coordinates ``x,y``.
|
||||
|
||||
|
||||
Extract coordinates.
|
||||
|
||||
**Examples**::
|
||||
|
||||
x,y = pos:unpack()
|
||||
|
||||
::
|
||||
|
||||
love.graphics.draw(self.image, self.pos:unpack())
|
||||
|
||||
|
||||
.. function:: vector:permul(other)
|
||||
|
||||
:param vector other: The second source vector.
|
||||
:returns: Vector whose components are products of the source vectors.
|
||||
|
||||
|
||||
Multiplies vectors coordinate wise, i.e. ``result = vector(a.x * b.x, a.y *
|
||||
b.y)``.
|
||||
|
||||
Does not change either argument vectors, but creates a new one.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- scale with different magnitudes
|
||||
scaled = original:permul(vector(1,1.5))
|
||||
|
||||
|
||||
.. function:: vector:len()
|
||||
|
||||
:returns: Length of the vector.
|
||||
|
||||
|
||||
Get length of the vector, i.e. ``math.sqrt(vec.x * vec.x + vec.y * vec.y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
distance = (a - b):len()
|
||||
|
||||
|
||||
.. function:: vector:toPolar()
|
||||
|
||||
:returns: The vector in polar coordinates (angle, radius).
|
||||
|
||||
Convert the vector to polar coordinates, i.e., the angle and the radius/lenth.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- complex multiplication
|
||||
p, q = a:toPolar(), b:toPolar()
|
||||
c = vector(p.x+q.x, p.y*q.y)
|
||||
|
||||
|
||||
.. function:: vector:len2()
|
||||
|
||||
:returns: Squared length of the vector.
|
||||
|
||||
|
||||
Get squared length of the vector, i.e. ``vec.x * vec.x + vec.y * vec.y``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
closest, dsq = vertices[1], (pos - vertices[1]):len2()
|
||||
for i = 2,#vertices do
|
||||
local temp = (pos - vertices[i]):len2()
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:dist(other)
|
||||
|
||||
:param vector other: Other vector to measure the distance to.
|
||||
:returns: The distance of the vectors.
|
||||
|
||||
|
||||
Get distance of two vectors. The same as ``(a - b):len()``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
-- slightly slower than the example using len2()
|
||||
closest, dist = vertices[1], pos:dist(vertices[1])
|
||||
for i = 2,#vertices do
|
||||
local temp = pos:dist(vertices[i])
|
||||
if temp < dist then
|
||||
closest, dist = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:dist2(other)
|
||||
|
||||
:param vector other: Other vector to measure the distance to.
|
||||
:returns: The squared distance of the vectors.
|
||||
|
||||
|
||||
Get squared distance of two vectors. The same as ``(a - b):len2()``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
-- slightly faster than the example using len2()
|
||||
closest, dsq = vertices[1], pos:dist2(vertices[1])
|
||||
for i = 2,#vertices do
|
||||
local temp = pos:dist2(vertices[i])
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:normalized()
|
||||
|
||||
:returns: Vector with same direction as the input vector, but length 1.
|
||||
|
||||
|
||||
Get normalized vector: a vector with the same direction as the input vector,
|
||||
but with length 1.
|
||||
|
||||
Does not change the input vector, but creates a new vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
direction = velocity:normalized()
|
||||
|
||||
|
||||
.. function:: vector:normalizeInplace()
|
||||
|
||||
:returns: Itself -- the normalized vector
|
||||
|
||||
|
||||
Normalize a vector, i.e. make the vector to have length 1. Great to use on
|
||||
intermediate results.
|
||||
|
||||
.. warning::
|
||||
This modifies the vector. If in doubt, use :func:`vector:normalized()`.
|
||||
|
||||
**Example**::
|
||||
|
||||
normal = (b - a):perpendicular():normalizeInplace()
|
||||
|
||||
|
||||
.. function:: vector:rotated(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians.
|
||||
:returns: The rotated vector
|
||||
|
||||
|
||||
Get a vector with same length, but rotated by ``angle``:
|
||||
|
||||
.. image:: _static/vector-rotated.png
|
||||
:alt: Sketch of rotated vector.
|
||||
|
||||
Does not change the input vector, but creates a new vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- approximate a circle
|
||||
circle = {}
|
||||
for i = 1,30 do
|
||||
local phi = 2 * math.pi * i / 30
|
||||
circle[#circle+1] = vector(0,1):rotated(phi)
|
||||
end
|
||||
|
||||
.. function:: vector:rotateInplace(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians.
|
||||
:returns: Itself -- the rotated vector
|
||||
|
||||
|
||||
Rotate a vector in-place. Great to use on intermediate results.
|
||||
|
||||
.. warning::
|
||||
Yhis modifies the vector. If in doubt, use :func:`vector:rotated()`.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- ongoing rotation
|
||||
spawner.direction:rotateInplace(dt)
|
||||
|
||||
|
||||
.. function:: vector:perpendicular()
|
||||
|
||||
:returns: A vector perpendicular to the input vector
|
||||
|
||||
Quick rotation by 90°. Creates a new vector. The same (but faster) as
|
||||
``vec:rotate(math.pi/2)``:
|
||||
|
||||
.. image:: _static/vector-perpendicular.png
|
||||
:alt: Sketch of two perpendicular vectors
|
||||
|
||||
**Example**::
|
||||
|
||||
normal = (b - a):perpendicular():normalizeInplace()
|
||||
|
||||
|
||||
|
||||
.. function:: vector:projectOn(v)
|
||||
|
||||
:param vector v: The vector to project on.
|
||||
:returns: ``vector`` The projected vector.
|
||||
|
||||
|
||||
Project vector onto another vector:
|
||||
|
||||
.. image:: _static/vector-projectOn.png
|
||||
:alt: Sketch of vector projection.
|
||||
|
||||
**Example**::
|
||||
|
||||
velocity_component = velocity:projectOn(axis)
|
||||
|
||||
|
||||
|
||||
.. function:: vector:mirrorOn(v)
|
||||
|
||||
:param vector v: The vector to mirror on.
|
||||
:returns: The mirrored vector.
|
||||
|
||||
|
||||
Mirrors vector on the axis defined by the other vector:
|
||||
|
||||
.. image: _static/vector-mirrorOn.png
|
||||
:alt: Sketch of a vector mirrored on another vector
|
||||
|
||||
**Example**::
|
||||
|
||||
deflected_velocity = ball.velocity:mirrorOn(surface_normal)
|
||||
|
||||
|
||||
.. function:: vector:cross(other)
|
||||
|
||||
:param vector other: Vector to compute the cross product with.
|
||||
:returns: ``number`` Cross product of both vectors.
|
||||
|
||||
|
||||
Get cross product of two vectors. Equals the area of the parallelogram spanned
|
||||
by both vectors.
|
||||
|
||||
**Example**::
|
||||
|
||||
parallelogram_area = a:cross(b)
|
||||
|
||||
|
||||
.. function:: vector:angleTo(other)
|
||||
|
||||
:param vector other: Vector to measure the angle to (optional).
|
||||
:returns: Angle in radians.
|
||||
|
||||
|
||||
Measures the angle between two vectors. If ``other`` is omitted it defaults
|
||||
to the vector ``(0,0)``, i.e. the function returns the angle to the coordinate
|
||||
system.
|
||||
|
||||
**Example**::
|
||||
|
||||
lean = self.upvector:angleTo(vector(0,1))
|
||||
if lean > .1 then self:fallOver() end
|
||||
|
||||
.. function:: vector:trimmed(max_length)
|
||||
|
||||
:param number max_length: Maximum allowed length of the vector.
|
||||
:returns: A trimmed vector.
|
||||
|
||||
Trim the vector to ``max_length``, i.e. return a vector that points in the same
|
||||
direction as the source vector, but has a magnitude smaller or equal to
|
||||
``max_length``.
|
||||
|
||||
Does not change the input vector, but creates a new vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
ship.velocity = ship.force * ship.mass * dt
|
||||
ship.velocity = ship.velocity:trimmed(299792458)
|
||||
|
||||
|
||||
.. function:: vector:trimInplace(max_length)
|
||||
|
||||
:param number max_length: Maximum allowed length of the vector.
|
||||
:returns: Itself -- the trimmed vector.
|
||||
|
||||
Trim the vector to ``max_length``, i.e. return a vector that points in the same
|
||||
direction as the source vector, but has a magnitude smaller or equal to
|
||||
``max_length``.
|
||||
|
||||
.. warning::
|
||||
Yhis modifies the vector. If in doubt, use :func:`vector:trimmed()`.
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
ship.velocity = (ship.velocity + ship.force * ship.mass * dt):trimInplace(299792458)
|
||||
@@ -0,0 +1,108 @@
|
||||
--[[
|
||||
Copyright (c) 2010-2013 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
|
||||
|
||||
-- default gamestate produces error on every callback
|
||||
local state_init = setmetatable({leave = __NULL__},
|
||||
{__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end})
|
||||
local stack = {state_init}
|
||||
local initialized_states = setmetatable({}, {__mode = "k"})
|
||||
local state_is_dirty = true
|
||||
|
||||
local GS = {}
|
||||
function GS.new(t) return t or {} end -- constructor - deprecated!
|
||||
|
||||
local function change_state(stack_offset, to, ...)
|
||||
local pre = stack[#stack]
|
||||
|
||||
-- initialize only on first call
|
||||
;(initialized_states[to] or to.init or __NULL__)(to)
|
||||
initialized_states[to] = __NULL__
|
||||
|
||||
stack[#stack+stack_offset] = to
|
||||
state_is_dirty = true
|
||||
return (to.enter or __NULL__)(to, pre, ...)
|
||||
end
|
||||
|
||||
function GS.switch(to, ...)
|
||||
assert(to, "Missing argument: Gamestate to switch to")
|
||||
assert(to ~= GS, "Can't call switch with colon operator")
|
||||
;(stack[#stack].leave or __NULL__)(stack[#stack])
|
||||
return change_state(0, to, ...)
|
||||
end
|
||||
|
||||
function GS.push(to, ...)
|
||||
assert(to, "Missing argument: Gamestate to switch to")
|
||||
assert(to ~= GS, "Can't call push with colon operator")
|
||||
return change_state(1, to, ...)
|
||||
end
|
||||
|
||||
function GS.pop(...)
|
||||
assert(#stack > 1, "No more states to pop!")
|
||||
local pre, to = stack[#stack], stack[#stack-1]
|
||||
stack[#stack] = nil
|
||||
;(pre.leave or __NULL__)(pre)
|
||||
state_is_dirty = true
|
||||
return (to.resume or __NULL__)(to, pre, ...)
|
||||
end
|
||||
|
||||
function GS.current()
|
||||
return stack[#stack]
|
||||
end
|
||||
|
||||
-- fetch event callbacks from love.handlers
|
||||
local all_callbacks = { 'draw', 'errhand', 'update' }
|
||||
for k in pairs(love.handlers) do
|
||||
all_callbacks[#all_callbacks+1] = k
|
||||
end
|
||||
|
||||
function GS.registerEvents(callbacks)
|
||||
local registry = {}
|
||||
callbacks = callbacks or all_callbacks
|
||||
for _, f in ipairs(callbacks) do
|
||||
registry[f] = love[f] or __NULL__
|
||||
love[f] = function(...)
|
||||
registry[f](...)
|
||||
return GS[f](...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- forward any undefined functions
|
||||
setmetatable(GS, {__index = function(_, func)
|
||||
-- call function only if at least one 'update' was called beforehand
|
||||
-- (see issue #46)
|
||||
if not state_is_dirty or func == 'update' then
|
||||
state_is_dirty = false
|
||||
return function(...)
|
||||
return (stack[#stack][func] or __NULL__)(stack[#stack], ...)
|
||||
end
|
||||
end
|
||||
return __NULL__
|
||||
end})
|
||||
|
||||
return GS
|
||||
@@ -0,0 +1,102 @@
|
||||
--[[
|
||||
Copyright (c) 2012-2013 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 Registry = {}
|
||||
Registry.__index = function(self, key)
|
||||
return Registry[key] or (function()
|
||||
local t = {}
|
||||
rawset(self, key, t)
|
||||
return t
|
||||
end)()
|
||||
end
|
||||
|
||||
function Registry:register(s, f)
|
||||
self[s][f] = f
|
||||
return f
|
||||
end
|
||||
|
||||
function Registry:emit(s, ...)
|
||||
for f in pairs(self[s]) do
|
||||
f(...)
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:remove(s, ...)
|
||||
local f = {...}
|
||||
for i = 1,select('#', ...) do
|
||||
self[s][f[i]] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:clear(...)
|
||||
local s = {...}
|
||||
for i = 1,select('#', ...) do
|
||||
self[s[i]] = {}
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:emitPattern(p, ...)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self:emit(s, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:registerPattern(p, f)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self:register(s, f) end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
function Registry:removePattern(p, ...)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self:remove(s, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:clearPattern(p)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self[s] = {} end
|
||||
end
|
||||
end
|
||||
|
||||
-- instancing
|
||||
function Registry.new()
|
||||
return setmetatable({}, Registry)
|
||||
end
|
||||
|
||||
-- default instance
|
||||
local default = Registry.new()
|
||||
|
||||
-- module forwards calls to default instance
|
||||
local module = {}
|
||||
for k in pairs(Registry) do
|
||||
if k ~= "__index" then
|
||||
module[k] = function(...) return default[k](default, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(module, {__call = Registry.new})
|
||||
@@ -0,0 +1,73 @@
|
||||
local timer = require 'timer'()
|
||||
|
||||
describe('hump.timer', function()
|
||||
it('runs a function during a specified time', function()
|
||||
local delta, remaining
|
||||
|
||||
timer:during(10, function(...) delta, remaining = ... end)
|
||||
|
||||
timer:update(2)
|
||||
assert.are.equal(delta, 2)
|
||||
assert.are.equal(8, remaining)
|
||||
|
||||
timer:update(5)
|
||||
assert.are.equal(delta, 5)
|
||||
assert.are.equal(3, remaining)
|
||||
|
||||
timer:update(10)
|
||||
assert.are.equal(delta, 10)
|
||||
assert.are.equal(0, remaining)
|
||||
end)
|
||||
|
||||
it('runs a function after a specified time', function()
|
||||
local finished1 = false
|
||||
local finished2 = false
|
||||
|
||||
timer:after(3, function(...) finished1 = true end)
|
||||
timer:after(5, function(...) finished2 = true end)
|
||||
|
||||
timer:update(4)
|
||||
assert.are.equal(true, finished1)
|
||||
assert.are.equal(false, finished2)
|
||||
|
||||
timer:update(4)
|
||||
assert.are.equal(true, finished1)
|
||||
assert.are.equal(true, finished2)
|
||||
end)
|
||||
|
||||
it('runs a function every so often', function()
|
||||
local count = 0
|
||||
|
||||
timer:every(1, function(...) count = count + 1 end)
|
||||
|
||||
timer:update(3)
|
||||
assert.are.equal(3, count)
|
||||
|
||||
timer:update(7)
|
||||
assert.are.equal(10, count)
|
||||
end)
|
||||
|
||||
it('can script timed events', function()
|
||||
local state
|
||||
|
||||
timer:script(function(wait)
|
||||
state = 'foo'
|
||||
wait(1)
|
||||
state = 'bar'
|
||||
end)
|
||||
|
||||
assert.are.equal('foo', state)
|
||||
timer:update(0.5)
|
||||
assert.are.equal('foo', state)
|
||||
timer:update(1)
|
||||
assert.are.equal('bar', state)
|
||||
end)
|
||||
|
||||
it('cancels and clears timer functions', function()
|
||||
pending('to be tested...')
|
||||
end)
|
||||
|
||||
it('tweens', function()
|
||||
pending('to be tested...')
|
||||
end)
|
||||
end)
|
||||
@@ -0,0 +1,210 @@
|
||||
--[[
|
||||
Copyright (c) 2010-2013 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 Timer = {}
|
||||
Timer.__index = Timer
|
||||
|
||||
local function _nothing_() end
|
||||
|
||||
function Timer:update(dt)
|
||||
local to_remove = {}
|
||||
|
||||
for handle in pairs(self.functions) do
|
||||
-- handle: {
|
||||
-- time = <number>,
|
||||
-- after = <function>,
|
||||
-- during = <function>,
|
||||
-- limit = <number>,
|
||||
-- count = <number>,
|
||||
-- }
|
||||
|
||||
handle.time = handle.time + dt
|
||||
handle.during(dt, math.max(handle.limit - handle.time, 0))
|
||||
|
||||
while handle.time >= handle.limit and handle.count > 0 do
|
||||
if handle.after(handle.after) == false then
|
||||
handle.count = 0
|
||||
break
|
||||
end
|
||||
handle.time = handle.time - handle.limit
|
||||
handle.count = handle.count - 1
|
||||
end
|
||||
|
||||
if handle.count == 0 then
|
||||
table.insert(to_remove, handle)
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #to_remove do
|
||||
self.functions[to_remove[i]] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Timer:during(delay, during, after)
|
||||
local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 }
|
||||
self.functions[handle] = true
|
||||
return handle
|
||||
end
|
||||
|
||||
function Timer:after(delay, func)
|
||||
return self:during(delay, _nothing_, func)
|
||||
end
|
||||
|
||||
function Timer:every(delay, after, count)
|
||||
local count = count or math.huge -- exploit below: math.huge - 1 = math.huge
|
||||
local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count }
|
||||
self.functions[handle] = true
|
||||
return handle
|
||||
end
|
||||
|
||||
function Timer:cancel(handle)
|
||||
self.functions[handle] = nil
|
||||
end
|
||||
|
||||
function Timer:clear()
|
||||
self.functions = {}
|
||||
end
|
||||
|
||||
function Timer:script(f)
|
||||
local co = coroutine.wrap(f)
|
||||
co(function(t)
|
||||
self:after(t, co)
|
||||
coroutine.yield()
|
||||
end)
|
||||
end
|
||||
|
||||
Timer.tween = setmetatable({
|
||||
-- helper functions
|
||||
out = function(f) -- 'rotates' a function
|
||||
return function(s, ...) return 1 - f(1-s, ...) end
|
||||
end,
|
||||
chain = function(f1, f2) -- concatenates two functions
|
||||
return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end
|
||||
end,
|
||||
|
||||
-- useful tweening functions
|
||||
linear = function(s) return s end,
|
||||
quad = function(s) return s*s end,
|
||||
cubic = function(s) return s*s*s end,
|
||||
quart = function(s) return s*s*s*s end,
|
||||
quint = function(s) return s*s*s*s*s end,
|
||||
sine = function(s) return 1-math.cos(s*math.pi/2) end,
|
||||
expo = function(s) return 2^(10*(s-1)) end,
|
||||
circ = function(s) return 1 - math.sqrt(1-s*s) end,
|
||||
|
||||
back = function(s,bounciness)
|
||||
bounciness = bounciness or 1.70158
|
||||
return s*s*((bounciness+1)*s - bounciness)
|
||||
end,
|
||||
|
||||
bounce = function(s) -- magic numbers ahead
|
||||
local a,b = 7.5625, 1/2.75
|
||||
return math.min(a*s^2, a*(s-1.5*b)^2 + .75, a*(s-2.25*b)^2 + .9375, a*(s-2.625*b)^2 + .984375)
|
||||
end,
|
||||
|
||||
elastic = function(s, amp, period)
|
||||
amp, period = amp and math.max(1, amp) or 1, period or .3
|
||||
return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1))
|
||||
end,
|
||||
}, {
|
||||
|
||||
-- register new tween
|
||||
__call = function(tween, self, len, subject, target, method, after, ...)
|
||||
-- recursively collects fields that are defined in both subject and target into a flat list
|
||||
local function tween_collect_payload(subject, target, out)
|
||||
for k,v in pairs(target) do
|
||||
local ref = subject[k]
|
||||
assert(type(v) == type(ref), 'Type mismatch in field "'..k..'".')
|
||||
if type(v) == 'table' then
|
||||
tween_collect_payload(ref, v, out)
|
||||
else
|
||||
local ok, delta = pcall(function() return (v-ref)*1 end)
|
||||
assert(ok, 'Field "'..k..'" does not support arithmetic operations')
|
||||
out[#out+1] = {subject, k, delta}
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
method = tween[method or 'linear'] -- see __index
|
||||
local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...}
|
||||
|
||||
local last_s = 0
|
||||
return self:during(len, function(dt)
|
||||
t = t + dt
|
||||
local s = method(math.min(1, t/len), unpack(args))
|
||||
local ds = s - last_s
|
||||
last_s = s
|
||||
for _, info in ipairs(payload) do
|
||||
local ref, key, delta = unpack(info)
|
||||
ref[key] = ref[key] + delta * ds
|
||||
end
|
||||
end, after)
|
||||
end,
|
||||
|
||||
-- fetches function and generated compositions for method `key`
|
||||
__index = function(tweens, key)
|
||||
if type(key) == 'function' then return key end
|
||||
|
||||
assert(type(key) == 'string', 'Method must be function or string.')
|
||||
if rawget(tweens, key) then return rawget(tweens, key) end
|
||||
|
||||
local function construct(pattern, f)
|
||||
local method = rawget(tweens, key:match(pattern))
|
||||
if method then return f(method) end
|
||||
return nil
|
||||
end
|
||||
|
||||
local out, chain = rawget(tweens,'out'), rawget(tweens,'chain')
|
||||
return construct('^in%-([^-]+)$', function(...) return ... end)
|
||||
or construct('^out%-([^-]+)$', out)
|
||||
or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end)
|
||||
or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end)
|
||||
or error('Unknown interpolation method: ' .. key)
|
||||
end})
|
||||
|
||||
-- Timer instancing
|
||||
function Timer.new()
|
||||
return setmetatable({functions = {}, tween = Timer.tween}, Timer)
|
||||
end
|
||||
|
||||
-- default instance
|
||||
local default = Timer.new()
|
||||
|
||||
-- module forwards calls to default instance
|
||||
local module = {}
|
||||
for k in pairs(Timer) do
|
||||
if k ~= "__index" then
|
||||
module[k] = function(...) return default[k](default, ...) end
|
||||
end
|
||||
end
|
||||
module.tween = setmetatable({}, {
|
||||
__index = Timer.tween,
|
||||
__newindex = function(k,v) Timer.tween[k] = v end,
|
||||
__call = function(t, ...) return default:tween(...) end,
|
||||
})
|
||||
|
||||
return setmetatable(module, {__call = Timer.new})
|
||||
@@ -0,0 +1,172 @@
|
||||
--[[
|
||||
Copyright (c) 2012-2013 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, atan2 = math.sqrt, math.cos, math.sin, math.atan2
|
||||
|
||||
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 fromPolar(angle, radius)
|
||||
return cos(angle)*radius, sin(angle)*radius
|
||||
end
|
||||
|
||||
local function toPolar(x, y)
|
||||
return atan2(y,x), len(x,y)
|
||||
end
|
||||
|
||||
local function dist2(x1,y1, x2,y2)
|
||||
return len2(x1-x2, y1-y2)
|
||||
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)
|
||||
if l > 0 then
|
||||
return x/l, y/l
|
||||
end
|
||||
return x,y
|
||||
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
|
||||
|
||||
-- ref.: http://blog.signalsondisplay.com/?p=336
|
||||
local function trim(maxLen, x, y)
|
||||
local s = maxLen * maxLen / len2(x, y)
|
||||
s = s > 1 and 1 or math.sqrt(s)
|
||||
return x * s, y * s
|
||||
end
|
||||
|
||||
local function angleTo(x,y, u,v)
|
||||
if u and v then
|
||||
return atan2(y, x) - atan2(v, u)
|
||||
end
|
||||
return atan2(y, x)
|
||||
end
|
||||
|
||||
-- the module
|
||||
return {
|
||||
str = str,
|
||||
|
||||
fromPolar = fromPolar,
|
||||
toPolar = toPolar,
|
||||
|
||||
-- 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,
|
||||
dist2 = dist2,
|
||||
dist = dist,
|
||||
normalize = normalize,
|
||||
rotate = rotate,
|
||||
perpendicular = perpendicular,
|
||||
project = project,
|
||||
mirror = mirror,
|
||||
trim = trim,
|
||||
angleTo = angleTo,
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
--[[
|
||||
Copyright (c) 2010-2013 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 assert = assert
|
||||
local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2
|
||||
|
||||
local vector = {}
|
||||
vector.__index = vector
|
||||
|
||||
local function new(x,y)
|
||||
return setmetatable({x = x or 0, y = y or 0}, vector)
|
||||
end
|
||||
local zero = new(0,0)
|
||||
|
||||
local function fromPolar(angle, radius)
|
||||
return new(cos(angle) * radius, sin(angle) * radius)
|
||||
end
|
||||
|
||||
local function isvector(v)
|
||||
return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number'
|
||||
end
|
||||
|
||||
function vector:clone()
|
||||
return new(self.x, self.y)
|
||||
end
|
||||
|
||||
function vector:unpack()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
function vector:__tostring()
|
||||
return "("..tonumber(self.x)..","..tonumber(self.y)..")"
|
||||
end
|
||||
|
||||
function vector.__unm(a)
|
||||
return new(-a.x, -a.y)
|
||||
end
|
||||
|
||||
function vector.__add(a,b)
|
||||
assert(isvector(a) and isvector(b), "Add: wrong argument types (<vector> expected)")
|
||||
return new(a.x+b.x, a.y+b.y)
|
||||
end
|
||||
|
||||
function vector.__sub(a,b)
|
||||
assert(isvector(a) and isvector(b), "Sub: wrong argument types (<vector> expected)")
|
||||
return new(a.x-b.x, a.y-b.y)
|
||||
end
|
||||
|
||||
function vector.__mul(a,b)
|
||||
if type(a) == "number" then
|
||||
return new(a*b.x, a*b.y)
|
||||
elseif type(b) == "number" then
|
||||
return new(b*a.x, b*a.y)
|
||||
else
|
||||
assert(isvector(a) and isvector(b), "Mul: wrong argument types (<vector> or <number> expected)")
|
||||
return a.x*b.x + a.y*b.y
|
||||
end
|
||||
end
|
||||
|
||||
function vector.__div(a,b)
|
||||
assert(isvector(a) and type(b) == "number", "wrong argument types (expected <vector> / <number>)")
|
||||
return new(a.x / b, a.y / b)
|
||||
end
|
||||
|
||||
function vector.__eq(a,b)
|
||||
return a.x == b.x and a.y == b.y
|
||||
end
|
||||
|
||||
function vector.__lt(a,b)
|
||||
return a.x < b.x or (a.x == b.x and a.y < b.y)
|
||||
end
|
||||
|
||||
function vector.__le(a,b)
|
||||
return a.x <= b.x and a.y <= b.y
|
||||
end
|
||||
|
||||
function vector.permul(a,b)
|
||||
assert(isvector(a) and isvector(b), "permul: wrong argument types (<vector> expected)")
|
||||
return new(a.x*b.x, a.y*b.y)
|
||||
end
|
||||
|
||||
function vector:toPolar()
|
||||
return new(atan2(self.x, self.y), self:len())
|
||||
end
|
||||
|
||||
function vector:len2()
|
||||
return self.x * self.x + self.y * self.y
|
||||
end
|
||||
|
||||
function vector:len()
|
||||
return sqrt(self.x * self.x + self.y * self.y)
|
||||
end
|
||||
|
||||
function vector.dist(a, b)
|
||||
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
|
||||
local dx = a.x - b.x
|
||||
local dy = a.y - b.y
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
function vector.dist2(a, b)
|
||||
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
|
||||
local dx = a.x - b.x
|
||||
local dy = a.y - b.y
|
||||
return (dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
function vector:normalizeInplace()
|
||||
local l = self:len()
|
||||
if l > 0 then
|
||||
self.x, self.y = self.x / l, self.y / l
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function vector:normalized()
|
||||
return self:clone():normalizeInplace()
|
||||
end
|
||||
|
||||
function vector:rotateInplace(phi)
|
||||
local c, s = cos(phi), sin(phi)
|
||||
self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y
|
||||
return self
|
||||
end
|
||||
|
||||
function vector:rotated(phi)
|
||||
local c, s = cos(phi), sin(phi)
|
||||
return new(c * self.x - s * self.y, s * self.x + c * self.y)
|
||||
end
|
||||
|
||||
function vector:perpendicular()
|
||||
return new(-self.y, self.x)
|
||||
end
|
||||
|
||||
function vector:projectOn(v)
|
||||
assert(isvector(v), "invalid argument: cannot project vector on " .. type(v))
|
||||
-- (self * v) * v / v:len2()
|
||||
local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
|
||||
return new(s * v.x, s * v.y)
|
||||
end
|
||||
|
||||
function vector:mirrorOn(v)
|
||||
assert(isvector(v), "invalid argument: cannot mirror vector on " .. type(v))
|
||||
-- 2 * self:projectOn(v) - self
|
||||
local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
|
||||
return new(s * v.x - self.x, s * v.y - self.y)
|
||||
end
|
||||
|
||||
function vector:cross(v)
|
||||
assert(isvector(v), "cross: wrong argument types (<vector> expected)")
|
||||
return self.x * v.y - self.y * v.x
|
||||
end
|
||||
|
||||
-- ref.: http://blog.signalsondisplay.com/?p=336
|
||||
function vector:trimInplace(maxLen)
|
||||
local s = maxLen * maxLen / self:len2()
|
||||
s = (s > 1 and 1) or math.sqrt(s)
|
||||
self.x, self.y = self.x * s, self.y * s
|
||||
return self
|
||||
end
|
||||
|
||||
function vector:angleTo(other)
|
||||
if other then
|
||||
return atan2(self.y, self.x) - atan2(other.y, other.x)
|
||||
end
|
||||
return atan2(self.y, self.x)
|
||||
end
|
||||
|
||||
function vector:trimmed(maxLen)
|
||||
return self:clone():trimInplace(maxLen)
|
||||
end
|
||||
|
||||
|
||||
-- the module
|
||||
return setmetatable({new = new, fromPolar = fromPolar, isvector = isvector, zero = zero},
|
||||
{__call = function(_, ...) return new(...) end})
|
||||
@@ -0,0 +1,160 @@
|
||||
-- lue.lua v0.1
|
||||
|
||||
-- Copyright (c) 2017 Ulysse Ramage
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
local lue, lueObject = {
|
||||
hue = 0,
|
||||
intensity = 0,
|
||||
speed = 50,
|
||||
c = {}
|
||||
}, {}
|
||||
|
||||
setmetatable(lue, lue)
|
||||
|
||||
--[[ Private ]]--
|
||||
|
||||
local function lerp(a, b, k) --smooth transitions
|
||||
if a == b then
|
||||
return a
|
||||
else
|
||||
if math.abs(a-b) < 0.005 then return b else return a * (1-k) + b * k end
|
||||
end
|
||||
end
|
||||
|
||||
--by Taehl https://love2d.org/wiki/HSL_color
|
||||
--where the magic happens
|
||||
local function HSL(h, s, l, a)
|
||||
if s<=0 then return l,l,l,a end
|
||||
h, s, l = (h%255)/256*6, s/255, l/255
|
||||
local c = (1-math.abs(2*l-1))*s
|
||||
local x = (1-math.abs(h%2-1))*c
|
||||
local m,r,g,b = (l-.5*c), 0,0,0
|
||||
if h < 1 then r,g,b = c,x,0
|
||||
elseif h < 2 then r,g,b = x,c,0
|
||||
elseif h < 3 then r,g,b = 0,c,x
|
||||
elseif h < 4 then r,g,b = 0,x,c
|
||||
elseif h < 5 then r,g,b = x,0,c
|
||||
else r,g,b = c,0,x
|
||||
end return {(r+m)*255, (g+m)*255, (b+m)*255, a}
|
||||
end
|
||||
|
||||
--[[ Public ]]--
|
||||
|
||||
function lue:update(dt)
|
||||
|
||||
self.hue = self.hue + self.speed * dt
|
||||
if self.hue > 255 then
|
||||
self.hue = self.hue - 255
|
||||
elseif self.hue < 0 then
|
||||
self.hue = self.hue + 255
|
||||
end
|
||||
|
||||
for k, v in pairs(self.c) do
|
||||
v:update(dt)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function lueObject:update(dt)
|
||||
|
||||
if self.target then
|
||||
local type, target = self.target.type, self.target.color
|
||||
if type == "hue" then
|
||||
target = lue:getHueColor(unpack(target))
|
||||
end
|
||||
for i = 1, 4 do
|
||||
self.color[i] = lerp(self.color[i] or 255, target[i] or 255, self.speed * dt)
|
||||
end
|
||||
elseif self.hue then
|
||||
self.color = lue:getHueColor(unpack(self.hue))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
function lue:initColor(name, f)
|
||||
|
||||
if not self.c[name] then self.c[name] = lue:newColor(f) end
|
||||
|
||||
end
|
||||
|
||||
function lue:newColor(f)
|
||||
|
||||
local _object = {}
|
||||
|
||||
setmetatable(_object, { __index = lueObject })
|
||||
|
||||
return _object
|
||||
end
|
||||
|
||||
function lue:setColor(name, f)
|
||||
|
||||
self:initColor(name)
|
||||
|
||||
return self.c[name]:setColor(f)
|
||||
|
||||
end
|
||||
|
||||
function lueObject:setColor(f)
|
||||
|
||||
if not (f.color or f.speed or f.hue) then f = { color = f } end --mind blown
|
||||
|
||||
f.speed = f.speed and (f.speed == true and 1 or f.speed) or false
|
||||
|
||||
if f.speed and self.color then
|
||||
self.target = { type = f.hue and "hue" or "color", color = f.color or f.hue }
|
||||
self.speed = f.speed
|
||||
else
|
||||
self.color = f.color or lue:getHueColor(unpack(f.hue))
|
||||
if f.hue then self.hue = f.hue end
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function lue:getColor(name, target)
|
||||
|
||||
return self.c[name] and self.c[name]:getColor(target) or false
|
||||
|
||||
end
|
||||
|
||||
function lueObject:getColor(target)
|
||||
|
||||
local color = ((target and self.target) and self.target or self.color)
|
||||
return color and {color[1], color[2], color[3], color[4] or 255} or false
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
function lue:setHueIntensity(intensity)
|
||||
self.intensity = intensity
|
||||
return self
|
||||
end
|
||||
|
||||
function lue:setHueSpeed(speed)
|
||||
self.speed = speed
|
||||
return self
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
function lue:getHueColor(s, l, a, offset)
|
||||
local _s, _l = math.min(math.max(0, s+self.intensity), 255), math.min(math.max(0, l+self.intensity), 255)
|
||||
return HSL(self.hue + (offset or 0), _s, _l, a)
|
||||
end
|
||||
|
||||
function lue:getHueIntensity() return self.intensity end
|
||||
|
||||
function lue:getHueSpeed() return self.speed end
|
||||
|
||||
function lue:getHue() return self.hue end
|
||||
|
||||
--[[ End ]]--
|
||||
|
||||
return lue
|
||||
@@ -0,0 +1,170 @@
|
||||
local middleclass = {
|
||||
_VERSION = 'middleclass v4.1.0',
|
||||
_DESCRIPTION = 'Object Orientation for Lua',
|
||||
_URL = 'https://github.com/kikito/middleclass',
|
||||
_LICENSE = [[
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2011 Enrique García Cota
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
}
|
||||
|
||||
local function _createIndexWrapper(aClass, f)
|
||||
if f == nil then
|
||||
return aClass.__instanceDict
|
||||
else
|
||||
return function(self, name)
|
||||
local value = aClass.__instanceDict[name]
|
||||
|
||||
if value ~= nil then
|
||||
return value
|
||||
elseif type(f) == "function" then
|
||||
return (f(self, name))
|
||||
else
|
||||
return f[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _propagateInstanceMethod(aClass, name, f)
|
||||
f = name == "__index" and _createIndexWrapper(aClass, f) or f
|
||||
aClass.__instanceDict[name] = f
|
||||
|
||||
for subclass in pairs(aClass.subclasses) do
|
||||
if rawget(subclass.__declaredMethods, name) == nil then
|
||||
_propagateInstanceMethod(subclass, name, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _declareInstanceMethod(aClass, name, f)
|
||||
aClass.__declaredMethods[name] = f
|
||||
|
||||
if f == nil and aClass.super then
|
||||
f = aClass.super.__instanceDict[name]
|
||||
end
|
||||
|
||||
_propagateInstanceMethod(aClass, name, f)
|
||||
end
|
||||
|
||||
local function _tostring(self) return "class " .. self.name end
|
||||
local function _call(self, ...) return self:new(...) end
|
||||
|
||||
local function _createClass(name, super)
|
||||
local dict = {}
|
||||
dict.__index = dict
|
||||
|
||||
local aClass = { name = name, super = super, static = {},
|
||||
__instanceDict = dict, __declaredMethods = {},
|
||||
subclasses = setmetatable({}, {__mode='k'}) }
|
||||
|
||||
if super then
|
||||
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end })
|
||||
else
|
||||
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
|
||||
end
|
||||
|
||||
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
|
||||
__call = _call, __newindex = _declareInstanceMethod })
|
||||
|
||||
return aClass
|
||||
end
|
||||
|
||||
local function _includeMixin(aClass, mixin)
|
||||
assert(type(mixin) == 'table', "mixin must be a table")
|
||||
|
||||
for name,method in pairs(mixin) do
|
||||
if name ~= "included" and name ~= "static" then aClass[name] = method end
|
||||
end
|
||||
|
||||
for name,method in pairs(mixin.static or {}) do
|
||||
aClass.static[name] = method
|
||||
end
|
||||
|
||||
if type(mixin.included)=="function" then mixin:included(aClass) end
|
||||
return aClass
|
||||
end
|
||||
|
||||
local DefaultMixin = {
|
||||
__tostring = function(self) return "instance of " .. tostring(self.class) end,
|
||||
|
||||
initialize = function(self, ...) end,
|
||||
|
||||
isInstanceOf = function(self, aClass)
|
||||
return type(aClass) == 'table' and (aClass == self.class or self.class:isSubclassOf(aClass))
|
||||
end,
|
||||
|
||||
static = {
|
||||
allocate = function(self)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
||||
return setmetatable({ class = self }, self.__instanceDict)
|
||||
end,
|
||||
|
||||
new = function(self, ...)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
|
||||
local instance = self:allocate()
|
||||
instance:initialize(...)
|
||||
return instance
|
||||
end,
|
||||
|
||||
subclass = function(self, name)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
|
||||
assert(type(name) == "string", "You must provide a name(string) for your class")
|
||||
|
||||
local subclass = _createClass(name, self)
|
||||
|
||||
for methodName, f in pairs(self.__instanceDict) do
|
||||
_propagateInstanceMethod(subclass, methodName, f)
|
||||
end
|
||||
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
|
||||
|
||||
self.subclasses[subclass] = true
|
||||
self:subclassed(subclass)
|
||||
|
||||
return subclass
|
||||
end,
|
||||
|
||||
subclassed = function(self, other) end,
|
||||
|
||||
isSubclassOf = function(self, other)
|
||||
return type(other) == 'table' and
|
||||
type(self.super) == 'table' and
|
||||
( self.super == other or self.super:isSubclassOf(other) )
|
||||
end,
|
||||
|
||||
include = function(self, ...)
|
||||
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
|
||||
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
|
||||
return self
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
function middleclass.class(name, super)
|
||||
assert(type(name) == 'string', "A name (string) is needed for the new class")
|
||||
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
|
||||
end
|
||||
|
||||
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
|
||||
|
||||
return middleclass
|
||||