Lower provider-authored world key lights

This commit is contained in:
Meta Vens 2026-07-15 08:36:03 +01:00
commit 63a41712c5
5 changed files with 107 additions and 4 deletions

View file

@ -29,6 +29,12 @@ own which semantic content belongs in a view, while EveUnity owns the native
layer mapping and subtracts only those layers from the camera's configured
culling mask.
World surfaces may also advertise a presentation-only directional key light
through `keyLightDirection`, `keyLightColor`, and `keyLightIntensity`.
EveUnity lowers those generic values into a transient Unity light and removes
it when the world camera lease ends. The provider owns the lighting design;
the light never becomes physics, visibility, or gameplay authority.
Retained provider feedback is lowered without becoming Unity authority.
`EveUnityFeedbackPresenter` emits each new `feedback.event` identity once, and
`EveUnityShotReceiptPresenter` does the same for `shot.receipt` trajectories.

View file

@ -33,6 +33,7 @@ namespace GameCult.Eve.UnityScene
private float _previousReflectionIntensity;
private Camera? _environmentCamera;
private CameraClearFlags _previousCameraClearFlags;
private GameObject? _keyLightObject;
public EveUnityPlayableWorldClientHost? Host
{
@ -124,6 +125,7 @@ namespace GameCult.Eve.UnityScene
var cameraComponent = camera.GetComponent<Camera>();
ApplyAmbientEnvironment(activeWorld, cameraComponent, skybox, reflection);
ApplyKeyLight(activeWorld);
if (cameraComponent != null && _renderPolicySource != null)
{
var cullingMask = cameraComponent.cullingMask;
@ -217,7 +219,14 @@ namespace GameCult.Eve.UnityScene
!IsFinite(world.AmbientLightG) ||
!IsFinite(world.AmbientLightB) ||
!IsFinite(world.AmbientLightIntensity) ||
!IsFinite(world.ReflectionIntensity))
!IsFinite(world.ReflectionIntensity) ||
!IsFinite(world.KeyLightDirectionX) ||
!IsFinite(world.KeyLightDirectionY) ||
!IsFinite(world.KeyLightDirectionZ) ||
!IsFinite(world.KeyLightColorR) ||
!IsFinite(world.KeyLightColorG) ||
!IsFinite(world.KeyLightColorB) ||
!IsFinite(world.KeyLightIntensity))
return false;
var assets = host.NativeAssetProvider;
if (!string.IsNullOrWhiteSpace(world.SkyboxAssetRef))
@ -245,6 +254,54 @@ namespace GameCult.Eve.UnityScene
return true;
}
private void ApplyKeyLight(EveUnityPlayableWorldProjection world)
{
var direction = new Vector3(
world.KeyLightDirectionX,
world.KeyLightDirectionY,
world.KeyLightDirectionZ);
if (world.KeyLightIntensity <= 0f || direction.sqrMagnitude <= 0.000001f)
{
ReleaseKeyLight();
return;
}
if (_keyLightObject == null)
{
_keyLightObject = new GameObject("Eve World Key Light");
_keyLightObject.transform.SetParent(transform, worldPositionStays: false);
var created = _keyLightObject.AddComponent<Light>();
created.type = LightType.Directional;
created.shadows = LightShadows.None;
}
var light = _keyLightObject.GetComponent<Light>();
light.color = new Color(
Mathf.Max(0f, world.KeyLightColorR),
Mathf.Max(0f, world.KeyLightColorG),
Mathf.Max(0f, world.KeyLightColorB),
1f);
light.intensity = Mathf.Max(0f, world.KeyLightIntensity);
var normalized = direction.normalized;
var up = Mathf.Abs(Vector3.Dot(normalized, Vector3.up)) > 0.999f
? Vector3.forward
: Vector3.up;
_keyLightObject.transform.rotation = Quaternion.LookRotation(normalized, up);
_keyLightObject.SetActive(true);
}
private void ReleaseKeyLight()
{
if (_keyLightObject == null)
return;
_keyLightObject.SetActive(false);
if (Application.isPlaying)
Destroy(_keyLightObject);
else
DestroyImmediate(_keyLightObject);
_keyLightObject = null;
}
private static bool IsFinite(float value) => !float.IsNaN(value) && !float.IsInfinity(value);
private void ApplyAmbientEnvironment(
@ -307,6 +364,7 @@ namespace GameCult.Eve.UnityScene
private void RestoreAmbientEnvironment()
{
ReleaseKeyLight();
if (!_ownsAmbientEnvironment) return;
var environmentChanged = !ReferenceEquals(RenderSettings.skybox, _previousSkybox);
RenderSettings.ambientMode = _previousAmbientMode;

View file

@ -142,7 +142,10 @@ namespace GameCult.Eve.UnityScene
worldRoot.GetProp("lookModel"),
worldRoot.GetProp("skyboxAssetRef"),
worldRoot.GetProp("reflectionAssetRef"),
ParseFloat(worldRoot.GetProp("reflectionIntensity"), 1f));
ParseFloat(worldRoot.GetProp("reflectionIntensity"), 1f),
ParseVector3(worldRoot.GetProp("keyLightDirection")),
ParseVector3(worldRoot.GetProp("keyLightColor")),
ParseFloat(worldRoot.GetProp("keyLightIntensity"), 0f));
}
private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component)
@ -369,7 +372,10 @@ namespace GameCult.Eve.UnityScene
string lookModel = "",
string skyboxAssetRef = "",
string reflectionAssetRef = "",
float reflectionIntensity = 1f)
float reflectionIntensity = 1f,
(float x, float y, float z) keyLightDirection = default,
(float r, float g, float b) keyLightColor = default,
float keyLightIntensity = 0f)
{
WorldRootId = worldRootId ?? "";
StatePointerId = statePointerId ?? "";
@ -406,6 +412,13 @@ namespace GameCult.Eve.UnityScene
SkyboxAssetRef = skyboxAssetRef ?? "";
ReflectionAssetRef = reflectionAssetRef ?? "";
ReflectionIntensity = reflectionIntensity;
KeyLightDirectionX = keyLightDirection.x;
KeyLightDirectionY = keyLightDirection.y;
KeyLightDirectionZ = keyLightDirection.z;
KeyLightColorR = keyLightColor.r;
KeyLightColorG = keyLightColor.g;
KeyLightColorB = keyLightColor.b;
KeyLightIntensity = keyLightIntensity;
}
public string WorldRootId { get; }
@ -468,6 +481,20 @@ namespace GameCult.Eve.UnityScene
public float ReflectionIntensity { get; }
public float KeyLightDirectionX { get; }
public float KeyLightDirectionY { get; }
public float KeyLightDirectionZ { get; }
public float KeyLightColorR { get; }
public float KeyLightColorG { get; }
public float KeyLightColorB { get; }
public float KeyLightIntensity { get; }
public string FocusCommand { get; }
public string TargetCommand { get; }

