using System.Collections.Generic; using System.Numerics; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; namespace AcDream.Core.Lighting; /// /// Converts a 's Lights dictionary (dat-level /// records) into runtime /// instances the can consume. /// /// /// Retail fields (r13 §1): /// /// ViewSpaceLocation: local Frame relative to the owning part. /// Color: packed ARGB. Alpha is ignored; channels go through /255. /// Intensity: multiplies color for final diffuse. /// Falloff: world metres — acts as the hard cutoff. /// ConeAngle: radians; 0 = point, >0 = spot cone. /// /// /// public static class LightInfoLoader { /// /// Extract all lights from a Setup, positioned in the entity's /// world frame (via + /// ). The dat's per-light Frame is /// treated as a local offset relative to the entity root; acdream /// doesn't yet transform through the animated part chain (retail's /// hand-held torches), so held lights render at the entity root /// until the animation hook layer handles per-part placement. /// public static IReadOnlyList Load( Setup setup, uint ownerId, Vector3 entityPosition, Quaternion entityRotation) { var results = new List(); if (setup?.Lights is null || setup.Lights.Count == 0) return results; foreach (var kvp in setup.Lights) { var info = kvp.Value; if (info is null) continue; // Local Frame offset into world space. Vector3 localOffset = Vector3.Zero; Quaternion localRot = Quaternion.Identity; if (info.ViewSpaceLocation is not null) { localOffset = new Vector3( info.ViewSpaceLocation.Origin.X, info.ViewSpaceLocation.Origin.Y, info.ViewSpaceLocation.Origin.Z); localRot = new Quaternion( info.ViewSpaceLocation.Orientation.X, info.ViewSpaceLocation.Orientation.Y, info.ViewSpaceLocation.Orientation.Z, info.ViewSpaceLocation.Orientation.W); } // Transform local offset into world space via the entity's // rotation + translation. No per-part chain yet — held // torches track the entity's root for now. Vector3 worldPos = entityPosition + Vector3.Transform(localOffset, entityRotation); Quaternion worldRot = entityRotation * localRot; Vector3 forward = Vector3.Transform(Vector3.UnitY, worldRot); var light = new LightSource { Kind = info.ConeAngle > 0f ? LightKind.Spot : LightKind.Point, WorldPosition = worldPos, WorldForward = forward, ColorLinear = new Vector3( (info.Color?.Red ?? 255) / 255f, (info.Color?.Green ?? 255) / 255f, (info.Color?.Blue ?? 255) / 255f), Intensity = info.Intensity, Range = info.Falloff, ConeAngle = info.ConeAngle, OwnerId = ownerId, IsLit = true, }; results.Add(light); } return results; } }