205 lines
5.7 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|