diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md index 57616c7..b445dcb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ :todoing +-isinlightrange still not working right -add body animations --optimize shadow body calculations and drawing methods # light_world.lua diff --git a/examples/gamera.lua b/examples/gamera.lua new file mode 100644 index 0000000..3fa2587 --- /dev/null +++ b/examples/gamera.lua @@ -0,0 +1,85 @@ +-- Example: Gamera Example +local gamera = require "examples/vendor/gamera" +local LightWorld = require "lib" + +function love.load() + x = 0 + y = 0 + scale = 1 + cam = gamera.new(0, 0, love.graphics.getWidth(), love.graphics.getHeight()) + + image = love.graphics.newImage("examples/gfx/machine2.png") + image_normal = love.graphics.newImage("examples/gfx/cone_normal.png") + normal = love.graphics.newImage("examples/gfx/refraction_normal.png") + glow = love.graphics.newImage("examples/gfx/machine2_glow.png") + + -- create light world + lightWorld = LightWorld({ + drawBackground = drawBackground, + drawForeground = drawForeground, + ambient = {55,55,55}, + refractionStrength = 32.0, + reflectionVisibility = 0.75, + }) + + -- create light + lightMouse = lightWorld:newLight(0, 0, 255, 127, 63, 300) + lightMouse:setGlowStrength(0.3) + + -- create shadow bodys + circleTest = lightWorld:newCircle(256, 256, 16) + rectangleTest = lightWorld:newRectangle(512, 512, 64, 64) + + imageTest = lightWorld:newImage(image, 64, 64, 24, 6) + imageTest:setNormalMap(image_normal) + imageTest:setGlowMap(glow) + imageTest:setOffset(12, -10) + + -- create body object + objectTest = lightWorld:newRefraction(normal, 64, 64, 128, 128) + objectTest:setReflection(true) +end + +function love.update(dt) + love.window.setTitle("Light vs. Shadow Engine (FPS:" .. love.timer.getFPS() .. ")") + + if love.keyboard.isDown("down") then + y = y - dt * 200 + elseif love.keyboard.isDown("up") then + y = y + dt * 200 + end + + if love.keyboard.isDown("right") then + x = x - dt * 200 + elseif love.keyboard.isDown("left") then + x = x + dt * 200 + end + + if love.keyboard.isDown("-") then + scale = scale - 0.01 + elseif love.keyboard.isDown("=") then + scale = scale + 0.01 + end + + lightMouse:setPosition((love.mouse.getX() - x)/scale, (love.mouse.getY() - y)/scale, z) + + cam:setScale(scale) + cam:setPosition(x, y) + lightWorld:setTranslation(x, y, scale) +end + +function love.draw() + cam:draw(function(l,t,w,h) + lightWorld:draw(function() + love.graphics.setColor(255, 255, 255) + love.graphics.rectangle("fill", -x/scale, -y/scale, love.graphics.getWidth()/scale, love.graphics.getHeight()/scale) + love.graphics.setColor(63, 255, 127) + local cx, cy = circleTest:getPosition() + love.graphics.circle("fill", cx, cy, circleTest:getRadius()) + love.graphics.polygon("fill", rectangleTest:getPoints()) + love.graphics.setColor(255, 255, 255) + love.graphics.draw(image, 64 - image:getWidth() * 0.5, 64 - image:getHeight() * 0.5) + end) + end) +end + diff --git a/examples/gfx/braid.png b/examples/gfx/braid.png new file mode 100644 index 0000000..7193775 Binary files /dev/null and b/examples/gfx/braid.png differ diff --git a/examples/gfx/braid_NRM.png b/examples/gfx/braid_NRM.png new file mode 100644 index 0000000..21aa04b Binary files /dev/null and b/examples/gfx/braid_NRM.png differ diff --git a/examples/hump.lua b/examples/hump.lua new file mode 100644 index 0000000..87964ed --- /dev/null +++ b/examples/hump.lua @@ -0,0 +1,84 @@ +-- Example: Hump Example +local Camera = require "examples/vendor/hump/camera" +local LightWorld = require "lib" + +function love.load() + x = 0 + y = 0 + scale = 1 + cam = Camera(love.graphics.getWidth()/2, love.graphics.getHeight()/2) + + image = love.graphics.newImage("examples/gfx/machine2.png") + image_normal = love.graphics.newImage("examples/gfx/cone_normal.png") + normal = love.graphics.newImage("examples/gfx/refraction_normal.png") + glow = love.graphics.newImage("examples/gfx/machine2_glow.png") + + -- create light world + lightWorld = LightWorld({ + drawBackground = drawBackground, + drawForeground = drawForeground, + ambient = {55,55,55}, + refractionStrength = 32.0, + reflectionVisibility = 0.75, + }) + + -- create light + lightMouse = lightWorld:newLight(0, 0, 255, 127, 63, 300) + lightMouse:setGlowStrength(0.3) + + -- create shadow bodys + circleTest = lightWorld:newCircle(256, 256, 16) + rectangleTest = lightWorld:newRectangle(512, 512, 64, 64) + + imageTest = lightWorld:newImage(image, 64, 64, 24, 6) + imageTest:setNormalMap(image_normal) + imageTest:setGlowMap(glow) + imageTest:setOffset(12, -10) + + -- create body object + objectTest = lightWorld:newRefraction(normal, 64, 64, 128, 128) + objectTest:setReflection(true) +end + +function love.update(dt) + love.window.setTitle("Light vs. Shadow Engine (FPS:" .. love.timer.getFPS() .. ")") + + if love.keyboard.isDown("down") then + y = y - dt * 200 + elseif love.keyboard.isDown("up") then + y = y + dt * 200 + end + + if love.keyboard.isDown("right") then + x = x - dt * 200 + elseif love.keyboard.isDown("left") then + x = x + dt * 200 + end + + if love.keyboard.isDown("-") then + scale = scale - 0.01 + elseif love.keyboard.isDown("=") then + scale = scale + 0.01 + end + + lightMouse:setPosition((love.mouse.getX() - x)/scale, (love.mouse.getY() - y)/scale, z) + + cam:lookAt(x, y) + cam:zoom(scale) + lightWorld:setTranslation(x, y, scale) +end + +function love.draw() + cam:attach() + lightWorld:draw(function() + love.graphics.setColor(255, 255, 255) + love.graphics.rectangle("fill", -x/scale, -y/scale, love.graphics.getWidth()/scale, love.graphics.getHeight()/scale) + love.graphics.setColor(63, 255, 127) + local cx, cy = circleTest:getPosition() + love.graphics.circle("fill", cx, cy, circleTest:getRadius()) + love.graphics.polygon("fill", rectangleTest:getPoints()) + love.graphics.setColor(255, 255, 255) + love.graphics.draw(image, 64 - image:getWidth() * 0.5, 64 - image:getHeight() * 0.5) + end) + cam:detach() +end diff --git a/examples/short.lua b/examples/short.lua index 58d5504..a6f50c8 100644 --- a/examples/short.lua +++ b/examples/short.lua @@ -1,6 +1,5 @@ -- Example: Short Example local LightWorld = require "lib" -local normal_map = require "lib/normal_map" function love.load() testShader = 0 diff --git a/examples/vendor/gamera.lua b/examples/vendor/gamera.lua new file mode 100644 index 0000000..ebbb330 --- /dev/null +++ b/examples/vendor/gamera.lua @@ -0,0 +1,208 @@ +-- gamera.lua v1.0.1 + +-- Copyright (c) 2012 Enrique García Cota +-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-- Based on YaciCode, from Julien Patte and LuaObject, from Sebastien Rocca-Serra + +local gamera = {} + +-- Private attributes and methods + +local gameraMt = {__index = gamera} +local abs, min, max = math.abs, math.min, math.max + +local function clamp(x, minX, maxX) + return x < minX and minX or (x>maxX and maxX or x) +end + +local function checkNumber(value, name) + if type(value) ~= 'number' then + error(name .. " must be a number (was: " .. tostring(value) .. ")") + end +end + +local function checkPositiveNumber(value, name) + if type(value) ~= 'number' or value <=0 then + error(name .. " must be a positive number (was: " .. tostring(value) ..")") + end +end + +local function checkAABB(l,t,w,h) + checkNumber(l, "l") + checkNumber(t, "t") + checkPositiveNumber(w, "w") + checkPositiveNumber(h, "h") +end + +local function getVisibleArea(self, scale) + scale = scale or self.scale + local sin, cos = abs(self.sin), abs(self.cos) + local w,h = self.w / scale, self.h / scale + w,h = cos*w + sin*h, sin*w + cos*h + return min(w,self.ww), min(h, self.wh) +end + +local function cornerTransform(self, x,y) + local scale, sin, cos = self.scale, self.sin, self.cos + x,y = x - self.x, y - self.y + x,y = -cos*x + sin*y, -sin*x - cos*y + return self.x - (x/scale + self.l), self.y - (y/scale + self.t) +end + +local function adjustPosition(self) + local wl,wt,ww,wh = self.wl, self.wt, self.ww, self.wh + local w,h = getVisibleArea(self) + local w2,h2 = w*0.5, h*0.5 + + local left, right = wl + w2, wl + ww - w2 + local top, bottom = wt + h2, wt + wh - h2 + + self.x, self.y = clamp(self.x, left, right), clamp(self.y, top, bottom) +end + +local function adjustScale(self) + local w,h,ww,wh = self.w, self.h, self.ww, self.wh + local rw,rh = getVisibleArea(self, 1) -- rotated frame: area around the window, rotated without scaling + local sx,sy = rw/ww, rh/wh -- vert/horiz scale: minimun scales that the window needs to occupy the world + local rscale = max(sx,sy) + + self.scale = max(self.scale, rscale) +end + +-- Public interface + +function gamera.new(l,t,w,h) + + local sw,sh = love.graphics.getWidth(), love.graphics.getHeight() + + local cam = setmetatable({ + x=0, y=0, + scale=1, + angle=0, sin=math.sin(0), cos=math.cos(0), + l=0, t=0, w=sw, h=sh, w2=sw*0.5, h2=sh*0.5 + }, gameraMt) + + cam:setWorld(l,t,w,h) + + return cam +end + +function gamera:setWorld(l,t,w,h) + checkAABB(l,t,w,h) + + self.wl, self.wt, self.ww, self.wh = l,t,w,h + + adjustPosition(self) +end + +function gamera:setWindow(l,t,w,h) + checkAABB(l,t,w,h) + + self.l, self.t, self.w, self.h, self.w2, self.h2 = l,t,w,h, w*0.5, h*0.5 + + adjustPosition(self) +end + +function gamera:setPosition(x,y) + checkNumber(x, "x") + checkNumber(y, "y") + + self.x, self.y = x,y + + adjustPosition(self) +end + +function gamera:setScale(scale) + checkNumber(scale, "scale") + + self.scale = scale + + adjustScale(self) + adjustPosition(self) +end + +function gamera:setAngle(angle) + checkNumber(angle, "angle") + + self.angle = angle + self.cos, self.sin = math.cos(angle), math.sin(angle) + + adjustScale(self) + adjustPosition(self) +end + +function gamera:getWorld() + return self.wl, self.wt, self.ww, self.wh +end + +function gamera:getWindow() + return self.l, self.t, self.w, self.h +end + +function gamera:getPosition() + return self.x, self.y +end + +function gamera:getScale() + return self.scale +end + +function gamera:getAngle() + return self.angle +end + +function gamera:getVisible() + local w,h = getVisibleArea(self) + return self.x - w*0.5, self.y - h*0.5, w, h +end + +function gamera:getVisibleCorners() + local x,y,w2,h2 = self.x, self.y, self.w2, self.h2 + + local x1,y1 = cornerTransform(self, x-w2,y-h2) + local x2,y2 = cornerTransform(self, x+w2,y-h2) + local x3,y3 = cornerTransform(self, x+w2,y+h2) + local x4,y4 = cornerTransform(self, x-w2,y+h2) + + return x1,y1,x2,y2,x3,y3,x4,y4 +end + +function gamera:draw(f) + love.graphics.setScissor(self:getWindow()) + + love.graphics.push() + local scale = self.scale + love.graphics.scale(scale) + + love.graphics.translate((self.w2 + self.l) / scale, (self.h2+self.t) / scale) + love.graphics.rotate(-self.angle) + love.graphics.translate(-self.x, -self.y) + + f(self:getVisible()) + + love.graphics.pop() + + love.graphics.setScissor() +end + +function gamera:toWorld(x,y) + local scale, sin, cos = self.scale, self.sin, self.cos + x,y = (x - self.w2 - self.l) / scale, (y - self.h2 - self.t) / scale + x,y = cos*x - sin*y, sin*x + cos*y + return x + self.x, y + self.y +end + +function gamera:toScreen(x,y) + local scale, sin, cos = self.scale, self.sin, self.cos + x,y = x - self.x, y - self.y + x,y = cos*x + sin*y, -sin*x + cos*y + return scale * x + self.w2 + self.l, scale * y + self.h2 + self.t +end + +return gamera + + + + diff --git a/examples/vendor/hump/README.md b/examples/vendor/hump/README.md new file mode 100644 index 0000000..899ee2f --- /dev/null +++ b/examples/vendor/hump/README.md @@ -0,0 +1,47 @@ +HUMP - Helper Utilities for Massive Progression +=============================================== + +__HUMP__ is a small collection of tools for developing games with LÖVE. + +Contents: +------------ + +* *gamestate.lua*: Easy gamestate management. +* *timer.lua*: Delayed and time-limited function calls and tweening functionality. +* *vector.lua*: 2D vector math. +* *vector-light.lua*: Lightweight 2D vector math (for optimisation purposes - leads to potentially ugly code). +* *class.lua*: Lightweight object orientation (class or prototype based). +* *signal.lua*: Simple Signal/Slot (aka. Observer) implementation. +* *camera.lua*: Move-, zoom- and rotatable camera. + +Documentation +============= + +You can find the documentation here: [http://vrld.github.com/hump/](http://vrld.github.com/hump/) + + +License +======= +> Copyright (c) 2010-2013 Matthias Richter +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> Except as contained in this notice, the name(s) of the above copyright holders +> shall not be used in advertising or otherwise to promote the sale, use or +> other dealings in this Software without prior written authorization. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/examples/vendor/hump/camera.lua b/examples/vendor/hump/camera.lua new file mode 100644 index 0000000..d40b823 --- /dev/null +++ b/examples/vendor/hump/camera.lua @@ -0,0 +1,117 @@ +--[[ +Copyright (c) 2010-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local _PATH = (...):match('^(.*[%./])[^%.%/]+$') or '' +local cos, sin = math.cos, math.sin + +local camera = {} +camera.__index = camera + +local function new(x,y, zoom, rot) + x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2 + zoom = zoom or 1 + rot = rot or 0 + return setmetatable({x = x, y = y, scale = zoom, rot = rot}, camera) +end + +function camera:lookAt(x,y) + self.x, self.y = x,y + return self +end + +function camera:move(x,y) + self.x, self.y = self.x + x, self.y + y + return self +end + +function camera:pos() + return self.x, self.y +end + +function camera:rotate(phi) + self.rot = self.rot + phi + return self +end + +function camera:rotateTo(phi) + self.rot = phi + return self +end + +function camera:zoom(mul) + self.scale = self.scale * mul + return self +end + +function camera:zoomTo(zoom) + self.scale = zoom + return self +end + +function camera:attach() + local cx,cy = love.graphics.getWidth()/(2*self.scale), love.graphics.getHeight()/(2*self.scale) + love.graphics.push() + love.graphics.scale(self.scale) + love.graphics.translate(cx, cy) + love.graphics.rotate(self.rot) + love.graphics.translate(-self.x, -self.y) +end + +function camera:detach() + love.graphics.pop() +end + +function camera:draw(func) + self:attach() + func() + self:detach() +end + +function camera:cameraCoords(x,y) + -- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center + local w,h = love.graphics.getWidth(), love.graphics.getHeight() + local c,s = cos(self.rot), sin(self.rot) + x,y = x - self.x, y - self.y + x,y = c*x - s*y, s*x + c*y + return x*self.scale + w/2, y*self.scale + h/2 +end + +function camera:worldCoords(x,y) + -- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y) + local w,h = love.graphics.getWidth(), love.graphics.getHeight() + local c,s = cos(-self.rot), sin(-self.rot) + x,y = (x - w/2) / self.scale, (y - h/2) / self.scale + x,y = c*x - s*y, s*x + c*y + return x+self.x, y+self.y +end + +function camera:mousepos() + return self:worldCoords(love.mouse.getPosition()) +end + +-- the module +return setmetatable({new = new}, + {__call = function(_, ...) return new(...) end}) diff --git a/examples/vendor/hump/class.lua b/examples/vendor/hump/class.lua new file mode 100644 index 0000000..20319f0 --- /dev/null +++ b/examples/vendor/hump/class.lua @@ -0,0 +1,94 @@ +--[[ +Copyright (c) 2010-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local function include_helper(to, from, seen) + if from == nil then + return to + elseif type(from) ~= 'table' then + return from + elseif seen[from] then + return seen[from] + end + + seen[from] = to + for k,v in pairs(from) do + k = include_helper({}, k, seen) -- keys might also be tables + if not to[k] then + to[k] = include_helper({}, v, seen) + end + end + return to +end + +-- deeply copies `other' into `class'. keys in `other' that are already +-- defined in `class' are omitted +local function include(class, other) + return include_helper(class, other, {}) +end + +-- returns a deep copy of `other' +local function clone(other) + return setmetatable(include({}, other), getmetatable(other)) +end + +local function new(class) + -- mixins + local inc = class.__includes or {} + if getmetatable(inc) then inc = {inc} end + + for _, other in ipairs(inc) do + include(class, other) + end + + -- class implementation + class.__index = class + class.init = class.init or class[1] or function() end + class.include = class.include or include + class.clone = class.clone or clone + + -- constructor call + return setmetatable(class, {__call = function(c, ...) + local o = setmetatable({}, c) + o:init(...) + return o + end}) +end + +-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons). +if class_commons ~= false and not common then + common = {} + function common.class(name, prototype, parent) + return new{__includes = {prototype, parent}} + end + function common.instance(class, ...) + return class(...) + end +end + + +-- the module +return setmetatable({new = new, include = include, clone = clone}, + {__call = function(_,...) return new(...) end}) diff --git a/examples/vendor/hump/gamestate.lua b/examples/vendor/hump/gamestate.lua new file mode 100644 index 0000000..c440180 --- /dev/null +++ b/examples/vendor/hump/gamestate.lua @@ -0,0 +1,97 @@ +--[[ +Copyright (c) 2010-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local function __NULL__() end + + -- default gamestate produces error on every callback +local state_init = setmetatable({leave = __NULL__}, + {__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end}) +local stack = {state_init} + +local GS = {} +function GS.new(t) return t or {} end -- constructor - deprecated! + +function GS.switch(to, ...) + assert(to, "Missing argument: Gamestate to switch to") + assert(to ~= GS, "Can't call switch with colon operator") + local pre = stack[#stack] + ;(pre.leave or __NULL__)(pre) + ;(to.init or __NULL__)(to) + to.init = nil + stack[#stack] = to + return (to.enter or __NULL__)(to, pre, ...) +end + +function GS.push(to, ...) + assert(to, "Missing argument: Gamestate to switch to") + assert(to ~= GS, "Can't call push with colon operator") + local pre = stack[#stack] + ;(to.init or __NULL__)(to) + to.init = nil + stack[#stack+1] = to + return (to.enter or __NULL__)(to, pre, ...) +end + +function GS.pop(...) + assert(#stack > 1, "No more states to pop!") + local pre = stack[#stack] + stack[#stack] = nil + ;(pre.leave or __NULL__)(pre) + return (stack[#stack].resume or __NULL__)(pre, ...) +end + +function GS.current() + return stack[#stack] +end + +local all_callbacks = { + 'draw', 'errhand', 'focus', 'keypressed', 'keyreleased', 'mousefocus', + 'mousepressed', 'mousereleased', 'quit', 'resize', 'textinput', + 'threaderror', 'update', 'visible', 'gamepadaxis', 'gamepadpressed', + 'gamepadreleased', 'joystickadded', 'joystickaxis', 'joystickhat', + 'joystickpressed', 'joystickreleased', 'joystickremoved' +} + +function GS.registerEvents(callbacks) + local registry = {} + callbacks = callbacks or all_callbacks + for _, f in ipairs(callbacks) do + registry[f] = love[f] or __NULL__ + love[f] = function(...) + registry[f](...) + return GS[f](...) + end + end +end + +-- forward any undefined functions +setmetatable(GS, {__index = function(_, func) + return function(...) + return (stack[#stack][func] or __NULL__)(stack[#stack], ...) + end +end}) + +return GS diff --git a/examples/vendor/hump/signal.lua b/examples/vendor/hump/signal.lua new file mode 100644 index 0000000..8d039a8 --- /dev/null +++ b/examples/vendor/hump/signal.lua @@ -0,0 +1,95 @@ +--[[ +Copyright (c) 2012-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local Registry = {} +Registry.__index = function(self, key) + return Registry[key] or (function() + local t = {} + rawset(self, key, t) + return t + end)() +end + +function Registry:register(s, f) + self[s][f] = f + return f +end + +function Registry:emit(s, ...) + for f in pairs(self[s]) do + f(...) + end +end + +function Registry:remove(s, ...) + local f = {...} + for i = 1,select('#', ...) do + self[s][f[i]] = nil + end +end + +function Registry:clear(...) + local s = {...} + for i = 1,select('#', ...) do + self[s[i]] = {} + end +end + +function Registry:emit_pattern(p, ...) + for s in pairs(self) do + if s:match(p) then self:emit(s, ...) end + end +end + +function Registry:remove_pattern(p, ...) + for s in pairs(self) do + if s:match(p) then self:remove(s, ...) end + end +end + +function Registry:clear_pattern(p) + for s in pairs(self) do + if s:match(p) then self[s] = {} end + end +end + +-- the module +local function new() + local registry = setmetatable({}, Registry) + + return setmetatable({ + new = new, + register = function(...) return registry:register(...) end, + emit = function(...) registry:emit(...) end, + remove = function(...) registry:remove(...) end, + clear = function(...) registry:clear(...) end, + emit_pattern = function(...) registry:emit_pattern(...) end, + remove_pattern = function(...) registry:remove_pattern(...) end, + clear_pattern = function(...) registry:clear_pattern(...) end, + }, {__call = new}) +end + +return new() diff --git a/examples/vendor/hump/timer.lua b/examples/vendor/hump/timer.lua new file mode 100644 index 0000000..a8b3e34 --- /dev/null +++ b/examples/vendor/hump/timer.lua @@ -0,0 +1,188 @@ +--[[ +Copyright (c) 2010-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local Timer = {} +Timer.__index = Timer + +local function _nothing_() end + +function Timer:update(dt) + local to_remove = {} + for handle, delay in pairs(self.functions) do + delay = delay - dt + if delay <= 0 then + to_remove[#to_remove+1] = handle + end + self.functions[handle] = delay + handle.func(dt, delay) + end + for _,handle in ipairs(to_remove) do + self.functions[handle] = nil + handle.after(handle.after) + end +end + +function Timer:do_for(delay, func, after) + local handle = {func = func, after = after or _nothing_} + self.functions[handle] = delay + return handle +end + +function Timer:add(delay, func) + return self:do_for(delay, _nothing_, func) +end + +function Timer:addPeriodic(delay, func, count) + local count, handle = count or math.huge -- exploit below: math.huge - 1 = math.huge + + handle = self:add(delay, function(f) + if func(func) == false then return end + count = count - 1 + if count > 0 then + self.functions[handle] = delay + end + end) + return handle +end + +function Timer:cancel(handle) + self.functions[handle] = nil +end + +function Timer:clear() + self.functions = {} +end + +Timer.tween = setmetatable({ + -- helper functions + out = function(f) -- 'rotates' a function + return function(s, ...) return 1 - f(1-s, ...) end + end, + chain = function(f1, f2) -- concatenates two functions + return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end + end, + + -- useful tweening functions + linear = function(s) return s end, + quad = function(s) return s*s end, + cubic = function(s) return s*s*s end, + quart = function(s) return s*s*s*s end, + quint = function(s) return s*s*s*s*s end, + sine = function(s) return 1-math.cos(s*math.pi/2) end, + expo = function(s) return 2^(10*(s-1)) end, + circ = function(s) return 1 - math.sqrt(1-s*s) end, + + back = function(s,bounciness) + bounciness = bounciness or 1.70158 + return s*s*((bounciness+1)*s - bounciness) + end, + + bounce = function(s) -- magic numbers ahead + local a,b = 7.5625, 1/2.75 + return math.min(a*s^2, a*(s-1.5*b)^2 + .75, a*(s-2.25*b)^2 + .9375, a*(s-2.625*b)^2 + .984375) + end, + + elastic = function(s, amp, period) + amp, period = amp and math.max(1, amp) or 1, period or .3 + return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1)) + end, +}, { + +-- register new tween +__call = function(tween, self, len, subject, target, method, after, ...) + -- recursively collects fields that are defined in both subject and target into a flat list + local function tween_collect_payload(subject, target, out) + for k,v in pairs(target) do + local ref = subject[k] + assert(type(v) == type(ref), 'Type mismatch in field "'..k..'".') + if type(v) == 'table' then + tween_collect_payload(ref, v, out) + else + local ok, delta = pcall(function() return (v-ref)*1 end) + assert(ok, 'Field "'..k..'" does not support arithmetic operations') + out[#out+1] = {subject, k, delta} + end + end + return out + end + + method = tween[method or 'linear'] -- see __index + local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...} + + local last_s = 0 + return self:do_for(len, function(dt) + t = t + dt + local s = method(math.min(1, t/len), unpack(args)) + local ds = s - last_s + last_s = s + for _, info in ipairs(payload) do + local ref, key, delta = unpack(info) + ref[key] = ref[key] + delta * ds + end + end, after) +end, + +-- fetches function and generated compositions for method `key` +__index = function(tweens, key) + if type(key) == 'function' then return key end + + assert(type(key) == 'string', 'Method must be function or string.') + if rawget(tweens, key) then return rawget(tweens, key) end + + local function construct(pattern, f) + local method = rawget(tweens, key:match(pattern)) + if method then return f(method) end + return nil + end + + local out, chain = rawget(tweens,'out'), rawget(tweens,'chain') + return construct('^in%-([^-]+)$', function(...) return ... end) + or construct('^out%-([^-]+)$', out) + or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end) + or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end) + or error('Unknown interpolation method: ' .. key) +end}) + +-- the module +local function new() + local timer = setmetatable({functions = {}, tween = Timer.tween}, Timer) + return setmetatable({ + new = new, + update = function(...) return timer:update(...) end, + do_for = function(...) return timer:do_for(...) end, + add = function(...) return timer:add(...) end, + addPeriodic = function(...) return timer:addPeriodic(...) end, + cancel = function(...) return timer:cancel(...) end, + clear = function(...) return timer:clear(...) end, + tween = setmetatable({}, { + __index = Timer.tween, + __newindex = function(_,k,v) Timer.tween[k] = v end, + __call = function(t,...) return timer:tween(...) end, + }) + }, {__call = new}) +end + +return new() diff --git a/examples/vendor/hump/vector-light.lua b/examples/vendor/hump/vector-light.lua new file mode 100644 index 0000000..962c2c4 --- /dev/null +++ b/examples/vendor/hump/vector-light.lua @@ -0,0 +1,161 @@ +--[[ +Copyright (c) 2012-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2 + +local function str(x,y) + return "("..tonumber(x)..","..tonumber(y)..")" +end + +local function mul(s, x,y) + return s*x, s*y +end + +local function div(s, x,y) + return x/s, y/s +end + +local function add(x1,y1, x2,y2) + return x1+x2, y1+y2 +end + +local function sub(x1,y1, x2,y2) + return x1-x2, y1-y2 +end + +local function permul(x1,y1, x2,y2) + return x1*x2, y1*y2 +end + +local function dot(x1,y1, x2,y2) + return x1*x2 + y1*y2 +end + +local function det(x1,y1, x2,y2) + return x1*y2 - y1*x2 +end + +local function eq(x1,y1, x2,y2) + return x1 == x2 and y1 == y2 +end + +local function lt(x1,y1, x2,y2) + return x1 < x2 or (x1 == x2 and y1 < y2) +end + +local function le(x1,y1, x2,y2) + return x1 <= x2 and y1 <= y2 +end + +local function len2(x,y) + return x*x + y*y +end + +local function len(x,y) + return sqrt(x*x + y*y) +end + +local function dist2(x1,y1, x2,y2) + return len2(x1-x2, y1-y2) +end + +local function dist(x1,y1, x2,y2) + return len(x1-x2, y1-y2) +end + +local function normalize(x,y) + local l = len(x,y) + if l > 0 then + return x/l, y/l + end + return x,y +end + +local function rotate(phi, x,y) + local c, s = cos(phi), sin(phi) + return c*x - s*y, s*x + c*y +end + +local function perpendicular(x,y) + return -y, x +end + +local function project(x,y, u,v) + local s = (x*u + y*v) / (u*u + v*v) + return s*u, s*v +end + +local function mirror(x,y, u,v) + local s = 2 * (x*u + y*v) / (u*u + v*v) + return s*u - x, s*v - y +end + +-- ref.: http://blog.signalsondisplay.com/?p=336 +local function trim(maxLen, x, y) + local s = maxLen * maxLen / len2(x, y) + s = s > 1 and 1 or math.sqrt(s) + return x * s, y * s +end + +local function angleTo(x,y, u,v) + if u and v then + return atan2(y, x) - atan2(v, u) + end + return atan2(y, x) +end + +-- the module +return { + str = str, + + -- arithmetic + mul = mul, + div = div, + add = add, + sub = sub, + permul = permul, + dot = dot, + det = det, + cross = det, + + -- relation + eq = eq, + lt = lt, + le = le, + + -- misc operations + len2 = len2, + len = len, + dist2 = dist2, + dist = dist, + normalize = normalize, + rotate = rotate, + perpendicular = perpendicular, + project = project, + mirror = mirror, + trim = trim, + angleTo = angleTo, +} diff --git a/examples/vendor/hump/vector.lua b/examples/vendor/hump/vector.lua new file mode 100644 index 0000000..9abdf34 --- /dev/null +++ b/examples/vendor/hump/vector.lua @@ -0,0 +1,191 @@ +--[[ +Copyright (c) 2010-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local assert = assert +local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2 + +local vector = {} +vector.__index = vector + +local function new(x,y) + return setmetatable({x = x or 0, y = y or 0}, vector) +end +local zero = new(0,0) + +local function isvector(v) + return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number' +end + +function vector:clone() + return new(self.x, self.y) +end + +function vector:unpack() + return self.x, self.y +end + +function vector:__tostring() + return "("..tonumber(self.x)..","..tonumber(self.y)..")" +end + +function vector.__unm(a) + return new(-a.x, -a.y) +end + +function vector.__add(a,b) + assert(isvector(a) and isvector(b), "Add: wrong argument types ( expected)") + return new(a.x+b.x, a.y+b.y) +end + +function vector.__sub(a,b) + assert(isvector(a) and isvector(b), "Sub: wrong argument types ( expected)") + return new(a.x-b.x, a.y-b.y) +end + +function vector.__mul(a,b) + if type(a) == "number" then + return new(a*b.x, a*b.y) + elseif type(b) == "number" then + return new(b*a.x, b*a.y) + else + assert(isvector(a) and isvector(b), "Mul: wrong argument types ( or expected)") + return a.x*b.x + a.y*b.y + end +end + +function vector.__div(a,b) + assert(isvector(a) and type(b) == "number", "wrong argument types (expected / )") + return new(a.x / b, a.y / b) +end + +function vector.__eq(a,b) + return a.x == b.x and a.y == b.y +end + +function vector.__lt(a,b) + return a.x < b.x or (a.x == b.x and a.y < b.y) +end + +function vector.__le(a,b) + return a.x <= b.x and a.y <= b.y +end + +function vector.permul(a,b) + assert(isvector(a) and isvector(b), "permul: wrong argument types ( expected)") + return new(a.x*b.x, a.y*b.y) +end + +function vector:len2() + return self.x * self.x + self.y * self.y +end + +function vector:len() + return sqrt(self.x * self.x + self.y * self.y) +end + +function vector.dist(a, b) + assert(isvector(a) and isvector(b), "dist: wrong argument types ( expected)") + local dx = a.x - b.x + local dy = a.y - b.y + return sqrt(dx * dx + dy * dy) +end + +function vector.dist2(a, b) + assert(isvector(a) and isvector(b), "dist: wrong argument types ( expected)") + local dx = a.x - b.x + local dy = a.y - b.y + return (dx * dx + dy * dy) +end + +function vector:normalize_inplace() + local l = self:len() + if l > 0 then + self.x, self.y = self.x / l, self.y / l + end + return self +end + +function vector:normalized() + return self:clone():normalize_inplace() +end + +function vector:rotate_inplace(phi) + local c, s = cos(phi), sin(phi) + self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y + return self +end + +function vector:rotated(phi) + local c, s = cos(phi), sin(phi) + return new(c * self.x - s * self.y, s * self.x + c * self.y) +end + +function vector:perpendicular() + return new(-self.y, self.x) +end + +function vector:projectOn(v) + assert(isvector(v), "invalid argument: cannot project vector on " .. type(v)) + -- (self * v) * v / v:len2() + local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y) + return new(s * v.x, s * v.y) +end + +function vector:mirrorOn(v) + assert(isvector(v), "invalid argument: cannot mirror vector on " .. type(v)) + -- 2 * self:projectOn(v) - self + local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y) + return new(s * v.x - self.x, s * v.y - self.y) +end + +function vector:cross(v) + assert(isvector(v), "cross: wrong argument types ( expected)") + return self.x * v.y - self.y * v.x +end + +-- ref.: http://blog.signalsondisplay.com/?p=336 +function vector:trim_inplace(maxLen) + local s = maxLen * maxLen / self:len2() + s = (s > 1 and 1) or math.sqrt(s) + self.x, self.y = self.x * s, self.y * s + return self +end + +function vector:angleTo(other) + if other then + return atan2(self.y, self.x) - atan2(other.y, other.x) + end + return atan2(self.y, self.x) +end + +function vector:trimmed(maxLen) + return self:clone():trim_inplace(maxLen) +end + + +-- the module +return setmetatable({new = new, isvector = isvector, zero = zero}, +{__call = function(_, ...) return new(...) end}) diff --git a/lib/anim8.lua b/lib/anim8.lua new file mode 100644 index 0000000..9197aec --- /dev/null +++ b/lib/anim8.lua @@ -0,0 +1,288 @@ +local anim8 = { + _VERSION = 'anim8 v2.1.0', + _DESCRIPTION = 'An animation library for LÖVE', + _URL = 'https://github.com/kikito/anim8', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2011 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +local Grid = {} + +local _frames = {} + +local function assertPositiveInteger(value, name) + if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end + if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end + if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end +end + +local function createFrame(self, x, y) + local fw, fh = self.frameWidth, self.frameHeight + return love.graphics.newQuad( + self.left + (x-1) * fw + x * self.border, + self.top + (y-1) * fh + y * self.border, + fw, + fh, + self.imageWidth, + self.imageHeight + ) +end + +local function getGridKey(...) + return table.concat( {...} ,'-' ) +end + +local function getOrCreateFrame(self, x, y) + if x < 1 or x > self.width or y < 1 or y > self.height then + error(("There is no frame for x=%d, y=%d"):format(x, y)) + end + local key = self._key + _frames[key] = _frames[key] or {} + _frames[key][x] = _frames[key][x] or {} + _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y) + return _frames[key][x][y] +end + +local function parseInterval(str) + if type(str) == "number" then return str,str,1 end + str = str:gsub('%s', '') -- remove spaces + local min, max = str:match("^(%d+)-(%d+)$") + assert(min and max, ("Could not parse interval from %q"):format(str)) + min, max = tonumber(min), tonumber(max) + local step = min <= max and 1 or -1 + return min, max, step +end + +function Grid:getFrames(...) + local result, args = {}, {...} + local minx, maxx, stepx, miny, maxy, stepy + + for i=1, #args, 2 do + minx, maxx, stepx = parseInterval(args[i]) + miny, maxy, stepy = parseInterval(args[i+1]) + for y = miny, maxy, stepy do + for x = minx, maxx, stepx do + result[#result+1] = getOrCreateFrame(self,x,y) + end + end + end + + return result +end + +local Gridmt = { + __index = Grid, + __call = Grid.getFrames +} + +local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) + assertPositiveInteger(frameWidth, "frameWidth") + assertPositiveInteger(frameHeight, "frameHeight") + assertPositiveInteger(imageWidth, "imageWidth") + assertPositiveInteger(imageHeight, "imageHeight") + + left = left or 0 + top = top or 0 + border = border or 0 + + local key = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) + + local grid = setmetatable( + { frameWidth = frameWidth, + frameHeight = frameHeight, + imageWidth = imageWidth, + imageHeight = imageHeight, + left = left, + top = top, + border = border, + width = math.floor(imageWidth/frameWidth), + height = math.floor(imageHeight/frameHeight), + _key = key + }, + Gridmt + ) + return grid +end + +----------------------------------------------------------- + +local Animation = {} + +local function cloneArray(arr) + local result = {} + for i=1,#arr do result[i] = arr[i] end + return result +end + +local function parseDurations(durations, frameCount) + local result = {} + if type(durations) == 'number' then + for i=1,frameCount do result[i] = durations end + else + local min, max, step + for key,duration in pairs(durations) do + assert(type(duration) == 'number', "The value [" .. tostring(duration) .. "] should be a number") + min, max, step = parseInterval(key) + for i = min,max,step do result[i] = duration end + end + end + + if #result < frameCount then + error("The durations table has length of " .. tostring(#result) .. ", but it should be >= " .. tostring(frameCount)) + end + + return result +end + +local function parseIntervals(durations) + local result, time = {0},0 + for i=1,#durations do + time = time + durations[i] + result[i+1] = time + end + return result, time +end + +local Animationmt = { __index = Animation } +local nop = function() end + +local function newAnimation(frames, durations, onLoop) + local td = type(durations); + if (td ~= 'number' or durations <= 0) and td ~= 'table' then + error("durations must be a positive number. Was " .. tostring(durations) ) + end + onLoop = onLoop or nop + durations = parseDurations(durations, #frames) + local intervals, totalDuration = parseIntervals(durations) + return setmetatable({ + frames = cloneArray(frames), + durations = durations, + intervals = intervals, + totalDuration = totalDuration, + onLoop = onLoop, + timer = 0, + position = 1, + status = "playing", + flippedH = false, + flippedV = false + }, + Animationmt + ) +end + +function Animation:clone() + local newAnim = newAnimation(self.frames, self.durations, self.onLoop) + newAnim.flippedH, newAnim.flippedV = self.flippedH, self.flippedV + return newAnim +end + +function Animation:flipH() + self.flippedH = not self.flippedH + return self +end + +function Animation:flipV() + self.flippedV = not self.flippedV + return self +end + +local function seekFrameIndex(intervals, timer) + local high, low, i = #intervals-1, 1, 1 + + while(low <= high) do + i = math.floor((low + high) / 2) + if timer > intervals[i+1] then low = i + 1 + elseif timer <= intervals[i] then high = i - 1 + else + return i + end + end + + return i +end + +function Animation:update(dt) + if self.status ~= "playing" then return end + + self.timer = self.timer + dt + local loops = math.floor(self.timer / self.totalDuration) + if loops ~= 0 then + self.timer = self.timer - self.totalDuration * loops + local f = type(self.onLoop) == 'function' and self.onLoop or self[self.onLoop] + f(self, loops) + end + + self.position = seekFrameIndex(self.intervals, self.timer) +end + +function Animation:pause() + self.status = "paused" +end + +function Animation:gotoFrame(position) + self.position = position + self.timer = self.intervals[self.position] +end + +function Animation:pauseAtEnd() + self.position = #self.frames + self.timer = self.totalDuration + self:pause() +end + +function Animation:pauseAtStart() + self.position = 1 + self.timer = 0 + self:pause() +end + +function Animation:resume() + self.status = "playing" +end + +function Animation:draw(image, x, y, r, sx, sy, ox, oy, ...) + local frame = self.frames[self.position] + if self.flippedH or self.flippedV then + r,sx,sy,ox,oy = r or 0, sx or 1, sy or 1, ox or 0, oy or 0 + local _,_,w,h = frame:getViewport() + + if self.flippedH then + sx = sx * -1 + ox = w - ox + end + if self.flippedV then + sy = sy * -1 + oy = h - oy + end + end + love.graphics.draw(image, frame, x, y, r, sx, sy, ox, oy, ...) +end + +----------------------------------------------------------- + +anim8.newGrid = newGrid +anim8.newAnimation = newAnimation + +return anim8 diff --git a/lib/init.lua b/lib/init.lua index 9b0785a..fe14315 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -224,8 +224,11 @@ function light_world:clear() light_world:clearBodies() end +function light_world:setTranslation(l, t, s) + self.l, self.t, self.s = l or self.l, t or self.t, s or self.s +end + function light_world:setScale(s) self.s = s end -function light_world:setTranslation(l, t, s) self.l, self.t, self.s = l, t, s end function light_world:clearLights() self.lights = {} end function light_world:clearBodies() self.body = {} end function light_world:setAmbientColor(red, green, blue) self.ambient = {red, green, blue} end