Compare commits
8 Commits
940c78e1cf
...
c162a5028d
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | c162a5028d | |
Jarrod Doyle | 0a41d78cac | |
Jarrod Doyle | 3df1b728e6 | |
Jarrod Doyle | ef95b07926 | |
Jarrod Doyle | 466b7a4c0b | |
Jarrod Doyle | 7ff91b2fda | |
Jarrod Doyle | 0c26aa1f0e | |
Jarrod Doyle | cd13cee48f |
|
@ -152,6 +152,7 @@ public class WorldRep : IChunk
|
||||||
|
|
||||||
public class Lightmap
|
public class Lightmap
|
||||||
{
|
{
|
||||||
|
private readonly bool[] _litLayers;
|
||||||
public List<byte[]> Pixels { get; set; }
|
public List<byte[]> Pixels { get; set; }
|
||||||
|
|
||||||
public int Layers;
|
public int Layers;
|
||||||
|
@ -163,10 +164,12 @@ public class WorldRep : IChunk
|
||||||
{
|
{
|
||||||
var layers = 1 + BitOperations.PopCount(bitmask);
|
var layers = 1 + BitOperations.PopCount(bitmask);
|
||||||
var length = bytesPerPixel * width * height;
|
var length = bytesPerPixel * width * height;
|
||||||
|
_litLayers = new bool[33];
|
||||||
Pixels = new List<byte[]>();
|
Pixels = new List<byte[]>();
|
||||||
for (var i = 0; i < layers; i++)
|
for (var i = 0; i < layers; i++)
|
||||||
{
|
{
|
||||||
Pixels.Add(reader.ReadBytes(length));
|
Pixels.Add(reader.ReadBytes(length));
|
||||||
|
_litLayers[i] = true;
|
||||||
}
|
}
|
||||||
Layers = layers;
|
Layers = layers;
|
||||||
Width = width;
|
Width = width;
|
||||||
|
@ -246,6 +249,8 @@ public class WorldRep : IChunk
|
||||||
pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255);
|
pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255);
|
||||||
pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255);
|
pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255);
|
||||||
pLayer[idx + 3] = 255;
|
pLayer[idx + 3] = 255;
|
||||||
|
|
||||||
|
_litLayers[layer] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddLight(int layer, int x, int y, Vector3 color, float strength, bool hdr)
|
public void AddLight(int layer, int x, int y, Vector3 color, float strength, bool hdr)
|
||||||
|
@ -260,12 +265,7 @@ public class WorldRep : IChunk
|
||||||
// the hue/saturation of coloured lights. Got to make sure we
|
// the hue/saturation of coloured lights. Got to make sure we
|
||||||
// maintain the colour ratios.
|
// maintain the colour ratios.
|
||||||
var c = color * strength;
|
var c = color * strength;
|
||||||
var ratio = 0.0f;
|
var ratio = Math.Max(Math.Max(Math.Max(0.0f, c.X), c.Y), c.Z) / 255.0f;
|
||||||
foreach (var e in new float[] { c.X, c.Y, c.Z })
|
|
||||||
{
|
|
||||||
ratio = Math.Max(ratio, e / 255.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ratio > 1.0f)
|
if (ratio > 1.0f)
|
||||||
{
|
{
|
||||||
c /= ratio;
|
c /= ratio;
|
||||||
|
@ -276,9 +276,18 @@ public class WorldRep : IChunk
|
||||||
|
|
||||||
public void Reset(Vector3 ambientLight, bool hdr)
|
public void Reset(Vector3 ambientLight, bool hdr)
|
||||||
{
|
{
|
||||||
Layers = 0;
|
Layers = 33;
|
||||||
Pixels.Clear();
|
Pixels.Clear();
|
||||||
AddLayer();
|
var bytesPerLayer = Width * Height * Bpp;
|
||||||
|
for (var i = 0; i < Layers; i++)
|
||||||
|
{
|
||||||
|
Pixels.Add(new byte[bytesPerLayer]);
|
||||||
|
for (var j = 0; j < bytesPerLayer; j++)
|
||||||
|
{
|
||||||
|
Pixels[i][j] = 0;
|
||||||
|
}
|
||||||
|
_litLayers[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
|
@ -289,22 +298,14 @@ public class WorldRep : IChunk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddLayer()
|
|
||||||
{
|
|
||||||
var bytesPerLayer = Width * Height * Bpp;
|
|
||||||
Pixels.Add(new byte[bytesPerLayer]);
|
|
||||||
for (var j = 0; j < bytesPerLayer; j++)
|
|
||||||
{
|
|
||||||
Pixels[Layers][j] = 0;
|
|
||||||
}
|
|
||||||
Layers++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(BinaryWriter writer)
|
public void Write(BinaryWriter writer)
|
||||||
{
|
{
|
||||||
foreach (var layer in Pixels)
|
for (var i = 0; i < Layers; i++)
|
||||||
{
|
{
|
||||||
writer.Write(layer);
|
if (_litLayers[i])
|
||||||
|
{
|
||||||
|
writer.Write(Pixels[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
public class ObjectHierarchy
|
public class ObjectHierarchy
|
||||||
{
|
{
|
||||||
public class DarkObject
|
private class DarkObject
|
||||||
{
|
{
|
||||||
public int objectId;
|
public int objectId;
|
||||||
public int parentId;
|
public int parentId;
|
||||||
|
@ -17,7 +17,7 @@ public class ObjectHierarchy
|
||||||
properties = new Dictionary<string, Property>();
|
properties = new Dictionary<string, Property>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetProperty<T>(string propName) where T : Property
|
public T? GetProperty<T>(string propName) where T : Property
|
||||||
{
|
{
|
||||||
if (properties.TryGetValue(propName, out var prop))
|
if (properties.TryGetValue(propName, out var prop))
|
||||||
{
|
{
|
||||||
|
@ -79,11 +79,12 @@ public class ObjectHierarchy
|
||||||
foreach (var prop in chunk.properties)
|
foreach (var prop in chunk.properties)
|
||||||
{
|
{
|
||||||
var id = prop.objectId;
|
var id = prop.objectId;
|
||||||
if (!_objects.ContainsKey(id))
|
if (!_objects.TryGetValue(id, out var value))
|
||||||
{
|
{
|
||||||
_objects.Add(id, new DarkObject(id));
|
value = new DarkObject(id);
|
||||||
|
_objects.Add(id, value);
|
||||||
}
|
}
|
||||||
_objects[id].properties.TryAdd(name, prop);
|
value.properties.TryAdd(name, prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ public class ObjectHierarchy
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
public T GetProperty<T>(int objectId, string propName, bool inherit = true) where T : Property
|
public T? GetProperty<T>(int objectId, string propName, bool inherit = true) where T : Property
|
||||||
{
|
{
|
||||||
if (!_objects.ContainsKey(objectId))
|
if (!_objects.ContainsKey(objectId))
|
||||||
{
|
{
|
||||||
|
|
|
@ -74,6 +74,7 @@ class Program
|
||||||
return;
|
return;
|
||||||
var ambient = rendParams.ambientLight * 255;
|
var ambient = rendParams.ambientLight * 255;
|
||||||
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
|
var lights = Timing.TimeStage("Gather Lights", () => BuildLightList(mis, hierarchy, campaign));
|
||||||
|
Timing.TimeStage("Set Light Indices", () => SetCellLightIndices(worldRep, [.. lights]));
|
||||||
Timing.TimeStage("Light", () => CastSceneParallel(scene, worldRep, [.. lights], ambient));
|
Timing.TimeStage("Light", () => CastSceneParallel(scene, worldRep, [.. lights], ambient));
|
||||||
Timing.TimeStage("Update Anim Mapping", () => SetAnimLightCellMaps(mis, worldRep, lights));
|
Timing.TimeStage("Update Anim Mapping", () => SetAnimLightCellMaps(mis, worldRep, lights));
|
||||||
|
|
||||||
|
@ -85,30 +86,6 @@ class Program
|
||||||
Console.WriteLine($"Lit {lights.Count} light");
|
Console.WriteLine($"Lit {lights.Count} light");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects Hue and Saturation are 0-1, Brightness 0-255
|
|
||||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
|
|
||||||
private static Vector3 HsbToRgb(float hue, float saturation, float brightness)
|
|
||||||
{
|
|
||||||
hue *= 360;
|
|
||||||
var hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
|
|
||||||
var f = hue / 60 - Math.Floor(hue / 60);
|
|
||||||
|
|
||||||
var v = Convert.ToInt32(brightness);
|
|
||||||
var p = Convert.ToInt32(brightness * (1 - saturation));
|
|
||||||
var q = Convert.ToInt32(brightness * (1 - f * saturation));
|
|
||||||
var t = Convert.ToInt32(brightness * (1 - (1 - f) * saturation));
|
|
||||||
|
|
||||||
return hi switch
|
|
||||||
{
|
|
||||||
0 => new Vector3(v, t, p),
|
|
||||||
1 => new Vector3(q, v, p),
|
|
||||||
2 => new Vector3(p, v, t),
|
|
||||||
3 => new Vector3(p, q, v),
|
|
||||||
4 => new Vector3(t, p, v),
|
|
||||||
_ => new Vector3(v, p, q),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetAnimLightCellMaps(
|
private static void SetAnimLightCellMaps(
|
||||||
DbFile mis,
|
DbFile mis,
|
||||||
WorldRep worldRep,
|
WorldRep worldRep,
|
||||||
|
@ -193,7 +170,7 @@ class Program
|
||||||
var light = new Light
|
var light = new Light
|
||||||
{
|
{
|
||||||
position = brush.position,
|
position = brush.position,
|
||||||
color = HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
|
color = Utils.HsbToRgb(sz.Y, sz.Z, Math.Min(sz.X, 255.0f)),
|
||||||
radius = float.MaxValue,
|
radius = float.MaxValue,
|
||||||
r2 = float.MaxValue,
|
r2 = float.MaxValue,
|
||||||
lightTableIndex = worldRep.LightingTable.LightCount,
|
lightTableIndex = worldRep.LightingTable.LightCount,
|
||||||
|
@ -266,7 +243,7 @@ class Program
|
||||||
var light = new Light
|
var light = new Light
|
||||||
{
|
{
|
||||||
position = baseLight.position + propLight.Offset,
|
position = baseLight.position + propLight.Offset,
|
||||||
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
|
color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propLight.Brightness),
|
||||||
innerRadius = propLight.InnerRadius,
|
innerRadius = propLight.InnerRadius,
|
||||||
radius = propLight.Radius,
|
radius = propLight.Radius,
|
||||||
r2 = propLight.Radius * propLight.Radius,
|
r2 = propLight.Radius * propLight.Radius,
|
||||||
|
@ -303,7 +280,7 @@ class Program
|
||||||
var light = new Light
|
var light = new Light
|
||||||
{
|
{
|
||||||
position = baseLight.position + propAnimLight.Offset,
|
position = baseLight.position + propAnimLight.Offset,
|
||||||
color = HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness),
|
color = Utils.HsbToRgb(propLightColor.Hue, propLightColor.Saturation, propAnimLight.MaxBrightness),
|
||||||
innerRadius = propAnimLight.InnerRadius,
|
innerRadius = propAnimLight.InnerRadius,
|
||||||
radius = propAnimLight.Radius,
|
radius = propAnimLight.Radius,
|
||||||
r2 = propAnimLight.Radius * propAnimLight.Radius,
|
r2 = propAnimLight.Radius * propAnimLight.Radius,
|
||||||
|
@ -369,9 +346,8 @@ class Program
|
||||||
var indices = new List<int>();
|
var indices = new List<int>();
|
||||||
|
|
||||||
var cells = worldRep.Cells;
|
var cells = worldRep.Cells;
|
||||||
for (var cellIdx = 0; cellIdx < cells.Length; cellIdx++)
|
foreach (var cell in cells)
|
||||||
{
|
{
|
||||||
var cell = cells[cellIdx];
|
|
||||||
var numPolys = cell.PolyCount;
|
var numPolys = cell.PolyCount;
|
||||||
var numRenderPolys = cell.RenderPolyCount;
|
var numRenderPolys = cell.RenderPolyCount;
|
||||||
var numPortalPolys = cell.PortalPolyCount;
|
var numPortalPolys = cell.PortalPolyCount;
|
||||||
|
@ -384,7 +360,7 @@ class Program
|
||||||
|
|
||||||
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
|
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
|
||||||
var cellIdxOffset = 0;
|
var cellIdxOffset = 0;
|
||||||
for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
|
for (var polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
|
||||||
{
|
{
|
||||||
var poly = cell.Polys[polyIdx];
|
var poly = cell.Polys[polyIdx];
|
||||||
|
|
||||||
|
@ -396,7 +372,7 @@ class Program
|
||||||
vertices.Add(vertex);
|
vertices.Add(vertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 1; j < numPolyVertices - 1; j++)
|
for (var j = 1; j < numPolyVertices - 1; j++)
|
||||||
{
|
{
|
||||||
indices.Add(meshIndexOffset);
|
indices.Add(meshIndexOffset);
|
||||||
indices.Add(meshIndexOffset + j);
|
indices.Add(meshIndexOffset + j);
|
||||||
|
@ -413,42 +389,6 @@ class Program
|
||||||
private static void CastSceneParallel(Raytracer scene, WorldRep wr, Light[] lights, Vector3 ambientLight)
|
private static void CastSceneParallel(Raytracer scene, WorldRep wr, Light[] lights, Vector3 ambientLight)
|
||||||
{
|
{
|
||||||
var hdr = wr.DataHeader.LightmapFormat == 2;
|
var hdr = wr.DataHeader.LightmapFormat == 2;
|
||||||
|
|
||||||
// We set up light indices in a separate loop because the actual lighting
|
|
||||||
// phase takes a lot of shortcuts that we don't want
|
|
||||||
Parallel.ForEach(wr.Cells, cell =>
|
|
||||||
{
|
|
||||||
cell.LightIndexCount = 0;
|
|
||||||
cell.LightIndices.Clear();
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// in range then we potentially affect the lighting in the cell and add it
|
|
||||||
// to the list. Cells already contain their sphere bounds so we just use
|
|
||||||
// that for now, but a tighter AABB is another option.
|
|
||||||
var cellSphere = new MathUtils.Sphere(cell.SphereCenter, cell.SphereRadius);
|
|
||||||
foreach (var light in lights)
|
|
||||||
{
|
|
||||||
// If the light had radius 0 (represented here with max float) then we
|
|
||||||
// always add it to the list
|
|
||||||
// TODO: Neaten this up
|
|
||||||
if (light.radius == float.MaxValue)
|
|
||||||
{
|
|
||||||
cell.LightIndexCount++;
|
|
||||||
cell.LightIndices.Add((ushort)light.lightTableIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lightSphere = new MathUtils.Sphere(light.position, light.radius);
|
|
||||||
if (MathUtils.Intersects(cellSphere, lightSphere))
|
|
||||||
{
|
|
||||||
cell.LightIndexCount++;
|
|
||||||
cell.LightIndices.Add((ushort)light.lightTableIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Parallel.ForEach(wr.Cells, cell =>
|
Parallel.ForEach(wr.Cells, cell =>
|
||||||
{
|
{
|
||||||
// Reset cell AnimLight palette
|
// Reset cell AnimLight palette
|
||||||
|
@ -468,7 +408,7 @@ class Program
|
||||||
|
|
||||||
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
|
var maxPolyIdx = Math.Min(numRenderPolys, numPolys - numPortalPolys);
|
||||||
var cellIdxOffset = 0;
|
var cellIdxOffset = 0;
|
||||||
for (int polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
|
for (var polyIdx = 0; polyIdx < maxPolyIdx; polyIdx++)
|
||||||
{
|
{
|
||||||
var poly = cell.Polys[polyIdx];
|
var poly = cell.Polys[polyIdx];
|
||||||
var plane = cell.Planes[poly.PlaneId];
|
var plane = cell.Planes[poly.PlaneId];
|
||||||
|
@ -505,13 +445,6 @@ class Program
|
||||||
foreach (var light in lights)
|
foreach (var light in lights)
|
||||||
{
|
{
|
||||||
var layer = 0;
|
var layer = 0;
|
||||||
if (light.anim)
|
|
||||||
{
|
|
||||||
// Because we're building the AnimLightBitmask in this loop we
|
|
||||||
// know there aren't any layers set above us. So the target layer
|
|
||||||
// is just the number of set bits + 1.
|
|
||||||
layer = BitOperations.PopCount(info.AnimLightBitmask) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if plane normal is facing towards the light
|
// Check if plane normal is facing towards the light
|
||||||
// If it's not then we're never going to be (directly) lit by this
|
// If it's not then we're never going to be (directly) lit by this
|
||||||
|
@ -595,11 +528,7 @@ class Program
|
||||||
cell.AnimLights.Add((ushort)light.lightTableIndex);
|
cell.AnimLights.Add((ushort)light.lightTableIndex);
|
||||||
}
|
}
|
||||||
info.AnimLightBitmask |= 1u << paletteIdx;
|
info.AnimLightBitmask |= 1u << paletteIdx;
|
||||||
|
layer = paletteIdx + 1;
|
||||||
if (layer >= lightmap.Layers)
|
|
||||||
{
|
|
||||||
lightmap.AddLayer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var strength = CalculateLightStrengthAtPoint(light, pos, plane);
|
var strength = CalculateLightStrengthAtPoint(light, pos, plane);
|
||||||
lightmap.AddLight(layer, x, y, light.color, strength, hdr);
|
lightmap.AddLight(layer, x, y, light.color, strength, hdr);
|
||||||
|
@ -613,6 +542,32 @@ class Program
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetCellLightIndices(WorldRep wr, Light[] lights)
|
||||||
|
{
|
||||||
|
// We set up light indices in separately from lighting because the actual
|
||||||
|
// lighting phase takes a lot of shortcuts that we don't want
|
||||||
|
Parallel.ForEach(wr.Cells, cell =>
|
||||||
|
{
|
||||||
|
cell.LightIndexCount = 0;
|
||||||
|
cell.LightIndices.Clear();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// in range then we potentially affect the lighting in the cell and add it
|
||||||
|
// to the list. Cells already contain their sphere bounds so we just use
|
||||||
|
// that for now, but a tighter AABB is another option.
|
||||||
|
var cellSphere = new MathUtils.Sphere(cell.SphereCenter, cell.SphereRadius);
|
||||||
|
foreach (var light in lights)
|
||||||
|
{
|
||||||
|
if (MathUtils.Intersects(cellSphere, new MathUtils.Sphere(light.position, light.radius)))
|
||||||
|
{
|
||||||
|
cell.LightIndexCount++;
|
||||||
|
cell.LightIndices.Add((ushort)light.lightTableIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static float CalculateLightStrengthAtPoint(Light light, Vector3 point, Plane plane)
|
private static float CalculateLightStrengthAtPoint(Light light, 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
|
||||||
|
|
|
@ -2,6 +2,33 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace KeepersCompound.Lightmapper;
|
namespace KeepersCompound.Lightmapper;
|
||||||
|
|
||||||
|
public static class Utils
|
||||||
|
{
|
||||||
|
// Expects Hue and Saturation are 0-1, Brightness 0-255
|
||||||
|
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
|
||||||
|
public static Vector3 HsbToRgb(float hue, float saturation, float brightness)
|
||||||
|
{
|
||||||
|
hue *= 360;
|
||||||
|
var hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
|
||||||
|
var f = hue / 60 - Math.Floor(hue / 60);
|
||||||
|
|
||||||
|
var v = Convert.ToInt32(brightness);
|
||||||
|
var p = Convert.ToInt32(brightness * (1 - saturation));
|
||||||
|
var q = Convert.ToInt32(brightness * (1 - f * saturation));
|
||||||
|
var t = Convert.ToInt32(brightness * (1 - (1 - f) * saturation));
|
||||||
|
|
||||||
|
return hi switch
|
||||||
|
{
|
||||||
|
0 => new Vector3(v, t, p),
|
||||||
|
1 => new Vector3(q, v, p),
|
||||||
|
2 => new Vector3(p, v, t),
|
||||||
|
3 => new Vector3(p, q, v),
|
||||||
|
4 => new Vector3(t, p, v),
|
||||||
|
_ => new Vector3(v, p, q),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class MathUtils
|
public static class MathUtils
|
||||||
{
|
{
|
||||||
public const float Epsilon = 0.001f;
|
public const float Epsilon = 0.001f;
|
||||||
|
@ -49,6 +76,7 @@ public static class MathUtils
|
||||||
return d2 < r2;
|
return d2 < r2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should automagically handle max float radii
|
||||||
public static bool Intersects(Sphere sphere, Sphere other)
|
public static bool Intersects(Sphere sphere, Sphere other)
|
||||||
{
|
{
|
||||||
var rsum = sphere.Radius + other.Radius;
|
var rsum = sphere.Radius + other.Radius;
|
Loading…
Reference in New Issue