# Lighting

I’m working on what isn’t so much lighting as it is a way to make a field of view where a character can’t see through walls in a 2D game. This is the first test of it (didn’t even have time to test or fix any code) but it is functional.

``````
--# Main
function setup()
attempt = physics.rect(1,1)
attempt2 = physics.rect(1,1)
light = Lights(vec2(WIDTH/2,HEIGHT/2),{attempt,attempt2})
frame = 0
rectMode(CENTER)
physics.gravity(0,0)
end
function draw()
background(255, 255, 255, 255)
fill(0, 0, 0, 255)
rect(attempt.position.x+1,attempt.position.y+1,51)
rect(attempt2.position.x+1,attempt2.position.y+1,51)
ellipse(WIDTH/2,HEIGHT/2,10)
light:draw()
end

function touched(t)
attempt.position = vec2(t.x,t.y)
attempt.linearVelocity = vec2(0,0)
attempt2.position = vec2(WIDTH-t.x,HEIGHT-t.y)
attempt2.linearVelocity = vec2(0,0)
end

physics.rect = function(x,y,w,h)
x = x or WIDTH/2
y = y or HEIGHT/2
w = w or 50
h = h or 50
return physics.body(POLYGON,
vec2(x-w/2,y-h/2),vec2(x+w/2,y-h/2),
vec2(x+w/2,y+h/2),vec2(x-w/2,y+h/2))
end

--# Lights
Lights = class()
displayMode(FULLSCREEN)
function Lights:init(center,bodies)
self.p = center
self.b = bodies
self.edges =
{physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)),
physics.body(EDGE,vec2(0,0),vec2(WIDTH,0)),
physics.body(EDGE,vec2(WIDTH,HEIGHT),vec2(0,HEIGHT)),
physics.body(EDGE,vec2(WIDTH,HEIGHT),vec2(WIDTH,0))}
for i,v in ipairs(self.edges) do
end
self.mesh = mesh()
end

