Develop #4

Merged
wariosolis merged 35 commits from develop into master 2017-01-04 00:04:34 +01:00
53 changed files with 7109 additions and 37 deletions
Showing only changes of commit 3693f69ed1 - Show all commits
+36
View File
@@ -0,0 +1,36 @@
-- Configuration
function love.conf(t)
t.identity = nil -- The name of the save directory (string)
t.version = "0.10.0" -- The LÖVE version this game was made for (string)
t.console = true -- Attach a console (boolean, Windows only)
t.title = "Shall I Kill Her" -- The title of the window the game is in (string)
t.window.icon = nil -- Filepath to an image to use as the window's icon (string)
t.window.width = 1280 -- The window width (number)
t.window.height = 720 -- The window height (number)
t.window.borderless = false -- Remove all border visuals from the window (boolean)
t.window.resizable = false -- Let the window be user-resizable (boolean)
t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
t.window.fullscreen = false -- Enable fullscreen (boolean)
t.window.fullscreentype = "desktop" -- Standard fullscreen or desktop fullscreen mode (string)
t.window.vsync = false -- Enable vertical sync (boolean)
t.window.fsaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
t.window.display = 1 -- Index of the monitor to show the window in (number)
t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean). Added in 0.9.1
t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean). Added in 0.9.1
t.modules.audio = true -- Enable the audio module (boolean)
t.modules.event = true -- Enable the event module (boolean)
t.modules.graphics = true -- Enable the graphics module (boolean)
t.modules.image = true -- Enable the image module (boolean)
t.modules.joystick = true -- Enable the joystick module (boolean)
t.modules.keyboard = true -- Enable the keyboard module (boolean)
t.modules.math = true -- Enable the math module (boolean)
t.modules.mouse = true -- Enable the mouse module (boolean)
t.modules.physics = true -- Enable the physics module (boolean)
t.modules.sound = true -- Enable the sound module (boolean)
t.modules.system = true -- Enable the system module (boolean)
t.modules.timer = true -- Enable the timer module (boolean)
t.modules.window = true -- Enable the window module (boolean)
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+44
View File
@@ -0,0 +1,44 @@
local game = require '../assets/scripts/game'
local stages = require '../assets/scripts/stages'
local controls = require '../assets/scripts/controls'
local bells = require '../assets/scripts/bells'
local camera = require '../assets/scripts/camera'
local stress = require '../assets/scripts/stress'
local messages = require '../assets/scripts/messages'
-- LOAD
function love.load()
game.load()
camera.load(game)
stages.load(game, camera)
stress.load(game, camera)
bells.load(game, camera)
controls.load(game)
messages.load(game)
end
-- UPDATE
function love.update(dt)
--require('assets/scripts/vendor/lovebird').update()
game.world:update(dt)
game.update(dt)
camera.update(game)
stages.update(dt, game, camera)
stress.update(dt, game, camera)
bells.update(dt, game, camera)
controls.update(dt, game, camera)
messages.update(dt)
end
-- DRAW
function love.draw()
camera.gcam:draw(
function(l,t,w,h)
stages.draw()
stress.draw()
bells.draw(game)
end
)
controls.draw()
messages.draw()
end
+456
View File
@@ -0,0 +1,456 @@
--[[
ProFi v1.3, by Luke Perkin 2012. MIT Licence http://www.opensource.org/licenses/mit-license.php.
Example:
ProFi = require 'ProFi'
ProFi:start()
some_function()
another_function()
coroutine.resume( some_coroutine )
ProFi:stop()
ProFi:writeReport( 'MyProfilingReport.txt' )
API:
*Arguments are specified as: type/name/default.
ProFi:start( string/once/nil )
ProFi:stop()
ProFi:checkMemory( number/interval/0, string/note/'' )
ProFi:writeReport( string/filename/'ProFi.txt' )
ProFi:reset()
ProFi:setHookCount( number/hookCount/0 )
ProFi:setGetTimeMethod( function/getTimeMethod/os.clock )
ProFi:setInspect( string/methodName, number/levels/1 )
]]
-----------------------
-- Locals:
-----------------------
local ProFi = {}
local onDebugHook, sortByDurationDesc, sortByCallCount, getTime
local DEFAULT_DEBUG_HOOK_COUNT = 0
local FORMAT_HEADER_LINE = "| %-50s: %-40s: %-20s: %-12s: %-12s: %-12s|\n"
local FORMAT_OUTPUT_LINE = "| %s: %-12s: %-12s: %-12s|\n"
local FORMAT_INSPECTION_LINE = "> %s: %-12s\n"
local FORMAT_TOTALTIME_LINE = "| TOTAL TIME = %f\n"
local FORMAT_MEMORY_LINE = "| %-20s: %-16s: %-16s| %s\n"
local FORMAT_HIGH_MEMORY_LINE = "H %-20s: %-16s: %-16sH %s\n"
local FORMAT_LOW_MEMORY_LINE = "L %-20s: %-16s: %-16sL %s\n"
local FORMAT_TITLE = "%-50.50s: %-40.40s: %-20s"
local FORMAT_LINENUM = "%4i"
local FORMAT_TIME = "%04.3f"
local FORMAT_RELATIVE = "%03.2f%%"
local FORMAT_COUNT = "%7i"
local FORMAT_KBYTES = "%7i Kbytes"
local FORMAT_MBYTES = "%7.1f Mbytes"
local FORMAT_MEMORY_HEADER1 = "\n=== HIGH & LOW MEMORY USAGE ===============================\n"
local FORMAT_MEMORY_HEADER2 = "=== MEMORY USAGE ==========================================\n"
local FORMAT_BANNER = [[
###############################################################################################################
##### ProFi, a lua profiler. This profile was generated on: %s
##### ProFi is created by Luke Perkin 2012 under the MIT Licence, www.locofilm.co.uk
##### Version 1.3. Get the most recent version at this gist: https://gist.github.com/2838755
###############################################################################################################
]]
-----------------------
-- Public Methods:
-----------------------
--[[
Starts profiling any method that is called between this and ProFi:stop().
Pass the parameter 'once' to so that this methodis only run once.
Example:
ProFi:start( 'once' )
]]
function ProFi:start( param )
if param == 'once' then
if self:shouldReturn() then
return
else
self.should_run_once = true
end
end
self.has_started = true
self.has_finished = false
self:resetReports( self.reports )
self:startHooks()
self.startTime = getTime()
end
--[[
Stops profiling.
]]
function ProFi:stop()
if self:shouldReturn() then
return
end
self.stopTime = getTime()
self:stopHooks()
self.has_finished = true
end
function ProFi:checkMemory( interval, note )
local time = getTime()
local interval = interval or 0
if self.lastCheckMemoryTime and time < self.lastCheckMemoryTime + interval then
return
end
self.lastCheckMemoryTime = time
local memoryReport = {
['time'] = time;
['memory'] = collectgarbage('count');
['note'] = note or '';
}
table.insert( self.memoryReports, memoryReport )
self:setHighestMemoryReport( memoryReport )
self:setLowestMemoryReport( memoryReport )
end
--[[
Writes the profile report to a file.
Param: [filename:string:optional] defaults to 'ProFi.txt' if not specified.
]]
function ProFi:writeReport( filename )
if #self.reports > 0 or #self.memoryReports > 0 then
filename = filename or 'ProFi.txt'
self:sortReportsWithSortMethod( self.reports, self.sortMethod )
self:writeReportsToFilename( filename )
print( string.format("[ProFi]\t Report written to %s", filename) )
end
end
--[[
Resets any profile information stored.
]]
function ProFi:reset()
self.reports = {}
self.reportsByTitle = {}
self.memoryReports = {}
self.highestMemoryReport = nil
self.lowestMemoryReport = nil
self.has_started = false
self.has_finished = false
self.should_run_once = false
self.lastCheckMemoryTime = nil
self.hookCount = self.hookCount or DEFAULT_DEBUG_HOOK_COUNT
self.sortMethod = self.sortMethod or sortByDurationDesc
self.inspect = nil
end
--[[
Set how often a hook is called.
See http://pgl.yoyo.org/luai/i/debug.sethook for information.
Param: [hookCount:number] if 0 ProFi counts every time a function is called.
if 2 ProFi counts every other 2 function calls.
]]
function ProFi:setHookCount( hookCount )
self.hookCount = hookCount
end
--[[
Set how the report is sorted when written to file.
Param: [sortType:string] either 'duration' or 'count'.
'duration' sorts by the time a method took to run.
'count' sorts by the number of times a method was called.
]]
function ProFi:setSortMethod( sortType )
if sortType == 'duration' then
self.sortMethod = sortByDurationDesc
elseif sortType == 'count' then
self.sortMethod = sortByCallCount
end
end
--[[
By default the getTime method is os.clock (CPU time),
If you wish to use other time methods pass it to this function.
Param: [getTimeMethod:function]
]]
function ProFi:setGetTimeMethod( getTimeMethod )
getTime = getTimeMethod
end
--[[
Allows you to inspect a specific method.
Will write to the report a list of methods that
call this method you're inspecting, you can optionally
provide a levels parameter to traceback a number of levels.
Params: [methodName:string] the name of the method you wish to inspect.
[levels:number:optional] the amount of levels you wish to traceback, defaults to 1.
]]
function ProFi:setInspect( methodName, levels )
if self.inspect then
self.inspect.methodName = methodName
self.inspect.levels = levels or 1
else
self.inspect = {
['methodName'] = methodName;
['levels'] = levels or 1;
}
end
end
-----------------------
-- Implementations methods:
-----------------------
function ProFi:shouldReturn( )
return self.should_run_once and self.has_finished
end
function ProFi:getFuncReport( funcInfo )
local title = self:getTitleFromFuncInfo( funcInfo )
local funcReport = self.reportsByTitle[ title ]
if not funcReport then
funcReport = self:createFuncReport( funcInfo )
self.reportsByTitle[ title ] = funcReport
table.insert( self.reports, funcReport )
end
return funcReport
end
function ProFi:getTitleFromFuncInfo( funcInfo )
local name = funcInfo.name or 'anonymous'
local source = funcInfo.short_src or 'C_FUNC'
local linedefined = funcInfo.linedefined or 0
linedefined = string.format( FORMAT_LINENUM, linedefined )
return string.format(FORMAT_TITLE, source, name, linedefined)
end
function ProFi:createFuncReport( funcInfo )
local name = funcInfo.name or 'anonymous'
local source = funcInfo.source or 'C Func'
local linedefined = funcInfo.linedefined or 0
local funcReport = {
['title'] = self:getTitleFromFuncInfo( funcInfo );
['count'] = 0;
['timer'] = 0;
}
return funcReport
end
function ProFi:startHooks()
debug.sethook( onDebugHook, 'cr', self.hookCount )
end
function ProFi:stopHooks()
debug.sethook()
end
function ProFi:sortReportsWithSortMethod( reports, sortMethod )
if reports then
table.sort( reports, sortMethod )
end
end
function ProFi:writeReportsToFilename( filename )
local file, err = io.open( filename, 'w' )
assert( file, err )
self:writeBannerToFile( file )
if #self.reports > 0 then
self:writeProfilingReportsToFile( self.reports, file )
end
if #self.memoryReports > 0 then
self:writeMemoryReportsToFile( self.memoryReports, file )
end
file:close()
end
function ProFi:writeProfilingReportsToFile( reports, file )
local totalTime = self.stopTime - self.startTime
local totalTimeOutput = string.format(FORMAT_TOTALTIME_LINE, totalTime)
file:write( totalTimeOutput )
local header = string.format( FORMAT_HEADER_LINE, "FILE", "FUNCTION", "LINE", "TIME", "RELATIVE", "CALLED" )
file:write( header )
for i, funcReport in ipairs( reports ) do
local timer = string.format(FORMAT_TIME, funcReport.timer)
local count = string.format(FORMAT_COUNT, funcReport.count)
local relTime = string.format(FORMAT_RELATIVE, (funcReport.timer / totalTime) * 100 )
local outputLine = string.format(FORMAT_OUTPUT_LINE, funcReport.title, timer, relTime, count )
file:write( outputLine )
if funcReport.inspections then
self:writeInpsectionsToFile( funcReport.inspections, file )
end
end
end
function ProFi:writeMemoryReportsToFile( reports, file )
file:write( FORMAT_MEMORY_HEADER1 )
self:writeHighestMemoryReportToFile( file )
self:writeLowestMemoryReportToFile( file )
file:write( FORMAT_MEMORY_HEADER2 )
for i, memoryReport in ipairs( reports ) do
local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_MEMORY_LINE )
file:write( outputLine )
end
end
function ProFi:writeHighestMemoryReportToFile( file )
local memoryReport = self.highestMemoryReport
local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_HIGH_MEMORY_LINE )
file:write( outputLine )
end
function ProFi:writeLowestMemoryReportToFile( file )
local memoryReport = self.lowestMemoryReport
local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_LOW_MEMORY_LINE )
file:write( outputLine )
end
function ProFi:formatMemoryReportWithFormatter( memoryReport, formatter )
local time = string.format(FORMAT_TIME, memoryReport.time)
local kbytes = string.format(FORMAT_KBYTES, memoryReport.memory)
local mbytes = string.format(FORMAT_MBYTES, memoryReport.memory/1024)
local outputLine = string.format(formatter, time, kbytes, mbytes, memoryReport.note)
return outputLine
end
function ProFi:writeBannerToFile( file )
local banner = string.format(FORMAT_BANNER, os.date())
file:write( banner )
end
function ProFi:writeInpsectionsToFile( inspections, file )
local inspectionsList = self:sortInspectionsIntoList( inspections )
file:write('\n==^ INSPECT ^======================================================================================================== COUNT ===\n')
for i, inspection in ipairs( inspectionsList ) do
local line = string.format(FORMAT_LINENUM, inspection.line)
local title = string.format(FORMAT_TITLE, inspection.source, inspection.name, line)
local count = string.format(FORMAT_COUNT, inspection.count)
local outputLine = string.format(FORMAT_INSPECTION_LINE, title, count )
file:write( outputLine )
end
file:write('===============================================================================================================================\n\n')
end
function ProFi:sortInspectionsIntoList( inspections )
local inspectionsList = {}
for k, inspection in pairs(inspections) do
inspectionsList[#inspectionsList+1] = inspection
end
table.sort( inspectionsList, sortByCallCount )
return inspectionsList
end
function ProFi:resetReports( reports )
for i, report in ipairs( reports ) do
report.timer = 0
report.count = 0
report.inspections = nil
end
end
function ProFi:shouldInspect( funcInfo )
return self.inspect and self.inspect.methodName == funcInfo.name
end
function ProFi:getInspectionsFromReport( funcReport )
local inspections = funcReport.inspections
if not inspections then
inspections = {}
funcReport.inspections = inspections
end
return inspections
end
function ProFi:getInspectionWithKeyFromInspections( key, inspections )
local inspection = inspections[key]
if not inspection then
inspection = {
['count'] = 0;
}
inspections[key] = inspection
end
return inspection
end
function ProFi:doInspection( inspect, funcReport )
local inspections = self:getInspectionsFromReport( funcReport )
local levels = 5 + inspect.levels
local currentLevel = 5
while currentLevel < levels do
local funcInfo = debug.getinfo( currentLevel, 'nS' )
if funcInfo then
local source = funcInfo.short_src or '[C]'
local name = funcInfo.name or 'anonymous'
local line = funcInfo.linedefined
local key = source..name..line
local inspection = self:getInspectionWithKeyFromInspections( key, inspections )
inspection.source = source
inspection.name = name
inspection.line = line
inspection.count = inspection.count + 1
currentLevel = currentLevel + 1
else
break
end
end
end
function ProFi:onFunctionCall( funcInfo )
local funcReport = ProFi:getFuncReport( funcInfo )
funcReport.callTime = getTime()
funcReport.count = funcReport.count + 1
if self:shouldInspect( funcInfo ) then
self:doInspection( self.inspect, funcReport )
end
end
function ProFi:onFunctionReturn( funcInfo )
local funcReport = ProFi:getFuncReport( funcInfo )
if funcReport.callTime then
funcReport.timer = funcReport.timer + (getTime() - funcReport.callTime)
end
end
function ProFi:setHighestMemoryReport( memoryReport )
if not self.highestMemoryReport then
self.highestMemoryReport = memoryReport
else
if memoryReport.memory > self.highestMemoryReport.memory then
self.highestMemoryReport = memoryReport
end
end
end
function ProFi:setLowestMemoryReport( memoryReport )
if not self.lowestMemoryReport then
self.lowestMemoryReport = memoryReport
else
if memoryReport.memory < self.lowestMemoryReport.memory then
self.lowestMemoryReport = memoryReport
end
end
end
-----------------------
-- Local Functions:
-----------------------
getTime = os.clock
onDebugHook = function( hookType )
local funcInfo = debug.getinfo( 2, 'nS' )
if hookType == "call" then
ProFi:onFunctionCall( funcInfo )
elseif hookType == "return" then
ProFi:onFunctionReturn( funcInfo )
end
end
sortByDurationDesc = function( a, b )
return a.timer > b.timer
end
sortByCallCount = function( a, b )
return a.count > b.count
end
-----------------------
-- Return Module:
-----------------------
ProFi:reset()
return ProFi
+208
View File
@@ -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
+47
View File
@@ -0,0 +1,47 @@
HUMP - Helper Utilities for Massive Progression
===============================================
__HUMP__ is a small collection of tools for developing games with L&Ouml;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.
+117
View File
@@ -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})
+94
View File
@@ -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})
+97
View File
@@ -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
+95
View File
@@ -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()
+188
View File
@@ -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()
+161
View File
@@ -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,
}
+191
View File
@@ -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 (<vector> expected)")
return new(a.x+b.x, a.y+b.y)
end
function vector.__sub(a,b)
assert(isvector(a) and isvector(b), "Sub: wrong argument types (<vector> expected)")
return new(a.x-b.x, a.y-b.y)
end
function vector.__mul(a,b)
if type(a) == "number" then
return new(a*b.x, a*b.y)
elseif type(b) == "number" then
return new(b*a.x, b*a.y)
else
assert(isvector(a) and isvector(b), "Mul: wrong argument types (<vector> or <number> expected)")
return a.x*b.x + a.y*b.y
end
end
function vector.__div(a,b)
assert(isvector(a) and type(b) == "number", "wrong argument types (expected <vector> / <number>)")
return new(a.x / b, a.y / b)
end
function vector.__eq(a,b)
return a.x == b.x and a.y == b.y
end
function vector.__lt(a,b)
return a.x < b.x or (a.x == b.x and a.y < b.y)
end
function vector.__le(a,b)
return a.x <= b.x and a.y <= b.y
end
function vector.permul(a,b)
assert(isvector(a) and isvector(b), "permul: wrong argument types (<vector> expected)")
return new(a.x*b.x, a.y*b.y)
end
function vector:len2()
return self.x * self.x + self.y * self.y
end
function vector:len()
return sqrt(self.x * self.x + self.y * self.y)
end
function vector.dist(a, b)
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
local dx = a.x - b.x
local dy = a.y - b.y
return sqrt(dx * dx + dy * dy)
end
function vector.dist2(a, b)
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
local dx = a.x - b.x
local dy = a.y - b.y
return (dx * dx + dy * dy)
end
function vector: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 (<vector> 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})
Vendored Executable
+388
View File
@@ -0,0 +1,388 @@
# Change Log
## 2016-01-12: v0.14.1.12
* Added: Basic support for object layers in Bump plugin (thanks @premek)
* Changed: New line token from CRLF to LF
* Fixed: Sprite batches should now respect the map draw order
## 2016-01-01: v0.14.1.11
* Fixed: Various bugs in the Box2D plugin (thanks @ChrisWeisiger)
* Fixed: Various bugs in the Bump plugin (thanks @bobbyjoness)
## 2015-12-31: v0.14.1.10
* Fixed: Box2D plugin was not recognizing a tile's embedded object group
## 2015-11-19: v0.14.1.9
* Changed: key in image cache to formatted path of image
## 2015-11-16: v0.14.1.8
* Added: image cache to STI module [sponsored by Binary Cocoa]
* Added: STI:flush() to clear out image cache
## 2015-11-15: v0.14.1.7
* Added: support for offsetting maps [sponsored by Binary Cocoa]
* Changed: Map.setDrawRange is more optimized via recycling tables
* Changed: render order now defaults to "right-down"
## 2015-11-07: v0.14.1.6
* Fixed: tileset images not being properly filtered
* Fixed: bump.lua plugin missing world argument in draw
## 2015-10-14: v0.14.1.5
* Added: bump.lua plugin (thanks @bobbyjoness)
## 2015-10-12: v0.14.1.4
* Fixed: removing a layer now properly removes tile and object instances
* Fixed: box2d plugin now properly removes collision objects
## 2015-10-09: v0.14.1.3
* Fixed: flipping animated tiles properly display
* Fixed: rotating animated tiles properly display
* Fixed: rotating tile objects properly display
* Fixed: box2d plugin properly creates rotated and flipped tile objects
* Fixed: box2d plugin no longer crashes when drawing a line with two vertices
## 2015-10-07: v0.14.1.2
* Added: support for all render orders (rd, ru, ld, lu)
* Added: support for sensors in the box2d plugin (only works on individual tiles and objects; sensor = true)
* Changed: addCustomLayer's index argument is now optional and defaults to the end of the array
* Fixed: a crash when using Base64 (uncompressed) with LOVE 0.9.2
## 2015-10-03: v0.14.1.1
* Added: support for gzip compressed maps (requires LOVE 0.10.0+)
## 2015-09-30: v0.14.1.0
* Added: support for Base64 compressed maps (requires LuaJIT)
* Added: support for zlib compressed maps (requires LOVE 0.10.0+)
## 2015-09-28: v0.14.0.1
* Added: Support for all staggered types (x/y, even/odd, iso/hex)
## 2015-09-27: v0.14.0.0
* Added: Hexagonal map support (thanks EntranceJew!)
* Added: Error message for compressed maps
* Fixed: box2d plugin threw an error in some cases (thanks maxha651!)
## 2015-09-17: v0.13.1.4
* Changed: sanity checks now search for love.physics instead of love.physics.*
## 2015-09-16: v0.13.1.3
* Changed: Improved documentation
## 2015-09-16: v0.13.1.2
* Changed: Simplified plugins
* Changed: Namespaced the box2d plugin
* Removed: Non-LOVE frameworks (they didn't work)
## 2015-09-16: v0.13.1.1
* Added: LDoc documentation
* Added: Plugin system where devs can extend STI
* Added: Reinstated the Box2D integration as a plugin
## 2015-09-15: v0.13.1.0
* Added: Map:convertToCustomLayer() now returns the layer
* Changed: Tightened localization of some functions
* Removed: Box2D collision integration
* Removed: Unused functions
## 2015-07-31: v0.12.3.0
* Added: Tiled version number to Map.tiledversion
* Added: Map.objects table indexed by unique object IDs
* Added: A better error message when trying to use Tile Collections
* Changed: Version number should now match Tiled's version number
* Changed: You must now add ".lua" in the filename of a new map as this is consistent with other libraries
* Changed: Renamed "pure" framework to "lua" (still doesn't work, though!)
* Changed: Map:setDrawRange no longer inverts tx and ty for you, do it yourself!
* Changed: Map:draw no longer accepts scale values, use love.graphics.scale!
* Fixed: A bug where tile objects were drawing an object border
* Removed: Corona framework file
## 2015-03-22: v0.9.8
* Fixed: A bug where Tiles without a Properties list would crash
## 2015-02-02: v0.9.7
* Added: userdata to Box2D fixtures
* Changed: changelog.txt -> CHANGELOG.md
* Changed: Flipping tiles now happens in both tile layers and object layers
* Fixed: A bug where tile objects were drawing oddly in some cases
* Fixed: A bug where circles would error if physics was disabled
## 2015-01-28: v0.9.6
* Added: getLayerProperties(), getTileProperties(), and getObjectProperties()
* Fixed: A bug where flipped tiles crashed STI during initCollision()
* Fixed: Flipped collision tiles now have correct offset
* Removed: Reverted the change in v0.9.3 that filled in empty tiles with false
## 2014-12-15: v0.9.5
* Fixed: A bug where tile collision objects were using the wrong size in some cases
* Fixed: A bug where flipped tiles weren't always creating collision objects
## 2014-12-05: v0.9.4
* Changed: STI's canvas plays nicely with other libraries
* Changed: addCustomLayer() now returns a handle on the created layer
## 2014-12-03: v0.9.3
* Added: Local Tile IDs to Tile objects
* Added: Terrain information
* Fixed: Some conversion functions
* Changed: Tile Layers now contain "false" instead of "nil" where there is no tile
* Changed: Added \_LICENSE, \_URL, \_VERSION, and \_DESCRIPTION properties to core STI object
## 2014-09-29: v0.9.2
* Added: Support for drawing tiles in object layers
* Fixed: Incorrect calculation of some collision objects
## 2014-09-26: v0.9.1
* Fixed: A crash when a collidable tile is initialized but not used
* Removed: Public access to formatPath(), rotateVertex(), and convertEllipseToPolygon()
## 2014-09-24: v0.9.0
* Added: Animated tiles! (Thanks to Clean3d)
* Fixed: A crash when a collidable rectangle has no rotation value
* Fixed: Incorrect values given to orthogonal collision objects
## 2014-09-24: v0.8.3
* Added: Map:convertScreenToTile() and Map:convertTileToScreen()
* Added: Map:convertScreenToIsometricTile() and Map:convertIsometricTileToScreen()
* Added: Map:convertScreenToStaggeredTile() and Map:convertStaggeredTileToScreen()
* Fixed: Map:removeLayer() now works properly
* Changed: Tile Objects now use the tile's collision map by default
## 2014-09-22: v0.8.2
* Added: "collidable" property for objects, tiles, and layers
* if collidable is set to true in Tiled, STI will pick it up and set all appropriate entities to collidable objects
* Fixed: Physics module no long required if not needed.
* Fixed: Whitespace discrepencies
* Changed: Map:initWorldCollision() now supports a whole lot more
## 2014-09-21: v0.8.1
* Added: README now lists minimum requirements
* Changed: README updated with new collision system
* Changed: Map:enableCollision() renamed to Map:initWorldCollision()
* Changed: Map:drawCollisionMap() renamed to Map:drawWorldCollision()
* Changed: Updated framework files (still no real Lua/Corona support)
* Changed: Tidied up collision code
* Removed: Map:getCollisionMap()
## 2014-09-20: v0.8.0
* Added: Box2D collision via Map:enableCollision()
* Added: Map:convertEllipseToPolygon()
## 2014-09-17: v0.7.6
* Added: Map:convertScreenToIsometric and Map:convertIsometricToScreen
* Added: Map:setObjectCoordinates
* Added: Map:rotateVertex
* Fixed: Adjusted map positioning for Isometric and Staggered maps
* Fixed: Object positioning in Isometric maps
* Removed: Temporary fix for Tiled 0.9.1
## 2014-08-05: v0.7.5
* Fixed: Properties offset by 1
* Fixed: Drawing a single Layer can now use Layer's name/index
## 2014-04-28: v0.7.4
* Fixed: Canvas resize type
## 2014-04-18: v0.7.3
* Fixed: Canvas using wrong filter
## 2014-04-08: v0.7.2
* Removed: Dependency for LuaJIT's bitwise operations
## 2014-04-08: v0.7.1
* Added: Map:resize(w, h)
* Changed: Map:draw() now takes two optional arguments: ScaleX and ScaleY
* Changed: STI now draws to a Canvas before drawing to screen (fixes scaling oddities)
## 2014-04-07: v0.7.0
* Added: Files for Corona and Pure Lua implementation
* Changed: Restructured sti.lua into several files
* Changed: Library is now LOVE agnostic and should allow for implementation of other frameworks
## 2014-03-1 : v0.6.16
* Changed: Ellipses now use polygons instead of... Not polygons.
## 2014-03-1 : v0.6.15
* Fixed: Tile spacing calculated properly in all cases
## 2014-02-0 : v0.6.14
* Fixed: Tile properties ACTUALLY being added now!
## 2014-01-2 : v0.6.13
* Added: Missing Tile Flag
## 2014-01-2 : v0.6.12
* Added: drawCollisionMap() now supports Isometric and Staggered maps
* Changed: drawCollisionMap() now requires a collision map parameter
* Changed: setCollisionMap() renamed to getCollisionMap()
* Changed: getCollisionMap() now returns the collision map
* Fixed: Tile properties not being added
* Removed: Map.collision table removed
## 2014-01-2 : v0.6.11
* Added: Descriptive error messages
* Fixed: Image filters for scaling
## 2014-01-2 : v0.6.10
* Fixed: Optimized load time
## 2014-01-25: v0.6.9
* Fixed: Parallax Scrolling
## 2014-01-25: v0.6.8
* Changed: Revised and restructured code
* Changed: createCollisionMap() renamed to setCollisionMap()
* Changed: newCustomLayer() renamed to addCustomLayer()
## 2014-01-24: v0.6.7
* Fixed: Number of tiles wasn't calculated properly
## 2014-01-24: v0.6.6
* Fixed: Spacing wasn't calculated properly
## 2014-01-24: v0.6.5
* Added: Staggered Maps
## 2014-01-24: v0.6.4
* Added: Isometric Maps
## 2014-01-20: v0.6.3
* Added: Tile Flags (flip/rotation)
## 2014-01-20: v0.6.2
* Fixed: A scaling bug
## 2014-01-19: v0.6.1
* Fixed: A bug causing the Collision Map to be nil
## 2014-01-19: v0.6.0
* Added: Sprite Batches
## 2014-01-19: v0.5.0
* Added: Draw Range optimization
## 2014-01-18: v0.4.3
* Added: Layer draw offsets
## 2014-01-17: v0.4.2
* Changed: Organized library a little better
## 2014-01-17: v0.4.1
* Fixed: Tiles incorrectly offset
* Fixed: Drawing concave polygons
## 2014-01-17: v0.4.0
* Added: Draw Object Layers
## 2014-01-16: v0.3.3
* Added: Create new Custom Layer
* Added: Callbacks for all layers
* Added: Remove Layer
* Changed: Simplified sti.new()
## 2014-01-16: v0.3.2
* Fixed: Crash if using Tiled 0.9.1
* Changed: Map structure to remove "map" table
## 2014-01-16: v0.3.1
* Added: Update callback to Custom Layers
## 2014-01-16: v0.3.0
* Added: Support for converting layers to Custom Layers
* Changed: sti.new() no longer requires the file extension
## 2014-01-15: v0.2.2
* Added: Support for basic collision layer
## 2014-01-15: v0.2.1
* Added: Support for map instances
* Added: Name alias to layer indices
* Changed: Sandboxed map environment
* Changed: Data structures are more efficient
* Removed: Unnecessary update function
Thanks to JarrettBillingsley for many of these changes
## 2014-01-14: v0.2.0
* Fixed: Drawing Tile Offset
* Changed: Tile Layer data structure is more efficient
* Changed: Simplified Quad generation
## 2014-01-14: v0.1.0
* Initial Commit
* Added: Orthogonal Map support
* Added: Draw Tile Layers
* Added: Draw Image Layers
* Added: Ignore Hidden Layers
* Added: Layer Opacity
Vendored Executable
+26
View File
@@ -0,0 +1,26 @@
# Simple Tiled Implementation
This code is licensed under the [**MIT/X11 Open Source License**][MIT].
Copyright (c) 2014 Landon Manning - LManning17@gmail.com - [LandonManning.com][LM]
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.
[MIT]: http://www.opensource.org/licenses/mit-license.html
[LM]: http://LandonManning.com
Vendored Executable
+96
View File
@@ -0,0 +1,96 @@
# Simple Tiled Implementation
Simple Tiled Implementation is a [**Tiled**][Tiled] map loader and renderer designed for the **\*awesome\*** [**LÖVE**][LOVE] framework. Please read the [**documentation**][dox] to learn how it works!
## Quick Example
```lua
-- This example uses the default Box2D (love.physics) plugin!!
local sti = require "sti"
function love.load()
-- Grab window size
windowWidth = love.graphics.getWidth()
windowHeight = love.graphics.getHeight()
-- Set world meter size (in pixels)
love.physics.setMeter(32)
-- Load a map exported to Lua from Tiled
map = sti.new("assets/maps/map01.lua", { "box2d" })
-- Prepare physics world with horizontal and vertical gravity
world = love.physics.newWorld(0, 0)
-- Prepare collision objects
map:box2d_init(world)
-- Create a Custom Layer
map:addCustomLayer("Sprite Layer", 3)
-- Add data to Custom Layer
local spriteLayer = map.layers["Sprite Layer"]
spriteLayer.sprites = {
player = {
image = love.graphics.newImage("assets/sprites/player.png"),
x = 64,
y = 64,
r = 0,
}
}
-- Update callback for Custom Layer
function spriteLayer:update(dt)
for _, sprite in pairs(self.sprites) do
sprite.r = sprite.r + math.rad(90 * dt)
end
end
-- Draw callback for Custom Layer
function spriteLayer:draw()
for _, sprite in pairs(self.sprites) do
local x = math.floor(sprite.x)
local y = math.floor(sprite.y)
local r = sprite.r
love.graphics.draw(sprite.image, x, y, r)
end
end
end
function love.update(dt)
map:update(dt)
end
function love.draw()
-- Translation would normally be based on a player's x/y
local translateX = 0
local translateY = 0
-- Draw Range culls unnecessary tiles
map:setDrawRange(-translateX, -translateY, windowWidth, windowHeight)
-- Draw the map and all objects within
map:draw()
-- Draw Collision Map (useful for debugging)
love.graphics.setColor(255, 0, 0, 255)
map:box2d_draw()
-- Reset color
love.graphics.setColor(255, 255, 255, 255)
end
```
## Requirements
This library recommends LÖVE 0.9.2 or 0.10.0 and Tiled 0.14.1. If you are updating from an older version of Tiled, please re-export your Lua map files.
## License
This code is licensed under the [**MIT/X11 Open Source License**][MIT]. Check out the LICENSE file for more information.
[Tiled]: http://www.mapeditor.org/
[LOVE]: https://www.love2d.org/
[dox]: http://karai17.github.io/Simple-Tiled-Implementation/
[MIT]: http://www.opensource.org/licenses/mit-license.html
Vendored Executable
+53
View File
@@ -0,0 +1,53 @@
--- Simple and fast Tiled map loader and renderer.
-- @module sti
-- @author Landon Manning
-- @copyright 2015
-- @license MIT/X11
local STI = {
_LICENSE = "MIT/X11",
_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
_VERSION = "0.14.1.12",
_DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.",
cache = {}
}
local path = (...):gsub('%.init$', '') .. "."
local Map = require(path .. "map")
--- Instance a new map.
-- @param path Path to the map file.
-- @param plugins A list of plugins to load.
-- @param ox Offset of map on the X axis (in pixels)
-- @param oy Offset of map on the Y axis (in pixels)
-- @return table The loaded Map.
function STI.new(map, plugins, ox, oy)
-- Check for valid map type
local ext = map:sub(-4, -1)
assert(ext == ".lua", string.format(
"Invalid file type: %s. File must be of type: lua.",
ext
))
-- Get path to map
local path = map:reverse():find("[/\\]") or ""
if path ~= "" then
path = map:sub(1, 1 + (#map - path))
end
-- Load map
map = love.filesystem.load(map)
setfenv(map, {})
map = setmetatable(map(), {__index = Map})
map:init(STI, path, plugins, ox, oy)
return map
end
--- Flush image cache.
function STI:flush()
self.cache = {}
end
return STI
Vendored Executable
+1388
View File
File diff suppressed because it is too large Load Diff
+349
View File
@@ -0,0 +1,349 @@
--- Box2D plugin for STI
-- @module box2d
-- @author Landon Manning
-- @copyright 2015
-- @license MIT/X11
return {
box2d_LICENSE = "MIT/X11",
box2d_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
box2d_VERSION = "2.3.2.1",
box2d_DESCRIPTION = "Box2D hooks for STI.",
--- Initialize Box2D physics world.
-- @param world The Box2D world to add objects to.
-- @return nil
box2d_init = function(map, world)
assert(love.physics, "To use the Box2D plugin, please enable the love.physics module.")
local body = love.physics.newBody(world, map.offsetx, map.offsety)
local collision = {
body = body,
}
local function convertEllipseToPolygon(x, y, w, h, max_segments)
local function calc_segments(segments)
local function vdist(a, b)
local c = {
x = a.x - b.x,
y = a.y - b.y,
}
return c.x * c.x + c.y * c.y
end
segments = segments or 64
local vertices = {}
local v = { 1, 2, math.ceil(segments/4-1), math.ceil(segments/4) }
local m
if love.physics then
m = love.physics.getMeter()
else
m = 32
end
for _, i in ipairs(v) do
local angle = (i / segments) * math.pi * 2
local px = x + w / 2 + math.cos(angle) * w / 2
local py = y + h / 2 + math.sin(angle) * h / 2
table.insert(vertices, { x = px / m, y = py / m })
end
local dist1 = vdist(vertices[1], vertices[2])
local dist2 = vdist(vertices[3], vertices[4])
-- Box2D threshold
if dist1 < 0.0025 or dist2 < 0.0025 then
return calc_segments(segments-2)
end
return segments
end
local segments = calc_segments(max_segments)
local vertices = {}
table.insert(vertices, { x = x + w / 2, y = y + h / 2 })
for i=0, segments do
local angle = (i / segments) * math.pi * 2
local px = x + w / 2 + math.cos(angle) * w / 2
local py = y + h / 2 + math.sin(angle) * h / 2
table.insert(vertices, { x = px, y = py })
end
return vertices
end
local function rotateVertex(v, x, y, cos, sin, oy)
oy = oy or 0
local vertex = {
x = v.x,
y = v.y - oy,
}
vertex.x = vertex.x - x
vertex.y = vertex.y - y
local vx = cos * vertex.x - sin * vertex.y
local vy = sin * vertex.x + cos * vertex.y
return vx + x, vy + y + oy
end
local function addObjectToWorld(objshape, vertices, userdata, object)
local shape
if objshape == "polyline" then
shape = love.physics.newChainShape(false, unpack(vertices))
else
shape = love.physics.newPolygonShape(unpack(vertices))
end
local fixture = love.physics.newFixture(body, shape)
fixture:setUserData(userdata)
if userdata.properties.sensor == "true" then
fixture:setSensor(true)
end
local obj = {
object = object,
shape = shape,
fixture = fixture,
}
table.insert(collision, obj)
end
local function getPolygonVertices(object)
local vertices = {}
for _, vertex in ipairs(object.polygon) do
table.insert(vertices, vertex.x)
table.insert(vertices, vertex.y)
end
return vertices
end
local function calculateObjectPosition(object, tile)
local o = {
shape = object.shape,
x = object.dx or object.x,
y = object.dy or object.y,
w = object.width,
h = object.height,
polygon = object.polygon or object.polyline or object.ellipse or object.rectangle
}
local userdata = {
object = o,
properties = object.properties
}
if o.shape == "rectangle" then
o.r = object.rotation or 0
local cos = math.cos(math.rad(o.r))
local sin = math.sin(math.rad(o.r))
local oy = 0
if object.gid then
local tileset = map.tilesets[map.tiles[object.gid].tileset]
local lid = object.gid - tileset.firstgid
local tile = {}
-- This fixes a height issue
o.y = o.y + map.tiles[object.gid].offset.y
oy = tileset.tileheight
for _, t in ipairs(tileset.tiles) do
if t.id == lid then
tile = t
break
end
end
if tile.objectGroup then
for _, obj in ipairs(tile.objectGroup.objects) do
-- Every object in the tile
calculateObjectPosition(obj, object)
end
return
else
o.w = map.tiles[object.gid].width
o.h = map.tiles[object.gid].height
end
end
o.polygon = {
{ x=o.x+0, y=o.y+0 },
{ x=o.x+o.w, y=o.y+0 },
{ x=o.x+o.w, y=o.y+o.h },
{ x=o.x+0, y=o.y+o.h },
}
for _, vertex in ipairs(o.polygon) do
if map.orientation == "isometric" then
vertex.x, vertex.y = map:convertIsometricToScreen(vertex.x, vertex.y)
end
vertex.x, vertex.y = rotateVertex(vertex, o.x, o.y, cos, sin, oy)
end
local vertices = getPolygonVertices(o)
addObjectToWorld(o.shape, vertices, userdata, tile or object)
elseif o.shape == "ellipse" then
if not o.polygon then
o.polygon = convertEllipseToPolygon(o.x, o.y, o.w, o.h)
end
local vertices = getPolygonVertices(o)
local triangles = love.math.triangulate(vertices)
for _, triangle in ipairs(triangles) do
addObjectToWorld(o.shape, triangle, userdata, tile or object)
end
elseif o.shape == "polygon" then
local vertices = getPolygonVertices(o)
local triangles = love.math.triangulate(vertices)
for _, triangle in ipairs(triangles) do
addObjectToWorld(o.shape, triangle, userdata, tile or object)
end
elseif o.shape == "polyline" then
local vertices = getPolygonVertices(o)
addObjectToWorld(o.shape, vertices, userdata, tile or object)
end
end
for _, tile in pairs(map.tiles) do
local tileset = map.tilesets[tile.tileset]
-- Every object in every instance of a tile
if tile.objectGroup then
if map.tileInstances[tile.gid] then
for _, instance in ipairs(map.tileInstances[tile.gid]) do
for _, object in ipairs(tile.objectGroup.objects) do
object.dx = object.x + instance.x
object.dy = object.y + instance.y
calculateObjectPosition(object, instance)
end
end
end
-- Every instance of a tile
elseif tile.properties and tile.properties.collidable == "true" and map.tileInstances[tile.gid] then
for _, instance in ipairs(map.tileInstances[tile.gid]) do
local object = {
shape = "rectangle",
x = instance.x,
y = instance.y,
width = tileset.tilewidth,
height = tileset.tileheight,
properties = tile.properties
}
calculateObjectPosition(object, instance)
end
end
end
for _, layer in ipairs(map.layers) do
-- Entire layer
if layer.properties.collidable == "true" then
if layer.type == "tilelayer" then
for gid, tiles in pairs(map.tileInstances) do
local tile = map.tiles[gid]
local tileset = map.tilesets[tile.tileset]
for _, instance in ipairs(tiles) do
if instance.layer == layer then
local object = {
shape = "rectangle",
x = instance.x,
y = instance.y,
width = tileset.tilewidth,
height = tileset.tileheight,
properties = tile.properties
}
calculateObjectPosition(object, instance)
end
end
end
elseif layer.type == "objectgroup" then
for _, object in ipairs(layer.objects) do
calculateObjectPosition(object)
end
elseif layer.type == "imagelayer" then
local object = {
shape = "rectangle",
x = layer.x or 0,
y = layer.y or 0,
width = layer.width,
height = layer.height,
properties = layer.properties
}
calculateObjectPosition(object)
end
end
-- Individual objects
if layer.type == "objectgroup" then
for _, object in ipairs(layer.objects) do
if object.properties.collidable == "true" then
calculateObjectPosition(object)
end
end
end
end
map.box2d_collision = collision
end,
--- Remove Box2D fixtures and shapes from world.
-- @param index The index or name of the layer being removed
-- @return nil
box2d_removeLayer = function(map, index)
local layer = assert(map.layers[index], "Layer not found: " .. index)
local collision = map.box2d_collision
-- Remove collision objects
for i=#collision, 1, -1 do
local obj = collision[i]
if obj.object.layer == layer then
obj.fixture:destroy()
table.remove(collision, i)
end
end
end,
--- Draw Box2D physics world.
-- @return nil
box2d_draw = function(map)
local collision = map.box2d_collision
for _, obj in ipairs(collision) do
local points = {collision.body:getWorldPoints(obj.shape:getPoints())}
if #points == 4 then
love.graphics.line(points)
else
love.graphics.polygon("line", points)
end
end
end,
}
--- Custom Properties in Tiled are used to tell this plugin what to do.
-- @table Properties
-- @field collidable set to "true", can be used on any Layer, Tile, or Object
-- @field sensor set to "true", can be used on any Tile or Object that is also collidable
+103
View File
@@ -0,0 +1,103 @@
--- Bump.lua plugin for STI
-- @module bump.lua
-- @author David Serrano (BobbyJones|FrenchFryLord)
-- @copyright 2016
-- @license MIT/X11
return {
bump_LICENSE = "MIT/X11",
bump_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
bump_VERSION = "3.1.5.2",
bump_DESCRIPTION = "Bump hooks for STI.",
--- Adds each collidable tile to the Bump world.
-- @param world The Bump world to add objects to.
-- @return collidables table containing the handles to the objects in the Bump world.
bump_init = function(map, world)
local collidables = {}
for _, tileset in ipairs(map.tilesets) do
for _, tile in ipairs(tileset.tiles) do
local gid = tileset.firstgid + tile.id
-- Every object in every instance of a tile
if tile.properties and tile.properties.collidable == "true" and map.tileInstances[gid] then
for _, instance in ipairs(map.tileInstances[gid]) do
local t = {properties = tile.properties, x = instance.x + map.offsetx, y = instance.y + map.offsety, width = map.tilewidth, height = map.tileheight, layer = instance.layer }
world:add(t, t.x,t.y, t.width,t.height)
table.insert(collidables,t)
end
end
end
end
for _, layer in ipairs(map.layers) do
-- Entire layer
if layer.properties.collidable == "true" then
if layer.type == "tilelayer" then
for y, tiles in ipairs(layer.data) do
for x, tile in pairs(tiles) do
local t = {properties = tile.properties, x = x * map.tilewidth + tile.offset.x + map.offsetx, y = y * map.tileheight + tile.offset.y + map.offsety, width = tile.width, height = tile.height, layer = layer }
world:add(t, t.x,t.y, t.width,t.height )
table.insert(collidables,t)
end
end
elseif layer.type == "imagelayer" then
world:add(layer, layer.x,layer.y, layer.width,layer.height)
table.insert(collidables,layer)
end
end
-- individual collidable objects in a layer that is not "collidable"
-- or whole collidable objects layer
if layer.type == "objectgroup" then
for _, obj in ipairs(layer.objects) do
if (layer.properties and layer.properties.collidable == "true")
or (obj.properties and obj.properties.collidable == "true") then
if obj.shape == "rectangle" then
local t = {properties = obj.properties, x = obj.x, y = obj.y, width = obj.width, height = obj.height, type = obj.type, name = obj.name, id = obj.id, gid = obj.gid, layer = layer }
if obj.gid then t.y = t.y - obj.height end
world:add(t, t.x,t.y, t.width,t.height )
table.insert(collidables,t)
end -- TODO implement other object shapes?
end
end
end
end
map.bump_collidables = collidables
end,
--- Remove layer
-- @params index to layer to be removed
-- @params world bump world the holds the tiles
-- @return nil
bump_removeLayer = function(map, index, world)
local layer = assert(map.layers[index], "Layer not found: " .. index)
local collidables = map.bump_collidables
-- Remove collision objects
for i=#collidables, 1, -1 do
local obj = collidables[i]
if obj.layer == layer
and (
layer.properties.collidable == "true"
or obj.properties.collidable == "true"
) then
world:remove(obj)
table.remove(collidables, i)
end
end
end,
--- Draw bump collisions world.
-- @params world bump world holding the tiles geometry
-- @return nil
bump_draw = function(map, world)
for k,collidable in pairs(map.bump_collidables) do
love.graphics.rectangle("line",world:getRect(collidable))
end
end
}
+288
View File
@@ -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
+781
View File
@@ -0,0 +1,781 @@
local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or ""
local normal_map = require(_PACKAGE..'/normal_map')
local util = require(_PACKAGE..'/util')
local anim8 = require(_PACKAGE..'/anim8')
local vector = require(_PACKAGE..'/vector')
local body = {}
body.__index = body
body.glowShader = love.graphics.newShader(_PACKAGE.."/shaders/glow.glsl")
body.materialShader = love.graphics.newShader(_PACKAGE.."/shaders/material.glsl")
local function new(id, type, ...)
local args = {...}
local obj = setmetatable({}, body)
obj.id = id
obj.type = type
obj.shine = true
obj.red = 1.0
obj.green = 1.0
obj.blue = 1.0
obj.alpha = 1.0
obj.glowRed = 255
obj.glowGreen = 255
obj.glowBlue = 255
obj.glowStrength = 0.0
obj.tileX = 0
obj.tileY = 0
obj.zheight = 1
obj.rotation = 0
obj.scalex = 1
obj.scaley = 1
obj.castsNoShadow = false
obj.visible = true
obj.is_on_screen = true
if obj.type == "circle" then
obj.x = args[1] or 0
obj.y = args[2] or 0
circle_canvas = love.graphics.newCanvas(args[3]*2, args[3]*2)
util.drawto(circle_canvas, 0, 0, 1, function()
love.graphics.circle('fill', args[3], args[3], args[3])
end)
obj.img = love.graphics.newImage(circle_canvas:newImageData())
obj.imgWidth = obj.img:getWidth()
obj.imgHeight = obj.img:getHeight()
obj.ix = obj.imgWidth * 0.5
obj.iy = obj.imgHeight * 0.5
obj:generateNormalMapFlat("top")
obj:setShadowType('circle', args[3], args[4], args[5])
elseif obj.type == "rectangle" then
local x = args[1] or 0
local y = args[2] or 0
local width = args[3] or 64
local height = args[4] or 64
local ox = args[5] or width * 0.5
local oy = args[6] or height * 0.5
obj:setPoints(
x - ox, y - oy,
x - ox + width, y - oy,
x - ox + width, y - oy + height,
x - ox, y - oy + height
)
elseif obj.type == "polygon" then
obj:setPoints(...)
elseif obj.type == "image" then
obj.img = args[1]
obj.x = args[2] or 0
obj.y = args[3] or 0
if obj.img then
obj.imgWidth = obj.img:getWidth()
obj.imgHeight = obj.img:getHeight()
obj.ix = obj.imgWidth * 0.5
obj.iy = obj.imgHeight * 0.5
end
obj:generateNormalMapFlat("top")
obj:setShadowType('rectangle', args[4] or obj.imgWidth, args[5] or obj.imgHeight, args[6], args[7])
obj.reflective = true
elseif obj.type == "animation" then
obj.img = args[1]
obj.x = args[2] or 0
obj.y = args[3] or 0
obj.animations = {}
obj.castsNoShadow = true
obj:generateNormalMapFlat("top")
obj.reflective = true
elseif obj.type == "refraction" then
obj.x = args[2] or 0
obj.y = args[3] or 0
obj:setNormalMap(args[1], args[4], args[5])
obj.width = args[4] or obj.normalWidth
obj.height = args[5] or obj.normalHeight
obj.ox = obj.width * 0.5
obj.oy = obj.height * 0.5
obj.refraction = true
elseif obj.type == "reflection" then
obj.x = args[2] or 0
obj.y = args[3] or 0
obj:setNormalMap(args[1], args[4], args[5])
obj.width = args[4] or obj.normalWidth
obj.height = args[5] or obj.normalHeight
obj.ox = obj.width * 0.5
obj.oy = obj.height * 0.5
obj.reflection = true
end
obj:commit_changes()
return obj
end
-- refresh
function body:refresh()
if self.shadowType == 'polygon' and self:has_changed() then
self.data = {unpack(self.unit_data)}
local center = vector(self.x, self.y)
for i = 1, #self.data, 2 do
local point = vector(self.data[i], self.data[i+1])
point = point:rotate(self.rotation)
point = point:scale(self.scalex, self.scaley)
self.data[i], self.data[i+1] = (point + center):unpack()
end
self:commit_changes()
end
end
function body:has_changed()
return self:position_changed() or
self:rotation_changed() or
self:scale_changed()
end
function body:position_changed()
return self.old_x ~= self.x or
self.old_y ~= self.y
end
function body:rotation_changed()
return self.old_rotation ~= self.rotation
end
function body:scale_changed()
return self.old_scalex ~= self.scalex or
self.old_scaley ~= self.scaley
end
function body:commit_changes()
self.old_x, self.old_y = self.x, self.y
self.old_rotation = self.rotation
self.old_scalex, self.old_scaley = self.scalex, self.scaley
end
function body:newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
return anim8.newGrid(
frameWidth, frameHeight,
imageWidth or self.img:getWidth(), imageHeight or self.img:getHeight(),
left, top, border
)
end
-- frameWidth, frameHeight, imageWidth, imageHeight, left, top, border
function body:addAnimation(name, frames, durations, onLoop)
self.animations[name] = anim8.newAnimation(frames, durations, onLoop)
if not self.current_animation_name then
self:setAnimation(name)
end
end
function body:setAnimation(name)
self.current_animation_name = name
self.animation = self.animations[self.current_animation_name]
local frame = self.animation.frames[self.animation.position]
_,_,self.width, self.height = frame:getViewport()
end
function body:gotoFrame(frame) self.animation:gotoFrame(frame) end
function body:pause() self.animation:pause() end
function body:resume() self.animation:resume() end
function body:flipH() self.animation:flipH() end
function body:flipV() self.animation:flipV() end
function body:pauseAtEnd() self.animation:pauseAtEnd() end
function body:pauseAtStart() self.animation:pauseAtStart() end
function body:update(dt)
self:refresh()
if self.type == "animation" and self.animation then
local frame = self.animation.frames[self.animation.position]
_,_,self.width, self.height = frame:getViewport()
self.imgWidth, self.imgHeight = self.width, self.height
self.normalWidth, self.normalHeight = self.width, self.height
self.ix, self.iy = self.imgWidth * 0.5,self.imgHeight * 0.5
self.nx, self.ny = self.ix, self.iy
self.animation:update(dt)
end
end
function body:rotate(angle)
self:setRotation(self.rotation + angle)
end
function body:setRotation(angle)
self.rotation = angle
end
function body:scale(sx, sy)
self.scalex = self.scalex + sx
self.scaley = self.scaley + (sy or sx)
end
function body:setScale(sx, sy)
self.scalex = sx
self.scaley = sy or sx
end
-- set position
function body:setPosition(x, y)
if x ~= self.x or y ~= self.y then
self.x = x
self.y = y
end
end
-- move position
function body:move(x, y)
if x then
self.x = self.x + x
end
if y then
self.y = self.y + y
end
end
-- get x position
function body:getPosition()
return self.x, self.y
end
-- get width
function body:getWidth()
return self.width
end
-- get height
function body:getHeight()
return self.height
end
-- get image width
function body:getImageWidth()
return self.imgWidth
end
-- get image height
function body:getImageHeight()
return self.imgHeight
end
-- set offset
function body:setOffset(ox, oy)
if ox ~= self.ox or oy ~= self.oy then
self.ox = ox
self.oy = oy
end
end
-- set offset
function body:setImageOffset(ix, iy)
if ix ~= self.ix or iy ~= self.iy then
self.ix = ix
self.iy = iy
end
end
-- set offset
function body:setNormalOffset(nx, ny)
if nx ~= self.nx or ny ~= self.ny then
self.nx = nx
self.ny = ny
end
end
-- set glow color
function body:setGlowColor(red, green, blue)
self.glowRed = red
self.glowGreen = green
self.glowBlue = blue
end
-- set glow alpha
function body:setGlowStrength(strength)
self.glowStrength = strength
end
function body:setVisible(visible)
self.visible = visible
end
-- get radius
function body:getRadius()
return self.radius * self.scalex
end
-- set radius
function body:setRadius(radius)
if radius ~= self.radius then
self.radius = radius
end
end
-- set polygon data
function body:setPoints(...)
self.unit_data = {...}
--calculate l,r,t,b
self.x, self.y, self.width, self.height = self.unit_data[1], self.unit_data[2], 0, 0
for i = 1, #self.unit_data, 2 do
local px, py = self.unit_data[i], self.unit_data[i+1]
if px < self.x then self.x = px end
if py < self.y then self.y = py end
if px > self.width then self.width = px end
if py > self.height then self.height = py end
end
-- normalize width and height
self.width = self.width - self.x
self.height = self.height - self.y
for i = 1, #self.unit_data, 2 do
self.unit_data[i], self.unit_data[i+1] = self.unit_data[i] - self.x, self.unit_data[i+1] - self.y
end
self.x = self.x + (self.width * 0.5)
self.y = self.y + (self.height * 0.5)
local poly_canvas = love.graphics.newCanvas(self.width, self.height)
util.drawto(poly_canvas, 0, 0, 1, function()
love.graphics.polygon('fill', self.unit_data)
end)
--normalize points to be around the center x y
for i = 1, #self.unit_data, 2 do
self.unit_data[i], self.unit_data[i+1] = self.unit_data[i] - self.width * 0.5, self.unit_data[i+1] - self.height * 0.5
end
if not self.img then
self.img = love.graphics.newImage(poly_canvas:newImageData())
self.imgWidth = self.img:getWidth()
self.imgHeight = self.img:getHeight()
self.ix = self.imgWidth * 0.5
self.iy = self.imgHeight * 0.5
self:generateNormalMapFlat("top")
end
--wrapping with polygon normals causes edges to show
--also we do not need wrapping for this default normal map
self.normal:setWrap("clamp", "clamp")
self.shadowType = "polygon"
self:refresh()
end
-- get polygon data
function body:getPoints()
return unpack(self.data)
end
-- set shadow on/off
function body:setShadow(b)
self.castsNoShadow = not b
end
-- set shine on/off
function body:setShine(b)
self.shine = b
end
-- set glass color
function body:setColor(red, green, blue)
self.red = red
self.green = green
self.blue = blue
end
-- set glass alpha
function body:setAlpha(alpha)
self.alpha = alpha
end
-- set reflection on/off
function body:setReflection(reflection)
self.reflection = reflection
end
-- set refraction on/off
function body:setRefraction(refraction)
self.refraction = refraction
end
-- set reflective on other objects on/off
function body:setReflective(reflective)
self.reflective = reflective
end
-- set refractive on other objects on/off
function body:setRefractive(refractive)
self.refractive = refractive
end
-- set image
function body:setImage(img)
if img then
self.img = img
self.imgWidth = self.img:getWidth()
self.imgHeight = self.img:getHeight()
self.ix = self.imgWidth * 0.5
self.iy = self.imgHeight * 0.5
end
end
-- set normal
function body:setNormalMap(normal, width, height, nx, ny)
if normal then
self.normal = normal
self.normal:setWrap("repeat", "repeat")
self.normalWidth = width or self.normal:getWidth()
self.normalHeight = height or self.normal:getHeight()
self.nx = nx or self.normalWidth * 0.5
self.ny = ny or self.normalHeight * 0.5
self.normalVert = {
{0.0, 0.0, 0.0, 0.0},
{self.normalWidth, 0.0, self.normalWidth / self.normal:getWidth(), 0.0},
{self.normalWidth, self.normalHeight, self.normalWidth / self.normal:getWidth(), self.normalHeight / self.normal:getHeight()},
{0.0, self.normalHeight, 0.0, self.normalHeight / self.normal:getHeight()}
}
self.normalMesh = love.graphics.newMesh(self.normalVert, "fan")
self.normalMesh:setTexture(self.normal)
else
self.normalMesh = nil
end
end
-- set height map
function body:setHeightMap(heightMap, strength)
self:setNormalMap(normal_map.fromHeightMap(heightMap, strength))
end
-- generate flat normal map
function body:generateNormalMapFlat(mode)
self:setNormalMap(normal_map.generateFlat(self.img, mode))
end
-- generate faded normal map
function body:generateNormalMapGradient(horizontalGradient, verticalGradient)
self:setNormalMap(normal_map.generateGradient(self.img, horizontalGradient, verticalGradient))
end
-- generate normal map
function body:generateNormalMap(strength)
self:setNormalMap(normal_map.fromHeightMap(self.img, strength))
end
-- set material
function body:setMaterial(material)
if material then
self.material = material
end
end
-- set normal
function body:setGlowMap(glow)
self.glow = glow
self.glowStrength = 1.0
end
-- set tile offset
function body:setNormalTileOffset(tx, ty)
self.tileX = tx / self.normalWidth
self.tileY = ty / self.normalHeight
self.normalVert = {
{0.0, 0.0, self.tileX, self.tileY},
{self.normalWidth, 0.0, self.tileX + 1.0, self.tileY},
{self.normalWidth, self.normalHeight, self.tileX + 1.0, self.tileY + 1.0},
{0.0, self.normalHeight, self.tileX, self.tileY + 1.0}
}
end
-- get type
function body:getType()
return self.type
end
-- get type
function body:setShadowType(type, ...)
self.shadowType = type
local args = {...}
if self.shadowType == "circle" then
self.radius = args[1] or 16
self.ox = args[2] or 0
self.oy = args[3] or 0
elseif self.shadowType == "rectangle" then
self.shadowType = "polygon"
local width = args[1] or 64
local height = args[2] or 64
self.ox = args[3] or width * 0.5
self.oy = args[4] or height * 0.5
self:setPoints(
self.x - self.ox, self.y - self.oy,
self.x - self.ox + width, self.y - self.oy,
self.x - self.ox + width, self.y - self.oy + height,
self.x - self.ox, self.y - self.oy + height
)
elseif self.shadowType == "polygon" then
self:setPoints(args)
elseif self.shadowType == "image" then
if self.img then
self.width = self.imgWidth
self.height = self.imgHeight
self.shadowVert = {
{0.0, 0.0, 0.0, 0.0},
{self.width, 0.0, 1.0, 0.0},
{self.width, self.height, 1.0, 1.0},
{0.0, self.height, 0.0, 1.0}
}
if not self.shadowMesh then
self.shadowMesh = love.graphics.newMesh(self.shadowVert)
self.shadowMesh:setTexture(self.img)
self.shadowMesh:setAttributeEnabled("VertexColor", true)
end
else
self.width = 64
self.height = 64
end
self.shadowX = args[1] or 0
self.shadowY = args[2] or 0
self.fadeStrength = args[3] or 0.0
end
end
function body:isVisible()
return self.visible and self.is_on_screen
end
function body:inLightRange(light)
local l, t, w = light.x - light.range, light.y - light.range, light.range*2
return self:inRange(l,t,w,w,1)
end
function body:inRange(l, t, w, h, s)
local radius
if self.type == 'circle' then
radius = self.radius * self.scalex
else
local sw = (self.width * self.scalex)
local sh = (self.height * self.scaley)
radius = (sw > sh and sw or sh)
end
local bx, by, bw, bh = self.x - radius, self.y - radius, radius * 2, radius * 2
return self.visible and (bx+bw) > (l/s) and bx < (l+w)/s and (by+bh) > (t/s) and by < (t+h)/s
end
function body:drawAnimation()
self.animation:draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
end
function body:drawNormal()
if not self.refraction and not self.reflection and self.normalMesh then
love.graphics.setColor(255, 255, 255)
if self.type == 'animation' then
self.animation:draw(self.normal, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
else
love.graphics.draw(self.normalMesh, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
end
end
end
function body:drawGlow()
love.graphics.setColor(self.glowRed * self.glowStrength, self.glowGreen * self.glowStrength, self.glowBlue * self.glowStrength)
if self.type == "circle" then
love.graphics.circle("fill", self.x, self.y, self.radius * self.scalex)
elseif self.type == "polygon" then
love.graphics.polygon("fill", unpack(self.data))
elseif (self.type == "image" or self.type == "animation") and self.img then
if self.glow then
love.graphics.setShader(self.glowShader)
self.glowShader:send("glowImage", self.glow)
self.glowShader:send("glowTime", love.timer.getTime() * 0.5)
love.graphics.setColor(255, 255, 255)
else
love.graphics.setColor(0, 0, 0)
end
if self.type == "animation" then
self.animation:draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
else
love.graphics.draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
end
love.graphics.setShader()
end
end
function body:drawRefraction()
if self.refraction and self.normal then
love.graphics.setColor(255, 255, 255)
if self.tileX == 0.0 and self.tileY == 0.0 then
love.graphics.draw(self.normal, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
else
self.normalMesh:setVertices(self.normalVert)
love.graphics.draw(self.normalMesh, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
end
end
love.graphics.setColor(0, 0, 0)
if not self.refractive then
if self.type == "circle" then
love.graphics.circle("fill", self.x, self.y, self.radius * self.scalex)
elseif self.type == "polygon" then
love.graphics.polygon("fill", unpack(self.data))
elseif self.type == "image" and self.img then
love.graphics.draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
elseif self.type == 'animation' then
self.animation:draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
end
end
end
function body:drawReflection()
if self.reflection and self.normal then
love.graphics.setColor(255, 0, 0)
self.normalMesh:setVertices(self.normalVert)
love.graphics.draw(self.normalMesh, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
end
if self.reflective and self.img then
love.graphics.setColor(0, 255, 0)
if self.type == 'animation' then
self.animation:draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
else
love.graphics.draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
end
elseif not self.reflection and self.img then
love.graphics.setColor(0, 0, 0)
if self.type == 'animation' then
self.animation:draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
else
love.graphics.draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
end
end
end
function body:drawMaterial()
if self.material and self.normal then
love.graphics.setShader(self.materialShader)
love.graphics.setColor(255, 255, 255)
self.materialShader:send("material", self.material)
if self.type == 'animation' then
self.animation:draw(self.normal, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
else
love.graphics.draw(self.normal, self.x, self.y, self.rotation, self.scalex, self.scaley, self.nx, self.ny)
end
love.graphics.setShader()
end
end
function body:drawStencil()
if not self.refraction and not self.reflection and not self.castsNoShadow then
love.graphics.draw(self.img, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ix, self.iy)
end
end
function body:drawShadow(light)
if self.castsNoShadow or (self.zheight - light.z) > 0 then
return
end
love.graphics.setColor(self.red, self.green, self.blue, self.alpha)
if self.shadowType == "polygon" then
self:drawPolyShadow(light)
elseif self.shadowType == "circle" then
self:drawCircleShadow(light)
elseif self.shadowType == "image" and self.img then
self:drawImageShadow(light)
end
end
--using shadow point calculations from this article
--http://web.cs.wpi.edu/~matt/courses/cs563/talks/shadow/shadow.html
function body:drawPolyShadow(light)
local lightPosition = vector(light.x, light.y)
local lh = lightPosition * self.zheight
local height_diff = (self.zheight - light.z)
if height_diff == 0 then -- prevent inf
height_diff = -0.001
end
for i = 1, #self.data, 2 do
local vertex = vector(self.data[i], self.data[i + 1])
local nextVertex = vector(self.data[(i + 2) % #self.data], self.data[(i + 2) % #self.data + 1])
local startToEnd = nextVertex - vertex
if vector(startToEnd.y, -startToEnd.x) * (vertex - lightPosition) > 0 then
local point1 = (lh - (vertex * light.z))/height_diff
local point2 = (lh - (nextVertex * light.z))/height_diff
love.graphics.polygon("fill",
vertex.x, vertex.y, point1.x, point1.y,
point2.x, point2.y, nextVertex.x, nextVertex.y)
end
end
end
--using shadow point calculations from this article
--http://web.cs.wpi.edu/~matt/courses/cs563/talks/shadow/shadow.html
function body:drawCircleShadow(light)
local selfPos = vector(self.x - self.ox, self.y - self.oy)
local lightPosition = vector(light.x, light.y)
local lh = lightPosition * self.zheight
local height_diff = (self.zheight - light.z)
local radius = self.radius * self.scalex
if height_diff == 0 then -- prevent inf
height_diff = -0.001
end
local angle = math.atan2(light.x - selfPos.x, selfPos.y - light.y) + math.pi / 2
local point1 = vector(selfPos.x + math.sin(angle) * radius,
selfPos.y - math.cos(angle) * radius)
local point2 = vector(selfPos.x - math.sin(angle) * radius,
selfPos.y + math.cos(angle) * radius)
local point3 = (lh - (point1 * light.z))/height_diff
local point4 = (lh - (point2 * light.z))/height_diff
local shadow_radius = point3:dist(point4)/2
local circleCenter = (point3 + point4)/2
if lightPosition:dist(selfPos) <= radius then
love.graphics.circle("fill", circleCenter.x, circleCenter.y, shadow_radius)
else
love.graphics.polygon("fill", point1.x, point1.y,
point2.x, point2.y,
point4.x, point4.y,
point3.x, point3.y)
if lightPosition:dist(circleCenter) < light.range then -- dont draw circle if way off screen
local angle1 = math.atan2(point3.y - circleCenter.y, point3.x - circleCenter.x)
local angle2 = math.atan2(point4.y - circleCenter.y, point4.x - circleCenter.x)
if angle1 < angle2 then
love.graphics.arc("fill", circleCenter.x, circleCenter.y, shadow_radius, angle1, angle2)
else
love.graphics.arc("fill", circleCenter.x, circleCenter.y, shadow_radius, angle1 - math.pi, angle2 - math.pi)
end
end
end
end
function body:drawImageShadow(light)
local height_diff = (light.z - self.zheight)
if height_diff <= 0.1 then -- prevent shadows from leaving thier person like peter pan.
height_diff = 0.1
end
local length = 1.0 / height_diff
local shadowRotation = math.atan2((self.x) - light.x, (self.y + self.oy) - light.y)
local shadowStartY = self.imgHeight + (math.cos(shadowRotation) + 1.0) * self.shadowY
local shadowX = math.sin(shadowRotation) * self.imgHeight * length
local shadowY = (length * math.cos(shadowRotation) + 1.0) * shadowStartY
self.shadowMesh:setVertices({
{shadowX, shadowY, 0, 0, self.red, self.green, self.blue, self.alpha},
{shadowX + self.imgWidth, shadowY, 1, 0, self.red, self.green, self.blue, self.alpha},
{self.imgWidth, shadowStartY, 1, 1, self.red, self.green, self.blue, self.alpha},
{0, shadowStartY, 0, 1, self.red, self.green, self.blue, self.alpha}
})
love.graphics.draw(self.shadowMesh, self.x, self.y, self.rotation, self.scalex, self.scaley, self.ox, self.oy)
end
return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})
+344
View File
@@ -0,0 +1,344 @@
--[[
The MIT License (MIT)
Copyright (c) 2014 Marcus Ihde, Tim Anema
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 _PACKAGE = string.gsub(...,"%.","/") or ""
if string.len(_PACKAGE) > 0 then _PACKAGE = _PACKAGE .. "/" end
local Light = require(_PACKAGE..'light')
local Body = require(_PACKAGE..'body')
local util = require(_PACKAGE..'util')
local PostShader = require(_PACKAGE..'postshader')
local light_world = {}
light_world.__index = light_world
light_world.image_mask = love.graphics.newShader(_PACKAGE.."/shaders/image_mask.glsl")
light_world.shadowShader = love.graphics.newShader(_PACKAGE.."/shaders/shadow.glsl")
light_world.refractionShader = love.graphics.newShader(_PACKAGE.."shaders/refraction.glsl")
light_world.reflectionShader = love.graphics.newShader(_PACKAGE.."shaders/reflection.glsl")
local function new(options)
local obj = {}
obj.lights = {}
obj.bodies = {}
obj.post_shader = PostShader()
obj.l, obj.t, obj.s = 0, 0, 1
obj.ambient = {0, 0, 0}
obj.refractionStrength = 8.0
obj.reflectionStrength = 16.0
obj.reflectionVisibility = 1.0
obj.shadowBlur = 2.0
obj.glowBlur = 1.0
obj.glowTimer = 0.0
obj.glowDown = false
obj.disableGlow = false
obj.disableMaterial = false
obj.disableReflection = true
obj.disableRefraction = true
options = options or {}
for k, v in pairs(options) do obj[k] = v end
local world = setmetatable(obj, light_world)
world:refreshScreenSize()
return world
end
function light_world:refreshScreenSize(w, h)
w, h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
self.w, self.h = w, h
self.render_buffer = love.graphics.newCanvas(w, h)
self.shadow_buffer = love.graphics.newCanvas(w, h)
self.normalMap = love.graphics.newCanvas(w, h)
self.shadowMap = love.graphics.newCanvas(w, h)
self.glowMap = love.graphics.newCanvas(w, h)
self.refractionMap = love.graphics.newCanvas(w, h)
self.reflectionMap = love.graphics.newCanvas(w, h)
self.post_shader:refreshScreenSize(w, h)
end
function light_world:update(dt)
for i = 1, #self.bodies do
self.bodies[i].is_on_screen = self.bodies[i]:inRange(-self.l,-self.t,self.w,self.h,self.s)
if self.bodies[i]:isVisible() then
self.bodies[i]:update(dt)
end
end
for i = 1, #self.lights do
self.lights[i].is_on_screen = self.lights[i]:inRange(self.l,self.t,self.w,self.h,self.s)
end
end
function light_world:draw(cb)
util.drawto(self.render_buffer, self.l, self.t, self.s, function()
cb( self.l,self.t,self.w,self.h,self.s)
_ = self.disableMaterial or self:drawMaterial( self.l,self.t,self.w,self.h,self.s)
self:drawShadows( self.l,self.t,self.w,self.h,self.s)
_ = self.disableGlow or self:drawGlow( self.l,self.t,self.w,self.h,self.s)
_ = self.disableRefraction or self:drawRefraction( self.l,self.t,self.w,self.h,self.s)
_ = self.disableReflection or self:drawReflection( self.l,self.t,self.w,self.h,self.s)
end)
self.post_shader:drawWith(self.render_buffer, self.l, self.t, self.s)
end
-- draw normal shading
function light_world:drawShadows(l,t,w,h,s)
love.graphics.setCanvas( self.normalMap )
love.graphics.clear()
love.graphics.setCanvas()
util.drawto(self.normalMap, l, t, s, function()
for i = 1, #self.bodies do
if self.bodies[i]:isVisible() then
self.bodies[i]:drawNormal()
end
end
end)
self.shadowShader:send('normalMap', self.normalMap)
self.shadowShader:send("invert_normal", self.normalInvert == true)
love.graphics.setCanvas( self.shadow_buffer )
love.graphics.clear()
love.graphics.setCanvas()
for i = 1, #self.lights do
local light = self.lights[i]
if light:isVisible() then
-- create shadow map for this light
love.graphics.setCanvas( self.shadowMap )
love.graphics.clear()
love.graphics.setCanvas()
util.drawto(self.shadowMap, l, t, s, function()
--I dont know if it uses both or just calls both
love.graphics.stencil(function()
local angle = light.direction - (light.angle / 2.0)
love.graphics.arc("fill", light.x, light.y, light.range, angle, angle + light.angle)
end)
love.graphics.setStencilTest("greater",0)
love.graphics.stencil(function()
love.graphics.setShader(self.image_mask)
for k = 1, #self.bodies do
if self.bodies[k]:inLightRange(light) and self.bodies[k]:isVisible() then
self.bodies[k]:drawStencil()
end
end
love.graphics.setShader()
end)
love.graphics.setStencilTest("equal", 0)
for k = 1, #self.bodies do
if self.bodies[k]:inLightRange(light) and self.bodies[k]:isVisible() then
self.bodies[k]:drawShadow(light)
end
end
end)
-- draw scene for this light using normals and shadowmap
self.shadowShader:send('lightColor', {light.red / 255.0, light.green / 255.0, light.blue / 255.0})
self.shadowShader:send("lightPosition", {(light.x + l/s) * s, (light.y + t/s) * s, (light.z * 10) / 255.0})
self.shadowShader:send('lightRange',light.range * s)
self.shadowShader:send("lightSmooth", light.smooth)
self.shadowShader:send("lightGlow", {1.0 - light.glowSize, light.glowStrength})
util.drawCanvasToCanvas(self.shadowMap, self.shadow_buffer, {
blendmode = 'add',
shader = self.shadowShader,
stencil = function()
local angle = light.direction - (light.angle / 2.0)
love.graphics.arc("fill", (light.x + l/s) * s, (light.y + t/s) * s, light.range, angle, angle + light.angle)
end
})
end
end
-- add in ambient color
util.drawto(self.shadow_buffer, l, t, s, function()
love.graphics.setBlendMode("add")
love.graphics.setColor({self.ambient[1], self.ambient[2], self.ambient[3]})
love.graphics.rectangle("fill", -l/s, -t/s, w/s,h/s)
end)
self.post_shader:drawBlur(self.shadow_buffer, {self.shadowBlur})
util.drawCanvasToCanvas(self.shadow_buffer, self.render_buffer, {blendmode = "multiply"})
love.graphics.setStencilTest()
end
-- draw material
function light_world:drawMaterial(l,t,w,h,s)
for i = 1, #self.bodies do
if self.bodies[i]:isVisible() then
self.bodies[i]:drawMaterial()
end
end
end
-- draw glow
function light_world:drawGlow(l,t,w,h,s)
if self.glowDown then
self.glowTimer = math.max(0.0, self.glowTimer - love.timer.getDelta())
else
self.glowTimer = math.min(self.glowTimer + love.timer.getDelta(), 1.0)
end
if self.glowTimer == 1.0 or self.glowTimer == 0.0 then
self.glowDown = not self.glowDown
end
local has_glow = false
-- create glow map
love.graphics.setCanvas( self.glowMap )
love.graphics.clear()
love.graphics.setCanvas()
util.drawto(self.glowMap, l, t, s, function()
for i = 1, #self.bodies do
if self.bodies[i]:isVisible() and self.bodies[i].glowStrength > 0.0 then
has_glow = true
self.bodies[i]:drawGlow()
end
end
end)
if has_glow then
self.post_shader:drawBlur(self.glowMap, {self.glowBlur})
util.drawCanvasToCanvas(self.glowMap, self.render_buffer, {blendmode = "add"})
end
end
-- draw refraction
function light_world:drawRefraction(l,t,w,h,s)
-- create refraction map
love.graphics.setCanvas( self.refractionMap )
love.graphics.clear()
love.graphics.setCanvas()
util.drawto(self.refractionMap, l, t, s, function()
for i = 1, #self.bodies do
if self.bodies[i]:isVisible() then
self.bodies[i]:drawRefraction()
end
end
end)
self.refractionShader:send("backBuffer", self.render_buffer)
self.refractionShader:send("refractionStrength", self.refractionStrength)
util.drawCanvasToCanvas(self.refractionMap, self.render_buffer, {shader = self.refractionShader})
end
-- draw reflection
function light_world:drawReflection(l,t,w,h,s)
-- create reflection map
love.graphics.setCanvas( self.reflectionMap )
love.graphics.clear()
love.graphics.setCanvas()
util.drawto(self.reflectionMap, l, t, s, function()
for i = 1, #self.bodies do
if self.bodies[i]:isVisible() then
self.bodies[i]:drawReflection()
end
end
end)
self.reflectionShader:send("backBuffer", self.render_buffer)
self.reflectionShader:send("reflectionStrength", self.reflectionStrength)
self.reflectionShader:send("reflectionVisibility", self.reflectionVisibility)
util.drawCanvasToCanvas(self.reflectionMap, self.render_buffer, {shader = self.reflectionShader})
end
-- new light
function light_world:newLight(x, y, red, green, blue, range)
self.lights[#self.lights + 1] = Light(x, y, red, green, blue, range)
return self.lights[#self.lights]
end
function light_world:clear()
light_world:clearLights()
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:clearLights() self.lights = {} end
function light_world:clearBodies() self.bodies = {} end
function light_world:setAmbientColor(red, green, blue) self.ambient = {red, green, blue} end
function light_world:setShadowBlur(blur) self.shadowBlur = blur end
function light_world:setGlowStrength(strength) self.glowBlur = strength end
function light_world:setRefractionStrength(strength) self.refractionStrength = strength end
function light_world:setReflectionStrength(strength) self.reflectionStrength = strength end
function light_world:setReflectionVisibility(visibility) self.reflectionVisibility = visibility end
function light_world:getBodyCount() return #self.bodies end
function light_world:getBody(n) return self.bodies[n] end
function light_world:getLightCount() return #self.lights end
function light_world:getLight(n) return self.lights[n] end
function light_world:newRectangle(...) return self:newBody("rectangle", ...) end
function light_world:newAnimationGrid(...) return self:newBody("animation", ...) end
function light_world:newCircle(...) return self:newBody("circle", ...) end
function light_world:newPolygon(...) return self:newBody("polygon", ...) end
function light_world:newImage(...) return self:newBody("image", ...) end
function light_world:newRefraction(...)
self.disableRefraction = false
return self:newBody("refraction", ...)
end
function light_world:newReflection(normal, ...)
self.disableReflection = false
return self:newBody("reflection", ...)
end
-- new body
function light_world:newBody(type, ...)
local id = #self.bodies + 1
self.bodies[id] = Body(id, type, ...)
return self.bodies[#self.bodies]
end
function light_world:is_body(target)
return target.type ~= nil
end
function light_world:is_light(target)
return target.angle ~= nil
end
function light_world:remove(to_kill)
if self:is_body(to_kill) then
for i = 1, #self.bodies do
if self.bodies[i] == to_kill then
table.remove(self.bodies, i)
return true
end
end
elseif self:is_light(to_kill) then
for i = 1, #self.lights do
if self.lights[i] == to_kill then
table.remove(self.lights, i)
return true
end
end
end
-- failed to find it
return false
end
return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})
+130
View File
@@ -0,0 +1,130 @@
local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or ""
local util = require(_PACKAGE..'/util')
local light = {}
light.__index = light
local function new(x, y, r, g, b, range)
local obj = {
direction = 0,
angle = math.pi * 2.0,
x = x or 0,
y = y or 0,
z = 1,
red = r or 255,
green = g or 255,
blue = b or 255,
range = range or 300,
smooth = 1.0,
glowSize = 0.1,
glowStrength = 0.0,
visible = true,
is_on_screen = true,
}
return setmetatable(obj, light)
end
-- set position
function light:setPosition(x, y, z)
if x ~= self.x or y ~= self.y or (z and z ~= self.z) then
self.x = x
self.y = y
if z then
self.z = z
end
end
end
-- move position
function light:move(x, y, z)
if x then
self.x = self.x + x
end
if y then
self.y = self.y + y
end
if z then
self.z = self.z + z
end
end
-- get position
function light:getPosition()
return self.x, self.y, self.z
end
-- set color
function light:setColor(red, green, blue)
self.red = red
self.green = green
self.blue = blue
end
-- get range
function light:getRange()
return self.range
end
-- set range
function light:setRange(range)
if range ~= self.range then
self.range = range
end
end
-- set direction
function light:setDirection(direction)
if direction ~= self.direction then
if direction > math.pi * 2 then
self.direction = math.mod(direction, math.pi * 2)
elseif direction < 0.0 then
self.direction = math.pi * 2 - math.mod(math.abs(direction), math.pi * 2)
else
self.direction = direction
end
end
end
-- set angle
function light:setAngle(angle)
if angle ~= self.angle then
if angle > math.pi then
self.angle = math.mod(angle, math.pi)
elseif angle < 0.0 then
self.angle = math.pi - math.mod(math.abs(angle), math.pi)
else
self.angle = angle
end
end
end
-- set glow size
function light:setSmooth(smooth)
self.smooth = smooth
end
-- set glow size
function light:setGlowSize(size)
self.glowSize = size
end
-- set glow strength
function light:setGlowStrength(strength)
self.glowStrength = strength
end
function light:isVisible()
return self.visible and self.is_on_screen
end
function light:inRange(l,t,w,h,s)
local lx, ly, rs = (self.x + l/s) * s, (self.y + t/s) * s, self.range * s
return self.visible and (lx + rs) > 0 and (lx - rs) < w/s and (ly + rs) > 0 and (ly - rs) < h/s
end
function light:setVisible(visible)
self.visible = visible
end
return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})
+121
View File
@@ -0,0 +1,121 @@
local normal_map = {}
function normal_map.fromHeightMap(heightMap, strength)
local imgData = heightMap:getData()
local imgData2 = love.image.newImageData(heightMap:getWidth(), heightMap:getHeight())
local red, green, blue, alpha
local x, y
local matrix = {}
matrix[1] = {}
matrix[2] = {}
matrix[3] = {}
strength = strength or 1.0
for i = 0, heightMap:getHeight() - 1 do
for k = 0, heightMap:getWidth() - 1 do
for l = 1, 3 do
for m = 1, 3 do
if k + (l - 1) < 1 then
x = heightMap:getWidth() - 1
elseif k + (l - 1) > heightMap:getWidth() - 1 then
x = 1
else
x = k + l - 1
end
if i + (m - 1) < 1 then
y = heightMap:getHeight() - 1
elseif i + (m - 1) > heightMap:getHeight() - 1 then
y = 1
else
y = i + m - 1
end
local red, green, blue, alpha = imgData:getPixel(x, y)
matrix[l][m] = red
end
end
red = (255 + ((matrix[1][2] - matrix[2][2]) + (matrix[2][2] - matrix[3][2])) * strength) / 2.0
green = (255 + ((matrix[2][2] - matrix[1][1]) + (matrix[2][3] - matrix[2][2])) * strength) / 2.0
blue = 192
imgData2:setPixel(k, i, red, green, blue)
end
end
return love.graphics.newImage(imgData2)
end
function normal_map.generateFlat(img, mode)
local imgData = img:getData()
local imgNormalData = love.image.newImageData(img:getWidth(), img:getHeight())
local color
if mode == "top" then
color = {127, 127, 255}
elseif mode == "front" then
color = {127, 0, 127}
elseif mode == "back" then
color = {127, 255, 127}
elseif mode == "left" then
color = {31, 0, 223}
elseif mode == "right" then
color = {223, 0, 127}
end
for i = 0, img:getHeight() - 1 do
for k = 0, img:getWidth() - 1 do
local r, g, b, a = imgData:getPixel(k, i)
imgNormalData:setPixel(k, i, color[1], color[2], color[3], a)
end
end
return love.graphics.newImage(imgNormalData)
end
function normal_map.generateGradient(img, horizontalGradient, verticalGradient)
horizontalGradient = horizontalGradient or "gradient"
verticalGradient = verticalGradient or horizontalGradient
local imgData = img:getData()
local imgWidth, imgHeight = img:getWidth(), img:getHeight()
local imgNormalData = love.image.newImageData(imgWidth, imgHeight)
local dx = 255.0 / imgWidth
local dy = 255.0 / imgHeight
local nx
local ny
local nz
for i = 0, imgWidth - 1 do
for k = 0, imgHeight - 1 do
local r, g, b, a = imgData:getPixel(i, k)
if a > 0 then
if horizontalGradient == "gradient" then
nx = i * dx
elseif horizontalGradient == "inverse" then
nx = 255 - i * dx
else
nx = 127
end
if verticalGradient == "gradient" then
ny = 127 - k * dy * 0.5
nz = 255 - k * dy * 0.5
elseif verticalGradient == "inverse" then
ny = 127 + k * dy * 0.5
nz = 127 - k * dy * 0.25
else
ny = 255
nz = 127
end
imgNormalData:setPixel(i, k, nx, ny, nz, 255)
end
end
end
return love.graphics.newImage(imgNormalData)
end
return normal_map
+164
View File
@@ -0,0 +1,164 @@
--[[
The MIT License (MIT)
Copyright (c) 2014 Marcus Ihde
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 _PACKAGE = (...):match("^(.+)[%./][^%./]+") or ""
local util = require(_PACKAGE..'/util')
local post_shader = {}
post_shader.__index = post_shader
local files = love.filesystem.getDirectoryItems(_PACKAGE.."/shaders/postshaders")
local shaders = {}
for i,v in ipairs(files) do
local name = _PACKAGE.."/shaders/postshaders".."/"..v
if love.filesystem.isFile(name) then
local str = love.filesystem.read(name)
local effect = love.graphics.newShader(name)
local defs = {}
for vtype, extern in str:gmatch("extern (%w+) (%w+)") do
defs[extern] = true
end
local shaderName = name:match(".-([^\\|/]-[^%.]+)$"):gsub("%.glsl", "")
shaders[shaderName] = {effect, defs}
end
end
local function new()
local obj = {effects = {}}
local class = setmetatable(obj, post_shader)
class:refreshScreenSize()
return class
end
function post_shader:refreshScreenSize(w, h)
w, h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
self.back_buffer = love.graphics.newCanvas(w, h)
end
function post_shader:addEffect(shaderName, ...)
self.effects[shaderName] = {...}
end
function post_shader:removeEffect(shaderName)
self.effects[shaderName] = nil
end
function post_shader:toggleEffect(shaderName, ...)
if self.effects[shaderName] ~= nil then
self:removeEffect(shaderName)
else
self:addEffect(shaderName, ...)
end
end
function post_shader:drawWith(canvas)
for shader, args in pairs(self.effects) do
if shader == "bloom" then
self:drawBloom(canvas, args)
elseif shader == "blur" then
self:drawBlur(canvas, args)
elseif shader == "tilt_shift" then
self:drawTiltShift(canvas, args)
else
self:drawShader(shader, canvas, args)
end
end
util.drawCanvasToCanvas(canvas)
end
function post_shader:drawBloom(canvas, args)
shaders['blurv'][1]:send("steps", args[1] or 2.0)
shaders['blurh'][1]:send("steps", args[1] or 2.0)
util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = shaders['blurv'][1]})
util.process(self.back_buffer, {shader = shaders['blurh'][1]})
util.process(self.back_buffer, {shader = shaders['contrast'][1]})
util.process(canvas, {shader = shaders['contrast'][1]})
util.drawCanvasToCanvas(self.back_buffer, canvas, {blendmode = "add", color = {255, 255, 255, (args[2] or 0.25) * 255}})
end
function post_shader:drawBlur(canvas, args)
shaders['blurv'][1]:send("steps", args[1] or 0.0)
shaders['blurh'][1]:send("steps", args[2] or args[1] or 0.0)
util.process(canvas, {shader = shaders['blurv'][1], blendmode = "alpha"})
util.process(canvas, {shader = shaders['blurh'][1], blendmode = "alpha"})
end
function post_shader:drawTiltShift(canvas, args)
shaders['blurv'][1]:send("steps", args[1] or 2.0)
shaders['blurh'][1]:send("steps", args[2] or 2.0)
util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = shaders['blurv'][1]})
util.process(self.back_buffer, {shader = shaders['blurh'][1]})
shaders['tilt_shift'][1]:send("imgBuffer", canvas)
util.drawCanvasToCanvas(self.back_buffer, canvas, {shader = shaders['tilt_shift'][1]})
end
function post_shader:drawShader(shaderName, canvas, args)
local current_arg = 1
local effect = shaders[shaderName]
if effect == nil then
print("no shader called "..shaderName)
return
end
for def in pairs(effect[2]) do
if def == "time" then
effect[1]:send("time", love.timer.getTime())
elseif def == "palette" then
effect[1]:send("palette", unpack(process_palette({
args[current_arg],
args[current_arg + 1],
args[current_arg + 2],
args[current_arg + 3]
})))
current_arg = current_arg + 4
elseif def == "tint" then
effect[1]:send("tint", {process_tint(args[1], args[2], args[3])})
current_arg = current_arg + 3
elseif def == "imgBuffer" then
effect[1]:send("imgBuffer", canvas)
else
local value = args[current_arg]
if value ~= nil then
effect[1]:send(def, value)
end
current_arg = current_arg + 1
end
end
util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = effect[1]})
util.drawCanvasToCanvas(self.back_buffer, canvas)
end
function process_tint(r, g, b)
return (r and r/255.0 or 1.0), (g and g/255.0 or 1.0), (b and b/255.0 or 1.0)
end
function process_palette(palette)
for i = 1, #palette do
palette[i] = {process_tint(unpack(palette[i]))}
end
return palette
end
return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})
+19
View File
@@ -0,0 +1,19 @@
extern Image glowImage;
extern float glowTime;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec3 glowInfo = Texel(glowImage, texture_coords).rgb;
if(glowInfo.r != glowInfo.g) {
float glowStrength = glowTime + glowInfo.b;
if(mod(glowStrength, 2.0) < 1.0) {
glowInfo.b = mod(glowStrength, 1.0);
} else {
glowInfo.b = 1.0 - mod(glowStrength, 1.0);
}
return Texel(texture, texture_coords) * (glowInfo.g + glowInfo.b * (glowInfo.r - glowInfo.g));
}
return vec4(Texel(texture, texture_coords).rgb * glowInfo.r, 1.0);
}
+6
View File
@@ -0,0 +1,6 @@
//https://love2d.org/wiki/love.graphics.setStencil image mask
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
if (Texel(texture, texture_coords).rgb == vec3(0.0))
discard;
return vec4(1.0);
}
+10
View File
@@ -0,0 +1,10 @@
extern Image material;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec4 normal = Texel(texture, texture_coords);
if(normal.a == 1.0) {
return Texel(material, vec2(normal.x, normal.y));
} else {
return vec4(0.0);
}
}
@@ -0,0 +1,13 @@
extern float exposure = 0.7;
extern float brightness = 1.0;
extern vec3 lumacomponents = vec3(1.0, 1.0, 1.0);
const vec3 lumcoeff = vec3(0.212671, 0.715160, 0.072169);
vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixel_coords) {
vec4 input0 = Texel(texture, texcoord);
input0 *= (exp2(input0)*vec4(exposure));
vec4 lumacomponents = vec4(lumcoeff * lumacomponents, 0.0 );
float luminance = dot(input0,lumacomponents);
vec4 luma = vec4(luminance);
return vec4(luma.rgb * brightness, 1.0);
}
+12
View File
@@ -0,0 +1,12 @@
extern float steps = 2.0;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 pSize = vec2(1.0 / love_ScreenSize.x, 1.0 / love_ScreenSize.y);
vec4 col = Texel(texture, texture_coords);
for(int i = 1; i <= steps; i++) {
col = col + Texel(texture, vec2(texture_coords.x, texture_coords.y - pSize.y * i));
col = col + Texel(texture, vec2(texture_coords.x, texture_coords.y + pSize.y * i));
}
col = col / (steps * 2.0 + 1.0);
return vec4(col.r, col.g, col.b, 1.0);
}
+12
View File
@@ -0,0 +1,12 @@
extern float steps = 2.0;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 pSize = vec2(1.0 / love_ScreenSize.x, 1.0 / love_ScreenSize.y);
vec4 col = Texel(texture, texture_coords);
for(int i = 1; i <= steps; i++) {
col = col + Texel(texture, vec2(texture_coords.x - pSize.x * i, texture_coords.y));
col = col + Texel(texture, vec2(texture_coords.x + pSize.x * i, texture_coords.y));
}
col = col / (steps * 2.0 + 1.0);
return vec4(col.r, col.g, col.b, 1.0);
}
@@ -0,0 +1,12 @@
extern vec2 redStrength = vec2(4.0, 3.0);
extern vec2 greenStrength = vec2(-2.0, -1.0);
extern vec2 blueStrength = vec2(1.0, -3.0);
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 pSize = vec2(1.0 / love_ScreenSize.x, 1.0 / love_ScreenSize.y);
float colRed = Texel(texture, vec2(texture_coords.x + pSize.x * redStrength.x, texture_coords.y - pSize.y * redStrength.y)).r;
float colGreen = Texel(texture, vec2(texture_coords.x + pSize.x * greenStrength.x, texture_coords.y - pSize.y * greenStrength.y)).g;
float colBlue = Texel(texture, vec2(texture_coords.x + pSize.x * blueStrength.x, texture_coords.y - pSize.y * blueStrength.y)).b;
return vec4(colRed, colGreen, colBlue, 1.0);
}
+5
View File
@@ -0,0 +1,5 @@
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec3 col = Texel(texture, texture_coords).rgb * 2.0;
col *= col;
return vec4(col, 1.0);
}
+18
View File
@@ -0,0 +1,18 @@
#define distortion 0.2
vec2 radialDistortion(vec2 coord) {
vec2 cc = coord - 0.5;
float dist = dot(cc, cc) * distortion;
return coord + cc * (1.0 + dist) * dist;
}
vec4 checkTexelBounds(Image texture, vec2 coords) {
vec2 ss = step(coords, vec2(1.0, 1.0)) * step(vec2(0.0, 0.0), coords);
return Texel(texture, coords) * ss.x * ss.y;
}
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 coords = radialDistortion(texture_coords);
vec4 texcolor = checkTexelBounds(texture, coords);
texcolor.a = 1.0;
return texcolor;
}
+39
View File
@@ -0,0 +1,39 @@
/*
Edge shader
Author: Themaister
License: Public domain.
modified by slime73 for use with love2d and mari0
*/
vec3 grayscale(vec3 color)
{
return vec3(dot(color, vec3(0.3, 0.59, 0.11)));
}
vec4 effect(vec4 vcolor, Image texture, vec2 tex, vec2 pixel_coords)
{
vec4 texcolor = Texel(texture, tex);
float x = 0.5 / love_ScreenSize.x;
float y = 0.5 / love_ScreenSize.y;
vec2 dg1 = vec2( x, y);
vec2 dg2 = vec2(-x, y);
vec3 c00 = Texel(texture, tex - dg1).xyz;
vec3 c02 = Texel(texture, tex + dg2).xyz;
vec3 c11 = texcolor.xyz;
vec3 c20 = Texel(texture, tex - dg2).xyz;
vec3 c22 = Texel(texture, tex + dg1).xyz;
vec2 texsize = love_ScreenSize.xy;
vec3 first = mix(c00, c20, fract(tex.x * texsize.x + 0.5));
vec3 second = mix(c02, c22, fract(tex.x * texsize.x + 0.5));
vec3 res = mix(first, second, fract(tex.y * texsize.y + 0.5));
vec4 final = vec4(5.0 * grayscale(abs(res - c11)), 1.0);
return clamp(final, 0.0, 1.0);
}
+8
View File
@@ -0,0 +1,8 @@
extern vec3 palette[4];
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){
vec4 pixel = Texel(texture, texture_coords);
int index = int(min(0.9999, max(0.0001,(pixel.r + pixel.g + pixel.b) / 3.0)) * 4);
return vec4(palette[index], 1.0);
}
+8
View File
@@ -0,0 +1,8 @@
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec4 rgb = Texel(texture, texture_coords);
vec4 intens = smoothstep(0.2,0.8,rgb) + normalize(vec4(rgb.xyz, 1.0));
if (fract(pixel_coords.y * 0.5) > 0.5) intens = rgb * 0.8;
intens.a = 1.0;
return intens;
}
+13
View File
@@ -0,0 +1,13 @@
extern float time = 0.0;
extern vec3 tint = vec3(1.0, 1.0, 1.0);
extern float fudge = 0.1;
float rand(vec2 position, float seed) {
return fract(sin(dot(position.xy,vec2(12.9898, 78.233))) * seed);
}
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){
vec4 pixel = Texel(texture, texture_coords);
float intensity = (pixel.r + pixel.g + pixel.b) / 3.0 + (rand(texture_coords, time) - 0.5) * fudge;
return vec4(intensity * tint.r, intensity * tint.g, intensity * tint.b, 1.0);
}
+157
View File
@@ -0,0 +1,157 @@
/*
caligari's scanlines
Copyright (C) 2011 caligari
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
(caligari gave their consent to have this shader distributed under the GPL
in this message:
http://board.byuu.org/viewtopic.php?p=36219#p36219
"As I said to Hyllian by PM, I'm fine with the GPL (not really a bi
deal...)"
)
*/
// 0.5 = the spot stays inside the original pixel
// 1.0 = the spot bleeds up to the center of next pixel
#define PHOSPHOR_WIDTH 0.9
#define PHOSPHOR_HEIGHT 0.65
// Used to counteract the desaturation effect of weighting.
#define COLOR_BOOST 1.9
// Constants used with gamma correction.
#define InputGamma 2.4
#define OutputGamma 2.2
// Uncomment to only draw every third pixel, which highlights the shape
// of individual (remaining) spots.
// #define DEBUG
// Uncomment one of these to choose a gamma correction method.
// If none are uncommented, no gamma correction is done.
// #define REAL_GAMMA
#define FAKE_GAMMA
// #define FAKER_GAMMA
#ifdef REAL_GAMMA
#define GAMMA_IN(color) pow(color, vec4(InputGamma))
#define GAMMA_OUT(color) pow(color, vec4(1.0 / OutputGamma))
#elif defined FAKE_GAMMA
/*
* Approximations:
* for 1<g<2 : x^g ~ ax + bx^2
* where a=6/(g+1)-2 and b=1-a
* for 2<g<3 : x^g ~ ax^2 + bx^3
* where a=12/(g+1)-3 and b=1-a
* for 1<g<2 : x^(1/g) ~ (sqrt(a^2+4bx)-a)
* where a=6/(g+1)-2 and b=1-a
* for 2<g<3 : x^(1/g) ~ (a sqrt(x) + b sqrt(sqrt(x)))
* where a = 6 - 15g / 2(g+1) and b = 1-a
*/
vec4 A_IN = vec4( 12.0/(InputGamma+1.0)-3.0 );
vec4 B_IN = vec4(1.0) - A_IN;
vec4 A_OUT = vec4(6.0 - 15.0 * OutputGamma / 2.0 / (OutputGamma+1.0));
vec4 B_OUT = vec4(1.0) - A_OUT;
#define GAMMA_IN(color) ( (A_IN + B_IN * color) * color * color )
#define GAMMA_OUT(color) ( A_OUT * sqrt(color) + B_OUT * sqrt( sqrt(color) ) )
#elif defined FAKER_GAMMA
vec4 A_IN = vec4(6.0/( InputGamma/OutputGamma + 1.0 ) - 2.0);
vec4 B_IN = vec4(1.0) - A_IN;
#define GAMMA_IN(color) ( (A_IN + B_IN * color) * color )
#define GAMMA_OUT(color) color
#else // No gamma correction
#define GAMMA_IN(color) color
#define GAMMA_OUT(color) color
#endif
#ifdef DEBUG
vec4 grid_color( vec2 coords )
{
vec2 snes = floor( coords * love_ScreenSize );
if ( (mod(snes.x, 3.0) == 0.0) && (mod(snes.y, 3.0) == 0.0) )
return texture2D(_tex0_, coords);
else
return vec4(0.0);
}
#define TEX2D(coords) GAMMA_IN( grid_color( coords ) )
#else // DEBUG
#define TEX2D(coords) GAMMA_IN( texture2D(_tex0_, coords) )
#endif // DEBUG
vec2 onex = vec2( 1.0/love_ScreenSize.x, 0.0 );
vec2 oney = vec2( 0.0, 1.0/love_ScreenSize.y );
vec4 effect(vec4 vcolor, Image texture, vec2 texCoord, vec2 pixel_coords)
{
vec2 coords = (texCoord * love_ScreenSize.xy);
vec2 pixel_start = floor(coords);
coords -= pixel_start;
vec2 pixel_center = pixel_start + vec2(0.5);
vec2 texture_coords = pixel_center / love_ScreenSize.xy;
vec4 color = vec4(0.0);
vec4 pixel;
vec3 centers = vec3(-0.25,-0.5,-0.75);
vec3 posx = vec3(coords.x);
vec3 hweight;
float vweight;
float dx,dy;
float w;
float i,j;
for (j = -1.0; j<=1.0; j++) {
// Vertical weight
dy = abs(coords.y - 0.5 - j );
vweight = smoothstep(1.0,0.0, dy / PHOSPHOR_HEIGHT);
if (vweight !=0.0 ) {
for ( i = -1.0; i<=1.0; i++ ) {
pixel = TEX2D(
texture_coords
+ i * onex
+ j * oney
);
/* Evaluate the distance (in x) from
* the pixel (posx) to the RGB centers
* (~centers):
* x_red = 0.25
* x_green = 0.5
* x_blue = 0.75
* if the distance > PHOSPHOR_WIDTH,
* this pixel doesn't contribute
* otherwise, smoothstep gives the
* weight of the contribution
*/
hweight = smoothstep(
1.0, 0.0,
abs((posx + centers - vec3(i))
/ vec3(PHOSPHOR_WIDTH))
);
color.rgb +=
pixel.rgb *
hweight *
vec3(vweight);
}
}
}
color *= vec4(COLOR_BOOST);
color.a = 1.0;
return clamp(GAMMA_OUT(color), 0.0, 1.0);
}
+45
View File
@@ -0,0 +1,45 @@
/*
Plain (and obviously inaccurate) phosphor.
Author: Themaister
License: Public Domain
*/
// modified by slime73 for use with love pixeleffects
vec3 to_focus(float pixel)
{
pixel = mod(pixel + 3.0, 3.0);
if (pixel >= 2.0) // Blue
return vec3(pixel - 2.0, 0.0, 3.0 - pixel);
else if (pixel >= 1.0) // Green
return vec3(0.0, 2.0 - pixel, pixel - 1.0);
else // Red
return vec3(1.0 - pixel, pixel, 0.0);
}
vec4 effect(vec4 vcolor, Image texture, vec2 texture_coords, vec2 pixel_coords)
{
float y = mod(texture_coords.y * love_ScreenSize.y, 1.0);
float intensity = exp(-0.2 * y);
vec2 one_x = vec2(1.0 / (3.0 * love_ScreenSize.x), 0.0);
vec3 color = Texel(texture, texture_coords - 0.0 * one_x).rgb;
vec3 color_prev = Texel(texture, texture_coords - 1.0 * one_x).rgb;
vec3 color_prev_prev = Texel(texture, texture_coords - 2.0 * one_x).rgb;
float pixel_x = 3.0 * texture_coords.x * love_ScreenSize.x;
vec3 focus = to_focus(pixel_x - 0.0);
vec3 focus_prev = to_focus(pixel_x - 1.0);
vec3 focus_prev_prev = to_focus(pixel_x - 2.0);
vec3 result =
0.8 * color * focus +
0.6 * color_prev * focus_prev +
0.3 * color_prev_prev * focus_prev_prev;
result = 2.3 * pow(result, vec3(1.4));
return vec4(intensity * result, 1.0);
}
+62
View File
@@ -0,0 +1,62 @@
#define glarebasesize 0.896
#define power 0.50
extern float time;
const vec3 green = vec3(0.17, 0.62, 0.25);
float luminance(vec3 color)
{
return (0.212671 * color.r) + (0.715160 * color.g) + (0.072169 * color.b);
}
float scanline(float ypos)
{
float c = mod(time * 3.0 + ypos * 5.0, 15.0);
return 1.0 - smoothstep(0.0, 1.0, c);
}
vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixel_coords)
{
vec4 texcolor = Texel(texture, texcoord);
vec4 sum = vec4(0.0);
vec4 bum = vec4(0.0);
vec2 glaresize = vec2(glarebasesize) / love_ScreenSize.xy;
float y_one = 1.0 / love_ScreenSize.y;
int j;
int i;
for (i = -2; i < 2; i++)
{
for (j = -1; j < 1; j++)
{
sum += Texel(texture, texcoord + vec2(-i, j)*glaresize) * power;
bum += Texel(texture, texcoord + vec2(j, i)*glaresize) * power;
}
}
float a = (scanline(texcoord.y) + scanline(texcoord.y + y_one * 1.5) + scanline(texcoord.y - y_one * 1.5)) / 3.0;
vec4 finalcolor;
if (texcolor.r < 2.0)
{
finalcolor = sum*sum*sum*0.001+bum*bum*bum*0.0080 * (0.8 + 0.05 * a) + texcolor;
}
else
{
finalcolor = vec4(0.0, 0.0, 0.0, 1.0);
}
float lum = pow(luminance(finalcolor.rgb), 1.4);
finalcolor.rgb = lum * green + (a * 0.03);
finalcolor.a = 1.0;
return finalcolor;
}
+10
View File
@@ -0,0 +1,10 @@
const float pixel_w = 2.0;
const float pixel_h = 2.0;
vec4 effect(vec4 vcolor, Image texture, vec2 uv, vec2 pixel_coords)
{
float dx = pixel_w*(1.0/love_ScreenSize.x);
float dy = pixel_h*(1.0/love_ScreenSize.y);
vec2 coord = vec2(dx*floor(uv.x/dx), dy*floor(uv.y/dy));
return Texel(texture, coord);
}
+20
View File
@@ -0,0 +1,20 @@
#define nsamples 5
extern number blurstart = 1.0; // 0 to 1
extern number blurwidth = -0.02; // -1 to 1
vec4 effect(vec4 vcolor, Image texture, vec2 texture_coords, vec2 pixel_coords)
{
vec4 c = vec4(0.0, 0.0, 0.0, 1.0);
int i;
for (i = 0; i < nsamples; i++)
{
number scale = blurstart + blurwidth * (i / float(nsamples-1));
c.rgb += Texel(texture, texture_coords * scale).rgb;
}
c.rgb /= nsamples;
return c;
}
+32
View File
@@ -0,0 +1,32 @@
extern float strength = 2.0;
extern float time = 0.0;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){
vec2 pSize = 1.0 / love_ScreenSize.xy;
float brightness = 1.0;
float offsetX = sin(texture_coords.y * 10.0 + time * strength) * pSize.x;
float corner = 500.0;
if(texture_coords.x < 0.5) {
if(texture_coords.y < 0.5) {
brightness = min(texture_coords.x * texture_coords.y * corner, 1.0);
} else {
brightness = min(texture_coords.x * (1.0 - texture_coords.y) * corner, 1.0);
}
} else {
if(texture_coords.y < 0.5) {
brightness = min((1.0 - texture_coords.x) * texture_coords.y * corner, 1.0);
} else {
brightness = min((1.0 - texture_coords.x) * (1.0 - texture_coords.y) * corner, 1.0);
}
}
float red = Texel(texture, vec2(texture_coords.x + offsetX, texture_coords.y + pSize.y * 0.5)).r;
float green = Texel(texture, vec2(texture_coords.x + offsetX, texture_coords.y - pSize.y * 0.5)).g;
float blue = Texel(texture, vec2(texture_coords.x + offsetX, texture_coords.y)).b;
if(fract(gl_FragCoord.y * (0.5*4.0/3.0)) > 0.5) {
return vec4(vec3(red, green, blue) * brightness, 1.0);
} else {
return vec4(vec3(red * 0.75, green * 0.75, blue * 0.75) * brightness, 1.0);
}
}
+12
View File
@@ -0,0 +1,12 @@
extern Image imgBuffer;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){
vec4 pixel = Texel(texture, texture_coords);
vec4 pixelBuffer = Texel(imgBuffer, texture_coords);
if(texture_coords.y > 0.5) {
return vec4(pixel.rgb * (texture_coords.y - 0.5) * 2.0 + pixelBuffer.rgb * (1.0 - texture_coords.y) * 2.0, 1.0);
} else {
return vec4(pixel.rgb * (0.5 - texture_coords.y) * 2.0 + pixelBuffer.rgb * texture_coords.y * 2.0, 1.0);
}
}
+55
View File
@@ -0,0 +1,55 @@
/*
Themaister's Waterpaint shader
Placed in the public domain.
(From this thread: http://board.byuu.org/viewtopic.php?p=30483#p30483
PD declaration here: http://board.byuu.org/viewtopic.php?p=30542#p30542 )
modified by slime73 for use with love2d and mari0
*/
vec4 compress(vec4 in_color, float threshold, float ratio)
{
vec4 diff = in_color - vec4(threshold);
diff = clamp(diff, 0.0, 100.0);
return in_color - (diff * (1.0 - 1.0/ratio));
}
vec4 effect(vec4 vcolor, Image texture, vec2 tex, vec2 pixel_coords)
{
float x = 0.5 * (1.0 / love_ScreenSize.x);
float y = 0.5 * (1.0 / love_ScreenSize.y);
vec2 dg1 = vec2( x, y);
vec2 dg2 = vec2(-x, y);
vec2 dx = vec2(x, 0.0);
vec2 dy = vec2(0.0, y);
vec3 c00 = Texel(texture, tex - dg1).xyz;
vec3 c01 = Texel(texture, tex - dx).xyz;
vec3 c02 = Texel(texture, tex + dg2).xyz;
vec3 c10 = Texel(texture, tex - dy).xyz;
vec3 c11 = Texel(texture, tex).xyz;
vec3 c12 = Texel(texture, tex + dy).xyz;
vec3 c20 = Texel(texture, tex - dg2).xyz;
vec3 c21 = Texel(texture, tex + dx).xyz;
vec3 c22 = Texel(texture, tex + dg1).xyz;
vec2 texsize = love_ScreenSize.xy;
vec3 first = mix(c00, c20, fract(tex.x * texsize.x + 0.5));
vec3 second = mix(c02, c22, fract(tex.x * texsize.x + 0.5));
vec3 mid_horiz = mix(c01, c21, fract(tex.x * texsize.x + 0.5));
vec3 mid_vert = mix(c10, c12, fract(tex.y * texsize.y + 0.5));
vec3 res = mix(first, second, fract(tex.y * texsize.y + 0.5));
vec4 final = vec4(0.26 * (res + mid_horiz + mid_vert) + 3.5 * abs(res - mix(mid_horiz, mid_vert, 0.5)), 1.0);
final = compress(final, 0.8, 5.0);
final.a = 1.0;
return final;
}
+23
View File
@@ -0,0 +1,23 @@
extern Image backBuffer;
extern float reflectionStrength;
extern float reflectionVisibility;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 pSize = vec2(1.0 / love_ScreenSize.x, 1.0 / love_ScreenSize.y);
vec4 normal = Texel(texture, texture_coords);
if(normal.a > 0.0 && normal.r > 0.0) {
vec3 pColor = Texel(backBuffer, texture_coords).rgb;
vec4 pColor2;
for(int i = 0; i < reflectionStrength; i++) {
pColor2 = Texel(texture, vec2(texture_coords.x, texture_coords.y + pSize.y * i));
if(pColor2.a > 0.0 && pColor2.g > 0.0) {
vec3 rColor = Texel(backBuffer, vec2(texture_coords.x, texture_coords.y + pSize.y * i * 2.0)).rgb;
return vec4(rColor, (1.0 - i / reflectionStrength) * reflectionVisibility);
}
}
return vec4(0.0);
} else {
return vec4(0.0);
}
}
+18
View File
@@ -0,0 +1,18 @@
extern Image backBuffer;
extern float refractionStrength = 1.0;
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 pSize = vec2(1.0 / love_ScreenSize.x, 1.0 / love_ScreenSize.y);
vec4 normal = Texel(texture, texture_coords);
if(normal.b > 0.0) {
vec4 normalOffset = Texel(texture, vec2(texture_coords.x + (normal.x - 0.5) * pSize.x * refractionStrength, texture_coords.y + (normal.y - 0.5) * pSize.y * refractionStrength));
if(normalOffset.b > 0.0) {
return Texel(backBuffer, vec2(texture_coords.x + (normal.x - 0.5) * pSize.x * refractionStrength, texture_coords.y + (normal.y - 0.5) * pSize.y * refractionStrength));
} else {
return Texel(backBuffer, texture_coords);
}
} else {
return vec4(0.0);
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
Copyright (c) 2014 Tim Anema
light shadow, shine and normal shader all in one
*/
#define PI 3.1415926535897932384626433832795
extern Image normalMap; //a canvas containing shadow data only
extern vec3 lightPosition; //the light position on the screen(not global)
extern vec3 lightColor; //the rgb color of the light
extern float lightRange; //the range of the light
extern float lightSmooth; //smoothing of the lights attenuation
extern vec2 lightGlow; //how brightly the light bulb part glows
extern bool invert_normal; //if the light should invert normals
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
float dist = distance(lightPosition, vec3(pixel_coords, 1.0));
if(dist > lightRange) { //not in range draw in shadows
return vec4(0.0, 0.0, 0.0, 1.0);
}else{
vec4 shadowColor = Texel(texture, texture_coords);
vec4 normalColor = Texel(normalMap, texture_coords);
vec4 pixel;
//calculate attenuation of light based on the distance
float att = clamp((1.0 - dist / lightRange) / lightSmooth, 0.0, 1.0);
// if not on the normal map draw attenuated shadows
if(normalColor.a <= 0.0) {
//start with a dark color and add in the light color and shadow color
pixel = vec4(0.0, 0.0, 0.0, 1.0);
if (lightGlow.x < 1.0 && lightGlow.y > 0.0) {
pixel.rgb = clamp(lightColor * pow(att, lightSmooth) + pow(smoothstep(lightGlow.x, 1.0, att), lightSmooth) * lightGlow.y, 0.0, 1.0);
} else {
pixel.rgb = lightColor * pow(att, lightSmooth);
}
} else {
vec3 normal = normalize(vec3(normalColor.r,invert_normal ? 1 - normalColor.g : normalColor.g, normalColor.b) * 2.0 - 1.0);
//on the normal map, draw normal shadows
vec3 dir = vec3((lightPosition.xy - pixel_coords.xy) / love_ScreenSize.xy, lightPosition.z);
dir.x *= love_ScreenSize.x / love_ScreenSize.y;
vec3 diff = lightColor * max(dot(normalize(normal), normalize(dir)), 0.0);
//return the light that is effected by the normal and attenuation
pixel = vec4(diff * att, 1.0);
}
if(shadowColor.a > 0.0) {
pixel.rgb = pixel.rgb * shadowColor.rgb;
}
return pixel;
}
}
+62
View File
@@ -0,0 +1,62 @@
local util = {}
--TODO: the whole stencil/canvas system should be reviewed since it has been changed in a naive way
function util.process(canvas, options)
--TODO: now you cannot draw a canvas to itself
temp = love.graphics.newCanvas()
util.drawCanvasToCanvas(canvas, temp, options)
util.drawCanvasToCanvas(temp, canvas, options)
end
function util.drawCanvasToCanvas(canvas, other_canvas, options)
options = options or {}
util.drawto(other_canvas, 0, 0, 1, function()
if options["blendmode"] then
love.graphics.setBlendMode(options["blendmode"])
end
if options["shader"] then
love.graphics.setShader(options["shader"])
end
if options["stencil"] then
love.graphics.stencil(options["stencil"])
love.graphics.setStencilTest("greater",0)
end
if options["istencil"] then
love.graphics.stencil(options["istencil"])
love.graphics.setStencilTest("equal", 0)
end
if options["color"] then
love.graphics.setColor(unpack(options["color"]))
else
love.graphics.setColor(255,255,255)
end
if love.graphics.getCanvas() ~= canvas then
love.graphics.draw(canvas,0,0)
end
if options["blendmode"] then
love.graphics.setBlendMode("alpha")
end
if options["shader"] then
love.graphics.setShader()
end
if options["stencil"] or options["istencil"] then
--love.graphics.setInvertedStencil()
love.graphics.setStencilTest()
end
end)
end
function util.drawto(canvas, x, y, scale, cb)
local last_buffer = love.graphics.getCanvas()
love.graphics.push()
love.graphics.origin()
love.graphics.setCanvas(canvas)
love.graphics.translate(x, y)
love.graphics.scale(scale)
cb()
love.graphics.setCanvas(last_buffer)
love.graphics.pop()
end
return util
+59
View File
@@ -0,0 +1,59 @@
local vector = {}
vector.__index = vector
local function new(x, y)
if type(x) == "table" then
return setmetatable({
x = x[1],
y = y[1]
}, vector)
else
return setmetatable({
x = x or 0,
y = y or 0
}, vector)
end
end
function vector.__add(a, b)
return new(a.x + b.x, a.y + b.y)
end
function vector.__sub(a, b)
return new(a.x - b.x, a.y - b.y)
end
function vector.__mul(a, b)
if type(b) == "number" then
return new(a.x * b, a.y * b)
else
return a.x * b.x + a.y * b.y
end
end
function vector.__div(a, b)
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:dist(b)
return math.sqrt(math.pow(b.x - self.x, 2) + math.pow(b.y-self.y, 2))
end
function vector:unpack()
return self.x, self.y
end
function vector:rotate(theta)
return new((math.cos(theta) * self.x) - (math.sin(theta) * self.y),
(math.sin(theta) * self.x) + (math.cos(theta) * self.y))
end
function vector:scale(sx, sy)
return new(self.x * sx, self.y * sy)
end
return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})
+365 -38
View File
@@ -1,45 +1,372 @@
local game = require 'assets/scripts/game'
local stages = require 'assets/scripts/stages'
local controls = require 'assets/scripts/controls'
local bells = require 'assets/scripts/bells'
local camera = require 'assets/scripts/camera'
local stress = require 'assets/scripts/stress'
local messages = require 'assets/scripts/messages'
require "lib/postshader"
local LightWorld = require "lib"
local ProFi = require 'game.vendor.ProFi'
exf = {}
exf.current = nil
exf.available = {}
-- LOAD
function love.load()
game.load()
camera.load(game)
stages.load(game, camera)
stress.load(game, camera)
bells.load(game, camera)
controls.load(game)
messages.load(game)
end
exf.list = List:new()
exf.smallfont = love.graphics.newFont(12)
exf.mediumfont = love.graphics.newFont(18)
exf.bigfont = love.graphics.newFont(40)
exf.list.font = exf.smallfont
-- UPDATE
function love.update(dt)
--require('assets/scripts/vendor/lovebird').update()
game.world:update(dt)
game.update(dt)
camera.update(game)
stages.update(dt, game, camera)
stress.update(dt, game, camera)
bells.update(dt, game, camera)
controls.update(dt, game, camera)
messages.update(dt)
end
exf.bigball = love.graphics.newImage("game/gfx/not-estudio.png")
exf.music = love.audio.newSource("assets/audio/music/theme.mp3", "static")
exf.music:setVolume(0.9) -- 90% of ordinary volume
exf.music:setPitch(0.5) -- one octave lower
exf:playMusic()
-- Find available demos.
local files = love.filesystem.getDirectoryItems("game")
local n = 0
-- DRAW
function love.draw()
camera.gcam:draw(
function(l,t,w,h)
stages.draw()
stress.draw()
bells.draw(game)
for i, v in ipairs(files) do
is_file = love.filesystem.isFile("game/".. v )
if is_file then
n = n + 1
table.insert(exf.available, v);
local file = love.filesystem.newFile(v, love.file_read)
file:open("r")
local contents = love.filesystem.read("game/" .. v, 100)
local s, e, c = string.find(contents, "Example: ([%a%p ]-)[\r\n]")
file:close(file)
if not c then c = "Untitled" end
local title = " Inicia la Aventura!"
exf.list:add(title, v)
end
)
controls.draw()
messages.draw()
end
exf.list:done()
exf.resume()
end
function love.update(dt) end
function love.draw() end
function love.keypressed(k) end
function love.keyreleased(k) end
function love.mousepressed(x, y, b) end
function love.mousereleased(x, y, b) end
function exf.empty() end
function exf.update(dt)
exf.list:update(dt)
lightMouse:setPosition(love.mouse.getX(), love.mouse.getY())
end
function exf.draw()
lightWorld:draw(function()
love.graphics.setBackgroundColor(0, 0, 0)
love.graphics.setColor(10, 110,110)
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor(255, 255, 255, 191)
love.graphics.setFont(exf.bigfont)
love.graphics.print("Shall I Kill Her", 500, 50)
love.graphics.setFont(exf.mediumfont)
love.graphics.print("Un hombre atormentado por oscuros sentimientos de debate", 380, 140)
love.graphics.print("entre el bien y el mal. Debe tomar la decisión entre matar o no a ", 360, 160)
love.graphics.print("su actual pareja. Para ello recorre sus recuerdos en busca de una razón.", 335, 180)
exf.list:draw()
love.graphics.setColor(255, 255, 255)
love.graphics.draw(exf.bigball, 760 - 128, 600 - 128, love.timer.getTime(), 1, 1, exf.bigball:getWidth() * 0.5, exf.bigball:getHeight() * 0.5)
end)
end
function exf.keypressed(k)
end
function exf.keyreleased(k)
end
function exf.mousepressed(x, y, b)
exf.list:mousepressed(x, y, b)
end
function exf.mousereleased(x, y, b)
exf.list:mousereleased(x, y, b)
end
function exf.intable(t, e)
for k, v in ipairs(t) do
if v == e then return true end
end
return false
end
function exf.start(item, file)
local e_id = string.sub(item, 1, 4)
local e_rest = string.sub(item, 5)
local unused1, unused2, n = string.find(item, "(%s)%.lua")
exf:stopMusic()
if exf.intable(exf.available, file) then
if not love.filesystem.exists("game/" .. file) then
print("Could not load game .. " .. file)
else
-- Clear all callbacks.
love.load = exf.empty
love.update = exf.empty
love.draw = exf.empty
love.keypressed = exf.empty
love.keyreleased = exf.empty
love.mousepressed = exf.empty
love.mousereleased = exf.empty
love.filesystem.load("game/" .. file)()
exf.clear()
--love.window.setTitle(e_rest)
-- Redirect keypress
local o_keypressed = love.keypressed
love.keypressed =
function(k)
if k == "escape" then
exf.resume()
end
o_keypressed(k)
end
love.load()
end
else
print("Game ".. e_id .. " does not exist.")
end
end
function exf.clear()
love.graphics.setBackgroundColor(0,0,0)
love.graphics.setColor(255, 255, 255)
love.graphics.setLineWidth(1)
love.graphics.setLineStyle("smooth")
love.graphics.setBlendMode("alpha")
love.mouse.setVisible(true)
end
function exf.resume()
load = nil
love.update = exf.update
love.draw = exf.draw
love.keypressed = exf.keypressed
love.keyreleased = exf.keyreleased
love.mousepressed = exf.mousepressed
love.mousereleased = exf.mousereleased
love.mouse.setVisible(true)
love.window.setTitle("Shall I Kill Her")
-- create light world
lightWorld = LightWorld({
ambient = {127, 127, 127}
})
-- create light
lightMouse = lightWorld:newLight(0, 0, 255, 127, 63, 500)
lightMouse:setSmooth(2)
-- create shadow bodys
circleTest = lightWorld:newCircle(760 - 128, 600 - 128, exf.bigball:getWidth()*0.5)
end
function inside(mx, my, x, y, w, h)
return mx >= x and mx <= (x+w) and my >= y and my <= (y+h)
end
----------------------
-- List object
----------------------
List = {}
function List:new()
o = {}
setmetatable(o, self)
self.__index = self
o.items = {}
o.files = {}
o.x = 520
o.y = 300
o.width = 220
o.height = 160
o.item_height = 23
o.sum_item_height = 0
o.bar_size = 20
o.bar_pos = 0
o.bar_max_pos = 0
o.bar_width = 15
o.bar_lock = nil
return o
end
function List:add(item, file)
table.insert(self.items, item)
table.insert(self.files, file)
end
function List:done()
self.items.n = #self.items
-- Recalc bar size.
self.bar_pos = 0
local num_items = (self.height/self.item_height)
local ratio = num_items/self.items.n
self.bar_size = self.height * ratio
self.bar_max_pos = self.height - self.bar_size - 3
-- Calculate height of everything.
self.sum_item_height = (self.item_height+1) * self.items.n + 2
end
function List:hasBar()
return self.sum_item_height > self.height
end
function List:getBarRatio()
return self.bar_pos/self.bar_max_pos
end
function List:getOffset()
local ratio = self.bar_pos/self.bar_max_pos
return math.floor((self.sum_item_height-self.height)*ratio + 0.5)
end
function List:update(dt)
if self.bar_lock then
local dy = math.floor(love.mouse.getY()-self.bar_lock.y+0.5)
self.bar_pos = self.bar_pos + dy
if self.bar_pos < 0 then
self.bar_pos = 0
elseif self.bar_pos > self.bar_max_pos then
self.bar_pos = self.bar_max_pos
end
self.bar_lock.y = love.mouse.getY()
end
end
function List:mousepressed(mx, my, b)
if self:hasBar() then
if b == 1 then
local x, y, w, h = self:getBarRect()
if inside(mx, my, x, y, w, h) then
self.bar_lock = { x = mx, y = my }
end
end
local per_pixel = (self.sum_item_height-self.height)/self.bar_max_pos
local bar_pixel_dt = math.floor(((self.item_height)*3)/per_pixel + 0.5)
if b == "wd" then
self.bar_pos = self.bar_pos + bar_pixel_dt
if self.bar_pos > self.bar_max_pos then
self.bar_pos = self.bar_max_pos
end
elseif b == "wu" then
self.bar_pos = self.bar_pos - bar_pixel_dt
if self.bar_pos < 0 then
self.bar_pos = 0
end
end
end
if b == 1 and inside(mx, my, self.x+2, self.y+1, self.width-3, self.height-3) then
local tx, ty = mx-self.x, my + self:getOffset() - self.y
local index = math.floor((ty/self.sum_item_height)*self.items.n)
local i = self.items[index+1]
local f = self.files[index+1]
if f then
exf.start(i, f)
end
end
end
function List:mousereleased(x, y, b)
if self:hasBar() then
if b == 1 then
self.bar_lock = nil
end
end
end
function List:getBarRect()
return self.x+self.width+2, self.y+1+self.bar_pos,
self.bar_width-3, self.bar_size
end
function List:getItemRect(i)
return self.x+2, self.y+((self.item_height+1)*(i-1)+1)-self:getOffset(),
self.width-3, self.item_height
end
function List:draw()
love.graphics.setLineWidth(2)
love.graphics.setLineStyle("rough")
love.graphics.setFont(self.font)
love.graphics.setColor(48, 156, 225)
local mx, my = love.mouse.getPosition()
-- Get interval to display.
local start_i = math.floor( self:getOffset()/(self.item_height+1) ) + 1
local end_i = start_i+math.floor( self.height/(self.item_height+1) ) + 1
if end_i > self.items.n then
end_i = self.items.n
end
love.graphics.setScissor(self.x, self.y, self.width, self.height)
-- Items.
for i = start_i,end_i do
local x, y, w, h = self:getItemRect(i)
local hover = inside(mx, my, x, y, w, h)
if hover then
love.graphics.setColor(0, 0, 0, 127)
else
love.graphics.setColor(0, 0, 0, 63)
end
love.graphics.rectangle("fill", x+1, y+i+1, w-3, h)
if hover then
love.graphics.setColor(255, 255, 255)
else
love.graphics.setColor(255, 255, 255, 127)
end
local e_id = string.sub(self.items[i], 1, 5)
local e_rest = string.sub(self.items[i], 5)
love.graphics.print(e_id, x+10, y+i+6) --Updated y placement -- Used to change position of Example IDs
love.graphics.print(e_rest, x+50, y+i+6) --Updated y placement -- Used to change position of Example Titles
end
love.graphics.setScissor()
end
function exf:playMusic()
exf.music:play()
end
function exf:stopMusic()
exf.music:stop()
end