From b3df40f76c0b1bfef8bf92a48a1fc94fc796d615 Mon Sep 17 00:00:00 2001 From: Paul Liverman Date: Sat, 25 Oct 2014 22:05:49 -0700 Subject: [PATCH] init --- run.bat | 2 + screenshots/something I am starting on.png | Bin 0 -> 77192 bytes src/conf.lua | 35 + src/lib/body.lua | 651 ++++++++++++++++++ src/lib/class.lua | 47 ++ src/lib/light.lua | 225 ++++++ src/lib/light_world.lua | 432 ++++++++++++ src/lib/normal_map.lua | 123 ++++ src/lib/postshader.lua | 183 +++++ src/lib/shaders/blurh.glsl | 13 + src/lib/shaders/blurv.glsl | 13 + src/lib/shaders/glow.glsl | 20 + src/lib/shaders/material.glsl | 10 + src/lib/shaders/normal.glsl | 50 ++ src/lib/shaders/normal_invert.glsl | 50 ++ src/lib/shaders/poly_shadow.glsl | 41 ++ .../shaders/postshaders/black_and_white.glsl | 24 + .../postshaders/chromatic_aberration.glsl | 13 + src/lib/shaders/postshaders/contrast.glsl | 6 + src/lib/shaders/postshaders/curvature.glsl | 88 +++ src/lib/shaders/postshaders/edges.glsl | 42 ++ src/lib/shaders/postshaders/four_colors.glsl | 8 + src/lib/shaders/postshaders/hdr_tv.glsl | 9 + src/lib/shaders/postshaders/monochrome.glsl | 13 + src/lib/shaders/postshaders/phosphor.glsl | 159 +++++ src/lib/shaders/postshaders/phosphorish.glsl | 48 ++ src/lib/shaders/postshaders/pip.glsl | 66 ++ src/lib/shaders/postshaders/pixellate.glsl | 13 + src/lib/shaders/postshaders/radialblur.glsl | 21 + src/lib/shaders/postshaders/scanlines.glsl | 33 + src/lib/shaders/postshaders/tilt_shift.glsl | 12 + src/lib/shaders/postshaders/waterpaint.glsl | 57 ++ src/lib/shaders/reflection.glsl | 24 + src/lib/shaders/refraction.glsl | 19 + src/lib/stencils.lua | 33 + src/lib/util.lua | 43 ++ src/lib/vector.lua | 21 + src/main.lua | 43 ++ 38 files changed, 2690 insertions(+) create mode 100644 run.bat create mode 100644 screenshots/something I am starting on.png create mode 100644 src/conf.lua create mode 100644 src/lib/body.lua create mode 100644 src/lib/class.lua create mode 100644 src/lib/light.lua create mode 100644 src/lib/light_world.lua create mode 100644 src/lib/normal_map.lua create mode 100644 src/lib/postshader.lua create mode 100644 src/lib/shaders/blurh.glsl create mode 100644 src/lib/shaders/blurv.glsl create mode 100644 src/lib/shaders/glow.glsl create mode 100644 src/lib/shaders/material.glsl create mode 100644 src/lib/shaders/normal.glsl create mode 100644 src/lib/shaders/normal_invert.glsl create mode 100644 src/lib/shaders/poly_shadow.glsl create mode 100644 src/lib/shaders/postshaders/black_and_white.glsl create mode 100644 src/lib/shaders/postshaders/chromatic_aberration.glsl create mode 100644 src/lib/shaders/postshaders/contrast.glsl create mode 100644 src/lib/shaders/postshaders/curvature.glsl create mode 100644 src/lib/shaders/postshaders/edges.glsl create mode 100644 src/lib/shaders/postshaders/four_colors.glsl create mode 100644 src/lib/shaders/postshaders/hdr_tv.glsl create mode 100644 src/lib/shaders/postshaders/monochrome.glsl create mode 100644 src/lib/shaders/postshaders/phosphor.glsl create mode 100644 src/lib/shaders/postshaders/phosphorish.glsl create mode 100644 src/lib/shaders/postshaders/pip.glsl create mode 100644 src/lib/shaders/postshaders/pixellate.glsl create mode 100644 src/lib/shaders/postshaders/radialblur.glsl create mode 100644 src/lib/shaders/postshaders/scanlines.glsl create mode 100644 src/lib/shaders/postshaders/tilt_shift.glsl create mode 100644 src/lib/shaders/postshaders/waterpaint.glsl create mode 100644 src/lib/shaders/reflection.glsl create mode 100644 src/lib/shaders/refraction.glsl create mode 100644 src/lib/stencils.lua create mode 100644 src/lib/util.lua create mode 100644 src/lib/vector.lua create mode 100644 src/main.lua diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..f52efb1 --- /dev/null +++ b/run.bat @@ -0,0 +1,2 @@ +@ECHO OFF +"C:\Program Files\LOVE\love.exe" "%cd%/src" diff --git a/screenshots/something I am starting on.png b/screenshots/something I am starting on.png new file mode 100644 index 0000000000000000000000000000000000000000..0befbb62371890971b1cafe96ae155b7e8a13ef3 GIT binary patch literal 77192 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 literal 0 HcmV?d00001 diff --git a/src/conf.lua b/src/conf.lua new file mode 100644 index 0000000..60b77a4 --- /dev/null +++ b/src/conf.lua @@ -0,0 +1,35 @@ +function love.conf(t) + t.identity = "Thompson Was a Clone" + --t.title = "Thompson Was a Clone" + --t.author = "Guard13007" + t.version = "0.9.1" + t.console = false + + t.window.title = "Thompson Was a Clone" + --t.window.icon = nil + t.window.width = 800 + t.window.height = 450 + t.window.borderless = false + t.window.resizable = false + t.window.fullscreen = false + + --t.window.vsync = false + t.window.fsaa = 0 + t.window.display = 1 + t.window.highdpi = false + t.window.srgb = false + + t.modules.audio = true + t.modules.event = true + t.modules.graphics = true + t.modules.image = true + t.modules.joystick = false --may change? + t.modules.keyboard = true + t.modules.math = true --may change ? + t.modules.mouse = true + t.modules.physics = false + t.modules.sound = true + t.modules.system = true + t.modules.timer = true + t.modules.window = true +end diff --git a/src/lib/body.lua b/src/lib/body.lua new file mode 100644 index 0000000..9cb52f5 --- /dev/null +++ b/src/lib/body.lua @@ -0,0 +1,651 @@ +local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or "" +local class = require(_PACKAGE.."/class") +local normal_map = require(_PACKAGE..'/normal_map') +local vector = require(_PACKAGE..'/vector') +local shadowLength = 100000 + +local body = class() + +body.glowShader = love.graphics.newShader(_PACKAGE.."/shaders/glow.glsl") +body.materialShader = love.graphics.newShader(_PACKAGE.."/shaders/material.glsl") + +function body:init(id, type, ...) + local args = {...} + self.id = id + self.type = type + self.shine = true + self.red = 0 + self.green = 0 + self.blue = 0 + self.alpha = 1.0 + self.glowRed = 255 + self.glowGreen = 255 + self.glowBlue = 255 + self.glowStrength = 0.0 + self.tileX = 0 + self.tileY = 0 + + if self.type == "circle" then + self.x = args[1] or 0 + self.y = args[2] or 0 + self:setShadowType('circle', args[3], args[4], args[5]) + elseif self.type == "rectangle" then + self.x = args[1] or 0 + self.y = args[2] or 0 + self:setShadowType('rectangle', args[3], args[4]) + elseif self.type == "polygon" then + self:setShadowType('polygon', ...) + elseif self.type == "image" then + self.img = args[1] + self.x = args[2] or 0 + self.y = args[3] or 0 + if self.img then + self.imgWidth = self.img:getWidth() + self.imgHeight = self.img:getHeight() + self.ix = self.imgWidth * 0.5 + self.iy = self.imgHeight * 0.5 + end + self:setShadowType('rectangle', args[4] or self.imgWidth, args[5] or self.imgHeight, args[6], args[7]) + self.reflective = true + elseif self.type == "refraction" then + self:initNormal(...) + self.refraction = true + elseif self.type == "reflection" then + self:initNormal(...) + self.reflection = true + end +end + +function body:initNormal(...) + local args = {...} + self.normal = args[1] + self.x = args[2] or 0 + self.y = args[3] or 0 + if self.normal then + self.normalWidth = self.normal:getWidth() + self.normalHeight = self.normal:getHeight() + self.width = args[4] or self.normalWidth + self.height = args[5] or self.normalHeight + self.nx = self.normalWidth * 0.5 + self.ny = self.normalHeight * 0.5 + self.normal:setWrap("repeat", "repeat") + self.normalVert = { + {0.0, 0.0, 0.0, 0.0}, + {self.width, 0.0, 1.0, 0.0}, + {self.width, self.height, 1.0, 1.0}, + {0.0, self.height, 0.0, 1.0} + } + self.normalMesh = love.graphics.newMesh(self.normalVert, self.normal, "fan") + else + self.width = args[4] or 64 + self.height = args[5] or 64 + end + self.ox = self.width * 0.5 + self.oy = self.height * 0.5 +end + +-- refresh +function body:refresh() + if self.x and self.y and self.width and self.height and self.ox and self.oy then + self.data = { + self.x - self.ox, + self.y - self.oy, + self.x - self.ox + self.width, + self.y - self.oy, + self.x - self.ox + self.width, + self.y - self.oy + self.height, + self.x - self.ox, + self.y - self.oy + self.height + } + end +end + +-- set position +function body:setPosition(x, y) + if x ~= self.x or y ~= self.y then + self.x = x + self.y = y + self:refresh() + end +end + +-- set x position +function body:setX(x) + if x ~= self.x then + self.x = x + self:refresh() + end +end + +-- set y position +function body:setY(y) + if y ~= self.y then + self.y = y + self:refresh() + end +end + +-- get x position +function body:getX() + return self.x +end + +-- get y position +function body:getY(y) + return self.y +end + +-- get width +function body:getWidth() + return self.width +end + +-- get height +function body:getHeight() + return self.height +end + +-- get image width +function body:getImageWidth() + return self.imgWidth +end + +-- get image height +function body:getImageHeight() + return self.imgHeight +end + +-- set dimension +function body:setDimension(width, height) + self.width = width + self.height = height + self:refresh() +end + +-- set offset +function body:setOffset(ox, oy) + if ox ~= self.ox or oy ~= self.oy then + self.ox = ox + self.oy = oy + self:refresh() + end +end + +-- set offset +function body:setImageOffset(ix, iy) + if ix ~= self.ix or iy ~= self.iy then + self.ix = ix + self.iy = iy + self:refresh() + end +end + +-- set offset +function body:setNormalOffset(nx, ny) + if nx ~= self.nx or ny ~= self.ny then + self.nx = nx + self.ny = ny + self:refresh() + end +end + +-- set glow color +function body:setGlowColor(red, green, blue) + self.glowRed = red + self.glowGreen = green + self.glowBlue = blue +end + +-- set glow alpha +function body:setGlowStrength(strength) + self.glowStrength = strength +end + +-- get radius +function body:getRadius() + return self.radius +end + +-- set radius +function body:setRadius(radius) + if radius ~= self.radius then + self.radius = radius + end +end + +-- set polygon data +function body:setPoints(...) + self.data = {...} +end + +-- get polygon data +function body:getPoints() + return unpack(self.data) +end + +-- set shadow on/off +function body:setShadow(b) + self.castsNoShadow = not b +end + +-- set shine on/off +function body:setShine(b) + self.shine = b +end + +-- set glass color +function body:setColor(red, green, blue) + self.red = red + self.green = green + self.blue = blue +end + +-- set glass alpha +function body:setAlpha(alpha) + self.alpha = alpha +end + +-- set reflection on/off +function body:setReflection(reflection) + self.reflection = reflection +end + +-- set refraction on/off +function body:setRefraction(refraction) + self.refraction = refraction +end + +-- set reflective on other objects on/off +function body:setReflective(reflective) + self.reflective = reflective +end + +-- set refractive on other objects on/off +function body:setRefractive(refractive) + self.refractive = refractive +end + +-- set image +function body:setImage(img) + if img then + self.img = img + self.imgWidth = self.img:getWidth() + self.imgHeight = self.img:getHeight() + self.ix = self.imgWidth * 0.5 + self.iy = self.imgHeight * 0.5 + end +end + +-- set normal +function body:setNormalMap(normal, width, height, nx, ny) + if normal then + self.normal = normal + self.normal:setWrap("repeat", "repeat") + self.normalWidth = width or self.normal:getWidth() + self.normalHeight = height or self.normal:getHeight() + self.nx = nx or self.normalWidth * 0.5 + self.ny = ny or self.normalHeight * 0.5 + self.normalVert = { + {0.0, 0.0, 0.0, 0.0}, + {self.normalWidth, 0.0, self.normalWidth / self.normal:getWidth(), 0.0}, + {self.normalWidth, self.normalHeight, self.normalWidth / self.normal:getWidth(), self.normalHeight / self.normal:getHeight()}, + {0.0, self.normalHeight, 0.0, self.normalHeight / self.normal:getHeight()} + } + self.normalMesh = love.graphics.newMesh(self.normalVert, self.normal, "fan") + else + self.normalMesh = nil + end +end + +-- set height map +function body:setHeightMap(heightMap, strength) + self:setNormalMap(normal_map.fromHeightMap(heightMap, strength)) +end + +-- generate flat normal map +function body:generateNormalMapFlat(mode) + self:setNormalMap(normal_map.generateFlat(self.img, mode)) +end + +-- generate faded normal map +function body:generateNormalMapGradient(horizontalGradient, verticalGradient) + self:setNormalMap(normal_map.generateGradient(self.img, horizontalGradient, verticalGradient)) +end + +-- generate normal map +function body:generateNormalMap(strength) + self:setNormalMap(normal_map.fromHeightMap(self.img, strength)) +end + +-- set material +function body:setMaterial(material) + if material then + self.material = material + end +end + +-- set normal +function body:setGlowMap(glow) + self.glow = glow + self.glowStrength = 1.0 +end + +-- set tile offset +function body:setNormalTileOffset(tx, ty) + self.tileX = tx / self.normalWidth + self.tileY = ty / self.normalHeight + self.normalVert = { + {0.0, 0.0, self.tileX, self.tileY}, + {self.normalWidth, 0.0, self.tileX + 1.0, self.tileY}, + {self.normalWidth, self.normalHeight, self.tileX + 1.0, self.tileY + 1.0}, + {0.0, self.normalHeight, self.tileX, self.tileY + 1.0} + } +end + +-- get type +function body:getType() + return self.type +end + +-- get type +function body:setShadowType(type, ...) + self.shadowType = type + local args = {...} + if self.shadowType == "circle" then + self.radius = args[1] or 16 + self.ox = args[2] or 0 + self.oy = args[3] or 0 + elseif self.shadowType == "rectangle" then + self.width = args[1] or 64 + self.height = args[2] or 64 + self.ox = args[3] or self.width * 0.5 + self.oy = args[4] or self.height * 0.5 + self:refresh() + elseif self.shadowType == "polygon" then + self.data = args or {0, 0, 0, 0, 0, 0} + elseif self.shadowType == "image" then + if self.img then + self.width = self.imgWidth + self.height = self.imgHeight + self.shadowVert = { + {0.0, 0.0, 0.0, 0.0}, + {self.width, 0.0, 1.0, 0.0}, + {self.width, self.height, 1.0, 1.0}, + {0.0, self.height, 0.0, 1.0} + } + if not self.shadowMesh then + self.shadowMesh = love.graphics.newMesh(self.shadowVert, self.img, "fan") + self.shadowMesh:setVertexColors(true) + end + else + self.width = 64 + self.height = 64 + end + self.shadowX = args[1] or 0 + self.shadowY = args[2] or 0 + self.fadeStrength = args[3] or 0.0 + end +end + +function body:stencil() + if self.shadowType == "circle" then + love.graphics.circle("fill", self.x - self.ox, self.y - self.oy, self.radius) + elseif self.shadowType == "rectangle" then + love.graphics.rectangle("fill", self.x - self.ox, self.y - self.oy, self.width, self.height) + elseif self.shadowType == "polygon" then + love.graphics.polygon("fill", unpack(self.data)) + elseif self.shadowType == "image" then + --love.graphics.rectangle("fill", self.x - self.ox, self.y - self.oy, self.width, self.height) + end +end + +function body:drawShadow(light) + if self.alpha < 1.0 then + love.graphics.setBlendMode("multiplicative") + love.graphics.setColor(self.red, self.green, self.blue) + if self.shadowType == "circle" then + love.graphics.circle("fill", self.x - self.ox, self.y - self.oy, self.radius) + elseif self.shadowType == "rectangle" then + love.graphics.rectangle("fill", self.x - self.ox, self.y - self.oy, self.width, self.height) + elseif self.shadowType == "polygon" then + love.graphics.polygon("fill", unpack(self.data)) + end + end + + if self.shadowType == "image" and self.img then + love.graphics.setBlendMode("alpha") + local length = 1.0 + local shadowRotation = math.atan2((self.x) - light.x, (self.y + self.oy) - light.y) + + self.shadowVert = { + { + math.sin(shadowRotation) * self.imgHeight * length, + (length * math.cos(shadowRotation) + 1.0) * self.imgHeight + (math.cos(shadowRotation) + 1.0) * self.shadowY, + 0, 0, + self.red, + self.green, + self.blue, + self.alpha * self.fadeStrength * 255 + }, + { + self.imgWidth + math.sin(shadowRotation) * self.imgHeight * length, + (length * math.cos(shadowRotation) + 1.0) * self.imgHeight + (math.cos(shadowRotation) + 1.0) * self.shadowY, + 1, 0, + self.red, + self.green, + self.blue, + self.alpha * self.fadeStrength * 255 + }, + { + self.imgWidth, + self.imgHeight + (math.cos(shadowRotation) + 1.0) * self.shadowY, + 1, 1, + self.red, + self.green, + self.blue, + self.alpha * 255 + }, + { + 0, + self.imgHeight + (math.cos(shadowRotation) + 1.0) * self.shadowY, + 0, 1, + self.red, + self.green, + self.blue, + self.alpha * 255 + } + } + + self.shadowMesh:setVertices(self.shadowVert) + love.graphics.draw(self.shadowMesh, self.x - self.ox, self.y - self.oy, 0, s, s) + end +end + +function body:drawPixelShadow() + if self.type == "image" and self.normalMesh then + love.graphics.setColor(255, 255, 255) + love.graphics.draw(self.normalMesh, self.x - self.nx, self.y - self.ny) + end +end + +function body:drawGlow() + if self.glowStrength > 0.0 then + love.graphics.setColor(self.glowRed * self.glowStrength, self.glowGreen * self.glowStrength, self.glowBlue * self.glowStrength) + else + love.graphics.setColor(0, 0, 0) + end + + if self.type == "circle" then + love.graphics.circle("fill", self.x, self.y, self.radius) + elseif self.type == "rectangle" then + love.graphics.rectangle("fill", self.x, self.y, self.width, self.height) + elseif self.type == "polygon" then + love.graphics.polygon("fill", unpack(self.data)) + elseif self.type == "image" and self.img then + if self.glowStrength > 0.0 and self.glow then + love.graphics.setShader(self.glowShader) + self.glowShader:send("glowImage", self.glow) + self.glowShader:send("glowTime", love.timer.getTime() * 0.5) + love.graphics.setColor(255, 255, 255) + else + love.graphics.setShader() + love.graphics.setColor(0, 0, 0) + end + love.graphics.draw(self.img, self.x - self.ix, self.y - self.iy) + end + + love.graphics.setShader() +end + +function body:drawRefraction() + if self.refraction and self.normal then + love.graphics.setColor(255, 255, 255) + if self.tileX == 0.0 and self.tileY == 0.0 then + love.graphics.draw(normal, self.x - self.nx, self.y - self.ny) + else + self.normalMesh:setVertices(self.normalVert) + love.graphics.draw(self.normalMesh, self.x - self.nx, self.y - self.ny) + end + end + + love.graphics.setColor(0, 0, 0) + + if not self.refractive then + if self.type == "circle" then + love.graphics.circle("fill", self.x, self.y, self.radius) + elseif self.type == "rectangle" then + love.graphics.rectangle("fill", self.x, self.y, self.width, self.height) + elseif self.type == "polygon" then + love.graphics.polygon("fill", unpack(self.data)) + elseif self.type == "image" and self.img then + love.graphics.draw(self.img, self.x - self.ix, self.y - self.iy) + end + end +end + +function body:drawReflection() + if self.reflection and self.normal then + love.graphics.setColor(255, 0, 0) + self.normalMesh:setVertices(self.normalVert) + love.graphics.draw(self.normalMesh, self.x - self.nx, self.y - self.ny) + end + if self.reflective and self.img then + love.graphics.setColor(0, 255, 0) + love.graphics.draw(self.img, self.x - self.ix, self.y - self.iy) + elseif not self.reflection and self.img then + love.graphics.setColor(0, 0, 0) + love.graphics.draw(self.img, self.x - self.ix, self.y - self.iy) + end +end + +function body:drawMaterial() + if self.material and self.normal then + love.graphics.setShader(self.materialShader) + love.graphics.setColor(255, 255, 255) + self.materialShader:send("material", self.material) + love.graphics.draw(self.normal, self.x - self.nx, self.y - self.ny) + love.graphics.setShader() + end +end + +function body:calculateShadow(light) + if self.shadowType == "rectangle" or self.shadowType == "polygon" then + return self:calculatePolyShadow(light) + elseif self.shadowType == "circle" then + return self:calculateCircleShadow(light) + end +end + +function body:calculatePolyShadow(light) + if self.castsNoShadow then + return nil + end + + local curPolygon = self.data + local edgeFacingTo = {} + for k = 1, #curPolygon, 2 do + local indexOfNextVertex = (k + 2) % #curPolygon + local normal = {-curPolygon[indexOfNextVertex+1] + curPolygon[k + 1], curPolygon[indexOfNextVertex] - curPolygon[k]} + local lightToPoint = {curPolygon[k] - light.x, curPolygon[k + 1] - light.y} + + normal = vector.normalize(normal) + lightToPoint = vector.normalize(lightToPoint) + + local dotProduct = vector.dot(normal, lightToPoint) + if dotProduct > 0 then table.insert(edgeFacingTo, true) + else table.insert(edgeFacingTo, false) end + end + + local curShadowGeometry = {} + for k = 1, #edgeFacingTo do + local nextIndex = (k + 1) % #edgeFacingTo + if nextIndex == 0 then nextIndex = #edgeFacingTo end + if edgeFacingTo[k] and not edgeFacingTo[nextIndex] then + curShadowGeometry[1] = curPolygon[nextIndex*2-1] + curShadowGeometry[2] = curPolygon[nextIndex*2] + + local lightVecFrontBack = vector.normalize({curPolygon[nextIndex*2-1] - light.x, curPolygon[nextIndex*2] - light.y}) + curShadowGeometry[3] = curShadowGeometry[1] + lightVecFrontBack[1] * shadowLength + curShadowGeometry[4] = curShadowGeometry[2] + lightVecFrontBack[2] * shadowLength + + elseif not edgeFacingTo[k] and edgeFacingTo[nextIndex] then + curShadowGeometry[7] = curPolygon[nextIndex*2-1] + curShadowGeometry[8] = curPolygon[nextIndex*2] + + local lightVecBackFront = vector.normalize({curPolygon[nextIndex*2-1] - light.x, curPolygon[nextIndex*2] - light.y}) + curShadowGeometry[5] = curShadowGeometry[7] + lightVecBackFront[1] * shadowLength + curShadowGeometry[6] = curShadowGeometry[8] + lightVecBackFront[2] * shadowLength + end + end + if curShadowGeometry[1] + and curShadowGeometry[2] + and curShadowGeometry[3] + and curShadowGeometry[4] + and curShadowGeometry[5] + and curShadowGeometry[6] + and curShadowGeometry[7] + and curShadowGeometry[8] + then + curShadowGeometry.alpha = self.alpha + curShadowGeometry.red = self.red + curShadowGeometry.green = self.green + curShadowGeometry.blue = self.blue + return curShadowGeometry + else + return nil + end +end + +function body:calculateCircleShadow(light) + if self.castsNoShadow then + return nil + end + local length = math.sqrt(math.pow(light.x - (self.x - self.ox), 2) + math.pow(light.y - (self.y - self.oy), 2)) + if length >= self.radius and length <= light.range then + local curShadowGeometry = {} + local angle = math.atan2(light.x - (self.x - self.ox), (self.y - self.oy) - light.y) + math.pi / 2 + local x2 = ((self.x - self.ox) + math.sin(angle) * self.radius) + local y2 = ((self.y - self.oy) - math.cos(angle) * self.radius) + local x3 = ((self.x - self.ox) - math.sin(angle) * self.radius) + local y3 = ((self.y - self.oy) + math.cos(angle) * self.radius) + + curShadowGeometry[1] = x2 + curShadowGeometry[2] = y2 + curShadowGeometry[3] = x3 + curShadowGeometry[4] = y3 + + curShadowGeometry[5] = x3 - (light.x - x3) * shadowLength + curShadowGeometry[6] = y3 - (light.y - y3) * shadowLength + curShadowGeometry[7] = x2 - (light.x - x2) * shadowLength + curShadowGeometry[8] = y2 - (light.y - y2) * shadowLength + curShadowGeometry.alpha = self.alpha + curShadowGeometry.red = self.red + curShadowGeometry.green = self.green + curShadowGeometry.blue = self.blue + return curShadowGeometry + else + return nil + end +end + +return body diff --git a/src/lib/class.lua b/src/lib/class.lua new file mode 100644 index 0000000..489ced4 --- /dev/null +++ b/src/lib/class.lua @@ -0,0 +1,47 @@ +-- class.lua +-- Compatible with Lua 5.1 (not 5.0). +local class = function(base, init) + local c = {} -- a new class instance + if not init and type(base) == 'function' then + init = base + base = nil + elseif type(base) == 'table' then + -- our new class is a shallow copy of the base class! + for i,v in pairs(base) do + c[i] = v + end + c._base = base + end + -- the class will be the metatable for all its objects, + -- and they will look up their methods in it. + c.__index = c + + -- expose a constructor which can be called by () + local mt = {} + mt.__call = function(class_tbl, ...) + local obj = {} + setmetatable(obj,c) + if class_tbl.init then + class_tbl.init(obj,...) + else + -- make sure that any stuff from the base class is initialized! + if base and base.init then + base.init(obj, ...) + end + end + return obj + end + c.init = init + c.is_a = function(self, klass) + local m = getmetatable(self) + while m do + if m == klass then return true end + m = m._base + end + return false + end + setmetatable(c, mt) + return c +end + +return class diff --git a/src/lib/light.lua b/src/lib/light.lua new file mode 100644 index 0000000..2afe867 --- /dev/null +++ b/src/lib/light.lua @@ -0,0 +1,225 @@ +local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or "" +local class = require(_PACKAGE.."/class") +local stencils = require(_PACKAGE..'/stencils') +local util = require(_PACKAGE..'/util') + +local light = class() + +light.shader = love.graphics.newShader(_PACKAGE.."/shaders/poly_shadow.glsl") +light.normalShader = love.graphics.newShader(_PACKAGE.."/shaders/normal.glsl") +light.normalInvertShader = love.graphics.newShader(_PACKAGE.."/shaders/normal_invert.glsl") + +function light:init(x, y, r, g, b, range) + self.direction = 0 + self.angle = math.pi * 2.0 + self.range = 0 + self.x = x or 0 + self.y = y or 0 + self.z = 15 + self.red = r or 255 + self.green = g or 255 + self.blue = b or 255 + self.range = range or 300 + self.smooth = 1.0 + self.glowSize = 0.1 + self.glowStrength = 0.0 + self.visible = true + self:refresh() +end + +function light:refresh(w, h) + w, h = w or love.window.getWidth(), h or love.window.getHeight() + + self.shadow = love.graphics.newCanvas(w, h) + self.shine = love.graphics.newCanvas(w, h) +end + +-- set position +function light:setPosition(x, y, z) + if x ~= self.x or y ~= self.y or (z and z ~= self.z) then + self.x = x + self.y = y + if z then + self.z = z + end + end +end + +-- get x +function light:getX() + return self.x +end + +-- get y +function light:getY() + return self.y +end + +-- set x +function light:setX(x) + if x ~= self.x then + self.x = x + end +end + +-- set y +function light:setY(y) + if y ~= self.y then + self.y = y + end +end + +-- set color +function light:setColor(red, green, blue) + self.red = red + self.green = green + self.blue = blue +end + +-- set range +function light:setRange(range) + if range ~= self.range then + self.range = range + end +end + +-- set direction +function light:setDirection(direction) + if direction ~= self.direction then + if direction > math.pi * 2 then + self.direction = math.mod(direction, math.pi * 2) + elseif direction < 0.0 then + self.direction = math.pi * 2 - math.mod(math.abs(direction), math.pi * 2) + else + self.direction = direction + end + end +end + +-- set angle +function light:setAngle(angle) + if angle ~= self.angle then + if angle > math.pi then + self.angle = math.mod(angle, math.pi) + elseif angle < 0.0 then + self.angle = math.pi - math.mod(math.abs(angle), math.pi) + else + self.angle = angle + end + end +end + +-- set glow size +function light:setSmooth(smooth) + self.smooth = smooth +end + +-- set glow size +function light:setGlowSize(size) + self.glowSize = size +end + +-- set glow strength +function light:setGlowStrength(strength) + self.glowStrength = strength +end + +function light:inRange(l,t,w,h) + return self.x + self.range > l and + self.x - self.range < (l+w) and + self.y + self.range > t and + self.y - self.range < (t+h) +end + +function light:drawShadow(l,t,w,h,s,bodies, canvas) + if self.visible and self:inRange(l,t,w,h) then + -- calculate shadows + local shadow_geometry = {} + for i = 1, #bodies do + local current = bodies[i]:calculateShadow(self) + if current ~= nil then + shadow_geometry[#shadow_geometry + 1] = current + end + end + + -- draw shadow + self.shadow:clear() + util.drawto(self.shadow, l, t, s, function() + self.shader:send("lightPosition", {self.x*s, (h/s - self.y)*s, self.z}) + self.shader:send("lightRange", self.range*s) + self.shader:send("lightColor", {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) + self.shader:send("lightSmooth", self.smooth) + self.shader:send("lightGlow", {1.0 - self.glowSize, self.glowStrength}) + self.shader:send("lightAngle", math.pi - self.angle / 2.0) + self.shader:send("lightDirection", self.direction) + love.graphics.setShader(self.shader) + love.graphics.setInvertedStencil(stencils.shadow(shadow_geometry, bodies)) + love.graphics.setBlendMode("additive") + love.graphics.rectangle("fill", -l/s,-t/s,w/s,h/s) + + -- draw color shadows + love.graphics.setBlendMode("multiplicative") + love.graphics.setShader() + for k = 1,#shadow_geometry do + if shadow_geometry[k].alpha < 1.0 then + love.graphics.setColor( + shadow_geometry[k].red * (1.0 - shadow_geometry[k].alpha), + shadow_geometry[k].green * (1.0 - shadow_geometry[k].alpha), + shadow_geometry[k].blue * (1.0 - shadow_geometry[k].alpha) + ) + love.graphics.polygon("fill", unpack(shadow_geometry[k])) + end + end + + for k = 1, #bodies do + bodies[k]:drawShadow(self,l,t,w,h,s) + end + end) + + love.graphics.setStencil() + love.graphics.setShader() + util.drawCanvasToCanvas(self.shadow, canvas, {blendmode = "additive"}) + end +end + +function light:drawShine(l,t,w,h,s,bodies,canvas) + if self.visible then + --update shine + self.shine:clear(255, 255, 255) + util.drawto(self.shine, l, t, s, function() + love.graphics.setShader(self.shader) + love.graphics.setBlendMode("alpha") + love.graphics.setStencil(stencils.shine(bodies)) + love.graphics.rectangle("fill", -l/s,-t/s,w/s,h/s) + end) + love.graphics.setStencil() + love.graphics.setShader() + util.drawCanvasToCanvas(self.shine, canvas, {blendmode = "additive"}) + end +end + +function light:drawPixelShadow(l,t,w,h, normalMap, canvas) + if self.visible then + if self.normalInvert then + self.normalInvertShader:send('screenResolution', {w, h}) + self.normalInvertShader:send('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) + self.normalInvertShader:send('lightPosition',{self.x, lh - self.y, self.z / 255.0}) + self.normalInvertShader:send('lightRange',{self.range}) + self.normalInvertShader:send("lightSmooth", self.smooth) + self.normalInvertShader:send("lightAngle", math.pi - self.angle / 2.0) + self.normalInvertShader:send("lightDirection", self.direction) + util.drawCanvasToCanvas(normalMap, canvas, {shader = self.normalInvertShader}) + else + self.normalShader:send('screenResolution', {w, h}) + self.normalShader:send('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) + self.normalShader:send('lightPosition',{self.x, h - self.y, self.z / 255.0}) + self.normalShader:send('lightRange',{self.range}) + self.normalShader:send("lightSmooth", self.smooth) + self.normalShader:send("lightAngle", math.pi - self.angle / 2.0) + self.normalShader:send("lightDirection", self.direction) + util.drawCanvasToCanvas(normalMap, canvas, {shader = self.normalShader}) + end + end +end + +return light diff --git a/src/lib/light_world.lua b/src/lib/light_world.lua new file mode 100644 index 0000000..a804b11 --- /dev/null +++ b/src/lib/light_world.lua @@ -0,0 +1,432 @@ +--[[ +The MIT License (MIT) + +Copyright (c) 2014 Marcus Ihde + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or "" +local class = require(_PACKAGE..'/class') +local Light = require(_PACKAGE..'/light') +local Body = require(_PACKAGE..'/body') +local util = require(_PACKAGE..'/util') +local normal_map = require(_PACKAGE..'/normal_map') +local PostShader = require(_PACKAGE..'/postshader') +require(_PACKAGE..'/postshader') + +local light_world = class() + +light_world.blurv = love.graphics.newShader(_PACKAGE.."/shaders/blurv.glsl") +light_world.blurh = love.graphics.newShader(_PACKAGE.."/shaders/blurh.glsl") +light_world.refractionShader = love.graphics.newShader(_PACKAGE.."/shaders/refraction.glsl") +light_world.reflectionShader = love.graphics.newShader(_PACKAGE.."/shaders/reflection.glsl") + +function light_world:init(options) + self.lights = {} + self.body = {} + self.post_shader = PostShader() + + self.ambient = {0, 0, 0} + self.normalInvert = false + + self.refractionStrength = 8.0 + self.reflectionStrength = 16.0 + self.reflectionVisibility = 1.0 + + self.blur = 2.0 + self.glowBlur = 1.0 + self.glowTimer = 0.0 + self.glowDown = false + + self.drawBackground = function() end + self.drawForground = function() end + + options = options or {} + for k, v in pairs(options) do self[k] = v end + + self:refreshScreenSize() +end + +function light_world:refreshScreenSize(w, h) + w, h = w or love.window.getWidth(), h or love.window.getHeight() + + self.render_buffer = love.graphics.newCanvas(w, h) + self.shadow = love.graphics.newCanvas(w, h) + self.shadow2 = love.graphics.newCanvas(w, h) + self.pixelShadow = love.graphics.newCanvas(w, h) + self.pixelShadow2 = love.graphics.newCanvas(w, h) + self.shine = love.graphics.newCanvas(w, h) + self.shine2 = love.graphics.newCanvas(w, h) + self.normalMap = love.graphics.newCanvas(w, h) + self.glowMap = love.graphics.newCanvas(w, h) + self.glowMap2 = love.graphics.newCanvas(w, h) + self.refractionMap = love.graphics.newCanvas(w, h) + self.refractionMap2 = love.graphics.newCanvas(w, h) + self.reflectionMap = love.graphics.newCanvas(w, h) + self.reflectionMap2 = love.graphics.newCanvas(w, h) + + self.blurv:send("screen", {w, h}) + self.blurh:send("screen", {w, h}) + self.refractionShader:send("screen", {w, h}) + self.reflectionShader:send("screen", {w, h}) + + for i = 1, #self.lights do + self.lights[i]:refresh(w, h) + end + + self.post_shader:refreshScreenSize(w, h) +end + +function light_world:draw(l,t,s) + l,t,s = (l or 0), (t or 0), s or 1 + local w, h = love.graphics.getWidth(), love.graphics.getHeight() + util.drawto(self.render_buffer, l, t, s, function() + self.drawBackground( l,t,w,h,s) + self:drawShadow( l,t,w,h,s) + self.drawForground( l,t,w,h,s) + self:drawMaterial( l,t,w,h,s) + self:drawShine( l,t,w,h,s) + self:drawPixelShadow(l,t,w,h,s) + self:drawGlow( l,t,w,h,s) + self:drawRefraction( l,t,w,h,s) + self:drawReflection( l,t,w,h,s) + end) + self.post_shader:drawWith(self.render_buffer, l, t, s) +end + +function light_world:drawBlur(blendmode, blur, canvas, canvas2, l, t, w, h, s) + if blur <= 0 then + return + end + canvas2:clear() + self.blurv:send("steps", blur) + self.blurh:send("steps", blur) + util.drawCanvasToCanvas(canvas, canvas2, {shader = self.blurv, blendmode = blendmode}) + util.drawCanvasToCanvas(canvas2, canvas, {shader = self.blurh, blendmode = blendmode}) +end + +-- draw shadow +function light_world:drawShadow(l,t,w,h,s) + if not self.isShadows and not self.isLight then + return + end + + -- draw ambient + util.drawto(self.shadow, l, t, s, function() + love.graphics.setColor(unpack(self.ambient)) + love.graphics.setBlendMode("alpha") + love.graphics.rectangle("fill", -l/s, -t/s, w/s, h/s) + for i = 1, #self.lights do + self.lights[i]:drawShadow(l,t,w,h,s,self.body, self.shadow) + end + end) + + light_world:drawBlur("alpha", self.blur, self.shadow, self.shadow2, l, t, w, h, s) + util.drawCanvasToCanvas(self.shadow, self.render_buffer, {blendmode = "multiplicative"}) +end + +-- draw shine +function light_world:drawShine(l,t,w,h,s) + if not self.isShadows then + return + end + + -- update shine + util.drawto(self.shine, l, t, s, function() + love.graphics.setColor(unpack(self.ambient)) + love.graphics.setBlendMode("alpha") + love.graphics.rectangle("fill", -l/s, -t/s, w/s, h/s) + for i = 1, #self.lights do + self.lights[i]:drawShine(l,t,w,h,s,self.body,self.shine) + end + end) + + --light_world:drawBlur("additive", self.blur, self.shine, self.shine2, l, t, w, h, s) + util.drawCanvasToCanvas(self.shine, self.render_buffer, {blendmode = "multiplicative"}) +end + +-- draw pixel shadow +function light_world:drawPixelShadow(l,t,w,h,s) + if not self.isShadows then + return + end + -- update pixel shadow + -- create normal map + self.normalMap:clear() + util.drawto(self.normalMap, l, t, s, function() + for i = 1, #self.body do + self.body[i]:drawPixelShadow() + end + end) + + self.pixelShadow2:clear() + for i = 1, #self.lights do + self.lights[i]:drawPixelShadow(l,t,w,h, self.normalMap, self.pixelShadow2) + end + + self.pixelShadow:clear(255, 255, 255) + util.drawCanvasToCanvas(self.pixelShadow2, self.pixelShadow, {blendmode = "alpha"}) + util.drawto(self.pixelShadow, l, t, s, function() + love.graphics.setBlendMode("additive") + love.graphics.setColor({self.ambient[1], self.ambient[2], self.ambient[3]}) + love.graphics.rectangle("fill", l/s,t/s,w/s,h/s) + end) + + util.drawCanvasToCanvas(self.pixelShadow, self.render_buffer, {blendmode = "multiplicative"}) +end + +-- draw material +function light_world:drawMaterial(l,t,w,h,s) + for i = 1, #self.body do + self.body[i]:drawMaterial() + end +end + +-- draw glow +function light_world:drawGlow(l,t,w,h,s) + if not self.isShadows then + return + end + + -- create glow map + self.glowMap:clear(0, 0, 0) + util.drawto(self.glowMap, l, t, s, function() + if self.glowDown then + self.glowTimer = math.max(0.0, self.glowTimer - love.timer.getDelta()) + if self.glowTimer == 0.0 then + self.glowDown = not self.glowDown + end + else + self.glowTimer = math.min(self.glowTimer + love.timer.getDelta(), 1.0) + if self.glowTimer == 1.0 then + self.glowDown = not self.glowDown + end + end + + for i = 1, #self.body do + self.body[i]:drawGlow() + end + end) + + light_world:drawBlur("alpha", self.glowBlur, self.glowMap, self.glowMap2, l, t, w, h, s) + util.drawCanvasToCanvas(self.glowMap, self.render_buffer, {blendmode = "additive"}) +end +-- draw refraction +function light_world:drawRefraction(l,t,w,h,s) + if not self.isRefraction then + return + end + + -- create refraction map + self.refractionMap:clear() + util.drawto(self.refractionMap, l, t, s, function() + for i = 1, #self.body do + self.body[i]:drawRefraction() + end + end) + + util.drawCanvasToCanvas(self.render_buffer, self.refractionMap2) + self.refractionShader:send("backBuffer", self.refractionMap2) + self.refractionShader:send("refractionStrength", self.refractionStrength) + util.drawCanvasToCanvas(self.refractionMap, self.render_buffer, {shader = self.refractionShader}) +end + +-- draw reflection +function light_world:drawReflection(l,t,w,h,s) + if not self.isReflection then + return + end + + -- create reflection map + self.reflectionMap:clear(0, 0, 0) + util.drawto(self.reflectionMap, l, t, s, function() + for i = 1, #self.body do + self.body[i]:drawReflection() + end + end) + + util.drawCanvasToCanvas(self.render_buffer, self.reflectionMap2) + self.reflectionShader:send("backBuffer", self.reflectionMap2) + self.reflectionShader:send("reflectionStrength", self.reflectionStrength) + self.reflectionShader:send("reflectionVisibility", self.reflectionVisibility) + util.drawCanvasToCanvas(self.reflectionMap, self.render_buffer, {shader = self.reflectionShader}) +end + +-- new light +function light_world:newLight(x, y, red, green, blue, range) + self.lights[#self.lights + 1] = Light(x, y, red, green, blue, range) + self.isLight = true + return self.lights[#self.lights] +end + +-- clear lights +function light_world:clearLights() + self.lights = {} + self.isLight = false +end + +-- clear objects +function light_world:clearBodys() + self.body = {} + self.isShadows = false + self.isRefraction = false + self.isReflection = false +end + +function light_world:setBackgroundMethod(fn) + self.drawBackground = fn or function() end +end + +function light_world:setForegroundMethod(fn) + self.drawForground = fn or function() end +end + +-- set ambient color +function light_world:setAmbientColor(red, green, blue) + self.ambient = {red, green, blue} +end + +-- set ambient red +function light_world:setAmbientRed(red) + self.ambient[1] = red +end + +-- set ambient green +function light_world:setAmbientGreen(green) + self.ambient[2] = green +end + +-- set ambient blue +function light_world:setAmbientBlue(blue) + self.ambient[3] = blue +end + +-- set normal invert +function light_world:setNormalInvert(invert) + self.normalInvert = invert +end + +-- set blur +function light_world:setBlur(blur) + self.blur = blur +end + +-- set blur +function light_world:setShadowBlur(blur) + self.blur = blur +end + +-- set glow blur +function light_world:setGlowStrength(strength) + self.glowBlur = strength +end + +-- set refraction blur +function light_world:setRefractionStrength(strength) + self.refractionStrength = strength +end + +-- set reflection strength +function light_world:setReflectionStrength(strength) + self.reflectionStrength = strength +end + +-- set reflection visibility +function light_world:setReflectionVisibility(visibility) + self.reflectionVisibility = visibility +end + +-- new rectangle +function light_world:newRectangle(x, y, w, h) + self.isShadows = true + return self:newBody("rectangle", x, y, width, height) +end + +-- new circle +function light_world:newCircle(x, y, r) + self.isShadows = true + return self:newBody("circle", x, y, r) +end + +-- new polygon +function light_world:newPolygon(...) + self.isShadows = true + return self:newBody("polygon", ...) +end + +-- new image +function light_world:newImage(img, x, y, width, height, ox, oy) + self.isShadows = true + return self:newBody("image", img, x, y, width, height, ox, oy) +end + +-- new refraction +function light_world:newRefraction(normal, x, y, width, height) + self.isRefraction = true + return self:newBody("refraction", normal, x, y, width, height) +end + +-- new refraction from height map +function light_world:newRefractionHeightMap(heightMap, x, y, strength) + local normal = normal_map.fromHeightMap(heightMap, strength) + self.isRefraction = true + return self.newRefraction(p, normal, x, y) +end + +-- new reflection +function light_world:newReflection(normal, x, y, width, height) + self.isReflection = true + return self:newBody("reflection", normal, x, y, width, height) +end + +-- new reflection from height map +function light_world:newReflectionHeightMap(heightMap, x, y, strength) + local normal = normal_map.fromHeightMap(heightMap, strength) + self.isReflection = true + return self.newReflection(p, normal, x, y) +end + +-- new body +function light_world:newBody(type, ...) + local id = #self.body + 1 + self.body[id] = Body(id, type, ...) + return self.body[#self.body] +end + +-- get body count +function light_world:getBodyCount() + return #self.body +end + +-- get light +function light_world:getBody(n) + return self.body[n] +end + +-- get light count +function light_world:getLightCount() + return #self.lights +end + +-- get light +function light_world:getLight(n) + return self.lights[n] +end + +return light_world diff --git a/src/lib/normal_map.lua b/src/lib/normal_map.lua new file mode 100644 index 0000000..04e5a04 --- /dev/null +++ b/src/lib/normal_map.lua @@ -0,0 +1,123 @@ +normal_map = {} + +function normal_map.fromHeightMap(heightMap, strength) + local imgData = heightMap:getData() + local imgData2 = love.image.newImageData(heightMap:getWidth(), heightMap:getHeight()) + local red, green, blue, alpha + local x, y + local matrix = {} + matrix[1] = {} + matrix[2] = {} + matrix[3] = {} + strength = strength or 1.0 + + for i = 0, heightMap:getHeight() - 1 do + for k = 0, heightMap:getWidth() - 1 do + for l = 1, 3 do + for m = 1, 3 do + if k + (l - 1) < 1 then + x = heightMap:getWidth() - 1 + elseif k + (l - 1) > heightMap:getWidth() - 1 then + x = 1 + else + x = k + l - 1 + end + + if i + (m - 1) < 1 then + y = heightMap:getHeight() - 1 + elseif i + (m - 1) > heightMap:getHeight() - 1 then + y = 1 + else + y = i + m - 1 + end + + local red, green, blue, alpha = imgData:getPixel(x, y) + matrix[l][m] = red + end + end + + red = (255 + ((matrix[1][2] - matrix[2][2]) + (matrix[2][2] - matrix[3][2])) * strength) / 2.0 + green = (255 + ((matrix[2][2] - matrix[1][1]) + (matrix[2][3] - matrix[2][2])) * strength) / 2.0 + blue = 192 + + imgData2:setPixel(k, i, red, green, blue) + end + end + + return love.graphics.newImage(imgData2) +end + +function normal_map.generateFlat(img, mode) + local imgData = img:getData() + local imgNormalData = love.image.newImageData(img:getWidth(), img:getHeight()) + local color + + if mode == "top" then + color = {127, 127, 255} + elseif mode == "front" then + color = {127, 0, 127} + elseif mode == "back" then + color = {127, 255, 127} + elseif mode == "left" then + color = {31, 0, 223} + elseif mode == "right" then + color = {223, 0, 127} + end + + for i = 0, self.imgHeight - 1 do + for k = 0, self.imgWidth - 1 do + local r, g, b, a = imgData:getPixel(k, i) + if a > 0 then + imgNormalData:setPixel(k, i, color[1], color[2], color[3], 255) + end + end + end + + return love.graphics.newImage(imgNormalData) +end + +function normal_map.generateGradient(img, horizontalGradient, verticalGradient) + horizontalGradient = horizontalGradient or "gradient" + verticalGradient = verticalGradient or horizontalGradient + + local imgData = img:getData() + local imgWidth, imgHeight = img:getWidth(), img:getHeight() + local imgNormalData = love.image.newImageData(imgWidth, imgHeight) + local dx = 255.0 / imgWidth + local dy = 255.0 / imgHeight + local nx + local ny + local nz + + for i = 0, imgWidth - 1 do + for k = 0, imgHeight - 1 do + local r, g, b, a = imgData:getPixel(i, k) + if a > 0 then + if horizontalGradient == "gradient" then + nx = i * dx + elseif horizontalGradient == "inverse" then + nx = 255 - i * dx + else + nx = 127 + end + + if verticalGradient == "gradient" then + ny = 127 - k * dy * 0.5 + nz = 255 - k * dy * 0.5 + elseif verticalGradient == "inverse" then + ny = 127 + k * dy * 0.5 + nz = 127 - k * dy * 0.25 + else + ny = 255 + nz = 127 + end + + imgNormalData:setPixel(i, k, nx, ny, nz, 255) + end + end + end + + return love.graphics.newImage(imgNormalData) +end + +return normal_map diff --git a/src/lib/postshader.lua b/src/lib/postshader.lua new file mode 100644 index 0000000..7db59a4 --- /dev/null +++ b/src/lib/postshader.lua @@ -0,0 +1,183 @@ +--[[ +The MIT License (MIT) + +Copyright (c) 2014 Marcus Ihde + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] +local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or "" +local class = require(_PACKAGE..'/class') +local util = require(_PACKAGE..'/util') + +local post_shader = class() +post_shader.blurv = love.graphics.newShader(_PACKAGE.."/shaders/blurv.glsl") +post_shader.blurh = love.graphics.newShader(_PACKAGE.."/shaders/blurh.glsl") +post_shader.contrast = love.graphics.newShader(_PACKAGE.."/shaders/postshaders/contrast.glsl") +post_shader.tilt_shift = love.graphics.newShader(_PACKAGE.."/shaders/postshaders/tilt_shift.glsl") + +local files = love.filesystem.getDirectoryItems(_PACKAGE.."/shaders/postshaders") +local shaders = {} + +for i,v in ipairs(files) do + local name = _PACKAGE.."/shaders/postshaders".."/"..v + if love.filesystem.isFile(name) then + local str = love.filesystem.read(name) + local effect = love.graphics.newShader(name) + local defs = {} + for vtype, extern in str:gmatch("extern (%w+) (%w+)") do + defs[extern] = true + end + local shaderName = name:match(".-([^\\|/]-[^%.]+)$"):gsub("%.glsl", "") + shaders[shaderName] = {effect, defs} + end +end + +function post_shader:init() + self:refreshScreenSize() + self.effects = {} +end + +function post_shader:refreshScreenSize(w, h) + w, h = w or love.window.getWidth(), h or love.window.getHeight() + + self.render_buffer = love.graphics.newCanvas(w, h) + self.back_buffer = love.graphics.newCanvas(w, h) + + post_shader.blurv:send("screen", {w, h}) + post_shader.blurh:send("screen", {w, h}) + for shaderName, v in pairs(shaders) do + for def in pairs(v[2]) do + if def == "screen" or def == "textureSize" or def == "inputSize" or def == "outputSize" then + v[1]:send(def, {w, h}) + end + end + end + + self.w = w + self.h = h +end + +function post_shader:addEffect(shaderName, ...) + self.effects[shaderName] = {...} +end + +function post_shader:removeEffect(shaderName) + self.effects[shaderName] = nil +end + +function post_shader:toggleEffect(shaderName, ...) + if self.effects[shaderName] ~= nil then + self:removeEffect(shaderName) + else + self:addEffect(shaderName, ...) + end +end + +function post_shader:drawWith(canvas) + for shader, args in pairs(self.effects) do + if shader == "bloom" then + self:drawBloom(canvas, args) + elseif shader == "blur" then + self:drawBlur(canvas, args) + elseif shader == "tilt_shift" then + self:drawTiltShift(canvas, args) + else + self:drawShader(shader, canvas, args) + end + end + util.drawCanvasToCanvas(canvas) +end + +function post_shader:drawBloom(canvas, args) + post_shader.blurv:send("steps", args[1] or 2.0) + post_shader.blurh:send("steps", args[1] or 2.0) + util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = post_shader.blurv}) + util.drawCanvasToCanvas(self.back_buffer, self.back_buffer, {shader = post_shader.blurh}) + util.drawCanvasToCanvas(self.back_buffer, self.back_buffer, {shader = post_shader.contrast}) + util.drawCanvasToCanvas(canvas, canvas, {shader = post_shader.contrast}) + util.drawCanvasToCanvas(self.back_buffer, canvas, {blendmode = "additive", color = {255, 255, 255, (args[2] or 0.25) * 255}}) +end + +function post_shader:drawBlur(canvas, args) + post_shader.blurv:send("steps", args[1] or 2.0) + post_shader.blurh:send("steps", args[2] or 2.0) + util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = post_shader.blurv}) + util.drawCanvasToCanvas(self.back_buffer, self.back_buffer, {shader = post_shader.blurh}) + util.drawCanvasToCanvas(self.back_buffer, canvas) +end + +function post_shader:drawTiltShift(canvas, args) + post_shader.blurv:send("steps", args[1] or 2.0) + post_shader.blurh:send("steps", args[2] or 2.0) + util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = post_shader.blurv}) + util.drawCanvasToCanvas(self.back_buffer, self.back_buffer, {shader = post_shader.blurh}) + post_shader.tilt_shift:send("imgBuffer", canvas) + util.drawCanvasToCanvas(self.back_buffer, canvas, {shader = post_shader.tilt_shift}) +end + +function post_shader:drawShader(shaderName, canvas, args) + local w, h = love.graphics.getWidth(), love.graphics.getHeight() + local current_arg = 1 + + local effect = shaders[shaderName] + if effect == nil then + print("no shader called "..shaderName) + return + end + for def in pairs(effect[2]) do + if def == "time" then + effect[1]:send("time", love.timer.getTime()) + elseif def == "palette" then + effect[1]:send("palette", unpack(process_palette({ + args[current_arg], + args[current_arg + 1], + args[current_arg + 2], + args[current_arg + 3] + }))) + current_arg = current_arg + 4 + elseif def == "tint" then + effect[1]:send("tint", {process_tint(args[1], args[2], args[3])}) + current_arg = current_arg + 3 + elseif def == "imgBuffer" then + effect[1]:send("imgBuffer", canvas) + elseif def ~= "screen" and def ~= "textureSize" and def ~= "inputSize" and def ~= "outputSize" then + local value = args[current_arg] + if value ~= nil then + effect[1]:send(def, value) + end + current_arg = current_arg + 1 + end + end + + util.drawCanvasToCanvas(canvas, self.back_buffer, {shader = effect[1]}) + util.drawCanvasToCanvas(self.back_buffer, canvas) +end + +function process_tint(r, g, b) + return (r and r/255.0 or 1.0), (g and g/255.0 or 1.0), (b and b/255.0 or 1.0) +end + +function process_palette(palette) + for i = 1, #palette do + palette[i] = {process_tint(unpack(palette[i]))} + end + return palette +end + +return post_shader diff --git a/src/lib/shaders/blurh.glsl b/src/lib/shaders/blurh.glsl new file mode 100644 index 0000000..747f383 --- /dev/null +++ b/src/lib/shaders/blurh.glsl @@ -0,0 +1,13 @@ +extern vec2 screen = vec2(800.0, 600.0); +extern float steps = 2.0; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec2 pSize = vec2(1.0 / screen.x, 1.0 / screen.y); + vec4 col = Texel(texture, texture_coords); + for(int i = 1; i <= steps; i++) { + col = col + Texel(texture, vec2(texture_coords.x, texture_coords.y - pSize.y * i)); + col = col + Texel(texture, vec2(texture_coords.x, texture_coords.y + pSize.y * i)); + } + col = col / (steps * 2.0 + 1.0); + return vec4(col.r, col.g, col.b, 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/blurv.glsl b/src/lib/shaders/blurv.glsl new file mode 100644 index 0000000..f92b51a --- /dev/null +++ b/src/lib/shaders/blurv.glsl @@ -0,0 +1,13 @@ +extern vec2 screen = vec2(800.0, 600.0); +extern float steps = 2.0; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec2 pSize = vec2(1.0 / screen.x, 1.0 / screen.y); + vec4 col = Texel(texture, texture_coords); + for(int i = 1; i <= steps; i++) { + col = col + Texel(texture, vec2(texture_coords.x - pSize.x * i, texture_coords.y)); + col = col + Texel(texture, vec2(texture_coords.x + pSize.x * i, texture_coords.y)); + } + col = col / (steps * 2.0 + 1.0); + return vec4(col.r, col.g, col.b, 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/glow.glsl b/src/lib/shaders/glow.glsl new file mode 100644 index 0000000..2b5d456 --- /dev/null +++ b/src/lib/shaders/glow.glsl @@ -0,0 +1,20 @@ +extern Image glowImage; + +extern float glowTime; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec3 glowInfo = Texel(glowImage, texture_coords).rgb; + + if(glowInfo.r != glowInfo.g) { + float glowStrength = glowTime + glowInfo.b; + if(mod(glowStrength, 2.0) < 1.0) { + glowInfo.b = mod(glowStrength, 1.0); + } else { + glowInfo.b = 1.0 - mod(glowStrength, 1.0); + } + + return Texel(texture, texture_coords) * (glowInfo.g + glowInfo.b * (glowInfo.r - glowInfo.g)); + } + + return vec4(Texel(texture, texture_coords).rgb * glowInfo.r, 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/material.glsl b/src/lib/shaders/material.glsl new file mode 100644 index 0000000..ea8f8c1 --- /dev/null +++ b/src/lib/shaders/material.glsl @@ -0,0 +1,10 @@ +extern Image material; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec4 normal = Texel(texture, texture_coords); + if(normal.a == 1.0) { + return Texel(material, vec2(normal.x, normal.y)); + } else { + return vec4(0.0); + } +} \ No newline at end of file diff --git a/src/lib/shaders/normal.glsl b/src/lib/shaders/normal.glsl new file mode 100644 index 0000000..987b6e0 --- /dev/null +++ b/src/lib/shaders/normal.glsl @@ -0,0 +1,50 @@ +#define PI 3.1415926535897932384626433832795 + +extern vec2 screenResolution; +extern vec3 lightPosition; +extern vec3 lightColor; +extern float lightRange; +extern float lightSmooth; +extern float lightDirection; +extern float lightAngle; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec4 pixelColor = Texel(texture, texture_coords); + + if(pixelColor.a > 0.0) { + if(lightAngle > 0.0) { + float angle2 = atan(lightPosition.x - pixel_coords.x, pixel_coords.y - lightPosition.y) + PI; + if(lightDirection - lightAngle > 0 && lightDirection + lightAngle < PI * 2) { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) && angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } else { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) || angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } + } + + vec3 normal = pixelColor.rgb; + float dist = distance(lightPosition, vec3(pixel_coords, normal.b)); + + if(dist < lightRange) { + vec3 dir = vec3((lightPosition.xy - pixel_coords.xy) / screenResolution.xy, lightPosition.z); + + dir.x *= screenResolution.x / screenResolution.y; + + vec3 N = normalize(normal * 2.0 - 1.0); + vec3 L = normalize(dir); + + vec3 diff = lightColor * max(dot(N, L), 0.0); + + float att = clamp((1.0 - dist / lightRange) / lightSmooth, 0.0, 1.0); + + return vec4(diff * att, 1.0); + } else { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } else { + return vec4(0.0); + } +} \ No newline at end of file diff --git a/src/lib/shaders/normal_invert.glsl b/src/lib/shaders/normal_invert.glsl new file mode 100644 index 0000000..f73185c --- /dev/null +++ b/src/lib/shaders/normal_invert.glsl @@ -0,0 +1,50 @@ +#define PI 3.1415926535897932384626433832795 + +extern vec2 screenResolution; +extern vec3 lightPosition; +extern vec3 lightColor; +extern float lightRange; +extern float lightSmooth; +extern float lightDirection; +extern float lightAngle; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec4 pixelColor = Texel(texture, texture_coords); + + if(pixelColor.a > 0.0) { + if(lightAngle > 0.0) { + float angle2 = atan(lightPosition.x - pixel_coords.x, pixel_coords.y - lightPosition.y) + PI; + if(lightDirection - lightAngle > 0 && lightDirection + lightAngle < PI * 2) { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) && angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } else { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) || angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } + } + + vec3 normal = vec3(pixelColor.r, 1 - pixelColor.g, pixelColor.b); + float dist = distance(lightPosition, vec3(pixel_coords, normal.b)); + + if(dist < lightRange) { + vec3 dir = vec3((lightPosition.xy - pixel_coords.xy) / screenResolution.xy, lightPosition.z); + + dir.x *= screenResolution.x / screenResolution.y; + + vec3 N = normalize(normal * 2.0 - 1.0); + vec3 L = normalize(dir); + + vec3 diff = lightColor * max(dot(N, L), 0.0); + + float att = clamp((1.0 - dist / lightRange) / lightSmooth, 0.0, 1.0); + + return vec4(diff * att, 1.0); + } else { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } else { + return vec4(0.0); + } +} \ No newline at end of file diff --git a/src/lib/shaders/poly_shadow.glsl b/src/lib/shaders/poly_shadow.glsl new file mode 100644 index 0000000..dde39e4 --- /dev/null +++ b/src/lib/shaders/poly_shadow.glsl @@ -0,0 +1,41 @@ +#define PI 3.1415926535897932384626433832795 + +extern vec3 lightPosition; +extern vec3 lightColor; +extern float lightRange; +extern float lightSmooth; +extern vec2 lightGlow; +extern float lightDirection; +extern float lightAngle; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){ + vec4 pixel = Texel(texture, texture_coords); + vec3 lightToPixel = vec3(pixel_coords.x, pixel_coords.y, 0.0) - lightPosition; + float distance = length(lightToPixel); + float att = 1 - distance / lightRange; + + if(lightAngle > 0.0) { + float angle2 = atan(lightPosition.x - pixel_coords.x, pixel_coords.y - lightPosition.y) + PI; + if(lightDirection - lightAngle > 0 && lightDirection + lightAngle < PI * 2) { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) && angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } else { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) || angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } + } + + if (distance <= lightRange) { + if (lightGlow.x < 1.0 && lightGlow.y > 0.0) { + pixel.rgb = clamp(lightColor * pow(att, lightSmooth) + pow(smoothstep(lightGlow.x, 1.0, att), lightSmooth) * lightGlow.y, 0.0, 1.0); + } else { + pixel.rgb = lightColor * pow(att, lightSmooth); + } + } else { + return vec4(0.0, 0.0, 0.0, 1.0); + } + + return pixel; +} diff --git a/src/lib/shaders/postshaders/black_and_white.glsl b/src/lib/shaders/postshaders/black_and_white.glsl new file mode 100644 index 0000000..57093f6 --- /dev/null +++ b/src/lib/shaders/postshaders/black_and_white.glsl @@ -0,0 +1,24 @@ +extern float exposure = 0.7; +extern float brightness = 1.0; +extern vec3 lumacomponents = vec3(1.0, 1.0, 1.0); + + +// luma +//const vec3 lumcoeff = vec3(0.299,0.587,0.114); +const vec3 lumcoeff = vec3(0.212671, 0.715160, 0.072169); + +vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixel_coords) +{ + vec4 input0 = Texel(texture, texcoord); + + //exposure knee + input0 *= (exp2(input0)*vec4(exposure)); + + vec4 lumacomponents = vec4(lumcoeff * lumacomponents, 0.0 ); + + float luminance = dot(input0,lumacomponents); + + vec4 luma = vec4(luminance); + + return vec4(luma.rgb * brightness, 1.0); +} diff --git a/src/lib/shaders/postshaders/chromatic_aberration.glsl b/src/lib/shaders/postshaders/chromatic_aberration.glsl new file mode 100644 index 0000000..6d6909c --- /dev/null +++ b/src/lib/shaders/postshaders/chromatic_aberration.glsl @@ -0,0 +1,13 @@ +extern vec2 screen = vec2(800.0, 600.0); +extern vec2 redStrength = vec2(4.0, 3.0); +extern vec2 greenStrength = vec2(-2.0, -1.0); +extern vec2 blueStrength = vec2(1.0, -3.0); + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec2 pSize = vec2(1.0 / screen.x, 1.0 / screen.y); + float colRed = Texel(texture, vec2(texture_coords.x + pSize.x * redStrength.x, texture_coords.y - pSize.y * redStrength.y)).r; + float colGreen = Texel(texture, vec2(texture_coords.x + pSize.x * greenStrength.x, texture_coords.y - pSize.y * greenStrength.y)).g; + float colBlue = Texel(texture, vec2(texture_coords.x + pSize.x * blueStrength.x, texture_coords.y - pSize.y * blueStrength.y)).b; + + return vec4(colRed, colGreen, colBlue, 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/postshaders/contrast.glsl b/src/lib/shaders/postshaders/contrast.glsl new file mode 100644 index 0000000..066a794 --- /dev/null +++ b/src/lib/shaders/postshaders/contrast.glsl @@ -0,0 +1,6 @@ +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) +{ + vec3 col = Texel(texture, texture_coords).rgb * 2.0; + col *= col; + return vec4(col, 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/postshaders/curvature.glsl b/src/lib/shaders/postshaders/curvature.glsl new file mode 100644 index 0000000..8f9a942 --- /dev/null +++ b/src/lib/shaders/postshaders/curvature.glsl @@ -0,0 +1,88 @@ +extern vec2 inputSize; +extern vec2 textureSize; + + +#define distortion 0.2 + +/* +#define f 0.6 +#define ox 0.5 +#define oy 0.5 +#define scale 0.8 +#define k1 0.7 +#define k2 -0.5 + +vec2 barrelDistort(vec2 coord) +{ + vec2 xy = (coord - vec2(ox, oy))/vec2(f) * scale; + + vec2 r = vec2(sqrt(dot(xy, xy))); + + float r2 = float(r*r); + + float r4 = r2*r2; + + float coeff = (k1*r2 + k2*r4); + + return ((xy+xy*coeff) * f) + vec2(ox, oy); +} +*/ +vec2 radialDistortion(vec2 coord, const vec2 ratio) +{ + float offsety = 1.0 - ratio.y; + coord.y -= offsety; + coord /= ratio; + + vec2 cc = coord - 0.5; + float dist = dot(cc, cc) * distortion; + vec2 result = coord + cc * (1.0 + dist) * dist; + + result *= ratio; + result.y += offsety; + + return result; +} +/* +vec4 checkTexelBounds(Image texture, vec2 coords, vec2 bounds) +{ + vec4 color = Texel(texture, coords) * + + vec2 ss = step(coords, vec2(bounds.x, 1.0)) * step(vec2(0.0, bounds.y), coords); + + color.rgb *= ss.x * ss.y; + color.a = step(color.a, ss.x * ss.y); + + return color; +}*/ + +vec4 checkTexelBounds(Image texture, vec2 coords, vec2 bounds) +{ + vec2 ss = step(coords, vec2(bounds.x, 1.0)) * step(vec2(0.0, bounds.y), coords); + return Texel(texture, coords) * ss.x * ss.y; +} + +/* +vec4 checkTexelBounds(Image texture, vec2 coords) +{ + vec2 bounds = vec2(inputSize.x / textureSize.x, 1.0 - inputSize.y / textureSize.y); + + vec4 color; + if (coords.x > bounds.x || coords.x < 0.0 || coords.y > 1.0 || coords.y < bounds.y) + color = vec4(0.0, 0.0, 0.0, 1.0); + else + color = Texel(texture, coords); + + return color; +} +*/ + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) +{ + vec2 coords = radialDistortion(texture_coords, inputSize / textureSize); + + vec4 texcolor = checkTexelBounds(texture, coords, vec2(inputSize.x / textureSize.x, 1.0 - inputSize.y / textureSize.y)); + texcolor.a = 1.0; + + return texcolor; +} + diff --git a/src/lib/shaders/postshaders/edges.glsl b/src/lib/shaders/postshaders/edges.glsl new file mode 100644 index 0000000..3fcf360 --- /dev/null +++ b/src/lib/shaders/postshaders/edges.glsl @@ -0,0 +1,42 @@ +/* + Edge shader + Author: Themaister + License: Public domain. + + modified by slime73 for use with love2d and mari0 +*/ + + +extern vec2 textureSize; + +vec3 grayscale(vec3 color) +{ + return vec3(dot(color, vec3(0.3, 0.59, 0.11))); +} + +vec4 effect(vec4 vcolor, Image texture, vec2 tex, vec2 pixel_coords) +{ + vec4 texcolor = Texel(texture, tex); + + float x = 0.5 / textureSize.x; + float y = 0.5 / textureSize.y; + vec2 dg1 = vec2( x, y); + vec2 dg2 = vec2(-x, y); + + vec3 c00 = Texel(texture, tex - dg1).xyz; + vec3 c02 = Texel(texture, tex + dg2).xyz; + vec3 c11 = texcolor.xyz; + vec3 c20 = Texel(texture, tex - dg2).xyz; + vec3 c22 = Texel(texture, tex + dg1).xyz; + + vec2 texsize = textureSize; + + vec3 first = mix(c00, c20, fract(tex.x * texsize.x + 0.5)); + vec3 second = mix(c02, c22, fract(tex.x * texsize.x + 0.5)); + + vec3 res = mix(first, second, fract(tex.y * texsize.y + 0.5)); + vec4 final = vec4(5.0 * grayscale(abs(res - c11)), 1.0); + return clamp(final, 0.0, 1.0); +} + + diff --git a/src/lib/shaders/postshaders/four_colors.glsl b/src/lib/shaders/postshaders/four_colors.glsl new file mode 100644 index 0000000..c68b1cf --- /dev/null +++ b/src/lib/shaders/postshaders/four_colors.glsl @@ -0,0 +1,8 @@ +extern vec3 palette[4]; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){ + vec4 pixel = Texel(texture, texture_coords); + int index = int(min(0.9999, max(0.0001,(pixel.r + pixel.g + pixel.b) / 3.0)) * 4); + + return vec4(palette[index], 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/postshaders/hdr_tv.glsl b/src/lib/shaders/postshaders/hdr_tv.glsl new file mode 100644 index 0000000..0bfcc90 --- /dev/null +++ b/src/lib/shaders/postshaders/hdr_tv.glsl @@ -0,0 +1,9 @@ +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) +{ + vec4 rgb = Texel(texture, texture_coords); + vec4 intens = smoothstep(0.2,0.8,rgb) + normalize(vec4(rgb.xyz, 1.0)); + + if (fract(pixel_coords.y * 0.5) > 0.5) intens = rgb * 0.8; + intens.a = 1.0; + return intens; +} diff --git a/src/lib/shaders/postshaders/monochrome.glsl b/src/lib/shaders/postshaders/monochrome.glsl new file mode 100644 index 0000000..d4bc55b --- /dev/null +++ b/src/lib/shaders/postshaders/monochrome.glsl @@ -0,0 +1,13 @@ +extern float time = 0.0; +extern vec3 tint = vec3(1.0, 1.0, 1.0); +extern float fudge = 0.1; + +float rand(vec2 position, float seed) { + return fract(sin(dot(position.xy,vec2(12.9898, 78.233))) * seed); +} + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){ + vec4 pixel = Texel(texture, texture_coords); + float intensity = (pixel.r + pixel.g + pixel.b) / 3.0 + (rand(texture_coords, time) - 0.5) * fudge; + return vec4(intensity * tint.r, intensity * tint.g, intensity * tint.b, 1.0); +} \ No newline at end of file diff --git a/src/lib/shaders/postshaders/phosphor.glsl b/src/lib/shaders/postshaders/phosphor.glsl new file mode 100644 index 0000000..f505d27 --- /dev/null +++ b/src/lib/shaders/postshaders/phosphor.glsl @@ -0,0 +1,159 @@ +/* + caligari's scanlines + + Copyright (C) 2011 caligari + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + (caligari gave their consent to have this shader distributed under the GPL + in this message: + + http://board.byuu.org/viewtopic.php?p=36219#p36219 + + "As I said to Hyllian by PM, I'm fine with the GPL (not really a bi + deal...)" + ) +*/ + +extern vec2 textureSize; + + +// 0.5 = the spot stays inside the original pixel +// 1.0 = the spot bleeds up to the center of next pixel +#define PHOSPHOR_WIDTH 0.9 +#define PHOSPHOR_HEIGHT 0.65 + +// Used to counteract the desaturation effect of weighting. +#define COLOR_BOOST 1.9 + +// Constants used with gamma correction. +#define InputGamma 2.4 +#define OutputGamma 2.2 + +// Uncomment to only draw every third pixel, which highlights the shape +// of individual (remaining) spots. +// #define DEBUG + +// Uncomment one of these to choose a gamma correction method. +// If none are uncommented, no gamma correction is done. +// #define REAL_GAMMA +#define FAKE_GAMMA +// #define FAKER_GAMMA + +#ifdef REAL_GAMMA +#define GAMMA_IN(color) pow(color, vec4(InputGamma)) +#define GAMMA_OUT(color) pow(color, vec4(1.0 / OutputGamma)) + +#elif defined FAKE_GAMMA +/* + * Approximations: + * for 1 PHOSPHOR_WIDTH, + * this pixel doesn't contribute + * otherwise, smoothstep gives the + * weight of the contribution + */ + hweight = smoothstep( + 1.0, 0.0, + abs((posx + centers - vec3(i)) + / vec3(PHOSPHOR_WIDTH)) + ); + color.rgb += + pixel.rgb * + hweight * + vec3(vweight); + } + } + } + + color *= vec4(COLOR_BOOST); + color.a = 1.0; + + return clamp(GAMMA_OUT(color), 0.0, 1.0); +} diff --git a/src/lib/shaders/postshaders/phosphorish.glsl b/src/lib/shaders/postshaders/phosphorish.glsl new file mode 100644 index 0000000..719c5cb --- /dev/null +++ b/src/lib/shaders/postshaders/phosphorish.glsl @@ -0,0 +1,48 @@ +/* + Plain (and obviously inaccurate) phosphor. + Author: Themaister + License: Public Domain +*/ +// modified by slime73 for use with love pixeleffects + + +extern vec2 textureSize; + +vec3 to_focus(float pixel) +{ + pixel = mod(pixel + 3.0, 3.0); + if (pixel >= 2.0) // Blue + return vec3(pixel - 2.0, 0.0, 3.0 - pixel); + else if (pixel >= 1.0) // Green + return vec3(0.0, 2.0 - pixel, pixel - 1.0); + else // Red + return vec3(1.0 - pixel, pixel, 0.0); +} + +vec4 effect(vec4 vcolor, Image texture, vec2 texture_coords, vec2 pixel_coords) +{ + float y = mod(texture_coords.y * textureSize.y, 1.0); + float intensity = exp(-0.2 * y); + + vec2 one_x = vec2(1.0 / (3.0 * textureSize.x), 0.0); + + vec3 color = Texel(texture, texture_coords - 0.0 * one_x).rgb; + vec3 color_prev = Texel(texture, texture_coords - 1.0 * one_x).rgb; + vec3 color_prev_prev = Texel(texture, texture_coords - 2.0 * one_x).rgb; + + float pixel_x = 3.0 * texture_coords.x * textureSize.x; + + vec3 focus = to_focus(pixel_x - 0.0); + vec3 focus_prev = to_focus(pixel_x - 1.0); + vec3 focus_prev_prev = to_focus(pixel_x - 2.0); + + vec3 result = + 0.8 * color * focus + + 0.6 * color_prev * focus_prev + + 0.3 * color_prev_prev * focus_prev_prev; + + result = 2.3 * pow(result, vec3(1.4)); + + return vec4(intensity * result, 1.0); +} + diff --git a/src/lib/shaders/postshaders/pip.glsl b/src/lib/shaders/postshaders/pip.glsl new file mode 100644 index 0000000..42ab20b --- /dev/null +++ b/src/lib/shaders/postshaders/pip.glsl @@ -0,0 +1,66 @@ +#define glarebasesize 0.896 +#define power 0.50 + +extern vec2 textureSize; +extern vec2 outputSize; + +extern float time; + +const vec3 green = vec3(0.17, 0.62, 0.25); + +float luminance(vec3 color) +{ + return (0.212671 * color.r) + (0.715160 * color.g) + (0.072169 * color.b); +} + +float scanline(float ypos) +{ + + float c = mod(time * 3.0 + ypos * 5.0, 15.0); + return 1.0 - smoothstep(0.0, 1.0, c); +} + +vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixel_coords) +{ + vec4 texcolor = Texel(texture, texcoord); + + vec4 sum = vec4(0.0); + vec4 bum = vec4(0.0); + + vec2 glaresize = vec2(glarebasesize) / textureSize; + + float y_one = 1.0 / outputSize.y; + + int j; + int i; + + for (i = -2; i < 2; i++) + { + for (j = -1; j < 1; j++) + { + sum += Texel(texture, texcoord + vec2(-i, j)*glaresize) * power; + bum += Texel(texture, texcoord + vec2(j, i)*glaresize) * power; + } + } + + float a = (scanline(texcoord.y) + scanline(texcoord.y + y_one * 1.5) + scanline(texcoord.y - y_one * 1.5)) / 3.0; + + vec4 finalcolor; + + if (texcolor.r < 2.0) + { + finalcolor = sum*sum*sum*0.001+bum*bum*bum*0.0080 * (0.8 + 0.05 * a) + texcolor; + } + else + { + finalcolor = vec4(0.0, 0.0, 0.0, 1.0); + } + + float lum = pow(luminance(finalcolor.rgb), 1.4); + + finalcolor.rgb = lum * green + (a * 0.03); + finalcolor.a = 1.0; + + return finalcolor; +} + diff --git a/src/lib/shaders/postshaders/pixellate.glsl b/src/lib/shaders/postshaders/pixellate.glsl new file mode 100644 index 0000000..40f78e0 --- /dev/null +++ b/src/lib/shaders/postshaders/pixellate.glsl @@ -0,0 +1,13 @@ + +extern vec2 textureSize; + +const float pixel_w = 2.0; +const float pixel_h = 2.0; + +vec4 effect(vec4 vcolor, Image texture, vec2 uv, vec2 pixel_coords) +{ + float dx = pixel_w*(1.0/textureSize.x); + float dy = pixel_h*(1.0/textureSize.y); + vec2 coord = vec2(dx*floor(uv.x/dx), dy*floor(uv.y/dy)); + return Texel(texture, coord); +} diff --git a/src/lib/shaders/postshaders/radialblur.glsl b/src/lib/shaders/postshaders/radialblur.glsl new file mode 100644 index 0000000..d47766e --- /dev/null +++ b/src/lib/shaders/postshaders/radialblur.glsl @@ -0,0 +1,21 @@ +#define nsamples 5 + +extern number blurstart = 1.0; // 0 to 1 +extern number blurwidth = -0.02; // -1 to 1 + + +vec4 effect(vec4 vcolor, Image texture, vec2 texture_coords, vec2 pixel_coords) +{ + vec4 c = vec4(0.0, 0.0, 0.0, 1.0); + + int i; + for (i = 0; i < nsamples; i++) + { + number scale = blurstart + blurwidth * (i / float(nsamples-1)); + c.rgb += Texel(texture, texture_coords * scale).rgb; + } + + c.rgb /= nsamples; + + return c; +} diff --git a/src/lib/shaders/postshaders/scanlines.glsl b/src/lib/shaders/postshaders/scanlines.glsl new file mode 100644 index 0000000..2e4582a --- /dev/null +++ b/src/lib/shaders/postshaders/scanlines.glsl @@ -0,0 +1,33 @@ +extern vec2 screen = vec2(800.0, 600.0); +extern float strength = 2.0; +extern float time = 0.0; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){ + vec2 pSize = 1.0 / screen; + float brightness = 1.0; + float offsetX = sin(texture_coords.y * 10.0 + time * strength) * pSize.x; + float corner = 500.0; + + if(texture_coords.x < 0.5) { + if(texture_coords.y < 0.5) { + brightness = min(texture_coords.x * texture_coords.y * corner, 1.0); + } else { + brightness = min(texture_coords.x * (1.0 - texture_coords.y) * corner, 1.0); + } + } else { + if(texture_coords.y < 0.5) { + brightness = min((1.0 - texture_coords.x) * texture_coords.y * corner, 1.0); + } else { + brightness = min((1.0 - texture_coords.x) * (1.0 - texture_coords.y) * corner, 1.0); + } + } + float red = Texel(texture, vec2(texture_coords.x + offsetX, texture_coords.y + pSize.y * 0.5)).r; + float green = Texel(texture, vec2(texture_coords.x + offsetX, texture_coords.y - pSize.y * 0.5)).g; + float blue = Texel(texture, vec2(texture_coords.x + offsetX, texture_coords.y)).b; + + if(fract(gl_FragCoord.y * (0.5*4.0/3.0)) > 0.5) { + return vec4(vec3(red, green, blue) * brightness, 1.0); + } else { + return vec4(vec3(red * 0.75, green * 0.75, blue * 0.75) * brightness, 1.0); + } +} diff --git a/src/lib/shaders/postshaders/tilt_shift.glsl b/src/lib/shaders/postshaders/tilt_shift.glsl new file mode 100644 index 0000000..911878c --- /dev/null +++ b/src/lib/shaders/postshaders/tilt_shift.glsl @@ -0,0 +1,12 @@ +extern Image imgBuffer; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){ + vec4 pixel = Texel(texture, texture_coords); + vec4 pixelBuffer = Texel(imgBuffer, texture_coords); + + if(texture_coords.y > 0.5) { + return vec4(pixel.rgb * (texture_coords.y - 0.5) * 2.0 + pixelBuffer.rgb * (1.0 - texture_coords.y) * 2.0, 1.0); + } else { + return vec4(pixel.rgb * (0.5 - texture_coords.y) * 2.0 + pixelBuffer.rgb * texture_coords.y * 2.0, 1.0); + } +} diff --git a/src/lib/shaders/postshaders/waterpaint.glsl b/src/lib/shaders/postshaders/waterpaint.glsl new file mode 100644 index 0000000..5461eb8 --- /dev/null +++ b/src/lib/shaders/postshaders/waterpaint.glsl @@ -0,0 +1,57 @@ +/* + Themaister's Waterpaint shader + + Placed in the public domain. + + (From this thread: http://board.byuu.org/viewtopic.php?p=30483#p30483 + PD declaration here: http://board.byuu.org/viewtopic.php?p=30542#p30542 ) + + modified by slime73 for use with love2d and mari0 +*/ + + +vec4 compress(vec4 in_color, float threshold, float ratio) +{ + vec4 diff = in_color - vec4(threshold); + diff = clamp(diff, 0.0, 100.0); + return in_color - (diff * (1.0 - 1.0/ratio)); +} + +extern vec2 textureSize; + +vec4 effect(vec4 vcolor, Image texture, vec2 tex, vec2 pixel_coords) +{ + float x = 0.5 * (1.0 / textureSize.x); + float y = 0.5 * (1.0 / textureSize.y); + + vec2 dg1 = vec2( x, y); + vec2 dg2 = vec2(-x, y); + vec2 dx = vec2(x, 0.0); + vec2 dy = vec2(0.0, y); + + vec3 c00 = Texel(texture, tex - dg1).xyz; + vec3 c01 = Texel(texture, tex - dx).xyz; + vec3 c02 = Texel(texture, tex + dg2).xyz; + vec3 c10 = Texel(texture, tex - dy).xyz; + vec3 c11 = Texel(texture, tex).xyz; + vec3 c12 = Texel(texture, tex + dy).xyz; + vec3 c20 = Texel(texture, tex - dg2).xyz; + vec3 c21 = Texel(texture, tex + dx).xyz; + vec3 c22 = Texel(texture, tex + dg1).xyz; + + vec2 texsize = textureSize; + + vec3 first = mix(c00, c20, fract(tex.x * texsize.x + 0.5)); + vec3 second = mix(c02, c22, fract(tex.x * texsize.x + 0.5)); + + vec3 mid_horiz = mix(c01, c21, fract(tex.x * texsize.x + 0.5)); + vec3 mid_vert = mix(c10, c12, fract(tex.y * texsize.y + 0.5)); + + vec3 res = mix(first, second, fract(tex.y * texsize.y + 0.5)); + vec4 final = vec4(0.26 * (res + mid_horiz + mid_vert) + 3.5 * abs(res - mix(mid_horiz, mid_vert, 0.5)), 1.0); + + final = compress(final, 0.8, 5.0); + final.a = 1.0; + + return final; +} diff --git a/src/lib/shaders/reflection.glsl b/src/lib/shaders/reflection.glsl new file mode 100644 index 0000000..edf662f --- /dev/null +++ b/src/lib/shaders/reflection.glsl @@ -0,0 +1,24 @@ +extern Image backBuffer; + +extern vec2 screen = vec2(800.0, 600.0); +extern float reflectionStrength; +extern float reflectionVisibility; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec2 pSize = vec2(1.0 / screen.x, 1.0 / screen.y); + vec4 normal = Texel(texture, texture_coords); + if(normal.a > 0.0 && normal.r > 0.0) { + vec3 pColor = Texel(backBuffer, texture_coords).rgb; + vec4 pColor2; + for(int i = 0; i < reflectionStrength; i++) { + pColor2 = Texel(texture, vec2(texture_coords.x, texture_coords.y + pSize.y * i)); + if(pColor2.a > 0.0 && pColor2.g > 0.0) { + vec3 rColor = Texel(backBuffer, vec2(texture_coords.x, texture_coords.y + pSize.y * i * 2.0)).rgb; + return vec4(rColor, (1.0 - i / reflectionStrength) * reflectionVisibility); + } + } + return vec4(0.0); + } else { + return vec4(0.0); + } +} \ No newline at end of file diff --git a/src/lib/shaders/refraction.glsl b/src/lib/shaders/refraction.glsl new file mode 100644 index 0000000..d9b3d23 --- /dev/null +++ b/src/lib/shaders/refraction.glsl @@ -0,0 +1,19 @@ +extern Image backBuffer; + +extern vec2 screen = vec2(800.0, 600.0); +extern float refractionStrength = 1.0; + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec2 pSize = vec2(1.0 / screen.x, 1.0 / screen.y); + vec4 normal = Texel(texture, texture_coords); + if(normal.b > 0.0) { + vec4 normalOffset = Texel(texture, vec2(texture_coords.x + (normal.x - 0.5) * pSize.x * refractionStrength, texture_coords.y + (normal.y - 0.5) * pSize.y * refractionStrength)); + if(normalOffset.b > 0.0) { + return Texel(backBuffer, vec2(texture_coords.x + (normal.x - 0.5) * pSize.x * refractionStrength, texture_coords.y + (normal.y - 0.5) * pSize.y * refractionStrength)); + } else { + return Texel(backBuffer, texture_coords); + } + } else { + return vec4(0.0); + } +} \ No newline at end of file diff --git a/src/lib/stencils.lua b/src/lib/stencils.lua new file mode 100644 index 0000000..de293c6 --- /dev/null +++ b/src/lib/stencils.lua @@ -0,0 +1,33 @@ +local stencils = {} + +function stencils.shadow(geometry, bodies) + return function() + --cast shadows + for i = 1,#geometry do + if geometry[i].alpha == 1.0 then + love.graphics.polygon("fill", unpack(geometry[i])) + end + end + -- underneath shadows + for i = 1, #bodies do + if not bodies[i].castsNoShadow then + bodies[i]:stencil() + end + end + end +end + +function stencils.shine(bodies) + return function() + for i = 1, #bodies do + if bodies[i].shine and + (bodies[i].glowStrength == 0.0 or + (bodies[i].type == "image" and not bodies[i].normal)) + then + bodies[i]:stencil() + end + end + end +end + +return stencils diff --git a/src/lib/util.lua b/src/lib/util.lua new file mode 100644 index 0000000..79f6595 --- /dev/null +++ b/src/lib/util.lua @@ -0,0 +1,43 @@ +local util = {} + +function util.drawCanvasToCanvas(canvas, other_canvas, options) + options = options or {} + + local last_buffer = love.graphics.getCanvas() + love.graphics.push() + love.graphics.origin() + love.graphics.setCanvas(other_canvas) + if options["blendmode"] then + love.graphics.setBlendMode(options["blendmode"]) + end + if options["shader"] then + love.graphics.setShader(options["shader"]) + end + if options["color"] then + love.graphics.setColor(unpack(options["color"])) + end + love.graphics.setColor(255,255,255) + love.graphics.draw(canvas,0,0) + love.graphics.setCanvas(last_buffer) + if options["blendmode"] then + love.graphics.setBlendMode("alpha") + end + if options["shader"] then + love.graphics.setShader() + end + love.graphics.pop() +end + +function util.drawto(canvas, x, y, scale, cb) + local last_buffer = love.graphics.getCanvas() + love.graphics.push() + love.graphics.origin() + love.graphics.setCanvas(canvas) + love.graphics.translate(x, y) + love.graphics.scale(scale) + cb() + love.graphics.setCanvas(last_buffer) + love.graphics.pop() +end + +return util diff --git a/src/lib/vector.lua b/src/lib/vector.lua new file mode 100644 index 0000000..c88cc24 --- /dev/null +++ b/src/lib/vector.lua @@ -0,0 +1,21 @@ +local vector = {} +-- vector functions +function vector.normalize(v) + local len = math.sqrt(math.pow(v[1], 2) + math.pow(v[2], 2)) + local normalizedv = {v[1] / len, v[2] / len} + return normalizedv +end + +function vector.dot(v1, v2) + return v1[1] * v2[1] + v1[2] * v2[2] +end + +function vector.lengthSqr(v) + return v[1] * v[1] + v[2] * v[2] +end + +function vector.length(v) + return math.sqrt(lengthSqr(v)) +end + +return vector diff --git a/src/main.lua b/src/main.lua new file mode 100644 index 0000000..eb020ec --- /dev/null +++ b/src/main.lua @@ -0,0 +1,43 @@ +function drawBackground() + --love.graphics.setColor(48, 156, 225) + --love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight()) + + love.graphics.setColor(35, 65, 85) + love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight()) +end + +function drawForground() + love.graphics.setColor(255, 0, 0) --red + love.graphics.rectangle("fill", 10, 10, 17, 27) --x,y,width,height +end + +function love.load() + love.graphics.setBackgroundColor(35, 65, 85) + + local LightWorld = require "lib.light_world" + lightWorld = LightWorld({ + drawBackground = drawBackground, + drawForground = drawForground, + ambient = {55, 55, 55} + }) + + --box = lightWorld:newBox() ?? + light = lightWorld:newLight(love.graphics.getWidth() / 2, love.graphics.getHeight() / 2, 255, 150, 100, 600) -- I don't know what any of these values do + --thompson = lightWorld:newRectangle(100, 100, 255, 0, 0, 17, 27) + thompson = lightWorld:newRectangle(100, 100, 17, 27) + lightWorld:newRectangle(700, 300, 10, 20) + lightWorld:newRectangle(200,5,5,5) +end + +function love.update() + love.window.setTitle("Thompson Was a Clone (FPS:" .. love.timer.getFPS() .. ")") +end + +function love.draw() + lightWorld:draw(0, 0, 1) + --[[love.graphics.push() + love.graphics.translate(0, 0) + love.graphics.scale(1) + lightWorld:draw(0, 0, 1) + love.graphics.pop()]] +end