Compare commits

..

7 Commits

Author SHA1 Message Date
Jarrod Doyle 65dda0194d
Fix #1 #2: Rewrite light gathering
It's now much neater, has support for SpotlightAndAmbient, and uses proper vhot hierarchy transforms
2024-12-23 18:01:33 +00:00
Jarrod Doyle 010757eb6f
Add light helper functions 2024-12-23 17:57:36 +00:00
Jarrod Doyle bf47578133
Tidy up mesh transform calculation 2024-12-23 17:54:52 +00:00
Jarrod Doyle 0f9467b8c4
Use new model joint application in meshbuilder 2024-12-23 17:51:25 +00:00
Jarrod Doyle a8bdf37097
Add joint transformation to modelfile 2024-12-23 17:46:45 +00:00
Jarrod Doyle 9c71529d8e
Add layer range check on AddLight 2024-12-23 17:42:03 +00:00
Jarrod Doyle e3aac88c17
Add SpotlightAndAmbient parsing 2024-12-23 17:40:50 +00:00
7 changed files with 193 additions and 135 deletions

View File

@ -240,9 +240,11 @@ public class WorldRep : IChunk
return bytes; return bytes;
} }
// TODO: This ONLY works for rgba (bpp = 4)!!!
public void AddLight(int layer, int x, int y, float r, float g, float b) public void AddLight(int layer, int x, int y, float r, float g, float b)
{ {
ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, nameof(layer));
ArgumentOutOfRangeException.ThrowIfGreaterThan(layer, Layers, nameof(layer));
var idx = (x + y * Width) * Bpp; var idx = (x + y * Width) * Bpp;
var pLayer = Pixels[layer]; var pLayer = Pixels[layer];
switch (Bpp) switch (Bpp)

View File

@ -166,6 +166,7 @@ public class DbFile
"P$AnimLight" => new PropertyChunk<PropAnimLight>(), "P$AnimLight" => new PropertyChunk<PropAnimLight>(),
"P$LightColo" => new PropertyChunk<PropLightColor>(), "P$LightColo" => new PropertyChunk<PropLightColor>(),
"P$Spotlight" => new PropertyChunk<PropSpotlight>(), "P$Spotlight" => new PropertyChunk<PropSpotlight>(),
"P$SpotAmb" => new PropertyChunk<PropSpotlightAndAmbient>(),
"P$RenderAlp" => new PropertyChunk<PropFloat>(), "P$RenderAlp" => new PropertyChunk<PropFloat>(),
"LD$MetaProp" => new LinkDataMetaProp(), "LD$MetaProp" => new LinkDataMetaProp(),
_ when entryName.StartsWith("L$") => new LinkChunk(), _ when entryName.StartsWith("L$") => new LinkChunk(),

View File

@ -103,6 +103,7 @@ public class ObjectHierarchy
AddProp<PropAnimLight>("P$AnimLight"); AddProp<PropAnimLight>("P$AnimLight");
AddProp<PropLightColor>("P$LightColo"); AddProp<PropLightColor>("P$LightColo");
AddProp<PropSpotlight>("P$Spotlight"); AddProp<PropSpotlight>("P$Spotlight");
AddProp<PropSpotlightAndAmbient>("P$SpotAmb");
} }
// TODO: Work out if there's some nice way to automatically decide if we inherit // TODO: Work out if there's some nice way to automatically decide if we inherit

View File

