Commit d7d5e30f authored by Carlo's avatar Carlo

Added assets, hump, and main.lua

parent 9a3cfe89
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.
--[[
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})
--[[
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})
# 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."
(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);
})();
This diff is collapsed.
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
# -*- 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()]
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
**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`
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.
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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
--[[
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
--[[
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})
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)
--[[
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})
--[[
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,
}
--[[
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})
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment