feat(actor3D): cast shadow below the actor.

This commit is contained in:
Kazhnuz 2019-07-06 18:00:00 +02:00
parent c043bb8ecf
commit a3b0f47127
4 changed files with 177 additions and 2 deletions

View file

@ -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

View file

@ -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

View file

@ -25,6 +25,8 @@ local Box3D = Object:extend()
function Box3D:new(owner, w, h, d)
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

View file

@ -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