Dynamic Lighting

by Forum_account
Dynamic Lighting
An easy way to add dynamic lighting to your game.
ID:119729
 
The library lets you attach light sources to objects and automatically provides global dynamic lighting. It's as simple as:

// make a mob a light source
mob.light = new(mob, 3)

// turn the light off
mob.light.off()

// turn it back on
mob.light.on()

// change the radius and intensity of the light
mob.light.radius(4)
mob.light.intensity(0.5)


The global illumination is automatically updated as the mob moves around. Light sources can also be attached to non-moving objects.

Version 7 (posted 03-18-2012)
• Changed how lighting is initialized. The lighting.init() proc is no longer used to set the icon, instead you just set lighting.icon directly. The init() proc initializes lighting. Any argument passed to it is a z level to be initialized, if no z levels are specified, lighting is initialized for all z levels.
• Changed how light sources work. Previously you could set their radius and intensity vars directly. It took extra CPU usage to detect these changes so now you have to use the radius() and intensity() procs to set these values. This way lighting is only recalculated when it's absolutely necessary
• Reorganized the code for the /light object. It now has a proc called loop() that is called by the lighting object's central loop. This proc checks for changes and calls apply(). The apply() proc removes the light's current effect and applies the updated effect.
• Added the intensity argument to the /light object's constructor and re-named the radius arg to be called "radius". Both are optional (the default is radius = 3, intensity = 1)
• Added a check so that shading.update() is only called when the lum value has changed in a way that will cause the icon_state to change. For example, a change from 0 to 0.1 will still use the same state and won't cause an update to occur.
• Removed the shading.update() proc and made it inline. It was being called so many times that the overhead was significant. Profiling shows this change decreased the CPU time used by the light.apply() proc by 15%.
• Changed the default light.lum proc to avoid unnecessary calls to sqrt(). Profiling shows this reduced the average CPU time of the light.effect() proc by about 20% and the average CPU time of light.apply() by 17%.
• Added the light.ambient() proc which can be used to change a light's ambient level.

Version 6
• Added the lighting.ambient var which can be used to set an ambient light level. If you change its value at runtime, it won't take effect until a tile is updated. Because the change would effect every tile, forcing every tile to update will often be too taxing on the CPU.
• A new demo called "day-night-demo" was added to show how to use the new lighting.ambient var to create a day/night cycle.

Version 5
• Changed the light.intensity var so it's now on the 0...1 scale. A value of 1 will make the center of the lighted area be at the maximum brightness regardless of the number of levels of shade you have.
• Changed the light.lum() proc to take pixel offsets into account (based on step_x and step_y) when computing distances. This means that light sources using pixel movement will cause updates to illumination with every step they take, instead of causing updates only when they transition to another turf.
• Added the lighting.pixel_movement var which is used to determine if pixel offsets are taken into account. By default this is enabled, but you can set light.pixel_movement = 0 to make light sources update tile-by-tile, even when using pixel movement.

Version 4
• Changed the way dynamic lighting is initialized, instead of calling new() to initialize it, you call init() and pass init the icon file to use. You call the same init() proc to initialize a z level - you can pass any number of z levels as arguments or pass nothing to make it initialize all z levels.
• Changed the /light_source object to just be /light
• Previously, each light_source object had a loop that executed every tick, this loop was moved to be a single global loop in the /Lighting object for performance reasons.
• Shading objects can now have decimal lum values (ex: 3.5) and will automatically be rounded to the nearest integer.
• Added some detail to the icons used in the demo and created a separate icon file with simpler graphics. Switch between the two icon files in demo\demo.dm to see how much better the lighting effects look when the turfs have more detailed icons.
• Added the light.intensity var which can be used to control the intensity of a light.
• Changed the default light.lum() proc to take into account the radius and intensity vars, the default provides a circular area of light with a value equal to the intensity var at the center and fading to zero at the edges.
• Updated each demo to have verbs for increasing and decreasing the light's intensity.

Version 3
• Replaced the world.illumination and atom.light procs with the light_source.lum and light_source.effect procs respectively. This lets you more easily extend the lighting behavior by creating child types of /light_source, see customization-demo for an example.