@ -288,6 +288,73 @@ public class ModelFile
} }
} }
// TODO: Apply transforms to normals and stuff
public void ApplyJoints(float[] joints)
{
// Build map of objects to their parent id
var objCount = Objects.Length;
var parentIds = new int[objCount];
for (var i = 0; i < objCount; i++)
{
parentIds[i] = -1;
}
for (var i = 0; i < objCount; i++)
{
var subObj = Objects[i];
var childIdx = subObj.Child;
while (childIdx != -1)
{
parentIds[childIdx] = i;
childIdx = Objects[childIdx].Next;
}
}
// Calculate base transforms for every subobj (including joint)
var subObjTransforms = new Matrix4x4[objCount];
for (var i = 0; i < objCount; i++)
{
var subObj = Objects[i];
var objTrans = Matrix4x4.Identity;
if (subObj.Joint != -1)
{
var ang = float.DegreesToRadians(joints[subObj.Joint]);
// TODO: Is this correct? Should I use a manual rotation matrix?
var jointRot = Matrix4x4.CreateFromYawPitchRoll(0, ang, 0);
objTrans = jointRot * subObj.Transform;
}
subObjTransforms[i] = objTrans;
}
// Apply sub object transforms
for (var i = 0; i < objCount; i++)
{
var subObj = Objects[i];
var transform = subObjTransforms[i];
// Build compound transformation
var parentId = parentIds[i];
while (parentId != -1)
{
transform *= subObjTransforms[parentId];
parentId = parentIds[parentId];
}
for (var j = 0; j < subObj.VhotCount; j++)
{
var v = VHots[subObj.VhotIdx + j];
v.Position = Vector3.Transform(v.Position, transform);
VHots[subObj.VhotIdx + j] = v;
}
for (var j = 0; j < subObj.PointCount; j++)
{
var v = Vertices[subObj.PointIdx + j];
Vertices[subObj.PointIdx + j] = Vector3.Transform(v, transform);
}
}
}
public bool TryGetVhot(VhotId id, out VHot vhot) public bool TryGetVhot(VhotId id, out VHot vhot)
{ {
foreach (var v in VHots) foreach (var v in VHots)

View File

@ -35,6 +35,30 @@ public class Light
}; };
} }
public void FixRadius()
{
if (Radius == 0)
{
Radius = float.MaxValue;
R2 = float.MaxValue;
}
}
public void ApplyTransforms(
Vector3 vhotLightPos,
Vector3 vhotLightDir,
Matrix4x4 translate,
Matrix4x4 rotate,
Matrix4x4 scale)
{
var transform = scale * rotate * translate;
vhotLightPos = Vector3.Transform(vhotLightPos, transform);
vhotLightDir = Vector3.Transform(vhotLightDir, transform);
Position = Vector3.Transform(Position, rotate) + vhotLightPos;
SpotlightDir = Vector3.Normalize(vhotLightDir - vhotLightPos);
}
public float StrengthAtPoint(Vector3 point, Plane plane) public float StrengthAtPoint(Vector3 point, Plane plane)
{ {
// Calculate light strength at a given point. As far as I can tell // Calculate light strength at a given point. As far as I can tell

View File

@ -202,10 +202,12 @@ public class LightMapper
return; return;
} }
var brightness = Math.Min(sz.X, 255.0f);
var light = new Light var light = new Light
{ {
Position = brush.position, Position = brush.position,
Color = Utils.HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)), Color = Utils.HsbToRgb(sz.Y, sz.Z, brightness),
Brightness = brightness,
Radius = float.MaxValue, Radius = float.MaxValue,
R2 = float.MaxValue, R2 = float.MaxValue,
LightTableIndex = lightTable.LightCount, LightTableIndex = lightTable.LightCount,
@ -219,79 +221,46 @@ public class LightMapper
{ {
// TODO: Handle PropSpotlightAndAmbient // TODO: Handle PropSpotlightAndAmbient
var id = (int)brush.brushInfo; var id = (int)brush.brushInfo;
var propScale = _hierarchy.GetProperty<PropVector>(id, "P$Scale");
var propAnimLight = _hierarchy.GetProperty<PropAnimLight>(id, "P$AnimLight", false); var propAnimLight = _hierarchy.GetProperty<PropAnimLight>(id, "P$AnimLight", false);
var propLight = _hierarchy.GetProperty<PropLight>(id, "P$Light", false); var propLight = _hierarchy.GetProperty<PropLight>(id, "P$Light", false);
var propLightColor = _hierarchy.GetProperty<PropLightColor>(id, "P$LightColo"); var propLightColor = _hierarchy.GetProperty<PropLightColor>(id, "P$LightColo");
var propSpotlight = _hierarchy.GetProperty<PropSpotlight>(id, "P$Spotlight"); var propSpotlight = _hierarchy.GetProperty<PropSpotlight>(id, "P$Spotlight");
var propSpotAmb = _hierarchy.GetProperty<PropSpotlightAndAmbient>(id, "P$SpotAmb");
var propModelName = _hierarchy.GetProperty<PropLabel>(id, "P$ModelName"); var propModelName = _hierarchy.GetProperty<PropLabel>(id, "P$ModelName");
var propJointPos = _hierarchy.GetProperty<PropJointPos>(id, "P$JointPos");
propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 }; propLightColor ??= new PropLightColor { Hue = 0, Saturation = 0 };
var joints = propJointPos?.Positions ?? [0, 0, 0, 0, 0, 0];
// TODO: Also apply scale? // Transform data
var rot = Matrix4x4.Identity; var translate = Matrix4x4.CreateTranslation(brush.position);
rot *= Matrix4x4.CreateRotationX(float.DegreesToRadians(brush.angle.X)); var rotate = Matrix4x4.Identity;
rot *= Matrix4x4.CreateRotationY(float.DegreesToRadians(brush.angle.Y)); rotate *= Matrix4x4.CreateRotationX(float.DegreesToRadians(brush.angle.X));
rot *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(brush.angle.Z)); rotate *= Matrix4x4.CreateRotationY(float.DegreesToRadians(brush.angle.Y));
rotate *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(brush.angle.Z));
var baseLight = new Light var scale = Matrix4x4.CreateScale(propScale?.value ?? Vector3.One);
{
Position = brush.position,
SpotlightDir = Vector3.Transform(-Vector3.UnitZ, rot),
SpotlightInnerAngle = -1.0f,
};
var vhotLightPos = Vector3.Zero;
var vhotLightDir = -Vector3.UnitZ;
if (propModelName != null) if (propModelName != null)
{ {
var resName = $"{propModelName.value.ToLower()}.bin"; var resName = $"{propModelName.value.ToLower()}.bin";
var modelPath = _campaign.GetResourcePath(ResourceType.Object, resName); var modelPath = _campaign.GetResourcePath(ResourceType.Object, resName);
if (modelPath != null) if (modelPath != null)
{ {
// TODO: Handle failing to find model more gracefully
var model = new ModelFile(modelPath); var model = new ModelFile(modelPath);
model.ApplyJoints(joints);
if (model.TryGetVhot(ModelFile.VhotId.LightPosition, out var vhot)) if (model.TryGetVhot(ModelFile.VhotId.LightPosition, out var vhot))
{ {
baseLight.Position += Vector3.Transform(vhot.Position - model.Header.Center, rot); vhotLightPos = vhot.Position - model.Header.Center;
} }
if (model.TryGetVhot(ModelFile.VhotId.LightDirection, out vhot)) if (model.TryGetVhot(ModelFile.VhotId.LightDirection, out vhot))
{ {
baseLight.SpotlightDir = Vector3.Transform(vhot.Position - model.Header.Center, rot); vhotLightDir = vhot.Position - model.Header.Center;
} }
} }
}
if (propSpotlight != null)
{
baseLight.Spotlight = true;
baseLight.SpotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.InnerAngle));
baseLight.SpotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.OuterAngle));
}
if (propLight != null && propLight.Brightness != 0f)
{
var light = new Light
{
Position = baseLight.Position + Vector3.Transform(propLight.Offset, rot),
Color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
InnerRadius = propLight.InnerRadius,
Radius = propLight.Radius,
R2 = propLight.Radius * propLight.Radius,
QuadLit = propLight.QuadLit,
Spotlight = baseLight.Spotlight,
SpotlightDir = baseLight.SpotlightDir,
SpotlightInnerAngle = baseLight.SpotlightInnerAngle,
SpotlightOuterAngle = baseLight.SpotlightOuterAngle,
LightTableIndex = lightTable.LightCount,
};
if (propLight.Radius == 0)
{
light.Radius = float.MaxValue;
light.R2 = float.MaxValue;
}
_lights.Add(light);
lightTable.AddLight(light.ToLightData(32.0f));
} }
if (propAnimLight != null) if (propAnimLight != null)
@ -301,26 +270,82 @@ public class LightMapper
var light = new Light var light = new Light
{ {
Position = baseLight.Position + Vector3.Transform(propAnimLight.Offset, rot), Position = propAnimLight.Offset,
Color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness), Color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness),
Brightness = propAnimLight.Brightness,
InnerRadius = propAnimLight.InnerRadius, InnerRadius = propAnimLight.InnerRadius,
Radius = propAnimLight.Radius, Radius = propAnimLight.Radius,
R2 = propAnimLight.Radius * propAnimLight.Radius, R2 = propAnimLight.Radius * propAnimLight.Radius,
QuadLit = propAnimLight.QuadLit, QuadLit = propAnimLight.QuadLit,
Spotlight = baseLight.Spotlight,
SpotlightDir = baseLight.SpotlightDir,
SpotlightInnerAngle = baseLight.SpotlightInnerAngle,
SpotlightOuterAngle = baseLight.SpotlightOuterAngle,
Anim = true,
ObjId = id, ObjId = id,
LightTableIndex = propAnimLight.LightTableLightIndex, LightTableIndex = propAnimLight.LightTableLightIndex,
Anim = true
}; };
if (propAnimLight.Radius == 0)
if (propSpotlight != null)
{ {
light.Radius = float.MaxValue; light.Spotlight = true;
light.R2 = float.MaxValue; light.SpotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.InnerAngle));
light.SpotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.OuterAngle));
} }
light.FixRadius();
light.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale);
_lights.Add(light);
lightTable.AddLight(light.ToLightData(32.0f));
}
if (propLight != null)
{
var light = new Light
{
Position = propLight.Offset,
Color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
Brightness = propLight.Brightness,
InnerRadius = propLight.InnerRadius,
Radius = propLight.Radius,
R2 = propLight.Radius * propLight.Radius,
QuadLit = propLight.QuadLit,
ObjId = id,
LightTableIndex = lightTable.LightCount,
};
if (propSpotAmb != null)
{
var spot = new Light
{
Position = light.Position,
Color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propSpotAmb.SpotBrightness),
InnerRadius = light.InnerRadius,
Radius = light.Radius,
R2 = light.R2,
QuadLit = light.QuadLit,
Spotlight = true,
SpotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotAmb.InnerAngle)),
SpotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotAmb.OuterAngle)),
ObjId = light.ObjId,
LightTableIndex = light.LightTableIndex,
};
light.LightTableIndex++; // Because we're inserting the spotlight part first
spot.FixRadius();
spot.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale);
_lights.Add(spot);
lightTable.AddLight(spot.ToLightData(32.0f));
}
else if (propSpotlight != null)
{
light.Spotlight = true;
light.SpotlightInnerAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.InnerAngle));
light.SpotlightOuterAngle = (float)Math.Cos(float.DegreesToRadians(propSpotlight.OuterAngle));
}
light.FixRadius();
light.ApplyTransforms(vhotLightPos, vhotLightDir, translate, rotate, scale);
_lights.Add(light); _lights.Add(light);
lightTable.AddLight(light.ToLightData(32.0f)); lightTable.AddLight(light.ToLightData(32.0f));
} }

