A way to do simple shadows

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: A way to do simple shadows

Post by ReFreezed »

Gunroar:Cannon() wrote: Sat Sep 24, 2022 9:46 pm Wondering how you got the eldritch blood (asking for a friend)...
A whispering voice in my ear says I'm not allowed to tell.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
pauljessup
Party member
Posts: 355
Joined: Wed Jul 03, 2013 4:06 am

Re: A way to do simple shadows

Post by pauljessup »

These are really cool!
User avatar
darkfrei
Party member
Posts: 1168
Joined: Sat Feb 08, 2020 11:09 pm

Re: A way to do simple shadows

Post by darkfrei »

Can you please explain how the first version works? It looks simple, but I cannot get it.

Code: Select all

--
-- Example program: Render sprites with simple shadows from a light source.
--
local LG = love.graphics

local images = {
	sprites = LG.newImage("sprites.png"),
	light   = LG.newImage("light.png"),
}
images.sprites:setFilter("nearest")

local quads = {
	player = LG.newQuad(00,00, 32,32, images.sprites),
	tree   = LG.newQuad(32,00, 32,32, images.sprites),
	sign   = LG.newQuad(00,32, 32,32, images.sprites),
}

local entities = {}
local player = {id=#entities, quad=quads.player, x=220,y=200, flip=false}
table.insert(entities, player)

local rand = love.math.random
for i = 1, 30 do  table.insert(entities, {id=#entities, quad=quads.tree, x=rand(400),y=rand(300), flip=false})  end
for i = 1, 4  do  table.insert(entities, {id=#entities, quad=quads.sign, x=rand(400),y=rand(300), flip=false})  end

local lightSource = {x=0, y=0}

function love.update(dt)
	local moveX = 0
	local moveY = 0

	if love.keyboard.isDown"left"  then  moveX = moveX - 1  end
	if love.keyboard.isDown"right" then  moveX = moveX + 1  end
	if love.keyboard.isDown"up"    then  moveY = moveY - 1  end
	if love.keyboard.isDown"down"  then  moveY = moveY + 1  end

	player.x = player.x + 1.5*50*moveX*dt
	player.y = player.y +     50*moveY*dt
	if moveX ~= 0 then  player.flip = (moveX < 0)  end

	local rotation = .3 * love.timer.getTime()
	lightSource.x  = 200 + 1.5*60*math.cos(rotation)
	lightSource.y  = 150 +     60*math.sin(rotation)
end

local function clamp(v, min, max)
	return math.max(math.min(v, max), min)
end

local function drawEntity(e, shearing, scaleY)
	local scaleX   = e.flip and -1 or 1
	local _,_, w,h = e.quad:getViewport()
	LG.draw(images.sprites, e.quad, e.x,e.y, 0, scaleX,scaleY, w/2,h, shearing*scaleX,0)
end

function love.draw()
	table.sort(entities, function(a, b)
		if a.y ~= b.y then  return a.y < b.y  end
		return a.id < b.id
	end)
	LG.scale(2)

	-- Ground.
	LG.clear(0, 0, 0)

	-- Light (on ground).
	local w,h = images.light:getDimensions()
	LG.setColor(.3, .6, .3) ; LG.draw(images.light, lightSource.x,lightSource.y, 0, 1.5*15,15, w/2,h/2)
	LG.setColor(1, 1, 1)    ; LG.draw(images.light, lightSource.x,lightSource.y, 0, 1.5*1,1,   w/2,h/2)

	-- Entity shadows.
	for _, e in ipairs(entities) do
		local dx       = lightSource.x - e.x
		local dy       = lightSource.y - e.y
		local distance = math.sqrt(dx^2 + dy^2) / 50
		local angle    = math.atan2(dy, dx)

		local shearing = distance * math.cos(angle)
		local scaleY   = distance * math.sin(angle)

		LG.setColor(0, 0, 0)
		drawEntity(e, shearing, scaleY)
	end

	-- Entities.
	for _, e in ipairs(entities) do
		local dx       = lightSource.x - e.x
		local dy       = lightSource.y - e.y
		local distance = math.sqrt((dx/1.5)^2 + (dy)^2)

		local lightDistance = clamp(1 - distance / 400, 0, 1) -- Darker if farther away.
		local lightFacing   = clamp(1 + dy       / 20 , 0, 1) -- Darker if facing away.
		local light         = lightDistance * lightFacing

		LG.setColor(light, light, light)
		drawEntity(e, 0, 1)
	end

	-- Info.
	LG.origin()
	LG.setColor(1, 1, 1)
	LG.print("Press arrow keys to move", 3,1)
end

function love.keypressed(key)
	if key == "escape" then love.event.quit() end
end
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: A way to do simple shadows

Post by ReFreezed »

darkfrei wrote: Thu Sep 29, 2022 1:42 pm Can you please explain how the first version works? It looks simple, but I cannot get it.
Sure. The important parts are the drawEntity() function and how we calculate shearing and y-scaling.

Code: Select all

local function drawEntity(e, shearingX, scaleY)
	local scaleX   = e.flip and -1 or 1   -- We just use x-scaling to flip the sprite.
	local _,_, w,h = e.quad:getViewport() -- The size of the sprite.

	LG.draw(
		images.sprites, e.quad,
		e.x, e.y,           -- Position of the feet.
		0,                  -- No rotation.
		scaleX, scaleY,     -- Scale the sprite.
		w/2, h,             -- Place the origin right between the feet so that scaling and shearing happens around that point.
		shearingX*scaleX, 0 -- Tilt the sprite.
	)
end

Code: Select all

-- Entity shadows.
for _, e in ipairs(entities) do
	-- Get the difference between the entity and light's x and y positions.
	local dx = lightSource.x - e.x
	local dy = lightSource.y - e.y

	-- Get the straight distance from the entity to the light. This will
	-- represent the relative length (i.e. scale) of the shadow, and we
	-- divide it by 50 to simply make the shadow a bit shorter. Without
	-- the division the shadow scale would increase by one when for every
	-- pixel of distance (i.e. the shadow would get really long very quickly).
	local distance = math.sqrt(dx^2 + dy^2) / 50

	-- Get the angle from the entity to the light.
	local angle = math.atan2(dy, dx)

	-- X-shearing makes the sprite tilt to the left/right. (It's similar
	-- to rotation but the corners keep their y-positions.) Note that
	-- shearing is a relative value just like with scaling.
	-- If angle is up/down: the cosine is 0 and we get no tilt.
	-- If angle is left/right: the cosine is 1 and we get maximum tilt.
	local shearingX = distance * math.cos(angle)

	-- The y-scale is the vertical length of the shadow.
	-- If angle is up/down: the sine is 1/-1 and we get the longest shadow up/down.
	-- If angle is left/right: the sine is 0 and we get a zero length shadow.
	local scaleY = distance * math.sin(angle)

	LG.setColor(0, 0, 0)
	drawEntity(e, shearingX, scaleY)
end
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
pauljessup
Party member
Posts: 355
Joined: Wed Jul 03, 2013 4:06 am

Re: A way to do simple shadows

Post by pauljessup »

oooh...working on a sprite stacked voxel tile engine, and was thinking about how to do decent lights and shadows with it...and I think this might work very well...
[email protected]
Prole
Posts: 1
Joined: Wed Oct 26, 2022 9:27 am
Contact:

Re: A way to do simple shadows

Post by [email protected] »

That's what I need!
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 24 guests