function Lights:draw()
local b = self.p
local verts = {}
for i,v in pairs(self.b) do
local points = {}
for j,z in pairs(self.b[i].points) do
local z2 = z + v.position
local m1 = (z2.y-b.y)
local m2 = (z2.x-b.x)
local dx = math.abs(WIDTH/m2)
local dy = math.abs(HEIGHT/m1)
local q = vec2((b.x+m2*50),(b.y+m1*50))
table.insert(points,{z2,q})--ep.point})
end
if #points>3 then
for i=1,#points/2 do
local a = points[i]
local b = points[i+2]
local tri=triangulate({a[1],a[2],b[2],b[1]})
for i=1,#tri do
verts[#verts+1] = tri[i]
end
end
self.mesh.vertices = verts
self.mesh:setColors(0,0,0,255)
end
end
self.mesh:draw()
self.mesh:clear()
end
``````

@Monkeyman32123 - If you’re just peeking through doorways, maybe try this instead. Move the parameter to slide the wall left and right to simulate changes to field of view, and see the characters behind.

There are a couple of ways to make things invisible behind walls. One is obviously to draw them before you draw the walls, as I’ve done with the left hand character.

What isn’t well known is that you can set a depth value in 2D graphics (ie z as well as x and y), which will draw objects behind others. That’s what I’ve done with the character on the right. So if you draw room objects at a depth slightly behind the walls, then they will be drawn behind the walls, no matter what order you draw the in.

``````function setup()
--move this to slide wall
parameter.number("X",-100,100,0)
end

function draw()
background(220)
fill(180, 113, 95, 255)
sprite("Planet Cute:Character Boy",350,280)
rect(200+X,200,200,150)
rect(500+X,200,200,150)
pushMatrix()
translate(0,0,-0.1) --draw this char BEHIND the walls
sprite("Planet Cute:Character Pink Girl",550,280)
popMatrix()
end
``````

Oh, I’m sorry, I think I may have not explained what mine is for very well. Say you’re in a top-down 2-D game and there are walls and obstacles that a normal human couldn’t see through, so you use this so that everything behind those walls is blocked from the player’s view.

Someone asked a similar question a while ago, and I tweaked my WIP godray shader for it to work. How’s this?

``````
--# Main
-- Skyrays Lighting

displayMode(OVERLAY)

function setup()
print("----\
Drag to move the image\
Double-tap to zoom in/out\
Zoom is kinda broken, sorry\
----")
sidebarTween = tween.delay(1.5, function()
displayMode(FULLSCREEN)
end)
shouldSidebar = 0
parameter.watch("FPS")
parameter.number("Samples", 0.5, 2.0, 0.5, callback)-- The shader can have the samples set to any amount, but 5 is the most I would recommend. 2 is a good mumber IMO, not too little that it looks really weird, but not too many that it's super laggy
parameter.boolean("Realistic Curve", true, callback) -- Whether or not the light effect should amplified realistically
parameter.boolean("Visible Walls", true, callback) -- Whether the walls should be colored or solid black
parameter.boolean("Overlay", false, callback) -- Sprite the image over where it would be so you can see it in the dark
mult = 1.0 -- Multiplier for the resolution of the screen (you can crank it up to 2k resolution on retinas, set the mult to 2)
screen = image(WIDTH * mult, HEIGHT * mult)
objects = image(WIDTH * mult, HEIGHT * mult)
m = mesh()
rIdx = m:addRect(WIDTH / 2 * mult, HEIGHT / 2 * mult, WIDTH * mult, HEIGHT * mult)
m.texture = screen
pos = {x = WIDTH / 8, y = HEIGHT / 2, s = 1}
img = readImage("Small World:Mine Large") -- The image that is put on the screen to block godrays

bg = image(WIDTH, HEIGHT)
setContext(bg, false)
local xSize = WIDTH / 7
local ySize = WIDTH / 7
for x = WIDTH / 2 - (xSize + 100), WIDTH / 2 + (xSize + 100), 100 do
for y = HEIGHT / 2 - (ySize + 100), HEIGHT / 2 + (ySize + 100), 100 do
sprite("Platformer Art:Block Brick", x, y, 100)
end
end
setContext()
math.randomseed(0)
obj = {}
for i = 1, 25 do
table.insert(obj, {math.random(0, WIDTH), math.random(0, HEIGHT)})
end
end

function callback()
if FPS ~= 0 then
tween.stop(sidebarTween)
shouldSidebar = 10
end
end

function draw()
--noSmooth()

local prev = shouldSidebar
shouldSidebar = math.max(0, shouldSidebar - DeltaTime)
if prev ~= 0 and shouldSidebar == 0 then
displayMode(FULLSCREEN)
end

background(119, 182, 200, 255)

strokeWidth(5)

setContext(screen, false)
background(0, 0)
pushMatrix()
translate(pos.x, pos.y)
scale(pos.s)
translate(-(pos.x), -(pos.y))
sprite(img, pos.x, pos.y, WIDTH, HEIGHT)
popMatrix()
setContext()

translate(WIDTH / 2, HEIGHT / 2)
scale(pos.s)
translate(WIDTH / -2, HEIGHT / -2)

local xOff = (pos.x / 100 - math.floor(pos.x / 100)) * 100
local yOff = (pos.y / 100 - math.floor(pos.y / 100)) * 100

sprite(bg, WIDTH / 2 + xOff, HEIGHT / 2 + yOff)

for k, v in ipairs(obj) do
sprite("Cargo Bot:Title Large Crate 1", v[1] + pos.x, v[2] + pos.y, 50)
end

sprite(objects, WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)

scale(1 / mult)

m:draw()

resetMatrix()

if Overlay then
tint(255, 31)
sprite(img, pos.x, pos.y, WIDTH, HEIGHT)
noTint()
end

resetMatrix()

fill(255)
font("HelveticaNeue-UltraLight")
fontSize(24)
textMode(CORNER)
local str = "FPS: " .. FPS
local w, h = textSize(str)
text(str, 5, HEIGHT - h - 5)
end

function touched(touch)
if touch.state ~= ENDED and touch.state ~= CANCELLED then
pos.x = pos.x + touch.deltaX
pos.y = pos.y + touch.deltaY
displayMode(FULLSCREEN)
elseif touch.tapCount == 2 then
tween(1, pos, {s = 3 - pos.s}, tween.easing.bounceOut)
if shouldSidebar > 0 then
displayMode(OVERLAY)
end
else
if shouldSidebar > 0 then
displayMode(OVERLAY)
end
end
end

-- FPS counter --

FPS = 0
local frames = 0
local time = 0
tween.delay(0, function()
local d = draw
draw = function()
frames = frames + 1
if math.floor(ElapsedTime) ~= math.floor(time) then
FPS = frames - 1
frames = 1
end
time = ElapsedTime
d()
end
end)
Skyrays = {
vS = [[
//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}

]],
fS = [[
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//uniform lowp sampler2D objects;

uniform vec2 point;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

uniform bool realisticCurve;

uniform float samples;

uniform bool visibleWalls;

const float distx = 1.0 / 3.0;
const float texMult = 1.0 / distx;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;

if (col.a <= 0.5) {
col = vec4(0.0);
}

float targetDist = distance(point, vTexCoord);
lowp vec4 pointSample = texture2D(texture, point);
bool startVisible = pointSample.a <= 0.5;

float light = 0.0;

float add = max(0.0, 1.0 - targetDist * texMult);
float objLight = add * col.a;

if (targetDist <= distx) {
vec2 dir = normalize(point - vTexCoord);
bool visible = startVisible || col.a <= 0.5;
float visibleNum = 0.0;
float mult;
int c = 0;
for (float dist = 0.0; dist <= targetDist; dist += targetDist / (samples * 10.0)) {
lowp vec4 sample = texture2D(texture, vTexCoord + dir * dist);// + texture2D(objects, vTexCoord + dir * dist);
mult = min(1.0, (1.0 - sample.a) + 0.2);
float objMult;
if (visibleWalls) objMult = min(1.0, (1.0 - sample.a) + 0.85);
if (sample.a != 0.0) {
if (sample.a <= 0.5) {
visibleNum += 1.0;
visible = true;
}
if ((visible && startVisible) || (!visible) || sample.a > 0.5) {
if (visibleWalls) objLight *= objMult;
if (col.a > 0.5) {
if (visibleWalls) objLight *= objMult;
c++;
}
if (c > 2) break;
}
}
}

if (visible) {
}
}

//Set the output color to the texture color
if (visibleWalls && col.a > 0.5) {
//light = (light + 0.1) * 2.0 - 0.1;
gl_FragColor = mix(vec4(vec3(0.0), 1.0), col, max(0.0, min(1.0, objLight)));
} else {
gl_FragColor = mix(vec4(vec3(0.0), 1.0), col, max(0.0, min(1.0, light)));
}
}

]]
}
}
``````

