From a3b0f471271e633724bb0e9fbddb87cc6de7e27f Mon Sep 17 00:00:00 2001 From: Kazhnuz Date: Sat, 6 Jul 2019 18:00:00 +0200 Subject: [PATCH] feat(actor3D): cast shadow below the actor. --- CHANGELOG.md | 2 +- gamecore/modules/world/actors/actor3D.lua | 75 +++++++++++++++++++ .../world/actors/utils/boxes/parent.lua | 67 ++++++++++++++++- gamecore/modules/world/world3D.lua | 35 +++++++++ 4 files changed, 177 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181810d..5d370f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **camera:** Add two new camera types: "middle" and "zoom". -- **world:** Add a fake 3D world, à la Zelda or BeatThemAll +- **world:** Add a fake 3D world, à la Zelda or BeatThemAll, complete with shadow support ### Changed diff --git a/gamecore/modules/world/actors/actor3D.lua b/gamecore/modules/world/actors/actor3D.lua index 21da23f..a87142c 100644 --- a/gamecore/modules/world/actors/actor3D.lua +++ b/gamecore/modules/world/actors/actor3D.lua @@ -37,9 +37,14 @@ function Actor3D:new(world, type, x, y, z, w, h, d, isSolid) self:initHitboxes() self.world:registerShape(self) self.boxes = Boxes + self.doCastShadows = true end function Actor3D:destroy() + self:removeOldShadowTargets() + if self.box ~= nil then + self.world:removeTerrain(self) + end self.world:removeActor(self) self.mainHitbox:destroy() self.world:removeShape(self) @@ -87,11 +92,19 @@ end function Actor3D:move(dx, dy, dz) local cols, colNumber = {}, 0 + local oldx, oldy, oldz = self.x, self.y, self.z if (self.isDestroyed == false) then self.x, self.y, self.z, cols, colNumber = self.mainHitbox:checkCollision(dx, dy, dz, self.filter) self.mainHitbox:updatePosition() self.world:updateShape(self) end + + if (oldx ~= self.x) or (oldy ~= self.y) or (oldz ~= self.z) or (self.shadowTargetsPrevious == nil) then + if (self.doCastShadows) then + self:castShadow() + end + end + return self.x, self.y, self.z, cols, colNumber end @@ -201,9 +214,71 @@ function Actor3D:checkHitboxCollisionsAtPoint(name, dx, dy, dz, filter) return x, y, z, cols, colNumber end +-- SHADOW FUNCTIONS +-- Handle everything related to shadow + +function Actor3D:castShadow() + local shadowTargets = self.world:getTerrainInRect(self.x, self.y, self.w, self.d) + -- initialize the shadowTargetsPrevious variable if it doesn't exist + if (self.shadowTargetsPrevious == nil) then + self.shadowTargetsPrevious = {} + end + + for i, target in ipairs(shadowTargets) do + -- We test if the actor is below the current actor + if (target ~= self) and (target.box ~= nil) then + + if (target.z + target.d <= self.z + self.d) then + -- Remove the target of the list of item targeted last update, + -- in order to only have object no longer shadowed after the + -- end of the loop + for j, oldtarget in ipairs(self.shadowTargetsPrevious) do + if (target == oldtarget) then + table.remove(self.shadowTargetsPrevious, j) + end + end + + -- We update the shadow source + local x, y = math.floor(self.x - target.x), math.floor(self.y - target.y) + target.box:setShadowSource(self, x, y) + end + + end + + end + + -- At this point, if a target is still in the shadowTargetsPrevious list, + -- it mean that it's not shadowed. So we can simply remove the shadow. + self:removeOldShadowTargets() + + self.shadowTargetsPrevious = shadowTargets +end + +function Actor3D:removeOldShadowTargets() + if (self.shadowTargetsPrevious ~= nil) then + for i, target in ipairs(self.shadowTargetsPrevious) do + if (target.box ~= nil) then + target.box:removeShadowSource(self) + end + end + end +end + +function Actor3D:redrawShadowCanvas() + if (self.box ~= nil) then + self.box:redrawShadowCanvas() + end +end + -- DRAW FUNCTIONS -- Draw the actors. +function Actor3D:drawShadow(x, y) + love.graphics.setColor(0, 0, 0, 1) + love.graphics.rectangle("fill", x, y, self.w, self.h) + utils.graphics.resetColor() +end + function Actor3D:getShape() return (self.x), (self.y - self.z - self.d), self.w, (self.h + self.d) end diff --git a/gamecore/modules/world/actors/utils/boxes/parent.lua b/gamecore/modules/world/actors/utils/boxes/parent.lua index 0da34e3..5459e48 100644 --- a/gamecore/modules/world/actors/utils/boxes/parent.lua +++ b/gamecore/modules/world/actors/utils/boxes/parent.lua @@ -24,7 +24,9 @@ local Box3D = Object:extend() function Box3D:new(owner, w, h, d) - self.owner = owner + self.owner = owner + self.world = owner.world + self.cameras = self.world.cameras self.w = w self.h = h @@ -32,15 +34,20 @@ function Box3D:new(owner, w, h, d) self.haveLine = true + self.shadowSources = {} + self.needRedraw = false + self.texture = {} self:setTopTexture() self:setBottomTexture() + self.texture.shadows = nil self:register() end function Box3D:register() self.owner.box = self + self.world:registerTerrain(self.owner) end function Box3D:setSizeFromOwner() @@ -80,6 +87,60 @@ function Box3D:setBottomTexture() canvas:release() end +function Box3D:setShadowSource(actor, x, y) + local foundShadow = false + + for i,v in ipairs(self.shadowSources) do + if (v.actor == actor) then + if (v.x ~= x) or (v.y ~= y) then + v.x = x + v.y = y + self.needRedraw = true + end + foundShadow = true + end + end + + if (foundShadow == false) then + local shadow = {} + shadow.actor = actor + shadow.x = x + shadow.y = y + self.needRedraw = true + + table.insert(self.shadowSources, shadow) + end +end + +function Box3D:removeShadowSource(actor) + for i,v in ipairs(self.shadowSources) do + if (v.actor == actor) then + table.remove(self.shadowSources, i) + self.needRedraw = true + end + end +end + +function Box3D:redrawShadowCanvas() + if (self.needRedraw) then + print("DEBUG: Generating " .. #self.shadowSources .. " shadows") + local canvas = love.graphics.newCanvas(self.w, self.h) + love.graphics.setCanvas( canvas ) + for i,v in ipairs(self.shadowSources) do + v.actor:drawShadow(v.x, v.y) + end + + love.graphics.setCanvas( ) + + local imagedata = canvas:newImageData() + self.texture.shadows = love.graphics.newImage( imagedata ) + imagedata:release() + canvas:release() + + self.needRedraw = false + end +end + function Box3D:draw(x, y, z) love.graphics.setColor(0, 0, 0, 1) if (self.haveLine) then @@ -88,6 +149,10 @@ function Box3D:draw(x, y, z) utils.graphics.resetColor() love.graphics.draw(self.texture.top, x, (y-z) - (self.d)) love.graphics.draw(self.texture.bottom, x, (y-z) - (self.d) + (self.h)) + if (self.texture.shadows ~= nil) and (#self.shadowSources > 0) then + print("DEBUG: Drawing " .. #self.shadowSources .. " shadow textures") + love.graphics.draw(self.texture.shadows, x, (y-z) - (self.d)) + end end return Box3D diff --git a/gamecore/modules/world/world3D.lua b/gamecore/modules/world/world3D.lua index e910d13..0d5ef76 100644 --- a/gamecore/modules/world/world3D.lua +++ b/gamecore/modules/world/world3D.lua @@ -46,6 +46,7 @@ function World3D:initActors() self.actors = {} self.bodies = Bump3D.newWorld(50) self:initShapes() + self:initTerrain() end function World3D:newActor(name, x, y, z) @@ -161,6 +162,17 @@ function World3D:getBodiesInRect(x, y, w, h) return {} --self.bodies:queryRect(x, y, w, h) end +-- UPDATE +-- Update everything in the world + +function World3D:updateActors(dt) + World3D.super.updateActors(self, dt) + local actors = self:getActors() + for i,v in ipairs(actors) do + v:redrawShadowCanvas() + end +end + -- SHAPE SYSTEM -- Handle onscreen shapes @@ -190,6 +202,29 @@ function World3D:getShapeInRect(x, y, w, h) return self.shapes:queryRect(x, y, w, h) end +-- TERRAIN SYSTEM +-- Handle onscreen shapes + +function World3D:initTerrain() + self.terrains = Bump.newWorld(50) +end + +function World3D:registerTerrain(actor) + return self.terrains:add(actor, actor.x, actor.y, actor.w, actor.h) +end + +function World3D:updateTerrain(actor) + return self.terrains:update(actor, actor.x, actor.y, actor.w, actor.h) +end + +function World3D:removeTerrain(actor) + return self.terrains:remove(actor) +end + +function World3D:getTerrainInRect(x, y, w, h) + return self.terrains:queryRect(x, y, w, h) +end + -- DRAW FUNCTIONS -- Functions to draw the world