Version 2
• Changed the /LightSource datum to be /light_source and made it a child of /obj so they have positions on the map.
• Made each /light_source object have a loop to check for changes even if the light source isn't attached to a movable atom. This is necessary for implementing shadows and lets you change its vars directly - instead of using the light's radius() proc to change its radius, just set the radius var directly (ex: light.radius += 1)
• Added the shadows-demo demo which shows how light sources can be made to cast shadows by overriding the atom/light() proc. Instead of just returning the basic circular range of light, the light() proc in the demo does some visibility calculations so that opaque objects block light.
• Added the world.illumination() proc which takes two atoms and a radius and returns the illumination value based on the distance between atoms and the radius of the light source. This way you can override atom/light() but still use the same illumination calculation, or you can override illumination() and keep the same atom/light() proc.

Version 1
• Initial version
Exceptionally fast, exceptionally good looking.

Very easy to include, as well. I am amazed, but not surprised; after all, this is generally what one gets from FA's libraries.

Now if only there could be more shades of darkness... *sigh*!
Gooseheaded wrote:
Now if only there could be more shades of darkness... *sigh*!

Look in icon-generator.dm in the library. You can use it to generate the shading icons used by the library and increase the number of states.

The library comes with icons for 4 and 5 shades of darkness. Those icons contain 256 and 625 icon states respectively. The increase to 6 shades of darkness bumps it to 1296 icon states. The icon files start to get large.
*slaps self* Indeed. Thank you.

In any, how would one go about using this with opaque objects?
Gooseheaded wrote:
*slaps self* Indeed. Thank you.

In any, how would one go about using this with opaque objects?

You mean to have opaque objects that cast shadows? I'll be adding that in the next update. [Edit: shadows have been added]
What about objects that block your view? There would have to be a way to have solid black shades, since visibility calculation is different(better) in the library compared to BYOND's 8-directional solid black squares.
Kaiochao wrote:
What about objects that block your view? There would have to be a way to have solid black shades, since visibility calculation is different(better) in the library compared to BYOND's 8-directional solid black squares.

The same dynamic lighting objects are visible to everyone. Blocking sight to limit visibility is specific to the player. This would be a separate library, though somewhat similar.

In a parallel universe I have a lot of spare time and I create the offspring of the Visibility Group and Dynamic Lighting libraries - the Fog of War library. Then I get back to work on an RTS framework.
This library chokes on itself with large maps (over 500x500x2), and triggers an inf loop check error.
Stevenw9 wrote:
This library chokes on itself with large maps (over 500x500x2), and triggers an inf loop check error.

When you call lighting.init() you can pass it a z value to initialize the two z levels separately. Or, if needed, you can add a sleep() statement to the library to ease up the cost of initializing the whole map.
Forum_account wrote:
Stevenw9 wrote:
This library chokes on itself with large maps (over 500x500x2), and triggers an inf loop check error.

When you call lighting.init() you can pass it a z value to initialize the two z levels separately. Or, if needed, you can add a sleep() statement to the library to ease up the cost of initializing the whole map.

That fixes the initialization slow down, but I'm getting an odd slow down on BYOND's default UI. Hitting X doesn't close the window until around half a minute after, yet movement in the project, lighting and otherwise, is perfectly responsive.
The library places an object on every turf. I've never used something like this on a map that large, but I can imagine that it could cause some latency in shutting the game down - that's a lot of objects (500,000) to clean up.
Forum_account wrote:
The library places an object on every turf. I've never used something like this on a map that large, but I can imagine that it could cause some latency in shutting the game down - that's a lot of objects (500,000) to clean up.

lol, yeah that is true. I was attempting to use this for the suns in my orbiting solar systems, but I need the map to be fairly large to accommodate for the large icons sizes, which are large due to detail requirements (like a planet not being the same size as a fighter ship). It looks AWSOME with this library, but I need to do something about the shutdown time to actually use it, if I can do anything about it at all.