View file

@ -1159,6 +1159,14 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(RenderSettings.customReflectionTexture, Is.SameAs(reflectionCubemap));
Assert.That(RenderSettings.reflectionIntensity, Is.EqualTo(1f).Within(0.001f));
Assert.That(camera.clearFlags, Is.EqualTo(CameraClearFlags.Skybox));
var keyLightTransform = hostObject.transform.Find("Eve World Key Light");
Assert.That(keyLightTransform, Is.Not.Null);
var keyLight = keyLightTransform.GetComponent<Light>();
Assert.That(keyLight.type, Is.EqualTo(LightType.Directional));
Assert.That(keyLight.intensity, Is.EqualTo(0.75f).Within(0.001f));
Assert.That(keyLight.color, Is.EqualTo(new Color(1f, 0.95f, 0.9f, 1f)));
Assert.That(Vector3.Dot(keyLightTransform.forward, new Vector3(0.4f, -1f, 0.25f).normalized),
Is.GreaterThan(0.999f));
Assert.That(nativeAssets.LastBinding?.AssetRef, Is.EqualTo("texture.environment.reflection"));
Assert.That(nativeAssets.LastBinding?.EntityKind, Is.Empty);
@ -1210,6 +1218,7 @@ namespace GameCult.Eve.UnityScene.Tests
Is.EqualTo(10f * (1f - Mathf.Exp(-0.5f))).Within(0.001f));
rig.CameraTransform = null;
Assert.That(host.ActiveCameraTransform, Is.Null);
Assert.That(hostObject.transform.Find("Eve World Key Light"), Is.Null);
aim.RefreshNow();
Assert.That(aim.ViewDotVisible, Is.False);
Assert.That(RenderSettings.ambientMode, Is.EqualTo(ambientMode));
@ -1895,6 +1904,9 @@ namespace GameCult.Eve.UnityScene.Tests
["skyboxAssetRef"] = skyboxAssetRef,
["reflectionAssetRef"] = reflectionAssetRef,
["reflectionIntensity"] = "1",
["keyLightDirection"] = "0.4,-1,0.25",
["keyLightColor"] = "1,0.95,0.9",
["keyLightIntensity"] = "0.75",
["excludedRenderChannels"] = "map",
["playerEntityId"] = "player-vanguard",
["movementCommand"] = "aetheria.daemon.move_intent",

View file

@ -1,7 +1,7 @@
{
"name": "org.gamecult.eve.unity-scene",
"displayName": "Eve Unity Scene Lowering",
"version": "0.3.26",
"version": "0.3.27",
"unity": "2021.3",
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
"dependencies": {