From 7db34a4a020c9346a973847b45ed384392bfb821 Mon Sep 17 00:00:00 2001 From: Tim Anema Date: Fri, 28 Nov 2014 10:20:00 -0500 Subject: [PATCH] made better shadow body calculations so that the z coordinate of the light effects the cast light --- examples/complex.lua | 2 +- examples/short.lua | 12 ++- lib/body.lua | 148 +++++++++++++++++---------------- lib/light.lua | 29 ++----- lib/shaders/normal.glsl | 10 ++- lib/shaders/normal_invert.glsl | 50 ----------- lib/stencils.lua | 3 + lib/vec2.lua | 27 ++++++ lib/vec3.lua | 30 +++++++ lib/vector.lua | 21 ----- 10 files changed, 166 insertions(+), 166 deletions(-) delete mode 100644 lib/shaders/normal_invert.glsl create mode 100644 lib/vec2.lua create mode 100644 lib/vec3.lua delete mode 100644 lib/vector.lua diff --git a/examples/complex.lua b/examples/complex.lua index c5e5b57..39839a6 100644 --- a/examples/complex.lua +++ b/examples/complex.lua @@ -134,7 +134,7 @@ function love.update(dt) mx, my = (love.mouse.getX() - offsetX)/scale, (love.mouse.getY() - offsetY)/scale - mouseLight:setPosition(mx, my, 16.0 + (math.sin(lightDirection) + 1.0) * 64.0) + mouseLight:setPosition(mx, my, 1 + (math.sin(lightDirection) + 1.0) * 64.0) lightDirection = lightDirection + dt colorAberration = math.max(0.0, colorAberration - dt * 10.0) diff --git a/examples/short.lua b/examples/short.lua index 342fcdf..ecb5436 100644 --- a/examples/short.lua +++ b/examples/short.lua @@ -5,6 +5,7 @@ function love.load() testShader = 0 x = 0 y = 0 + z = 1 scale = 1 colorAberration = 0.0 -- load images @@ -116,7 +117,15 @@ function love.update(dt) lightWorld.post_shader:removeEffect("chromatic_aberration") end - lightMouse:setPosition((love.mouse.getX() - x)/scale, (love.mouse.getY() - y)/scale) + lightMouse:setPosition((love.mouse.getX() - x)/scale, (love.mouse.getY() - y)/scale, z) +end + +function love.mousepressed(x, y, c) + if c == "wu" then + z = z + 1 + elseif c == "wd" then + z = z - 1 + end end function love.draw() @@ -131,6 +140,7 @@ function love.draw() love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), 24) love.graphics.setColor(0, 255, 0) love.graphics.print("To toggle postshaders, use 0-9 and q->y, to scale use - and =, and to translate use arrows") + love.graphics.print("light z: " .. lightMouse.z, 0, 50) end function drawBackground(l,t,w,h) diff --git a/lib/body.lua b/lib/body.lua index f85b5c0..433b811 100644 --- a/lib/body.lua +++ b/lib/body.lua @@ -1,9 +1,8 @@ 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 vec2 = require(_PACKAGE..'/vec2') +local vec3 = require(_PACKAGE..'/vec3') local body = class() body.glowShader = love.graphics.newShader(_PACKAGE.."/shaders/glow.glsl") @@ -24,6 +23,7 @@ function body:init(id, type, ...) self.glowStrength = 0.0 self.tileX = 0 self.tileY = 0 + self.zheight = 1 if self.type == "circle" then self.x = args[1] or 0 @@ -88,14 +88,10 @@ end 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 + 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 @@ -452,7 +448,7 @@ function body:drawShadow(light) end function body:drawPixelShadow() - if self.normalMesh then + 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 @@ -546,56 +542,57 @@ function body:calculateShadow(light) end end +--using shadow point calculations from this article +--http://web.cs.wpi.edu/~matt/courses/cs563/talks/shadow/shadow.html function body:calculatePolyShadow(light) - if self.castsNoShadow then + if self.castsNoShadow or (self.zheight - light.z) > 0 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} + for k = 1, #self.data, 2 do + local indexOfNextVertex = (k + 2) % #self.data + local normal = vec2(-self.data[indexOfNextVertex+1] + self.data[k + 1], self.data[indexOfNextVertex] - self.data[k]):normalize() + local lightToPoint = vec2(self.data[k] - light.x, self.data[k + 1] - light.y):normalize() - 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 + local dotProduct = normal:dot(lightToPoint) + if dotProduct > 0 then + table.insert(edgeFacingTo, true) + else + table.insert(edgeFacingTo, false) + end end local curShadowGeometry = {} + local lxh = (light.x * self.zheight) + local lyh = (light.y * self.zheight) + local height_diff = (self.zheight - light.z) + if height_diff == 0 then -- prevent inf + height_diff = -0.001 + end for k = 1, #edgeFacingTo do local nextIndex = (k + 1) % #edgeFacingTo if nextIndex == 0 then nextIndex = #edgeFacingTo end + + local x, y = self.data[nextIndex*2-1], self.data[nextIndex*2] + local xs, ys = (lxh - (x * light.z))/height_diff, (lyh - (y * light.z))/height_diff + 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 - + curShadowGeometry[#curShadowGeometry+1] = x + curShadowGeometry[#curShadowGeometry+1] = y + curShadowGeometry[#curShadowGeometry+1] = xs + curShadowGeometry[#curShadowGeometry+1] = ys + elseif not edgeFacingTo[k] and not edgeFacingTo[nextIndex] then + curShadowGeometry[#curShadowGeometry+1] = xs + curShadowGeometry[#curShadowGeometry+1] = ys 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 + curShadowGeometry[#curShadowGeometry+1] = xs + curShadowGeometry[#curShadowGeometry+1] = ys + curShadowGeometry[#curShadowGeometry+1] = x + curShadowGeometry[#curShadowGeometry+1] = y 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 + if #curShadowGeometry >= 6 then curShadowGeometry.alpha = self.alpha curShadowGeometry.red = self.red curShadowGeometry.green = self.green @@ -606,38 +603,47 @@ function body:calculatePolyShadow(light) end end +--using shadow point calculations from this article +--http://web.cs.wpi.edu/~matt/courses/cs563/talks/shadow/shadow.html function body:calculateCircleShadow(light) - if self.castsNoShadow then + if self.castsNoShadow or (self.zheight - light.z) > 0 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 + 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[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[1] = x2 + curShadowGeometry[2] = y2 + curShadowGeometry[3] = x3 + curShadowGeometry[4] = y3 - curShadowGeometry.red = self.red - curShadowGeometry.green = self.green - curShadowGeometry.blue = self.blue - curShadowGeometry.alpha = self.alpha - - return curShadowGeometry - else - return nil + local lxh = (light.x * self.zheight) + local lyh = (light.y * self.zheight) + local height_diff = (self.zheight - light.z) + if height_diff == 0 then -- prevent inf + height_diff = -0.001 end + + curShadowGeometry[5] = (lxh - (x3 * light.z))/height_diff + curShadowGeometry[6] = (lyh - (y3 * light.z))/height_diff + curShadowGeometry[7] = (lxh - (x2 * light.z))/height_diff + curShadowGeometry[8] = (lyh - (y2 * light.z))/height_diff + + local radius = math.sqrt(math.pow(curShadowGeometry[7] - curShadowGeometry[5], 2) + math.pow(curShadowGeometry[8]-curShadowGeometry[6], 2)) / 2 + local cx, cy = (curShadowGeometry[5] + curShadowGeometry[7])/2, (curShadowGeometry[6] + curShadowGeometry[8])/2 + curShadowGeometry.circle = {cx, cy, radius} + + curShadowGeometry.red = self.red + curShadowGeometry.green = self.green + curShadowGeometry.blue = self.blue + curShadowGeometry.alpha = self.alpha + + return curShadowGeometry end return body diff --git a/lib/light.lua b/lib/light.lua index 0ade1ed..03e127a 100644 --- a/lib/light.lua +++ b/lib/light.lua @@ -7,7 +7,6 @@ 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 @@ -15,7 +14,7 @@ function light:init(x, y, r, g, b, range) self.range = 0 self.x = x or 0 self.y = y or 0 - self.z = 15 + self.z = 1 self.red = r or 255 self.green = g or 255 self.blue = b or 255 @@ -32,7 +31,6 @@ function light:refresh(w, h) self.shadow = love.graphics.newCanvas(w, h) self.shine = love.graphics.newCanvas(w, h) - self.normalInvertShader:send('screenResolution', {w, h}) self.normalShader:send('screenResolution', {w, h}) end @@ -199,23 +197,14 @@ end function light:drawPixelShadow(l,t,w,h,s, normalMap, canvas) if self.visible and self:inRange(l,t,w,h,s) then - if self.normalInvert then - self.normalInvertShader:send('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) - self.normalInvertShader:send("lightPosition", {(self.x + l/s) * s, (h/s - (self.y + t/s)) * s, 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('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) - self.normalShader:send("lightPosition", {(self.x + l/s) * s, (h/s - (self.y + t/s)) * s, 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 + self.normalShader:send('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) + self.normalShader:send("lightPosition", {(self.x + l/s) * s, (h/s - (self.y + t/s)) * s, 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) + self.normalShader:send("invert_normal", self.normalInvert == true) + util.drawCanvasToCanvas(normalMap, canvas, {shader = self.normalShader}) end end diff --git a/lib/shaders/normal.glsl b/lib/shaders/normal.glsl index 987b6e0..550c675 100644 --- a/lib/shaders/normal.glsl +++ b/lib/shaders/normal.glsl @@ -7,6 +7,7 @@ extern float lightRange; extern float lightSmooth; extern float lightDirection; extern float lightAngle; +extern bool invert_normal; vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { vec4 pixelColor = Texel(texture, texture_coords); @@ -25,7 +26,12 @@ vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { } } - vec3 normal = pixelColor.rgb; + vec3 normal; + if(invert_normal == true) { + normal = vec3(pixelColor.r, 1 - pixelColor.g, pixelColor.b); + } else { + normal = pixelColor.rgb; + } float dist = distance(lightPosition, vec3(pixel_coords, normal.b)); if(dist < lightRange) { @@ -47,4 +53,4 @@ vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { } else { return vec4(0.0); } -} \ No newline at end of file +} diff --git a/lib/shaders/normal_invert.glsl b/lib/shaders/normal_invert.glsl deleted file mode 100644 index f73185c..0000000 --- a/lib/shaders/normal_invert.glsl +++ /dev/null @@ -1,50 +0,0 @@ -#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/lib/stencils.lua b/lib/stencils.lua index de293c6..2a1a903 100644 --- a/lib/stencils.lua +++ b/lib/stencils.lua @@ -6,6 +6,9 @@ function stencils.shadow(geometry, bodies) for i = 1,#geometry do if geometry[i].alpha == 1.0 then love.graphics.polygon("fill", unpack(geometry[i])) + if geometry[i].circle then + love.graphics.circle("fill", unpack(geometry[i].circle)) + end end end -- underneath shadows diff --git a/lib/vec2.lua b/lib/vec2.lua new file mode 100644 index 0000000..06b682a --- /dev/null +++ b/lib/vec2.lua @@ -0,0 +1,27 @@ +local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or "" +local class = require(_PACKAGE.."/class") + +local vec2 = class() + +function vec2:init(x, y) + self.x, self.y = x, y +end + +function vec2:normalize() + local len = self:length() + return vec2(self.x / len, self.y / len) +end + +function vec2:dot(v2) + return (self.x * v2.x) + (self.y * v2.y) +end + +function vec2:cross(v2) + return ((self.x * v2.y) - (self.y * v2.x)) +end + +function vec2:length() + return math.sqrt(self:dot(self)) +end + +return vec2 diff --git a/lib/vec3.lua b/lib/vec3.lua new file mode 100644 index 0000000..1d499a4 --- /dev/null +++ b/lib/vec3.lua @@ -0,0 +1,30 @@ +local _PACKAGE = (...):match("^(.+)[%./][^%./]+") or "" +local class = require(_PACKAGE.."/class") + +local vec3 = class() + +function vec3:init(x, y, z) + self.x, self.y, self.z = x, y, z +end + +function vec3:normalize() + local len = self:length() + return vec3((self.x / len), (self.y / len), (self.z / len)) +end + +function vec3:dot(v2) + return (self.x * v2.x) + (self.y * v2.y) + (self.z * v2.z) +end + +function vec3:cross(v2) + return ((self.y * v2.z) - (self.z * v2.y)), + ((self.z * v2.x) - (self.x * v2.z)), + ((self.x * v2.y) - (self.y * v2.x)) +end + +function vec3:length() + return math.sqrt(self:dot(self)) +end + +return vec3 + diff --git a/lib/vector.lua b/lib/vector.lua deleted file mode 100644 index c88cc24..0000000 --- a/lib/vector.lua +++ /dev/null @@ -1,21 +0,0 @@ -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