EDIT: On a side note, wouldn't it be a better idea as far as efficiency to only render (or in this case place objects?) light within the players view? Or light that reaches into the players view? I have no idea myself as I have no experience in Dynamic Lighting.

EDIT EDIT: Actually, after severely slowing down my orbiting proc (which should be slow anyway), this library sped up. I think they were competing for resources. o.o Not that BYOND was using even half the resources my computer has >_>

EDIT EDIT EDIT: Now if I can just figure out how to get shadows to work with pixel movement that doesn't call the Move() proc...
I might be able to make a mode where it creates the objects as they're needed. The problem is that you need to check if they're needed. The checks can slow things down if the objects have already been created.

If you simply don't need these objects on the entire map you could modify the library to not create these lighting objects in certain areas. For example, if you had a 200x200 map but the players could only access an L-shaped part of it, the library will still create lighting objects for turfs in the part of the map that players can't access. You can create an area (ex: /area/no_light) and place it on the map where you don't need lighting objects. Then, around line 91 in dynamic-lighting.dm make the library check the turf's loc before creating the light object - if it's in a no_light area, don't create the light.
Forum_account wrote:
I might be able to make a mode where it creates the objects as they're needed. The problem is that you need to check if they're needed. The checks can slow things down if the objects have already been created.

If you simply don't need these objects on the entire map you could modify the library to not create these lighting objects in certain areas. For example, if you had a 200x200 map but the players could only access an L-shaped part of it, the library will still create lighting objects for turfs in the part of the map that players can't access. You can create an area (ex: /area/no_light) and place it on the map where you don't need lighting objects. Then, around line 91 in dynamic-lighting.dm make the library check the turf's loc before creating the light object - if it's in a no_light area, don't create the light.

lawl, I'm actually using a 600x600 map (Currently only generating lighting on one Z level) and in the case of space, every single tile is used because everything has an orbital motion of some caliber.

In the case of doing it by view, I'll leave that up to your own better judgement with the library. I was just curious if it would provide any increased efficiency with large maps.

Either way, the slow downs aren't much of a problem anymore after slowing the orbits to normal levels. They were left in 'fast forward' mode which did not make BYOND very happy.
My only problem now is getting shadows to update properly with opaque objects moving via pixel movement. :D

EDIT: The light source updates perfectly though even while moving via pixel movement.
Stevenw9 wrote:
My only problem now is getting shadows to update properly with opaque objects moving via pixel movement. :D

EDIT: The light source updates perfectly though even while moving via pixel movement.

I noticed some problems when using it with pixel movement but didn't take the time to fix it. I'm pretty sure I know what the problem is.
See if changing the turf/opaque() proc that's defined in shadows-demo\demo.dm (around line 85) to this and see if that fixes the problem with opaque objects and shadows when using pixel movement:

turf
proc
// return 1 if the turf is opaque or contains an
// opaque object, return 0 otherwise
opaque()
if(opaque) return 1

for(var/atom/a in src)
if(!a.opaque) continue

var/atom/movable/m = a

if(istype(m))
// d is the amount of pixels they overlap
var/d = -bounds_dist(m, src)

if(d > min(m.bound_width / 2, m.bound_height / 2))
return 1

else
return 1

return 0
Forum_account wrote:
In a parallel universe I have a lot of spare time and I create the offspring of the Visibility Group and Dynamic Lighting libraries - the Fog of War library. Then I get back to work on an RTS framework.

I have a rough cut of this working. The fog updates as mobs move and allows for three states - turfs you can't see at all, turfs you have explored but can't currently see, and turfs you can see. It lets multiple mobs contribute towards your sight (ex: your vision in an RTS is the combination of what all of your units see) and uses images so it's specific to each client.

The next part of it is to make mobs capable of contributing to the sight for multiple clients (ex: you share sight with allies in an RTS). The most annoying part will be to make mobs hidden when they're on a turf you can't currently see.
That didn't fix it. It seems to be updating by full tile loc changes.
The lighting is always tile based even when movement is pixel based. The lighting should update when the mob's center moves to a different tile.
Page: 1 2 3