qstbak/Auspex/Auspex.Lighting.Internal/NativeLightHandle.cs
2026-08-17 20:25:32 +10:00

205 lines
5.7 KiB
C#

using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
namespace Auspex.Lighting.Internal;
internal sealed class NativeLightHandle : IDisposable
{
private unsafe GameLightData* data;
private Vector3 position;
private Quaternion rotation;
private Vector3 color;
private float intensity;
private float range;
private float lightAngle;
private float falloffAngle;
private AxLightFlags flags;
private AxFalloffType falloffType;
private float falloffPower;
private float shadowNear;
private float shadowFar;
private float charaShadowRange;
public unsafe static NativeLightHandle Create(Vector3 position, AxLightType lightType, AxLightConfig config)
{
GameLightData* ptr = IMemorySpace.GetDefaultSpace()->Malloc<GameLightData>(8uL);
if (LightNativeBridge.Spawn(ptr) == null)
{
IMemorySpace.Free(ptr, (ulong)sizeof(GameLightData));
throw new InvalidOperationException("Failed to spawn game light");
}
LightNativeBridge.Create(ptr);
LightNativeBridge.Finalize(ptr);
((long*)ptr)[7] |= 2L;
LightRenderData* renderObject = ptr->RenderObject;
if (renderObject == null)
{
IMemorySpace.Free(ptr, (ulong)sizeof(GameLightData));
throw new InvalidOperationException("Game did not create RenderObject for light");
}
renderObject->Transform = &ptr->Transform;
renderObject->EmissionType = lightType;
renderObject->Color = config.Color;
renderObject->Intensity = config.Intensity;
renderObject->Range = config.Range;
renderObject->FalloffType = config.FalloffType;
renderObject->Falloff = config.FalloffPower;
renderObject->LightAngle = config.LightAngle;
renderObject->FalloffAngle = config.FalloffAngle;
renderObject->CharacterShadowRange = config.CharaShadowRange;
renderObject->ShadowPlaneNear = config.ShadowNear;
renderObject->ShadowPlaneFar = config.ShadowFar;
renderObject->Flags = config.Flags;
LightNativeBridge.UpdateRange(renderObject);
Quaternion quaternion = config.Rotation ?? Quaternion.Identity;
Unsafe.Write<Vector3>((void*)(&ptr->Transform.Position), position);
Unsafe.Write<Quaternion>((void*)(&ptr->Transform.Rotation), quaternion);
ptr->Scale = Vector3.One;
return new NativeLightHandle
{
data = ptr,
position = position,
rotation = quaternion,
color = config.Color,
intensity = config.Intensity,
range = config.Range,
lightAngle = config.LightAngle,
falloffAngle = config.FalloffAngle,
flags = config.Flags,
falloffType = config.FalloffType,
falloffPower = config.FalloffPower,
shadowNear = config.ShadowNear,
shadowFar = config.ShadowFar,
charaShadowRange = config.CharaShadowRange
};
}
public unsafe void UpdatePosition(Vector3 newPosition)
{
if (data != null && !(position == newPosition))
{
position = newPosition;
Unsafe.Write<Vector3>((void*)(&data->Transform.Position), newPosition);
}
}
public unsafe void UpdateRotation(Quaternion newRotation)
{
if (data != null && !(rotation == newRotation))
{
rotation = newRotation;
Unsafe.Write<Quaternion>((void*)(&data->Transform.Rotation), newRotation);
}
}
public unsafe void UpdateColor(Vector3 newColor)
{
if (data != null && data->RenderObject != null && !(color == newColor))
{
color = newColor;
data->RenderObject->Color = newColor;
}
}
public unsafe void UpdateIntensity(float newIntensity)
{
if (data != null && data->RenderObject != null && intensity != newIntensity)
{
intensity = newIntensity;
data->RenderObject->Intensity = newIntensity;
}
}
public unsafe void UpdateRange(float newRange)
{
if (data != null && data->RenderObject != null && range != newRange)
{
range = newRange;
data->RenderObject->Range = newRange;
LightNativeBridge.UpdateRange(data->RenderObject);
}
}
public unsafe void UpdateFlags(AxLightFlags newFlags)
{
if (data != null && data->RenderObject != null && flags != newFlags)
{
flags = newFlags;
data->RenderObject->Flags = newFlags;
}
}
public unsafe void UpdateAngles(float newLightAngle, float newFalloffAngle)
{
if (data != null && data->RenderObject != null && (lightAngle != newLightAngle || falloffAngle != newFalloffAngle))
{
lightAngle = newLightAngle;
falloffAngle = newFalloffAngle;
data->RenderObject->LightAngle = newLightAngle;
data->RenderObject->FalloffAngle = newFalloffAngle;
}
}
public unsafe void UpdateFalloff(AxFalloffType newFalloffType, float newFalloffPower)
{
if (data != null && data->RenderObject != null && (falloffType != newFalloffType || falloffPower != newFalloffPower))
{
falloffType = newFalloffType;
falloffPower = newFalloffPower;
data->RenderObject->FalloffType = newFalloffType;
data->RenderObject->Falloff = newFalloffPower;
}
}
public unsafe void UpdateShadow(float newShadowNear, float newShadowFar, float newCharaShadowRange)
{
if (data != null && data->RenderObject != null && (shadowNear != newShadowNear || shadowFar != newShadowFar || charaShadowRange != newCharaShadowRange))
{
shadowNear = newShadowNear;
shadowFar = newShadowFar;
charaShadowRange = newCharaShadowRange;
data->RenderObject->ShadowPlaneNear = newShadowNear;
data->RenderObject->ShadowPlaneFar = newShadowFar;
data->RenderObject->CharacterShadowRange = newCharaShadowRange;
}
}
internal unsafe void Tick()
{
if (data != null)
{
LightNativeBridge.UpdateCulling(data);
LightNativeBridge.UpdateMaterial(data);
}
}
public unsafe void Dispose()
{
if (data != null)
{
GameLightVTable* virtualTable = data->VirtualTable;
if (virtualTable != null)
{
virtualTable->Cleanup(data);
virtualTable->Destructor(data, false);
}
data = null;
GC.SuppressFinalize(this);
}
}
}