@Monkeyman32123 - the problem with a top down view as you are suggesting, is that you have to do all the work to figure out what is visible - normally OpenGL does this for you.

@SkyTheCoder’s approach appears to use a shader to test whether each point can be seen by the camera, by checking a series of sample points in between to see if anything is blocking the view. I use a similar sampling approach in my 3D dungeon.

What you might find simpler, is to use a tiled map, ie break your map into a grid of (for example) 10x10 pixels squares. Each square can contain one object, eg wall, character, weapon.

Then you have a 2D table where you store what is in each square (you could have a set of characters which stand for different objects like walls, doors etc).

Each time you draw, you look around in all directions, creating a table of which tiles are visible. This can be done using a flood fill algorithm (I have code for this). Then you draw the visible tiles.

I think there’s a huge misunderstanding here. My code is perfectly functional but still being worked on. It is for doing top-down views and it blocks what shouldn’t be able to be seen. This is made for 2-D games and that’s all it works for. I wasn’t asking a question, thus I put it in “code sharing”. Since I’m confused what you are saying and I don’t think I’ve expressed what this is for well enough I will whip up a demo over the next few days to show what I mean

Sorry about that. A demo would be good.

@Monkeyman32123 Sorry, I also thought you were asking for help. Looking back at your post, it seems obvious that you were sharing your own code…I guess I just read it too quickly

@Monkeyman32123 This is some good stuff, I’ve used it in my game to make a demo for it. Is it okay to use it for my game, I’m going to tweak a few things but I’ll give you credit!
@Ignatz here is your demo using it in my game:

That little dude needs to teach his feet how to behave!

Looks fun

@Ignatz thank you, it’s a bit erratic when the fps is only 30 but it’s being fixed currently.

Of course you may Luatee I post code because I want people to have a use for it. If you come back periodically I plan to make improvements and a slew of changes. Currently it only functions (properly) with convex even-vertexed polygons (as this was just a test of concept) but the changes are a-comin’

Oh and it’s quite alright Ignatz and Sky it happens.

That’s awesome! It reminds me of Lasersquad (on the Commodore 64 and then the Amiga). I think the same developer went on to make UFO and then XCOM. Although when I played XCOM recently that point of view effect didn’t seem quite as pronounced as I remembered it in the days of Lasersquad. On iOS, the Hunters games from Rodeo are probably closest to the original Lasersquad. @Monkeyman32123 is this for something tactical and turn-based?

