game and hud

This commit is contained in:
Martijn de Boer 2022-10-13 16:06:52 +02:00
parent cf34540a2b
commit 2f455e9c55
No known key found for this signature in database
GPG key ID: 9D2E42402DD372D1
41 changed files with 3443 additions and 0 deletions

22
assets.lua Normal file
View file

@ -0,0 +1,22 @@
multilily = lily.loadMulti({
{lily.newImage, "assets/logo.png"},
{lily.newSoundData, "assets/intro.ogg"},
{lily.newImage, "assets/hud.png"},
{lily.newImage, "assets/captains/steve-layer.png"},
{lily.newImage, "assets/captains/robert-davis.png"},
{lily.newImage, "assets/captains/john-danger.png"}
})
multilily:onComplete(function(_, lilies)
gameLogo = lilies[1][1]
introSound = love.audio.newSource( lilies[2][1] )
gameHud = lilies[3][1]
CaptainSteve.image = lilies[4][1]
CaptainRobert.image = lilies[5][1]
CaptainJohn.image = lilies[6][1]
windowWidth = love.graphics.getWidth()
windowHeight = love.graphics.getHeight()
TitleScene:init()
currentScene = TitleScene
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/hud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
assets/hud.psd Normal file

Binary file not shown.

BIN
assets/intro.ogg Normal file

Binary file not shown.

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
assets/logo.psd Normal file

Binary file not shown.

7
captain/johndanger.lua Normal file
View file

@ -0,0 +1,7 @@
JohnDanger = Object.extend(Player)
function JohnDanger:new()
self.name = "John Danger";
self.shipName = "The Decorator"
self.image = nil
end

7
captain/player.lua Normal file
View file

@ -0,0 +1,7 @@
Player = Object.extend(Object)
function Player:new()
self.name = "";
self.shipName = ""
self.image = nil
end

7
captain/robertdavis.lua Normal file
View file

@ -0,0 +1,7 @@
RobertDavis = Object.extend(Player)
function RobertDavis:new()
self.name = "Robert Davis";
self.shipName = "Excalibur IV"
self.image = nil
end

7
captain/stevelayer.lua Normal file
View file

@ -0,0 +1,7 @@
SteveLayer = Object.extend(Player)
function SteveLayer:new()
self.name = "Steve Layer"
self.shipName = "Blackstar 5"
self.image = nil
end

14
conf.lua Normal file
View file

@ -0,0 +1,14 @@
function love.conf(t)
t.title = "Vision"
t.author = "The Impossible Astronaut"
t.version = "11.4" -- The LÖVE version this game was made for (string)
t.identity = "Vision"
t.gammacorrect = false
t.width = 800
t.height = 600
t.window.borderless = false
t.window.msaa = 4
t.window.highdpi = true
end

81
main.lua Normal file
View file

@ -0,0 +1,81 @@
moonshine = nil
effect = nil
currentScene = nil
currentPlayer = nil
LoadScene = nil
TitleScene = nil
bg = { r= 31/255, g= 44/255, b= 56/255, a= 1 }
text = { r=239/255, g= 247/255, b= 255/255, a= 1 }
accent = { r=255/255, g= 194/255, b= 62/255, a= 1 }
lily = require "vendor/lily"
function love.load()
love.mouse.setVisible(false)
Object = require "vendor/classic"
moonshine = require "shaders"
effect = moonshine(moonshine.effects.vignette).chain(moonshine.effects.filmgrain)
effect.vignette.softness = 0.4
effect.vignette.opacity = 0.2
effect.filmgrain.size = 2
require "scene/load"
require "scene/title"
require "scene/game"
require "captain/player"
require "captain/robertdavis"
require "captain/stevelayer"
require "captain/johndanger"
LoadScene = LoadScene()
TitleScene = TitleScene()
GameScene = GameScene()
CaptainRobert = RobertDavis()
CaptainSteve = SteveLayer()
CaptainJohn = JohnDanger()
currentScene = LoadScene;
currentPlayer = CaptainSteve;
require "assets"
end
function love.update(dt)
if currentScene ~= nil then
currentScene:update(dt)
end
end
function love.draw()
love.graphics.clear(bg.r, bg.g, bg.b, bg.a)
love.graphics.setBackgroundColor(bg.r, bg.g, bg.b, bg.a)
love.graphics.setColor( text.r, text.g, text.b, text.a )
if currentScene ~= nil then
effect(function()
currentScene:draw()
end)
currentScene:drawHud()
end
end
function love.quit()
love.mouse.setVisible(true)
end
function love.keypressed( key, unicode )
if key == "f4" and (love.keyboard.isDown("ralt") or love.keyboard.isDown("lalt"))
or key == "escape" then
love.event.quit();
end
if currentScene ~= nil then
currentScene:keypressed( key, unicode )
end
end

10
planet/planet.lua Normal file
View file

@ -0,0 +1,10 @@
Planet = Object.extend(Object)
function Planet:new()
end
function Planet:update(dt)
end
function Planet:draw()
end

40
scene/game.lua Normal file
View file

@ -0,0 +1,40 @@
GameScene = Object.extend(Object)
function GameScene:new()
end
function GameScene:init()
end
function GameScene:update(dt)
end
function GameScene:draw()
end
function GameScene:drawHud()
-- Draw images
love.graphics.setColor(255,255,255, 1)
love.graphics.draw( gameHud, 0, 600-66 )
love.graphics.setColor(255,255,255, 0.75)
love.graphics.draw( currentPlayer.image, 14, 550,0,0.5,0.5 )
love.graphics.setColor( text.r, text.g, text.b, text.alpha )
love.graphics.print( currentPlayer.name, 48, 550 )
love.graphics.print( currentPlayer.shipName, 48, 566 )
end
function GameScene:keypressed(key,unicode)
if key == "1" then
currentPlayer = CaptainSteve
elseif key == "2" then
currentPlayer = CaptainRobert
elseif key == "3" then
currentPlayer = CaptainJohn
end
end

18
scene/load.lua Normal file
View file

@ -0,0 +1,18 @@
LoadScene = Object.extend(Object)
function LoadScene:new()
end
function LoadScene:update(dt)
end
function LoadScene:draw()
love.graphics.setColor( text.r, text.g, text.b, text.a )
love.graphics.print("Loading assets", 10, 10)
end
function LoadScene:drawHud()
end
function LoadScene:keypressed(key,unicode)
end

69
scene/title.lua Normal file
View file

@ -0,0 +1,69 @@
TitleScene = Object.extend(Object)
function TitleScene:new()
end
function TitleScene:init()
logoWidth = gameLogo:getWidth()
logoHeight = gameLogo:getHeight()
progressDelta = 0
progressTarget = 0.5
percentage = 0
introSound:play()
state = "in"
end
function TitleScene:update(dt)
if progressDelta < progressTarget then
progressDelta = math.min( progressTarget, progressDelta + dt )
end
percentage = (progressDelta/progressTarget)
if state == "in" and progressDelta == progressTarget then
state = "wait"
progressDelta = 0
progressTarget = 3
percentage = 0
end
if state == "wait" and progressDelta == progressTarget then
state = "out"
progressDelta = 0
progressTarget = 0.5
percentage = 0
end
if state == "out" and progressDelta == progressTarget then
state = "complete"
end
if state == "complete" then
currentScene = GameScene
end
end
function TitleScene:draw()
if state == "in" then
logoAlpha = percentage
logoYpos = ( (windowHeight - logoHeight) / 2 ) + ( 10 * (1-percentage) )
elseif state == "wait" then
logoAlpha = 1
logoYpos = ( (windowHeight - logoHeight) / 2 )
elseif state == "out" then
logoAlpha = 1 - percentage
logoYpos = ( (windowHeight - logoHeight) / 2 ) - ( 20 * (percentage) )
end
love.graphics.setColor(255,255,255, logoAlpha)
love.graphics.draw(gameLogo, (windowWidth - logoWidth) / 2, logoYpos )
end
function TitleScene:drawHud()
end
function TitleScene:keypressed(key,unicode)
end

580
shaders/README.md Normal file
View file

@ -0,0 +1,580 @@
# moonshine
Chainable post-processing shaders for LÖVE.
## Overview
* [Getting started](#getting-started)
* [General usage](#general-usage)
* [List of effects](#list-of-effects)
* [Writing effects](#writing-effects)
* [License](#license)
<a name="getting-started"></a>
## Getting started
Clone this repository into your game folder:
git clone https://github.com/vrld/moonshine.git
This will create the folder `moonshine`.
In your `main.lua`, or wherever you load your libraries, add the following:
```lua
local moonshine = require 'moonshine'
```
Create and parametrize the post-processing effect in `love.load()`, for example:
```lua
function love.load()
effect = moonshine(moonshine.effects.filmgrain)
.chain(moonshine.effects.vignette)
effect.filmgrain.size = 2
end
```
Lastly, wrap the things you want to be drawn with the effect inside a function:
```lua
function love.draw()
effect(function()
love.graphics.rectangle("fill", 300,200, 200,200)
end)
end
```
When you package your game for release, you might want consider deleting the
(hidden) `.git` folder in the moonshine directory.
<a name="general-usage"></a>
## General usage
The main concept behind moonshine are chains. A chain consists of one or more
effects. Effects that come later in the chain will be applied to the result of
the effects that come before. In the example above, the vignette is drawn on
top of the filmgrain.
### Chains
Chains are created using the `moonshine.chain` function:
```lua
chain = moonshine.chain(effect)
```
For convenience, `moonshine(effect)` is an alias to `moonshine.chain(effect)`.
You can add new effects to a chain using
```lua
chain = chain.chain(another_effect)
```
or using `chain.next()`, which is an alias to `chain.chain()`.
As the function returns the chain, you can specify your whole chain in one go,
as shown in the example above.
### Effects and effect parameters
The effects that come bundled with moonshine (see [List of effects](#list-of-effects))
are accessed by `chain.effects.<effect-name>`, e.g.,
```lua
moonshine.effects.glow
```
Most effects are parametrized to change how they look. In the example above,
the size of the grains was set to 2 pixels (the default is 1 pixel).
Effect parameters are set by first specifying the name of the effect and then
the name of the parameter:
```lua
chain.<effect>.<parameter> = <value>
```
For example, if `chain` contained the `glow` and `crt` effects, you can set the
glow `strength` parameter and crt `distortionFactor` parameter as such:
```lua
chain.glow.strength = 10
chain.crt.distortionFactor = {1.06, 1.065}
```
Because you likely initialize a bunch of parameters at once, you can set all
parameters with the special key `parameters` (or `params` or `settings`). This
is equivalent to the above:
```lua
chain.parameters = {
glow = {strength = 10},
crt = {distortionFactor = {1.06, 1.065}},
}
```
Note that this will only set the parameters specified in the table. The crt
parameter `feather`, for example, will be left untouched.
### Drawing effects
Creating effects and setting parameters is fine, but not very useful on its
own. You also need to apply it to something. This is done using `chain.draw()`:
```lua
chain.draw(func, ...)
```
This will apply the effect to everything that is drawn inside `func(...)`.
Everything that is drawn outside of `func(...)` will not be affected. For
example,
```lua
love.graphics.draw(img1, 0,0)
chain.draw(function()
love.graphics.draw(img2, 200,0)
end)
love.graphics.draw(img3, 400,0)
```
will apply the effect to `img2`, but not to `img1` and `img3`. Note that some
effects (like filmgrain) draw on the whole screen. So if in this example `chain`
would consist of a gaussianblur and filmgrain effect, `img1` will be covered
with grain, but will not be blurred, `img2` will get both effects, and `img3`
will be left untouched.
Similar to chain creation, `chain(func, ...)` is an alias to the more verbose
`chain.draw(func, ...)`.
### Temporarily disabling effects
You can disable effects in a chain by using `chain.disable(names...)` and
`chain.enable(names...)`.
For example,
```lua
effect = moonshine(moonshine.effects.boxblur)
.chain(moonshine.effects.filmgrain)
.chain(moonshine.effects.vignette)
effect.disable("boxblur", "filmgrain")
effect.enable("filmgrain")
```
would first disable the boxblur and filmgrain effect, and then enable the
filmgrain again.
Note that the effects are still in the chain, they are only not drawn.
### Canvas size
You can change the size of the internal canvas, for example when the window was
resized, by calling `chain.resize(width, height)`.
Do this anytime you want, but best not during `chain.draw()`.
You can also specify the initial canvas size by starting the chain like this:
```lua
effect = moonshine(400,300, moonshine.effects.vignette)
```
That is, you specify the width and height before the first effect in the chain.
### Is this efficient?
Of course, using moonshine is not as efficient as writing your own shader that
does all the effects you want in the least amount of passes, but moonshine
tries to minimize the overhead.
On the other hand, you don't waste time writing the same shader over and over
again when using moonshine: You're trading a small amount of computation time
for a large amount of development time.
<a name="list-of-effects"></a>
## List of effects
Currently, moonshine contains the following effects (in alphabetical order):
* [boxblur](#effect-boxblur): simple blurring
* [chromasep](#effect-chromasep): cheap/fake chromatic aberration
* [colorgradesimple](#effect-colorgradesimple): weighting of color channels
* [crt](#effect-crt): crt/barrel distortion
* [desaturate](#effect-desaturate): desaturation and tinting
* [dmg](#effect-dmg): Gameboy and other four color palettes
* [fastgaussianblur](#effect-fastgaussianblur): faster Gaussian blurring
* [filmgrain](#effect-filmgrain): image noise
* [gaussianblur](#effect-gaussianblur): Gaussian blurring
* [glow](#effect-glow): aka (light bloom
* [godsray](#effect-godsray): aka light scattering
* [pixelate](#effect-pixelate): sub-sampling (for that indie look)
* [posterize](#effect-posterize): restrict number of colors
* [scanlines](#effect-scanlines): horizontal lines
* [sketch](#effect-sketch): simulate pencil drawings
* [vignette](#effect-vignette): shadow in the corners
<a name="effect-boxblur"></a>
### boxblur
```lua
moonshine.effects.boxblur
```
**Parameters:**
Name | Type | Default
-----|------|--------
radius | number or table of numbers | {3,3}
radius_x | number | 3
radius_y | number | 3
<a name="effect-chromasep"></a>
### chromasep
```lua
moonshine.effects.chromasep
```
**Parameters:**
Name | Type | Default
-----|------|--------
angle | number (in radians) | 0
radius | number | 0
<a name="effect-colorgradesimple"></a>
### colorgradesimple
```lua
moonshine.effects.colorgradesimple
```
**Parameters:**
Name | Type | Default
-----|------|--------
factors | table of numbers | {1,1,1}
<a name="effect-crt"></a>
### crt
```lua
moonshine.effects.crt
```
**Parameters:**
Name | Type | Default
-----|------|--------
distortionFactor | table of numbers | {1.06, 1.065}
x | number | 1.06
y | number | 1.065
scaleFactor | number or table of numbers | {1,1}
feather | number | 0.02
<a name="effect-desaturate"></a>
### desaturate
```lua
moonshine.effects.desaturate
```
**Parameters:**
Name | Type | Default
-----|------|--------
tint | color / table of numbers | {255,255,255}
strength | number between 0 and 1 | 0.5
<a name="effect-dmg"></a>
### dmg
```lua
moonshine.effects.dmg
```
Name | Type | Default
-----|------|--------
palette | number or string or table of table of numbers | "default"
DMG ships with 7 palettes:
1. `default`
2. `dark_yellow`
3. `light_yellow`
4. `green`
5. `greyscale`
6. `stark_bw`
7. `pocket`
Custom palettes must be in the format `{{R,G,B}, {R,G,B}, {R,G,B}, {R,G,B}}`,
where `R`, `G`, and `B` are numbers between `0` and `255`.
<a name="effect-fastgaussianblur"></a>
### fastgaussianblur
```lua
moonshine.effects.fastgaussianblur
```
**Parameters:**
Name | Type | Default
-----|------|--------
taps | odd number >= 3 | 7 | (amount of blur)
offset | number | 1
sigma | number | -1
<a name="effect-filmgrain"></a>
### filmgrain
```lua
moonshine.effects.filmgrain
```
**Parameters:**
Name | Type | Default
-----|------|--------
opacity | number | 0.3
size | number | 1
<a name="effect-gaussianblur"></a>
### gaussianblur
```lua
moonshine.effects.gaussianblur
```
**Parameters:**
Name | Type | Default
-----|------|--------
sigma | number | 1 | (amount of blur)
<a name="effect-glow"></a>
### glow
```lua
moonshine.effects.glow
```
**Parameters:**
Name | Type | Default
-----|------|--------
min_luma | number between 0 and 1 | 0.7
strength | number >= 0 | 5
<a name="effect-godsray"></a>
### godsray
```lua
moonshine.effects.godsray
```
**Parameters:**
Name | Type | Default
-----|------|--------
exposire | number between 0 and 1 | 0.5
decay | number between 0 and 1 | 0.95
density | number between 0 and 1 | 0.05
weight | number between 0 and 1 | 0.5
light_position | table of two numbers | {0.5, 0.5}
light_x | number | 0.5
light_y | number | 0.5
samples | number >= 1 | 70
<a name="effect-pixelate"></a>
### pixelate
```lua
moonshine.effects.pixelate
```
**Parameters:**
Name | Type | Default
-----|------|--------
size | number or table of two numbers | {5,5}
feedback | number between 0 and 1 | 0
<a name="effect-posterize"></a>
### posterize
```lua
moonshine.effects.posterize
```
**Parameters:**
Name | Type | Default
-----|------|--------
num_bands | number >= 1 | 3
<a name="effect-scanlines"></a>
### scanlines
```lua
moonshine.effects.scanlines
```
**Parameters:**
Name | Type | Default
-----|------|--------
width | number | 2
frequency | number | screen-height
phase | number | 0
thickness | number | 1
opacity | number | 1
color | color / table of numbers | {0,0,0}
<a name="effect-sketch"></a>
### sketch
```lua
moonshine.effects.sketch
```
**Parameters:**
Name | Type | Default
-----|------|--------
amp | number | 0.0007
center | table of numbers | {0,0}
<a name="effect-vignette"></a>
### vignette
```lua
moonshine.effects.vignette
```
**Parameters:**
Name | Type | Default
-----|------|--------
radius | number > 0 | 0.8
softness | number > 0 | 0.5
opacity | number > 0 | 0.5
color | color / table of numbers | {0,0,0}
<a name="effect-fog"></a>
### fog
```lua
moonshine.effects.fog
```
**Parameters:**
Name | Type | Default
-----|------|--------
fog_color | color/table of numbers | {0.35, 0.48, 0.95}
octaves | number > 0 | 4
speed | vec2/table of numbers | {0.5, 0.5}
<a name="writing-effects"></a>
## Writing effects
An effect is essentially a function that returns a `moonshine.Effect{}`, which
must specify at least a `name` and a `shader` or a `draw` function.
It may also specify a `setters` table that contains functions that set the
effect parameters and a `defaults` table with the corresponding default values.
The default values will be set when the effect is instantiated.
A good starting point to see how to write effects is the `colorgradesimple`
effect, which uses the `shader`, `setters` and `defaults` fields.
Moonshine uses double buffering to draw the effects. A function to swap and
access the buffers is provided to the `draw(buffer)` function of your effect:
```lua
front, back = buffer() -- swaps front and back buffer and returns both
```
You don't have to care about canvases or restoring defaults, moonshine handles
all that for you.
If you only need a custom draw function because your effect needs multiple
shader passes, moonshine provides the `draw_shader(buffer, shader)` function.
As you might have guessed, this function uses `shader` to draw the front buffer
to the back buffer. The `boxblur` effect gives a simple example how to use this
function.
If for some reason you need more than two buffer, you are more or less on your
own. You can do everything, but make sure that the blend mode and the order of
back and front buffer is the same before and after your custom `draw` function.
The `glow` effect gives an example of a more complicated `draw` function.
<a name="license"></a>
## License
See [here](https://github.com/vrld/moonshine/graphs/contributors) for a list of
contributors.
The main library can freely be used under the following conditions:
The MIT License (MIT)
Copyright (c) 2017 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.
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.
Most of the effects are public domain (see comments inside the files):
* boxblur.lua
* chromasep.lua
* colorgradesimple.lua
* crt.lua
* desaturate.lua
* filmgrain.lua
* gaussianblur.lua
* glow.lua
* pixelate.lua
* posterize.lua
* scanlines.lua
* vignette.lua
These effects are MIT-licensed with multiple authors:
* dmg.lua: Joseph Patoprsty, Matthias Richter
* fastgaussianblur.lua: Tim Moore, Matthias Richter
* godsray.lua: Joseph Patoprsty, Matthias Richter. Based on work by ioxu, Fabien Sanglard, Kenny Mitchell and Jason Mitchell.
* sketch.lua: Martin Felis, Matthias Richter
* fog.lua: Brandon Blanker Lim-it. Based on work by Gonkee.

62
shaders/boxblur.lua Normal file
View file

@ -0,0 +1,62 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local radius_x, radius_y = 3, 3
local shader = love.graphics.newShader[[
extern vec2 direction;
extern number radius;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec4 c = vec4(0.0);
for (float i = -radius; i <= radius; i += 1.0)
{
c += Texel(texture, tc + i * direction);
}
return c / (2.0 * radius + 1.0) * color;
}]]
local setters = {}
setters.radius = function(v)
if type(v) == "number" then
radius_x, radius_y = v, v
elseif type(v) == "table" and #v >= 2 then
radius_x, radius_y = tonumber(v[1] or v.h or v.x), tonumber(v[2] or v.v or v.y)
else
error("Invalid argument `radius'")
end
end
setters.radius_x = function(v) radius_x = tonumber(v) end
setters.radius_y = function(v) radius_y = tonumber(v) end
local draw = function(buffer)
shader:send('direction', {1 / love.graphics.getWidth(), 0})
shader:send('radius', math.floor(radius_x + .5))
moonshine.draw_shader(buffer, shader)
shader:send('direction', {0, 1 / love.graphics.getHeight()})
shader:send('radius', math.floor(radius_y + .5))
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "boxblur",
draw = draw,
setters = setters,
defaults = {radius = 3}
}
end

49
shaders/chromasep.lua Normal file
View file

@ -0,0 +1,49 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec2 direction;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
{
return color * vec4(
Texel(texture, tc - direction).r,
Texel(texture, tc).g,
Texel(texture, tc + direction).b,
1.0);
}]]
local angle, radius = 0, 0
local setters = {
angle = function(v) angle = tonumber(v) or 0 end,
radius = function(v) radius = tonumber(v) or 0 end
}
local draw = function(buffer, effect)
local dx = math.cos(angle) * radius / love.graphics.getWidth()
local dy = math.sin(angle) * radius / love.graphics.getHeight()
shader:send("direction", {dx,dy})
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "chromasep",
draw = draw,
setters = setters,
defaults = {angle = 0, radius = 0}
}
end

View file

@ -0,0 +1,33 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec3 factors;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
return vec4(factors, 1.0) * Texel(texture, tc) * color;
}]]
local setters = {}
return moonshine.Effect{
name = "colorgradesimple",
shader = shader,
setters = {factors = function(v) shader:send("factors", v) end},
defaults = {factors = {1,1,1}}
}
end

79
shaders/crt.lua Normal file
View file

@ -0,0 +1,79 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
-- Barrel distortion adapted from Daniel Oaks (see commit cef01b67fd)
-- Added feather to mask out outside of distorted texture
local distortionFactor
local shader = love.graphics.newShader[[
extern vec2 distortionFactor;
extern vec2 scaleFactor;
extern number feather;
vec4 effect(vec4 color, Image tex, vec2 uv, vec2 px) {
// to barrel coordinates
uv = uv * 2.0 - vec2(1.0);
// distort
uv *= scaleFactor;
uv += (uv.yx*uv.yx) * uv * (distortionFactor - 1.0);
number mask = (1.0 - smoothstep(1.0-feather,1.0,abs(uv.x)))
* (1.0 - smoothstep(1.0-feather,1.0,abs(uv.y)));
// to cartesian coordinates
uv = (uv + vec2(1.0)) / 2.0;
return color * Texel(tex, uv) * mask;
}
]]
local setters = {}
setters.distortionFactor = function(v)
assert(type(v) == "table" and #v == 2, "Invalid value for `distortionFactor'")
distortionFactor = {unpack(v)}
shader:send("distortionFactor", v)
end
setters.x = function(v) setters.distortionFactor{v, distortionFactor[2]} end
setters.y = function(v) setters.distortionFactor{distortionFactor[1], v} end
setters.scaleFactor = function(v)
if type(v) == "table" and #v == 2 then
shader:send("scaleFactor", v)
elseif type(v) == "number" then
shader:send("scaleFactor", {v,v})
else
error("Invalid value for `scaleFactor'")
end
end
setters.feather = function(v) shader:send("feather", v) end
local defaults = {
distortionFactor = {1.06, 1.065},
feather = 0.02,
scaleFactor = 1,
}
return moonshine.Effect{
name = "crt",
shader = shader,
setters = setters,
defaults = defaults
}
end

52
shaders/desaturate.lua Normal file
View file

@ -0,0 +1,52 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec4 tint;
extern number strength;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
color = Texel(texture, tc);
number luma = dot(vec3(0.299, 0.587, 0.114), color.rgb);
return mix(color, tint * luma, strength);
}]]
local setters = {}
setters.tint = function(c)
assert(type(c) == "table" and #c == 3, "Invalid value for `tint'")
shader:send("tint", {
(tonumber(c[1]) or 0) / 255,
(tonumber(c[2]) or 0) / 255,
(tonumber(c[3]) or 0) / 255,
1
})
end
setters.strength = function(v)
shader:send("strength", math.max(0, math.min(1, tonumber(v) or 0)))
end
local defaults = {tint = {255,255,255}, strength = 0.5}
return moonshine.Effect{
name = "desaturate",
shader = shader,
setters = setters,
defaults = defaults
}
end

153
shaders/dmg.lua Normal file
View file

@ -0,0 +1,153 @@
--[[
The MIT License (MIT)
Original code: Copyright (c) 2015 Josef Patoprsty
Port to moonshine: Copyright (c) 2017 Matthias Richter <vrld@vrld.org>
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 palettes = {
-- Default color palette. Source:
-- http://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Original_Game_Boy
{
name = "default",
colors = {
{ 15/255, 56/255, 15/255},
{ 48/255, 98/255, 48/255},
{139/255,172/255, 15/255},
{155/255,188/255, 15/255}
}
},
-- Hardcore color profiles. Source:
-- http://www.hardcoregaming101.net/gbdebate/gbcolours.htm
{
name = "dark_yellow",
colors = {
{33/255,32/255,16/255},
{107/255,105/255,49/255},
{181/255,174/255,74/255},
{255/255,247/255,123/255}
}
},
{
name = "light_yellow",
colors = {
{102/255,102/255,37/255},
{148/255,148/255,64/255},
{208/255,208/255,102/255},
{255/255,255/255,148/255}
}
},
{
name = "green",
colors = {
{8/255,56/255,8/255},
{48/255,96/255,48/255},
{136/255,168/255,8/255},
{183/255,220/255,17/255}
}
},
{
name = "greyscale",
colors = {
{56/255,56/255,56/255},
{117/255,117/255,117/255},
{178/255,178/255,178/255},
{239/255,239/255,239/255}
}
},
{
name = "stark_bw",
colors = {
{0/255,0/255,0/255},
{117/255,117/255,117/255},
{178/255,178/255,178/255},
{255/255,255/255,255/255}
}
},
{
name = "pocket",
colors = {
{108/255,108/255,78/255},
{142/255,139/255,87/255},
{195/255,196/255,165/255},
{227/255,230/255,201/255}
}
}
}
local lookup_palette = function(name)
for _,palette in pairs(palettes) do
if palette.name == name then
return palette
end
end
end
local is_valid_palette = function(v)
-- Needs to match: {{R,G,B},{R,G,B},{R,G,B},{R,G,B}}
if #v ~= 4 then return false end
for i = 1,4 do
if type(v[i]) ~= "table" or #v[i] ~= 3 then return false end
for c = 1,3 do
if type(v[i][c]) ~= "number" then return false end
local x = v[i][c]
if x > 1 then x = x / 255 end
if x < 0 or x > 1 then return false end
v[i][c] = x
end
end
return true
end
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec3 palette[ 4 ];
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec4 pixel = Texel(texture, texture_coords);
float avg = min(0.9999,max(0.0001,(pixel.r + pixel.g + pixel.b)/3));
int index = int(avg*4);
return vec4(palette[index], pixel.a);
}]]
local setters = {}
setters.palette = function(v)
if type(v) == "number" and palettes[math.floor(v)] then -- indexed palette
palette = palettes[math.floor(v)]
elseif type(v) == "string" then -- named palette
palette = lookup_palette(v)
elseif type(v) == "table" and is_valid_palette(v) then -- custom palette
palette = {colors=v}
else -- Fall back to default
palette = palettes[1]
end
shader:send("palette", palette.colors[1], palette.colors[2],
palette.colors[3], palette.colors[4], {})
end
return moonshine.Effect{
name = "dmg",
shader = shader,
setters = setters,
defaults = {palette = "default"}
}
end

View file

@ -0,0 +1,139 @@
--[[
The MIT License (MIT)
Copyright (c) 2017 Tim Moore
Adapted for new moonshine API by 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.
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.
]]--
-- Bilinear Gaussian blur filter as detailed here: http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
-- Produces near identical results to a standard Gaussian blur by using sub-pixel sampling,
-- this allows us to do ~1/2 the number of pixel lookups.
-- unroll convolution loop
local function build_shader(taps, offset, offset_type, sigma)
taps = math.floor(taps)
sigma = sigma >= 1 and sigma or (taps - 1) * offset / 6
sigma = math.max(sigma, 1)
local steps = (taps + 1) / 2
-- Calculate gaussian function.
local g_offsets = {}
local g_weights = {}
for i = 1, steps, 1 do
g_offsets[i] = offset * (i - 1)
-- We don't need to include the constant part of the gaussian function as we normalize later.
-- 1 / math.sqrt(2 * sigma ^ math.pi) * math.exp(-0.5 * ((offset - 0) / sigma) ^ 2 )
g_weights[i] = math.exp(-0.5 * (g_offsets[i] - 0) ^ 2 * 1 / sigma ^ 2 )
end
-- Calculate offsets and weights for sub-pixel samples.
local offsets = {}
local weights = {}
for i = #g_weights, 2, -2 do
local oA, oB = g_offsets[i], g_offsets[i - 1]
local wA, wB = g_weights[i], g_weights[i - 1]
wB = oB == 0 and wB / 2 or wB -- On center tap the middle is getting sampled twice so half weight.
local weight = wA + wB
offsets[#offsets + 1] = offset_type == 'center' and (oA + oB) / 2 or (oA * wA + oB * wB) / weight
weights[#weights + 1] = weight
end
local code = {[[
extern vec2 direction;
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) {]]}
local norm = 0
if #g_weights % 2 == 0 then
code[#code+1] = 'vec4 c = vec4( 0.0 );'
else
local weight = g_weights[1]
norm = norm + weight
code[#code+1] = ('vec4 c = %f * texture2D(tex, tc);'):format(weight)
end
local tmpl = 'c += %f * ( texture2D(tex, tc + %f * direction)+ texture2D(tex, tc - %f * direction));\n'
for i = 1, #offsets, 1 do
local offset = offsets[i]
local weight = weights[i]
norm = norm + weight * 2
code[#code+1] = tmpl:format(weight, offset, offset)
end
code[#code+1] = ('return c * vec4(%f) * color; }'):format(1 / norm)
local shader = table.concat(code)
return love.graphics.newShader(shader)
end
return function(moonshine)
local taps, offset, offset_type, sigma = 7, 1, 'weighted', -1
local shader = build_shader(taps, offset, offset_type, sigma)
local function draw(buffer)
shader:send('direction', {1 / love.graphics.getWidth(), 0})
moonshine.draw_shader(buffer, shader)
shader:send('direction', {0, 1 / love.graphics.getHeight()})
moonshine.draw_shader(buffer, shader)
end
local setters = {}
-- Number of effective samples to take per pass. e.g. 3-tap is the current pixel and the neighbors each side.
-- More taps = larger blur, but slower.
setters.taps = function(v)
assert(tonumber(v) >= 3, "Invalid value for `taps': Must be >= 3")
assert(tonumber(v)%2 == 1, "Invalid value for `taps': Must be odd")
taps = tonumber(v)
shader = build_shader(taps, offset, offset_type, sigma)
end
-- Offset of each tap.
-- For highest quality this should be <=1 but if the image has low entropy we
-- can approximate the blur with a number > 1 and less taps, for better performance.
setters.offset = function(v)
offset = tonumber(v) or 0
shader = build_shader(taps, offset, offset_type, sigma)
end
-- Offset type, either 'weighted' or 'center'.
-- 'weighted' gives a more accurate gaussian decay but can introduce modulation
-- for high frequency details.
setters.offset_type = function(v)
assert(v == 'weighted' or v == 'center', "Invalid value for 'offset_type': Must be 'weighted' or 'center'.")
offset_type = v
shader = build_shader(taps, offset, offset_type, sigma)
end
-- Sigma value for gaussian distribution. You don't normally need to set this.
setters.sigma = function(v)
sigma = tonumber(v) or -1
shader = build_shader(taps, offset, offset_type, sigma)
end
return moonshine.Effect{
name = "fastgaussianblur",
draw = draw,
setters = setters,
-- no defaults here, as we dont want the shader to be built 3 times on startup
}
end

63
shaders/filmgrain.lua Normal file
View file

@ -0,0 +1,63 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local noisetex = love.image.newImageData(256,256)
noisetex:mapPixel(function()
local l = love.math.random() * 255
return l,l,l,l
end)
noisetex = love.graphics.newImage(noisetex)
local shader = love.graphics.newShader[[
extern number opacity;
extern number size;
extern vec2 noise;
extern Image noisetex;
extern vec2 tex_ratio;
float rand(vec2 co) {
return Texel(noisetex, mod(co * tex_ratio / vec2(size), vec2(1.0))).r;
}
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
return color * Texel(texture, tc) * mix(1.0, rand(tc+vec2(noise)), opacity);
}]]
shader:send("noisetex", noisetex)
shader:send("tex_ratio", {love.graphics.getWidth() / noisetex:getWidth(),
love.graphics.getHeight() / noisetex:getHeight()})
local setters = {}
for _,k in ipairs{"opacity", "size"} do
setters[k] = function(v) shader:send(k, math.max(0, tonumber(v) or 0)) end
end
local defaults = {opacity = .3, size = 1}
local draw = function(buffer)
shader:send("noise", {love.math.random(), love.math.random()})
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "filmgrain",
draw = draw,
setters = setters,
defaults = defaults
}
end

129
shaders/fog.lua Normal file
View file

@ -0,0 +1,129 @@
--[[
Animated 2D Fog (procedural)
Originally for Godot Engine by Gonkee https://www.youtube.com/watch?v=QEaTsz_0o44&t=6s
Translated for löve by Brandon Blanker Lim-it @flamendless
]]--
--[[
SAMPLE USAGE:
local moonshine = require("moonshine")
local effect
local image, bg
local image_data
local shader_fog
local time = 0
function love.load()
image_data = love.image.newImageData(love.graphics.getWidth(), love.graphics.getHeight())
image = love.graphics.newImage(image_data)
bg = love.graphics.newImage("bg.png")
effect = moonshine(moonshine.effects.fog)
effect.fog.fog_color = {0.1, 0.0, 0.0}
effect.fog.speed = {0.2, 0.9}
end
function love.update(dt)
time = time + dt
effect.fog.time = time
end
function love.draw()
love.graphics.draw(bg)
effect(function()
love.graphics.draw(image)
end)
end
]]
return function(moonshine)
local fog_color
local octaves
local speed
local time
local shader = love.graphics.newShader([[
extern vec3 fog_color = vec3(0.35, 0.48, 0.95);
extern int octaves = 4;
extern vec2 speed = vec2(0.0, 1.0);
extern float time;
float rand(vec2 coord)
{
return fract(sin(dot(coord, vec2(56, 78)) * 1000.0) * 1000.0);
}
float noise(vec2 coord)
{
vec2 i = floor(coord); //get the whole number
vec2 f = fract(coord); //get the fraction number
float a = rand(i); //top-left
float b = rand(i + vec2(1.0, 0.0)); //top-right
float c = rand(i + vec2(0.0, 1.0)); //bottom-left
float d = rand(i + vec2(1.0, 1.0)); //bottom-right
vec2 cubic = f * f * (3.0 - 2.0 * f);
return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y; //interpolate
}
float fbm(vec2 coord) //fractal brownian motion
{
float value = 0.0;
float scale = 0.5;
for (int i = 0; i < octaves; i++)
{
value += noise(coord) * scale;
coord *= 2.0;
scale *= 0.5;
}
return value;
}
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 sc)
{
vec2 coord = tc * 20.0;
vec2 motion = vec2(fbm(coord + vec2(time * speed.x, time * speed.y)));
float final = fbm(coord + motion);
return vec4(fog_color, final * 0.5);
}
]])
local setters = {}
setters.fog_color = function(t)
assert(type(t) == "table", "Passed argument to fog_color must be a table containing 3 color values")
fog_color = t
shader:send("fog_color", fog_color)
end
setters.octaves = function(i)
assert(type(i) == "number", "Passed argument to octaves must be an integer")
octaves = i
shader:send("octaves", octaves)
end
setters.speed = function(t)
assert(type(t) == "table", "Passed argument to speed must be a table containing 2 values")
speed = t
shader:send("speed", speed)
end
setters.time = function(n)
assert(type(n) == "number", "Passed argument to time must be a number")
time = n
shader:send("time", time)
end
local defaults = {
fog_color = {0.35, 0.48, 0.95},
octaves = 4,
speed = {0.5, 0.5},
}
return moonshine.Effect({
name = "fog",
shader = shader,
setters = setters,
defaults = defaults,
})
end

55
shaders/gaussianblur.lua Normal file
View file

@ -0,0 +1,55 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
]]--
local function resetShader(sigma)
local support = math.max(1, math.floor(3*sigma + .5))
local one_by_sigma_sq = sigma > 0 and 1 / (sigma * sigma) or 1
local norm = 0
local code = {[[
extern vec2 direction;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
{ vec4 c = vec4(0.0);
]]}
local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);"
for i = -support,support do
local coeff = math.exp(-.5 * i*i * one_by_sigma_sq)
norm = norm + coeff
code[#code+1] = blur_line:format(coeff, i)
end
code[#code+1] = ("return c * vec4(%f) * color;}"):format(norm > 0 and 1/norm or 1)
return love.graphics.newShader(table.concat(code))
end
return function(moonshine)
local shader
local setters = {}
setters.sigma = function(v)
shader = resetShader(math.max(0,tonumber(v) or 1))
end
local draw = function(buffer)
shader:send('direction', {1 / love.graphics.getWidth(), 0})
moonshine.draw_shader(buffer, shader)
shader:send('direction', {0, 1 / love.graphics.getHeight()})
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "gaussianblur",
draw = draw,
setters = setters,
defaults = {sigma = 1},
}
end

104
shaders/glow.lua Normal file
View file

@ -0,0 +1,104 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
-- unroll convolution loop for gaussian blur shader
local function make_blur_shader(sigma)
local support = math.max(1, math.floor(3*sigma + .5))
local one_by_sigma_sq = sigma > 0 and 1 / (sigma * sigma) or 1
local norm = 0
local code = {[[
extern vec2 direction;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec4 c = vec4(0.0);
]]}
local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);"
for i = -support,support do
local coeff = math.exp(-.5 * i*i * one_by_sigma_sq)
norm = norm + coeff
code[#code+1] = blur_line:format(coeff, i)
end
code[#code+1] = ("return c * vec4(%f) * color;}"):format(1 / norm)
return love.graphics.newShader(table.concat(code))
end
return function(moonshine)
local blurshader -- set in setters.glow_strength
local threshold = love.graphics.newShader[[
extern number min_luma;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec4 c = Texel(texture, tc);
number luma = dot(vec3(0.299, 0.587, 0.114), c.rgb);
return c * step(min_luma, luma) * color;
}]]
local setters = {}
setters.strength = function(v)
blurshader = make_blur_shader(math.max(0,tonumber(v) or 1))
end
setters.min_luma = function(v)
threshold:send("min_luma", math.max(0, math.min(1, tonumber(v) or 0.5)))
end
local scene = love.graphics.newCanvas()
local draw = function(buffer)
local front, back = buffer() -- scene so far is in `back'
scene, back = back, scene -- save it for second draw below
-- 1st pass: draw scene with brightness threshold
love.graphics.setCanvas(front)
love.graphics.clear()
love.graphics.setShader(threshold)
love.graphics.draw(scene)
-- 2nd pass: apply blur shader in x
blurshader:send('direction', {1 / love.graphics.getWidth(), 0})
love.graphics.setCanvas(back)
love.graphics.clear()
love.graphics.setShader(blurshader)
love.graphics.draw(front)
-- 3nd pass: apply blur shader in y and draw original and blurred scene
love.graphics.setCanvas(front)
love.graphics.clear()
-- original scene without blur shader
love.graphics.setShader()
love.graphics.setBlendMode("add", "premultiplied")
love.graphics.draw(scene) -- original scene
-- second pass of light blurring
blurshader:send('direction', {0, 1 / love.graphics.getHeight()})
love.graphics.setShader(blurshader)
love.graphics.draw(back)
-- restore things as they were before entering draw()
love.graphics.setBlendMode("alpha", "premultiplied")
scene = back
end
return moonshine.Effect{
name = "glow",
draw = draw,
setters = setters,
defaults = {min_luma=.7, strength = 5}
}
end

107
shaders/godsray.lua Normal file
View file

@ -0,0 +1,107 @@
--[[
The MIT License (MIT)
Original code: Copyright (c) 2015 Josef Patoprsty
Port to moonshine: Copyright (c) 2017 Matthias Richter <vrld@vrld.org>
Based on work by: ioxu
https://www.love2d.org/forums/viewtopic.php?f=4&t=3733&start=120#p71099
Based on work by: Fabien Sanglard
http://fabiensanglard.net/lightScattering/index.php
Based on work from:
[Mitchell]: Kenny Mitchell "Volumetric Light Scattering as a Post-Process" GPU Gems 3 (2005).
[Mitchell2]: Jason Mitchell "Light Shaft Rendering" ShadersX3 (2004).
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.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number exposure;
extern number decay;
extern number density;
extern number weight;
extern vec2 light_position;
extern number samples;
vec4 effect(vec4 color, Image tex, vec2 uv, vec2 px) {
color = Texel(tex, uv);
vec2 offset = (uv - light_position) * density / samples;
number illumination = decay;
vec4 c = vec4(.0, .0, .0, 1.0);
for (int i = 0; i < int(samples); ++i) {
uv -= offset;
c += Texel(tex, uv) * illumination * weight;
illumination *= decay;
}
return vec4(c.rgb * exposure + color.rgb, color.a);
}]]
local setters, light_position = {}
for _,k in ipairs{"exposure", "decay", "density", "weight"} do
setters[k] = function(v)
shader:send(k, math.min(1, math.max(0, tonumber(v) or 0)))
end
end
setters.light_position = function(v)
light_position = {unpack(v)}
shader:send("light_position", v)
end
setters.light_x = function(v)
assert(type(v) == "number", "Invalid value for `light_x'")
setters.light_position{v, light_position[2]}
end
setters.light_y = function(v)
assert(type(v) == "number", "Invalid value for `light_y'")
setters.light_position{light_position[1], v}
end
setters.samples = function(v)
shader:send("samples", math.max(1,tonumber(v) or 1))
end
local defaults = {
exposure = 0.25,
decay = 0.95,
density = 0.15,
weight = 0.5,
light_position = {0.5,0.5},
samples = 70
}
return moonshine.Effect{
name = "godsray",
shader = shader,
setters = setters,
defaults = defaults
}
end

171
shaders/init.lua Normal file
View file

@ -0,0 +1,171 @@
--[[
The MIT License (MIT)
Copyright (c) 2017 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.
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 BASE = ...
local moonshine = {}
moonshine.draw_shader = function(buffer, shader)
local front, back = buffer()
love.graphics.setCanvas(front)
love.graphics.clear()
if shader ~= love.graphics.getShader() then
love.graphics.setShader(shader)
end
love.graphics.draw(back)
end
moonshine.chain = function(w,h,effect)
-- called as moonshine.chain(effect)'
if h == nil then
effect, w,h = w, love.window.getMode()
end
assert(effect ~= nil, "No effect")
local front, back = love.graphics.newCanvas(w,h), love.graphics.newCanvas(w,h)
local buffer = function()
back, front = front, back
return front, back
end
local disabled = {} -- set of disabled effects
local chain = {}
chain.resize = function(w, h)
front, back = love.graphics.newCanvas(w,h), love.graphics.newCanvas(w,h)
return chain
end
chain.draw = function(func, ...)
-- save state
local canvas = love.graphics.getCanvas()
local shader = love.graphics.getShader()
local fg_r, fg_g, fg_b, fg_a = love.graphics.getColor()
-- draw scene to front buffer
love.graphics.setCanvas((buffer())) -- parens are needed: take only front buffer
love.graphics.clear(love.graphics.getBackgroundColor())
func(...)
-- save more state
local blendmode = love.graphics.getBlendMode()
-- process all shaders
love.graphics.setColor(fg_r, fg_g, fg_b, fg_a)
love.graphics.setBlendMode("alpha", "premultiplied")
for _,e in ipairs(chain) do
if not disabled[e.name] then
(e.draw or moonshine.draw_shader)(buffer, e.shader)
end
end
-- present result
love.graphics.setShader()
love.graphics.setCanvas(canvas)
love.graphics.draw(front,0,0)
-- restore state
love.graphics.setBlendMode(blendmode)
love.graphics.setShader(shader)
end
chain.next = function(e)
if type(e) == "function" then
e = e()
end
assert(e.name, "Invalid effect: must provide `name'.")
assert(e.shader or e.draw, "Invalid effect: must provide `shader' or `draw'.")
table.insert(chain, e)
return chain
end
chain.chain = chain.next
chain.disable = function(name, ...)
if name then
disabled[name] = name
return chain.disable(...)
end
end
chain.enable = function(name, ...)
if name then
disabled[name] = nil
return chain.enable(...)
end
end
setmetatable(chain, {
__call = function(_, ...) return chain.draw(...) end,
__index = function(_,k)
for _, e in ipairs(chain) do
if e.name == k then return e end
end
error(("Effect `%s' not in chain"):format(k), 2)
end,
__newindex = function(_, k, v)
if k == "parameters" or k == "params" or k == "settings" then
for e,par in pairs(v) do
for k,v in pairs(par) do
chain[e][k] = v
end
end
else
rawset(chain, k, v)
end
end
})
return chain.next(effect)
end
moonshine.Effect = function(e)
-- set defaults
for k,v in pairs(e.defaults or {}) do
assert(e.setters[k], ("No setter for parameter `%s'"):format(k))(v, k)
e.setters[k](v,k)
end
-- expose setters
return setmetatable(e, {
__newindex = function(self,k,v)
assert(self.setters[k], ("Unknown property: `%s.%s'"):format(e.name, k))
self.setters[k](v, k)
end})
end
-- autoloading effects
moonshine.effects = setmetatable({}, {__index = function(self, key)
local ok, effect = pcall(require, BASE .. "." .. key)
if not ok then
error("No such effect: "..key, 2)
end
-- expose moonshine to effect
local con = function(...) return effect(moonshine, ...) end
-- cache effect constructor
self[key] = con
return con
end})
return setmetatable(moonshine, {__call = function(_, ...) return moonshine.chain(...) end})

55
shaders/pixelate.lua Normal file
View file

@ -0,0 +1,55 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec2 size;
extern number feedback;
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 _)
{
vec4 c = Texel(tex, tc);
// average pixel color over 5 samples
vec2 scale = love_ScreenSize.xy / size;
tc = floor(tc * scale + vec2(.5));
vec4 meanc = Texel(tex, tc/scale);
meanc += Texel(tex, (tc+vec2( 1.0, .0))/scale);
meanc += Texel(tex, (tc+vec2(-1.0, .0))/scale);
meanc += Texel(tex, (tc+vec2( .0, 1.0))/scale);
meanc += Texel(tex, (tc+vec2( .0,-1.0))/scale);
return color * mix(.2*meanc, c, feedback);
}
]]
local setters = {}
setters.size = function(v)
if type(v) == "number" then v = {v,v} end
assert(type(v) == "table" and #v == 2, "Invalid value for `size'")
shader:send("size", v)
end
setters.feedback = function(v)
shader:send("feedback", math.min(1, math.max(0, tonumber(v) or 0)))
end
return moonshine.Effect{
name = "pixelate",
shader = shader,
setters = setters,
defaults = {size = {5,5}, feedback = 0}
}
end

59
shaders/posterize.lua Normal file
View file

@ -0,0 +1,59 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
shader based on code by sam hocevar, see
https://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number num_bands;
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
{
color = Texel(texture, tc);
vec3 hsv = floor((rgb2hsv(color.rgb) * num_bands) + vec3(0.5)) / num_bands;
return vec4(hsv2rgb(hsv), color.a);
}]]
return moonshine.Effect{
name = "posterize",
shader = shader,
setters = {
num_bands = function(v)
shader:send("num_bands", math.max(1, tonumber(v) or 1))
end
},
defaults = {num_bands = 3}
}
end

73
shaders/scanlines.lua Normal file
View file

@ -0,0 +1,73 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number width;
extern number phase;
extern number thickness;
extern number opacity;
extern vec3 color;
vec4 effect(vec4 c, Image tex, vec2 tc, vec2 _) {
number v = .5*(sin(tc.y * 3.14159 / width * love_ScreenSize.y + phase) + 1.);
c = Texel(tex,tc);
//c.rgb = mix(color, c.rgb, mix(1, pow(v, thickness), opacity));
c.rgb -= (color - c.rgb) * (pow(v,thickness) - 1.0) * opacity;
return c;
}]]
local defaults = {
width = 2,
phase = 0,
thickness = 1,
opacity = 1,
color = {0,0,0},
}
local setters = {}
setters.width = function(v)
shader:send("width", tonumber(v) or defaults.width)
end
setters.frequency = function(v)
shader:send("width", love.graphics.getHeight()/(tonumber(v) or love.graphics.getHeight()))
end
setters.phase = function(v)
shader:send("phase", tonumber(v) or defaults.phase)
end
setters.thickness = function(v)
shader:send("thickness", math.max(0, tonumber(v) or defaults.thickness))
end
setters.opacity = function(v)
shader:send("opacity", math.min(1, math.max(0, tonumber(v) or defaults.opacity)))
end
setters.color = function(c)
assert(type(c) == "table" and #c == 3, "Invalid value for `color'")
shader:send("color", {
(tonumber(c[1]) or defaults.color[0]) / 255,
(tonumber(c[2]) or defaults.color[1]) / 255,
(tonumber(c[3]) or defaults.color[2]) / 255
})
end
return moonshine.Effect{
name = "scanlines",
shader = shader,
setters = setters,
defaults = defaults,
}
end

64
shaders/sketch.lua Normal file
View file

@ -0,0 +1,64 @@
--[[
The MIT License (MIT)
Copyright (c) 2015 Martin Felis
Copyright (c) 2017 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.
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.
]]--
return function(moonshine)
local noisetex = love.image.newImageData(256,256)
noisetex:mapPixel(function()
return love.math.random() * 255,love.math.random() * 255, 0, 0
end)
noisetex = love.graphics.newImage(noisetex)
noisetex:setWrap ("repeat", "repeat")
noisetex:setFilter("nearest", "nearest")
local shader = love.graphics.newShader[[
extern Image noisetex;
extern number amp;
extern vec2 center;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec2 displacement = Texel(noisetex, tc + center).rg;
tc += normalize(displacement * 2.0 - vec2(1.0)) * amp;
return Texel(texture, tc);
}]]
shader:send("noisetex", noisetex)
local setters = {}
setters.amp = function(v)
shader:send("amp", math.max(0, tonumber(v) or 0))
end
setters.center = function(v)
assert(type(v) == "table" and #v == 2, "Invalid value for `center'")
shader:send("center", v)
end
return moonshine.Effect{
name = "sketch",
shader = shader,
setters = setters,
defaults = {amp = .0007, center = {0,0}}
}
end

59
shaders/vignette.lua Normal file
View file

@ -0,0 +1,59 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number radius;
extern number softness;
extern number opacity;
extern vec4 color;
vec4 effect(vec4 c, Image tex, vec2 tc, vec2 _)
{
number aspect = love_ScreenSize.x / love_ScreenSize.y;
aspect = max(aspect, 1.0 / aspect); // use different aspect when in portrait mode
number v = 1.0 - smoothstep(radius, radius-softness,
length((tc - vec2(0.5)) * aspect));
return mix(Texel(tex, tc), color, v*opacity);
}]]
local setters = {}
for _,k in ipairs{"radius", "softness", "opacity"} do
setters[k] = function(v) shader:send(k, math.max(0, tonumber(v) or 0)) end
end
setters.color = function(c)
assert(type(c) == "table" and #c == 3, "Invalid value for `color'")
shader:send("color", {
(tonumber(c[1]) or 0) / 255,
(tonumber(c[2]) or 0) / 255,
(tonumber(c[3]) or 0) / 255,
1
})
end
return moonshine.Effect{
name = "vignette",
shader = shader,
setters = setters,
defaults = {
radius = .8,
softness = .5,
opacity = .5,
color = {0,0,0}
}
}
end

68
vendor/classic.lua vendored Normal file
View file

@ -0,0 +1,68 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
local Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({...}) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

765
vendor/lily.lua vendored Normal file
View file

@ -0,0 +1,765 @@
-- LOVE Asset Async Loader
-- Copyright (c) 2021 Miku AuahDark
--
-- This software is provided 'as-is', without any express or implied
-- warranty. In no event will the authors be held liable for any damages
-- arising from the use of this software.
--
-- Permission is granted to anyone to use this software for any purpose,
-- including commercial applications, and to alter it and redistribute it
-- freely, subject to the following restrictions:
--
-- 1. The origin of this software must not be misrepresented; you must not
-- claim that you wrote the original software. If you use this software
-- in a product, an acknowledgment in the product documentation would be
-- appreciated but is not required.
-- 2. Altered source versions must be plainly marked as such, and must not be
-- misrepresented as being the original software.
-- 3. This notice may not be removed or altered from any source distribution.
-- NOTICE: For custom `love.run` users.
-- 1. You have to explicitly pass event with name "lily_resp"
-- to `love.handlers.lily_resp` along with all of it's arguments.
-- 2. When you're handling "quit" event and you integrate Lily into
-- your `love.run` loop, call `lily.quit` before `return`.
-- Need love module
local love = require("love")
assert(love._version >= "11.0", "Lily v3.x require at least LOVE 11.0")
-- Need love.event and love.thread
assert(love.event, "Lily requires love.event. Enable it in conf.lua or require it manually!")
assert(love.thread, "Lily requires love.thread. Enable it in conf.lua or require it manually!")
local modulePath = select(1, ...):match("(.-)[^%.]+$")
local lily = {
_VERSION = "3.0.12",
-- Loaded modules
modules = {},
-- List of threads
threads = {},
-- Function handler
handlers = {},
-- Request list
request = {}
}
-- List of excluded modules to be loaded (doesn't make sense to be async)
-- PS: "event" module will be always loaded regardless.
local excludedModules = {
"event",
"joystick",
"keyboard",
"math",
"mouse",
"physics",
"system",
"timer",
"touch",
"window"
}
-- List all loaded LOVE modules using hidden "love._modules" table
for name in pairs(love._modules) do
local f = false
for i = 1, #excludedModules do
if excludedModules[i] == name then
-- Excluded
f = true
break
end
end
-- If not excluded, add it.
if not(f) then
lily.modules[#lily.modules + 1] = name
end
end
-- We have some ways to get processor count
local amountOfCPU = 1
if love.system then
-- love.system is loaded. We can use that.
amountOfCPU = love.system.getProcessorCount()
elseif love._os == "Windows" then
-- Windows. Use NUMBER_OF_PROCESSORS environment variable
amountOfCPU = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
-- We still have some workaround if that fails
if not(amountOfCPU) and os.execute("wmic exit") == 0 then
-- Use WMIC
local a = io.popen("wmic cpu get NumberOfLogicalProcessors")
a:read("*l")
amountOfCPU = a:read("*n")
a:close()
end
-- If it's fallback to 1, it's either very weird system configuration!
-- (except if the CPU only has 1 processor)
amountOfCPU = amountOfCPU or 1
elseif os.execute() == 1 then
-- Ok we have shell support
if os.execute("nproc") == 0 then
-- Use nproc
local a = io.popen("nproc", "r")
amountOfCPU = a:read("*n")
a:close()
end
-- Fallback to single core (discouraged, it will perform same as love-loader)
end
-- Limit CPU to 4. Imagine how many threads will be created when
-- someone runs this in threadripper.
amountOfCPU = math.min(amountOfCPU, 4)
-- Dummy channel used to signal main thread that there's error
local errorChannel = love.thread.newChannel()
-- Main channel used to push task
lily.taskChannel = love.thread.newChannel()
-- Main channel used to pull task
lily.dataPullChannel = love.thread.newChannel()
-- Main channel to determine how to push event
lily.updateModeChannel = love.thread.newChannel()
lily.updateModeChannel:push("automatic") -- Use LOVE event handling by default
-- Variable used to indicate that embedded code should be used
-- instead of loading file (lily_single)
local lilyThreadScript = nil
-- Function to initialize threads. Must be declared as local
-- then called later
local function initThreads()
for i = 1, amountOfCPU do
-- Create thread
local a = love.thread.newThread(
lilyThreadScript or
(modulePath:gsub("%.", "/").."vendor/lily_thread.lua")
)
-- Arguments are:
-- Loaded modules
-- errorChannel
-- taskChannel
-- dataPullChannel
-- updateModeChannel
a:start(lily.modules, errorChannel, lily.taskChannel, lily.dataPullChannel, lily.updateModeChannel)
lily.threads[i] = a
end
end
--luacheck: push no unused args
----------------
-- LilyObject --
----------------
local lilyObjectMethod = {}
local lilyObjectMeta = {__index = lilyObjectMethod}
-- Complete function
function lilyObjectMethod.complete(userdata, ...)
end
-- On error function
function lilyObjectMethod.error(userdata, errorMessage, source)
error(errorMessage.."\n"..source)
end
function lilyObjectMethod:onComplete(func)
self.complete = assert(
type(func) == "function" and func,
"bad argument #1 to 'lilyObject:onComplete' (function expected)"
)
return self
end
function lilyObjectMethod:onError(func)
self.error = assert(
type(func) == "function" and func,
"bad argument #1 to 'lilyObject:onError' (function expected)"
)
return self
end
function lilyObjectMethod:setUserData(userdata)
self.userdata = userdata
return self
end
function lilyObjectMethod:isComplete()
return not(not(self.values))
end
function lilyObjectMethod:getValues()
assert(self.values, "Incomplete request")
return unpack(self.values)
end
function lilyObjectMeta:__tostring()
return "LilyObject: "..self.requestType
end
---------------------
-- MultiLilyObject --
---------------------
local multiObjectMethod = {}
local multiObjectMeta = {__index = multiObjectMethod}
-- On loaded function (noop)
multiObjectMethod.loaded = lilyObjectMethod.complete
-- On error function
function multiObjectMethod.error(userdata, lilyIndex, errorMessage, source)
error(errorMessage.."\n"..source)
end
-- On complete function (noop)
multiObjectMethod.complete = lilyObjectMethod.complete
-- Internal function for child lilies error handler
local function multiObjectChildErrorHandler(userdata, errorMessage, source)
-- Userdata is {index, parentObject}
local multi = userdata[2]
multi.error(multi.userdata, userdata[1], errorMessage, source)
end
-- Internal function used for child lilies onComplete callback
local function multiObjectOnLoaded(info, ...)
-- Info is {index, parentObject}
local multiLily = info[2]
multiLily.completedRequest = multiLily.completedRequest + 1
multiLily.loaded(multiLily.userdata, info[1], select(1, ...))
-- If it's complete, then call onComplete callback of MultiLilyObject
if multiLily:isComplete() then
-- Process
local output = {}
for i = 1, #multiLily.lilies do
output[i] = multiLily.lilies[i].values
end
multiLily.complete(multiLily.userdata, output)
end
end
function multiObjectMethod:onLoaded(func)
self.loaded = assert(
type(func) == "function" and func,
"bad argument #1 to 'lilyObject:onLoaded' (function expected)"
)
return self
end
function multiObjectMethod:onComplete(func)
self.complete = assert(
type(func) == "function" and func,
"bad argument #1 to 'lilyObject:onComplete' (function expected)"
)
return self
end
function multiObjectMethod:onError(func)
self.error = assert(
type(func) == "function" and func,
"bad argument #1 to 'lilyObject:onError' (function expected)"
)
return self
end
function multiObjectMethod:setUserData(userdata)
self.userdata = userdata
return self
end
function multiObjectMethod:isComplete()
return self.completedRequest >= #self.lilies
end
function multiObjectMethod:getValues(index)
assert(self:isComplete(), "Incomplete request")
if index == nil then
local output = {}
for i = 1, #self.lilies do
output[i] = self.lilies[i].values
end
return output
end
return assert(self.lilies[index], "Invalid index"):getValues()
end
function multiObjectMethod:getCount()
return #self.lilies
end
function multiObjectMethod:getLoadedCount()
return self.completedRequest
end
multiObjectMeta.__len = multiObjectMethod.getCount
-- luacheck: pop
-- Lily global event handling function
local function lilyEventHandler(reqID, v1, v2)
-- Check if specified request exist
if lily.request[reqID] then
local lilyObject = lily.request[reqID]
lily.request[reqID] = nil
-- Check for error
if v1 == errorChannel then
-- Second argument is the error message
lilyObject.error(lilyObject.userdata, v2, lilyObject.trace)
else
-- "v2" is returned values
-- Call main thread handler for specified request type
local values = {pcall(lily.handlers[lilyObject.requestType], lilyObject, unpack(v2))}
-- If values[1] is false then there's error
if not(values[1]) then
lilyObject.error(lilyObject.userdata, values[2])
else
-- No error. Remove first value (pcall status)
table.remove(values, 1)
-- Set values table
lilyObject.values = values
lilyObject.complete(lilyObject.userdata, unpack(values))
end
end
end
end
-- Add Lily event handler to love.handlers (lily_resp)
love.handlers.lily_resp = lilyEventHandler
--- Get amount of thread for processing
-- In most cases, this is amount of logical CPU available.
-- @treturn number Amount of threads used by Lily.
function lily.getThreadCount()
return amountOfCPU
end
--- Uninitializes Lily and used threads.
-- Call this just before your game quit (inside `love.quit()`).
-- Not calling this function in iOS and Android can cause
-- strange crash when re-starting your game!
function lily.quit()
-- Clear up the task channel
while lily.taskChannel:getCount() > 0 do
lily.taskChannel:pop()
end
-- Push quit request in task channel
-- Anything that is not a table is considered as "exit"
for i = 1, amountOfCPU do
lily.taskChannel:push(i)
end
-- Clean up threads
for i = 1, amountOfCPU do
local t = lily.threads[i]
if t then
-- Wait
t:wait()
-- Clear
lily.threads[i] = nil
end
end
-- Reset package table
package.loaded.lily = nil
end
do
local function atomicSetUpdateMode(_, mode)
lily.updateModeChannel:pop()
lily.updateModeChannel:push(mode)
end
--- Set update mode.
-- tell Lily to pull data by using LOVE event handler or by
-- using `lily.update` function.
-- @tparam string mode Either `automatic` or `manual`.
function lily.setUpdateMode(mode)
if mode ~= "automatic" and mode ~= "manual" then
error("bad argument #1 to 'setUpdateMode' (\"automatic\" or \"manual\" expected)", 2)
end
-- Set update mode
lily.updateModeChannel:performAtomic(atomicSetUpdateMode, mode)
end
end -- do
local function manualProcessSingleData()
local count = lily.dataPullChannel:getCount()
local processed = false
if count > 0 then
-- Pop data
local data = lily.dataPullChannel:pop()
-- Pass to event handler
lilyEventHandler(data[1], data[2], data[3])
processed = true
end
return count, processed
end
--- Pull processed data from other threads.
-- Signals other loader object (calling their callback function) when necessary.
function lily.update(timeout)
timeout = timeout or -1
local left = -1
local count = 0
if love.timer and timeout >= 0 then
local t = love.timer.getTime() + timeout
while love.timer.getTime() < t and (left > 0 or left == -1) do
local processed
left, processed = manualProcessSingleData()
count = count + (processed and 1 or 0)
end
else
-- No love.timer (can't use timeout object) or timeout is negative.
while (left > 0 or left == -1) do
local processed
left, processed = manualProcessSingleData()
count = count + (processed and 1 or 0)
end
end
return count, math.max(left, 0)
end
----------------------------------------
-- Lily async asset loading functions --
----------------------------------------
local function dummyhandler(...)
return select(2, ...)
end
local function wraphandler(fname)
return function(...)
return fname(select(2, ...))
end
end
-- Internal function to create request ID
local function createReqID()
local t = {}
for _ = 1, 64 do
t[#t + 1] = string.char(math.random(0, 255))
end
return table.concat(t)
end
-- Internal function which return function to create LilyObject
-- with specified request type
local function newLilyFunction(requestType, handlerFunc)
local tracebackname = "Function is lily."..requestType
-- This function is the constructor
lily[requestType] = function(...)
-- Initialize
local this = setmetatable({}, lilyObjectMeta)
local reqID = createReqID()
local args = {...}
-- Values
this.requestType = requestType
this.done = false
this.values = nil
this.trace = debug.traceback(tracebackname)
-- Push task
-- See structure in lily_thread.lua
local treq = {reqID, requestType, #args}
-- Push arguments
for i = 1, #args do
treq[i + 3] = args[i]
end
-- Add to task channel
lily.taskChannel:push(treq)
-- Insert to request table (to prevent GC collecting it)
lily.request[reqID] = this
-- Return
return this
end
-- Handler function
lily.handlers[requestType] = handlerFunc and wraphandler(handlerFunc) or dummyhandler
end
-- love.audio
if love.audio then
newLilyFunction("newSource")
end
-- love.data (always exists)
if love.data then
local function dataGetString(value)
return value:getString()
end
newLilyFunction("compress", dataGetString)
newLilyFunction("decompress", dataGetString)
end
-- love.filesystem (always exists)
if love.filesystem then
newLilyFunction("append")
newLilyFunction("newFileData")
newLilyFunction("read")
newLilyFunction("readFile")
newLilyFunction("write")
newLilyFunction("writeFile")
end
-- Most love.graphics functions are not meant for multithread, but we can circumvent that.
if love.graphics then
-- Internal function
local function defMultiToSingleError(udata, _, msg)
udata[1].error(udata[1].userdata, msg)
end
-- Internal function to generate complete callback
local function defImageMultiGen(f)
return function(udata, values)
local this = udata[1]
local v = {}
for i = 1, #values do
v[i] = values[i][1]
end
this.values = {f(v, udata[2])}
this.complete(this.userdata, unpack(this.values))
end
end
-- Internal function to generate layering-based function
local function genLayerImage(name, handlerFunc)
local defCompleteFunction = defImageMultiGen(handlerFunc)
lily.handlers[name] = wraphandler(handlerFunc)
lily[name] = function(layers, setting)
local multiCount = {}
for _, v in ipairs(layers) do
if type(v) == "table" then
-- List of mipmaps
error("Nested table (mipmaps) is not supported at the moment")
else
multiCount[#multiCount + 1] = {lily.newImageData, v, setting}
end
end
-- Check count
if #multiCount == 0 then
error("Layers is empty", 2)
end
-- Initialize
local this = setmetatable({}, lilyObjectMeta)
-- Values
this.requestType = name
this.done = false
this.values = nil
this.multi = lily.loadMulti(multiCount)
:setUserData({this, setting})
:onComplete(defCompleteFunction)
:onError(defMultiToSingleError)
-- Return
return this
end
end
-- Basic function which is supported on all systems
newLilyFunction("newFont", love.graphics.newFont)
newLilyFunction("newImage", love.graphics.newImage)
newLilyFunction("newVideo", love.graphics.newVideo)
-- Get texture type
local texType = love.graphics.getTextureTypes()
-- Not all system support cube image. Make it unavailable in that case.
if texType.cube then
-- Another internal function
local defNewCubeImageMulti = defImageMultiGen(love.graphics.newCubeImage)
lily.newCubeImage = function(layers, setting)
local multiCount = {}
-- If it's table, that means it contains list of files
if type(layers) == "table" then
assert(#layers == 6, "Invalid list of files (must be exactly 6)")
for _, v in ipairs(layers) do
if type(v) == "table" then
-- List of mipmaps
error("Nested table (mipmaps) is not supported at the moment")
else
multiCount[#multiCount + 1] = {lily.newImage, v, setting}
end
end
-- Are you specify tons of "Image" objects?
if #multiCount == 0 then
error("Nothing to parallelize", 2)
end
end
-- Initialize
local this = setmetatable({}, lilyObjectMeta)
local reqID = createReqID()
-- Values
this.requestType = "newCubeImage"
this.done = false
this.values = nil
-- If multi count is 0, that means it's just single file
if #multiCount == 0 then
-- Insert to request table
lily.request[reqID] = this
-- Create and push new task
local treq = {reqID, "newImage", 2, layers, setting}
lily.taskChannel:push(treq)
else
this.multi = lily.loadMulti(multiCount)
:setUserData({this, setting})
:onComplete(defNewCubeImageMulti)
:onError(defMultiToSingleError)
end
-- Return
return this
end
lily.handlers.newCubeImage = wraphandler(love.graphics.newCubeImage)
end
-- Not all system support array image
if texType.array then
genLayerImage("newArrayImage", love.graphics.newArrayImage)
end
-- Not all system support volume image
if texType.volume then
genLayerImage("newVolumeImage", love.graphics.newVolumeImage)
end
end
if love.image then
newLilyFunction("encodeImageData")
newLilyFunction("newImageData")
newLilyFunction("newCompressedData")
newLilyFunction("pasteImageData")
end
if love.sound then
newLilyFunction("newSoundData")
end
if love.video then
newLilyFunction("newVideoStream")
end
function lily.loadMulti(tabdecl)
local this = setmetatable({
lilies = {},
completedRequest = 0
}, multiObjectMeta)
for i = 1, #tabdecl do
local tab = tabdecl[i]
-- tab[1] is lily name, the rest is arguments
local func
if type(tab[1]) == "string" then
if lily[tab[1]] and lily.handlers[tab[1]] then
func = lily[tab[1]]
else
error("Invalid lily function ("..tab[1]..") at index #"..i)
end
elseif type(tab[1]) == "function" then
-- Must be `lily[function]`
func = tab[1]
else
error("Invalid lily function at index #"..i)
end
local lilyobj = func(unpack(tab, 2))
:setUserData({i, this})
:onComplete(multiObjectOnLoaded)
:onError(multiObjectChildErrorHandler)
this.lilies[#this.lilies + 1] = lilyobj
end
return this
end
-- do not remove this comment!
initThreads()
return lily
--[[
Changelog:
v3.0.12: 23-11-2021
> Fixed lily.update count value always 1 if no timeout is specified and there are no pending queues.
v3.0.11: 01-10-2021
> Added timeout parameter to lily.update. Requires love.timer.
v3.0.10: 23-07-2021
> Fixed lily.newArrayImage and lily.newVolumeImage
v3.0.9: 14-06-2021
> Any lily request now saves the traceback of the caller and will be printed on error
v3.0.8: 11-03-2021
> Fixed `lily.setUpdateMode`
> Thread: call `collectgarbage()` twice before serving
v3.0.7: 15-06-2020
> Fixed `lily.newFont` ignores type hinting and DPI scale
v3.0.6: 08-04-2019
> Reorder lily.newImage image loading function
> Fixed lily.newCubeImage is missing
v3.0.5: 26-12-2018
> Limit threads to 4
v3.0.4: 25-11-2018
> Fixed `lily.decompress` error when passing Data object in LOVE 11.1 and earlier
> Fixed `lily.compress` error
> Make error message more comprehensive
v3.0.3: 12-09-2018
> Explicitly check for LOVE 11.0
> `lily.compress` and `lily.decompress` now follows v2.x API
> Fixed multi:getValues() errors even multi:isComplete() is true
v3.0.2: 18-07-2018
> Fixed calling `lily.newCompressedData` cause Lily thread to crash (fix issue #1)
v3.0.1: 16-07-2018
> Fixed `lily.newFont` ignores size parameter
v3.0.0: 13-06-2018
> Major refactoring
> Allow to set update mode, whetever to use Lily style (automatic) or love-loader style (manual)
> New functions: newArrayImage and newVolumeImage (only on supported systems)
> Loading speed improvements
v2.0.8: 09-06-2018
> Fixed additional arguments were not passed to task handler in separate thread
> Make error message more meaningful (but the stack traceback is still meaningless)
v2.0.7: 06-06-2018
> Fixed `lily.quit` deadlock.
v2.0.6: 05-06-2018
> Added `lily.newCubeImage`
> Fix error handler function signature incorrect for MultiLilyObject
> Added `MultiLilyObject:getLoadedCount()`
v2.0.5: 02-05-2018
> Fixed LOVE 11.0 detection
v2.0.4: 09-01-2018
> Fixed if love.data emulation is used in 0.10.0
v2.0.2: 04-01-2018
> Fixed random crash (again)
> Fixed when lily in folder, it doesn't work
v2.0.1: 03-01-2018
> Fixed random crash
v2.0.0: 01-01-2018
> Support `newVideoStream`
> Support multi loading (`lily.loadMulti`)
> More methods for `LilyObject`
v1.0.0: 21-12-2017
> Initial Release
]]

242
vendor/lily_thread.lua vendored Normal file
View file

@ -0,0 +1,242 @@
-- LOVE Asset Async Loader (Thread Part)
-- Copyright (c) 2021 Miku AuahDark
--
-- This software is provided 'as-is', without any express or implied
-- warranty. In no event will the authors be held liable for any damages
-- arising from the use of this software.
--
-- Permission is granted to anyone to use this software for any purpose,
-- including commercial applications, and to alter it and redistribute it
-- freely, subject to the following restrictions:
--
-- 1. The origin of this software must not be misrepresented; you must not
-- claim that you wrote the original software. If you use this software
-- in a product, an acknowledgment in the product documentation would be
-- appreciated but is not required.
-- 2. Altered source versions must be plainly marked as such, and must not be
-- misrepresented as being the original software.
-- 3. This notice may not be removed or altered from any source distribution.
-- Task channel structure (inside table)
-- request ID (string) or error channel to signal quit
-- Task name (string)
-- Amount of arguments (number)
-- n-amount of arguments (Variant)
-- Load LOVE module
local love = require("love")
require("love.event")
require("love.data")
-- Non-thread-safe modules
local ntsModules = {"graphics", "window"}
-- But love.graphics must be treated specially
local hasGraphics = false
-- See lily.lua initThreads() function for more information about
-- arguments passed
local modules, errorChannel, taskChannel, dataPullChannel, updateModeChannel = ...
-- Load modules
for _, v in pairs(modules) do
local f = false
if v == "graphics" then hasGraphics = true end
for i = 1, #ntsModules do
if ntsModules[i] == v then
f = true
break
end
end
if not(f) then
require("love."..v)
end
end
-- Handlers
local lilyProcessor = {}
local getUpdateMode
do
local function atomicFuncGetUpdateMode()
return updateModeChannel:peek()
end
function getUpdateMode()
return updateModeChannel:performAtomic(atomicFuncGetUpdateMode)
end
end
-- Function to push data
local function pushData(reqID, v1, v2)
local updateMode = getUpdateMode()
if updateMode == "automatic" then
-- Event push
love.event.push("lily_resp", reqID, v1, v2)
elseif updateMode == "manual" then
-- Channel push
dataPullChannel:push({reqID, v1, v2})
end
end
-- Macro function to create handler
local function lilyHandlerFunc(reqtype, minarg, handler)
lilyProcessor[reqtype] = {minarg = minarg, handler = handler}
end
if love.audio then
lilyHandlerFunc("newSource", 2, function(t)
return love.audio.newSource(t[1], t[2])
end)
end
-- Always exist
if love.data then
local function isCompressedData(t)
return type(t) == "userdata" and t:typeOf("CompressedData")
end
lilyHandlerFunc("compress", 1, function(t)
return love.data.compress("data", t[1] or "lz4", t[2], t[3])
end)
lilyHandlerFunc("decompress", 1, function(t)
if type(t[2]) == "userdata" and t[2]:typeOf("Data") and love._version < "11.2" then
-- Prior to LOVE 11.2, love.data.decompress can't decompress
-- Data object (not CompressedData) due to bug in the code
-- when handling this variant. So, convert it to string before
-- passing it.
t[2] = t[2]:getString()
end
return love.data.decompress("data", t[1], t[2])
end)
end
-- Always exist
if love.filesystem then
lilyHandlerFunc("append", 2, function(t)
return assert(love.filesystem.append(t[1], t[2], t[3]))
end)
lilyHandlerFunc("newFileData", 1, function(t)
return assert(love.filesystem.newFileData(t[1], t[2], t[3]))
end)
lilyHandlerFunc("read", 1, function(t)
return assert(love.filesystem.read(t[1], t[2]))
end)
lilyHandlerFunc("readFile", 1, function(t)
return t[1].read(t[1], t[2])
end)
lilyHandlerFunc("write", 2, function(t)
return assert(love.filesystem.write(t[1], t[2], t[3]))
end)
lilyHandlerFunc("writeFile", 2, function(t)
return t[1].write(t[1], t[2], t[3])
end)
end
if hasGraphics then
lilyHandlerFunc("newFont", 1, function(t)
return love.font.newRasterizer(t[1], t[2], t[3], t[4])
end)
lilyHandlerFunc("newImage", 1, function(t)
local s, x = pcall(love.image.newImageData, t[1])
return (s and x or love.image.newCompressedData(t[1])), select(2, unpack(t))
end)
lilyHandlerFunc("newVideo", 1, function(t)
return love.video.newVideoStream(t[1]), t[2]
end)
lilyHandlerFunc("newCubeImage", 1, function(t)
-- If it's not table, then it should be processed with
-- love.image.newCubeFaces (undocumented function)
if type(t[1]) ~= "table" then
local id = t[1]
if type(id) ~= "userdata" or id:type() ~= "ImageData" then
id = love.image.newImageData(id)
end
t[1] = {love.image.newCubeFaces(id)}
end
for i = 1, 6 do
local v = t[1][i]
local t = v:type()
if t ~= "userdata" or (t ~= "ImageData" and t ~= "CompressedImageData") then
t[1][i] = love.image.newImageData(v)
end
end
return t[1], t[2]
end)
end
if love.image then
lilyHandlerFunc("encodeImageData", 1, function(t)
return t[1].encode(t[1], t[2])
end)
lilyHandlerFunc("newImageData", 1, function(t)
return love.image.newImageData(t[1])
end)
lilyHandlerFunc("newCompressedData", 1, function(t)
return love.image.newCompressedData(t[1])
end)
lilyHandlerFunc("pasteImageData", 7, function(t)
return t[1].paste(t[1], t[2], t[3], t[4], t[5], t[6], t[7])
end)
end
if love.sound then
lilyHandlerFunc("newSoundData", 1, function(t)
return love.sound.newSoundData(t[1], t[2], t[3], t[4])
end)
end
if love.video then
lilyHandlerFunc("newVideoStream", 1, function(t)
return love.video.newVideoStream(t[1])
end)
end
local handlerFunc
local handlerArg
local function callHandler()
return handlerFunc(handlerArg)
end
-- Main loop
while true do
collectgarbage()
collectgarbage()
-- Get request (see the beginning of file for table format)
local request = taskChannel:demand()
-- If it's not table then quit signaled
if type(request) ~= "table" then return end
local tasktype = request[2]
-- If it's exist in lilyProcessor table that means it's valid event
if lilyProcessor[tasktype] then
local task = lilyProcessor[tasktype]
local argc = request[3]
-- Check minarg count
if argc < task.minarg then
-- Too few arguments
pushData(request[1], errorChannel, string.format(
"'%s': too few arguments (at least %d is required)",
tasktype,
task.minarg
))
else
local argv = {}
-- Get arguments
for i = 1, argc do
argv[i] = request[3 + i] -- 4th and later are arguments
end
-- Call
handlerFunc = task.handler
handlerArg = argv
local result = {xpcall(callHandler, debug.traceback)}
if result[1] == false then
-- Error
pushData(request[1], errorChannel, string.format("'%s': %s", tasktype, result[2]))
end
-- Push data (v1 is true, v2 is return values)
table.remove(result, 1)
pushData(request[1], true, result)
end
end
end