Light sources
A light source is anything that lights the level. Lambent asks every source for its lights once a
frame, before it draws anything, and the source hands them over through the LightFrame it is given.
Nothing is kept from one frame to the next unless you ask for it again. Lights you stop handing over
are gone the next frame.
Registering
LambentClient.register(LightSource source); // priority 0
LambentClient.register(int priority, LightSource source);
LambentClient.unregister(LightSource source);
LightSource is a functional interface with one method, void collect(LightFrame frame), so a lambda
or a method reference will do. Register from client-only code, once, while the client starts.
Priority. Sources are asked highest priority first, and sources with the same priority are asked in the order they registered. Every kind of light has a fixed room per frame (see Lights). Past it, what a source hands over is dropped, so the sources asked first keep their lights. Veltorque uses:
| Priority | Source | Why |
|---|---|---|
| 100 | Flashlights | The light in your own hand must never be crowded out. |
| 0 | Vehicle lamps | Headlight pairs, reverse lamps, lens bloom, tail glows. |
| -100 | Flares | There can be dozens; the nearest few get point lights and the rest glow. |
Failures. A source that throws a RuntimeException is switched off and logged with its stack trace.
The other sources keep running. Failed sources come back on when the resources reload (F3+T).
What a frame tells you
| Method | Returns |
|---|---|
minecraft(), level(), camera() |
The client, its level, the camera this frame is drawn from. |
eye() |
The camera’s world position. Every position you hand over is relative to it. |
relative(Vec3 world) |
world - eye() as a Vector3f. |
partialTick() |
How far between ticks this frame is. Use it to place lights on moving entities. |
dt() |
Seconds since the last frame, at most 0.1. |
mayShow(x, y, z, radius) |
Whether a ball (camera-relative centre) may be on screen. Use it to skip work for lights out of view; a spot light’s beam can be on screen when its lamp is not, so test the beam’s reach, not just the lamp. |
ambient(int sky) |
The lightmap color with no block light and that sky light, as the level was just drawn. |
ambient(Vec3 at) |
The same, at the sky light of a world point. |
weather() |
How much lamps should show because of rain and storms alone: 0.35 in rain, up to 0.6 in a thunderstorm. |
lamps(), glows(), lenses(), points() |
How many of each have been handed over so far this frame, by every source. |
lampRoom(), glowRoom(), lensRoom(), pointRoom() |
Whether there is room for one more. Check before doing expensive work. |
Darkness
Lamps should show at night and fade out by day. Lambent gives you the pieces rather than deciding for you:
Vector3f ambient = frame.ambient(worldPos);
float dark = Math.max(LightFrame.darkness(ambient), frame.weather());
LightFrame.darkness(ambient) is 1 when the lightmap color is dark and eases to 0 as it brightens
(a smoothstep on its luminance between 0.2 and 0.8). Multiply your light’s color by dark. Pass the
same ambient to the light: Lambent uses it to work out how the surfaces round the light were already
lit, so the added light looks right on top of them.
A lamp that should show even by day (a tail lamp’s glow, a flare’s glare) can use a floor:
Math.max(dark, 0.2f).
Fading in
float in = frame.fade(int key, float seconds);
Returns how far a light has faded in, from 0 to 1, over seconds. Lambent keeps the value by key as
long as you call fade for that key every frame. Skip one frame and it starts again from dark the next
time. Call it only when the light is actually on, and multiply the color by it.
Handing over lights
Each call returns true if the light was taken and false if its kind was already full.
| Call | Kind | Page |
|---|---|---|
pair(key, beam, holder, first, last, direction, up, aimFirst, shineFirst, aimLast, shineLast, color, ambient) |
Spot light: two beams sharing one shadow map | Lights |
spot(key, beam, holder, position, direction, shining, color, ambient, glare, handheld) |
Spot light: one beam | Lights |
lamp(Lamp) |
Spot light worked out by hand | Lights |
point(Point) |
Point light | Lights |
glow(Glow) |
Glowing lens | Lights |
lens(Lens) |
Lens bloom | Lights |
room(id, Vec3 at) |
The hollow a point light at at is shut in, as a world box, or null in the open |
Lights |
Keys
A spot light’s key identifies it across frames. Lambent keeps two things by key: the mesh of blocks round the beam that it draws into the shadow map, and how shut in the air along the beam is (which sets how much dust floats in it). Reusing a key for a different light makes it inherit the wrong blocks for a moment, so pick keys no other source uses:
- entities:
entity.getId() * N + which, wherewhichtells the entity’s lamps apart; - players:
-1 - player.getId(), below zero, so they never meet an entity’s key.
A point light’s room id works the same way, but only for room: it keeps the hollow the light is in.
Example: a lantern on every armour stand
LambentClient.register(-10, frame -> {
for (Entity e : frame.level().entitiesForRendering()) {
if (!(e instanceof ArmorStand) || !frame.pointRoom()) continue;
Vec3 at = e.getPosition(frame.partialTick()).add(0, 1.8, 0);
Vector3f p = frame.relative(at);
if (!frame.mayShow(p.x, p.y, p.z, 16f)) continue;
Vector3f ambient = frame.ambient(at);
AABB room = frame.room(e.getId(), at);
Vector3f min = room == null ? new Vector3f(-1e5f) : frame.relative(new Vec3(room.minX, room.minY, room.minZ));
Vector3f max = room == null ? new Vector3f(1e5f) : frame.relative(new Vec3(room.maxX, room.maxY, room.maxZ));
frame.point(new Point(p, new Vector3f(1f, 0.6f, 0.25f).mul(2f), 16f, ambient, 9f, min, max));
}
});