game and hud
This commit is contained in:
parent
cf34540a2b
commit
2f455e9c55
41 changed files with 3443 additions and 0 deletions
22
assets.lua
Normal file
22
assets.lua
Normal 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)
|
BIN
assets/captains/john-danger.png
Normal file
BIN
assets/captains/john-danger.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/captains/robert-davis.png
Normal file
BIN
assets/captains/robert-davis.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/captains/steve-layer.png
Normal file
BIN
assets/captains/steve-layer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/hud.png
Normal file
BIN
assets/hud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
assets/hud.psd
Normal file
BIN
assets/hud.psd
Normal file
Binary file not shown.
BIN
assets/intro.ogg
Normal file
BIN
assets/intro.ogg
Normal file
Binary file not shown.
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
BIN
assets/logo.psd
Normal file
BIN
assets/logo.psd
Normal file
Binary file not shown.
7
captain/johndanger.lua
Normal file
7
captain/johndanger.lua
Normal 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
7
captain/player.lua
Normal 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
7
captain/robertdavis.lua
Normal 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
7
captain/stevelayer.lua
Normal 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
14
conf.lua
Normal 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
81
main.lua
Normal 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
10
planet/planet.lua
Normal 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
40
scene/game.lua
Normal 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
18
scene/load.lua
Normal 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
69
scene/title.lua
Normal 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
580
shaders/README.md
Normal 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
62
shaders/boxblur.lua
Normal 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
49
shaders/chromasep.lua
Normal 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
|
33
shaders/colorgradesimple.lua
Normal file
33
shaders/colorgradesimple.lua
Normal 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
79
shaders/crt.lua
Normal 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
52
shaders/desaturate.lua
Normal 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
153
shaders/dmg.lua
Normal 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
|
139
shaders/fastgaussianblur.lua
Normal file
139
shaders/fastgaussianblur.lua
Normal 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
63
shaders/filmgrain.lua
Normal 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
129
shaders/fog.lua
Normal 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
55
shaders/gaussianblur.lua
Normal 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
104
shaders/glow.lua
Normal 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
107
shaders/godsray.lua
Normal 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
171
shaders/init.lua
Normal 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
55
shaders/pixelate.lua
Normal 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
59
shaders/posterize.lua
Normal 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
73
shaders/scanlines.lua
Normal 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
64
shaders/sketch.lua
Normal 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
59
shaders/vignette.lua
Normal 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
68
vendor/classic.lua
vendored
Normal 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
765
vendor/lily.lua
vendored
Normal 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
242
vendor/lily_thread.lua
vendored
Normal 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
|
Loading…
Reference in a new issue