.. contents:: Table of Contents .. _light: Light ===== The engine provides an opt-in, per-target lighting system exposed through the global ``mbm`` table. Lighting uses a classic (Phong-style) material model: ambient, directional (diffuse) and point lights combine with each subset's material to produce the final lit color. .. Attention:: | Lighting state is tracked separately per **target**. The only two valid target strings are ``'3d'`` and ``'2dw'`` (exact, case sensitive). ``'2ds'`` lighting is not implemented. | Lighting is disabled by default for both targets; call :ref:`setLightEnabled ` to turn it on. | Loading a new :ref:`scene ` resets all light state for both targets back to defaults. Lighting only affects objects created after it is turned on ----------------------------------------------------------- .. Attention:: | Whether an object receives lighting at all is decided **once**, at the moment its default shader is built (when it is loaded, or when an animation is added to it) — not every frame, and not retroactively. It depends on whether :ref:`setLightEnabled ` was already ``true`` for that object's target *at that exact moment*. | Calling :ref:`setLightEnabled ` afterwards only updates the light values consumed by objects that were already classified as lit; it does **not** upgrade objects that were already classified as unlit, and it does not downgrade lit objects back to unlit either. This is an easy contract to trip over, so here is the exact scenario: .. code-block:: lua -- lighting for '3d' is still OFF here local meshesBeforeLight = {} for i = 1, 5 do meshesBeforeLight[i] = mesh:new('3d') meshesBeforeLight[i]:load('crate.msh') -- classified unlit right now end mbm.setLightEnabled('3d', true) -- turn the light ON local meshesAfterLight = {} for i = 1, 3 do meshesAfterLight[i] = mesh:new('3d') meshesAfterLight[i]:load('crate.msh') -- classified lit right now end -- meshesBeforeLight (5 objects) stay unlit forever, even though the '3d' light is now on. -- meshesAfterLight (3 objects) are lit and will react to mbm.setAmbientLight/setDirectionalLight/etc. If you need previously-created objects to become lit (or unlit), you have to make them go through that same classification moment again — for example by calling :ref:`load ` (or the equivalent ``load``/``loadAsync`` method for :ref:`sprite `, :ref:`tile `, :ref:`particle ` or :ref:`backGround `) again after changing :ref:`setLightEnabled `, or by adding a fresh animation to the object. Simply toggling the light does not reach back and change shaders that already exist. .. Note:: In practice this means: decide and set lighting for a target with :ref:`setLightEnabled ` **before** creating/loading the objects that should be lit for that target. Light methods ------------- setLightEnabled ^^^^^^^^^^^^^^^ .. _setLightEnabled: .. data:: setLightEnabled(string target, boolean enabled) :noindex: Enable or disable lighting for a target. When disabled the target renders unlit, same as before lighting existed. :param string: **target** either ``'3d'`` or ``'2dw'``. :param boolean: **enabled** value. *Example:* .. code-block:: lua mbm.setLightEnabled('3d', true) resetLight ^^^^^^^^^^ .. data:: resetLight(string target) :noindex: Reset every light setting for a target back to its default values: disables lighting, restores the default ambient/directional colors and direction, clears the point-light list added through :ref:`addPointLight `, and restores the default requested max lights and selection mode. :param string: **target** either ``'3d'`` or ``'2dw'``. *Example:* .. code-block:: lua mbm.resetLight('3d') getLightState ^^^^^^^^^^^^^ .. data:: getLightState(string target) :noindex: Return a snapshot table describing the current light state of a target. :param string: **target** either ``'3d'`` or ``'2dw'``. :return: ``table`` with the fields below. | ``enabled`` - ``boolean``. | ``target`` - ``string``, ``'3d'`` or ``'2dw'``. | ``requestedMaxLights`` - ``number``. | ``supportedMaxLights`` - ``number``. | ``validatedMaxLights`` - ``number`` (``0`` if the requested value is invalid). | ``lightSelectionMode`` - ``string``. | ``ambientColor`` - ``{r, g, b, a}``. | ``directionalColor`` - ``{r, g, b, a}``. | ``directionalDirection`` - ``{x, y, z}`` (normalized). | ``pointColor`` - ``{r, g, b, a}`` of the legacy single point light slot. | ``pointPosition`` - ``{x, y, z}`` of the legacy single point light slot. | ``pointRadius`` - ``number`` of the legacy single point light slot. | ``pointLights`` - 1-indexed array of ``{position={x,y,z}, radius, color={r,g,b,a}}``, one per light added with :ref:`addPointLight `. *Example:* .. code-block:: lua local light = mbm.getLightState('3d') print(light.enabled, light.ambientColor.r) Default values ^^^^^^^^^^^^^^ | ``enabled = false`` | ``ambientColor = {r = 0.2, g = 0.2, b = 0.2, a = 1.0}`` | ``directionalColor = {r = 1.0, g = 1.0, b = 1.0, a = 1.0}`` | ``directionalDirection`` normalized ``{x = 0.0, y = -0.707, z = -0.707}`` | ``requestedMaxLights`` equals the engine's compiled maximum (currently ``4``) | ``lightSelectionMode = 'per_object_nearest'`` | point-light list (see :ref:`addPointLight `) starts empty Color channels passed to any light function below are silently clamped to the ``0..1`` range. Ambient & Directional Light --------------------------- setAmbientLight ^^^^^^^^^^^^^^^ .. data:: setAmbientLight(string target, table color) :noindex: Set the ambient light color of a target, applied uniformly regardless of light direction or distance. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **color** ``{r, g, b, *a}`` (``a`` optional, defaults to ``1.0``). *Example:* .. code-block:: lua mbm.setAmbientLight('3d', {r = 0.2, g = 0.2, b = 0.2, a = 1.0}) .. data:: setAmbientLight(string target, number r, number g, number b, number * a) :noindex: Same as above passing the color channels directly instead of a table. :param string: **target** either ``'3d'`` or ``'2dw'``. :param number: **r** red channel. :param number: **g** green channel. :param number: **b** blue channel. :param number: **a** alpha channel (optional, defaults to ``1.0``). *Example:* .. code-block:: lua mbm.setAmbientLight('3d', 0.2, 0.2, 0.2) setDirectionalLight ^^^^^^^^^^^^^^^^^^^ .. data:: setDirectionalLight(string target, table direction, table color) :noindex: Set the directional (diffuse) light of a target: a single light with a direction but no position, similar to sunlight. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **direction** ``{x, y, z}``. It is normalized internally; a zero-length direction silently falls back to the default direction instead of raising an error. :param table: **color** ``{r, g, b, *a}`` (``a`` optional, defaults to ``1.0``). *Example:* .. code-block:: lua mbm.setDirectionalLight('3d', {x = 0.0, y = -0.707, z = -0.707}, {r = 1.0, g = 1.0, b = 1.0, a = 1.0}) .. data:: setDirectionalLight(string target, number x, number y, number z, number r, number g, number b, number * a) :noindex: Same as above passing direction and color channels directly instead of tables. :param string: **target** either ``'3d'`` or ``'2dw'``. :param number: **x**, **y**, **z** direction. :param number: **r**, **g**, **b** color, **a** alpha (optional, defaults to ``1.0``). setDirectionalLightDirection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. data:: setDirectionalLightDirection(string target, table direction) :noindex: Update only the direction of the target's directional light, keeping its current color. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **direction** ``{x, y, z}`` (normalized internally, same zero-length fallback as above). .. data:: setDirectionalLightDirection(string target, number x, number y, number z) :noindex: Same as above passing the direction channels directly. setDirectionalLightColor ^^^^^^^^^^^^^^^^^^^^^^^^ .. data:: setDirectionalLightColor(string target, table color) :noindex: Update only the color of the target's directional light, keeping its current direction. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **color** ``{r, g, b, *a}`` (``a`` optional, defaults to ``1.0``). .. data:: setDirectionalLightColor(string target, number r, number g, number b, number * a) :noindex: Same as above passing the color channels directly. Point Light ----------- .. Note:: | The engine keeps **two independent point-light storages** per target: | 1. A single legacy point-light slot, mutated by :ref:`setPointLight ` and its related ``setPointLight*`` functions below. | 2. A growable list of point lights, appended to by :ref:`addPointLight ` and emptied by :ref:`clearPointLights `. There is no function to update or remove a single entry of this list by index. | :ref:`getSelectedPointLights ` uses the list when it is not empty. If the list is empty, it falls back to treating the legacy slot as a single candidate light. .. _setPointLight: setPointLight ^^^^^^^^^^^^^ .. data:: setPointLight(string target, table position, number radius, table color) :noindex: Set the legacy single point-light slot of a target (position, radius and color). :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **position** ``{x, y, z}`` (or a :ref:`vec3 `). :param number: **radius** of influence. Negative values are clamped to ``0``. :param table: **color** ``{r, g, b, *a}`` (``a`` optional, defaults to ``1.0``). *Example:* .. code-block:: lua mbm.setPointLight('3d', {x = 0, y = 0, z = 128}, 512, {r = 1, g = 1, b = 1, a = 1}) .. data:: setPointLight(string target, number x, number y, number z, number radius, number r, number g, number b, number * a) :noindex: Same as above passing position and color channels directly instead of tables. setPointLightPosition ^^^^^^^^^^^^^^^^^^^^^ .. data:: setPointLightPosition(string target, table position) :noindex: Update only the position of the legacy single point-light slot. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **position** ``{x, y, z}`` (or a :ref:`vec3 `). .. data:: setPointLightPosition(string target, number x, number y, number z) :noindex: Same as above passing the position channels directly. setPointLightRadius ^^^^^^^^^^^^^^^^^^^ .. data:: setPointLightRadius(string target, number radius) :noindex: Update only the radius of the legacy single point-light slot. Negative values are clamped to ``0``. :param string: **target** either ``'3d'`` or ``'2dw'``. :param number: **radius** of influence. setPointLightColor ^^^^^^^^^^^^^^^^^^ .. data:: setPointLightColor(string target, table color) :noindex: Update only the color of the legacy single point-light slot. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **color** ``{r, g, b, *a}`` (``a`` optional, defaults to ``1.0``). .. data:: setPointLightColor(string target, number r, number g, number b, number * a) :noindex: Same as above passing the color channels directly. .. _addPointLight: addPointLight ^^^^^^^^^^^^^ .. data:: addPointLight(string target, table position, number radius, table color) :noindex: Append a new point light to the target's point-light list. There is no fixed cap enforced when adding - only :ref:`getSelectedPointLights ` limits how many of them are actually used at render time (see :ref:`setRequestedMaxLights `). :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **position** ``{x, y, z}`` (or a :ref:`vec3 `). :param number: **radius** of influence. Negative values are clamped to ``0``. :param table: **color** ``{r, g, b, *a}`` (``a`` optional, defaults to ``1.0``). *Example:* .. code-block:: lua mbm.addPointLight('3d', {x = 100, y = 0, z = 0}, 300, {r = 1, g = 0, b = 0, a = 1}) mbm.addPointLight('3d', {x = -100, y = 0, z = 0}, 300, {r = 0, g = 0, b = 1, a = 1}) .. data:: addPointLight(string target, number x, number y, number z, number radius, number r, number g, number b, number * a) :noindex: Same as above passing position and color channels directly instead of tables. .. _clearPointLights: clearPointLights ^^^^^^^^^^^^^^^^ .. data:: clearPointLights(string target) :noindex: Remove every point light previously added with :ref:`addPointLight ` for a target. This is the only way to remove point lights from the list; there is no function to remove a single entry by index. :param string: **target** either ``'3d'`` or ``'2dw'``. *Example:* .. code-block:: lua mbm.clearPointLights('3d') Multi-light Limits & Selection ------------------------------ .. Note:: The engine renders a bounded number of point lights per object (shader arrays need a fixed size). There are three related numbers per target: what the game *requests*, what the active backend actually *supports*, and what ends up *validated* (the smaller of the two). .. _setRequestedMaxLights: setRequestedMaxLights ^^^^^^^^^^^^^^^^^^^^^ .. data:: setRequestedMaxLights(string target, number max_lights) :noindex: Request how many point lights should be considered per object for a target. Values below ``1`` are silently raised to ``1``. Requesting more than :ref:`getSupportedMaxLights ` raises a Lua error. :param string: **target** either ``'3d'`` or ``'2dw'``. :param number: **max lights** requested. *Example:* .. code-block:: lua mbm.setRequestedMaxLights('3d', 4) .. _getSupportedMaxLights: getSupportedMaxLights ^^^^^^^^^^^^^^^^^^^^^ .. data:: getSupportedMaxLights(string target) :noindex: Get the maximum number of point lights the active backend supports for a target (currently a fixed engine constant regardless of graphics backend). :param string: **target** either ``'3d'`` or ``'2dw'``. :return: ``number``. getValidatedMaxLights ^^^^^^^^^^^^^^^^^^^^^ .. data:: getValidatedMaxLights(string target) :noindex: Get the currently requested max lights re-validated against the backend's supported max lights for a target. :param string: **target** either ``'3d'`` or ``'2dw'``. :return: ``number``. setLightSelectionMode ^^^^^^^^^^^^^^^^^^^^^ .. data:: setLightSelectionMode(string target, string mode) :noindex: Set how point lights are selected per object for a target. Currently the only supported (and default) mode is ``'per_object_nearest'``. :param string: **target** either ``'3d'`` or ``'2dw'``. :param string: **mode**, exact string ``'per_object_nearest'``. .. _getSelectedPointLights: getSelectedPointLights ^^^^^^^^^^^^^^^^^^^^^^ .. data:: getSelectedPointLights(string target, table objectCenter, table objectBoundingAABB) :noindex: Compute which point lights (from the list added through :ref:`addPointLight `) would affect an object, following the target's selection mode. Lights whose distance to ``objectCenter`` is greater than their own radius plus the object's radius are discarded; the remaining ones are sorted by distance (nearest first) and truncated to ``min(validatedMaxLights, supportedMaxLights)`` entries. :param string: **target** either ``'3d'`` or ``'2dw'``. :param table: **objectCenter** ``{x, y, z}`` (or a :ref:`vec3 `). :param table: **objectBoundingAABB** ``{x, y, z}`` full size (not half-extents) of the object's bounding box. :return: 1-indexed ``table`` array, each entry shaped as ``{sourceIndex, distanceToObjectCenter, position={x,y,z}, radius, color={r,g,b,a}}``. *Example:* .. code-block:: lua local lights = mbm.getSelectedPointLights('3d', tMesh:getPos(), tMesh:getAABB()) for i, l in ipairs(lights) do print(i, l.sourceIndex, l.distanceToObjectCenter, l.radius) end