Compare commits
No commits in common. "b34131f3b5ca429eabeecc58d89e1e032ccb478f" and "b3a71e68276ba66d25cab9ac7bdfcb72923cd1b4" have entirely different histories.
b34131f3b5
...
b3a71e6827
|
@ -500,27 +500,3 @@ public class PropSpotlightAndAmbient : Property
|
||||||
writer.Write(SpotBrightness);
|
writer.Write(SpotBrightness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PropJointPos : Property
|
|
||||||
{
|
|
||||||
public float[] Positions;
|
|
||||||
|
|
||||||
public override void Read(BinaryReader reader)
|
|
||||||
{
|
|
||||||
base.Read(reader);
|
|
||||||
Positions = new float[6];
|
|
||||||
for (var i = 0; i < 6; i++)
|
|
||||||
{
|
|
||||||
Positions[i] = reader.ReadSingle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(BinaryWriter writer)
|
|
||||||
{
|
|
||||||
base.Write(writer);
|
|
||||||
foreach (var position in Positions)
|
|
||||||
{
|
|
||||||
writer.Write(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ public class RendParams : IChunk
|
||||||
|
|
||||||
public string palette;
|
public string palette;
|
||||||
public Vector3 ambientLight;
|
public Vector3 ambientLight;
|
||||||
public bool useSunlight;
|
public int useSunlight;
|
||||||
public SunlightMode sunlightMode;
|
public SunlightMode sunlightMode;
|
||||||
public Vector3 sunlightDirection;
|
public Vector3 sunlightDirection;
|
||||||
public float sunlightHue;
|
public float sunlightHue;
|
||||||
|
@ -31,8 +31,7 @@ public class RendParams : IChunk
|
||||||
{
|
{
|
||||||
palette = reader.ReadNullString(16);
|
palette = reader.ReadNullString(16);
|
||||||
ambientLight = reader.ReadVec3();
|
ambientLight = reader.ReadVec3();
|
||||||
useSunlight = reader.ReadBoolean();
|
useSunlight = reader.ReadInt32();
|
||||||
reader.ReadBytes(3);
|
|
||||||
sunlightMode = (SunlightMode)reader.ReadUInt32();
|
sunlightMode = (SunlightMode)reader.ReadUInt32();
|
||||||
sunlightDirection = reader.ReadVec3();
|
sunlightDirection = reader.ReadVec3();
|
||||||
sunlightHue = reader.ReadSingle();
|
sunlightHue = reader.ReadSingle();
|
||||||
|
@ -59,7 +58,6 @@ public class RendParams : IChunk
|
||||||
writer.WriteNullString(palette, 16);
|
writer.WriteNullString(palette, 16);
|
||||||
writer.WriteVec3(ambientLight);
|
writer.WriteVec3(ambientLight);
|
||||||
writer.Write(useSunlight);
|
writer.Write(useSunlight);
|
||||||
writer.Write(new byte[3]);
|
|
||||||
writer.Write((uint)sunlightMode);
|
writer.Write((uint)sunlightMode);
|
||||||
writer.WriteVec3(sunlightDirection);
|
writer.WriteVec3(sunlightDirection);
|
||||||
writer.Write(sunlightHue);
|
writer.Write(sunlightHue);
|
||||||
|
|
|
@ -155,9 +155,6 @@ public class DbFile
|
||||||
"P$ModelName" => new PropertyChunk<PropLabel>(),
|
"P$ModelName" => new PropertyChunk<PropLabel>(),
|
||||||
"P$Scale" => new PropertyChunk<PropVector>(),
|
"P$Scale" => new PropertyChunk<PropVector>(),
|
||||||
"P$RenderTyp" => new PropertyChunk<PropRenderType>(),
|
"P$RenderTyp" => new PropertyChunk<PropRenderType>(),
|
||||||
"P$JointPos" => new PropertyChunk<PropJointPos>(),
|
|
||||||
"P$Immobile" => new PropertyChunk<PropBool>(),
|
|
||||||
"P$StatShad" => new PropertyChunk<PropBool>(),
|
|
||||||
"P$OTxtRepr0" => new PropertyChunk<PropString>(),
|
"P$OTxtRepr0" => new PropertyChunk<PropString>(),
|
||||||
"P$OTxtRepr1" => new PropertyChunk<PropString>(),
|
"P$OTxtRepr1" => new PropertyChunk<PropString>(),
|
||||||
"P$OTxtRepr2" => new PropertyChunk<PropString>(),
|
"P$OTxtRepr2" => new PropertyChunk<PropString>(),
|
||||||
|
|
|
@ -91,9 +91,6 @@ public class ObjectHierarchy
|
||||||
AddProp<PropLabel>("P$ModelName");
|
AddProp<PropLabel>("P$ModelName");
|
||||||
AddProp<PropVector>("P$Scale");
|
AddProp<PropVector>("P$Scale");
|
||||||
AddProp<PropRenderType>("P$RenderTyp");
|
AddProp<PropRenderType>("P$RenderTyp");
|
||||||
AddProp<PropJointPos>("P$JointPos");
|
|
||||||
AddProp<PropBool>("P$Immobile");
|
|
||||||
AddProp<PropBool>("P$StatShad");
|
|
||||||
AddProp<PropString>("P$OTxtRepr0");
|
AddProp<PropString>("P$OTxtRepr0");
|
||||||
AddProp<PropString>("P$OTxtRepr1");
|
AddProp<PropString>("P$OTxtRepr1");
|
||||||
AddProp<PropString>("P$OTxtRepr2");
|
AddProp<PropString>("P$OTxtRepr2");
|
||||||
|
|
|
@ -92,54 +92,6 @@ public class ModelFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SubObject
|
|
||||||
{
|
|
||||||
public string Name;
|
|
||||||
public byte Type;
|
|
||||||
public int Joint;
|
|
||||||
public float MinJointValue;
|
|
||||||
public float MaxJointValue;
|
|
||||||
public Matrix4x4 Transform;
|
|
||||||
public short Child;
|
|
||||||
public short Next;
|
|
||||||
public ushort VhotIdx;
|
|
||||||
public ushort VhotCount;
|
|
||||||
public ushort PointIdx;
|
|
||||||
public ushort PointCount;
|
|
||||||
public ushort LightIdx;
|
|
||||||
public ushort LightCount;
|
|
||||||
public ushort NormalIdx;
|
|
||||||
public ushort NormalCount;
|
|
||||||
public ushort NodeIdx;
|
|
||||||
public ushort NodeCount;
|
|
||||||
|
|
||||||
public SubObject(BinaryReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.ReadNullString(8);
|
|
||||||
Type = reader.ReadByte();
|
|
||||||
Joint = reader.ReadInt32();
|
|
||||||
MinJointValue = reader.ReadSingle();
|
|
||||||
MaxJointValue = reader.ReadSingle();
|
|
||||||
var v1 = reader.ReadVec3();
|
|
||||||
var v2 = reader.ReadVec3();
|
|
||||||
var v3 = reader.ReadVec3();
|
|
||||||
var v4 = reader.ReadVec3();
|
|
||||||
Transform = new Matrix4x4(v1.X, v1.Y, v1.Z, 0, v2.X, v2.Y, v2.Z, 0, v3.X, v3.Y, v3.Z, 0, v4.X, v4.Y, v4.Z, 1);
|
|
||||||
Child = reader.ReadInt16();
|
|
||||||
Next = reader.ReadInt16();
|
|
||||||
VhotIdx = reader.ReadUInt16();
|
|
||||||
VhotCount = reader.ReadUInt16();
|
|
||||||
PointIdx = reader.ReadUInt16();
|
|
||||||
PointCount = reader.ReadUInt16();
|
|
||||||
LightIdx = reader.ReadUInt16();
|
|
||||||
LightCount = reader.ReadUInt16();
|
|
||||||
NormalIdx = reader.ReadUInt16();
|
|
||||||
NormalCount = reader.ReadUInt16();
|
|
||||||
NodeIdx = reader.ReadUInt16();
|
|
||||||
NodeCount = reader.ReadUInt16();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Polygon
|
public struct Polygon
|
||||||
{
|
{
|
||||||
public ushort Index;
|
public ushort Index;
|
||||||
|
@ -231,7 +183,6 @@ public class ModelFile
|
||||||
public Polygon[] Polygons { get; }
|
public Polygon[] Polygons { get; }
|
||||||
public Material[] Materials { get; }
|
public Material[] Materials { get; }
|
||||||
public VHot[] VHots { get; }
|
public VHot[] VHots { get; }
|
||||||
public SubObject[] Objects { get; }
|
|
||||||
|
|
||||||
public ModelFile(string filename)
|
public ModelFile(string filename)
|
||||||
{
|
{
|
||||||
|
@ -280,12 +231,6 @@ public class ModelFile
|
||||||
{
|
{
|
||||||
VHots[i] = new VHot(reader);
|
VHots[i] = new VHot(reader);
|
||||||
}
|
}
|
||||||
stream.Seek(Header.ObjectOffset, SeekOrigin.Begin);
|
|
||||||
Objects = new SubObject[Header.ObjectCount];
|
|
||||||
for (var i = 0; i < Objects.Length; i++)
|
|
||||||
{
|
|
||||||
Objects[i] = new SubObject(reader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetVhot(VhotId id, out VHot vhot)
|
public bool TryGetVhot(VhotId id, out VHot vhot)
|
||||||
|
|
|
@ -8,23 +8,20 @@ namespace KeepersCompound.Lightmapper;
|
||||||
|
|
||||||
public class LightMapper
|
public class LightMapper
|
||||||
{
|
{
|
||||||
// The objcast element of sunlight is ignored, we just care if it's quadlit
|
private enum SurfaceType
|
||||||
private struct SunSettings
|
|
||||||
{
|
{
|
||||||
public bool Enabled;
|
Solid,
|
||||||
public bool QuadLit;
|
Sky,
|
||||||
public Vector3 Direction;
|
Water,
|
||||||
public Vector3 Color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Settings
|
private class Settings
|
||||||
{
|
{
|
||||||
public Vector3 AmbientLight;
|
public Vector3 AmbientLight;
|
||||||
public bool Hdr;
|
public bool Hdr;
|
||||||
public SoftnessMode MultiSampling;
|
public SoftnessMode MultiSampling;
|
||||||
public float MultiSamplingCenterWeight;
|
public float MultiSamplingCenterWeight;
|
||||||
public bool LightmappedWater;
|
public bool LightmappedWater;
|
||||||
public SunSettings Sunlight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourcePathManager.CampaignResources _campaign;
|
private ResourcePathManager.CampaignResources _campaign;
|
||||||
|
@ -33,7 +30,7 @@ public class LightMapper
|
||||||
private ObjectHierarchy _hierarchy;
|
private ObjectHierarchy _hierarchy;
|
||||||
private Raytracer _scene;
|
private Raytracer _scene;
|
||||||
private List<Light> _lights;
|
private List<Light> _lights;
|
||||||
private SurfaceType[] _triangleTypeMap;
|
private List<SurfaceType> _triangleTypeMap;
|
||||||
|
|
||||||
public LightMapper(
|
public LightMapper(
|
||||||
string installPath,
|
string installPath,
|
||||||
|
@ -45,17 +42,9 @@ public class LightMapper
|
||||||
_misPath = _campaign.GetResourcePath(ResourceType.Mission, missionName);
|
_misPath = _campaign.GetResourcePath(ResourceType.Mission, missionName);
|
||||||
_mission = Timing.TimeStage("Parse DB", () => new DbFile(_misPath));
|
_mission = Timing.TimeStage("Parse DB", () => new DbFile(_misPath));
|
||||||
_hierarchy = Timing.TimeStage("Build Hierarchy", BuildHierarchy);
|
_hierarchy = Timing.TimeStage("Build Hierarchy", BuildHierarchy);
|
||||||
|
_triangleTypeMap = [];
|
||||||
|
_scene = Timing.TimeStage("Build Scene", BuildRaytracingScene);
|
||||||
_lights = [];
|
_lights = [];
|
||||||
|
|
||||||
var mesh = Timing.TimeStage("Build Mesh", BuildMesh);
|
|
||||||
_triangleTypeMap = mesh.TriangleSurfaceMap;
|
|
||||||
_scene = Timing.TimeStage("Build RT Scene", () =>
|
|
||||||
{
|
|
||||||
var rt = new Raytracer();
|
|
||||||
rt.AddMesh(new TriangleMesh(mesh.Vertices, mesh.Indices));
|
|
||||||
rt.CommitScene();
|
|
||||||
return rt;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Light()
|
public void Light()
|
||||||
|
@ -68,14 +57,6 @@ public class LightMapper
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sunlightSettings = new SunSettings()
|
|
||||||
{
|
|
||||||
Enabled = rendParams.useSunlight,
|
|
||||||
QuadLit = rendParams.sunlightMode is RendParams.SunlightMode.QuadUnshadowed or RendParams.SunlightMode.QuadObjcastShadows,
|
|
||||||
Direction = rendParams.sunlightDirection,
|
|
||||||
Color = Utils.HsbToRgb(rendParams.sunlightHue, rendParams.sunlightSaturation, rendParams.sunlightBrightness),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: lmParams LightmappedWater doesn't mean the game will actually *use* the lightmapped water hmm
|
// TODO: lmParams LightmappedWater doesn't mean the game will actually *use* the lightmapped water hmm
|
||||||
var settings = new Settings
|
var settings = new Settings
|
||||||
{
|
{
|
||||||
|
@ -84,7 +65,6 @@ public class LightMapper
|
||||||
MultiSampling = lmParams.ShadowSoftness,
|
MultiSampling = lmParams.ShadowSoftness,
|
||||||
MultiSamplingCenterWeight = lmParams.CenterWeight,
|
MultiSamplingCenterWeight = lmParams.CenterWeight,
|
||||||
LightmappedWater = lmParams.LightmappedWater,
|
LightmappedWater = lmParams.LightmappedWater,
|
||||||
Sunlight = sunlightSettings,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Timing.TimeStage("Gather Lights", BuildLightList);
|
Timing.TimeStage("Gather Lights", BuildLightList);
|
||||||
|
@ -129,21 +109,70 @@ public class LightMapper
|
||||||
return new ObjectHierarchy(_mission);
|
return new ObjectHierarchy(_mission);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mesh BuildMesh()
|
private Raytracer BuildRaytracingScene()
|
||||||
{
|
{
|
||||||
var meshBuilder = new MeshBuilder();
|
|
||||||
|
|
||||||
// TODO: Should this throw?
|
// TODO: Should this throw?
|
||||||
// TODO: Only do object polys if objcast lighting?
|
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep))
|
||||||
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep) ||
|
|
||||||
!_mission.TryGetChunk<BrList>("BRLIST", out var brList))
|
|
||||||
{
|
{
|
||||||
return meshBuilder.Build();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
meshBuilder.AddWorldRepPolys(worldRep);
|
var vertices = new List<Vector3>();
|
||||||
meshBuilder.AddObjectPolys(brList, _hierarchy, _campaign);
|
var indices = new List<int>();
|
||||||
return meshBuilder.Build();
|
foreach (var cell in worldRep.Cells)
|
||||||
|
{
|
||||||
|
var numPolys = cell.PolyCount;
|
||||||
|
var numRenderPolys = cell.RenderPolyCount;
|
||||||
|
var numPortalPolys = cell.PortalPolyCount;
|
||||||
|
|
||||||
|
// There's nothing to render
|
||||||
|
if (numRenderPolys == 0 || numPortalPolys >= numPolys)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var solidPolys = numPolys - numPortalPolys;
|
||||||
|
var cellIdxOffset = 0;
|
||||||
|
for (var polyIdx = 0; polyIdx < numRenderPolys; polyIdx++)
|
||||||
|
{
|
||||||
|
var poly = cell.Polys[polyIdx];
|
||||||
|
var meshIndexOffset = vertices.Count;
|
||||||
|
var numPolyVertices = poly.VertexCount;
|
||||||
|
for (var j = 0; j < numPolyVertices; j++)
|
||||||
|
{
|
||||||
|
var vertex = cell.Vertices[cell.Indices[cellIdxOffset + j]];
|
||||||
|
vertices.Add(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to know what type of surface this poly is so we can map Embree primitive IDs to surface
|
||||||
|
// types
|
||||||
|
var renderPoly = cell.RenderPolys[polyIdx];
|
||||||
|
var primType = SurfaceType.Solid;
|
||||||
|
if (renderPoly.TextureId == 249)
|
||||||
|
{
|
||||||
|
primType = SurfaceType.Sky;
|
||||||
|
} else if (polyIdx >= solidPolys)
|
||||||
|
{
|
||||||
|
primType = SurfaceType.Water;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cell polygons are n-sided, but fortunately they're convex so we can just do a fan triangulation
|
||||||
|
for (var j = 1; j < numPolyVertices - 1; j++)
|
||||||
|
{
|
||||||
|
indices.Add(meshIndexOffset);
|
||||||
|
indices.Add(meshIndexOffset + j);
|
||||||
|
indices.Add(meshIndexOffset + j + 1);
|
||||||
|
_triangleTypeMap.Add(primType);
|
||||||
|
}
|
||||||
|
|
||||||
|
cellIdxOffset += cell.Polys[polyIdx].VertexCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rt = new Raytracer();
|
||||||
|
rt.AddMesh(new TriangleMesh([.. vertices], [.. indices]));
|
||||||
|
rt.CommitScene();
|
||||||
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildLightList()
|
private void BuildLightList()
|
||||||
|
@ -305,8 +334,6 @@ public class LightMapper
|
||||||
|
|
||||||
private void SetCellLightIndices()
|
private void SetCellLightIndices()
|
||||||
{
|
{
|
||||||
// TODO: Doors aren't blocking lights. Need to do some cell traversal to remove light indices :(
|
|
||||||
|
|
||||||
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep))
|
if (!_mission.TryGetChunk<WorldRep>("WREXT", out var worldRep))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -327,14 +354,12 @@ public class LightMapper
|
||||||
// The OG lightmapper uses the cell traversal to work out all the cells that
|
// The OG lightmapper uses the cell traversal to work out all the cells that
|
||||||
// are actually visited. We're a lot more coarse and just say if a cell is
|
// are actually visited. We're a lot more coarse and just say if a cell is
|
||||||
// in range then we potentially affect the lighting in the cell and add it
|
// in range then we potentially affect the lighting in the cell and add it
|
||||||
// to the list.
|
// to the list. Cells already contain their sphere bounds so we just use
|
||||||
// There's a soft length limit here of 96 due to the runtime object shadow
|
// that for now, but a tighter AABB is another option.
|
||||||
// cache, so we want this to be as minimal as possible. Additionally large
|
var cellSphere = new MathUtils.Sphere(cell.SphereCenter, cell.SphereRadius);
|
||||||
// lists actually cause performance issues!
|
|
||||||
var cellAabb = new MathUtils.Aabb(cell.Vertices);
|
|
||||||
foreach (var light in _lights)
|
foreach (var light in _lights)
|
||||||
{
|
{
|
||||||
if (MathUtils.Intersects(new MathUtils.Sphere(light.Position, light.Radius), cellAabb))
|
if (MathUtils.Intersects(cellSphere, new MathUtils.Sphere(light.Position, light.Radius)))
|
||||||
{
|
{
|
||||||
cell.LightIndexCount++;
|
cell.LightIndexCount++;
|
||||||
cell.LightIndices.Add((ushort)light.LightTableIndex);
|
cell.LightIndices.Add((ushort)light.LightTableIndex);
|
||||||
|
@ -411,109 +436,48 @@ public class LightMapper
|
||||||
}
|
}
|
||||||
var planeMapper = new MathUtils.PlanePointMapper(plane.Normal, vs[0], vs[1]);
|
var planeMapper = new MathUtils.PlanePointMapper(plane.Normal, vs[0], vs[1]);
|
||||||
var v2ds = planeMapper.MapTo2d(vs);
|
var v2ds = planeMapper.MapTo2d(vs);
|
||||||
|
|
||||||
var (texU, texV) = renderPoly.TextureVectors;
|
|
||||||
var (offsets, weights) =
|
|
||||||
GetTraceOffsetsAndWeights(settings.MultiSampling, texU, texV, settings.MultiSamplingCenterWeight);
|
|
||||||
var (quadOffsets, quadWeights) = settings.MultiSampling == SoftnessMode.HighFourPoint
|
|
||||||
? (offsets, weights)
|
|
||||||
: GetTraceOffsetsAndWeights(SoftnessMode.HighFourPoint, texU, texV, settings.MultiSamplingCenterWeight);
|
|
||||||
|
|
||||||
for (var y = 0; y < lightmap.Height; y++)
|
foreach (var light in _lights)
|
||||||
{
|
{
|
||||||
for (var x = 0; x < lightmap.Width; x++)
|
var layer = 0;
|
||||||
|
|
||||||
|
// Check if plane normal is facing towards the light
|
||||||
|
// If it's not then we're never going to be (directly) lit by this
|
||||||
|
// light.
|
||||||
|
var centerDirection = renderPoly.Center - light.Position;
|
||||||
|
if (Vector3.Dot(plane.Normal, centerDirection) >= 0)
|
||||||
{
|
{
|
||||||
var pos = topLeft;
|
continue;
|
||||||
pos += x * 0.25f * renderPoly.TextureVectors.Item1;
|
}
|
||||||
pos += y * 0.25f * renderPoly.TextureVectors.Item2;
|
|
||||||
|
|
||||||
// TODO: Handle quad lit lights better. Right now we're computing two sets of points for every
|
|
||||||
// luxel. Maybe it's better to only compute if we encounter a quadlit light?
|
|
||||||
var tracePoints = GetTracePoints(pos, offsets, renderPoly.Center, planeMapper, v2ds);
|
|
||||||
var quadTracePoints = settings.MultiSampling == SoftnessMode.HighFourPoint
|
|
||||||
? tracePoints
|
|
||||||
: GetTracePoints(pos, quadOffsets, renderPoly.Center, planeMapper, v2ds);
|
|
||||||
|
|
||||||
// TODO: This isn't quite right yet. It seems to be too bright
|
|
||||||
if (settings.Sunlight.Enabled) {
|
|
||||||
// Check if plane normal is facing towards the light
|
|
||||||
// If it's not then we're never going to be (directly) lit by this
|
|
||||||
// light.
|
|
||||||
var sunAngle = Vector3.Dot(-settings.Sunlight.Direction, plane.Normal);
|
|
||||||
if (sunAngle > 0)
|
|
||||||
{
|
|
||||||
var strength = 0f;
|
|
||||||
var targetPoints = settings.Sunlight.QuadLit ? quadTracePoints : tracePoints;
|
|
||||||
var targetWeights = settings.Sunlight.QuadLit ? quadWeights : weights;
|
|
||||||
for (var idx = 0; idx < targetPoints.Length; idx++)
|
|
||||||
{
|
|
||||||
var point = targetPoints[idx];
|
|
||||||
if (TraceSunRay(point, -settings.Sunlight.Direction))
|
|
||||||
{
|
|
||||||
// Sunlight is a simpler lighting algorithm than normal lights so we can just
|
|
||||||
// do it here
|
|
||||||
strength += targetWeights[idx] * sunAngle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strength != 0f)
|
// If there aren't *any* points on the plane that are in range of the light
|
||||||
{
|
// then none of the lightmap points will be so we can discard.
|
||||||
lightmap.AddLight(0, x, y, settings.Sunlight.Color, strength, settings.Hdr);
|
// The more compact a map is the less effective this is
|
||||||
}
|
var planeDist = MathUtils.DistanceFromPlane(plane, light.Position);
|
||||||
}
|
if (planeDist > light.Radius)
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var light in _lights)
|
// If the poly of the lightmap doesn't intersect the light radius then
|
||||||
|
// none of the lightmap points will so we can discard.
|
||||||
|
if (!MathUtils.Intersects(new MathUtils.Sphere(light.Position, light.Radius), aabb))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = 0; y < lightmap.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < lightmap.Width; x++)
|
||||||
{
|
{
|
||||||
var layer = 0;
|
var pos = topLeft;
|
||||||
|
pos += x * 0.25f * renderPoly.TextureVectors.Item1;
|
||||||
// Check if plane normal is facing towards the light
|
pos += y * 0.25f * renderPoly.TextureVectors.Item2;
|
||||||
// If it's not then we're never going to be (directly) lit by this
|
|
||||||
// light.
|
|
||||||
var centerDirection = renderPoly.Center - light.Position;
|
|
||||||
if (Vector3.Dot(plane.Normal, centerDirection) >= 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there aren't *any* points on the plane that are in range of the light
|
|
||||||
// then none of the lightmap points will be so we can discard.
|
|
||||||
// The more compact a map is the less effective this is
|
|
||||||
var planeDist = MathUtils.DistanceFromPlane(plane, light.Position);
|
|
||||||
if (planeDist > light.Radius)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the poly of the lightmap doesn't intersect the light radius then
|
|
||||||
// none of the lightmap points will so we can discard.
|
|
||||||
if (!MathUtils.Intersects(new MathUtils.Sphere(light.Position, light.Radius), aabb))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var strength = 0f;
|
if (TracePixelMultisampled(
|
||||||
var targetPoints = light.QuadLit ? quadTracePoints : tracePoints;
|
settings.MultiSampling, light, pos, renderPoly.Center, plane, planeMapper, v2ds,
|
||||||
var targetWeights = light.QuadLit ? quadWeights : weights;
|
renderPoly.TextureVectors.Item1, renderPoly.TextureVectors.Item2,
|
||||||
for (var idx = 0; idx < targetPoints.Length; idx++)
|
settings.MultiSamplingCenterWeight, out var strength))
|
||||||
{
|
|
||||||
var point = targetPoints[idx];
|
|
||||||
|
|
||||||
// If we're out of range there's no point casting a ray
|
|
||||||
// There's probably a better way to discard the entire lightmap
|
|
||||||
// if we're massively out of range
|
|
||||||
if ((point - light.Position).LengthSquared() > light.R2)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TraceRay(light.Position, point))
|
|
||||||
{
|
|
||||||
strength += targetWeights[idx] * light.StrengthAtPoint(point, plane);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strength != 0f)
|
|
||||||
{
|
{
|
||||||
// If we're an anim light there's a lot of stuff we need to update
|
// If we're an anim light there's a lot of stuff we need to update
|
||||||
// Firstly we need to add the light to the cells anim light palette
|
// Firstly we need to add the light to the cells anim light palette
|
||||||
|
@ -543,85 +507,126 @@ public class LightMapper
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (Vector3[], float[]) GetTraceOffsetsAndWeights(
|
public bool TracePixelMultisampled(
|
||||||
SoftnessMode mode,
|
SoftnessMode mode,
|
||||||
|
Light light,
|
||||||
|
Vector3 pos,
|
||||||
|
Vector3 polyCenter,
|
||||||
|
Plane plane,
|
||||||
|
MathUtils.PlanePointMapper planeMapper,
|
||||||
|
Vector2[] v2ds,
|
||||||
Vector3 texU,
|
Vector3 texU,
|
||||||
Vector3 texV,
|
Vector3 texV,
|
||||||
float centerWeight)
|
float centerWeight,
|
||||||
|
out float strength)
|
||||||
{
|
{
|
||||||
var offsetScale = mode switch
|
bool FourPoint(float offsetScale, out float strength)
|
||||||
{
|
{
|
||||||
SoftnessMode.HighFourPoint or SoftnessMode.HighFivePoint or SoftnessMode.HighNinePoint => 4f,
|
var hit = false;
|
||||||
SoftnessMode.MediumFourPoint or SoftnessMode.MediumFivePoint or SoftnessMode.MediumNinePoint => 8f,
|
var xOffset = texU / offsetScale;
|
||||||
SoftnessMode.LowFourPoint => 16f,
|
var yOffset = texV / offsetScale;
|
||||||
_ => 1f,
|
hit |= TracePixel(light, pos - xOffset - yOffset, polyCenter, plane, planeMapper, v2ds, out var strength1);
|
||||||
};
|
hit |= TracePixel(light, pos + xOffset - yOffset, polyCenter, plane, planeMapper, v2ds, out var strength2);
|
||||||
|
hit |= TracePixel(light, pos - xOffset + yOffset, polyCenter, plane, planeMapper, v2ds, out var strength3);
|
||||||
var cw = centerWeight;
|
hit |= TracePixel(light, pos + xOffset + yOffset, polyCenter, plane, planeMapper, v2ds, out var strength4);
|
||||||
var w = 1f - cw;
|
strength = (strength1 + strength2 + strength3 + strength4) / 4f;
|
||||||
texU /= offsetScale;
|
return hit;
|
||||||
texV /= offsetScale;
|
|
||||||
|
|
||||||
return mode switch
|
|
||||||
{
|
|
||||||
SoftnessMode.LowFourPoint or SoftnessMode.MediumFourPoint or SoftnessMode.HighFourPoint => (
|
|
||||||
[-texU - texV, -texU - texV, -texU + texV, texU + texV],
|
|
||||||
[0.25f, 0.25f, 0.25f, 0.25f]),
|
|
||||||
SoftnessMode.MediumFivePoint or SoftnessMode.HighFivePoint => (
|
|
||||||
[Vector3.Zero, -texU - texV, texU - texV, -texU + texV, texU + texV],
|
|
||||||
[cw, w * 0.25f, w * 0.25f, w * 0.25f, w * 0.25f]),
|
|
||||||
SoftnessMode.MediumNinePoint or SoftnessMode.HighNinePoint => (
|
|
||||||
[Vector3.Zero, -texU - texV, texU - texV, -texU + texV, texU + texV, -texU, texU, -texV, texV],
|
|
||||||
[cw, w * 0.125f, w * 0.125f, w * 0.125f, w * 0.125f, w * 0.125f, w * 0.125f, w * 0.125f, w * 0.125f]),
|
|
||||||
_ => (
|
|
||||||
[Vector3.Zero],
|
|
||||||
[1f]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3[] GetTracePoints(
|
|
||||||
Vector3 basePosition,
|
|
||||||
Vector3[] offsets,
|
|
||||||
Vector3 polyCenter,
|
|
||||||
MathUtils.PlanePointMapper planeMapper,
|
|
||||||
Vector2[] v2ds)
|
|
||||||
{
|
|
||||||
var tracePoints = new Vector3[offsets.Length];
|
|
||||||
for (var i = 0; i < offsets.Length; i++)
|
|
||||||
{
|
|
||||||
var offset = offsets[i];
|
|
||||||
var pos = basePosition + offset;
|
|
||||||
|
|
||||||
// Embree has robustness issues when hitting poly edges which
|
|
||||||
// results in false misses. To alleviate this we pre-push everything
|
|
||||||
// slightly towards the center of the poly.
|
|
||||||
var centerOffset = polyCenter - pos;
|
|
||||||
if (centerOffset.LengthSquared() > MathUtils.Epsilon)
|
|
||||||
{
|
|
||||||
pos += Vector3.Normalize(centerOffset) * MathUtils.Epsilon;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we can't see our target point from the center of the poly
|
|
||||||
// then it's outside the world. We need to clip the point to slightly
|
|
||||||
// inside the poly and retrace to avoid three problems:
|
|
||||||
// 1. Darkened spots from lightmap pixels whose center is outside
|
|
||||||
// the polygon but is partially contained in the polygon
|
|
||||||
// 2. Darkened spots from linear filtering of points outside the
|
|
||||||
// polygon which have missed
|
|
||||||
// 3. Darkened spots where centers are on the exact edge of a poly
|
|
||||||
// which can sometimes cause Embree to miss casts
|
|
||||||
var inPoly = TraceRay(polyCenter + planeMapper.Normal * 0.25f, pos);
|
|
||||||
if (!inPoly)
|
|
||||||
{
|
|
||||||
var p2d = planeMapper.MapTo2d(pos);
|
|
||||||
p2d = MathUtils.ClipPointToPoly2d(p2d, v2ds);
|
|
||||||
pos = planeMapper.MapTo3d(p2d);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracePoints[i] = pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracePoints;
|
bool FivePoint(float offsetScale, out float strength)
|
||||||
|
{
|
||||||
|
var hit = false;
|
||||||
|
hit |= TracePixel(light, pos, polyCenter, plane, planeMapper, v2ds, out var centerStrength);
|
||||||
|
hit |= FourPoint(offsetScale, out strength);
|
||||||
|
strength = (1.0f - centerWeight) * strength + centerWeight * centerStrength;
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NinePoint(float offsetScale, out float strength)
|
||||||
|
{
|
||||||
|
var hit = false;
|
||||||
|
var xOffset = texU / offsetScale;
|
||||||
|
var yOffset = texV / offsetScale;
|
||||||
|
hit |= TracePixel(light, pos - xOffset - yOffset, polyCenter, plane, planeMapper, v2ds, out var s1);
|
||||||
|
hit |= TracePixel(light, pos + xOffset - yOffset, polyCenter, plane, planeMapper, v2ds, out var s2);
|
||||||
|
hit |= TracePixel(light, pos - xOffset + yOffset, polyCenter, plane, planeMapper, v2ds, out var s3);
|
||||||
|
hit |= TracePixel(light, pos + xOffset + yOffset, polyCenter, plane, planeMapper, v2ds, out var s4);
|
||||||
|
hit |= TracePixel(light, pos - xOffset, polyCenter, plane, planeMapper, v2ds, out var s5);
|
||||||
|
hit |= TracePixel(light, pos + xOffset, polyCenter, plane, planeMapper, v2ds, out var s6);
|
||||||
|
hit |= TracePixel(light, pos - yOffset, polyCenter, plane, planeMapper, v2ds, out var s7);
|
||||||
|
hit |= TracePixel(light, pos + yOffset, polyCenter, plane, planeMapper, v2ds, out var s8);
|
||||||
|
hit |= TracePixel(light, pos, polyCenter, plane, planeMapper, v2ds, out var centerStrength);
|
||||||
|
strength = (s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8) / 8f;
|
||||||
|
strength = (1.0f - centerWeight) * strength + centerWeight * centerStrength;
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode switch {
|
||||||
|
SoftnessMode.Standard when light.QuadLit => FourPoint(4f, out strength),
|
||||||
|
SoftnessMode.HighFourPoint => FourPoint(4f, out strength),
|
||||||
|
SoftnessMode.HighFivePoint => FivePoint(4f, out strength),
|
||||||
|
SoftnessMode.HighNinePoint => NinePoint(4f, out strength),
|
||||||
|
SoftnessMode.MediumFourPoint => FourPoint(8f, out strength),
|
||||||
|
SoftnessMode.MediumFivePoint => FivePoint(8f, out strength),
|
||||||
|
SoftnessMode.MediumNinePoint => NinePoint(8f, out strength),
|
||||||
|
SoftnessMode.LowFourPoint => FourPoint(16f, out strength),
|
||||||
|
_ => TracePixel(light, pos, polyCenter, plane, planeMapper, v2ds, out strength)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TracePixel(
|
||||||
|
Light light,
|
||||||
|
Vector3 pos,
|
||||||
|
Vector3 polyCenter,
|
||||||
|
Plane plane,
|
||||||
|
MathUtils.PlanePointMapper planeMapper,
|
||||||
|
Vector2[] v2ds,
|
||||||
|
out float strength)
|
||||||
|
{
|
||||||
|
strength = 0f;
|
||||||
|
|
||||||
|
// Embree has robustness issues when hitting poly edges which
|
||||||
|
// results in false misses. To alleviate this we pre-push everything
|
||||||
|
// slightly towards the center of the poly.
|
||||||
|
var centerOffset = polyCenter - pos;
|
||||||
|
if (centerOffset.LengthSquared() > MathUtils.Epsilon)
|
||||||
|
{
|
||||||
|
pos += Vector3.Normalize(centerOffset) * MathUtils.Epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't see our target point from the center of the poly
|
||||||
|
// then it's outside the world. We need to clip the point to slightly
|
||||||
|
// inside the poly and retrace to avoid three problems:
|
||||||
|
// 1. Darkened spots from lightmap pixels whose center is outside
|
||||||
|
// the polygon but is partially contained in the polygon
|
||||||
|
// 2. Darkened spots from linear filtering of points outside the
|
||||||
|
// polygon which have missed
|
||||||
|
// 3. Darkened spots where centers are on the exact edge of a poly
|
||||||
|
// which can sometimes cause Embree to miss casts
|
||||||
|
var inPoly = TraceRay(polyCenter + plane.Normal * 0.25f, pos);
|
||||||
|
if (!inPoly)
|
||||||
|
{
|
||||||
|
var p2d = planeMapper.MapTo2d(pos);
|
||||||
|
p2d = MathUtils.ClipPointToPoly2d(p2d, v2ds);
|
||||||
|
pos = planeMapper.MapTo3d(p2d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're out of range there's no point casting a ray
|
||||||
|
// There's probably a better way to discard the entire lightmap
|
||||||
|
// if we're massively out of range
|
||||||
|
if ((pos - light.Position).LengthSquared() > light.R2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cast from the light to the pixel because the light has
|
||||||
|
// no mesh in the scene to hit
|
||||||
|
var hit = TraceRay(light.Position, pos);
|
||||||
|
if (hit)
|
||||||
|
{
|
||||||
|
strength += light.StrengthAtPoint(pos, plane);
|
||||||
|
}
|
||||||
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TraceRay(Vector3 origin, Vector3 target)
|
private bool TraceRay(Vector3 origin, Vector3 target)
|
||||||
|
@ -645,28 +650,6 @@ public class LightMapper
|
||||||
return Math.Abs(hitDistanceFromTarget) < MathUtils.Epsilon;
|
return Math.Abs(hitDistanceFromTarget) < MathUtils.Epsilon;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Can this be merged with the above?
|
|
||||||
private bool TraceSunRay(Vector3 origin, Vector3 direction)
|
|
||||||
{
|
|
||||||
// Avoid self intersection
|
|
||||||
origin += direction * MathUtils.Epsilon;
|
|
||||||
|
|
||||||
var hitSurfaceType = SurfaceType.Water;
|
|
||||||
while (hitSurfaceType == SurfaceType.Water)
|
|
||||||
{
|
|
||||||
var hitResult = _scene.Trace(new Ray
|
|
||||||
{
|
|
||||||
Origin = origin,
|
|
||||||
Direction = Vector3.Normalize(direction),
|
|
||||||
});
|
|
||||||
|
|
||||||
hitSurfaceType = _triangleTypeMap[(int)hitResult.PrimId];
|
|
||||||
origin = hitResult.Position += direction * MathUtils.Epsilon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hitSurfaceType == SurfaceType.Sky;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetAnimLightCellMaps()
|
private void SetAnimLightCellMaps()
|
||||||
{
|
{
|
||||||
if (!_mission.TryGetChunk<PropertyChunk<PropAnimLight>>("P$AnimLight", out var animLightChunk) ||
|
if (!_mission.TryGetChunk<PropertyChunk<PropAnimLight>>("P$AnimLight", out var animLightChunk) ||
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
using System.Numerics;
|
|
||||||
using KeepersCompound.LGS;
|
|
||||||
using KeepersCompound.LGS.Database;
|
|
||||||
using KeepersCompound.LGS.Database.Chunks;
|
|
||||||
|
|
||||||
namespace KeepersCompound.Lightmapper;
|
|
||||||
|
|
||||||
// TODO: Rename to CastSurfaceType?
|
|
||||||
public enum SurfaceType
|
|
||||||
{
|
|
||||||
Solid,
|
|
||||||
Sky,
|
|
||||||
Water,
|
|
||||||
Air,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Mesh(int triangleCount, List<Vector3> vertices, List<int> indices, List<SurfaceType> triangleSurfaceMap)
|
|
||||||
{
|
|
||||||
public int TriangleCount { get; } = triangleCount;
|
|
||||||
public Vector3[] Vertices { get; } = [..vertices];
|
|
||||||
public int[] Indices { get; } = [..indices];
|
|
||||||
public SurfaceType[] TriangleSurfaceMap { get; } = [..triangleSurfaceMap];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MeshBuilder
|
|
||||||
{
|
|
||||||
private int _triangleCount = 0;
|
|
||||||
private readonly List<Vector3> _vertices = [];
|
|
||||||
private readonly List<int> _indices = [];
|
|
||||||
private readonly List<SurfaceType> _primSurfaceMap = [];
|
|
||||||
|
|
||||||
public void AddWorldRepPolys(WorldRep worldRep)
|
|
||||||
{
|
|
||||||
var polyVertices = new List<Vector3>();
|
|
||||||
foreach (var cell in worldRep.Cells)
|
|
||||||
{
|
|
||||||
var numPolys = cell.PolyCount;
|
|
||||||
var numRenderPolys = cell.RenderPolyCount;
|
|
||||||
var numPortalPolys = cell.PortalPolyCount;
|
|
||||||
var solidPolys = numPolys - numPortalPolys;
|
|
||||||
|
|
||||||
var cellIdxOffset = 0;
|
|
||||||
for (var polyIdx = 0; polyIdx < numPolys; polyIdx++)
|
|
||||||
{
|
|
||||||
// There's 3 types of poly that we need to include in the mesh:
|
|
||||||
// - Terrain
|
|
||||||
// - Water surfaces
|
|
||||||
// - Door vision blockers
|
|
||||||
//
|
|
||||||
// Door vision blockers are the interesting one. They're not RenderPolys at all, and we only include
|
|
||||||
// them in the mesh if the cell only has two of them (otherwise the door is in the middle of the air)
|
|
||||||
SurfaceType primType;
|
|
||||||
if (polyIdx < solidPolys)
|
|
||||||
{
|
|
||||||
primType = cell.RenderPolys[polyIdx].TextureId == 249 ? SurfaceType.Sky : SurfaceType.Solid;
|
|
||||||
}
|
|
||||||
else if (polyIdx < numRenderPolys)
|
|
||||||
{
|
|
||||||
primType = SurfaceType.Water;
|
|
||||||
}
|
|
||||||
else if (cell is { Flags: 24, PortalPolyCount: 2 }) // TODO: Work out what these flags are!!
|
|
||||||
{
|
|
||||||
primType = SurfaceType.Solid;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var poly = cell.Polys[polyIdx];
|
|
||||||
polyVertices.Clear();
|
|
||||||
polyVertices.EnsureCapacity(poly.VertexCount);
|
|
||||||
for (var i = 0; i < poly.VertexCount; i++)
|
|
||||||
{
|
|
||||||
polyVertices.Add(cell.Vertices[cell.Indices[cellIdxOffset + i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddPolygon(polyVertices, primType);
|
|
||||||
cellIdxOffset += poly.VertexCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddObjectPolys(
|
|
||||||
BrList brushList,
|
|
||||||
ObjectHierarchy hierarchy,
|
|
||||||
ResourcePathManager.CampaignResources campaignResources)
|
|
||||||
{
|
|
||||||
var polyVertices = new List<Vector3>();
|
|
||||||
foreach (var brush in brushList.Brushes)
|
|
||||||
{
|
|
||||||
if (brush.media != BrList.Brush.Media.Object)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = (int)brush.brushInfo;
|
|
||||||
var modelNameProp = hierarchy.GetProperty<PropLabel>(id, "P$ModelName");
|
|
||||||
var scaleProp = hierarchy.GetProperty<PropVector>(id, "P$Scale");
|
|
||||||
var renderTypeProp = hierarchy.GetProperty<PropRenderType>(id, "P$RenderTyp");
|
|
||||||
var jointPosProp = hierarchy.GetProperty<PropJointPos>(id, "P$JointPos");
|
|
||||||
var immobileProp = hierarchy.GetProperty<PropBool>(id, "P$Immobile");
|
|
||||||
var staticShadowProp = hierarchy.GetProperty<PropBool>(id, "P$StatShad");
|
|
||||||
|
|
||||||
var joints = jointPosProp?.Positions ?? [0, 0, 0, 0, 0, 0];
|
|
||||||
var castsShadows = (immobileProp?.value ?? false) || (staticShadowProp?.value ?? false);
|
|
||||||
var renderMode = renderTypeProp?.mode ?? PropRenderType.Mode.Normal;
|
|
||||||
|
|
||||||
// TODO: Check which rendermodes cast shadows :)
|
|
||||||
if (modelNameProp == null || !castsShadows || renderMode == PropRenderType.Mode.CoronaOnly)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's try and place an object :)
|
|
||||||
var modelName = modelNameProp.value.ToLower() + ".bin";
|
|
||||||
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
|
|
||||||
if (modelPath == null)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
pos -= model.Header.Center;
|
|
||||||
|
|
||||||
// for each object modify the vertices
|
|
||||||
// TODO: Almost perfect transform!
|
|
||||||
// TODO: Handle nested sub objects
|
|
||||||
foreach (var subObj in model.Objects)
|
|
||||||
{
|
|
||||||
var jointTrans = Matrix4x4.Identity;
|
|
||||||
if (subObj.Joint != -1)
|
|
||||||
{
|
|
||||||
var ang = float.DegreesToRadians(joints[subObj.Joint]);
|
|
||||||
var jointRot = Matrix4x4.CreateFromYawPitchRoll(0, ang, 0);
|
|
||||||
var objTrans = subObj.Transform;
|
|
||||||
jointTrans = jointRot * objTrans;
|
|
||||||
}
|
|
||||||
var scalePart = Matrix4x4.CreateScale(scale);
|
|
||||||
var rotPart = Matrix4x4.CreateFromYawPitchRoll(float.DegreesToRadians(rot.Y), float.DegreesToRadians(rot.X),
|
|
||||||
float.DegreesToRadians(rot.Z));
|
|
||||||
var transPart = Matrix4x4.CreateTranslation(pos);
|
|
||||||
var transform = jointTrans * scalePart * rotPart * transPart;
|
|
||||||
|
|
||||||
var start = subObj.PointIdx;
|
|
||||||
var end = start + subObj.PointCount;
|
|
||||||
for (var i = start; i < end; i++)
|
|
||||||
{
|
|
||||||
var v = model.Vertices[i];
|
|
||||||
model.Vertices[i] = Vector3.Transform(v, transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each polygon slam its vertices and indices :)
|
|
||||||
foreach (var poly in model.Polygons)
|
|
||||||
{
|
|
||||||
polyVertices.Clear();
|
|
||||||
polyVertices.EnsureCapacity(poly.VertexCount);
|
|
||||||
foreach (var idx in poly.VertexIndices)
|
|
||||||
{
|
|
||||||
polyVertices.Add(model.Vertices[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddPolygon(polyVertices, SurfaceType.Solid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddPolygon(List<Vector3> vertices, SurfaceType surfaceType)
|
|
||||||
{
|
|
||||||
var vertexCount = vertices.Count;
|
|
||||||
var indexOffset = _vertices.Count;
|
|
||||||
|
|
||||||
// Polygons are n-sided, but fortunately they're convex so we can just do a fan triangulation
|
|
||||||
_vertices.AddRange(vertices);
|
|
||||||
for (var i = 1; i < vertexCount - 1; i++)
|
|
||||||
{
|
|
||||||
_indices.Add(indexOffset);
|
|
||||||
_indices.Add(indexOffset + i);
|
|
||||||
_indices.Add(indexOffset + i + 1);
|
|
||||||
_primSurfaceMap.Add(surfaceType);
|
|
||||||
_triangleCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mesh Build()
|
|
||||||
{
|
|
||||||
return new Mesh(_triangleCount, _vertices, _indices, _primSurfaceMap);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -90,14 +90,12 @@ public static class MathUtils
|
||||||
|
|
||||||
public record PlanePointMapper
|
public record PlanePointMapper
|
||||||
{
|
{
|
||||||
public Vector3 Normal { get; }
|
|
||||||
Vector3 _origin;
|
Vector3 _origin;
|
||||||
Vector3 _xAxis;
|
Vector3 _xAxis;
|
||||||
Vector3 _yAxis;
|
Vector3 _yAxis;
|
||||||
|
|
||||||
public PlanePointMapper(Vector3 normal, Vector3 p0, Vector3 p1)
|
public PlanePointMapper(Vector3 normal, Vector3 p0, Vector3 p1)
|
||||||
{
|
{
|
||||||
Normal = normal;
|
|
||||||
_origin = p0;
|
_origin = p0;
|
||||||
_xAxis = p1 - _origin;
|
_xAxis = p1 - _origin;
|
||||||
_yAxis = Vector3.Cross(normal, _xAxis);
|
_yAxis = Vector3.Cross(normal, _xAxis);
|
||||||
|
|
Loading…
Reference in New Issue