Okay, so I was completely redoing the code to use edges (much more difficult) rather than crisscrossed quadrilaterals. As you move the block, occasionally the shadow overlaps the square and it seems to be random, and I cannot figure out why

``````function setup()
attempt = physics.rect(1,1)
--attempt2 = physics.rect(1,1)
--attempt3 = physics.rect(WIDTH/2,HEIGHT/4,WIDTH/2,20)
light = Lights(vec2(WIDTH/2,HEIGHT/2),{attempt})--,attempt2,attempt3})
frame = 0
rectMode(CENTER)
physics.gravity(0,0)
end
function draw()
--light.p = light.p + vec2(Gravity.x,Gravity.y)*15
background(255, 255, 255, 255)
fill(255, 0, 0, 255)
rect(attempt.position.x+1,attempt.position.y+1,51)
--rect(attempt2.position.x+1,attempt2.position.y+1,51)
--rect(WIDTH/2,HEIGHT/4,WIDTH/2+1,20+1)
ellipse(light.center.x,light.center.y,30)
light:draw()
end

function touched(t)
attempt.position = vec2(t.x,t.y)
attempt.linearVelocity = vec2(0,0)
--attempt2.position = vec2(WIDTH-t.x-2,HEIGHT-t.y-2)
--attempt2.linearVelocity = vec2(0,0)
end

physics.rect = function(x,y,w,h)
x = x or WIDTH/2
y = y or HEIGHT/2
w = w or 50
h = h or 50
return physics.body(POLYGON,
vec2(x-w/2,y-h/2),vec2(x+w/2,y-h/2),
vec2(x+w/2,y+h/2),vec2(x-w/2,y+h/2))
end

Lights = class()
function Lights:init(center,bodies)
self.center = center
self.bodies = bodies
self.mesh = mesh()
end

function Lights:draw()
local c = self.center
local verts = {}
for i,v in pairs(self.bodies) do
local last = nil
local keypoints = {}
local points = {}
local extrusions = {}
local on = nil
for j,r in pairs(v.points) do
local z = v:getWorldPoint(r)
local m = z-c
local zt = self:isvis(z-(m*.001))
if not zt and last then
keypoints[1] = j-1
elseif zt and not last then
keypoints[2] = j
end
last = zt
end
if not keypoints[1] then
keypoints[1] = #v.points
end
if not keypoints[2] then
keypoints[2] = 1
end
for g=keypoints[1],math.huge do
local x = g
if g > #v.points then
x = g-#v.points
end
local poi = v:getWorldPoint(v.points[x or g])
points[g-keypoints[1]+1] = poi
local m = poi-c
extrusions[g-keypoints[1]+1] = poi+(m*100)
if x == keypoints[2] then
break
end
end
for l=#extrusions,1,-1 do
points[#points+1] = extrusions[l]
end
local tri = triangulate(points)
if tri then
for q=1,#tri do
verts[#verts+1] = tri[q]
end
end
end
self.mesh.vertices = verts
self.mesh:setColors(0,0,0,255)
self.mesh:draw()
self.mesh:clear()
end

function Lights:isvis(p)
isnotvis = physics.raycast(self.center,p)
if isnotvis then
return false
else
return true
end
end
``````

In the draw routine, won’t drawing the rect after the light:draw fix the problem.

Yes, it will, but I want to fix the bug because adding vertices to the triangulation seems like it could become a problem with more complex shapes. Also, I’d just like to know what is causing it so that the rects drawing doesn’t need to be order-specific

And I really only want to know as a mental excercise (just got back to coding so proposed it to myself as a way to challenge myself back into it). That version is probably scrapped ASAP.

@Monkeyman32123 I see what your problem is, but I’m not sure I can explain it to you. Add the line of code I show below as the last line in setup(). Rotate your iPad to portrait mode and run your code. The red square should be in the lower right area of the screen. The shadow will cover the lower right portion of the square. What’s happening is when you calculate the verts to draw your shadow, the 3 points of the red square being covered are part of the verts. I was able to print out the vert x,y values and the points x,y values and was able to see the mesh areas that are colored black. I’m not sure how to fix it yet or why as you mover the square around, certain positions will cause this problem.

``````    attempt.position=vec2(370,245)
``````

@Monkeyman32123 Here’s your problem. Under some conditions, the triangulate function is taking 6 points you calculate and creates 4 meshes. One of those meshes is a triangular part of the red square that’s colored black.