View File

@ -113,6 +113,7 @@ public class MeshBuilder
} }
// Let's try and place an object :) // Let's try and place an object :)
// TODO: Handle failing to find model more gracefully
var modelName = modelNameProp.value.ToLower() + ".bin"; var modelName = modelNameProp.value.ToLower() + ".bin";
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName); var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
if (modelPath == null) if (modelPath == null)
@ -120,80 +121,15 @@ public class MeshBuilder
continue; continue;
} }
// TODO: Handle failing to find model more gracefully
var pos = brush.position;
var rot = brush.angle;
var scale = scaleProp?.value ?? Vector3.One;
var model = new ModelFile(modelPath); var model = new ModelFile(modelPath);
pos -= model.Header.Center; model.ApplyJoints(joints);
// Calculate base model transform // Calculate base model transform
var scalePart = Matrix4x4.CreateScale(scale); var transform = Matrix4x4.CreateScale(scaleProp?.value ?? Vector3.One);
var rotPart = Matrix4x4.Identity; transform *= Matrix4x4.CreateRotationX(float.DegreesToRadians(brush.angle.X));
rotPart *= Matrix4x4.CreateRotationX(float.DegreesToRadians(rot.X)); transform *= Matrix4x4.CreateRotationY(float.DegreesToRadians(brush.angle.Y));
rotPart *= Matrix4x4.CreateRotationY(float.DegreesToRadians(rot.Y)); transform *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(brush.angle.Z));
rotPart *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(rot.Z)); transform *= Matrix4x4.CreateTranslation(brush.position - model.Header.Center);
var transPart = Matrix4x4.CreateTranslation(pos);
var modelTrans = scalePart * rotPart * transPart;
// Calculate base transforms for every subobj (including joint)
var objCount = model.Objects.Length;
var subObjTransforms = new Matrix4x4[objCount];
for (var i = 0; i < objCount; i++)
{
var subObj = model.Objects[i];
var objTrans = Matrix4x4.Identity;
if (subObj.Joint != -1)
{
var ang = float.DegreesToRadians(joints[subObj.Joint]);
// TODO: Is this correct? Should I use a manual rotation matrix?
var jointRot = Matrix4x4.CreateFromYawPitchRoll(0, ang, 0);
objTrans = jointRot * subObj.Transform;
}
subObjTransforms[i] = objTrans;
}
// Build map of objects to their parent id
var parentIds = new int[objCount];
for (var i = 0; i < objCount; i++)
{
parentIds[i] = -1;
}
for (var i = 0; i < objCount; i++)
{
var subObj = model.Objects[i];
var childIdx = subObj.Child;
while (childIdx != -1)
{
parentIds[childIdx] = i;
childIdx = model.Objects[childIdx].Next;
}
}
// Apply sub object transforms + the base object transform to each vertex
for (var i = 0; i < objCount; i++)
{
var subObj = model.Objects[i];
var transform = subObjTransforms[i];
var parentId = parentIds[i];
while (parentId != -1)
{
transform *= subObjTransforms[parentId];
parentId = parentIds[parentId];
}
transform *= modelTrans;
var start = subObj.PointIdx;
var end = start + subObj.PointCount;
for (var j = start; j < end; j++)
{
var v = model.Vertices[j];
model.Vertices[j] = Vector3.Transform(v, transform);
}
}
// for each polygon slam its vertices and indices :) // for each polygon slam its vertices and indices :)
foreach (var poly in model.Polygons) foreach (var poly in model.Polygons)
@ -202,7 +138,9 @@ public class MeshBuilder
polyVertices.EnsureCapacity(poly.VertexCount); polyVertices.EnsureCapacity(poly.VertexCount);
foreach (var idx in poly.VertexIndices) foreach (var idx in poly.VertexIndices)
{ {
polyVertices.Add(model.Vertices[idx]); var vertex = model.Vertices[idx];
vertex = Vector3.Transform(vertex, transform);
polyVertices.Add(vertex);
} }
AddPolygon(polyVertices, SurfaceType.Solid); AddPolygon(polyVertices, SurfaceType.Solid);