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 0000000..0befbb6 Binary files /dev/null and b/screenshots/something I am starting on.png differ 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