Develop #4
@@ -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 |
@@ -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
|
||||
Vendored
+456
@@ -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
|
||||
Vendored
+208
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
HUMP - Helper Utilities for Massive Progression
|
||||
===============================================
|
||||
|
||||
__HUMP__ is a small collection of tools for developing games with LÖVE.
|
||||
|
||||
Contents:
|
||||
------------
|
||||
|
||||
* *gamestate.lua*: Easy gamestate management.
|
||||
* *timer.lua*: Delayed and time-limited function calls and tweening functionality.
|
||||
* *vector.lua*: 2D vector math.
|
||||
* *vector-light.lua*: Lightweight 2D vector math (for optimisation purposes - leads to potentially ugly code).
|
||||
* *class.lua*: Lightweight object orientation (class or prototype based).
|
||||
* *signal.lua*: Simple Signal/Slot (aka. Observer) implementation.
|
||||
* *camera.lua*: Move-, zoom- and rotatable camera.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can find the documentation here: [http://vrld.github.com/hump/](http://vrld.github.com/hump/)
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
> Copyright (c) 2010-2013 Matthias Richter
|
||||
>
|
||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
> of this software and associated documentation files (the "Software"), to deal
|
||||
> in the Software without restriction, including without limitation the rights
|
||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
> copies of the Software, and to permit persons to whom the Software is
|
||||
> furnished to do so, subject to the following conditions:
|
||||
>
|
||||
> The above copyright notice and this permission notice shall be included in
|
||||
> all copies or substantial portions of the Software.
|
||||
>
|
||||
> Except as contained in this notice, the name(s) of the above copyright holders
|
||||
> shall not be used in advertising or otherwise to promote the sale, use or
|
||||
> other dealings in this Software without prior written authorization.
|
||||
>
|
||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
> THE SOFTWARE.
|
||||
Vendored
+117
@@ -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})
|
||||
Vendored
+94
@@ -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})
|
||||
Vendored
+97
@@ -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
|
||||
Vendored
+95
@@ -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()
|
||||
Vendored
+188
@@ -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()
|
||||
Vendored
+161
@@ -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,
|
||||
}
|
||||
Vendored
+191
@@ -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})
|
||||
+388
@@ -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
|
||||
+26
@@ -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
|
||||
+96
@@ -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
|
||||
+53
@@ -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
|
||||
+1388
File diff suppressed because it is too large
Load Diff
+349
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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})
|
||||
@@ -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
|
||||
@@ -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})
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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})
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user