├── .travis.yml ├── README.md ├── camera.lua ├── class.lua ├── docs ├── Makefile ├── _static │ ├── graph-tweens.js │ ├── in-out-interpolators.png │ ├── interpolators.png │ ├── inv-interpolators.png │ ├── vector-cross.png │ ├── vector-mirrorOn.png │ ├── vector-perpendicular.png │ ├── vector-projectOn.png │ └── vector-rotated.png ├── camera.rst ├── class.rst ├── conf.py ├── gamestate.rst ├── index.rst ├── license.rst ├── signal.rst ├── timer.rst ├── vector-light.rst └── vector.rst ├── gamestate.lua ├── hump-0.4-2.rockspec ├── signal.lua ├── spec └── timer_spec.lua ├── timer.lua ├── vector-light.lua └── vector.lua /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: precise 2 | sudo: false # Use container-based infrastructure 3 | language: python 4 | 5 | git: 6 | depth: 4 7 | 8 | #Testing with lastest stable version of LUAJIT 9 | env: 10 | - LUA_RUNTIME="luajit" 11 | LUA_VERSION="2.0.5" 12 | #- LUA_RUNTIME="luajit" 13 | # LUA_VERSION="2.1" 14 | 15 | before_install: 16 | - pip install hererocks 17 | - hererocks lua_installations/$LUA_RUNTIME$LUA_VERSION/ --luarocks ^ --$LUA_RUNTIME $LUA_VERSION 18 | - export PATH=$PATH:$PWD/lua_installations/$LUA_RUNTIME$LUA_VERSION/bin 19 | 20 | before_script: 21 | - luarocks install busted 22 | - busted --version 23 | 24 | script: 25 | - busted --output=TAP #default utfTerminal 26 | 27 | #Checks if Luajit and luarocks are already installed 28 | #Comment out to reinstall or if changing versions 29 | cache: 30 | directories: 31 | - $PWD/lua_installations 32 | 33 | # Can add email notification here 34 | #notifications: 35 | #email: 36 | #recipients: 37 | #- email@here.com 38 | #on_failure: always #default always 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hump - Helper Utilities for Massive Progression 2 | =============================================== 3 | 4 | **hump** is a small collection of tools for developing games with LÖVE. 5 | [![Build Status](https://travis-ci.org/vrld/hump.svg?branch=master)](https://travis-ci.org/vrld/hump) 6 | 7 | Contents: 8 | ------------ 9 | 10 | * *gamestate.lua*: Easy gamestate management. 11 | * *timer.lua*: Delayed and time-limited function calls and tweening. 12 | * *vector.lua*: 2D vector math. 13 | * *vector-light.lua*: Lightweight 2D vector math (for optimisation purposes - leads to potentially ugly code). 14 | * *class.lua*: Lightweight object orientation (class or prototype based). 15 | * *signal.lua*: Simple Signal/Slot (aka. Observer) implementation. 16 | * *camera.lua*: Move-, zoom- and rotatable camera with camera locking and movement smoothing. 17 | 18 | Documentation 19 | ============= 20 | 21 | You can find the documentation here: [hump.readthedocs.org](http://hump.readthedocs.org) 22 | 23 | 24 | License 25 | ======= 26 | > Copyright (c) 2010-2018 Matthias Richter 27 | > 28 | > Permission is hereby granted, free of charge, to any person obtaining a copy 29 | > of this software and associated documentation files (the "Software"), to deal 30 | > in the Software without restriction, including without limitation the rights 31 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 32 | > copies of the Software, and to permit persons to whom the Software is 33 | > furnished to do so, subject to the following conditions: 34 | > 35 | > The above copyright notice and this permission notice shall be included in 36 | > all copies or substantial portions of the Software. 37 | > 38 | > Except as contained in this notice, the name(s) of the above copyright holders 39 | > shall not be used in advertising or otherwise to promote the sale, use or 40 | > other dealings in this Software without prior written authorization. 41 | > 42 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | > THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /camera.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2015 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local _PATH = (...):match('^(.*[%./])[^%.%/]+$') or '' 28 | local cos, sin = math.cos, math.sin 29 | 30 | local camera = {} 31 | camera.__index = camera 32 | 33 | -- Movement interpolators (for camera locking/windowing) 34 | camera.smooth = {} 35 | 36 | function camera.smooth.none() 37 | return function(dx,dy) return dx,dy end 38 | end 39 | 40 | function camera.smooth.linear(speed) 41 | assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed)) 42 | return function(dx,dy, s) 43 | -- normalize direction 44 | local d = math.sqrt(dx*dx+dy*dy) 45 | local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal 46 | if d > 0 then 47 | dx,dy = dx/d, dy/d 48 | end 49 | 50 | return dx*dts, dy*dts 51 | end 52 | end 53 | 54 | function camera.smooth.damped(stiffness) 55 | assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness)) 56 | return function(dx,dy, s) 57 | local dts = love.timer.getDelta() * (s or stiffness) 58 | return dx*dts, dy*dts 59 | end 60 | end 61 | 62 | 63 | local function new(x,y, zoom, rot, smoother) 64 | x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2 65 | zoom = zoom or 1 66 | rot = rot or 0 67 | smoother = smoother or camera.smooth.none() -- for locking, see below 68 | return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera) 69 | end 70 | 71 | function camera:lookAt(x,y) 72 | self.x, self.y = x, y 73 | return self 74 | end 75 | 76 | function camera:move(dx,dy) 77 | self.x, self.y = self.x + dx, self.y + dy 78 | return self 79 | end 80 | 81 | function camera:position() 82 | return self.x, self.y 83 | end 84 | 85 | function camera:rotate(phi) 86 | self.rot = self.rot + phi 87 | return self 88 | end 89 | 90 | function camera:rotateTo(phi) 91 | self.rot = phi 92 | return self 93 | end 94 | 95 | function camera:zoom(mul) 96 | self.scale = self.scale * mul 97 | return self 98 | end 99 | 100 | function camera:zoomTo(zoom) 101 | self.scale = zoom 102 | return self 103 | end 104 | 105 | function camera:attach(x,y,w,h, noclip) 106 | x,y = x or 0, y or 0 107 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 108 | 109 | self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor() 110 | if not noclip then 111 | love.graphics.setScissor(x,y,w,h) 112 | end 113 | 114 | local cx,cy = x+w/2, y+h/2 115 | love.graphics.push() 116 | love.graphics.translate(cx, cy) 117 | love.graphics.scale(self.scale) 118 | love.graphics.rotate(self.rot) 119 | love.graphics.translate(-self.x, -self.y) 120 | end 121 | 122 | function camera:detach() 123 | love.graphics.pop() 124 | love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh) 125 | end 126 | 127 | function camera:draw(...) 128 | local x,y,w,h,noclip,func 129 | local nargs = select("#", ...) 130 | if nargs == 1 then 131 | func = ... 132 | elseif nargs == 5 then 133 | x,y,w,h,func = ... 134 | elseif nargs == 6 then 135 | x,y,w,h,noclip,func = ... 136 | else 137 | error("Invalid arguments to camera:draw()") 138 | end 139 | 140 | self:attach(x,y,w,h,noclip) 141 | func() 142 | self:detach() 143 | end 144 | 145 | -- world coordinates to camera coordinates 146 | function camera:cameraCoords(x,y, ox,oy,w,h) 147 | ox, oy = ox or 0, oy or 0 148 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 149 | 150 | -- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center 151 | local c,s = cos(self.rot), sin(self.rot) 152 | x,y = x - self.x, y - self.y 153 | x,y = c*x - s*y, s*x + c*y 154 | return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy 155 | end 156 | 157 | -- camera coordinates to world coordinates 158 | function camera:worldCoords(x,y, ox,oy,w,h) 159 | ox, oy = ox or 0, oy or 0 160 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 161 | 162 | -- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y) 163 | local c,s = cos(-self.rot), sin(-self.rot) 164 | x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale 165 | x,y = c*x - s*y, s*x + c*y 166 | return x+self.x, y+self.y 167 | end 168 | 169 | function camera:mousePosition(ox,oy,w,h) 170 | local mx,my = love.mouse.getPosition() 171 | return self:worldCoords(mx,my, ox,oy,w,h) 172 | end 173 | 174 | -- camera scrolling utilities 175 | function camera:lockX(x, smoother, ...) 176 | local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...) 177 | self.x = self.x + dx 178 | return self 179 | end 180 | 181 | function camera:lockY(y, smoother, ...) 182 | local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...) 183 | self.y = self.y + dy 184 | return self 185 | end 186 | 187 | function camera:lockPosition(x,y, smoother, ...) 188 | return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...)) 189 | end 190 | 191 | function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...) 192 | -- figure out displacement in camera coordinates 193 | x,y = self:cameraCoords(x,y) 194 | local dx, dy = 0,0 195 | if x < x_min then 196 | dx = x - x_min 197 | elseif x > x_max then 198 | dx = x - x_max 199 | end 200 | if y < y_min then 201 | dy = y - y_min 202 | elseif y > y_max then 203 | dy = y - y_max 204 | end 205 | 206 | -- transform displacement to movement in world coordinates 207 | local c,s = cos(-self.rot), sin(-self.rot) 208 | dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale 209 | 210 | -- move 211 | self:move((smoother or self.smoother)(dx,dy,...)) 212 | end 213 | 214 | -- the module 215 | return setmetatable({new = new, smooth = camera.smooth}, 216 | {__call = function(_, ...) return new(...) end}) 217 | -------------------------------------------------------------------------------- /class.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local function include_helper(to, from, seen) 28 | if from == nil then 29 | return to 30 | elseif type(from) ~= 'table' then 31 | return from 32 | elseif seen[from] then 33 | return seen[from] 34 | end 35 | 36 | seen[from] = to 37 | for k,v in pairs(from) do 38 | k = include_helper({}, k, seen) -- keys might also be tables 39 | if to[k] == nil then 40 | to[k] = include_helper({}, v, seen) 41 | end 42 | end 43 | return to 44 | end 45 | 46 | -- deeply copies `other' into `class'. keys in `other' that are already 47 | -- defined in `class' are omitted 48 | local function include(class, other) 49 | return include_helper(class, other, {}) 50 | end 51 | 52 | -- returns a deep copy of `other' 53 | local function clone(other) 54 | return setmetatable(include({}, other), getmetatable(other)) 55 | end 56 | 57 | local function new(class) 58 | -- mixins 59 | class = class or {} -- class can be nil 60 | local inc = class.__includes or {} 61 | if getmetatable(inc) then inc = {inc} end 62 | 63 | for _, other in ipairs(inc) do 64 | if type(other) == "string" then 65 | other = _G[other] 66 | end 67 | include(class, other) 68 | end 69 | 70 | -- class implementation 71 | class.__index = class 72 | class.init = class.init or class[1] or function() end 73 | class.include = class.include or include 74 | class.clone = class.clone or clone 75 | 76 | -- constructor call 77 | return setmetatable(class, {__call = function(c, ...) 78 | local o = setmetatable({}, c) 79 | o:init(...) 80 | return o 81 | end}) 82 | end 83 | 84 | -- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons). 85 | if class_commons ~= false and not common then 86 | common = {} 87 | function common.class(name, prototype, parent) 88 | return new{__includes = {prototype, parent}} 89 | end 90 | function common.instance(class, ...) 91 | return class(...) 92 | end 93 | end 94 | 95 | 96 | -- the module 97 | return setmetatable({new = new, include = include, clone = clone}, 98 | {__call = function(_,...) return new(...) end}) 99 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(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/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hump.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hump.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/hump" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hump" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_static/graph-tweens.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | 4 | // DISCLAIMER: I just started learning d3, so this is certainly not good 5 | // idiomatic d3 code. But hey, it works (kinda). 6 | 7 | var tweens = { 8 | 'out': function(f) { return function(s) { return 1-f(1-s) } }, 9 | 'chain': function(f1, f2) { return function(s) { return ((s<.5) ? f1(2*s) : 1+f2(2*s-1)) * .5 } }, 10 | 'linear': function(s) { return s }, 11 | 'quad': function(s) { return s*s }, 12 | 'cubic': function(s) { return s*s*s }, 13 | 'quart': function(s) { return s*s*s*s }, 14 | 'quint': function(s) { return s*s*s*s*s }, 15 | 'sine': function(s) { return 1 - Math.cos(s*Math.PI/2) }, 16 | 'expo': function(s) { return Math.pow(2, 10*(s-1)) }, 17 | 'circ': function(s) { return 1 - Math.sqrt(Math.max(0,1-s*s)) }, 18 | 'back': function(s) { var b = 1.70158; return s*s*((b+1)*s - b) }, 19 | 'bounce': function(s) { 20 | return Math.min( 21 | 7.5625 * Math.pow(s, 2), 22 | 7.5625 * Math.pow((s - .545455), 2) + .75, 23 | 7.5625 * Math.pow((s - .818182), 2) + .90375, 24 | 7.5625 * Math.pow((s - .954546), 2) + .984375) 25 | }, 26 | 'elastic': function(s) { 27 | return -Math.sin(2/0.3 * Math.PI * (s-1) - Math.asin(1)) * Math.pow(2, 10*(s-1)) 28 | }, 29 | }; 30 | var tweenfunc = tweens.linear; 31 | 32 | 33 | var width_graph = 320, 34 | width_anim_move = 110, 35 | width_anim_rotate = 110, 36 | width_anim_size = 110, 37 | height = 250; 38 | 39 | // "UI" 40 | var graph_ui = d3.select("#tween-graph").append("div") 41 | .attr("id", "tween-graph-ui"); 42 | // rest see below 43 | 44 | // the graph 45 | var graph = d3.select("#tween-graph").append("svg") 46 | .attr("width", width_graph).attr("height", height); 47 | 48 | // background 49 | graph.append("rect") 50 | .attr("width", "100%").attr("height", "100%") 51 | .attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);"); 52 | 53 | var y_zero = height * .78, y_one = height * .22; 54 | graph.append("rect") 55 | .attr("y", y_one) 56 | .attr("width", "100%").attr("height", y_zero - y_one) 57 | .attr("style", "fill:steelblue;fill-opacity:.3;stroke-width:1;stroke:rgba(100,100,100,.7)"); 58 | 59 | // time arrow 60 | graph.append("defs") 61 | .append("marker") 62 | .attr("id", "triangle") 63 | .attr("viewBox", "0 0 10 10") 64 | .attr("refX", 1).attr("refY", 5) 65 | .attr("markerWidth", 4) 66 | .attr("markerHeight", 4) 67 | .attr("orient", "auto") 68 | .attr("style", "fill:rgba(0,0,0,.5)") 69 | .append("path").attr("d", "M 0 0 L 10 5 L 0 10 z"); 70 | 71 | graph.append("line") 72 | .attr("x1", width_graph/2-80) 73 | .attr("x2", width_graph/2+80) 74 | .attr("y1", y_zero + 40).attr("y2", y_zero + 40) 75 | .attr("style", "stroke-width:2;stroke:rgba(0,0,0,.5)") 76 | .attr("marker-end", "url(#triangle)"); 77 | 78 | graph.append("text") 79 | .text("Time") 80 | .attr("x", width_graph/2).attr("y", y_zero + 55) 81 | .attr("style", "text-anchor:middle;fill:rgba(0,0,0,.5);font-size:15px"); 82 | 83 | // the actual graph 84 | var curve = d3.svg.line() 85 | .x(function(x) { return x*width_graph; }) 86 | .y(function(x) { return tweenfunc(x) * (y_one - y_zero) + y_zero; }) 87 | 88 | var graph_curve = graph.append("path").attr("d", curve(d3.range(0,1.05,.005))) 89 | .attr("style", "fill:none;stroke-width:2;stroke:seagreen;"); 90 | 91 | var graph_marker = graph.append("circle") 92 | .attr("r", 5) 93 | .attr("style", "stroke:goldenrod;fill:none;stroke-width:3"); 94 | 95 | // finally, a label 96 | var graph_label = graph.append("text") 97 | .text("linear") 98 | .attr("x", width_graph/2).attr("y", 20) 99 | .attr("style", "text-anchor:middle;font-weight:bold;font-size:15px;"); 100 | 101 | 102 | // animation examples - moving ball 103 | var anim_move = d3.select("#tween-graph").append("svg") 104 | .attr("width", width_anim_move).attr("height", height); 105 | 106 | anim_move.append("rect") 107 | .attr("width", "100%").attr("height", "100%") 108 | .attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);"); 109 | 110 | anim_move.append("rect") 111 | .attr("width", 10).attr("height", (y_zero - y_one)) 112 | .attr("x", width_anim_move/2-5).attr("y", y_one) 113 | .attr("style", "fill:black;opacity:.1"); 114 | 115 | var anim_move_ball = anim_move.append("circle") 116 | .attr("cx", width_anim_move/2).attr("cy", y_one) 117 | .attr("r", 17) 118 | .attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;"); 119 | 120 | // animation examples - rotating square 121 | var anim_rotate = d3.select("#tween-graph").append("svg") 122 | .attr("width", width_anim_size).attr("height", height); 123 | 124 | anim_rotate.append("rect") 125 | .attr("width", "100%").attr("height", "100%") 126 | .attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);"); 127 | 128 | var w = width_anim_size/2; 129 | var anim_rotate_square = anim_rotate.append("rect") 130 | .attr("x", -w/2).attr("y", -w/4) 131 | .attr("width", w).attr("height", w/2) 132 | .attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;"); 133 | 134 | // animation examples - resizing ellipse 135 | var anim_size = d3.select("#tween-graph").append("svg") 136 | .attr("width", width_anim_size).attr("height", height); 137 | 138 | anim_size.append("rect") 139 | .attr("width", "100%").attr("height", "100%") 140 | .attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);"); 141 | 142 | anim_size.append("ellipse") 143 | .attr("cx", width_anim_size/2).attr("cy", height/2) 144 | .attr("rx", 40).attr("ry", 120) 145 | .attr("style", "fill:rgb(150,150,150);stroke:black;stroke-width:2;opacity:.1"); 146 | 147 | var anim_size_ellipse = anim_size.append("ellipse") 148 | .attr("cx", width_anim_size/2).attr("cy", height/2) 149 | .attr("rx", 40).attr("ry", 40) 150 | .attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;"); 151 | 152 | 153 | // make it move! 154 | var t = 0; 155 | window.setInterval(function() { 156 | t = (t + .025 / 3); 157 | if (t > 1.3) { t = -.3; } 158 | var tt = Math.max(Math.min(t, 1), 0); 159 | 160 | var s = tweenfunc(tt) 161 | var yy = s * (y_one - y_zero) + y_zero; 162 | var translate = "translate("+(width_anim_size/2)+" "+(height/2)+")"; 163 | var rotate = "rotate(" + (s * 360) + ")"; 164 | 165 | graph_marker.attr("cx", tt*width_graph).attr("cy", yy); 166 | anim_move_ball.attr("cy", y_one + y_zero - yy); 167 | anim_rotate_square.attr("transform", translate + " " + rotate); 168 | anim_size_ellipse.attr("ry", s * 80 + 40); 169 | }, 25); 170 | 171 | 172 | // ui continued 173 | graph_ui.append("strong").text("Function: "); 174 | var select_modifier = graph_ui.append("select"); 175 | select_modifier.append("option").text("in"); 176 | select_modifier.append("option").text("out"); 177 | select_modifier.append("option").text("in-out"); 178 | select_modifier.append("option").text("out-in"); 179 | graph_ui.append("strong").text("-") 180 | 181 | var select_func = graph_ui.append("select") 182 | var funcs = []; 183 | for (var k in tweens) 184 | { 185 | if (k != "out" && k != "chain") 186 | { 187 | select_func.append("option").text(k); 188 | funcs.push(k); 189 | } 190 | } 191 | 192 | var change_tweenfunc = function() 193 | { 194 | var fname = funcs[select_func.node().selectedIndex]; 195 | var mod = select_modifier.node().selectedIndex; 196 | 197 | tweenfunc = tweens[fname]; 198 | if (mod == 1) // out 199 | tweenfunc = tweens.out(tweenfunc); 200 | else if (mod == 2) // in-out 201 | tweenfunc = tweens.chain(tweenfunc, tweens.out(tweenfunc)); 202 | else if (mod == 3) // out-in 203 | tweenfunc = tweens.chain(tweens.out(tweenfunc), tweenfunc); 204 | 205 | // update curve 206 | graph_curve.attr("d", curve(d3.range(0,1.05,.005))) 207 | 208 | // update label 209 | if (mod != 0) 210 | graph_label.text((['in','out','in-out','out-in'])[mod] + "-" + fname); 211 | else 212 | graph_label.text(fname); 213 | } 214 | 215 | select_func.on("change", change_tweenfunc); 216 | select_modifier.on("change", change_tweenfunc); 217 | 218 | })(); 219 | -------------------------------------------------------------------------------- /docs/_static/in-out-interpolators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/in-out-interpolators.png -------------------------------------------------------------------------------- /docs/_static/interpolators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/interpolators.png -------------------------------------------------------------------------------- /docs/_static/inv-interpolators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/inv-interpolators.png -------------------------------------------------------------------------------- /docs/_static/vector-cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/vector-cross.png -------------------------------------------------------------------------------- /docs/_static/vector-mirrorOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/vector-mirrorOn.png -------------------------------------------------------------------------------- /docs/_static/vector-perpendicular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/vector-perpendicular.png -------------------------------------------------------------------------------- /docs/_static/vector-projectOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/vector-projectOn.png -------------------------------------------------------------------------------- /docs/_static/vector-rotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrld/hump/08937cc0ecf72d1a964a8de6cd552c5e136bf0d4/docs/_static/vector-rotated.png -------------------------------------------------------------------------------- /docs/camera.rst: -------------------------------------------------------------------------------- 1 | hump.camera 2 | =========== 3 | 4 | :: 5 | 6 | Camera = require "hump.camera" 7 | 8 | A camera utility for LÖVE. A camera can "look" at a position. It can zoom in 9 | and out and it can rotate it's view. In the background, this is done by 10 | actually moving, scaling and rotating everything in the game world. But don't 11 | worry about that. 12 | 13 | **Example**:: 14 | 15 | function love.load() 16 | camera = Camera(player.pos.x, player.pos.y) 17 | end 18 | 19 | function love.update(dt) 20 | local dx,dy = player.x - camera.x, player.y - camera.y 21 | camera:move(dx/2, dy/2) 22 | end 23 | 24 | function love.draw() 25 | camera:attach() 26 | -- do your drawing here 27 | camera:detach() 28 | end 29 | 30 | List of Functions 31 | ----------------- 32 | 33 | * :func:`Camera.new(x,y, zoom, rot) ` 34 | * :func:`camera:move(dx,dy) ` 35 | * :func:`camera:lookAt(x,y) ` 36 | * :func:`camera:position() ` 37 | * :func:`camera:rotate(angle) ` 38 | * :func:`camera:rotateTo(angle) ` 39 | * :func:`camera:zoom(mul) ` 40 | * :func:`camera:zoomTo(zoom) ` 41 | * :func:`camera:attach() ` 42 | * :func:`camera:detach() ` 43 | * :func:`camera:draw(func) ` 44 | * :func:`camera:worldCoords(x, y) ` 45 | * :func:`camera:cameraCoords(x, y) ` 46 | * :func:`camera:mousePosition() ` 47 | * :func:`camera:lockX(x, smoother, ...) ` 48 | * :func:`camera:lockY(y, smoother, ...) ` 49 | * :func:`camera:lockPosition(x,y, smoother, ...) ` 50 | * :func:`camera:lockWindow(x,y, x_min, x_max, y_min, y_max, smoother, ...) ` 51 | * :func:`Camera.smooth.none() ` 52 | * :func:`Camera.smooth.linear(speed) ` 53 | * :func:`Camera.smooth.damped(stiffness) ` 54 | 55 | Function Reference 56 | ------------------ 57 | 58 | .. function:: Camera.new(x,y, zoom, rot) 59 | 60 | :param numbers x,y: Point for the camera to look at. (optional) 61 | :param number zoom: Camera zoom. (optional) 62 | :param number rot: Camera rotation in radians. (optional) 63 | :returns: A new camera. 64 | 65 | 66 | Creates a new camera. You can access the camera position using ``camera.x, 67 | camera.y``, the zoom using ``camera.scale`` and the rotation using ``camera.rot``. 68 | 69 | The module variable name can be used at a shortcut to ``new()``. 70 | 71 | **Example**:: 72 | 73 | camera = require 'hump.camera' 74 | -- camera looking at (100,100) with zoom 2 and rotated by 45 degrees 75 | cam = camera(100,100, 2, math.pi/2) 76 | 77 | 78 | .. function:: camera:move(dx,dy) 79 | 80 | :param numbers dx,dy: Direction to move the camera. 81 | :returns: The camera. 82 | 83 | 84 | Move the camera *by* some vector. To set the position, use 85 | :func:`camera:lookAt`. 86 | 87 | This function is shortcut to ``camera.x,camera.y = camera.x+dx, camera.y+dy``. 88 | 89 | **Examples**:: 90 | 91 | function love.update(dt) 92 | camera:move(dt * 5, dt * 6) 93 | end 94 | 95 | :: 96 | 97 | function love.update(dt) 98 | camera:move(dt * 5, dt * 6):rotate(dt) 99 | end 100 | 101 | 102 | .. function:: camera:lookAt(x,y) 103 | 104 | :param numbers x,y: Position to look at. 105 | :returns: The camera. 106 | 107 | 108 | Let the camera look at a point. In other words, it sets the camera position. To 109 | move the camera *by* some amount, use :func:`camera:move`. 110 | 111 | This function is shortcut to ``camera.x,camera.y = x, y``. 112 | 113 | **Examples**:: 114 | 115 | function love.update(dt) 116 | camera:lookAt(player.pos:unpack()) 117 | end 118 | 119 | :: 120 | 121 | function love.update(dt) 122 | camera:lookAt(player.pos:unpack()):rotate(player.rot) 123 | end 124 | 125 | .. function:: camera:position() 126 | 127 | :returns: ``x,y`` -- Camera position. 128 | 129 | 130 | Returns ``camera.x, camera.y``. 131 | 132 | **Example**:: 133 | 134 | -- let the camera fly! 135 | local cam_dx, cam_dy = 0, 0 136 | 137 | function love.mousereleased(x,y) 138 | local cx,cy = camera:position() 139 | dx, dy = x-cx, y-cy 140 | end 141 | 142 | function love.update(dt) 143 | camera:move(dx * dt, dy * dt) 144 | end 145 | 146 | 147 | .. function:: camera:rotate(angle) 148 | 149 | :param number angle: Rotation angle in radians 150 | :returns: The camera. 151 | 152 | 153 | Rotate the camera by some angle. To set the angle use :func:`camera:rotateTo`. 154 | 155 | This function is shortcut to ``camera.rot = camera.rot + angle``. 156 | 157 | **Examples**:: 158 | 159 | function love.update(dt) 160 | camera:rotate(dt) 161 | end 162 | 163 | :: 164 | 165 | function love.update(dt) 166 | camera:rotate(dt):move(dt,dt) 167 | end 168 | 169 | 170 | .. function:: camera:rotateTo(angle) 171 | 172 | :param number angle: Rotation angle in radians 173 | :returns: The camera. 174 | 175 | Set rotation: ``camera.rot = angle``. 176 | 177 | **Example**:: 178 | 179 | camera:rotateTo(math.pi/2) 180 | 181 | 182 | .. function:: camera:zoom(mul) 183 | 184 | :param number mul: Zoom change. Should be > 0. 185 | :returns: The camera. 186 | 187 | 188 | *Multiply* zoom: ``camera.scale = camera.scale * mul``. 189 | 190 | **Examples**:: 191 | 192 | camera:zoom(2) -- make everything twice as big 193 | 194 | :: 195 | 196 | camera:zoom(0.5) -- ... and back to normal 197 | 198 | :: 199 | 200 | camera:zoom(-1) -- mirror and flip everything upside down 201 | 202 | 203 | .. function:: camera:zoomTo(zoom) 204 | 205 | :param number zoom: New zoom. 206 | :returns: The camera. 207 | 208 | 209 | Set zoom: ``camera.scale = zoom``. 210 | 211 | **Example**:: 212 | 213 | camera:zoomTo(1) -- reset zoom 214 | 215 | 216 | .. function:: camera:attach() 217 | 218 | Start looking through the camera. 219 | 220 | Apply camera transformations, i.e. move, scale and rotate everything until 221 | ``camera:detach()`` as if looking through the camera. 222 | 223 | **Example**:: 224 | 225 | function love.draw() 226 | camera:attach() 227 | draw_world() 228 | camera:detach() 229 | 230 | draw_hud() 231 | end 232 | 233 | 234 | .. function:: camera:detach() 235 | 236 | Stop looking through the camera. 237 | 238 | **Example**:: 239 | 240 | function love.draw() 241 | camera:attach() 242 | draw_world() 243 | camera:detach() 244 | 245 | draw_hud() 246 | end 247 | 248 | 249 | .. function:: camera:draw(func) 250 | 251 | :param function func: Drawing function to be wrapped. 252 | 253 | Wrap a function between a ``camera:attach()``/``camera:detach()`` pair. 254 | Equivalent to:: 255 | 256 | camera:attach() 257 | func() 258 | camera:detach() 259 | 260 | 261 | **Example**:: 262 | 263 | function love.draw() 264 | camera:draw(draw_world) 265 | draw_hud() 266 | end 267 | 268 | 269 | .. function:: camera:worldCoords(x, y) 270 | 271 | :param numbers x, y: Point to transform. 272 | :returns: ``x,y`` -- Transformed point. 273 | 274 | Because a camera has a point it looks at, a rotation and a zoom factor, it 275 | defines a coordinate system. A point now has two sets of coordinates: One 276 | defines where the point is to be found in the game world, and the other 277 | describes the position on the computer screen. The first set of coordinates is 278 | called world coordinates, the second one camera coordinates. Sometimes it is 279 | needed to convert between the two coordinate systems, for example to get the 280 | position of a mouse click in the game world in a strategy game, or to see if an 281 | object is visible on the screen. 282 | 283 | :func:`camera:worldCoords` and :func:`camera:cameraCoords` transform points 284 | between these two coordinate systems. 285 | 286 | **Example**:: 287 | 288 | x,y = camera:worldCoords(love.mouse.getPosition()) 289 | selectedUnit:plotPath(x,y) 290 | 291 | 292 | .. function:: camera:cameraCoords(x, y) 293 | 294 | :param numbers x, y: Point to transform. 295 | :returns: ``x,y`` -- Transformed point. 296 | 297 | 298 | Because a camera has a point it looks at, a rotation and a zoom factor, it 299 | defines a coordinate system. A point now has two sets of coordinates: One 300 | defines where the point is to be found in the game world, and the other 301 | describes the position on the computer screen. The first set of coordinates is 302 | called world coordinates, the second one camera coordinates. Sometimes it is 303 | needed to convert between the two coordinate systems, for example to get the 304 | position of a mouse click in the game world in a strategy game, or to see if an 305 | object is visible on the screen. 306 | 307 | :func:`camera:worldCoords` and :func:`camera:cameraCoords` transform points 308 | between these two coordinate systems. 309 | 310 | **Example**:: 311 | 312 | x,y = camera:cameraCoords(player.pos.x, player.pos.y) 313 | love.graphics.line(x, y, love.mouse.getPosition()) 314 | 315 | 316 | .. function:: camera:mousePosition() 317 | 318 | :returns: Mouse position in world coordinates. 319 | 320 | 321 | Shortcut to ``camera:worldCoords(love.mouse.getPosition())``. 322 | 323 | **Example**:: 324 | 325 | x,y = camera:mousePosition() 326 | selectedUnit:plotPath(x,y) 327 | 328 | 329 | Camera Movement Control 330 | ----------------------- 331 | 332 | Camera movement is one of these things that go almost unnoticed when done well, 333 | but add a lot to the overall experience. 334 | The article `Scroll Back: The Theory and Practice of Cameras in SideScrollers 335 | `_ 336 | by Itay Keren gives a lot of insight into how to design good camera systems. 337 | 338 | **hump.camera** offers functions that help to implement most of the techniques 339 | discussed in the article. The functions :func:`camera:lockX`, 340 | :func:`camera:lockY`, :func:`camera:lockPosition`, and :func:`camera:lockWindow` 341 | move the camera so that the interesting content stays in frame. 342 | Note that the functions must be called every frame:: 343 | 344 | function love.update() 345 | -- vertical locking 346 | camera:lockX(player.pos.x) 347 | end 348 | 349 | 350 | All movements are subject to smoothing (see :ref:`Movement Smoothers 351 | `). 352 | You can specify a default movement smoother by assigning the variable 353 | :attr:`camera.smoother`:: 354 | 355 | cam.smoother = Camera.smooth.linear(100) 356 | 357 | 358 | 359 | .. function:: camera:lockX(x, smoother, ...) 360 | 361 | :param number x: X coordinate (in world coordinates) to lock to. 362 | :param function smoother: Movement smoothing override. (optional) 363 | :param mixed ...: Additional parameters to the smoothing function. (optional) 364 | 365 | Horizontal camera locking: Keep the camera locked on the defined ``x``-position 366 | (in *world coordinates*). The ``y``-position is not affected. 367 | 368 | You can define an off-center locking position by "aiming" the camera left or 369 | right of your actual target. For example, to center the player 20 pixels to the 370 | *left* of the screen, aim 20 pixels to it's *right* (see examples). 371 | 372 | **Examples**:: 373 | 374 | -- lock on player vertically 375 | camera:lockX(player.x) 376 | 377 | :: 378 | 379 | -- ... with linear smoothing at 25 px/s 380 | camera:lockX(player.x, Camera.smooth.linear(25)) 381 | 382 | :: 383 | 384 | -- lock player 20px left of center 385 | camera:lockX(player.x + 20) 386 | 387 | 388 | 389 | .. function:: camera:lockY(y, smoother, ...) 390 | 391 | :param number y: Y coordinate (in world coordinates) to lock to. 392 | :param function smoother: Movement smoothing override. (optional) 393 | :param mixed ...: Additional parameters to the smoothing function. (optional) 394 | 395 | Vertical camera locking: Keep the camera locked on the defined ``y``-position 396 | (in *world coordinates*). The ``x``-position is not affected. 397 | 398 | You can define an off-center locking position by "aiming" the camera above or 399 | below your actual target. For example, to center the player 20 pixels *below* the 400 | screen center, aim 20 pixels *above* it (see examples). 401 | 402 | **Examples**:: 403 | 404 | -- lock on player horizontally 405 | camera:lockY(player.y) 406 | 407 | :: 408 | 409 | -- ... with damped smoothing with a stiffness of 10 410 | camera:lockY(player.y, Camera.smooth.damped(10)) 411 | 412 | :: 413 | 414 | -- lock player 20px below the screen center 415 | camera:lockY(player.y - 20) 416 | 417 | 418 | 419 | .. function:: camera:lockPosition(x,y, smoother, ...) 420 | 421 | :param numbers x,y: Position (in world coordinates) to lock to. 422 | :param function smoother: Movement smoothing override. (optional) 423 | :param mixed ...: Additional parameters to the smoothing function. (optional) 424 | 425 | Horizontal and vertical camera locking: Keep the camera locked on the defined 426 | position (in *world coordinates*). 427 | 428 | You can define an off-center locking position by "aiming" the camera to the 429 | opposite direction away from your real target. 430 | For example, to center the player 10 pixels to the *left* and 20 pixels *above* 431 | the screen center, aim 10 pixels to the *right* and 20 pixels *below*. 432 | 433 | **Examples**:: 434 | 435 | -- lock on player 436 | camera:lockPosition(player.x, player.y) 437 | 438 | :: 439 | 440 | -- lock 50 pixels into player's aiming direction 441 | camera:lockPosition(player.x - player.aiming.x * 50, player.y - player.aiming.y * 50) 442 | 443 | 444 | 445 | .. function:: camera:lockWindow(x,y, x_min, x_max, y_min, y_max, smoother, ...) 446 | 447 | :param numbers x,y: Position (in world coordinates) to lock to. 448 | :param numbers x_min: Upper left X coordinate of the camera window *(in camera coordinates!)*. 449 | :param numbers x_max: Lower right X coordinate of the camera window *(in camera coordinates!)*. 450 | :param numbers y_min: Upper left Y coordinate of the camera window *(in camera coordinates!)*. 451 | :param numbers y_max: Lower right Y coordinate of the camera window *(in camera coordinates!)*. 452 | :param function smoother: Movement smoothing override. (optional) 453 | :param mixed ...: Additional parameters to the smoothing function. (optional) 454 | 455 | The most powerful locking method: Lock camera to ``x,y``, but only move the 456 | camera if the position would be out of the screen-rectangle defined by ``x_min``, 457 | ``x_max``, ``y_min``, ``y_max``. 458 | 459 | .. note:: 460 | The locking window is defined in camera coordinates, whereas the position to 461 | lock to is defined in world coordinates! 462 | 463 | All of the other locking methods can be implemented by window locking. For 464 | position locking, set ``x_min = x_max`` and ``y_min = y_max``. 465 | Off-center locking can be done by defining the locking window accordingly. 466 | 467 | **Examples**:: 468 | 469 | -- lock on player 470 | camera:lock(player.x, player.y) 471 | 472 | .. attribute:: camera.smoother 473 | 474 | The default smoothing operator. Must be a ``function`` with the following 475 | prototype:: 476 | 477 | function customSmoother(dx,dy, ...) 478 | do_stuff() 479 | return new_dx,new_dy 480 | end 481 | 482 | where ``dx,dy`` is the offset the camera would move before smoothing and 483 | ``new_dx, new_dy`` is the offset the camera should move after smoothing. 484 | 485 | 486 | .. _movement-smoothers: 487 | 488 | Movement Smoothers 489 | ^^^^^^^^^^^^^^^^^^ 490 | 491 | It is not always desirable that the camera instantly locks on a target. 492 | `Platform snapping 493 | `_, 494 | for example, would look terrible if the camera would instantly jump to the 495 | focussed platform. 496 | Smoothly moving the camera to the locked position can also give the illusion of 497 | a camera operator an add to the overall feel of your game. 498 | 499 | **hump.camera** allows to smooth the movement by either passing movement 500 | smoother functions to the locking functions or by setting a default smoother 501 | (see :attr:`camera.smoother`). 502 | 503 | Smoothing functions must have the following prototype:: 504 | 505 | function customSmoother(dx,dy, ...) 506 | do_stuff() 507 | return new_dx,new_dy 508 | end 509 | 510 | where ``dx,dy`` is the offset the camera would move before smoothing and 511 | ``new_dx, new_dy`` is the offset the camera should move after smoothing. 512 | 513 | This is a simple "rubber-band" smoother:: 514 | 515 | function rubber_band(dx,dy) 516 | local dt = love.timer.getDelta() 517 | return dx*dt, dy*dt 518 | end 519 | 520 | **hump.camera** defines generators for the most common smoothers: 521 | 522 | .. function:: Camera.smooth.none() 523 | 524 | :returns: Smoothing function. 525 | 526 | Dummy smoother: does not smooth the motion. 527 | 528 | **Example**:: 529 | 530 | cam.smoother = Camera.smooth.none() 531 | 532 | 533 | .. function:: Camera.smooth.linear(speed) 534 | 535 | :param number speed: Smoothing speed. 536 | :returns: Smoothing function. 537 | 538 | Smoothly moves the camera towards to snapping goal with constant speed. 539 | 540 | **Examples**:: 541 | 542 | cam.smoother = Camera.smooth.linear(100) 543 | 544 | :: 545 | 546 | -- warning: creates a function every frame! 547 | camera:lockX(player.x, Camera.smooth.linear(25)) 548 | 549 | 550 | .. function:: Camera.smooth.damped(stiffness) 551 | 552 | :param number stiffness: Speed of the camera movement. 553 | :returns: Smoothing function. 554 | 555 | Smoothly moves the camera towards the goal with a speed proportional to the 556 | distance to the target. 557 | Stiffness defines the speed of the motion: Higher values mean that the camera 558 | moves more quickly. 559 | 560 | **Examples**:: 561 | 562 | cam.smoother = Camera.smooth.damped(10) 563 | 564 | :: 565 | 566 | -- warning: creates a function every frame! 567 | camera:lockPosition(player.x, player.y, Camera.smooth.damped(2)) 568 | -------------------------------------------------------------------------------- /docs/class.rst: -------------------------------------------------------------------------------- 1 | hump.class 2 | ========== 3 | 4 | :: 5 | 6 | Class = require "hump.class" 7 | 8 | A small, fast class/prototype implementation with multiple inheritance. 9 | 10 | Implements `class commons `_. 11 | 12 | **Example**:: 13 | 14 | Critter = Class{ 15 | init = function(self, pos, img) 16 | self.pos = pos 17 | self.img = img 18 | end, 19 | speed = 5 20 | } 21 | 22 | function Critter:update(dt, player) 23 | -- see hump.vector 24 | local dir = (player.pos - self.pos):normalize_inplace() 25 | self.pos = self.pos + dir * Critter.speed * dt 26 | end 27 | 28 | function Critter:draw() 29 | love.graphics.draw(self.img, self.pos.x, self.pos.y) 30 | end 31 | 32 | List of Functions 33 | ----------------- 34 | 35 | * :func:`Class.new() ` 36 | * :func:`class.init(object, ...) ` 37 | * :func:`Class:include(other) ` 38 | * :func:`class:clone() ` 39 | 40 | Function Reference 41 | ------------------ 42 | 43 | .. function:: Class.new() 44 | Class.new({init = constructor, __includes = parents, ...}) 45 | 46 | :param function constructor: Class constructor. Can be accessed with ``theclass.init(object, ...)``. (optional) 47 | :param class or table of classes parents: Classes to inherit from. Can either be a single class or a table of classes. (optional) 48 | :param mixed ...: Any other fields or methods common to all instances of this class. (optional) 49 | :returns: The class. 50 | 51 | 52 | Declare a new class. 53 | 54 | ``init()`` will receive the new object instance as first argument. Any other 55 | arguments will also be forwarded (see examples), i.e. ``init()`` has the 56 | following signature:: 57 | 58 | function init(self, ...) 59 | 60 | If you do not specify a constructor, an empty constructor will be used instead. 61 | 62 | The name of the variable that holds the module can be used as a shortcut to 63 | ``new()`` (see example). 64 | 65 | **Examples**:: 66 | 67 | Class = require 'hump.class' -- `Class' is now a shortcut to new() 68 | 69 | -- define a class class 70 | Feline = Class{ 71 | init = function(self, size, weight) 72 | self.size = size 73 | self.weight = weight 74 | end; 75 | -- define a method 76 | stats = function(self) 77 | return string.format("size: %.02f, weight: %.02f", self.size, self.weight) 78 | end; 79 | } 80 | 81 | -- create two objects 82 | garfield = Feline(.7, 45) 83 | felix = Feline(.8, 12) 84 | 85 | print("Garfield: " .. garfield:stats(), "Felix: " .. felix:stats()) 86 | 87 | :: 88 | 89 | Class = require 'hump.class' 90 | 91 | -- same as above, but with 'external' function definitions 92 | Feline = Class{} 93 | 94 | function Feline:init(size, weight) 95 | self.size = size 96 | self.weight = weight 97 | end 98 | 99 | function Feline:stats() 100 | return string.format("size: %.02f, weight: %.02f", self.size, self.weight) 101 | end 102 | 103 | garfield = Feline(.7, 45) 104 | print(Feline, garfield) 105 | 106 | :: 107 | 108 | Class = require 'hump.class' 109 | A = Class{ 110 | foo = function() print('foo') end 111 | } 112 | 113 | B = Class{ 114 | bar = function() print('bar') end 115 | } 116 | 117 | -- single inheritance 118 | C = Class{__includes = A} 119 | instance = C() 120 | instance:foo() -- prints 'foo' 121 | instance:bar() -- error: function not defined 122 | 123 | -- multiple inheritance 124 | D = Class{__includes = {A,B}} 125 | instance = D() 126 | instance:foo() -- prints 'foo' 127 | instance:bar() -- prints 'bar' 128 | 129 | :: 130 | 131 | -- class attributes are shared across instances 132 | A = Class{ foo = 'foo' } -- foo is a class attribute/static member 133 | 134 | one, two, three = A(), A(), A() 135 | print(one.foo, two.foo, three.foo) --> prints 'foo foo foo' 136 | 137 | one.foo = 'bar' -- overwrite/specify for instance `one' only 138 | print(one.foo, two.foo, three.foo) --> prints 'bar foo foo' 139 | 140 | A.foo = 'baz' -- overwrite for all instances without specification 141 | print(one.foo, two.foo, three.foo) --> prints 'bar baz baz' 142 | 143 | 144 | .. function:: class.init(object, ...) 145 | 146 | :param Object object: The object. Usually ``self``. 147 | :param mixed ...: Arguments to pass to the constructor. 148 | :returns: Whatever the parent class constructor returns. 149 | 150 | 151 | Calls class constructor of a class on an object. 152 | 153 | Derived classes should use this function their constructors to initialize the 154 | parent class(es) portions of the object. 155 | 156 | **Example**:: 157 | 158 | Class = require 'hump.class' 159 | 160 | Shape = Class{ 161 | init = function(self, area) 162 | self.area = area 163 | end; 164 | __tostring = function(self) 165 | return "area = " .. self.area 166 | end 167 | } 168 | 169 | Rectangle = Class{__includes = Shape, 170 | init = function(self, width, height) 171 | Shape.init(self, width * height) 172 | self.width = width 173 | self.height = height 174 | end; 175 | __tostring = function(self) 176 | local strs = { 177 | "width = " .. self.width, 178 | "height = " .. self.height, 179 | Shape.__tostring(self) 180 | } 181 | return table.concat(strs, ", ") 182 | end 183 | } 184 | 185 | print( Rectangle(2,4) ) -- prints 'width = 2, height = 4, area = 8' 186 | 187 | 188 | .. function:: Class:include(other) 189 | 190 | :param tables other: Parent classes/mixins. 191 | :returns: The class. 192 | 193 | 194 | Inherit functions and variables of another class, but only if they are not 195 | already defined. This is done by (deeply) copying the functions and variables 196 | over to the subclass. 197 | 198 | .. note:: 199 | ``class:include()`` doesn't actually care if the arguments supplied are 200 | hump classes. Just any table will work. 201 | 202 | .. note:: 203 | You can use ``Class.include(a, b)`` to copy any fields from table ``a`` 204 | to table ``b`` (see second example). 205 | 206 | **Examples**:: 207 | 208 | Class = require 'hump.class' 209 | 210 | Entity = Class{ 211 | init = function(self) 212 | GameObjects.register(self) 213 | end 214 | } 215 | 216 | Collidable = { 217 | dispatch_collision = function(self, other, dx, dy) 218 | if self.collision_handler[other.type]) 219 | return collision_handler[other.type](self, other, dx, dy) 220 | end 221 | return collision_handler["*"](self, other, dx, dy) 222 | end, 223 | 224 | collision_handler = {["*"] = function() end}, 225 | } 226 | 227 | Spaceship = Class{ 228 | init = function(self) 229 | self.type = "Spaceship" 230 | -- ... 231 | end 232 | } 233 | 234 | -- make Spaceship collidable 235 | Spaceship:include(Collidable) 236 | 237 | Spaceship.collision_handler["Spaceship"] = function(self, other, dx, dy) 238 | -- ... 239 | end 240 | 241 | :: 242 | 243 | -- using Class.include() 244 | Class = require 'hump.class' 245 | a = { 246 | foo = 'bar', 247 | bar = {one = 1, two = 2, three = 3}, 248 | baz = function() print('baz') end, 249 | } 250 | b = { 251 | foo = 'nothing to see here...' 252 | } 253 | 254 | Class.include(b, a) -- copy values from a to b 255 | -- note that neither a nor b are hump classes! 256 | 257 | print(a.foo, b.foo) -- prints 'bar nothing to see here...' 258 | 259 | b.baz() -- prints 'baz' 260 | 261 | b.bar.one = 10 -- changes only values in b 262 | print(a.bar.one, b.bar.one) -- prints '1 10' 263 | 264 | 265 | .. function:: class:clone() 266 | 267 | :returns: A deep copy of the class/table. 268 | 269 | 270 | Create a clone/deep copy of the class. 271 | 272 | .. note:: 273 | You can use ``Class.clone(a)`` to create a deep copy of any table (see 274 | second example). 275 | 276 | **Examples**:: 277 | 278 | Class = require 'hump.class' 279 | 280 | point = Class{ x = 0, y = 0 } 281 | 282 | a = point:clone() 283 | a.x, a.y = 10, 10 284 | print(a.x, a.y) --> prints '10 10' 285 | 286 | b = point:clone() 287 | print(b.x, b.y) --> prints '0 0' 288 | 289 | c = a:clone() 290 | print(c.x, c.y) --> prints '10 10' 291 | 292 | :: 293 | 294 | -- using Class.clone() to copy tables 295 | Class = require 'hump.class' 296 | a = { 297 | foo = 'bar', 298 | bar = {one = 1, two = 2, three = 3}, 299 | baz = function() print('baz') end, 300 | } 301 | b = Class.clone(a) 302 | 303 | b.baz() -- prints 'baz' 304 | b.bar.one = 10 305 | print(a.bar.one, b.bar.one) -- prints '1 10' 306 | 307 | 308 | 309 | Caveats 310 | ------- 311 | 312 | Be careful when using metamethods like ``__add`` or ``__mul``: If a subclass 313 | inherits those methods from a superclass, but does not overwrite them, the 314 | result of the operation may be of the type superclass. Consider the following:: 315 | 316 | Class = require 'hump.class' 317 | 318 | A = Class{init = function(self, x) self.x = x end} 319 | function A:__add(other) return A(self.x + other.x) end 320 | function A:show() print("A:", self.x) end 321 | 322 | B = Class{init = function(self, x, y) A.init(self, x) self.y = y end} 323 | function B:show() print("B:", self.x, self.y) end 324 | function B:foo() print("foo") end 325 | B:include(A) 326 | 327 | one, two = B(1,2), B(3,4) 328 | result = one + two -- result will be of type A, *not* B! 329 | result:show() -- prints "A: 4" 330 | result:foo() -- error: method does not exist 331 | 332 | Note that while you can define the ``__index`` metamethod of the class, this is 333 | not a good idea: It will break the class mechanism. To add a custom ``__index`` 334 | metamethod without breaking the class system, you have to use ``rawget()``. But 335 | beware that this won't affect subclasses:: 336 | 337 | Class = require 'hump.class' 338 | 339 | A = Class{} 340 | function A:foo() print('bar') end 341 | 342 | function A:__index(key) 343 | print(key) 344 | return rawget(A, key) 345 | end 346 | 347 | instance = A() 348 | instance:foo() -- prints foo bar 349 | 350 | B = Class{__includes = A} 351 | instance = B() 352 | instance:foo() -- prints only foo 353 | 354 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # hump documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Oct 10 13:10:12 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.mathjax', 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'hump' 52 | copyright = u'2015, Matthias Richter' 53 | author = u'Matthias Richter' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '1.0' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '1.0' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = ['_build'] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | #default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | #add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | #add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | #show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = 'sphinx' 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | #modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built documents. 103 | #keep_warnings = False 104 | 105 | # If true, `todo` and `todoList` produce output, else they produce nothing. 106 | todo_include_todos = False 107 | 108 | 109 | # -- Options for HTML output ---------------------------------------------- 110 | 111 | # The theme to use for HTML and HTML Help pages. See the documentation for 112 | # a list of builtin themes. 113 | html_theme = 'alabaster' 114 | 115 | # Theme options are theme-specific and customize the look and feel of a theme 116 | # further. For a list of options available for each theme, see the 117 | # documentation. 118 | #html_theme_options = {} 119 | 120 | # Add any paths that contain custom themes here, relative to this directory. 121 | #html_theme_path = [] 122 | 123 | # The name for this set of Sphinx documents. If None, it defaults to 124 | # " v documentation". 125 | #html_title = None 126 | 127 | # A shorter title for the navigation bar. Default is the same as html_title. 128 | #html_short_title = None 129 | 130 | # The name of an image file (relative to this directory) to place at the top 131 | # of the sidebar. 132 | #html_logo = None 133 | 134 | # The name of an image file (within the static path) to use as favicon of the 135 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 136 | # pixels large. 137 | #html_favicon = None 138 | 139 | # Add any paths that contain custom static files (such as style sheets) here, 140 | # relative to this directory. They are copied after the builtin static files, 141 | # so a file named "default.css" will overwrite the builtin "default.css". 142 | html_static_path = ['_static'] 143 | 144 | # Add any extra paths that contain custom files (such as robots.txt or 145 | # .htaccess) here, relative to this directory. These files are copied 146 | # directly to the root of the documentation. 147 | #html_extra_path = [] 148 | 149 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 150 | # using the given strftime format. 151 | #html_last_updated_fmt = '%b %d, %Y' 152 | 153 | # If true, SmartyPants will be used to convert quotes and dashes to 154 | # typographically correct entities. 155 | #html_use_smartypants = True 156 | 157 | # Custom sidebar templates, maps document names to template names. 158 | #html_sidebars = {} 159 | 160 | # Additional templates that should be rendered to pages, maps page names to 161 | # template names. 162 | #html_additional_pages = {} 163 | 164 | # If false, no module index is generated. 165 | #html_domain_indices = True 166 | 167 | # If false, no index is generated. 168 | #html_use_index = True 169 | 170 | # If true, the index is split into individual pages for each letter. 171 | #html_split_index = False 172 | 173 | # If true, links to the reST sources are added to the pages. 174 | #html_show_sourcelink = True 175 | 176 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 177 | #html_show_sphinx = True 178 | 179 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages will 183 | # contain a tag referring to it. The value of this option must be the 184 | # base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Language to be used for generating the HTML full-text search index. 191 | # Sphinx supports the following languages: 192 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 193 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 194 | #html_search_language = 'en' 195 | 196 | # A dictionary with options for the search language support, empty by default. 197 | # Now only 'ja' uses this config value 198 | #html_search_options = {'type': 'default'} 199 | 200 | # The name of a javascript file (relative to the configuration directory) that 201 | # implements a search results scorer. If empty, the default will be used. 202 | #html_search_scorer = 'scorer.js' 203 | 204 | # Output file base name for HTML help builder. 205 | htmlhelp_basename = 'humpdoc' 206 | 207 | # -- Options for LaTeX output --------------------------------------------- 208 | 209 | latex_elements = { 210 | # The paper size ('letterpaper' or 'a4paper'). 211 | #'papersize': 'letterpaper', 212 | 213 | # The font size ('10pt', '11pt' or '12pt'). 214 | #'pointsize': '10pt', 215 | 216 | # Additional stuff for the LaTeX preamble. 217 | #'preamble': '', 218 | 219 | # Latex figure (float) alignment 220 | #'figure_align': 'htbp', 221 | } 222 | 223 | # Grouping the document tree into LaTeX files. List of tuples 224 | # (source start file, target name, title, 225 | # author, documentclass [howto, manual, or own class]). 226 | latex_documents = [ 227 | (master_doc, 'hump.tex', u'hump Documentation', 228 | u'Matthias Richter', 'manual'), 229 | ] 230 | 231 | # The name of an image file (relative to this directory) to place at the top of 232 | # the title page. 233 | #latex_logo = None 234 | 235 | # For "manual" documents, if this is true, then toplevel headings are parts, 236 | # not chapters. 237 | #latex_use_parts = False 238 | 239 | # If true, show page references after internal links. 240 | #latex_show_pagerefs = False 241 | 242 | # If true, show URL addresses after external links. 243 | #latex_show_urls = False 244 | 245 | # Documents to append as an appendix to all manuals. 246 | #latex_appendices = [] 247 | 248 | # If false, no module index is generated. 249 | #latex_domain_indices = True 250 | 251 | 252 | # -- Options for manual page output --------------------------------------- 253 | 254 | # One entry per manual page. List of tuples 255 | # (source start file, name, description, authors, manual section). 256 | man_pages = [ 257 | (master_doc, 'hump', u'hump Documentation', 258 | [author], 1) 259 | ] 260 | 261 | # If true, show URL addresses after external links. 262 | #man_show_urls = False 263 | 264 | 265 | # -- Options for Texinfo output ------------------------------------------- 266 | 267 | # Grouping the document tree into Texinfo files. List of tuples 268 | # (source start file, target name, title, author, 269 | # dir menu entry, description, category) 270 | texinfo_documents = [ 271 | (master_doc, 'hump', u'hump Documentation', 272 | author, 'hump', 'One line description of project.', 273 | 'Miscellaneous'), 274 | ] 275 | 276 | # Documents to append as an appendix to all manuals. 277 | #texinfo_appendices = [] 278 | 279 | # If false, no module index is generated. 280 | #texinfo_domain_indices = True 281 | 282 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 283 | #texinfo_show_urls = 'footnote' 284 | 285 | # If true, do not generate a @detailmenu in the "Top" node's menu. 286 | #texinfo_no_detailmenu = False 287 | 288 | primary_domain = "js" 289 | highlight_language = "lua" 290 | 291 | import sphinx_rtd_theme 292 | html_theme = 'sphinx_rtd_theme' 293 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 294 | -------------------------------------------------------------------------------- /docs/gamestate.rst: -------------------------------------------------------------------------------- 1 | hump.gamestate 2 | ============== 3 | 4 | :: 5 | 6 | Gamestate = require "hump.gamestate" 7 | 8 | A gamestate encapsulates independent data and behaviour in a single table. 9 | 10 | A typical game could consist of a menu-state, a level-state and a game-over-state. 11 | 12 | **Example**:: 13 | 14 | local menu = {} -- previously: Gamestate.new() 15 | local game = {} 16 | 17 | function menu:draw() 18 | love.graphics.print("Press Enter to continue", 10, 10) 19 | end 20 | 21 | function menu:keyreleased(key, code) 22 | if key == 'return' then 23 | Gamestate.switch(game) 24 | end 25 | end 26 | 27 | function game:enter() 28 | Entities.clear() 29 | -- setup entities here 30 | end 31 | 32 | function game:update(dt) 33 | Entities.update(dt) 34 | end 35 | 36 | function game:draw() 37 | Entities.draw() 38 | end 39 | 40 | function love.load() 41 | Gamestate.registerEvents() 42 | Gamestate.switch(menu) 43 | end 44 | 45 | List of Functions 46 | ----------------- 47 | 48 | * :func:`Gamestate.new() ` 49 | * :func:`Gamestate.switch(to, ...) ` 50 | * :func:`Gamestate.current() ` 51 | * :func:`Gamestate.push(to, ...) ` 52 | * :func:`Gamestate.pop(...) ` 53 | * :func:`Gamestate.(...) >` 54 | * :func:`Gamestate.registerEvents([callbacks]) ` 55 | 56 | 57 | .. _callbacks: 58 | 59 | Gamestate Callbacks 60 | ------------------- 61 | 62 | A gamestate can define all callbacks that LÖVE defines. In addition, there are 63 | callbacks for initalizing, entering and leaving a state: 64 | 65 | ``init()`` 66 | Called once, and only once, before entering the state the first time. See 67 | :func:`Gamestate.switch`. 68 | 69 | ``enter(previous, ...)`` 70 | Called every time when entering the state. See :func:`Gamestate.switch`. 71 | 72 | ``leave()`` 73 | Called when leaving a state. See :func:`Gamestate.switch` and :func:`Gamestate.pop`. 74 | 75 | ``resume()`` 76 | Called when re-entering a state by :func:`Gamestate.pop`-ing another state. 77 | 78 | ``update()`` 79 | Update the game state. Called every frame. 80 | 81 | ``draw()`` 82 | Draw on the screen. Called every frame. 83 | 84 | ``focus()`` 85 | Called if the window gets or loses focus. 86 | 87 | ``keypressed()`` 88 | Triggered when a key is pressed. 89 | 90 | ``keyreleased()`` 91 | Triggered when a key is released. 92 | 93 | ``mousepressed()`` 94 | Triggered when a mouse button is pressed. 95 | 96 | ``mousereleased()`` 97 | Triggered when a mouse button is released. 98 | 99 | ``joystickpressed()`` 100 | Triggered when a joystick button is pressed. 101 | 102 | ``joystickreleased()`` 103 | Triggered when a joystick button is released. 104 | 105 | ``quit()`` 106 | Called on quitting the game. Only called on the active gamestate. 107 | 108 | When using :func:`Gamestate.registerEvents`, all these callbacks will be called by the 109 | corresponding LÖVE callbacks and receive the same arguments (e.g. 110 | ``state:update(dt)`` will be called by ``love.update(dt)``). 111 | 112 | **Example**:: 113 | 114 | menu = {} -- previously: Gamestate.new() 115 | 116 | function menu:init() 117 | self.background = love.graphics.newImage('bg.jpg') 118 | Buttons.initialize() 119 | end 120 | 121 | function menu:enter(previous) -- runs every time the state is entered 122 | Buttons.setActive(Buttons.start) 123 | end 124 | 125 | function menu:update(dt) -- runs every frame 126 | Buttons.update(dt) 127 | end 128 | 129 | function menu:draw() 130 | love.graphics.draw(self.background, 0, 0) 131 | Buttons.draw() 132 | end 133 | 134 | function menu:keyreleased(key) 135 | if key == 'up' then 136 | Buttons.selectPrevious() 137 | elseif key == 'down' then 138 | Buttons.selectNext() 139 | elseif 140 | Buttons.active:onClick() 141 | end 142 | end 143 | 144 | function menu:mousereleased(x,y, mouse_btn) 145 | local button = Buttons.hovered(x,y) 146 | if button then 147 | Button.select(button) 148 | if mouse_btn == 'l' then 149 | button:onClick() 150 | end 151 | end 152 | end 153 | 154 | 155 | Function Reference 156 | ------------------ 157 | 158 | .. function:: Gamestate.new() 159 | 160 | :returns: An empty table. 161 | 162 | 163 | **Deprecated: Use the table constructor instead (see example)** 164 | 165 | Declare a new gamestate (just an empty table). A gamestate can define several 166 | callbacks. 167 | 168 | **Example**:: 169 | 170 | menu = {} 171 | -- deprecated method: 172 | menu = Gamestate.new() 173 | 174 | 175 | .. function:: Gamestate.switch(to, ...) 176 | 177 | :param Gamestate to: Target gamestate. 178 | :param mixed ...: Additional arguments to pass to ``to:enter(current, ...)``. 179 | :returns: The results of ``to:enter(current, ...)``. 180 | 181 | 182 | Switch to a gamestate, with any additional arguments passed to the new state. 183 | 184 | Switching a gamestate will call the ``leave()`` callback on the current 185 | gamestate, replace the current gamestate with ``to``, call the ``init()`` function 186 | if, and only if, the state was not yet inialized and finally call 187 | ``enter(old_state, ...)`` on the new gamestate. 188 | 189 | .. note:: 190 | Processing of callbacks is suspended until ``update()`` is called on the new 191 | gamestate, but the function calling :func:`Gamestate.switch` can still continue - it is 192 | your job to make sure this is handled correctly. See also the examples below. 193 | 194 | 195 | **Examples**:: 196 | 197 | Gamestate.switch(game, level_two) 198 | 199 | :: 200 | 201 | -- stop execution of the current state by using return 202 | if player.has_died then 203 | return Gamestate.switch(game, level_two) 204 | end 205 | 206 | -- this will not be called when the state is switched 207 | player:update() 208 | 209 | 210 | 211 | .. function:: Gamestate.current() 212 | 213 | :returns: The active gamestate. 214 | 215 | Returns the currently activated gamestate. 216 | 217 | **Example**:: 218 | 219 | function love.keypressed(key) 220 | if Gamestate.current() ~= menu and key == 'p' then 221 | Gamestate.push(pause) 222 | end 223 | end 224 | 225 | 226 | .. function:: Gamestate.push(to, ...) 227 | 228 | :param Gamestate to: Target gamestate. 229 | :param mixed ...: Additional arguments to pass to ``to:enter(current, ...)``. 230 | :returns: The results of ``to:enter(current, ...)``. 231 | 232 | Pushes the ``to`` on top of the state stack, i.e. makes it the active state. 233 | Semantics are the same as ``switch(to, ...)``, except that ``leave()`` is *not* 234 | called on the previously active state. 235 | 236 | Useful for pause screens, menus, etc. 237 | 238 | .. note:: 239 | Processing of callbacks is suspended until ``update()`` is called on the 240 | new gamestate, but the function calling ``GS.push()`` can still continue - 241 | it is your job to make sure this is handled correctly. See also the 242 | example below. 243 | 244 | 245 | **Example**:: 246 | 247 | -- pause gamestate 248 | Pause = Gamestate.new() 249 | function Pause:enter(from) 250 | self.from = from -- record previous state 251 | end 252 | 253 | function Pause:draw() 254 | local W, H = love.graphics.getWidth(), love.graphics.getHeight() 255 | -- draw previous screen 256 | self.from:draw() 257 | -- overlay with pause message 258 | love.graphics.setColor(0,0,0, 100) 259 | love.graphics.rectangle('fill', 0,0, W,H) 260 | love.graphics.setColor(255,255,255) 261 | love.graphics.printf('PAUSE', 0, H/2, W, 'center') 262 | end 263 | 264 | -- [...] 265 | function love.keypressed(key) 266 | if Gamestate.current() ~= menu and key == 'p' then 267 | return Gamestate.push(pause) 268 | end 269 | end 270 | 271 | 272 | .. function:: Gamestate.pop(...) 273 | 274 | :returns: The results of ``new_state:resume(...)``. 275 | 276 | Calls ``leave()`` on the current state and then removes it from the stack, making 277 | the state below the current state and calls ``resume(...)`` on the activated state. 278 | Does *not* call ``enter()`` on the activated state. 279 | 280 | .. note:: 281 | Processing of callbacks is suspended until ``update()`` is called on the 282 | new gamestate, but the function calling ``GS.pop()`` can still continue - 283 | it is your job to make sure this is handled correctly. See also the 284 | example below. 285 | 286 | 287 | **Example**:: 288 | 289 | -- extending the example of Gamestate.push() above 290 | function Pause:keypressed(key) 291 | if key == 'p' then 292 | return Gamestate.pop() -- return to previous state 293 | end 294 | end 295 | 296 | 297 | .. function:: Gamestate.(...) 298 | 299 | :param mixed ...: Arguments to pass to the corresponding function. 300 | :returns: The result of the callback function. 301 | 302 | Calls a function on the current gamestate. Can be any function, but is intended 303 | to be one of the :ref:`callbacks`. Mostly useful when not using 304 | :func:`Gamestate.registerEvents`. 305 | 306 | **Example**:: 307 | 308 | function love.draw() 309 | Gamestate.draw() -- is `draw' 310 | end 311 | 312 | function love.update(dt) 313 | Gamestate.update(dt) -- pass dt to currentState:update(dt) 314 | end 315 | 316 | function love.keypressed(key, code) 317 | Gamestate.keypressed(key, code) -- pass multiple arguments 318 | end 319 | 320 | 321 | .. function:: Gamestate.registerEvents([callbacks]) 322 | 323 | :param table callbacks: Names of the callbacks to register. If omitted, 324 | register all love callbacks (optional). 325 | 326 | Overwrite love callbacks to call ``Gamestate.update()``, ``Gamestate.draw()``, 327 | etc. automatically. ``love`` callbacks (e.g. ``love.update()``) are still 328 | invoked as usual. 329 | 330 | This is by done by overwriting the love callbacks, e.g.:: 331 | 332 | local old_update = love.update 333 | function love.update(dt) 334 | old_update(dt) 335 | return Gamestate.current:update(dt) 336 | end 337 | 338 | .. note:: 339 | Only works when called in love.load() or any other function that is 340 | executed *after* the whole file is loaded. 341 | 342 | **Examples**:: 343 | 344 | function love.load() 345 | Gamestate.registerEvents() 346 | Gamestate.switch(menu) 347 | end 348 | 349 | -- love callback will still be invoked 350 | function love.update(dt) 351 | Timer.update(dt) 352 | -- no need for Gamestate.update(dt) 353 | end 354 | 355 | :: 356 | 357 | function love.load() 358 | -- only register draw, update and quit 359 | Gamestate.registerEvents{'draw', 'update', 'quit'} 360 | Gamestate.switch(menu) 361 | end 362 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | **hump** - Helper Utilities for a Multitude of Problems 2 | ======================================================= 3 | 4 | **hump** is a set of lightweight helpers for the awesome `LÖVE 5 | `_ game framework. 6 | It will help to get you over the initial hump when starting to build a new 7 | game. 8 | 9 | **hump** does nothing that you couldn't do yourself. 10 | But why should you? 11 | You want to write games, not boilerplate! 12 | 13 | **hump**'s components are so loosely coupled that every component is 14 | independent of the others. 15 | You can choose what you need and leave the rest behind. 16 | hump won't judge. 17 | 18 | **hump** just wants to make you happy. 19 | 20 | Read on 21 | ------- 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | 26 | hump.gamestate 27 | hump.timer 28 | hump.vector 29 | hump.vector-light 30 | hump.class 31 | hump.signal 32 | hump.camera 33 | license 34 | 35 | 36 | Get hump 37 | -------- 38 | 39 | You can view and download the individual modules on github: `vrld/hump 40 | `_. 41 | You may also download the whole packed sourcecode either in the `zip 42 | `_ or `tar 43 | `_ format. 44 | 45 | Using `Git `_, you can clone the project by running: 46 | 47 | git clone git://github.com/vrld/hump 48 | 49 | Once done, you can check for updates by running 50 | 51 | git pull 52 | 53 | 54 | Indices and tables 55 | ------------------ 56 | 57 | * :ref:`genindex` 58 | * :ref:`modindex` 59 | * :ref:`search` 60 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | Copyright (c) 2011-2018 Matthias Richter 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | Except as contained in this notice, the name(s) of the above copyright holders 17 | shall not be used in advertising or otherwise to promote the sale, use or 18 | other dealings in this Software without prior written authorization. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /docs/signal.rst: -------------------------------------------------------------------------------- 1 | hump.signal 2 | =========== 3 | 4 | :: 5 | 6 | Signal = require 'hump.signal' 7 | 8 | A simple yet effective implementation of `Signals and Slots 9 | `_, aka the `Observer pattern 10 | `_: Functions can be dynamically 11 | bound to signals. When a *signal* is *emitted*, all registered functions will 12 | be invoked. Simple as that. 13 | 14 | ``hump.signal`` makes things a little more interesting by allowing to emit all 15 | signals that match a `Lua string pattern 16 | `_. 17 | 18 | **Example**:: 19 | 20 | -- in AI.lua 21 | Signal.register('shoot', function(x,y, dx,dy) 22 | -- for every critter in the path of the bullet: 23 | -- try to avoid being hit 24 | for critter in pairs(critters) do 25 | if critter:intersectsRay(x,y, dx,dy) then 26 | critter:setMoveDirection(-dy, dx) 27 | end 28 | end 29 | end) 30 | 31 | -- in sounds.lua 32 | Signal.register('shoot', function() 33 | Sounds.fire_bullet:play() 34 | end) 35 | 36 | -- in main.lua 37 | function love.keypressed(key) 38 | if key == ' ' then 39 | local x,y = player.pos:unpack() 40 | local dx,dy = player.direction:unpack() 41 | Signal.emit('shoot', x,y, dx,dy) 42 | end 43 | end 44 | 45 | List of Functions 46 | ----------------- 47 | 48 | * :func:`Signal.new() ` 49 | * :func:`Signal.register(s, f) ` 50 | * :func:`Signal.emit(s, ...) ` 51 | * :func:`Signal.remove(s, ...) ` 52 | * :func:`Signal.clear(s) ` 53 | * :func:`Signal.emitPattern(p, ...) ` 54 | * :func:`Signal.removePattern(p, ...) ` 55 | * :func:`Signal.clearPattern(p) ` 56 | 57 | Function Reference 58 | ------------------ 59 | 60 | .. function:: Signal.new() 61 | 62 | :returns: A new signal registry. 63 | 64 | Creates a new signal registry that is independent of the default registry: It 65 | will manage its own list of signals and does not in any way affect the the 66 | global registry. Likewise, the global registry does not affect the instance. 67 | 68 | .. note:: 69 | If you don't need multiple independent registries, you can use the 70 | global/default registry (see examples). 71 | 72 | .. note:: 73 | Unlike the default one, signal registry instances use the colon-syntax, 74 | i.e., you need to call ``instance:emit('foo', 23)`` instead of 75 | ``Signal.emit('foo', 23)``. 76 | 77 | **Example**:: 78 | 79 | player.signals = Signal.new() 80 | 81 | 82 | .. function:: Signal.register(s, f) 83 | 84 | :param string s: The signal identifier. 85 | :param function f: The function to register. 86 | :returns: A function handle to use in :func:`Signal.remove()`. 87 | 88 | 89 | Registers a function ``f`` to be called when signal ``s`` is emitted. 90 | 91 | **Examples**:: 92 | 93 | Signal.register('level-complete', function() self.fanfare:play() end) 94 | 95 | :: 96 | 97 | handle = Signal.register('level-load', function(level) level.show_help() end) 98 | 99 | :: 100 | 101 | menu:register('key-left', select_previous_item) 102 | 103 | 104 | .. function:: Signal.emit(s, ...) 105 | 106 | :param string s: The signal identifier. 107 | :param mixed ...: Arguments to pass to the bound functions. (optional) 108 | 109 | 110 | Calls all functions bound to signal ``s`` with the supplied arguments. 111 | 112 | 113 | **Examples**:: 114 | 115 | function love.keypressed(key) 116 | -- using a signal instance 117 | if key == 'left' then menu:emit('key-left') end 118 | end 119 | 120 | :: 121 | 122 | if level.is_finished() then 123 | -- adding arguments 124 | Signal.emit('level-load', level.next_level) 125 | end 126 | 127 | 128 | .. function:: Signal.remove(s, ...) 129 | 130 | :param string s: The signal identifier. 131 | :param functions ...: Functions to unbind from the signal. 132 | 133 | 134 | Unbinds (removes) functions from signal ``s``. 135 | 136 | **Example**:: 137 | 138 | Signal.remove('level-load', handle) 139 | 140 | 141 | .. function:: Signal.clear(s) 142 | 143 | :param string s: The signal identifier. 144 | 145 | 146 | Removes all functions from signal ``s``. 147 | 148 | **Example**:: 149 | 150 | Signal.clear('key-left') 151 | 152 | 153 | .. function:: Signal.emitPattern(p, ...) 154 | 155 | :param string p: The signal identifier pattern. 156 | :param mixed ...: Arguments to pass to the bound functions. (optional) 157 | 158 | 159 | Emits all signals that match a `Lua string pattern 160 | `_. 161 | 162 | **Example**:: 163 | 164 | -- emit all update signals 165 | Signal.emitPattern('^update%-.*', dt) 166 | 167 | 168 | .. function:: Signal.removePattern(p, ...) 169 | 170 | :param string p: The signal identifier pattern. 171 | :param functions ...: Functions to unbind from the signals. 172 | 173 | 174 | Removes functions from all signals that match a `Lua string pattern 175 | `_. 176 | 177 | **Example**:: 178 | 179 | Signal.removePattern('key%-.*', play_click_sound) 180 | 181 | 182 | .. function:: Signal.clearPattern(p) 183 | 184 | :param string p: The signal identifier pattern. 185 | 186 | 187 | Removes **all** functions from all signals that match a `Lua string pattern 188 | `_. 189 | 190 | **Examples**:: 191 | 192 | Signal.clearPattern('sound%-.*') 193 | 194 | :: 195 | 196 | player.signals:clearPattern('.*') -- clear all signals 197 | 198 | -------------------------------------------------------------------------------- /docs/timer.rst: -------------------------------------------------------------------------------- 1 | hump.timer 2 | ========== 3 | 4 | :: 5 | 6 | Timer = require "hump.timer" 7 | 8 | hump.timer offers a simple interface to schedule the execution of functions. It 9 | is possible to run functions *after* and *for* some amount of time. For 10 | example, a timer could be set to move critters every 5 seconds or to make the 11 | player invincible for a short amount of time. 12 | 13 | In addition to that, ``hump.timer`` offers various `tweening 14 | `_ functions that make it 15 | easier to produce `juicy games `_. 16 | 17 | **Example**:: 18 | 19 | function love.keypressed(key) 20 | if key == ' ' then 21 | Timer.after(1, function() print("Hello, world!") end) 22 | end 23 | end 24 | 25 | function love.update(dt) 26 | Timer.update(dt) 27 | end 28 | 29 | List of Functions 30 | ----------------- 31 | 32 | * :func:`Timer.new() ` 33 | * :func:`Timer.after(delay, func) ` 34 | * :func:`Timer.script(func) ` 35 | * :func:`Timer.every(delay, func, count) ` 36 | * :func:`Timer.during(delay, func, after) ` 37 | * :func:`Timer.cancel(handle) ` 38 | * :func:`Timer.clear() ` 39 | * :func:`Timer.update(dt) ` 40 | * :func:`Timer.tween(duration, subject, target, method, after, ...) ` 41 | 42 | Function Reference 43 | ------------------ 44 | 45 | .. function:: Timer.new() 46 | 47 | :returns: A timer instance. 48 | 49 | 50 | Creates a new timer instance that is independent of the global timer: It will 51 | manage it's own list of scheduled functions and does not in any way affect the 52 | the global timer. Likewise, the global timer does not affect timer instances. 53 | 54 | .. note:: 55 | If you don't need multiple independent schedulers, you can use the 56 | global/default timer (see examples). 57 | 58 | .. note:: 59 | Unlike the default timer, timer instances use the colon-syntax, i.e., 60 | you need to call ``instance:after(1, foo)`` instead of ``Timer.after(1, 61 | foo)``. 62 | 63 | **Example**:: 64 | 65 | menuTimer = Timer.new() 66 | 67 | 68 | .. function:: Timer.after(delay, func) 69 | 70 | :param number delay: Number of seconds the function will be delayed. 71 | :param function func: The function to be delayed. 72 | :returns: The timer handle. See also :func:`Timer.cancel`. 73 | 74 | 75 | Schedule a function. The function will be executed after ``delay`` seconds have 76 | elapsed, given that ``update(dt)`` is called every frame. 77 | 78 | .. note:: 79 | There is no guarantee that the delay will not be exceeded, it is only 80 | guaranteed that the function will *not* be executed *before* the delay has 81 | passed. 82 | 83 | ``func`` will receive itself as only parameter. This is useful to implement 84 | periodic behavior (see the example). 85 | 86 | **Examples**:: 87 | 88 | -- grant the player 5 seconds of immortality 89 | player.isInvincible = true 90 | Timer.after(5, function() player.isInvincible = false end) 91 | 92 | :: 93 | 94 | -- print "foo" every second. See also every() 95 | Timer.after(1, function(func) print("foo") Timer.after(1, func) end) 96 | 97 | :: 98 | 99 | --Using a timer instance: 100 | menuTimer:after(1, finishAnimation) 101 | 102 | 103 | .. function:: Timer.script(func) 104 | 105 | :param function func: Script to execute. 106 | 107 | Execute a function that can be paused without causing the rest of the program to 108 | be suspended. ``func`` will receive a function - ``wait`` - to do interrupt the 109 | script (but not the whole program) as only argument. The function prototype of 110 | wait is: ``wait(delay)``. 111 | 112 | **Examples**:: 113 | 114 | Timer.script(function(wait) 115 | print("Now") 116 | wait(1) 117 | print("After one second") 118 | wait(1) 119 | print("Bye!") 120 | end) 121 | 122 | :: 123 | 124 | -- useful for splash screens 125 | Timer.script(function(wait) 126 | Timer.tween(0.5, splash.pos, {x = 300}, 'in-out-quad') 127 | wait(5) -- show the splash for 5 seconds 128 | Timer.tween(0.5, slpash.pos, {x = 800}, 'in-out-quad') 129 | end) 130 | 131 | :: 132 | 133 | -- repeat something with a varying delay 134 | Timer.script(function(wait) 135 | while true do 136 | spawn_ship() 137 | wait(1 / (1-production_speed)) 138 | end 139 | end) 140 | 141 | :: 142 | 143 | -- jumping with timer.script 144 | self.timers:script(function(wait) 145 | local w = 1/12 146 | self.jumping = true 147 | Timer.tween(w*2, self, {z = -8}, "out-cubic", function() 148 | Timer.tween(w*2, self, {z = 0},"in-cubic") 149 | end) 150 | 151 | self.quad = self.quads.jump[1] 152 | wait(w) 153 | 154 | self.quad = self.quads.jump[2] 155 | wait(w) 156 | 157 | self.quad = self.quads.jump[3] 158 | wait(w) 159 | 160 | self.quad = self.quads.jump[4] 161 | wait(w) 162 | 163 | self.jumping = false 164 | self.z = 0 165 | end) 166 | 167 | 168 | .. function:: Timer.every(delay, func, count) 169 | 170 | :param number delay: Number of seconds between two consecutive function calls. 171 | :param function func: The function to be called periodically. 172 | :param number count: Number of times the function is to be called (optional). 173 | :returns: The timer handle. See also :func:`Timer.cancel`. 174 | 175 | 176 | Add a function that will be called ``count`` times every ``delay`` seconds. 177 | 178 | If ``count`` is omitted, the function will be called until it returns ``false`` 179 | or :func:`Timer.cancel` or :func:`Timer.clear` is called on the timer instance. 180 | 181 | **Example**:: 182 | 183 | -- toggle light on and off every second 184 | Timer.every(1, function() lamp:toggleLight() end) 185 | 186 | :: 187 | 188 | -- launch 5 fighters in quick succession (using a timer instance) 189 | mothership_timer:every(0.3, function() self:launchFighter() end, 5) 190 | 191 | :: 192 | 193 | -- flicker player's image as long as he is invincible 194 | Timer.every(0.1, function() 195 | player:flipImage() 196 | return player.isInvincible 197 | end) 198 | 199 | 200 | .. function:: Timer.during(delay, func, after) 201 | 202 | :param number delay: Number of seconds the func will be called. 203 | :param function func: The function to be called on ``update(dt)``. 204 | :param function after: A function to be called after delay seconds (optional). 205 | :returns: The timer handle. See also :func:`Timer.cancel`. 206 | 207 | 208 | Run ``func(dt)`` for the next ``delay`` seconds. The function is called every 209 | time ``update(dt)`` is called. Optionally run ``after()`` once ``delay`` 210 | seconds have passed. 211 | 212 | ``after()`` will receive itself as only parameter. 213 | 214 | .. note:: 215 | You should not add new timers in ``func(dt)``, as this can lead to random 216 | crashes. 217 | 218 | **Examples**:: 219 | 220 | -- play an animation for 5 seconds 221 | Timer.during(5, function(dt) animation:update(dt) end) 222 | 223 | :: 224 | 225 | -- shake the camera for one second 226 | local orig_x, orig_y = camera:position() 227 | Timer.during(1, function() 228 | camera:lookAt(orig_x + math.random(-2,2), orig_y + math.random(-2,2)) 229 | end, function() 230 | -- reset camera position 231 | camera:lookAt(orig_x, orig_y) 232 | end) 233 | 234 | :: 235 | 236 | player.isInvincible = true 237 | -- flash player for 3 seconds 238 | local t = 0 239 | player.timer:during(3, function(dt) 240 | t = t + dt 241 | player.visible = (t % .2) < .1 242 | end, function() 243 | -- make sure the player is visible after three seconds 244 | player.visible = true 245 | player.isInvincible = false 246 | end) 247 | 248 | 249 | .. function:: Timer.cancel(handle) 250 | 251 | :param table handle: The function to be canceled. 252 | 253 | Prevent a timer from being executed in the future. 254 | 255 | **Examples**:: 256 | 257 | function tick() 258 | print('tick... tock...') 259 | end 260 | handle = Timer.every(1, tick) 261 | -- later 262 | Timer.cancel(handle) -- NOT: Timer.cancel(tick) 263 | 264 | :: 265 | 266 | -- using a timer instance 267 | function tick() 268 | print('tick... tock...') 269 | end 270 | handle = menuTimer:every(1, tick) 271 | -- later 272 | menuTimer:cancel(handle) 273 | 274 | 275 | .. function:: Timer.clear() 276 | 277 | Remove all timed and periodic functions. Functions that have not yet been 278 | executed will discarded. 279 | 280 | **Examples**:: 281 | 282 | Timer.clear() 283 | 284 | :: 285 | 286 | menuTimer:clear() 287 | 288 | 289 | .. function:: Timer.update(dt) 290 | 291 | :param number dt: Time that has passed since the last ``update()``. 292 | 293 | Update timers and execute functions if the deadline is reached. Call in 294 | ``love.update(dt)``. 295 | 296 | **Examples**:: 297 | 298 | function love.update(dt) 299 | do_stuff() 300 | Timer.update(dt) 301 | end 302 | 303 | :: 304 | 305 | -- using hump.gamestate and a timer instance 306 | function menuState:update(dt) 307 | self.timers:update(dt) 308 | end 309 | 310 | 311 | .. function:: Timer.tween(duration, subject, target, method, after, ...) 312 | 313 | :param number duration: Duration of the tween. 314 | :param table subject: Object to be tweened. 315 | :param table target: Target values. 316 | :param string method: Tweening method, defaults to 'linear' (:ref:`see here 317 | `, optional). 318 | :param function after: Function to execute after the tween has finished 319 | (optional). 320 | :param mixed ...: Additional arguments to the *tweening* function. 321 | :returns: A timer handle. 322 | 323 | 324 | `Tweening `_ (short for 325 | in-betweening) is the process that happens between two defined states. For 326 | example, a tween can be used to gradually fade out a graphic or move a text 327 | message to the center of the screen. For more information why tweening should 328 | be important to you, check out this great talk on `juicy games 329 | `_. 330 | 331 | ``hump.timer`` offers two interfaces for tweening: the low-level 332 | :func:`Timer.during` and the higher level interface :func:`Timer.tween`. 333 | 334 | To see which tweening methods hump offers, :ref:`see below `. 335 | 336 | **Examples**:: 337 | 338 | function love.load() 339 | color = {0, 0, 0} 340 | Timer.tween(10, color, {255, 255, 255}, 'in-out-quad') 341 | end 342 | 343 | function love.update(dt) 344 | Timer.update(dt) 345 | end 346 | 347 | function love.draw() 348 | love.graphics.setBackgroundColor(color) 349 | end 350 | 351 | :: 352 | 353 | function love.load() 354 | circle = {rad = 10, pos = {x = 400, y = 300}} 355 | -- multiple tweens can work on the same subject 356 | -- and nested values can be tweened, too 357 | Timer.tween(5, circle, {rad = 50}, 'in-out-quad') 358 | Timer.tween(2, circle, {pos = {y = 550}}, 'out-bounce') 359 | end 360 | 361 | function love.update(dt) 362 | Timer.update(dt) 363 | end 364 | 365 | function love.draw() 366 | love.graphics.circle('fill', circle.pos.x, circle.pos.y, circle.rad) 367 | end 368 | 369 | :: 370 | 371 | function love.load() 372 | -- repeated tweening 373 | 374 | circle = {rad = 10, x = 100, y = 100} 375 | local grow, shrink, move_down, move_up 376 | grow = function() 377 | Timer.tween(1, circle, {rad = 50}, 'in-out-quad', shrink) 378 | end 379 | shrink = function() 380 | Timer.tween(2, circle, {rad = 10}, 'in-out-quad', grow) 381 | end 382 | 383 | move_down = function() 384 | Timer.tween(3, circle, {x = 700, y = 500}, 'bounce', move_up) 385 | end 386 | move_up = function() 387 | Timer.tween(5, circle, {x = 200, y = 200}, 'out-elastic', move_down) 388 | end 389 | 390 | grow() 391 | move_down() 392 | end 393 | 394 | function love.update(dt) 395 | Timer.update(dt) 396 | end 397 | 398 | function love.draw() 399 | love.graphics.circle('fill', circle.x, circle.y, circle.rad) 400 | end 401 | 402 | 403 | 404 | .. _tweening-methods: 405 | 406 | Tweening methods 407 | ---------------- 408 | 409 | At the core of tweening lie interpolation methods. These methods define how the 410 | output should look depending on how much time has passed. For example, consider 411 | the following tween:: 412 | 413 | -- now: player.x = 0, player.y = 0 414 | Timer.tween(2, player, {x = 2}) 415 | Timer.tween(4, player, {y = 8}) 416 | 417 | At the beginning of the tweens (no time passed), the interpolation method would 418 | place the player at ``x = 0, y = 0``. After one second, the player should be at 419 | ``x = 1, y = 2``, and after two seconds the output is ``x = 2, y = 4``. 420 | 421 | The actual duration of and time since starting the tween is not important, only 422 | the fraction of the two. Similarly, the starting value and output are not 423 | important to the interpolation method, since it can be calculated from the 424 | start and end point. Thus an interpolation method can be fully characterized by 425 | a function that takes a number between 0 and 1 and returns a number that 426 | defines the output (usually also between 0 and 1). The interpolation function 427 | must hold that the output is 0 for input 0 and 1 for input 1. 428 | 429 | **hump** predefines several commonly used interpolation methods, which are 430 | generalized versions of `Robert Penner's easing 431 | functions `_. Those are: 432 | 433 | ``'linear'``, 434 | ``'quad'``, 435 | ``'cubic'``, 436 | ``'quart'``, 437 | ``'quint'``, 438 | ``'sine'``, 439 | ``'expo'``, 440 | ``'circ'``, 441 | ``'back'``, 442 | ``'bounce'``, and 443 | ``'elastic'``. 444 | 445 | It's hard to understand how these functions behave by staring at a graph, so 446 | below are some animation examples. You can change the type of the tween by 447 | changing the selections. 448 | 449 | .. raw:: html 450 | 451 |
452 | 453 | 454 | 455 | Note that while the animations above show tweening of shapes, other attributes 456 | (color, opacity, volume of a sound, ...) can be changed as well. 457 | 458 | 459 | Custom interpolators 460 | ^^^^^^^^^^^^^^^^^^^^ 461 | 462 | .. warning: 463 | This is a stub 464 | 465 | You can add custom interpolation methods by adding them to the `tween` table:: 466 | 467 | Timer.tween.sqrt = function(t) return math.sqrt(t) end 468 | -- or just Timer.tween.sqrt = math.sqrt 469 | 470 | Access the your method like you would the predefined ones. You can even use the 471 | modyfing prefixes:: 472 | 473 | Timer.tween(5, circle, {radius = 50}, 'in-out-sqrt') 474 | 475 | You can also invert and chain functions:: 476 | 477 | outsqrt = Timer.tween.out(math.sqrt) 478 | inoutsqrt = Timer.tween.chain(math.sqrt, outsqrt) 479 | -------------------------------------------------------------------------------- /docs/vector-light.rst: -------------------------------------------------------------------------------- 1 | hump.vector-light 2 | ================= 3 | 4 | :: 5 | 6 | vector = require "hump.vector-light" 7 | 8 | An table-free version of :doc:`hump.vector `. Instead of a vector type, 9 | ``hump.vector-light`` provides functions that operate on numbers. 10 | 11 | .. note:: 12 | 13 | Using this module instead of :doc:`hump.vector ` may result in 14 | faster code, but does so at the expense of speed of development and code 15 | readability. Unless you are absolutely sure that your code is 16 | significantly slowed down by :doc:`hump.vector `, I recommend using 17 | it instead. 18 | 19 | **Example**:: 20 | 21 | function player:update(dt) 22 | local dx,dy = 0,0 23 | if love.keyboard.isDown('left') then 24 | dx = -1 25 | elseif love.keyboard.isDown('right') then 26 | dx = 1 27 | end 28 | if love.keyboard.isDown('up') then 29 | dy = -1 30 | elseif love.keyboard.isDown('down') then 31 | dy = 1 32 | end 33 | dx,dy = vector.normalize(dx, dy) 34 | 35 | player.velx, player.vely = vector.add(player.velx, player.vely, 36 | vector.mul(dy, dx, dy)) 37 | 38 | if vector.len(player.velx, player.vely) > player.max_velocity then 39 | player.velx, player.vely = vector.mul(player.max_velocity, 40 | vector.normalize(player.velx, player.vely) 41 | end 42 | 43 | player.x = player.x + dt * player.velx 44 | player.y = player.y + dt * player.vely 45 | end 46 | 47 | List of Functions 48 | ----------------- 49 | 50 | * :func:`vector.str(x,y) ` 51 | * :func:`vector.fromPolar(angle, radius) ` 52 | * :func:`vector.toPolar(x, y) ` 53 | * :func:`vector.randomDirection(len_min, len_max) ` 54 | * :func:`vector.mul(s, x,y) ` 55 | * :func:`vector.div(s, x,y) ` 56 | * :func:`vector.idiv(s, x,y) ` 57 | * :func:`vector.add(x1,y1, x2,y2) ` 58 | * :func:`vector.sub(x1,y1, x2,y2) ` 59 | * :func:`vector.permul(x1,y1, x2,y2) ` 60 | * :func:`vector.dot(x1,y1, x2,y2) ` 61 | * :func:`vector.cross(x1,y1, x2,y2) ` 62 | * :func:`vector.vector.det(x1,y1, x2,y2) ` 63 | * :func:`vector.eq(x1,y1, x2,y2) ` 64 | * :func:`vector.le(x1,y1, x2,y2) ` 65 | * :func:`vector.lt(x1,y1, x2,y2) ` 66 | * :func:`vector.len(x,y) ` 67 | * :func:`vector.len2(x,y) ` 68 | * :func:`vector.dist(x1,y1, x2,y2) ` 69 | * :func:`vector.dist2(x1,y1, x2,y2) ` 70 | * :func:`vector.normalize(x,y) ` 71 | * :func:`vector.rotate(phi, x,y) ` 72 | * :func:`vector.perpendicular(x,y) ` 73 | * :func:`vector.project(x,y, u,v) ` 74 | * :func:`vector.mirror(x,y, u,v) ` 75 | * :func:`vector.angleTo(ox,y, u,v) ` 76 | * :func:`vector.trim(max_length, x,y) ` 77 | 78 | Function Reference 79 | ------------------ 80 | 81 | .. function:: vector.str(x,y) 82 | 83 | :param numbers x,y: The vector. 84 | :returns: The string representation. 85 | 86 | 87 | Produce a human-readable string of the form ``(x,y)``. 88 | Useful for debugging. 89 | 90 | **Example**:: 91 | 92 | print(vector.str(love.mouse.getPosition())) 93 | 94 | 95 | .. function:: vector.fromPolar(angle, radius) 96 | 97 | :param number angle: Angle of the vector in radians. 98 | :param number radius: Length of the vector (optional, default = 1). 99 | :returns: ``x``, ``y``: The vector in cartesian coordinates. 100 | 101 | 102 | Convert polar coordinates to cartesian coordinates. 103 | The ``angle`` is measured against the vector (1,0), i.e., the x axis. 104 | 105 | **Examples**:: 106 | 107 | x,y = vector.polar(math.pi,10) 108 | 109 | 110 | .. function:: vector.toPolar(x, y) 111 | 112 | :param numbers x,y: A vector. 113 | :returns: ``angle``, ``radius``: The vector in polar coordinates. 114 | 115 | Convert the vector to polar coordinates, i.e., the angle and the radius/lenth. 116 | 117 | **Example**:: 118 | 119 | -- complex multiplication 120 | phase1, abs1 = vector.toPolar(re1, im1) 121 | phase2, abs2 = vector.toPolar(re2, im2) 122 | 123 | vector.fromPolar(phase1+phase2, abs1*abs2) 124 | 125 | .. function:: vector.randomDirection(len_min, len_max) 126 | 127 | :param number len_min: Minimum length of the vector (optional, default = 1). 128 | :param number len_max: Maximum length of the vector (optional, default = ``len_min``). 129 | :returns: ``x``, ``y``: A vector pointing in a random direction with a random length between ``len_min`` and ``len_max``. 130 | 131 | Sample a vector with random direction and (optional) length. 132 | 133 | **Examples**:: 134 | 135 | x,y = vector.randomDirection() -- length is 1 136 | x,y = vector.randomDirection(1,5) -- length is a random value between 1 and 5 137 | x,y = vector.randomDirection(100) -- length is 100 138 | 139 | 140 | .. function:: vector.mul(s, x,y) 141 | 142 | :param number s: A scalar. 143 | :param numbers x,y: A vector. 144 | :returns: ``x*s, y*s``. 145 | 146 | 147 | Computes ``x*s,y*s``. The order of arguments is chosen so that it's possible to 148 | chain operations (see example). 149 | 150 | **Example**:: 151 | 152 | velx,vely = vec.mul(dt, vec.add(velx,vely, accx,accy)) 153 | 154 | 155 | .. function:: vector.div(s, x,y) 156 | 157 | :param number s: A scalar. 158 | :param numbers x,y: A vector. 159 | :returns: ``x/s, y/s``. 160 | 161 | 162 | Computes ``x/s,y/s``. The order of arguments is chosen so that it's possible to 163 | chain operations (see example). 164 | 165 | **Example**:: 166 | 167 | x,y = vec.div(self.zoom, vec.sub(x,y, w/2,h/2)) 168 | x,y = vec.div(self.zoom, x-w/2, y-h/2) 169 | 170 | 171 | .. function:: vector.idiv(s, x,y) 172 | 173 | :param number s: A scalar. 174 | :param numbers x,y: A vector. 175 | :returns: ``x//s, y//s``. 176 | 177 | 178 | Computes integer division ``x//s,y//s`` (only Lua 5.3 and up). The order of 179 | arguments is chosen so that it's possible to chain operations (see example). 180 | 181 | **Example**:: 182 | 183 | i,k = vec.idiv(grid.cellsize, x,y) 184 | i,k = vec.idiv(grid.cellsize, love.mouse.getPosition()) 185 | 186 | 187 | .. function:: vector.add(x1,y1, x2,y2) 188 | 189 | :param numbers x1,y1: First vector. 190 | :param numbers x2,y2: Second vector. 191 | :returns: ``x1+x2, x1+x2``. 192 | 193 | 194 | Computes the sum \\((x1+x2, y1+y2)\\)`` of two vectors. Meant to be used in 195 | conjunction with other functions like :func:`vector.mul`. 196 | 197 | **Example**:: 198 | 199 | player.x,player.y = vector.add(player.x,player.y, vector.mul(dt, dx,dy)) 200 | 201 | 202 | .. function:: vector.sub(x1,y1, x2,y2) 203 | 204 | :param numbers x1,y1: First vector. 205 | :param numbers x2,y2: Second vector. 206 | :returns: ``x1-x2, x1-x2``. 207 | 208 | 209 | Computes the difference \\((x1-x2, y1-y2)\\) of two vectors. Meant to be used in 210 | conjunction with other functions like :func:`vector.mul`. 211 | 212 | **Example**:: 213 | 214 | dx,dy = vector.sub(400,300, love.mouse.getPosition()) 215 | 216 | 217 | .. function:: vector.permul(x1,y1, x2,y2) 218 | 219 | :param numbers x1,y1: First vector. 220 | :param numbers x2,y2: Second vector. 221 | :returns: ``x1*x2, y1*y2``. 222 | 223 | 224 | Component-wise multiplication, i.e.: ``x1*x2, y1*y2``. 225 | 226 | **Example**:: 227 | 228 | x,y = vector.permul(x,y, 1,1.5) 229 | 230 | 231 | .. function:: vector.dot(x1,y1, x2,y2) 232 | 233 | :param numbers x1,y1: First vector. 234 | :param numbers x2,y2: Second vector. 235 | :returns: ``x1*x2 + y1*y2``. 236 | 237 | 238 | Computes the `dot product `_ of two 239 | vectors: ``x1*x2 + y1*y2``. 240 | 241 | **Example**:: 242 | 243 | cosphi = vector.dot(rx,ry, vx,vy) 244 | 245 | 246 | .. function:: vector.cross(x1,y1, x2,y2) 247 | 248 | :param numbers x1,y1: First vector. 249 | :param numbers x2,y2: Second vector. 250 | :returns: ``x1*y2 - y1*x2``. 251 | 252 | 253 | Computes the `cross product `_ of 254 | two vectors: ``x1*y2 - y1*x2``. 255 | 256 | **Example**:: 257 | 258 | parallelogram_area = vector.cross(ax,ay, bx,by) 259 | 260 | 261 | .. function:: vector.vector.det(x1,y1, x2,y2) 262 | 263 | :param numbers x1,y1: First vector. 264 | :param numbers x2,y2: Second vector. 265 | :returns: ``x1*y2 - y1*x2``. 266 | 267 | 268 | Alias to :func:`vector.cross`. 269 | 270 | **Example**:: 271 | 272 | parallelogram_area = vector.det(ax,ay, bx,by) 273 | 274 | 275 | .. function:: vector.eq(x1,y1, x2,y2) 276 | 277 | :param numbers x1,y1: First vector. 278 | :param numbers x2,y2: Second vector. 279 | :returns: ``x1 == x2 and y1 == y2`` 280 | 281 | Test for equality. 282 | 283 | **Example**:: 284 | 285 | if vector.eq(x1,y1, x2,y2) then be.happy() end 286 | 287 | 288 | .. function:: vector.le(x1,y1, x2,y2) 289 | 290 | :param numbers x1,y1: First vector. 291 | :param numbers x2,y2: Second vector. 292 | :returns: ``x1 <= x2 and y1 <= y2``. 293 | 294 | Test for partial lexicographical order, ``<=``. 295 | 296 | **Example**:: 297 | 298 | if vector.le(x1,y1, x2,y2) then be.happy() end 299 | 300 | 301 | .. function:: vector.lt(x1,y1, x2,y2) 302 | 303 | :param numbers x1,y1: First vector. 304 | :param numbers x2,y2: Second vector. 305 | :returns: ``x1 < x2 or (x1 == x2) and y1 <= y2``. 306 | 307 | 308 | Test for strict lexicographical order, ``<``. 309 | 310 | **Example**:: 311 | 312 | if vector.lt(x1,y1, x2,y2) then be.happy() end 313 | 314 | 315 | .. function:: vector.len(x,y) 316 | 317 | :param numbers x,y: The vector. 318 | :returns: Length of the vector. 319 | 320 | Get length of a vector, i.e. ``math.sqrt(x*x + y*y)``. 321 | 322 | **Example**:: 323 | 324 | distance = vector.len(love.mouse.getPosition()) 325 | 326 | 327 | .. function:: vector.len2(x,y) 328 | 329 | :param numbers x,y: The vector. 330 | :returns: Squared length of the vector. 331 | 332 | Get squared length of a vector, i.e. ``x*x + y*y``. 333 | 334 | **Example**:: 335 | 336 | -- get closest vertex to a given vector 337 | closest, dsq = vertices[1], vector.len2(px-vertices[1].x, py-vertices[1].y) 338 | for i = 2,#vertices do 339 | local temp = vector.len2(px-vertices[i].x, py-vertices[i].y) 340 | if temp < dsq then 341 | closest, dsq = vertices[i], temp 342 | end 343 | end 344 | 345 | 346 | .. function:: vector.dist(x1,y1, x2,y2) 347 | 348 | :param numbers x1,y1: First vector. 349 | :param numbers x2,y2: Second vector. 350 | :returns: The distance of the points. 351 | 352 | 353 | Get distance of two points. The same as ``vector.len(x1-x2, y1-y2)``. 354 | 355 | **Example**:: 356 | 357 | -- get closest vertex to a given vector 358 | -- slightly slower than the example using len2() 359 | closest, dist = vertices[1], vector.dist(px,py, vertices[1].x,vertices[1].y) 360 | for i = 2,#vertices do 361 | local temp = vector.dist(px,py, vertices[i].x,vertices[i].y) 362 | if temp < dist then 363 | closest, dist = vertices[i], temp 364 | end 365 | end 366 | 367 | 368 | .. function:: vector.dist2(x1,y1, x2,y2) 369 | 370 | :param numbers x1,y1: First vector. 371 | :param numbers x2,y2: Second vector. 372 | :returns: The squared distance of two points. 373 | 374 | Get squared distance of two points. The same as ``vector.len2(x1-x2, y1-y2)``. 375 | 376 | **Example**:: 377 | 378 | -- get closest vertex to a given vector 379 | closest, dsq = vertices[1], vector.dist2(px,py, vertices[1].x,vertices[1].y) 380 | for i = 2,#vertices do 381 | local temp = vector.dist2(px,py, vertices[i].x,vertices[i].y) 382 | if temp < dsq then 383 | closest, dsq = vertices[i], temp 384 | end 385 | end 386 | 387 | 388 | .. function:: vector.normalize(x,y) 389 | 390 | :param numbers x,y: The vector. 391 | :returns: Vector with same direction as the input vector, but length 1. 392 | 393 | 394 | Get normalized vector, i.e. a vector with the same direction as the input 395 | vector, but with length 1. 396 | 397 | **Example**:: 398 | 399 | dx,dy = vector.normalize(vx,vy) 400 | 401 | 402 | .. function:: vector.rotate(phi, x,y) 403 | 404 | :param number phi: Rotation angle in radians. 405 | :param numbers x,y: The vector. 406 | :returns: The rotated vector 407 | 408 | 409 | Get a rotated vector. 410 | 411 | **Example**:: 412 | 413 | -- approximate a circle 414 | circle = {} 415 | for i = 1,30 do 416 | local phi = 2 * math.pi * i / 30 417 | circle[i*2-1], circle[i*2] = vector.rotate(phi, 0,1) 418 | end 419 | 420 | 421 | .. function:: vector.perpendicular(x,y) 422 | 423 | :param numbers x,y: The vector. 424 | :returns: A vector perpendicular to the input vector 425 | 426 | 427 | Quick rotation by 90°. The same (but faster) as ``vector.rotate(math.pi/2, x,y)``. 428 | 429 | **Example**:: 430 | 431 | nx,ny = vector.normalize(vector.perpendicular(bx-ax, by-ay)) 432 | 433 | 434 | .. function:: vector.project(x,y, u,v) 435 | 436 | :param numbers x,y: The vector to project. 437 | :param numbers u,v: The vector to project onto. 438 | :returns: The projected vector. 439 | 440 | 441 | Project vector onto another vector. 442 | 443 | **Example**:: 444 | 445 | vx_p,vy_p = vector.project(vx,vy, ax,ay) 446 | 447 | 448 | .. function:: vector.mirror(x,y, u,v) 449 | 450 | :param numbers x,y: The vector to mirror. 451 | :param numbers u,v: The vector defining the axis. 452 | :returns: The mirrored vector. 453 | 454 | 455 | Mirrors vector on the axis defined by the other vector. 456 | 457 | **Example**:: 458 | 459 | vx,vy = vector.mirror(vx,vy, surface.x,surface.y) 460 | 461 | 462 | .. function:: vector.angleTo(ox,y, u,v) 463 | 464 | :param numbers x,y: Vector to measure the angle. 465 | :param numbers u,v (optional): Reference vector. 466 | :returns: Angle in radians. 467 | 468 | 469 | Measures the angle between two vectors. ``u`` and ``v`` default to ``0`` if omitted, 470 | i.e. the function returns the angle to the coordinate system. 471 | 472 | **Example**:: 473 | 474 | lean = vector.angleTo(self.upx, self.upy, 0,1) 475 | if lean > .1 then self:fallOver() end 476 | 477 | 478 | .. function:: vector.trim(max_length, x,y) 479 | 480 | :param number max_length: Maximum allowed length of the vector. 481 | :param numbers x,y: Vector to trim. 482 | :returns: The trimmed vector. 483 | 484 | Trim the vector to ``max_length``, i.e. return a vector that points in the same 485 | direction as the source vector, but has a magnitude smaller or equal to 486 | ``max_length``. 487 | 488 | **Example**:: 489 | 490 | vel_x, vel_y = vector.trim(299792458, 491 | vector.add(vel_x, vel_y, 492 | vector.mul(mass * dt, force_x, force_y))) 493 | -------------------------------------------------------------------------------- /docs/vector.rst: -------------------------------------------------------------------------------- 1 | hump.vector 2 | =========== 3 | 4 | :: 5 | 6 | vector = require "hump.vector" 7 | 8 | A handy 2D vector class providing most of the things you do with vectors. 9 | 10 | You can access the individual coordinates by ``vec.x`` and ``vec.y``. 11 | 12 | .. note:: 13 | 14 | The vectors are stored as tables. Most operations create new vectors and 15 | thus new tables, which *may* put the garbage collector under stress. 16 | If you experience slowdowns that are caused by hump.vector, try the 17 | table-less version :doc:`hump.vector-light `. 18 | 19 | **Example**:: 20 | 21 | function player:update(dt) 22 | local delta = vector(0,0) 23 | if love.keyboard.isDown('left') then 24 | delta.x = -1 25 | elseif love.keyboard.isDown('right') then 26 | delta.x = 1 27 | end 28 | if love.keyboard.isDown('up') then 29 | delta.y = -1 30 | elseif love.keyboard.isDown('down') then 31 | delta.y = 1 32 | end 33 | delta:normalizeInplace() 34 | 35 | player.velocity = player.velocity + delta * player.acceleration * dt 36 | 37 | if player.velocity:len() > player.max_velocity then 38 | player.velocity = player.velocity:normalized() * player.max_velocity 39 | end 40 | 41 | player.position = player.position + player.velocity * dt 42 | end 43 | 44 | List of Functions 45 | ----------------- 46 | 47 | * :func:`vector.new(x,y) ` 48 | * :func:`vector.fromPolar(angle, radius) ` 49 | * :func:`vector.randomDirection(len_min, len_max) ` 50 | * :func:`vector.isvector(v) ` 51 | * :func:`vector:clone() ` 52 | * :func:`vector:unpack() ` 53 | * :func:`vector:permul(other) ` 54 | * :func:`vector:len() ` 55 | * :func:`vector:toPolar() ` 56 | * :func:`vector:len2() ` 57 | * :func:`vector:dist(other) ` 58 | * :func:`vector:dist2(other) ` 59 | * :func:`vector:normalized() ` 60 | * :func:`vector:normalizeInplace() ` 61 | * :func:`vector:rotated(angle) ` 62 | * :func:`vector:rotateInplace(angle) ` 63 | * :func:`vector:perpendicular() ` 64 | * :func:`vector:projectOn(v) ` 65 | * :func:`vector:mirrorOn(v) ` 66 | * :func:`vector:cross(other) ` 67 | * :func:`vector:angleTo(other) ` 68 | * :func:`vector:trimmed(max_length) ` 69 | * :func:`vector:trimInplace(max_length) ` 70 | 71 | 72 | Vector arithmetic 73 | ----------------- 74 | 75 | **hump** provides vector arithmetic by implement the corresponding metamethods 76 | (``__add``, ``__mul``, etc.). Here are the semantics: 77 | 78 | ``vector + vector = vector`` 79 | Component wise sum: \\((a,b) + (x,y) = (a+x, b+y)\\) 80 | ``vector - vector = vector`` 81 | Component wise difference: \\((a,b) - (x,y) = (a-x, b-y)\\) 82 | ``vector * vector = number`` 83 | Dot product: \\((a,b) \\cdot (x,y) = a\\cdot x + b\\cdot y\\) 84 | ``number * vector = vector`` 85 | Scalar multiplication/scaling: \\((a,b) \\cdot s = (s\\cdot a, s\\cdot b)\\) 86 | ``vector * number = vector`` 87 | Scalar multiplication/scaling: \\(s \\cdot (x,y) = (s\\cdot x, s\\cdot y)\\) 88 | ``vector / number = vector`` 89 | Scalar division: \\((a,b) / s = (a/s, b/s)\\). 90 | ``vector // number = vector`` 91 | Scalar integer division (only Lua 5.3 and up): \\((a,b) // s = (a//s, b//s)\\). 92 | 93 | Common relations are also defined: 94 | 95 | ``a == b`` 96 | Same as ``a.x == b.x and a.y == b.y``. 97 | ``a <= b`` 98 | Same as ``a.x <= b.x and a.y <= b.y``. 99 | ``a < b`` 100 | Lexicographical order: ``a.x < b.x or (a.x == b.x and a.y < b.y)``. 101 | 102 | **Example**:: 103 | 104 | -- acceleration, player.velocity and player.position are vectors 105 | acceleration = vector(0,-9) 106 | player.velocity = player.velocity + acceleration * dt 107 | player.position = player.position + player.velocity * dt 108 | 109 | 110 | Function Reference 111 | ------------------ 112 | 113 | .. function:: vector.new(x,y) 114 | 115 | :param numbers x,y: Coordinates. 116 | :returns: The vector. 117 | 118 | 119 | Create a new vector. 120 | 121 | **Examples**:: 122 | 123 | a = vector.new(10,10) 124 | 125 | :: 126 | 127 | -- as a shortcut, you can call the module like a function: 128 | vector = require "hump.vector" 129 | a = vector(10,10) 130 | 131 | 132 | .. function:: vector.fromPolar(angle, radius) 133 | 134 | :param number angle: Angle of the vector in radians. 135 | :param number radius: Length of the vector (optional, default = 1). 136 | :returns: The vector in cartesian coordinates. 137 | 138 | 139 | Create a new vector from polar coordinates. 140 | The ``angle`` is measured against the vector (1,0), i.e., the x axis. 141 | 142 | **Examples**:: 143 | 144 | a = vector.polar(math.pi,10) 145 | 146 | .. function:: vector.randomDirection(len_min, len_max) 147 | 148 | :param number len_min: Minimum length of the vector (optional, default = 1). 149 | :param number len_max: Maximum length of the vector (optional, default = ``len_min``). 150 | :returns: A vector pointing in a random direction with a random length between ``len_min`` and ``len_max``. 151 | 152 | **Examples**:: 153 | 154 | rnd = vector.randomDirection() -- length is 1 155 | rnd = vector.randomDirection(100) -- length is 100 156 | rnd = vector.randomDirection(1,5) -- length is a random value between 1 and 5 157 | 158 | Sample a vector with random direction and (optional) length. 159 | 160 | .. function:: vector.isvector(v) 161 | 162 | :param mixed v: The variable to test. 163 | :returns: ``true`` if ``v`` is a vector, ``false`` otherwise. 164 | 165 | Test whether a variable is a vector. 166 | 167 | **Example**:: 168 | 169 | if not vector.isvector(v) then 170 | v = vector(v,0) 171 | end 172 | 173 | 174 | .. function:: vector:clone() 175 | 176 | :returns: Copy of the vector. 177 | 178 | Copy a vector. Assigning a vector to a variable will create a *reference*, so 179 | when modifying the vector referenced by the new variable would also change the 180 | old one:: 181 | 182 | a = vector(1,1) -- create vector 183 | b = a -- b references a 184 | c = a:clone() -- c is a copy of a 185 | b.x = 0 -- changes a,b and c 186 | print(a,b,c) -- prints '(1,0), (1,0), (1,1)' 187 | 188 | **Example**:: 189 | 190 | copy = original:clone() 191 | 192 | 193 | .. function:: vector:unpack() 194 | 195 | :returns: The coordinates ``x,y``. 196 | 197 | 198 | Extract coordinates. 199 | 200 | **Examples**:: 201 | 202 | x,y = pos:unpack() 203 | 204 | :: 205 | 206 | love.graphics.draw(self.image, self.pos:unpack()) 207 | 208 | 209 | .. function:: vector:permul(other) 210 | 211 | :param vector other: The second source vector. 212 | :returns: Vector whose components are products of the source vectors. 213 | 214 | 215 | Multiplies vectors coordinate wise, i.e. ``result = vector(a.x * b.x, a.y * 216 | b.y)``. 217 | 218 | Does not change either argument vectors, but creates a new one. 219 | 220 | **Example**:: 221 | 222 | -- scale with different magnitudes 223 | scaled = original:permul(vector(1,1.5)) 224 | 225 | 226 | .. function:: vector:len() 227 | 228 | :returns: Length of the vector. 229 | 230 | 231 | Get length of the vector, i.e. ``math.sqrt(vec.x * vec.x + vec.y * vec.y)``. 232 | 233 | **Example**:: 234 | 235 | distance = (a - b):len() 236 | 237 | 238 | .. function:: vector:toPolar() 239 | 240 | :returns: The vector in polar coordinates (angle, radius). 241 | 242 | Convert the vector to polar coordinates, i.e., the angle and the radius/lenth. 243 | 244 | **Example**:: 245 | 246 | -- complex multiplication 247 | p, q = a:toPolar(), b:toPolar() 248 | c = vector(p.x+q.x, p.y*q.y) 249 | 250 | 251 | .. function:: vector:len2() 252 | 253 | :returns: Squared length of the vector. 254 | 255 | 256 | Get squared length of the vector, i.e. ``vec.x * vec.x + vec.y * vec.y``. 257 | 258 | **Example**:: 259 | 260 | -- get closest vertex to a given vector 261 | closest, dsq = vertices[1], (pos - vertices[1]):len2() 262 | for i = 2,#vertices do 263 | local temp = (pos - vertices[i]):len2() 264 | if temp < dsq then 265 | closest, dsq = vertices[i], temp 266 | end 267 | end 268 | 269 | 270 | .. function:: vector:dist(other) 271 | 272 | :param vector other: Other vector to measure the distance to. 273 | :returns: The distance of the vectors. 274 | 275 | 276 | Get distance of two vectors. The same as ``(a - b):len()``. 277 | 278 | **Example**:: 279 | 280 | -- get closest vertex to a given vector 281 | -- slightly slower than the example using len2() 282 | closest, dist = vertices[1], pos:dist(vertices[1]) 283 | for i = 2,#vertices do 284 | local temp = pos:dist(vertices[i]) 285 | if temp < dist then 286 | closest, dist = vertices[i], temp 287 | end 288 | end 289 | 290 | 291 | .. function:: vector:dist2(other) 292 | 293 | :param vector other: Other vector to measure the distance to. 294 | :returns: The squared distance of the vectors. 295 | 296 | 297 | Get squared distance of two vectors. The same as ``(a - b):len2()``. 298 | 299 | **Example**:: 300 | 301 | -- get closest vertex to a given vector 302 | -- slightly faster than the example using len2() 303 | closest, dsq = vertices[1], pos:dist2(vertices[1]) 304 | for i = 2,#vertices do 305 | local temp = pos:dist2(vertices[i]) 306 | if temp < dsq then 307 | closest, dsq = vertices[i], temp 308 | end 309 | end 310 | 311 | 312 | .. function:: vector:normalized() 313 | 314 | :returns: Vector with same direction as the input vector, but length 1. 315 | 316 | 317 | Get normalized vector: a vector with the same direction as the input vector, 318 | but with length 1. 319 | 320 | Does not change the input vector, but creates a new vector. 321 | 322 | **Example**:: 323 | 324 | direction = velocity:normalized() 325 | 326 | 327 | .. function:: vector:normalizeInplace() 328 | 329 | :returns: Itself -- the normalized vector 330 | 331 | 332 | Normalize a vector, i.e. make the vector to have length 1. Great to use on 333 | intermediate results. 334 | 335 | .. warning:: 336 | This modifies the vector. If in doubt, use :func:`vector:normalized()`. 337 | 338 | **Example**:: 339 | 340 | normal = (b - a):perpendicular():normalizeInplace() 341 | 342 | 343 | .. function:: vector:rotated(angle) 344 | 345 | :param number angle: Rotation angle in radians. 346 | :returns: The rotated vector 347 | 348 | 349 | Get a vector with same length, but rotated by ``angle``: 350 | 351 | .. image:: _static/vector-rotated.png 352 | :alt: Sketch of rotated vector. 353 | 354 | Does not change the input vector, but creates a new vector. 355 | 356 | **Example**:: 357 | 358 | -- approximate a circle 359 | circle = {} 360 | for i = 1,30 do 361 | local phi = 2 * math.pi * i / 30 362 | circle[#circle+1] = vector(0,1):rotated(phi) 363 | end 364 | 365 | .. function:: vector:rotateInplace(angle) 366 | 367 | :param number angle: Rotation angle in radians. 368 | :returns: Itself -- the rotated vector 369 | 370 | 371 | Rotate a vector in-place. Great to use on intermediate results. 372 | 373 | .. warning:: 374 | This modifies the vector. If in doubt, use :func:`vector:rotated()`. 375 | 376 | **Example**:: 377 | 378 | -- ongoing rotation 379 | spawner.direction:rotateInplace(dt) 380 | 381 | 382 | .. function:: vector:perpendicular() 383 | 384 | :returns: A vector perpendicular to the input vector 385 | 386 | Quick rotation by 90°. Creates a new vector. The same (but faster) as 387 | ``vec:rotate(math.pi/2)``: 388 | 389 | .. image:: _static/vector-perpendicular.png 390 | :alt: Sketch of two perpendicular vectors 391 | 392 | **Example**:: 393 | 394 | normal = (b - a):perpendicular():normalizeInplace() 395 | 396 | 397 | 398 | .. function:: vector:projectOn(v) 399 | 400 | :param vector v: The vector to project on. 401 | :returns: ``vector`` The projected vector. 402 | 403 | 404 | Project vector onto another vector: 405 | 406 | .. image:: _static/vector-projectOn.png 407 | :alt: Sketch of vector projection. 408 | 409 | **Example**:: 410 | 411 | velocity_component = velocity:projectOn(axis) 412 | 413 | 414 | 415 | .. function:: vector:mirrorOn(v) 416 | 417 | :param vector v: The vector to mirror on. 418 | :returns: The mirrored vector. 419 | 420 | 421 | Mirrors vector on the axis defined by the other vector: 422 | 423 | .. image: _static/vector-mirrorOn.png 424 | :alt: Sketch of a vector mirrored on another vector 425 | 426 | **Example**:: 427 | 428 | deflected_velocity = ball.velocity:mirrorOn(surface_normal) 429 | 430 | 431 | .. function:: vector:cross(other) 432 | 433 | :param vector other: Vector to compute the cross product with. 434 | :returns: ``number`` Cross product of both vectors. 435 | 436 | 437 | Get cross product of two vectors. Equals the area of the parallelogram spanned 438 | by both vectors. 439 | 440 | **Example**:: 441 | 442 | parallelogram_area = a:cross(b) 443 | 444 | 445 | .. function:: vector:angleTo(other) 446 | 447 | :param vector other: Vector to measure the angle to (optional). 448 | :returns: Angle in radians. 449 | 450 | 451 | Measures the angle between two vectors. If ``other`` is omitted it defaults 452 | to the vector ``(0,0)``, i.e. the function returns the angle to the coordinate 453 | system. 454 | 455 | **Example**:: 456 | 457 | lean = self.upvector:angleTo(vector(0,1)) 458 | if lean > .1 then self:fallOver() end 459 | 460 | .. function:: vector:trimmed(max_length) 461 | 462 | :param number max_length: Maximum allowed length of the vector. 463 | :returns: A trimmed vector. 464 | 465 | Trim the vector to ``max_length``, i.e. return a vector that points in the same 466 | direction as the source vector, but has a magnitude smaller or equal to 467 | ``max_length``. 468 | 469 | Does not change the input vector, but creates a new vector. 470 | 471 | **Example**:: 472 | 473 | ship.velocity = ship.force * ship.mass * dt 474 | ship.velocity = ship.velocity:trimmed(299792458) 475 | 476 | 477 | .. function:: vector:trimInplace(max_length) 478 | 479 | :param number max_length: Maximum allowed length of the vector. 480 | :returns: Itself -- the trimmed vector. 481 | 482 | Trim the vector to ``max_length``, i.e. return a vector that points in the same 483 | direction as the source vector, but has a magnitude smaller or equal to 484 | ``max_length``. 485 | 486 | .. warning:: 487 | This modifies the vector. If in doubt, use :func:`vector:trimmed()`. 488 | 489 | 490 | **Example**:: 491 | 492 | ship.velocity = (ship.velocity + ship.force * ship.mass * dt):trimInplace(299792458) 493 | -------------------------------------------------------------------------------- /gamestate.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local function __NULL__() end 28 | 29 | -- default gamestate produces error on every callback 30 | local state_init = setmetatable({leave = __NULL__}, 31 | {__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end}) 32 | local stack = {state_init} 33 | local initialized_states = setmetatable({}, {__mode = "k"}) 34 | local state_is_dirty = true 35 | 36 | local GS = {} 37 | function GS.new(t) return t or {} end -- constructor - deprecated! 38 | 39 | local function change_state(stack_offset, to, ...) 40 | local pre = stack[#stack] 41 | 42 | -- initialize only on first call 43 | ;(initialized_states[to] or to.init or __NULL__)(to) 44 | initialized_states[to] = __NULL__ 45 | 46 | stack[#stack+stack_offset] = to 47 | state_is_dirty = true 48 | return (to.enter or __NULL__)(to, pre, ...) 49 | end 50 | 51 | function GS.switch(to, ...) 52 | assert(to, "Missing argument: Gamestate to switch to") 53 | assert(to ~= GS, "Can't call switch with colon operator") 54 | ;(stack[#stack].leave or __NULL__)(stack[#stack]) 55 | return change_state(0, to, ...) 56 | end 57 | 58 | function GS.push(to, ...) 59 | assert(to, "Missing argument: Gamestate to switch to") 60 | assert(to ~= GS, "Can't call push with colon operator") 61 | return change_state(1, to, ...) 62 | end 63 | 64 | function GS.pop(...) 65 | assert(#stack > 1, "No more states to pop!") 66 | local pre, to = stack[#stack], stack[#stack-1] 67 | stack[#stack] = nil 68 | ;(pre.leave or __NULL__)(pre) 69 | state_is_dirty = true 70 | return (to.resume or __NULL__)(to, pre, ...) 71 | end 72 | 73 | function GS.current() 74 | return stack[#stack] 75 | end 76 | 77 | -- XXX: don't overwrite love.errorhandler by default: 78 | -- this callback is different than the other callbacks 79 | -- (see http://love2d.org/wiki/love.errorhandler) 80 | -- overwriting thi callback can result in random crashes (issue #95) 81 | local all_callbacks = { 'draw', 'update' } 82 | 83 | -- fetch event callbacks from love.handlers 84 | for k in pairs(love.handlers) do 85 | all_callbacks[#all_callbacks+1] = k 86 | end 87 | 88 | function GS.registerEvents(callbacks) 89 | local registry = {} 90 | callbacks = callbacks or all_callbacks 91 | for _, f in ipairs(callbacks) do 92 | registry[f] = love[f] or __NULL__ 93 | love[f] = function(...) 94 | registry[f](...) 95 | return GS[f](...) 96 | end 97 | end 98 | end 99 | 100 | -- forward any undefined functions 101 | setmetatable(GS, {__index = function(_, func) 102 | -- call function only if at least one 'update' was called beforehand 103 | -- (see issue #46) 104 | if not state_is_dirty or func == 'update' then 105 | state_is_dirty = false 106 | return function(...) 107 | return (stack[#stack][func] or __NULL__)(stack[#stack], ...) 108 | end 109 | end 110 | return __NULL__ 111 | end}) 112 | 113 | return GS 114 | -------------------------------------------------------------------------------- /hump-0.4-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "hump" 2 | version = "0.4-2" 3 | source = { 4 | url = "git://github.com/vrld/hump" 5 | } 6 | description = { 7 | summary = "Lightweight game development utilities", 8 | detailed = [[Collection of independent components that implement common task needed in games: 9 | - Gamestates that can stack on each other (e.g., for menus) 10 | - Timers and Tweens with thread-like scripting support 11 | - Cameras with camera movement control (locking, smooth follow, etc) 12 | - 2D vector math 13 | - Signals and Slots 14 | - Prototype-based OOP helper 15 | ]], 16 | homepage = "https://hump.readthedocs.io", 17 | license = "MIT", 18 | } 19 | dependencies = { 20 | "lua >= 5.1" 21 | } 22 | build = { 23 | type = "builtin", 24 | modules = { 25 | ["hump.camera"] = "camera.lua", 26 | ["hump.class"] = "class.lua", 27 | ["hump.gamestate"] = "gamestate.lua", 28 | ["hump.signal"] = "signal.lua", 29 | ["hump.timer"] = "timer.lua", 30 | ["hump.vector"] = "vector.lua", 31 | ["hump.vector-light"] = "vector-light.lua" 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /signal.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2012-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local Registry = {} 28 | Registry.__index = function(self, key) 29 | return Registry[key] or (function() 30 | local t = {} 31 | rawset(self, key, t) 32 | return t 33 | end)() 34 | end 35 | 36 | function Registry:register(s, f) 37 | self[s][f] = f 38 | return f 39 | end 40 | 41 | function Registry:emit(s, ...) 42 | for f in pairs(self[s]) do 43 | f(...) 44 | end 45 | end 46 | 47 | function Registry:remove(s, ...) 48 | local f = {...} 49 | for i = 1,select('#', ...) do 50 | self[s][f[i]] = nil 51 | end 52 | end 53 | 54 | function Registry:clear(...) 55 | local s = {...} 56 | for i = 1,select('#', ...) do 57 | self[s[i]] = {} 58 | end 59 | end 60 | 61 | function Registry:emitPattern(p, ...) 62 | for s in pairs(self) do 63 | if s:match(p) then self:emit(s, ...) end 64 | end 65 | end 66 | 67 | function Registry:registerPattern(p, f) 68 | for s in pairs(self) do 69 | if s:match(p) then self:register(s, f) end 70 | end 71 | return f 72 | end 73 | 74 | function Registry:removePattern(p, ...) 75 | for s in pairs(self) do 76 | if s:match(p) then self:remove(s, ...) end 77 | end 78 | end 79 | 80 | function Registry:clearPattern(p) 81 | for s in pairs(self) do 82 | if s:match(p) then self[s] = {} end 83 | end 84 | end 85 | 86 | -- instancing 87 | function Registry.new() 88 | return setmetatable({}, Registry) 89 | end 90 | 91 | -- default instance 92 | local default = Registry.new() 93 | 94 | -- module forwards calls to default instance 95 | local module = {} 96 | for k in pairs(Registry) do 97 | if k ~= "__index" then 98 | module[k] = function(...) return default[k](default, ...) end 99 | end 100 | end 101 | 102 | return setmetatable(module, {__call = Registry.new}) 103 | -------------------------------------------------------------------------------- /spec/timer_spec.lua: -------------------------------------------------------------------------------- 1 | local timer = require 'timer'() 2 | 3 | describe('hump.timer', function() 4 | it('runs a function during a specified time', function() 5 | local delta, remaining 6 | 7 | timer:during(10, function(...) delta, remaining = ... end) 8 | 9 | timer:update(2) 10 | assert.are.equal(delta, 2) 11 | assert.are.equal(8, remaining) 12 | 13 | timer:update(5) 14 | assert.are.equal(delta, 5) 15 | assert.are.equal(3, remaining) 16 | 17 | timer:update(10) 18 | assert.are.equal(delta, 10) 19 | assert.are.equal(0, remaining) 20 | end) 21 | 22 | it('runs a function after a specified time', function() 23 | local finished1 = false 24 | local finished2 = false 25 | 26 | timer:after(3, function(...) finished1 = true end) 27 | timer:after(5, function(...) finished2 = true end) 28 | 29 | timer:update(4) 30 | assert.are.equal(true, finished1) 31 | assert.are.equal(false, finished2) 32 | 33 | timer:update(4) 34 | assert.are.equal(true, finished1) 35 | assert.are.equal(true, finished2) 36 | end) 37 | 38 | it('runs a function every so often', function() 39 | local count = 0 40 | 41 | timer:every(1, function(...) count = count + 1 end) 42 | 43 | timer:update(3) 44 | assert.are.equal(3, count) 45 | 46 | timer:update(7) 47 | assert.are.equal(10, count) 48 | end) 49 | 50 | it('can script timed events', function() 51 | local state 52 | 53 | timer:script(function(wait) 54 | state = 'foo' 55 | wait(1) 56 | state = 'bar' 57 | end) 58 | 59 | assert.are.equal('foo', state) 60 | timer:update(0.5) 61 | assert.are.equal('foo', state) 62 | timer:update(1) 63 | assert.are.equal('bar', state) 64 | end) 65 | 66 | it('cancels and clears timer functions', function() 67 | pending('to be tested...') 68 | end) 69 | 70 | it('tweens', function() 71 | pending('to be tested...') 72 | end) 73 | end) 74 | -------------------------------------------------------------------------------- /timer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local Timer = {} 28 | Timer.__index = Timer 29 | 30 | local function _nothing_() end 31 | 32 | local function updateTimerHandle(handle, dt) 33 | -- handle: { 34 | -- time = , 35 | -- after = , 36 | -- during = , 37 | -- limit = , 38 | -- count = , 39 | -- } 40 | handle.time = handle.time + dt 41 | handle.during(dt, math.max(handle.limit - handle.time, 0)) 42 | 43 | while handle.time >= handle.limit and handle.count > 0 do 44 | if handle.after(handle.after) == false then 45 | handle.count = 0 46 | break 47 | end 48 | handle.time = handle.time - handle.limit 49 | handle.count = handle.count - 1 50 | end 51 | end 52 | 53 | function Timer:update(dt) 54 | -- timers may create new timers, which leads to undefined behavior 55 | -- in pairs() - so we need to put them in a different table first 56 | local to_update = {} 57 | for handle in pairs(self.functions) do 58 | to_update[handle] = handle 59 | end 60 | 61 | for handle in pairs(to_update) do 62 | if self.functions[handle] then 63 | updateTimerHandle(handle, dt) 64 | if handle.count == 0 then 65 | self.functions[handle] = nil 66 | end 67 | end 68 | end 69 | end 70 | 71 | function Timer:during(delay, during, after) 72 | local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 } 73 | self.functions[handle] = true 74 | return handle 75 | end 76 | 77 | function Timer:after(delay, func) 78 | return self:during(delay, _nothing_, func) 79 | end 80 | 81 | function Timer:every(delay, after, count) 82 | local count = count or math.huge -- exploit below: math.huge - 1 = math.huge 83 | local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count } 84 | self.functions[handle] = true 85 | return handle 86 | end 87 | 88 | function Timer:cancel(handle) 89 | self.functions[handle] = nil 90 | end 91 | 92 | function Timer:clear() 93 | self.functions = {} 94 | end 95 | 96 | function Timer:script(f) 97 | local co = coroutine.wrap(f) 98 | co(function(t) 99 | self:after(t, co) 100 | coroutine.yield() 101 | end) 102 | end 103 | 104 | Timer.tween = setmetatable({ 105 | -- helper functions 106 | out = function(f) -- 'rotates' a function 107 | return function(s, ...) return 1 - f(1-s, ...) end 108 | end, 109 | chain = function(f1, f2) -- concatenates two functions 110 | return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end 111 | end, 112 | 113 | -- useful tweening functions 114 | linear = function(s) return s end, 115 | quad = function(s) return s*s end, 116 | cubic = function(s) return s*s*s end, 117 | quart = function(s) return s*s*s*s end, 118 | quint = function(s) return s*s*s*s*s end, 119 | sine = function(s) return 1-math.cos(s*math.pi/2) end, 120 | expo = function(s) return 2^(10*(s-1)) end, 121 | circ = function(s) return 1 - math.sqrt(1-s*s) end, 122 | 123 | back = function(s,bounciness) 124 | bounciness = bounciness or 1.70158 125 | return s*s*((bounciness+1)*s - bounciness) 126 | end, 127 | 128 | bounce = function(s) -- magic numbers ahead 129 | local a,b = 7.5625, 1/2.75 130 | 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) 131 | end, 132 | 133 | elastic = function(s, amp, period) 134 | amp, period = amp and math.max(1, amp) or 1, period or .3 135 | return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1)) 136 | end, 137 | }, { 138 | 139 | -- register new tween 140 | __call = function(tween, self, len, subject, target, method, after, ...) 141 | -- recursively collects fields that are defined in both subject and target into a flat list 142 | local function tween_collect_payload(subject, target, out) 143 | for k,v in pairs(target) do 144 | local ref = subject[k] 145 | assert(type(v) == type(ref), 'Type mismatch in field "'..k..'".') 146 | if type(v) == 'table' then 147 | tween_collect_payload(ref, v, out) 148 | else 149 | local ok, delta = pcall(function() return (v-ref)*1 end) 150 | assert(ok, 'Field "'..k..'" does not support arithmetic operations') 151 | out[#out+1] = {subject, k, delta} 152 | end 153 | end 154 | return out 155 | end 156 | 157 | method = tween[method or 'linear'] -- see __index 158 | local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...} 159 | 160 | local last_s = 0 161 | return self:during(len, function(dt) 162 | t = t + dt 163 | local s = method(math.min(1, t/len), unpack(args)) 164 | local ds = s - last_s 165 | last_s = s 166 | for _, info in ipairs(payload) do 167 | local ref, key, delta = unpack(info) 168 | ref[key] = ref[key] + delta * ds 169 | end 170 | end, after) 171 | end, 172 | 173 | -- fetches function and generated compositions for method `key` 174 | __index = function(tweens, key) 175 | if type(key) == 'function' then return key end 176 | 177 | assert(type(key) == 'string', 'Method must be function or string.') 178 | if rawget(tweens, key) then return rawget(tweens, key) end 179 | 180 | local function construct(pattern, f) 181 | local method = rawget(tweens, key:match(pattern)) 182 | if method then return f(method) end 183 | return nil 184 | end 185 | 186 | local out, chain = rawget(tweens,'out'), rawget(tweens,'chain') 187 | return construct('^in%-([^-]+)$', function(...) return ... end) 188 | or construct('^out%-([^-]+)$', out) 189 | or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end) 190 | or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end) 191 | or error('Unknown interpolation method: ' .. key) 192 | end}) 193 | 194 | -- Timer instancing 195 | function Timer.new() 196 | return setmetatable({functions = {}, tween = Timer.tween}, Timer) 197 | end 198 | 199 | -- default instance 200 | local default = Timer.new() 201 | 202 | -- module forwards calls to default instance 203 | local module = {} 204 | for k in pairs(Timer) do 205 | if k ~= "__index" then 206 | module[k] = function(...) return default[k](default, ...) end 207 | end 208 | end 209 | module.tween = setmetatable({}, { 210 | __index = Timer.tween, 211 | __newindex = function(k,v) Timer.tween[k] = v end, 212 | __call = function(t, ...) return default:tween(...) end, 213 | }) 214 | 215 | return setmetatable(module, {__call = Timer.new}) 216 | -------------------------------------------------------------------------------- /vector-light.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2012-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2 28 | 29 | local function str(x,y) 30 | return "("..tonumber(x)..","..tonumber(y)..")" 31 | end 32 | 33 | local function mul(s, x,y) 34 | return s*x, s*y 35 | end 36 | 37 | local function div(s, x,y) 38 | return x/s, y/s 39 | end 40 | 41 | local function add(x1,y1, x2,y2) 42 | return x1+x2, y1+y2 43 | end 44 | 45 | local function sub(x1,y1, x2,y2) 46 | return x1-x2, y1-y2 47 | end 48 | 49 | local function permul(x1,y1, x2,y2) 50 | return x1*x2, y1*y2 51 | end 52 | 53 | local function dot(x1,y1, x2,y2) 54 | return x1*x2 + y1*y2 55 | end 56 | 57 | local function det(x1,y1, x2,y2) 58 | return x1*y2 - y1*x2 59 | end 60 | 61 | local function eq(x1,y1, x2,y2) 62 | return x1 == x2 and y1 == y2 63 | end 64 | 65 | local function lt(x1,y1, x2,y2) 66 | return x1 < x2 or (x1 == x2 and y1 < y2) 67 | end 68 | 69 | local function le(x1,y1, x2,y2) 70 | return x1 <= x2 and y1 <= y2 71 | end 72 | 73 | local function len2(x,y) 74 | return x*x + y*y 75 | end 76 | 77 | local function len(x,y) 78 | return sqrt(x*x + y*y) 79 | end 80 | 81 | local function fromPolar(angle, radius) 82 | radius = radius or 1 83 | return cos(angle)*radius, sin(angle)*radius 84 | end 85 | 86 | local function randomDirection(len_min, len_max) 87 | len_min = len_min or 1 88 | len_max = len_max or len_min 89 | 90 | assert(len_max > 0, "len_max must be greater than zero") 91 | assert(len_max >= len_min, "len_max must be greater than or equal to len_min") 92 | 93 | return fromPolar(math.random()*2*math.pi, 94 | math.random() * (len_max-len_min) + len_min) 95 | end 96 | 97 | local function toPolar(x, y) 98 | return atan2(y,x), len(x,y) 99 | end 100 | 101 | local function dist2(x1,y1, x2,y2) 102 | return len2(x1-x2, y1-y2) 103 | end 104 | 105 | local function dist(x1,y1, x2,y2) 106 | return len(x1-x2, y1-y2) 107 | end 108 | 109 | local function normalize(x,y) 110 | local l = len(x,y) 111 | if l > 0 then 112 | return x/l, y/l 113 | end 114 | return x,y 115 | end 116 | 117 | local function rotate(phi, x,y) 118 | local c, s = cos(phi), sin(phi) 119 | return c*x - s*y, s*x + c*y 120 | end 121 | 122 | local function perpendicular(x,y) 123 | return -y, x 124 | end 125 | 126 | local function project(x,y, u,v) 127 | local s = (x*u + y*v) / (u*u + v*v) 128 | return s*u, s*v 129 | end 130 | 131 | local function mirror(x,y, u,v) 132 | local s = 2 * (x*u + y*v) / (u*u + v*v) 133 | return s*u - x, s*v - y 134 | end 135 | 136 | -- ref.: http://blog.signalsondisplay.com/?p=336 137 | local function trim(maxLen, x, y) 138 | local s = maxLen * maxLen / len2(x, y) 139 | s = s > 1 and 1 or math.sqrt(s) 140 | return x * s, y * s 141 | end 142 | 143 | local function angleTo(x,y, u,v) 144 | if u and v then 145 | return atan2(y, x) - atan2(v, u) 146 | end 147 | return atan2(y, x) 148 | end 149 | 150 | -- the module 151 | return { 152 | str = str, 153 | 154 | fromPolar = fromPolar, 155 | toPolar = toPolar, 156 | randomDirection = randomDirection, 157 | 158 | -- arithmetic 159 | mul = mul, 160 | div = div, 161 | idiv = idiv, 162 | add = add, 163 | sub = sub, 164 | permul = permul, 165 | dot = dot, 166 | det = det, 167 | cross = det, 168 | 169 | -- relation 170 | eq = eq, 171 | lt = lt, 172 | le = le, 173 | 174 | -- misc operations 175 | len2 = len2, 176 | len = len, 177 | dist2 = dist2, 178 | dist = dist, 179 | normalize = normalize, 180 | rotate = rotate, 181 | perpendicular = perpendicular, 182 | project = project, 183 | mirror = mirror, 184 | trim = trim, 185 | angleTo = angleTo, 186 | } 187 | -------------------------------------------------------------------------------- /vector.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local assert = assert 28 | local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2 29 | 30 | local vector = {} 31 | vector.__index = vector 32 | 33 | local function new(x,y) 34 | return setmetatable({x = x or 0, y = y or 0}, vector) 35 | end 36 | local zero = new(0,0) 37 | 38 | local function fromPolar(angle, radius) 39 | radius = radius or 1 40 | return new(cos(angle) * radius, sin(angle) * radius) 41 | end 42 | 43 | local function randomDirection(len_min, len_max) 44 | len_min = len_min or 1 45 | len_max = len_max or len_min 46 | 47 | assert(len_max > 0, "len_max must be greater than zero") 48 | assert(len_max >= len_min, "len_max must be greater than or equal to len_min") 49 | 50 | return fromPolar(math.random() * 2*math.pi, 51 | math.random() * (len_max-len_min) + len_min) 52 | end 53 | 54 | local function isvector(v) 55 | return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number' 56 | end 57 | 58 | function vector:clone() 59 | return new(self.x, self.y) 60 | end 61 | 62 | function vector:unpack() 63 | return self.x, self.y 64 | end 65 | 66 | function vector:__tostring() 67 | return "("..tonumber(self.x)..","..tonumber(self.y)..")" 68 | end 69 | 70 | function vector.__unm(a) 71 | return new(-a.x, -a.y) 72 | end 73 | 74 | function vector.__add(a,b) 75 | assert(isvector(a) and isvector(b), "Add: wrong argument types ( expected)") 76 | return new(a.x+b.x, a.y+b.y) 77 | end 78 | 79 | function vector.__sub(a,b) 80 | assert(isvector(a) and isvector(b), "Sub: wrong argument types ( expected)") 81 | return new(a.x-b.x, a.y-b.y) 82 | end 83 | 84 | function vector.__mul(a,b) 85 | if type(a) == "number" then 86 | return new(a*b.x, a*b.y) 87 | elseif type(b) == "number" then 88 | return new(b*a.x, b*a.y) 89 | else 90 | assert(isvector(a) and isvector(b), "Mul: wrong argument types ( or expected)") 91 | return a.x*b.x + a.y*b.y 92 | end 93 | end 94 | 95 | function vector.__div(a,b) 96 | assert(isvector(a) and type(b) == "number", "wrong argument types (expected / )") 97 | return new(a.x / b, a.y / b) 98 | end 99 | 100 | function vector.__eq(a,b) 101 | return a.x == b.x and a.y == b.y 102 | end 103 | 104 | function vector.__lt(a,b) 105 | return a.x < b.x or (a.x == b.x and a.y < b.y) 106 | end 107 | 108 | function vector.__le(a,b) 109 | return a.x <= b.x and a.y <= b.y 110 | end 111 | 112 | function vector.permul(a,b) 113 | assert(isvector(a) and isvector(b), "permul: wrong argument types ( expected)") 114 | return new(a.x*b.x, a.y*b.y) 115 | end 116 | 117 | function vector:toPolar() 118 | return new(atan2(self.x, self.y), self:len()) 119 | end 120 | 121 | function vector:len2() 122 | return self.x * self.x + self.y * self.y 123 | end 124 | 125 | function vector:len() 126 | return sqrt(self.x * self.x + self.y * self.y) 127 | end 128 | 129 | function vector.dist(a, b) 130 | assert(isvector(a) and isvector(b), "dist: wrong argument types ( expected)") 131 | local dx = a.x - b.x 132 | local dy = a.y - b.y 133 | return sqrt(dx * dx + dy * dy) 134 | end 135 | 136 | function vector.dist2(a, b) 137 | assert(isvector(a) and isvector(b), "dist: wrong argument types ( expected)") 138 | local dx = a.x - b.x 139 | local dy = a.y - b.y 140 | return (dx * dx + dy * dy) 141 | end 142 | 143 | function vector:normalizeInplace() 144 | local l = self:len() 145 | if l > 0 then 146 | self.x, self.y = self.x / l, self.y / l 147 | end 148 | return self 149 | end 150 | 151 | function vector:normalized() 152 | return self:clone():normalizeInplace() 153 | end 154 | 155 | function vector:rotateInplace(phi) 156 | local c, s = cos(phi), sin(phi) 157 | self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y 158 | return self 159 | end 160 | 161 | function vector:rotated(phi) 162 | local c, s = cos(phi), sin(phi) 163 | return new(c * self.x - s * self.y, s * self.x + c * self.y) 164 | end 165 | 166 | function vector:perpendicular() 167 | return new(-self.y, self.x) 168 | end 169 | 170 | function vector:projectOn(v) 171 | assert(isvector(v), "invalid argument: cannot project vector on " .. type(v)) 172 | -- (self * v) * v / v:len2() 173 | local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y) 174 | return new(s * v.x, s * v.y) 175 | end 176 | 177 | function vector:mirrorOn(v) 178 | assert(isvector(v), "invalid argument: cannot mirror vector on " .. type(v)) 179 | -- 2 * self:projectOn(v) - self 180 | local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y) 181 | return new(s * v.x - self.x, s * v.y - self.y) 182 | end 183 | 184 | function vector:cross(v) 185 | assert(isvector(v), "cross: wrong argument types ( expected)") 186 | return self.x * v.y - self.y * v.x 187 | end 188 | 189 | -- ref.: http://blog.signalsondisplay.com/?p=336 190 | function vector:trimInplace(maxLen) 191 | local s = maxLen * maxLen / self:len2() 192 | s = (s > 1 and 1) or math.sqrt(s) 193 | self.x, self.y = self.x * s, self.y * s 194 | return self 195 | end 196 | 197 | function vector:angleTo(other) 198 | if other then 199 | return atan2(self.y, self.x) - atan2(other.y, other.x) 200 | end 201 | return atan2(self.y, self.x) 202 | end 203 | 204 | function vector:trimmed(maxLen) 205 | return self:clone():trimInplace(maxLen) 206 | end 207 | 208 | 209 | -- the module 210 | return setmetatable({ 211 | new = new, 212 | fromPolar = fromPolar, 213 | randomDirection = randomDirection, 214 | isvector = isvector, 215 | zero = zero 216 | }, { 217 | __call = function(_, ...) return new(...) end 218 | }) 219 | --------------------------------------------------------------------------------