CodeWalker/CodeWalker.Core/GameFiles/Resources/Bounds.cs

5103 lines
189 KiB
C#
Raw Normal View History

2017-09-21 18:33:05 +08:00
/*
Copyright(c) 2015 Neodymium
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
//shamelessly stolen and mangled
using SharpDX;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
2020-01-16 15:03:24 +08:00
using System.Xml;
2017-09-21 18:33:05 +08:00
using System.Text;
using System.Threading.Tasks;
using CodeWalker.World;
2017-09-21 18:33:05 +08:00
using TC = System.ComponentModel.TypeConverterAttribute;
using EXP = System.ComponentModel.ExpandableObjectConverter;
using Collections.Pooled;
using CommunityToolkit.HighPerformance;
using static CodeWalker.GameFiles.MetaXmlBase;
2017-09-21 18:33:05 +08:00
namespace CodeWalker.GameFiles
{
[TC(typeof(EXP))] public class BoundsDictionary : ResourceFileBase
2017-09-21 18:33:05 +08:00
{
public override long BlockLength
{
get { return 64; }
}
// structure data
public uint Unknown_10h { get; set; } // 0x00000001
public uint Unknown_14h { get; set; } // 0x00000001
public uint Unknown_18h { get; set; } // 0x00000001
public uint Unknown_1Ch { get; set; } // 0x00000001
public ResourceSimpleList64_uint BoundNameHashes;
2017-09-21 18:33:05 +08:00
public ResourcePointerList64<Bounds> Bounds { get; set; }
/// <summary>
/// Reads the data-block from a stream.
/// </summary>
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
// read structure data
this.Unknown_10h = reader.ReadUInt32();
this.Unknown_14h = reader.ReadUInt32();
this.Unknown_18h = reader.ReadUInt32();
this.Unknown_1Ch = reader.ReadUInt32();
this.BoundNameHashes = reader.ReadBlock<ResourceSimpleList64_uint>();
2017-09-21 18:33:05 +08:00
this.Bounds = reader.ReadBlock<ResourcePointerList64<Bounds>>();
}
/// <summary>
/// Writes the data-block to a stream.
/// </summary>
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// write structure data
writer.Write(this.Unknown_10h);
writer.Write(this.Unknown_14h);
writer.Write(this.Unknown_18h);
writer.Write(this.Unknown_1Ch);
writer.WriteBlock(this.BoundNameHashes);
writer.WriteBlock(this.Bounds);
2017-09-21 18:33:05 +08:00
}
public override (long, IResourceBlock)[] GetParts()
2017-09-21 18:33:05 +08:00
{
return new (long, IResourceBlock)[] {
(0x20, BoundNameHashes),
(0x30, Bounds)
2017-09-21 18:33:05 +08:00
};
}
}
2020-01-03 19:50:23 +08:00
public enum BoundsType : byte
{
None = 255, //not contained in files, but used as a placeholder in XML conversion
2020-01-03 19:50:23 +08:00
Sphere = 0,
Capsule = 1,
Box = 3,
Geometry = 4,
GeometryBVH = 8,
Composite = 10,
Disc = 12,
Cylinder = 13,
Cloth = 15,
}
[TC(typeof(EXP))] public class Bounds : ResourceFileBase, IResourceXXSystemBlock
2017-09-21 18:33:05 +08:00
{
public override long BlockLength => 112;
2017-09-21 18:33:05 +08:00
// structure data
2020-01-03 19:50:23 +08:00
public BoundsType Type { get; set; }
public byte Unknown_11h { get; set; } // 0x00000000
public ushort Unknown_12h { get; set; } // 0x00000000
public float SphereRadius { get; set; }
public uint Unknown_18h { get; set; } // 0x00000000
public uint Unknown_1Ch { get; set; } // 0x00000000
public Vector3 BoxMax;
public float Margin { get; set; }
public Vector3 BoxMin;
2020-01-03 19:50:23 +08:00
public uint Unknown_3Ch { get; set; } = 1; //1, 2 (yft only)
public Vector3 BoxCenter { get; set; }
public byte MaterialIndex { get; set; }
public byte ProceduralId { get; set; }
public byte RoomId_and_PedDensity { get; set; } //5bits for RoomID and then 3bits for PedDensity
2020-01-03 19:50:23 +08:00
public byte UnkFlags { get; set; } // (bit5 related to PolyFlags, should be a flag called "Has PolyFlags")[check this?]
public Vector3 SphereCenter { get; set; }
public byte PolyFlags { get; set; }
public byte MaterialColorIndex { get; set; }
2020-01-03 19:50:23 +08:00
public ushort Unknown_5Eh { get; set; } // 0x00000000
public Vector3 Unknown_60h { get; set; }
public float Volume { get; set; }
2017-09-21 18:33:05 +08:00
2020-01-03 19:50:23 +08:00
public byte RoomId
{
get => (byte)(RoomId_and_PedDensity & 0x1F);
2020-01-03 19:50:23 +08:00
set
{
RoomId_and_PedDensity = (byte)((RoomId_and_PedDensity & 0xE0) + (value & 0x1F));
}
}
public byte PedDensity
{
get => (byte)(RoomId_and_PedDensity >> 5);
2020-01-03 19:50:23 +08:00
set
{
RoomId_and_PedDensity = (byte)((RoomId_and_PedDensity & 0x1F) + ((value & 0x7) << 5));
}
}
2017-09-21 18:33:05 +08:00
2020-01-03 19:50:23 +08:00
public bool HasChanged { get; set; } = false;
2020-01-01 21:16:49 +08:00
public BoundComposite Parent { get; set; }
2020-01-03 19:50:23 +08:00
public YbnFile OwnerYbn { get; set; }
2020-01-06 19:17:51 +08:00
public object Owner { get; set; }
2017-09-21 18:33:05 +08:00
public string OwnerName { get; set; }
public bool OwnerIsFragment => (Owner is FragPhysicsLOD) || (Owner is FragPhysArchetype) || (Owner is FragDrawable);
2017-09-21 18:33:05 +08:00
public string GetName()
{
string n = OwnerName;
var p = Parent;
while (p != null)
{
n = p.OwnerName;
p = p.Parent;
}
return n;
}
public string GetTitle() => $"{Type}: {GetName()}";
2020-01-03 19:50:23 +08:00
public YbnFile GetRootYbn()
{
var r = OwnerYbn;
var p = Parent;
while (p is not null && r is null)
2020-01-03 19:50:23 +08:00
{
r = p.OwnerYbn;
p = p.Parent;
}
return r;
}
2020-01-06 19:17:51 +08:00
public object GetRootOwner()
{
var r = Owner;
var p = Parent;
while (p is not null && r is null)
2020-01-06 19:17:51 +08:00
{
r = p.Owner;
p = p.Parent;
}
return r;
}
2017-09-21 18:33:05 +08:00
public Matrix _Transform = Matrix.Identity;
public Matrix Transform { get => _Transform; set => _Transform = value; } //when it's the child of a bound composite
public Matrix TransformInv { get; set; } = Matrix.Identity;
2020-01-06 19:17:51 +08:00
public BoundCompositeChildrenFlags CompositeFlags1 { get; set; }
public BoundCompositeChildrenFlags CompositeFlags2 { get; set; }
2020-01-01 21:16:49 +08:00
public virtual Vector3 Scale
{
get => Transform.ScaleVector;
2020-01-01 21:16:49 +08:00
set
{
var m = Transform;
m.ScaleVector = value;
Transform = m;
TransformInv = Matrix.Invert(m);
}
}
public virtual Vector3 Position
{
get => Transform.TranslationVector;
2020-01-01 21:16:49 +08:00
set
{
var m = Transform;
m.TranslationVector = value;
Transform = m;
TransformInv = Matrix.Invert(m);
}
}
public virtual Quaternion Orientation
{
get => Transform.ToQuaternion();
2020-01-01 21:16:49 +08:00
set
{
var m = value.ToMatrix();
m.TranslationVector = Transform.TranslationVector;
m.ScaleVector = Transform.ScaleVector;
Transform = m;
TransformInv = Matrix.Invert(m);
}
}
2017-09-21 18:33:05 +08:00
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
// read structure data
2020-01-03 19:50:23 +08:00
this.Type = (BoundsType)reader.ReadByte();
2017-09-21 18:33:05 +08:00
this.Unknown_11h = reader.ReadByte();
this.Unknown_12h = reader.ReadUInt16();
2020-01-03 19:50:23 +08:00
this.SphereRadius = reader.ReadSingle();
2017-09-21 18:33:05 +08:00
this.Unknown_18h = reader.ReadUInt32();
this.Unknown_1Ch = reader.ReadUInt32();
2020-01-03 19:50:23 +08:00
this.BoxMax = reader.ReadVector3();
this.Margin = reader.ReadSingle();
2020-01-03 19:50:23 +08:00
this.BoxMin = reader.ReadVector3();
2017-09-21 18:33:05 +08:00
this.Unknown_3Ch = reader.ReadUInt32();
2020-01-03 19:50:23 +08:00
this.BoxCenter = reader.ReadVector3();
this.MaterialIndex = reader.ReadByte();
this.ProceduralId = reader.ReadByte();
this.RoomId_and_PedDensity = reader.ReadByte();
2020-01-03 19:50:23 +08:00
this.UnkFlags = reader.ReadByte();
this.SphereCenter = reader.ReadVector3();
this.PolyFlags = reader.ReadByte();
this.MaterialColorIndex = reader.ReadByte();
this.Unknown_5Eh = reader.ReadUInt16();
2020-01-03 19:50:23 +08:00
this.Unknown_60h = reader.ReadVector3();
this.Volume = reader.ReadSingle();
2017-09-21 18:33:05 +08:00
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// write structure data
2020-01-03 19:50:23 +08:00
writer.Write((byte)this.Type);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_11h);
writer.Write(this.Unknown_12h);
2020-01-03 19:50:23 +08:00
writer.Write(this.SphereRadius);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_18h);
writer.Write(this.Unknown_1Ch);
2020-01-03 19:50:23 +08:00
writer.Write(this.BoxMax);
writer.Write(this.Margin);
2020-01-03 19:50:23 +08:00
writer.Write(this.BoxMin);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_3Ch);
2020-01-03 19:50:23 +08:00
writer.Write(this.BoxCenter);
writer.Write(this.MaterialIndex);
writer.Write(this.ProceduralId);
writer.Write(this.RoomId_and_PedDensity);
2020-01-03 19:50:23 +08:00
writer.Write(this.UnkFlags);
writer.Write(this.SphereCenter);
writer.Write(this.PolyFlags);
writer.Write(this.MaterialColorIndex);
writer.Write(this.Unknown_5Eh);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_60h);
2020-01-03 19:50:23 +08:00
writer.Write(this.Volume);
2017-09-21 18:33:05 +08:00
}
2020-01-16 15:03:24 +08:00
public virtual void WriteXml(StringBuilder sb, int indent)
{
YbnXml.SelfClosingTag(sb, indent, "BoxMin " + FloatUtil.GetVector3XmlString(BoxMin));
YbnXml.SelfClosingTag(sb, indent, "BoxMax " + FloatUtil.GetVector3XmlString(BoxMax));
YbnXml.SelfClosingTag(sb, indent, "BoxCenter " + FloatUtil.GetVector3XmlString(BoxCenter));
YbnXml.SelfClosingTag(sb, indent, "SphereCenter " + FloatUtil.GetVector3XmlString(SphereCenter));
YbnXml.ValueTag(sb, indent, "SphereRadius", FloatUtil.ToString(SphereRadius));
YbnXml.ValueTag(sb, indent, "Margin", FloatUtil.ToString(Margin));
YbnXml.ValueTag(sb, indent, "Volume", FloatUtil.ToString(Volume));
YbnXml.SelfClosingTag(sb, indent, "Inertia " + FloatUtil.GetVector3XmlString(Unknown_60h));
YbnXml.ValueTag(sb, indent, "MaterialIndex", MaterialIndex.ToString());
YbnXml.ValueTag(sb, indent, "MaterialColourIndex", MaterialColorIndex.ToString());
YbnXml.ValueTag(sb, indent, "ProceduralID", ProceduralId.ToString());
YbnXml.ValueTag(sb, indent, "RoomID", RoomId.ToString());
YbnXml.ValueTag(sb, indent, "PedDensity", PedDensity.ToString());
YbnXml.ValueTag(sb, indent, "UnkFlags", UnkFlags.ToString());
YbnXml.ValueTag(sb, indent, "PolyFlags", PolyFlags.ToString());
YbnXml.ValueTag(sb, indent, "UnkType", Unknown_3Ch.ToString());
if (Parent != null)
{
YbnXml.WriteRawArray(sb, Transform.ToArray(), indent, "CompositeTransform", "", FloatUtil.ToString, 4);
2020-01-22 20:36:34 +08:00
if (!Parent.OwnerIsFragment)
{
YbnXml.StringTag(sb, indent, "CompositeFlags1", CompositeFlags1.Flags1.ToString());
YbnXml.StringTag(sb, indent, "CompositeFlags2", CompositeFlags1.Flags2.ToString());
}
2020-01-16 15:03:24 +08:00
}
}
public virtual void ReadXml(XmlNode node)
{
BoxMin = Xml.GetChildVector3Attributes(node, "BoxMin");
BoxMax = Xml.GetChildVector3Attributes(node, "BoxMax");
BoxCenter = Xml.GetChildVector3Attributes(node, "BoxCenter");
SphereCenter = Xml.GetChildVector3Attributes(node, "SphereCenter");
2020-01-16 15:03:24 +08:00
SphereRadius = Xml.GetChildFloatAttribute(node, "SphereRadius", "value");
Margin = Xml.GetChildFloatAttribute(node, "Margin", "value");
Volume = Xml.GetChildFloatAttribute(node, "Volume", "value");
Unknown_60h = Xml.GetChildVector3Attributes(node, "Inertia");
2020-01-16 15:03:24 +08:00
MaterialIndex = (byte)Xml.GetChildUIntAttribute(node, "MaterialIndex", "value");
MaterialColorIndex = (byte)Xml.GetChildUIntAttribute(node, "MaterialColourIndex", "value");
ProceduralId = (byte)Xml.GetChildUIntAttribute(node, "ProceduralID", "value");
RoomId = (byte)Xml.GetChildUIntAttribute(node, "RoomID", "value");
PedDensity = (byte)Xml.GetChildUIntAttribute(node, "PedDensity", "value");
UnkFlags = (byte)Xml.GetChildUIntAttribute(node, "UnkFlags", "value");
PolyFlags = (byte)Xml.GetChildUIntAttribute(node, "PolyFlags", "value");
Unknown_3Ch = (byte)Xml.GetChildUIntAttribute(node, "UnkType", "value");
if (Parent != null)
{
Transform = new Matrix(Xml.GetChildRawFloatArray(node, "CompositeTransform"));
TransformInv = Matrix.Invert(Transform);
2020-01-22 20:36:34 +08:00
if (!Parent.OwnerIsFragment)
{
var f = new BoundCompositeChildrenFlags();
f.Flags1 = Xml.GetChildEnumInnerText<EBoundCompositeFlags>(node, "CompositeFlags1");
f.Flags2 = Xml.GetChildEnumInnerText<EBoundCompositeFlags>(node, "CompositeFlags2");
CompositeFlags1 = f;
CompositeFlags2 = f;
}
2020-01-16 15:03:24 +08:00
}
}
public static void WriteXmlNode(Bounds b, StringBuilder sb, int indent, string name = "Bounds")
{
if (b is null)
{
YbnXml.SelfClosingTag(sb, indent, name + " type=\"" + BoundsType.None.ToString() + "\"");
}
else
{
YbnXml.OpenTag(sb, indent, name + " type=\"" + b.Type.ToString() + "\"");
b.WriteXml(sb, indent + 1);
YbnXml.CloseTag(sb, indent, name);
}
2020-01-16 15:03:24 +08:00
}
public static Bounds? ReadXmlNode(XmlNode node, object owner = null, BoundComposite parent = null)
2020-01-16 15:03:24 +08:00
{
if (node is null)
return null;
2020-01-16 15:03:24 +08:00
var typestr = Xml.GetStringAttribute(node, "type");
var type = Xml.GetEnumValue<BoundsType>(typestr);
var b = Create(type);
if (b != null)
{
b.Type = type;
b.Owner = owner;
b.Parent = parent;
b.ReadXml(node);
}
return b;
}
2017-09-21 18:33:05 +08:00
public IResourceSystemBlock GetType(ResourceDataReader reader, params object[] parameters)
{
reader.Position += 16;
2020-01-03 19:50:23 +08:00
var type = (BoundsType)reader.ReadByte();
2017-09-21 18:33:05 +08:00
reader.Position -= 17;
2020-01-16 15:03:24 +08:00
return Create(type);
}
public static Bounds? Create(BoundsType type)
{
return type switch
{
BoundsType.None => null,
BoundsType.Sphere => new BoundSphere(),
BoundsType.Capsule => new BoundCapsule(),
BoundsType.Box => new BoundBox(),
BoundsType.Geometry => new BoundGeometry(),
BoundsType.GeometryBVH => new BoundBVH(),
BoundsType.Composite => new BoundComposite(),
BoundsType.Disc => new BoundDisc(),
BoundsType.Cylinder => new BoundCylinder(),
BoundsType.Cloth => new BoundCloth(),
_ => null,// throw new Exception("Unknown bound type");
};
2017-09-21 18:33:05 +08:00
}
public virtual void CopyFrom(Bounds? other)
2020-01-13 13:25:52 +08:00
{
if (other is null)
return;
2020-01-13 13:25:52 +08:00
SphereRadius = other.SphereRadius;
SphereCenter = other.SphereCenter;
2020-01-16 15:03:24 +08:00
BoxCenter = other.BoxCenter;
2020-01-13 13:25:52 +08:00
BoxMin = other.BoxMin;
BoxMax = other.BoxMax;
Margin = other.Margin;
Unknown_3Ch = other.Unknown_3Ch;
MaterialIndex = other.MaterialIndex;
MaterialColorIndex = other.MaterialColorIndex;
ProceduralId = other.ProceduralId;
RoomId_and_PedDensity = other.RoomId_and_PedDensity;
UnkFlags = other.UnkFlags;
PolyFlags = other.PolyFlags;
Unknown_60h = other.Unknown_60h;
Volume = other.Volume;
}
public virtual SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return new SpaceSphereIntersectResult();
}
public virtual SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
return new SpaceRayIntersectResult();
}
2017-09-21 18:33:05 +08:00
}
[TC(typeof(EXP))]
public class BoundSphere : Bounds
{
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
FileVFT = 1080221960;
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
var bsph = new BoundingSphere();
2020-01-03 19:50:23 +08:00
bsph.Center = SphereCenter;
bsph.Radius = SphereRadius;
if (sph.Intersects(ref bsph))
{
res.Hit = true;
2020-01-03 19:50:23 +08:00
res.Normal = Vector3.Normalize(sph.Center - SphereCenter);
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var bsph = new BoundingSphere();
2020-01-03 19:50:23 +08:00
bsph.Center = SphereCenter;
bsph.Radius = SphereRadius;
float testdist;
if (ray.Intersects(ref bsph, out testdist) && (testdist < maxdist))
{
res.Hit = true;
res.HitDist = testdist;
res.HitBounds = this;
res.Position = ray.Position + ray.Direction * testdist;
2020-01-03 19:50:23 +08:00
res.Normal = Vector3.Normalize(res.Position - SphereCenter);
res.Material.Type = MaterialIndex;
}
return res;
}
}
[TC(typeof(EXP))] public class BoundCapsule : Bounds
2017-09-21 18:33:05 +08:00
{
public override long BlockLength
{
get { return 128; }
}
// structure data
public uint Unknown_70h { get; set; } // 0x00000000
public uint Unknown_74h { get; set; } // 0x00000000
public uint Unknown_78h { get; set; } // 0x00000000
public uint Unknown_7Ch { get; set; } // 0x00000000
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
// read structure data
this.Unknown_70h = reader.ReadUInt32();
this.Unknown_74h = reader.ReadUInt32();
this.Unknown_78h = reader.ReadUInt32();
this.Unknown_7Ch = reader.ReadUInt32();
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// write structure data
writer.Write(this.Unknown_70h);
writer.Write(this.Unknown_74h);
writer.Write(this.Unknown_78h);
writer.Write(this.Unknown_7Ch);
}
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
FileVFT = 1080213112;
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
var bcap = new BoundingCapsule();
2020-01-03 19:50:23 +08:00
var extent = new Vector3(0, SphereRadius - Margin, 0);
bcap.PointA = SphereCenter - extent;
bcap.PointB = SphereCenter + extent;
bcap.Radius = Margin;
if (sph.Intersects(in bcap, out res.Normal))
{
res.Hit = true;
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var bcap = new BoundingCapsule();
2020-01-03 19:50:23 +08:00
var extent = new Vector3(0, SphereRadius - Margin, 0);
bcap.PointA = SphereCenter - extent;
bcap.PointB = SphereCenter + extent;
bcap.Radius = Margin;
float testdist;
if (ray.Intersects(in bcap, out testdist) && (testdist < maxdist))
{
res.Hit = true;
res.HitDist = testdist;
res.HitBounds = this;
res.Position = ray.Position + ray.Direction * testdist;
res.Normal = bcap.Normal(in res.Position);
res.Material.Type = MaterialIndex;
}
return res;
}
2017-09-21 18:33:05 +08:00
}
[TC(typeof(EXP))] public class BoundBox : Bounds
{
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
FileVFT = 1080221016;
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
2020-01-03 19:50:23 +08:00
var bbox = new BoundingBox(BoxMin, BoxMax);
if (sph.Intersects(ref bbox))
{
var sphmin = sph.Center - sph.Radius;
var sphmax = sph.Center + sph.Radius;
var n = Vector3.Zero;
float eps = sph.Radius * 0.8f;
2020-01-03 19:50:23 +08:00
if (Math.Abs(sphmax.X - BoxMin.X) < eps) n -= Vector3.UnitX;
else if (Math.Abs(sphmin.X - BoxMax.X) < eps) n += Vector3.UnitX;
else if (Math.Abs(sphmax.Y - BoxMin.Y) < eps) n -= Vector3.UnitY;
else if (Math.Abs(sphmin.Y - BoxMax.Y) < eps) n += Vector3.UnitY;
else if (Math.Abs(sphmax.Z - BoxMin.Z) < eps) n -= Vector3.UnitZ;
else if (Math.Abs(sphmin.Z - BoxMax.Z) < eps) n += Vector3.UnitZ;
else
{ n = Vector3.UnitZ; } //ray starts inside the box...
res.Normal = Vector3.Normalize(n);
res.Hit = true;
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
2020-01-03 19:50:23 +08:00
var bbox = new BoundingBox(BoxMin, BoxMax);
float testdist;
if (ray.Intersects(ref bbox, out testdist) && (testdist < maxdist))
{
const float eps = 0.002f;
var n = Vector3.Zero;
var hpt = ray.Position + ray.Direction * testdist;
2020-01-03 19:50:23 +08:00
if (Math.Abs(hpt.X - BoxMin.X) < eps) n = -Vector3.UnitX;
else if (Math.Abs(hpt.X - BoxMax.X) < eps) n = Vector3.UnitX;
else if (Math.Abs(hpt.Y - BoxMin.Y) < eps) n = -Vector3.UnitY;
else if (Math.Abs(hpt.Y - BoxMax.Y) < eps) n = Vector3.UnitY;
else if (Math.Abs(hpt.Z - BoxMin.Z) < eps) n = -Vector3.UnitZ;
else if (Math.Abs(hpt.Z - BoxMax.Z) < eps) n = Vector3.UnitZ;
else
{ n = Vector3.UnitZ; } //ray starts inside the box...
res.Hit = true;
res.HitDist = testdist;
res.HitBounds = this;
res.Position = hpt;
res.Normal = n;
res.Material.Type = MaterialIndex;
}
return res;
}
}
[TC(typeof(EXP))] public class BoundDisc : Bounds
{
public override long BlockLength
{
get { return 128; }
}
// structure data
public uint Unknown_70h { get; set; } // 0x00000000
public uint Unknown_74h { get; set; } // 0x00000000
public uint Unknown_78h { get; set; } // 0x00000000
public uint Unknown_7Ch { get; set; } // 0x00000000
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
// read structure data
this.Unknown_70h = reader.ReadUInt32();
this.Unknown_74h = reader.ReadUInt32();
this.Unknown_78h = reader.ReadUInt32();
this.Unknown_7Ch = reader.ReadUInt32();
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// write structure data
writer.Write(this.Unknown_70h);
writer.Write(this.Unknown_74h);
writer.Write(this.Unknown_78h);
writer.Write(this.Unknown_7Ch);
}
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
FileVFT = 1080229960;
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
//as a temporary hack, just use the sphere-sphere intersection //TODO: sphere-disc intersection
var res = new SpaceSphereIntersectResult();
var bsph = new BoundingSphere();
2020-01-03 19:50:23 +08:00
bsph.Center = SphereCenter;
bsph.Radius = SphereRadius;
if (sph.Intersects(ref bsph))
{
res.Hit = true;
2020-01-03 19:50:23 +08:00
res.Normal = Vector3.Normalize(sph.Center - SphereCenter);
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var bcyl = new BoundingCylinder();
var size = new Vector3(Margin, 0, 0);
2020-01-03 19:50:23 +08:00
bcyl.PointA = SphereCenter - size;
bcyl.PointB = SphereCenter + size;
bcyl.Radius = SphereRadius;
Vector3 n;
float testdist;
if (ray.Intersects(in bcyl, out testdist, out n) && (testdist < maxdist))
{
res.Hit = true;
res.HitDist = testdist;
res.HitBounds = this;
res.Position = ray.Position + ray.Direction * testdist;
res.Normal = n;
res.Material.Type = MaterialIndex;
}
return res;
}
}
[TC(typeof(EXP))] public class BoundCylinder : Bounds
{
public override long BlockLength
{
get { return 128; }
}
// structure data
public uint Unknown_70h { get; set; } // 0x00000000
public uint Unknown_74h { get; set; } // 0x00000000
public uint Unknown_78h { get; set; } // 0x00000000
public uint Unknown_7Ch { get; set; } // 0x00000000
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
// read structure data
this.Unknown_70h = reader.ReadUInt32();
this.Unknown_74h = reader.ReadUInt32();
this.Unknown_78h = reader.ReadUInt32();
this.Unknown_7Ch = reader.ReadUInt32();
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// write structure data
writer.Write(this.Unknown_70h);
writer.Write(this.Unknown_74h);
writer.Write(this.Unknown_78h);
writer.Write(this.Unknown_7Ch);
}
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
FileVFT = 1080202872;
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
//as a temporary hack, just use the sphere-capsule intersection //TODO: sphere-cylinder intersection
var res = new SpaceSphereIntersectResult();
var bcap = new BoundingCapsule();
2020-01-03 19:50:23 +08:00
var extent = (BoxMax - BoxMin).Abs();
var size = new Vector3(0, extent.Y * 0.5f, 0);
2020-01-03 19:50:23 +08:00
bcap.PointA = SphereCenter - size;
bcap.PointB = SphereCenter + size;
bcap.Radius = extent.X * 0.5f;
if (sph.Intersects(in bcap, out res.Normal))
{
res.Hit = true;
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var bcyl = new BoundingCylinder();
2020-01-03 19:50:23 +08:00
var extent = (BoxMax - BoxMin).Abs();
var size = new Vector3(0, extent.Y * 0.5f, 0);
2020-01-03 19:50:23 +08:00
bcyl.PointA = SphereCenter - size;
bcyl.PointB = SphereCenter + size;
bcyl.Radius = extent.X * 0.5f;
Vector3 n;
float testdist;
if (ray.Intersects(in bcyl, out testdist, out n) && (testdist < maxdist))
{
res.Hit = true;
res.HitDist = testdist;
res.HitBounds = this;
res.Position = ray.Position + ray.Direction * testdist;
res.Normal = n;
res.Material.Type = MaterialIndex;
}
return res;
}
}
2020-01-03 19:50:23 +08:00
[TC(typeof(EXP))] public class BoundCloth : Bounds
{
//public override long BlockLength
//{
// get { return 128; }
//}
//// TODO! length currently unknown!
//public uint Unknown_70h { get; set; }
//public uint Unknown_74h { get; set; }
//public uint Unknown_78h { get; set; }
//public uint Unknown_7Ch { get; set; }
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
//// read structure data
//this.Unknown_70h = reader.ReadUInt32();
//this.Unknown_74h = reader.ReadUInt32();
//this.Unknown_78h = reader.ReadUInt32();
//this.Unknown_7Ch = reader.ReadUInt32();
// found in eg:
//dt1_16_build4_cloth11.yft
//dt1_16_build4_cloth12.yft
//dt1_16_build4_cloth8.yft
//dt1_16_build4_clothb.yft
//dt1_17_cloth1.yft
//dt1_17_cloth1b.yft
//dt1_17_cloth3.yft
//dt1_21_dtzz_cloth_a.yft
//dt1_21_dtzz_cloth_b.yft
//dt1_21_dtzz_cloth_bx.yft
//dt1_21_dtzz_cloth_c.yft
//dt1_21_dtzz_cloth_d.yft
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
//// write structure data
//writer.Write(this.Unknown_70h);
//writer.Write(this.Unknown_74h);
//writer.Write(this.Unknown_78h);
//writer.Write(this.Unknown_7Ch);
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult(); //TODO...
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult(); //TODO...
return res;
}
}
[TC(typeof(EXP))] public class BoundGeometry : Bounds
2017-09-21 18:33:05 +08:00
{
public override long BlockLength
{
get { return 304; }
}
// structure data
2020-01-03 19:50:23 +08:00
public uint Unknown_70h { get; set; } // 0x00000000
public uint Unknown_74h { get; set; } // 0x00000000
2023-07-28 07:15:42 +08:00
public ulong VerticesShrunkPointer { get; set; }
2020-01-03 19:50:23 +08:00
public ushort Unknown_80h { get; set; } // 0x0000
public ushort Unknown_82h { get; set; } //des_.ydr's? some extra data to read..?? is this some extra poly count?
2023-07-28 07:15:42 +08:00
public uint VerticesShrunkCount { get; set; } //always equal to VerticesCount
2017-09-21 18:33:05 +08:00
public ulong PolygonsPointer { get; set; }
public Vector3 Quantum { get; set; }
public float Unknown_9Ch { get; set; }
public Vector3 CenterGeom { get; set; }
public float Unknown_ACh { get; set; }
public ulong VerticesPointer { get; set; }
2020-01-06 19:17:51 +08:00
public ulong VertexColoursPointer { get; set; }
public ulong OctantsPointer { get; set; }
public ulong OctantItemsPointer { get; set; }
2017-09-21 18:33:05 +08:00
public uint VerticesCount { get; set; }
public uint PolygonsCount { get; set; }
public uint Unknown_D8h { get; set; } // 0x00000000
public uint Unknown_DCh { get; set; } // 0x00000000
public uint Unknown_E0h { get; set; } // 0x00000000
public uint Unknown_E4h { get; set; } // 0x00000000
public uint Unknown_E8h { get; set; } // 0x00000000
public uint Unknown_ECh { get; set; } // 0x00000000
public ulong MaterialsPointer { get; set; }
public ulong MaterialColoursPointer { get; set; }
public uint Unknown_100h { get; set; } // 0x00000000
public uint Unknown_104h { get; set; } // 0x00000000
public uint Unknown_108h { get; set; } // 0x00000000
public uint Unknown_10Ch { get; set; } // 0x00000000
public uint Unknown_110h { get; set; } // 0x00000000
public uint Unknown_114h { get; set; } // 0x00000000
public ulong PolygonMaterialIndicesPointer { get; set; }
public byte MaterialsCount { get; set; }
public byte MaterialColoursCount { get; set; }
public ushort Unknown_122h { get; set; } // 0x0000
public uint Unknown_124h { get; set; } // 0x00000000
public uint Unknown_128h { get; set; } // 0x00000000
public uint Unknown_12Ch { get; set; } // 0x00000000
2023-07-28 07:15:42 +08:00
public Vector3[] VerticesShrunk { get; set; } // Vertices but shrunk by margin along normal
2017-09-21 18:33:05 +08:00
public BoundPolygon[] Polygons { get; set; }
public Vector3[] Vertices { get; set; }
2020-01-06 19:17:51 +08:00
public BoundMaterialColour[] VertexColours { get; set; }//not sure, it seems like colours anyway, see eg. prologue03_10.ybn
public BoundGeomOctants Octants { get; set; }
2017-09-21 18:33:05 +08:00
public BoundMaterial_s[] Materials { get; set; }
public BoundMaterialColour[] MaterialColours { get; set; }
public byte[] PolygonMaterialIndices { get; set; }
2023-07-28 07:15:42 +08:00
private ResourceSystemStructBlock<BoundVertex_s> VerticesShrunkBlock = null;
private ResourceSystemDataBlock PolygonsBlock = null;
private ResourceSystemStructBlock<BoundVertex_s> VerticesBlock = null;
2020-01-06 19:17:51 +08:00
private ResourceSystemStructBlock<BoundMaterialColour> VertexColoursBlock = null;
private ResourceSystemStructBlock<BoundMaterial_s> MaterialsBlock = null;
private ResourceSystemStructBlock<BoundMaterialColour> MaterialColoursBlock = null;
private ResourceSystemStructBlock<byte> PolygonMaterialIndicesBlock = null;
2020-01-07 17:51:53 +08:00
private BoundVertex[] VertexObjects = null; //for use by the editor, created as needed by GetVertexObject()
2017-09-21 18:33:05 +08:00
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
this.Unknown_70h = reader.ReadUInt32();
this.Unknown_74h = reader.ReadUInt32();
2023-07-28 07:15:42 +08:00
this.VerticesShrunkPointer = reader.ReadUInt64();
2020-01-03 19:50:23 +08:00
this.Unknown_80h = reader.ReadUInt16();
this.Unknown_82h = reader.ReadUInt16();
2023-07-28 07:15:42 +08:00
this.VerticesShrunkCount = reader.ReadUInt32();
2017-09-21 18:33:05 +08:00
this.PolygonsPointer = reader.ReadUInt64();
2020-01-03 19:50:23 +08:00
this.Quantum = reader.ReadVector3();
this.Unknown_9Ch = reader.ReadSingle();
this.CenterGeom = reader.ReadVector3();
2017-09-21 18:33:05 +08:00
this.Unknown_ACh = reader.ReadSingle();
this.VerticesPointer = reader.ReadUInt64();
2020-01-06 19:17:51 +08:00
this.VertexColoursPointer = reader.ReadUInt64();
this.OctantsPointer = reader.ReadUInt64();
this.OctantItemsPointer = reader.ReadUInt64();
2017-09-21 18:33:05 +08:00
this.VerticesCount = reader.ReadUInt32();
this.PolygonsCount = reader.ReadUInt32();
this.Unknown_D8h = reader.ReadUInt32();
this.Unknown_DCh = reader.ReadUInt32();
this.Unknown_E0h = reader.ReadUInt32();
this.Unknown_E4h = reader.ReadUInt32();
this.Unknown_E8h = reader.ReadUInt32();
this.Unknown_ECh = reader.ReadUInt32();
this.MaterialsPointer = reader.ReadUInt64();
this.MaterialColoursPointer = reader.ReadUInt64();
this.Unknown_100h = reader.ReadUInt32();
this.Unknown_104h = reader.ReadUInt32();
this.Unknown_108h = reader.ReadUInt32();
this.Unknown_10Ch = reader.ReadUInt32();
this.Unknown_110h = reader.ReadUInt32();
this.Unknown_114h = reader.ReadUInt32();
this.PolygonMaterialIndicesPointer = reader.ReadUInt64();
this.MaterialsCount = reader.ReadByte();
this.MaterialColoursCount = reader.ReadByte();
this.Unknown_122h = reader.ReadUInt16();
this.Unknown_124h = reader.ReadUInt32();
this.Unknown_128h = reader.ReadUInt32();
this.Unknown_12Ch = reader.ReadUInt32();
2023-07-28 07:15:42 +08:00
var vertsShrunk = reader.ReadStructsAt<BoundVertex_s>(this.VerticesShrunkPointer, this.VerticesShrunkCount);
if (vertsShrunk != null) //seems to be in YFT's
2020-01-06 19:17:51 +08:00
{
2023-07-28 07:15:42 +08:00
VerticesShrunk = new Vector3[vertsShrunk.Length];
for (int i = 0; i < vertsShrunk.Length; i++)
2020-01-06 19:17:51 +08:00
{
2023-07-28 07:15:42 +08:00
var bv = vertsShrunk[i];
VerticesShrunk[i] = bv.Vector * Quantum;
2020-01-06 19:17:51 +08:00
}
}
2017-09-21 18:33:05 +08:00
ReadPolygons(reader);
var verts = reader.ReadStructsAt<BoundVertex_s>(this.VerticesPointer, this.VerticesCount);
if (verts != null)
{
Vertices = new Vector3[verts.Length];
for (int i = 0; i < verts.Length; i++)
{
var bv = verts[i];
2020-01-06 19:17:51 +08:00
Vertices[i] = bv.Vector * Quantum;
2017-09-21 18:33:05 +08:00
}
}
2020-01-06 19:17:51 +08:00
this.VertexColours = reader.ReadStructsAt<BoundMaterialColour>(this.VertexColoursPointer, this.VerticesCount);
2017-09-21 18:33:05 +08:00
this.Octants = reader.ReadBlockAt<BoundGeomOctants>(this.OctantsPointer, this.OctantItemsPointer);
2017-09-21 18:33:05 +08:00
this.Materials = reader.ReadStructsAt<BoundMaterial_s>(this.MaterialsPointer, (this.MaterialsCount < 4) ? 4u : MaterialsCount);
2017-09-21 18:33:05 +08:00
this.MaterialColours = reader.ReadStructsAt<BoundMaterialColour>(this.MaterialColoursPointer, this.MaterialColoursCount);
this.PolygonMaterialIndices = reader.ReadBytesAt(this.PolygonMaterialIndicesPointer, (uint)PolygonsCount);
2020-01-03 19:50:23 +08:00
if ((MaterialsPointer != 0) && (MaterialsCount < 4))
{
//for (var i = MaterialsCount; i < 4; i++)
//{
// var m = Materials[i];
// if ((m.Data1 != 0) || (m.Data2 != 0))
// { }//no hit
//}
//the read array was padded, so remove the padding from this array. will re-add padding in BuildMaterials...
var mats = new BoundMaterial_s[MaterialsCount];
for (int i = 0; i < MaterialsCount; i++)
{
mats[i] = Materials[i];
}
Materials = mats;
}
//if (Vertices2Count != VerticesCount)
//{ }//no hit here
//if (Unknown_9Ch != 0)
//{ }
//if (Unknown_ACh != 0)
//{ }
2020-01-03 19:50:23 +08:00
//switch (Unknown_82h)
//{
// case 0:
// case 1://ybns
// break;
// default://des_.ydr's? some extra data to read..?? is this some extra poly count?
// break;
//}
//if (OctantsPointer != 0)
//{
// if (OctantItemsPointer != OctantsPointer + 32)
// { }//no hit
//}
2020-01-03 19:50:23 +08:00
2017-09-21 18:33:05 +08:00
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// update structure data
2023-07-28 07:15:42 +08:00
this.VerticesShrunkPointer = (ulong)(this.VerticesShrunkBlock != null ? this.VerticesShrunkBlock.FilePosition : 0);
this.PolygonsPointer = (ulong)(this.PolygonsBlock != null ? this.PolygonsBlock.FilePosition : 0);
this.VerticesPointer = (ulong)(this.VerticesBlock != null ? this.VerticesBlock.FilePosition : 0);
2020-01-06 19:17:51 +08:00
this.VertexColoursPointer = (ulong)(this.VertexColoursBlock != null ? this.VertexColoursBlock.FilePosition : 0);
this.OctantsPointer = (ulong)(this.Octants != null ? this.Octants.FilePosition : 0);
this.OctantItemsPointer = (OctantsPointer != 0) ? OctantsPointer + 32 : 0;
this.VerticesCount = (uint)(this.VerticesBlock != null ? this.VerticesBlock.ItemCount : 0);
2023-07-28 07:15:42 +08:00
this.VerticesShrunkCount = this.VerticesCount;
this.PolygonsCount = (uint)(this.Polygons != null ? this.Polygons.Length : 0);
this.MaterialsPointer = (ulong)(this.MaterialsBlock != null ? this.MaterialsBlock.FilePosition : 0);
this.MaterialColoursPointer = (ulong)(this.MaterialColoursBlock != null ? this.MaterialColoursBlock.FilePosition : 0);
this.PolygonMaterialIndicesPointer = (ulong)(this.PolygonMaterialIndicesBlock != null ? this.PolygonMaterialIndicesBlock.FilePosition : 0);
//this.MaterialsCount = (byte)(this.MaterialsBlock != null ? this.MaterialsBlock.ItemCount : 0);//this is updated by BuildMaterials, and the array could include padding!
this.MaterialColoursCount = (byte)(this.MaterialColoursBlock != null ? this.MaterialColoursBlock.ItemCount : 0);
2017-09-21 18:33:05 +08:00
// write structure data
writer.Write(this.Unknown_70h);
writer.Write(this.Unknown_74h);
2023-07-28 07:15:42 +08:00
writer.Write(this.VerticesShrunkPointer);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_80h);
2020-01-03 19:50:23 +08:00
writer.Write(this.Unknown_82h);
2023-07-28 07:15:42 +08:00
writer.Write(this.VerticesShrunkCount);
2017-09-21 18:33:05 +08:00
writer.Write(this.PolygonsPointer);
2020-01-03 19:50:23 +08:00
writer.Write(this.Quantum);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_9Ch);
2020-01-03 19:50:23 +08:00
writer.Write(this.CenterGeom);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_ACh);
writer.Write(this.VerticesPointer);
2020-01-06 19:17:51 +08:00
writer.Write(this.VertexColoursPointer);
writer.Write(this.OctantsPointer);
writer.Write(this.OctantItemsPointer);
2017-09-21 18:33:05 +08:00
writer.Write(this.VerticesCount);
writer.Write(this.PolygonsCount);
writer.Write(this.Unknown_D8h);
writer.Write(this.Unknown_DCh);
writer.Write(this.Unknown_E0h);
writer.Write(this.Unknown_E4h);
writer.Write(this.Unknown_E8h);
writer.Write(this.Unknown_ECh);
writer.Write(this.MaterialsPointer);
writer.Write(this.MaterialColoursPointer);
writer.Write(this.Unknown_100h);
writer.Write(this.Unknown_104h);
writer.Write(this.Unknown_108h);
writer.Write(this.Unknown_10Ch);
writer.Write(this.Unknown_110h);
writer.Write(this.Unknown_114h);
writer.Write(this.PolygonMaterialIndicesPointer);
writer.Write(this.MaterialsCount);
writer.Write(this.MaterialColoursCount);
writer.Write(this.Unknown_122h);
writer.Write(this.Unknown_124h);
writer.Write(this.Unknown_128h);
writer.Write(this.Unknown_12Ch);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
base.WriteXml(sb, indent);
YbnXml.SelfClosingTag(sb, indent, "GeometryCenter " + FloatUtil.GetVector3XmlString(CenterGeom));
YbnXml.ValueTag(sb, indent, "UnkFloat1", FloatUtil.ToString(Unknown_9Ch));
YbnXml.ValueTag(sb, indent, "UnkFloat2", FloatUtil.ToString(Unknown_ACh));
if (Materials != null)
{
YbnXml.WriteItemArray(sb, Materials, indent, "Materials");
}
if (MaterialColours != null)
{
YbnXml.WriteRawArray(sb, MaterialColours, indent, "MaterialColours", "", YbnXml.FormatBoundMaterialColour, 1);
}
if (Vertices != null)
{
YbnXml.WriteRawArray(sb, Vertices, indent, "Vertices", "", YbnXml.FormatVector3, 1);
}
if (VertexColours != null)
{
YbnXml.WriteRawArray(sb, VertexColours, indent, "VertexColours", "", YbnXml.FormatBoundMaterialColour, 1);
}
if (Polygons != null)
{
YbnXml.WriteCustomItemArray(sb, Polygons, indent, "Polygons");
}
}
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
CenterGeom = Xml.GetChildVector3Attributes(node, "GeometryCenter");
2020-01-16 15:03:24 +08:00
Unknown_9Ch = Xml.GetChildFloatAttribute(node, "UnkFloat1", "value");
Unknown_ACh = Xml.GetChildFloatAttribute(node, "UnkFloat2", "value");
Materials = XmlMeta.ReadItemArray<BoundMaterial_s>(node, "Materials");
MaterialColours = XmlYbn.GetChildRawBoundMaterialColourArray(node, "MaterialColours");
Vertices = Xml.GetChildRawVector3ArrayNullable(node, "Vertices");
VertexColours = XmlYbn.GetChildRawBoundMaterialColourArray(node, "VertexColours");
var pnode = node.SelectSingleNode("Polygons");
if (pnode != null)
{
var inodes = pnode.ChildNodes;
if (inodes?.Count > 0)
{
var polylist = new List<BoundPolygon>();
foreach (XmlNode inode in inodes)
{
var typestr = inode.Name;
var type = Xml.GetEnumValue<BoundPolygonType>(typestr);
var poly = CreatePolygon(type);
if (poly != null)
{
poly.ReadXml(inode);
polylist.Add(poly);
}
}
Polygons = polylist.ToArray();
}
}
CalculateVertsShrunkByMargin();
CalculateOctants();
2020-01-16 15:03:24 +08:00
BuildMaterials();
CalculateQuantum();
UpdateEdgeIndices();
UpdateTriangleAreas();
FileVFT = 1080226408;
2020-01-16 15:03:24 +08:00
}
2017-09-21 18:33:05 +08:00
public override IResourceBlock[] GetReferences()
{
2020-01-06 19:17:51 +08:00
BuildMaterials();
CalculateQuantum();
2020-01-12 11:25:27 +08:00
UpdateEdgeIndices();
2020-01-13 20:31:27 +08:00
UpdateTriangleAreas();
2020-01-06 19:17:51 +08:00
using var list = new PooledList<IResourceBlock>(base.GetReferences());
2023-07-28 07:15:42 +08:00
if (VerticesShrunk != null)
{
var verts = new BoundVertex_s[VerticesShrunk.Length];
for (int i = 0; i < VerticesShrunk.Length; i++)
2020-01-06 19:17:51 +08:00
{
var vq = VerticesShrunk[i] / Quantum;
verts[i] = new BoundVertex_s(vq);
2020-01-06 19:17:51 +08:00
}
VerticesShrunkBlock = new ResourceSystemStructBlock<BoundVertex_s>(verts);
2023-07-28 07:15:42 +08:00
list.Add(VerticesShrunkBlock);
}
if (Polygons != null)
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
foreach (var poly in Polygons)
{
poly.Write(bw);
}
var polydata = new byte[ms.Length];
ms.Position = 0;
ms.Read(polydata, 0, polydata.Length);
for (int i = 0; i < Polygons.Length; i++)
{
var o = i * 16;
var t = (byte)Polygons[i].Type;
var b = polydata[o];
polydata[o] = (byte)((b & 0xF8) | (t & 7)); //add the poly types back in!
}
if (polydata.Length != (Polygons.Length * 16))
{ }
PolygonsBlock = new ResourceSystemDataBlock(polydata);
list.Add(PolygonsBlock);
}
if (Vertices != null)
{
var verts = new List<BoundVertex_s>();
foreach (var v in Vertices)
{
2020-01-06 19:17:51 +08:00
var vq = v / Quantum;
var vs = new BoundVertex_s(vq);
verts.Add(vs);
}
VerticesBlock = new ResourceSystemStructBlock<BoundVertex_s>(verts.ToArray());
list.Add(VerticesBlock);
}
2020-01-06 19:17:51 +08:00
if (VertexColours != null)
{
2020-01-06 19:17:51 +08:00
VertexColoursBlock = new ResourceSystemStructBlock<BoundMaterialColour>(VertexColours);
list.Add(VertexColoursBlock);
}
if (Octants != null)
{
list.Add(Octants);//this one is already a resource block!
}
if (Materials != null)
{
MaterialsBlock = new ResourceSystemStructBlock<BoundMaterial_s>(Materials);
list.Add(MaterialsBlock);
}
if (MaterialColours != null)
{
MaterialColoursBlock = new ResourceSystemStructBlock<BoundMaterialColour>(MaterialColours);
list.Add(MaterialColoursBlock);
}
if (PolygonMaterialIndices != null)
{
PolygonMaterialIndicesBlock = new ResourceSystemStructBlock<byte>(PolygonMaterialIndices);
list.Add(PolygonMaterialIndicesBlock);
}
return list.ToArray();
}
2020-01-16 15:03:24 +08:00
private void ReadPolygons(ResourceDataReader reader)
{
if(PolygonsCount==0)
{ return; }
Polygons = new BoundPolygon[PolygonsCount];
uint polybytecount = PolygonsCount * 16;
var polygonData = reader.ReadBytesAt(PolygonsPointer, polybytecount);
for (int i = 0; i < PolygonsCount; i++)
{
var offset = i * 16;
byte b0 = polygonData[offset];
polygonData[offset] = (byte)(b0 & 0xF8);//mask it off
BoundPolygonType type = (BoundPolygonType)(b0 & 7);
BoundPolygon? p = CreatePolygon(type);
if (p is not null)
2020-01-16 15:03:24 +08:00
{
p.Index = i;
p.Read(polygonData, offset);
}
Polygons[i] = p;
}
}
public BoundVertex GetVertexObject(int index)
{
//gets a cached object which references a single vertex in this geometry
if (Vertices == null) return null;
if ((index < 0) || (index >= Vertices.Length)) return null;
if ((VertexObjects == null) || (VertexObjects.Length != Vertices.Length))
{
VertexObjects = new BoundVertex[Vertices.Length];
}
if (index >= VertexObjects.Length) return null;
var r = VertexObjects[index];
if (r == null)
{
r = new BoundVertex(this, index);
VertexObjects[index] = r;
}
return r;
}
public ref readonly Vector3 GetVertex(int index)
2020-01-16 15:03:24 +08:00
{
if (index < 0 || index >= Vertices.Length)
return ref Vector3.Zero;
return ref Vertices[index];
2020-01-16 15:03:24 +08:00
}
public Vector3 GetVertexPos(int index)
{
var v = GetVertex(index) + CenterGeom;
return Vector3.Transform(v, _Transform).XYZ();
2020-01-16 15:03:24 +08:00
}
public void SetVertexPos(int index, Vector3 v)
{
if (index >= 0 && index < Vertices.Length)
2020-01-16 15:03:24 +08:00
{
var t = Vector3.Transform(v, TransformInv).XYZ() - CenterGeom;
Vertices[index] = t;
}
}
public BoundMaterialColour GetVertexColour(int index)
{
return ((VertexColours != null) && (index >= 0) && (index < VertexColours.Length)) ? VertexColours[index] : new BoundMaterialColour();
}
public void SetVertexColour(int index, BoundMaterialColour c)
{
if ((VertexColours != null) && (index >= 0) && (index < VertexColours.Length))
{
VertexColours[index] = c;
}
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
var box = new BoundingBox();
if (Polygons == null)
{ return res; }
2020-01-03 19:50:23 +08:00
box.Minimum = BoxMin;
box.Maximum = BoxMax;
if (!sph.Intersects(ref box))
{ return res; }
SphereIntersectPolygons(ref sph, ref res, 0, Polygons.Length);
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var box = new BoundingBox();
if (Polygons == null)
{ return res; }
2020-01-03 19:50:23 +08:00
box.Minimum = BoxMin;
box.Maximum = BoxMax;
float bvhboxhittest;
if (!ray.Intersects(ref box, out bvhboxhittest))
{ return res; }
if (bvhboxhittest > maxdist)
{ return res; } //already a closer hit.
res.HitDist = maxdist;
RayIntersectPolygons(ref ray, ref res, 0, Polygons.Length);
return res;
}
protected void SphereIntersectPolygons(ref BoundingSphere sph, ref SpaceSphereIntersectResult res, int startIndex, int endIndex)
{
var box = new BoundingBox();
var tsph = new BoundingSphere();
var spht = new BoundingSphere();
var sp = sph.Center;
var sr = sph.Radius;
Vector3 p1, p2, p3, p4, a1, a2, a3;
Vector3 n1 = Vector3.Zero;
for (int p = startIndex; p < endIndex; p++)
{
var polygon = Polygons[p];
bool polyhit = false;
switch (polygon.Type)
{
case BoundPolygonType.Triangle:
var ptri = polygon as BoundPolygonTriangle;
2019-12-31 21:00:04 +08:00
p1 = GetVertexPos(ptri.vertIndex1);
p2 = GetVertexPos(ptri.vertIndex2);
p3 = GetVertexPos(ptri.vertIndex3);
polyhit = sph.Intersects(ref p1, ref p2, ref p3);
if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1));
break;
case BoundPolygonType.Sphere:
var psph = polygon as BoundPolygonSphere;
2019-12-31 21:00:04 +08:00
tsph.Center = GetVertexPos(psph.sphereIndex);
tsph.Radius = psph.sphereRadius;
polyhit = sph.Intersects(ref tsph);
if (polyhit) n1 = Vector3.Normalize(sph.Center - tsph.Center);
break;
case BoundPolygonType.Capsule:
var pcap = polygon as BoundPolygonCapsule;
var tcap = new BoundingCapsule();
2019-12-31 21:00:04 +08:00
tcap.PointA = GetVertexPos(pcap.capsuleIndex1);
tcap.PointB = GetVertexPos(pcap.capsuleIndex2);
tcap.Radius = pcap.capsuleRadius;
polyhit = sph.Intersects(in tcap, out n1);
break;
case BoundPolygonType.Box:
var pbox = polygon as BoundPolygonBox;
2019-12-31 21:00:04 +08:00
p1 = GetVertexPos(pbox.boxIndex1);//corner
p2 = GetVertexPos(pbox.boxIndex2);
p3 = GetVertexPos(pbox.boxIndex3);
p4 = GetVertexPos(pbox.boxIndex4);
a1 = ((p3 + p4) - (p1 + p2)) * 0.5f;
a2 = p3 - (p1 + a1);
a3 = p4 - (p1 + a1);
Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length());
Vector3 m1 = a1 / bs.X;
Vector3 m2 = a2 / bs.Y;
Vector3 m3 = a3 / bs.Z;
if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3);
else if (bs.Y < bs.Z) m2 = Vector3.Cross(m3, m1);
else m3 = Vector3.Cross(m1, m2);
2019-12-31 21:00:04 +08:00
Vector3 tp = sp - (p1);//+cg
spht.Center = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3));
spht.Radius = sph.Radius;
box.Minimum = Vector3.Zero;
box.Maximum = bs;
polyhit = spht.Intersects(ref box);
if (polyhit)
{
Vector3 smin = spht.Center - spht.Radius;
Vector3 smax = spht.Center + spht.Radius;
float eps = spht.Radius * 0.8f;
n1 = Vector3.Zero;
if (Math.Abs(smax.X) < eps) n1 -= m1;
else if (Math.Abs(smin.X - bs.X) < eps) n1 += m1;
if (Math.Abs(smax.Y) < eps) n1 -= m2;
else if (Math.Abs(smin.Y - bs.Y) < eps) n1 += m2;
if (Math.Abs(smax.Z) < eps) n1 -= m3;
else if (Math.Abs(smin.Z - bs.Z) < eps) n1 += m3;
float n1l = n1.Length();
if (n1l > 0.0f) n1 = n1 / n1l;
else n1 = Vector3.UnitZ;
}
break;
case BoundPolygonType.Cylinder:
var pcyl = polygon as BoundPolygonCylinder;
//var tcyl = new BoundingCylinder();
2019-12-31 21:00:04 +08:00
//tcyl.PointA = GetVertexPos(pcyl.cylinderIndex1);
//tcyl.PointB = GetVertexPos(pcyl.cylinderIndex2);
//tcyl.Radius = pcyl.cylinderRadius;
2019-12-31 21:00:04 +08:00
//////polyhit = sph.Intersects(ref tcyl, out polyhittestdist, out n1);
////////TODO
var ttcap = new BoundingCapsule();//just use the capsule intersection for now...
2019-12-31 21:00:04 +08:00
ttcap.PointA = GetVertexPos(pcyl.cylinderIndex1);
ttcap.PointB = GetVertexPos(pcyl.cylinderIndex2);
ttcap.Radius = pcyl.cylinderRadius;
polyhit = sph.Intersects(in ttcap, out n1);
break;
default:
break;
}
if (polyhit) // && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist))
{
res.HitPolygon = polygon;
//itemhitdist = polyhittestdist;
//ybnhit = true;
res.Hit = true;
res.Normal = n1;
}
res.TestedPolyCount++;
}
}
protected void RayIntersectPolygons(ref Ray ray, ref SpaceRayIntersectResult res, int startIndex, int endIndex)
{
var box = new BoundingBox();
var tsph = new BoundingSphere();
var rayt = new Ray();
var rp = ray.Position;
var rd = ray.Direction;
Vector3 p1, p2, p3, p4, a1, a2, a3;
Vector3 n1 = Vector3.Zero;
for (int p = startIndex; p < endIndex; p++)
{
var polygon = Polygons[p];
float polyhittestdist = float.MaxValue;
bool polyhit = false;
switch (polygon.Type)
{
case BoundPolygonType.Triangle:
var ptri = polygon as BoundPolygonTriangle;
2019-12-31 21:00:04 +08:00
p1 = GetVertexPos(ptri.vertIndex1);
p2 = GetVertexPos(ptri.vertIndex2);
p3 = GetVertexPos(ptri.vertIndex3);
polyhit = ray.Intersects(ref p1, ref p2, ref p3, out polyhittestdist);
if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1));
break;
case BoundPolygonType.Sphere:
var psph = polygon as BoundPolygonSphere;
2019-12-31 21:00:04 +08:00
tsph.Center = GetVertexPos(psph.sphereIndex);
tsph.Radius = psph.sphereRadius;
polyhit = ray.Intersects(ref tsph, out polyhittestdist);
if (polyhit) n1 = Vector3.Normalize((ray.Position + ray.Direction * polyhittestdist) - tsph.Center);
break;
case BoundPolygonType.Capsule:
var pcap = polygon as BoundPolygonCapsule;
var tcap = new BoundingCapsule();
2019-12-31 21:00:04 +08:00
tcap.PointA = GetVertexPos(pcap.capsuleIndex1);
tcap.PointB = GetVertexPos(pcap.capsuleIndex2);
tcap.Radius = pcap.capsuleRadius;
polyhit = ray.Intersects(in tcap, out polyhittestdist);
res.Position = (ray.Position + ray.Direction * polyhittestdist);
if (polyhit) n1 = tcap.Normal(in res.Position);
break;
case BoundPolygonType.Box:
var pbox = polygon as BoundPolygonBox;
2019-12-31 21:00:04 +08:00
p1 = GetVertexPos(pbox.boxIndex1);//corner
p2 = GetVertexPos(pbox.boxIndex2);
p3 = GetVertexPos(pbox.boxIndex3);
p4 = GetVertexPos(pbox.boxIndex4);
a1 = ((p3 + p4) - (p1 + p2)) * 0.5f;
a2 = p3 - (p1 + a1);
a3 = p4 - (p1 + a1);
Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length());
Vector3 m1 = a1 / bs.X;
Vector3 m2 = a2 / bs.Y;
Vector3 m3 = a3 / bs.Z;
if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3);
else if (bs.Y < bs.Z) m2 = Vector3.Cross(m3, m1);
else m3 = Vector3.Cross(m1, m2);
2019-12-31 21:00:04 +08:00
Vector3 tp = rp - (p1);//+cg
rayt.Position = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3));
rayt.Direction = new Vector3(Vector3.Dot(rd, m1), Vector3.Dot(rd, m2), Vector3.Dot(rd, m3));
box.Minimum = Vector3.Zero;
box.Maximum = bs;
polyhit = rayt.Intersects(ref box, out polyhittestdist);
if (polyhit)
{
Vector3 hpt = rayt.Position + rayt.Direction * polyhittestdist;
const float eps = 0.002f;
if (Math.Abs(hpt.X) < eps) n1 = -m1;
else if (Math.Abs(hpt.X - bs.X) < eps) n1 = m1;
else if (Math.Abs(hpt.Y) < eps) n1 = -m2;
else if (Math.Abs(hpt.Y - bs.Y) < eps) n1 = m2;
else if (Math.Abs(hpt.Z) < eps) n1 = -m3;
else if (Math.Abs(hpt.Z - bs.Z) < eps) n1 = m3;
else
{ n1 = Vector3.UnitZ; } //ray starts inside the box...
}
break;
case BoundPolygonType.Cylinder:
var pcyl = polygon as BoundPolygonCylinder;
var tcyl = new BoundingCylinder();
2019-12-31 21:00:04 +08:00
tcyl.PointA = GetVertexPos(pcyl.cylinderIndex1);
tcyl.PointB = GetVertexPos(pcyl.cylinderIndex2);
tcyl.Radius = pcyl.cylinderRadius;
polyhit = ray.Intersects(in tcyl, out polyhittestdist, out n1);
break;
default:
break;
}
if (polyhit && (polyhittestdist < res.HitDist))
{
res.HitDist = polyhittestdist;
res.Hit = true;
2020-01-07 17:51:53 +08:00
res.Position = (ray.Position + ray.Direction * polyhittestdist);
res.Normal = n1;
2020-01-07 17:51:53 +08:00
res.HitVertex = (polygon != null) ? polygon.NearestVertex(res.Position) : new BoundVertexRef(-1, float.MaxValue);
res.HitPolygon = polygon;
res.HitBounds = this;
2020-01-06 19:17:51 +08:00
res.Material = polygon.Material;
}
res.TestedPolyCount++;
}
}
2020-01-06 19:17:51 +08:00
2020-01-16 15:03:24 +08:00
public int GetMaterialIndex(int polyIndex)
2020-01-06 19:17:51 +08:00
{
var matind = 0;
if ((PolygonMaterialIndices != null) && (polyIndex < PolygonMaterialIndices.Length))
{
matind = PolygonMaterialIndices[polyIndex];
}
2020-01-16 15:03:24 +08:00
return matind;
}
public BoundMaterial_s GetMaterialByIndex(int matIndex)
{
if ((Materials != null) && (matIndex < Materials.Length))
2020-01-06 19:17:51 +08:00
{
2020-01-16 15:03:24 +08:00
return Materials[matIndex];
2020-01-06 19:17:51 +08:00
}
return new BoundMaterial_s();
}
2020-01-16 15:03:24 +08:00
public BoundMaterial_s GetMaterial(int polyIndex)
{
var matind = GetMaterialIndex(polyIndex);
return GetMaterialByIndex(matind);
}
2020-01-06 19:17:51 +08:00
public void SetMaterial(int polyIndex, BoundMaterial_s mat)
{
//updates the shared material for the given poly.
var matind = 0;
if ((PolygonMaterialIndices != null) && (polyIndex < PolygonMaterialIndices.Length))
{
matind = PolygonMaterialIndices[polyIndex];
}
if ((Materials != null) && (matind < Materials.Length))
{
Materials[matind] = mat;
}
}
2023-07-28 05:48:41 +08:00
public void CalculateOctants()
{
Octants = new BoundGeomOctants();
Vector3[] flipDirection = new Vector3[8]
{
new Vector3(1.0f, 1.0f, 1.0f),
new Vector3(-1.0f, 1.0f, 1.0f),
new Vector3(1.0f, -1.0f, 1.0f),
new Vector3(-1.0f, -1.0f, 1.0f),
new Vector3(1.0f, 1.0f, -1.0f),
new Vector3(-1.0f, 1.0f, -1.0f),
new Vector3(1.0f, -1.0f, -1.0f),
new Vector3(-1.0f, -1.0f, -1.0f)
};
bool isShadowed(Vector3 v1, Vector3 v2, int octant)
{
Vector3 direction = v2 - v1;
Vector3 flip = flipDirection[octant];
direction *= flip;
return direction.X >= 0.0 && direction.Y >= 0.0 && direction.Z >= 0.0;
}
uint[] getVerticesInOctant(int octant)
{
List<uint> octantIndices = new List<uint>();
2023-07-28 07:15:42 +08:00
for (uint ind1 = 0; ind1 < VerticesShrunk.Length; ind1++)
2023-07-28 05:48:41 +08:00
{
2023-07-28 07:15:42 +08:00
Vector3 vertex = VerticesShrunk[ind1];
2023-07-28 05:48:41 +08:00
bool shouldAdd = true;
List<uint> octantIndices2 = new List<uint>();
foreach (uint ind2 in octantIndices)
{
2023-07-28 07:15:42 +08:00
Vector3 vertex2 = VerticesShrunk[ind2];
2023-07-28 05:48:41 +08:00
if (isShadowed(vertex, vertex2, octant))
{
shouldAdd = false;
octantIndices2 = octantIndices;
break;
}
if (!isShadowed(vertex2, vertex, octant))
{
octantIndices2.Add(ind2);
}
}
if (shouldAdd)
{
octantIndices2.Add(ind1);
}
octantIndices = octantIndices2;
}
return octantIndices.ToArray();
}
for (int i = 0; i < 8; i++)
{
Octants.Items[i] = getVerticesInOctant(i);
Octants.UpdateCounts();
}
}
2020-01-06 19:17:51 +08:00
public void CalculateVertsShrunkByMargin()
{
Vector3[] vertNormals = CalculateVertNormals();
2023-07-28 07:15:42 +08:00
VerticesShrunk = new Vector3[Vertices.Length];
for (int i = 0; i < Vertices.Length; i++)
{
Vector3 normalShrunk = vertNormals[i] * -Margin;
2023-07-28 07:15:42 +08:00
VerticesShrunk[i] = Vertices[i] + normalShrunk;
}
}
public Vector3[] CalculateVertNormals()
{
Vector3[] vertNormals = new Vector3[Vertices.Length];
for (int i = 0; i < Polygons.Length; i++)
{
if (Polygons[i] is not BoundPolygonTriangle tri) { continue; }
var p1 = tri.Vertex1;
var p2 = tri.Vertex2;
var p3 = tri.Vertex3;
var p1Local = p1 - p2;
var p3Local = p3 - p2;
var normal = Vector3.Cross(p1Local, p3Local);
normal.Normalize();
vertNormals[tri.vertIndex1] += normal;
vertNormals[tri.vertIndex2] += normal;
vertNormals[tri.vertIndex3] += normal;
}
for (int i = 0; i < vertNormals.Length; i++)
{
if (vertNormals[i].IsZero) { continue; }
vertNormals[i].Normalize();
}
return vertNormals;
}
2020-01-06 19:17:51 +08:00
public void CalculateQuantum()
{
var min = new Vector3(float.MaxValue);
var max = new Vector3(float.MinValue);
if (Vertices != null)
{
foreach (var v in Vertices)
{
min = Vector3.Min(min, v);
max = Vector3.Max(max, v);
}
}
2023-07-28 07:15:42 +08:00
if (VerticesShrunk != null)
2020-01-06 19:17:51 +08:00
{
2023-07-28 07:15:42 +08:00
foreach (var v in VerticesShrunk)
2020-01-06 19:17:51 +08:00
{
min = Vector3.Min(min, v);
max = Vector3.Max(max, v);
}
}
var maxsiz = Vector3.Max(min.Abs(), max.Abs());
var q = (maxsiz+Margin) / 32767.0f;
Quantum = q;
}
public void BuildMaterials()
{
//update Materials and PolygonMaterialIndices arrays, using custom materials from polys and existing materials
var matdict = new Dictionary<BoundMaterial_s, byte>();
var matlist = new List<BoundMaterial_s>();
var polymats = new List<byte>();
if (Polygons != null)
{
foreach (var poly in Polygons)
{
var mat = poly.Material;
if (matdict.TryGetValue(mat, out byte matidx))
{
polymats.Add(matidx);
}
else
{
matidx = (byte)matlist.Count;
matdict.Add(mat, matidx);
matlist.Add(mat);
polymats.Add(matidx);
}
}
}
MaterialsCount = (byte)matlist.Count;
//add padding to the array for writing
for (int i = matlist.Count; i < 4; i++)
{
matlist.Add(new BoundMaterial_s());
}
2020-01-06 19:17:51 +08:00
Materials = matlist.ToArray();
PolygonMaterialIndices = polymats.ToArray();
}
2020-01-11 16:20:14 +08:00
public void UpdateEdgeIndices()
{
//update all triangle edge indices, based on shared vertex indices
2020-01-12 11:25:27 +08:00
if (Polygons == null)
{ return; }
2020-01-23 15:36:34 +08:00
for (int i = 0; i < Polygons.Length; i++)
{
var poly = Polygons[i];
if (poly != null)
{
poly.Index = i;
}
}
2020-01-11 16:20:14 +08:00
var edgedict = new Dictionary<BoundEdgeRef, BoundEdge>();
2020-01-12 11:25:27 +08:00
foreach (var poly in Polygons)
2020-01-11 16:20:14 +08:00
{
2020-01-12 11:25:27 +08:00
if (poly is BoundPolygonTriangle btri)
2020-01-11 16:20:14 +08:00
{
2020-01-12 11:25:27 +08:00
var e1 = new BoundEdgeRef(btri.vertIndex1, btri.vertIndex2);
var e2 = new BoundEdgeRef(btri.vertIndex2, btri.vertIndex3);
var e3 = new BoundEdgeRef(btri.vertIndex3, btri.vertIndex1);
2020-01-11 16:20:14 +08:00
2020-01-12 11:25:27 +08:00
if (edgedict.TryGetValue(e1, out BoundEdge edge1))
{
if (edge1.Triangle2 != null)
{
btri.SetEdgeIndex(1, (short)edge1.Triangle1.Index);
}
else
2020-01-11 16:20:14 +08:00
{
edge1.Triangle2 = btri;
edge1.EdgeID2 = 1;
}
2020-01-12 11:25:27 +08:00
}
else
{
edgedict[e1] = new BoundEdge(btri, 1);
}
if (edgedict.TryGetValue(e2, out BoundEdge edge2))
{
if (edge2.Triangle2 != null)
2020-01-11 16:20:14 +08:00
{
2020-01-12 11:25:27 +08:00
btri.SetEdgeIndex(2, (short)edge2.Triangle1.Index);
2020-01-11 16:20:14 +08:00
}
2020-01-12 11:25:27 +08:00
else
2020-01-11 16:20:14 +08:00
{
edge2.Triangle2 = btri;
edge2.EdgeID2 = 2;
}
2020-01-12 11:25:27 +08:00
}
else
{
edgedict[e2] = new BoundEdge(btri, 2);
}
if (edgedict.TryGetValue(e3, out BoundEdge edge3))
{
if (edge3.Triangle2 != null)
2020-01-11 16:20:14 +08:00
{
2020-01-12 11:25:27 +08:00
btri.SetEdgeIndex(3, (short)edge3.Triangle1.Index);
2020-01-11 16:20:14 +08:00
}
2020-01-12 11:25:27 +08:00
else
2020-01-11 16:20:14 +08:00
{
edge3.Triangle2 = btri;
edge3.EdgeID2 = 3;
}
}
2020-01-12 11:25:27 +08:00
else
{
edgedict[e3] = new BoundEdge(btri, 3);
}
2020-01-11 16:20:14 +08:00
}
2020-01-12 11:25:27 +08:00
}
2020-01-11 16:20:14 +08:00
2020-01-12 11:25:27 +08:00
foreach (var kvp in edgedict)
{
var eref = kvp.Key;
var edge = kvp.Value;
2020-01-11 16:20:14 +08:00
2020-01-12 11:25:27 +08:00
if (edge.Triangle1 == null)
{ continue; }
2020-01-11 16:20:14 +08:00
2020-01-12 11:25:27 +08:00
if (edge.Triangle2 == null)
{
edge.Triangle1.SetEdgeIndex(edge.EdgeID1, -1);
}
else
{
edge.Triangle1.SetEdgeIndex(edge.EdgeID1, (short)edge.Triangle2.Index);
edge.Triangle2.SetEdgeIndex(edge.EdgeID2, (short)edge.Triangle1.Index);
2020-01-11 16:20:14 +08:00
}
2020-01-12 11:25:27 +08:00
}
2020-01-11 16:20:14 +08:00
2020-01-12 11:25:27 +08:00
foreach (var poly in Polygons)
{
if (poly is BoundPolygonTriangle btri)
{
if (btri.edgeIndex1 >= Polygons.Length)
{ } //just checking....
if (btri.edgeIndex2 >= Polygons.Length)
{ }
if (btri.edgeIndex3 >= Polygons.Length)
{ }
}
2020-01-11 16:20:14 +08:00
}
}
2020-01-13 20:31:27 +08:00
public void UpdateTriangleAreas()
{
//update all triangle areas, based on vertex positions
if (Polygons == null)
{ return; }
var edgedict = new Dictionary<BoundEdgeRef, BoundEdge>();
foreach (var poly in Polygons)
{
if (poly is BoundPolygonTriangle btri)
{
var v1 = btri.Vertex1;
var v2 = btri.Vertex2;
var v3 = btri.Vertex3;
var area = TriangleMath.Area(in v1, in v2, in v3);
2020-01-13 20:31:27 +08:00
//if (Math.Abs(btri.triArea - area) > Math.Max(area*0.1f,0.1f))
//{ }//ehh good enough
btri.triArea = area;
}
}
}
2020-01-11 16:20:14 +08:00
public bool DeletePolygon(BoundPolygon p)
{
if (Polygons != null)
{
var polys = Polygons.ToList();
var polymats = PolygonMaterialIndices.ToList();
var idx = polys.IndexOf(p);
if (idx >= 0)
{
polys.RemoveAt(idx);
polymats.RemoveAt(idx);
Polygons = polys.ToArray();
PolygonMaterialIndices = polymats.ToArray();
PolygonsCount = (uint)polys.Count;
for (int i = 0; i < Polygons.Length; i++)
{
var poly = Polygons[i];
if (poly is BoundPolygonTriangle btri)
{
if (btri.edgeIndex1 == idx) btri.edgeIndex1 = -1;
if (btri.edgeIndex1 > idx) btri.edgeIndex1--;
if (btri.edgeIndex2 == idx) btri.edgeIndex2 = -1;
if (btri.edgeIndex2 > idx) btri.edgeIndex2--;
if (btri.edgeIndex3 == idx) btri.edgeIndex3 = -1;
if (btri.edgeIndex3 > idx) btri.edgeIndex3--;
}
poly.Index = i;
}
var verts = p.VertexIndices;
for (int i = 0; i < verts.Length; i++)
{
if (DeleteVertex(verts[i], false)) //delete any orphaned vertices
{
verts = p.VertexIndices;//vertex indices will have changed, need to continue with the new ones!
}
}
return true;
}
}
return false;
}
public bool DeleteVertex(int index, bool deletePolys = true)
{
if (Vertices != null)
{
if (!deletePolys)
{
//if not deleting polys, make sure this vertex isn't used by any
foreach (var poly in Polygons)
{
var pverts = poly.VertexIndices;
for (int i = 0; i < pverts.Length; i++)
{
if (pverts[i] == index)
{
return false;
}
}
}
}
var verts = Vertices.ToList();
2023-07-28 07:15:42 +08:00
var verts2 = VerticesShrunk?.ToList();
2020-01-11 16:20:14 +08:00
var vertcols = VertexColours?.ToList();
2020-01-12 19:53:57 +08:00
var vertobjs = VertexObjects?.ToList();
2020-01-11 16:20:14 +08:00
verts.RemoveAt(index);
verts2?.RemoveAt(index);
vertcols?.RemoveAt(index);
2020-01-12 19:53:57 +08:00
vertobjs?.RemoveAt(index);
2020-01-11 16:20:14 +08:00
Vertices = verts.ToArray();
2023-07-28 07:15:42 +08:00
VerticesShrunk = verts2?.ToArray();
2020-01-11 16:20:14 +08:00
VertexColours = vertcols?.ToArray();
2020-01-12 19:53:57 +08:00
VertexObjects = vertobjs?.ToArray();
2020-01-11 16:20:14 +08:00
VerticesCount = (uint)verts.Count;
2023-07-28 07:15:42 +08:00
VerticesShrunkCount = VerticesCount;
2020-01-11 16:20:14 +08:00
2020-01-12 19:53:57 +08:00
if (VertexObjects != null)
{
for (int i = 0; i < VertexObjects.Length; i++)
{
if (VertexObjects[i] != null) VertexObjects[i].Index = i;
}
}
2020-01-11 16:20:14 +08:00
if (Polygons != null)
{
var delpolys = new List<BoundPolygon>();
foreach (var poly in Polygons)
{
var pverts = poly.VertexIndices;
for (int i = 0; i < pverts.Length; i++)
{
if (pverts[i] == index)
{
delpolys.Add(poly);
}
if (pverts[i] > index)
{
pverts[i]--;
}
}
poly.VertexIndices = pverts;
}
if (deletePolys)
{
foreach (var delpoly in delpolys)
{
DeletePolygon(delpoly);
}
}
else
{
if (delpolys.Count > 0)
{ } //this shouldn't happen! shouldn't have deleted the vertex if it is used by polys
}
}
return true;
}
return false;
}
2020-01-12 19:53:57 +08:00
public int AddVertex()
{
using var verts = Vertices?.ToPooledList() ?? new PooledList<Vector3>();
using var verts2 = VerticesShrunk?.ToPooledList();
using var vertcols = VertexColours?.ToPooledList();
using var vertobjs = VertexObjects?.ToPooledList();
2020-01-12 19:53:57 +08:00
var index = verts.Count;
verts.Add(Vector3.Zero);
verts2?.Add(Vector3.Zero);
vertcols?.Add(new BoundMaterialColour());
vertobjs?.Add(null);
Vertices = verts.ToArray();
2023-07-28 07:15:42 +08:00
VerticesShrunk = verts2?.ToArray();
2020-01-12 19:53:57 +08:00
VertexColours = vertcols?.ToArray();
VertexObjects = vertobjs?.ToArray();
VerticesCount = (uint)verts.Count;
2023-07-28 07:15:42 +08:00
VerticesShrunkCount = VerticesCount;
2020-01-12 19:53:57 +08:00
return index;
}
public BoundPolygon AddPolygon(BoundPolygonType type)
{
var p = CreatePolygon(type);
var polys = Polygons?.ToList() ?? new List<BoundPolygon>();
var polymats = PolygonMaterialIndices?.ToList() ?? new List<byte>();
p.Index = polys.Count;
polys.Add(p);
polymats.Add(0);
Polygons = polys.ToArray();
PolygonMaterialIndices = polymats.ToArray();
PolygonsCount = (uint)polys.Count;
var vinds = p.VertexIndices; //just get the required array size
if (vinds != null)
{
for (int i = 0; i < vinds.Length; i++)
{
vinds[i] = AddVertex();
}
p.VertexIndices = vinds;
}
return p;
}
private BoundPolygon? CreatePolygon(BoundPolygonType type)
2020-01-12 19:53:57 +08:00
{
BoundPolygon? p = type switch
2020-01-12 19:53:57 +08:00
{
BoundPolygonType.Triangle => new BoundPolygonTriangle(),
BoundPolygonType.Sphere => new BoundPolygonSphere(),
BoundPolygonType.Capsule => new BoundPolygonCapsule(),
BoundPolygonType.Box => new BoundPolygonBox(),
BoundPolygonType.Cylinder => new BoundPolygonCylinder(),
_ => null,
};
if (p is not null)
2020-01-12 19:53:57 +08:00
{
p.Owner = this;
}
return p;
}
}
2020-01-03 19:50:23 +08:00
[TC(typeof(EXP))] public class BoundBVH : BoundGeometry
{
public override long BlockLength
{
2020-01-03 19:50:23 +08:00
get { return 336; }
}
2020-01-03 19:50:23 +08:00
// structure data
public ulong BvhPointer { get; set; }
public uint Unknown_138h { get; set; } // 0x00000000
public uint Unknown_13Ch { get; set; } // 0x00000000
public ushort Unknown_140h { get; set; } = 0xFFFF; // 0xFFFF
public ushort Unknown_142h { get; set; } // 0x0000
public uint Unknown_144h { get; set; } // 0x00000000
public uint Unknown_148h { get; set; } // 0x00000000
public uint Unknown_14Ch { get; set; } // 0x00000000
2020-01-03 19:50:23 +08:00
// reference data
public BVH? BVH { get; set; }
2020-01-03 19:50:23 +08:00
public override void Read(ResourceDataReader reader, params object[] parameters)
{
2020-01-03 19:50:23 +08:00
base.Read(reader, parameters);
2020-01-03 19:50:23 +08:00
// read structure data
this.BvhPointer = reader.ReadUInt64();
this.Unknown_138h = reader.ReadUInt32();
this.Unknown_13Ch = reader.ReadUInt32();
this.Unknown_140h = reader.ReadUInt16();
this.Unknown_142h = reader.ReadUInt16();
this.Unknown_144h = reader.ReadUInt32();
this.Unknown_148h = reader.ReadUInt32();
this.Unknown_14Ch = reader.ReadUInt32();
2020-01-03 19:50:23 +08:00
// read reference data
if (this.BvhPointer > 65535)
{
2020-01-03 19:50:23 +08:00
this.BVH = reader.ReadBlockAt<BVH>(
this.BvhPointer // offset
);
2020-03-13 01:21:30 +08:00
//var cap = BVH.Nodes.EntriesCount;//how to calc this?
//var diff = BVH.Nodes.EntriesCapacity - cap;
//switch (diff)
//{
// case 0:
// case 1:
// case 2:
// break;
// default:
// break;//no hit
//}
2020-01-03 19:50:23 +08:00
}
else
{
//this can happen in some ydr's for some reason
}
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
2020-01-03 19:50:23 +08:00
base.Write(writer, parameters);
2020-01-03 19:50:23 +08:00
// update structure data
this.BvhPointer = (ulong)(this.BVH != null ? this.BVH.FilePosition : 0);
// write structure data
writer.Write(this.BvhPointer);
writer.Write(this.Unknown_138h);
writer.Write(this.Unknown_13Ch);
writer.Write(this.Unknown_140h);
writer.Write(this.Unknown_142h);
writer.Write(this.Unknown_144h);
writer.Write(this.Unknown_148h);
writer.Write(this.Unknown_14Ch);
}
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
FileVFT = 1080228536;
}
public override IResourceBlock[] GetReferences()
{
BuildBVH(false);
2020-01-06 19:17:51 +08:00
if (BVH is null)
return base.GetReferences();
using var list = new PooledList<IResourceBlock>(base.GetReferences());
if (BVH != null)
list.Add(BVH);
2017-09-21 18:33:05 +08:00
return list.ToArray();
}
public void BuildBVH(bool updateParent = true)
{
2020-01-06 19:17:51 +08:00
if ((Polygons?.Length ?? 0) <= 0) //in some des_ drawables?
{
BVH = null;
return;
}
if (BVH != null)
{
//var tnodes = BVHBuilder.Unbuild(BVH);
}
var items = new List<BVHBuilderItem>(Polygons.Length);
2020-01-06 19:17:51 +08:00
for (int i = 0; i < Polygons.Length; i++)
{
var poly = Polygons[i];
if (poly != null)
{
var it = new BVHBuilderItem
{
Min = poly.BoxMin,
Max = poly.BoxMax,
Index = i,
Polygon = poly
};
2020-01-06 19:17:51 +08:00
items.Add(it);
}
}
var bvh = BVHBuilder.Build(items, 4); //geometries have BVH item threshold of 4
var newpolys = new BoundPolygon[items.Count];
var newpolymats = new byte[items.Count];
var itemlookup = new short[items.Count];
for (int i = 0; i < items.Count; i++)
{
var poly = items[i]?.Polygon;
if (poly != null)
{
if (poly.Index < itemlookup.Length)
{
itemlookup[poly.Index] = (short)i;
}
else
{ }//shouldn't happen
}
else
{ }//shouldn't happen
}
for (int i = 0; i < items.Count; i++)
{
var poly = items[i]?.Polygon;
if (poly != null)
{
newpolys[i] = poly;
newpolymats[i] = (poly.Index < PolygonMaterialIndices.Length) ? PolygonMaterialIndices[poly.Index] : (byte)0;//is this necessary?
poly.Index = i;
if (poly is BoundPolygonTriangle ptri)
{
ptri.edgeIndex1 = ((ptri.edgeIndex1 >= 0) && (ptri.edgeIndex1 < itemlookup.Length)) ? itemlookup[ptri.edgeIndex1] : (short)-1;
ptri.edgeIndex2 = ((ptri.edgeIndex2 >= 0) && (ptri.edgeIndex2 < itemlookup.Length)) ? itemlookup[ptri.edgeIndex2] : (short)-1;
ptri.edgeIndex3 = ((ptri.edgeIndex3 >= 0) && (ptri.edgeIndex3 < itemlookup.Length)) ? itemlookup[ptri.edgeIndex3] : (short)-1;
}
}
}
Polygons = newpolys;
PolygonMaterialIndices = newpolymats;
2020-01-13 13:25:52 +08:00
BoxMin = bvh.BoundingBoxMin.XYZ();
BoxMax = bvh.BoundingBoxMax.XYZ();
BoxCenter = bvh.BoundingBoxCenter.XYZ();
SphereCenter = BoxCenter;
SphereRadius = (BoxMax - BoxCenter).Length();
2020-01-06 19:17:51 +08:00
BVH = bvh;
if (updateParent && (Parent != null)) //only update parent when live editing in world view!
2020-01-13 13:25:52 +08:00
{
Parent.BuildBVH();
}
}
2020-01-03 19:50:23 +08:00
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
2020-01-03 19:50:23 +08:00
var res = new SpaceSphereIntersectResult();
var box = new BoundingBox();
2020-01-03 19:50:23 +08:00
if (Polygons == null)
{ return res; }
if ((BVH?.Nodes?.data_items == null) || (BVH?.Trees?.data_items == null))
{ return res; }
2020-01-03 19:50:23 +08:00
box.Minimum = BoxMin;
box.Maximum = BoxMax;
if (!sph.Intersects(ref box))
{ return res; }
2020-01-03 19:50:23 +08:00
var q = BVH.Quantum.XYZ();
var c = BVH.BoundingBoxCenter.XYZ();
for (int t = 0; t < BVH.Trees.data_items.Length; t++)
{
var tree = BVH.Trees.data_items[t];
2020-01-06 19:17:51 +08:00
box.Minimum = tree.Min * q + c;
box.Maximum = tree.Max * q + c;
2020-01-03 19:50:23 +08:00
if (!sph.Intersects(ref box))
{ continue; }
int nodeind = tree.NodeIndex1;
int lastind = tree.NodeIndex2;
while (nodeind < lastind)
{
var node = BVH.Nodes.data_items[nodeind];
2020-01-06 19:17:51 +08:00
box.Minimum = node.Min * q + c;
box.Maximum = node.Max * q + c;
2020-01-03 19:50:23 +08:00
bool nodehit = sph.Intersects(ref box);
bool nodeskip = !nodehit;
if (node.ItemCount <= 0) //intermediate node with child nodes
{
if (nodeskip)
{
nodeind += node.ItemId; //(child node count)
}
else
{
nodeind++;
}
}
else //leaf node, with polygons
{
if (!nodeskip)
{
var lastp = Math.Min(node.ItemId + node.ItemCount, (int)PolygonsCount);
SphereIntersectPolygons(ref sph, ref res, node.ItemId, lastp);
}
nodeind++;
}
res.TestedNodeCount++;
}
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var box = new BoundingBox();
if (Polygons == null)
{ return res; }
if ((BVH?.Nodes?.data_items == null) || (BVH?.Trees?.data_items == null))
{ return res; }
box.Minimum = BoxMin;
box.Maximum = BoxMax;
float bvhboxhittest;
if (!ray.Intersects(ref box, out bvhboxhittest))
{ return res; }
if (bvhboxhittest > maxdist)
{ return res; } //already a closer hit.
res.HitDist = maxdist;
var q = BVH.Quantum.XYZ();
var c = BVH.BoundingBoxCenter.XYZ();
for (int t = 0; t < BVH.Trees.data_items.Length; t++)
{
var tree = BVH.Trees.data_items[t];
2020-01-06 19:17:51 +08:00
box.Minimum = tree.Min * q + c;
box.Maximum = tree.Max * q + c;
2020-01-03 19:50:23 +08:00
if (!ray.Intersects(ref box, out bvhboxhittest))
{ continue; }
if (bvhboxhittest > res.HitDist)
{ continue; } //already a closer hit.
int nodeind = tree.NodeIndex1;
int lastind = tree.NodeIndex2;
while (nodeind < lastind)
{
var node = BVH.Nodes.data_items[nodeind];
2020-01-06 19:17:51 +08:00
box.Minimum = node.Min * q + c;
box.Maximum = node.Max * q + c;
2020-01-03 19:50:23 +08:00
bool nodehit = ray.Intersects(ref box, out bvhboxhittest);
bool nodeskip = !nodehit || (bvhboxhittest > res.HitDist);
if (node.ItemCount <= 0) //intermediate node with child nodes
{
if (nodeskip)
{
nodeind += node.ItemId; //(child node count)
}
else
{
nodeind++;
}
}
else //leaf node, with polygons
{
if (!nodeskip)
{
var lastp = Math.Min(node.ItemId + node.ItemCount, (int)PolygonsCount);
RayIntersectPolygons(ref ray, ref res, node.ItemId, lastp);
}
nodeind++;
}
res.TestedNodeCount++;
}
}
return res;
}
}
2020-01-03 19:50:23 +08:00
[TC(typeof(EXP))] public class BoundComposite : Bounds
{
public override long BlockLength
{
get { return 176; }
}
// structure data
public ulong ChildrenPointer { get; set; }
public ulong ChildrenTransformation1Pointer { get; set; }
public ulong ChildrenTransformation2Pointer { get; set; }
public ulong ChildrenBoundingBoxesPointer { get; set; }
public ulong ChildrenFlags1Pointer { get; set; }
public ulong ChildrenFlags2Pointer { get; set; }
public ushort ChildrenCount1 { get; set; }
public ushort ChildrenCount2 { get; set; }
public uint Unknown_A4h { get; set; } // 0x00000000
public ulong BVHPointer { get; set; }
// reference data
public ResourcePointerArray64<Bounds> Children { get; set; }
public Matrix4F_s[]? ChildrenTransformation1 { get; set; }
public Matrix4F_s[]? ChildrenTransformation2 { get; set; }
public AABB_s[]? ChildrenBoundingBoxes { get; set; }
public BoundCompositeChildrenFlags[] ChildrenFlags1 { get; set; } = [];
public BoundCompositeChildrenFlags[] ChildrenFlags2 { get; set; } = [];
2020-01-03 19:50:23 +08:00
public BVH? BVH { get; set; }
2020-01-03 19:50:23 +08:00
private ResourceSystemStructBlock<Matrix4F_s>? ChildrenTransformation1Block = null;
private ResourceSystemStructBlock<Matrix4F_s>? ChildrenTransformation2Block = null;
private ResourceSystemStructBlock<AABB_s>? ChildrenBoundingBoxesBlock = null;
private ResourceSystemStructBlock<BoundCompositeChildrenFlags>? ChildrenFlags1Block = null;
private ResourceSystemStructBlock<BoundCompositeChildrenFlags>? ChildrenFlags2Block = null;
2020-01-03 19:50:23 +08:00
public override void Read(ResourceDataReader reader, params object[] parameters)
{
base.Read(reader, parameters);
// read structure data
this.ChildrenPointer = reader.ReadUInt64();
this.ChildrenTransformation1Pointer = reader.ReadUInt64();
this.ChildrenTransformation2Pointer = reader.ReadUInt64();
this.ChildrenBoundingBoxesPointer = reader.ReadUInt64();
this.ChildrenFlags1Pointer = reader.ReadUInt64();
this.ChildrenFlags2Pointer = reader.ReadUInt64();
this.ChildrenCount1 = reader.ReadUInt16();
this.ChildrenCount2 = reader.ReadUInt16();
this.Unknown_A4h = reader.ReadUInt32();
this.BVHPointer = reader.ReadUInt64();
// read reference data
2020-03-13 01:21:30 +08:00
this.Children = reader.ReadBlockAt<ResourcePointerArray64<Bounds>>(this.ChildrenPointer, this.ChildrenCount1);
2020-01-23 15:36:34 +08:00
this.ChildrenTransformation1 = reader.ReadStructsAt<Matrix4F_s>(this.ChildrenTransformation1Pointer, this.ChildrenCount1);
this.ChildrenTransformation2 = reader.ReadStructsAt<Matrix4F_s>(this.ChildrenTransformation2Pointer, this.ChildrenCount1);
2020-01-03 19:50:23 +08:00
this.ChildrenBoundingBoxes = reader.ReadStructsAt<AABB_s>(this.ChildrenBoundingBoxesPointer, this.ChildrenCount1);
this.ChildrenFlags1 = reader.ReadStructsAt<BoundCompositeChildrenFlags>(this.ChildrenFlags1Pointer, this.ChildrenCount1) ?? [];
this.ChildrenFlags2 = reader.ReadStructsAt<BoundCompositeChildrenFlags>(this.ChildrenFlags2Pointer, this.ChildrenCount1) ?? [];
2020-03-13 01:21:30 +08:00
this.BVH = reader.ReadBlockAt<BVH>(this.BVHPointer);
2020-01-03 19:50:23 +08:00
2020-03-13 01:21:30 +08:00
//if (BVH != null)
//{
// var cap = Math.Max(BVH.Nodes.EntriesCount + 2, ChildrenCount1 * 2 + 1);
// var diff = BVH.Nodes.EntriesCapacity - cap;
// if (diff != 0)
// { }//no hit
//}
2020-01-03 19:50:23 +08:00
var childTransforms = ChildrenTransformation1 ?? ChildrenTransformation2;
if ((Children != null) && (Children.data_items != null))
{
for (int i = 0; i < Children.data_items.Length; i++)
{
var child = Children.data_items[i];
if (child != null)
{
child.Parent = this;
2020-01-23 15:36:34 +08:00
var xform = ((childTransforms != null) && (i < childTransforms.Length)) ? childTransforms[i].ToMatrix() : Matrix.Identity;
2020-01-03 19:50:23 +08:00
child.Transform = xform;
child.TransformInv = Matrix.Invert(xform);
child.CompositeFlags1 = i < ChildrenFlags1.Length ? ChildrenFlags1[i] : new BoundCompositeChildrenFlags();
child.CompositeFlags2 = i < ChildrenFlags2.Length ? ChildrenFlags2[i] : new BoundCompositeChildrenFlags();
2020-01-13 20:31:27 +08:00
//if ((child.CompositeFlags1.Flags1 != child.CompositeFlags2.Flags1) || (child.CompositeFlags1.Flags2 != child.CompositeFlags2.Flags2))
//{ } //no hits
2020-01-03 19:50:23 +08:00
}
}
}
2020-01-06 19:17:51 +08:00
2020-01-23 15:36:34 +08:00
//if (ChildrenTransformation1 != null)
//{
// foreach (var m in ChildrenTransformation1)
// {
// switch (m.Flags1)
// {
// case 0:
// case 0x7f800001: //only in yft's!
// case 0x80000000: //only in yft's! = -0
// break;
// default:
// break;//no hit
// }
// switch (m.Flags2)
// {
// case 1:
// case 0: //only in yft's!
// case 0x7f800001: //only in yft's!
// case 0x80000000: //only in yft's! = -0
// break;
// default:
// break;//no hit
// }
// switch (m.Flags3)
// {
// case 1:
// case 0: //only in yft's!
// case 0x7f800001: //only in yft's!
// case 0x80000000: //only in yft's! = -0
// break;
// default:
// break;//no hit
// }
// switch (m.Flags4)
// {
// case 0:
// case 0x7f800001: //only in yft's!
// case 0x42f9c6a4: //only in yft! = 124.888f
// case 0x42f94c3a: //only in yft! = 124.649f
// case 0x42f33400: //only in yft! = 121.602f
// case 0x42f4793c: //only in yft! = 122.237f
// case 0x42f2d6a2: //only in yft! = 121.419f
// case 0x42eca103:
// case 0x42ede882:
// case 0x42e6ec74:
// case 0x42e26655:
// case 0x42e2ac9e:
// case 0x42eb1c90:
// case 0x4474bd4d:
// case 0x433b2027:
// break;
// default:
// break;//+ more! in yft's! it really seems like a float...
// }
// }
//}
2020-01-06 19:17:51 +08:00
//if ((ChildrenTransformation1 != null) && (ChildrenTransformation2 != null))
//{
// //if (ChildrenTransformation1.Length != ChildrenTransformation2.Length)
// //{ }//no hits
// //else
// //{
// // for (int i = 0; i < ChildrenTransformation1.Length; i++)
// // {
// // if (ChildrenTransformation1[i] != ChildrenTransformation2[i])
// // { }//no hits
// // }
// //}
//}
//else
//{ }//no hits
//if (ChildrenFlags1 != null)
//{ }
//else
//{ }//some props ydr's
//if (ChildrenFlags2 != null)
//{ }
//else
//{ }//some props ydr's
2020-01-03 19:50:23 +08:00
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
base.Write(writer, parameters);
// update structure data
this.ChildrenPointer = (ulong)(this.Children != null ? this.Children.FilePosition : 0);
this.ChildrenTransformation1Pointer = (ulong)(this.ChildrenTransformation1Block != null ? this.ChildrenTransformation1Block.FilePosition : 0);
this.ChildrenTransformation2Pointer = (ulong)(this.ChildrenTransformation2Block != null ? this.ChildrenTransformation2Block.FilePosition : (long)ChildrenTransformation1Pointer);
2020-01-03 19:50:23 +08:00
this.ChildrenBoundingBoxesPointer = (ulong)(this.ChildrenBoundingBoxesBlock != null ? this.ChildrenBoundingBoxesBlock.FilePosition : 0);
this.ChildrenFlags1Pointer = (ulong)(this.ChildrenFlags1Block != null ? this.ChildrenFlags1Block.FilePosition : 0);
this.ChildrenFlags2Pointer = (ulong)(this.ChildrenFlags2Block != null ? this.ChildrenFlags2Block.FilePosition : 0);
this.ChildrenCount1 = (ushort)(this.Children != null ? this.Children.Count : 0);
this.ChildrenCount2 = (ushort)(this.Children != null ? this.Children.Count : 0);
this.BVHPointer = (ulong)(this.BVH != null ? this.BVH.FilePosition : 0);
// write structure data
writer.Write(this.ChildrenPointer);
writer.Write(this.ChildrenTransformation1Pointer);
writer.Write(this.ChildrenTransformation2Pointer);
writer.Write(this.ChildrenBoundingBoxesPointer);
writer.Write(this.ChildrenFlags1Pointer);
writer.Write(this.ChildrenFlags2Pointer);
writer.Write(this.ChildrenCount1);
writer.Write(this.ChildrenCount2);
writer.Write(this.Unknown_A4h);
writer.Write(this.BVHPointer);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
base.WriteXml(sb, indent);
var c = Children?.data_items;
if ((c == null) || (c.Length == 0))
{
YbnXml.SelfClosingTag(sb, indent, "Children");
}
else
{
var cind = indent + 1;
YbnXml.OpenTag(sb, indent, "Children");
foreach (var child in c)
{
Bounds.WriteXmlNode(child, sb, cind, "Item");
2020-01-16 15:03:24 +08:00
}
YbnXml.CloseTag(sb, indent, "Children");
}
}
public override void ReadXml(XmlNode node)
{
base.ReadXml(node);
var cnode = node.SelectSingleNode("Children");
if (cnode != null)
{
var cnodes = cnode.SelectNodes("Item");
if (cnodes?.Count > 0)
{
var blist = new List<Bounds>();
foreach (XmlNode inode in cnodes)
{
var b = Bounds.ReadXmlNode(inode, Owner, this);
blist.Add(b);
2020-01-16 15:03:24 +08:00
}
var arr = blist.ToArray();
Children = new ResourcePointerArray64<Bounds>();
Children.data_items = arr;
BuildBVH();
UpdateChildrenFlags();
UpdateChildrenBounds();
UpdateChildrenTransformations();
}
}
FileVFT = 1080212136;
2020-01-16 15:03:24 +08:00
}
2020-01-03 19:50:23 +08:00
public override IResourceBlock[] GetReferences()
{
2020-01-06 19:17:51 +08:00
BuildBVH();
UpdateChildrenFlags();
UpdateChildrenBounds();
UpdateChildrenTransformations();
using var list = new PooledList<IResourceBlock>(base.GetReferences());
if (Children is not null)
list.Add(Children);
if (ChildrenTransformation1 is not null && ChildrenTransformation1.Length > 0)
2020-01-03 19:50:23 +08:00
{
2020-01-23 15:36:34 +08:00
ChildrenTransformation1Block = new ResourceSystemStructBlock<Matrix4F_s>(ChildrenTransformation1);
2020-01-03 19:50:23 +08:00
list.Add(ChildrenTransformation1Block);
}
else
{
ChildrenTransformation1Block = null;
}
if (ChildrenTransformation2 is not null && ChildrenTransformation2.Length > 0)
2020-01-03 19:50:23 +08:00
{
2020-01-23 15:36:34 +08:00
ChildrenTransformation2Block = new ResourceSystemStructBlock<Matrix4F_s>(ChildrenTransformation2);
2020-01-03 19:50:23 +08:00
list.Add(ChildrenTransformation2Block);
}
else
{
ChildrenTransformation2Block = null;
}
if (ChildrenBoundingBoxes is not null && ChildrenBoundingBoxes.Length > 0)
2020-01-03 19:50:23 +08:00
{
ChildrenBoundingBoxesBlock = new ResourceSystemStructBlock<AABB_s>(ChildrenBoundingBoxes);
list.Add(ChildrenBoundingBoxesBlock);
}
else
{
ChildrenBoundingBoxesBlock = null;
}
if (ChildrenFlags1.Length > 0)
2020-01-03 19:50:23 +08:00
{
ChildrenFlags1Block = new ResourceSystemStructBlock<BoundCompositeChildrenFlags>(ChildrenFlags1);
list.Add(ChildrenFlags1Block);
}
else
{
ChildrenFlags1Block = null;
}
if (ChildrenFlags2.Length > 0)
2020-01-03 19:50:23 +08:00
{
ChildrenFlags2Block = new ResourceSystemStructBlock<BoundCompositeChildrenFlags>(ChildrenFlags2);
list.Add(ChildrenFlags2Block);
}
else
{
ChildrenFlags2Block = null;
}
if (BVH is not null)
list.Add(BVH);
2020-01-03 19:50:23 +08:00
return list.ToArray();
}
public void BuildBVH()
{
if (Children?.data_items is null || Children.data_items.Length <= 5) //composites only get BVHs if they have 6 or more children.
2020-01-06 19:17:51 +08:00
{
BVH = null;
return;
}
if (BVH is not null)
2020-01-06 19:17:51 +08:00
{
//var tnodes = BVHBuilder.Unbuild(BVH);
}
else
{
2020-01-16 15:03:24 +08:00
//why are we here? yft's hit this... (and when loading XML!)
if (Owner is not FragPhysicsLOD && Owner is not FragPhysArchetype && Owner is not VerletCloth)
2020-01-06 19:17:51 +08:00
{ }
}
if (Owner is FragPhysArchetype fpa)
{
if (fpa == fpa.Owner?.Archetype2) //for destroyed yft archetype, don't use a BVH.
{
BVH = null;
return;
}
}
2020-01-06 19:17:51 +08:00
var items = new List<BVHBuilderItem>(Children.data_items.Length);
2020-01-06 19:17:51 +08:00
for (int i = 0; i < Children.data_items.Length; i++)
{
var child = Children.data_items[i];
if (child != null)
{
var cbox = new BoundingBox(child.BoxMin, child.BoxMax);
2023-07-29 00:55:26 +08:00
var tcbox = cbox.Transform(child.Transform);
var it = new BVHBuilderItem
{
Min = tcbox.Minimum,
Max = tcbox.Maximum,
Index = i,
Bounds = child
};
2020-01-06 19:17:51 +08:00
items.Add(it);
}
else
{
items.Add(null);//items need to have correct count to set the correct capacity for the BVH!
}
2020-01-06 19:17:51 +08:00
}
2023-07-29 00:55:26 +08:00
BVH = BVHBuilder.Build(items, 1); //composites have BVH item threshold of 1
2020-01-06 19:17:51 +08:00
}
public void UpdateChildrenFlags()
{
if (Children?.data_items is null)
2020-01-06 19:17:51 +08:00
{
ChildrenFlags1 = [];
ChildrenFlags2 = [];
2020-01-06 19:17:51 +08:00
return;
}
2020-01-22 20:36:34 +08:00
if (OwnerIsFragment)//don't use flags in fragments
{
ChildrenFlags1 = [];
ChildrenFlags2 = [];
2020-01-22 20:36:34 +08:00
return;
}
2020-01-06 19:17:51 +08:00
var cf1 = new BoundCompositeChildrenFlags[Children.data_items.Length];
var cf2 = new BoundCompositeChildrenFlags[Children.data_items.Length];
2020-01-06 19:17:51 +08:00
for(int i = 0; i < Children.data_items.Length; i++)
2020-01-06 19:17:51 +08:00
{
var child = Children.data_items[i];
2020-01-06 19:17:51 +08:00
var f1 = new BoundCompositeChildrenFlags();
var f2 = new BoundCompositeChildrenFlags();
if (child is not null)
2020-01-06 19:17:51 +08:00
{
f1 = child.CompositeFlags1;
f2 = child.CompositeFlags2;
}
cf1[i] = f1;
cf2[i] = f2;
2020-01-06 19:17:51 +08:00
}
ChildrenFlags1 = cf1;
ChildrenFlags2 = cf2;
2020-01-06 19:17:51 +08:00
}
public void UpdateChildrenBounds()
{
if (Children?.data_items == null)
{
ChildrenBoundingBoxes = null;
return;
}
var cbl = new AABB_s[Children.data_items.Length];
for(int i = 0; i < Children.data_items.Length; i++)
2020-01-06 19:17:51 +08:00
{
var child = Children.data_items[i];
if (child is not null)
2020-01-06 19:17:51 +08:00
{
cbl[i] = new AABB_s()
{
Min = new Vector4(child.BoxMin, float.Epsilon),
Max = new Vector4(child.BoxMax, child.Margin),
};
2020-01-06 19:17:51 +08:00
}
}
ChildrenBoundingBoxes = cbl;
2020-01-03 19:50:23 +08:00
}
2020-01-06 19:17:51 +08:00
public void UpdateChildrenTransformations()
{
var dataItems = Children?.data_items;
if (dataItems is null)
2020-01-06 19:17:51 +08:00
{
ChildrenTransformation1 = [];
ChildrenTransformation2 = [];
2020-01-06 19:17:51 +08:00
return;
}
2020-01-03 19:50:23 +08:00
var ct1 = new Matrix4F_s[dataItems.Length];
for (int i = 0; i < dataItems.Length; i++)
2020-01-06 19:17:51 +08:00
{
var child = dataItems[i];
2020-01-23 15:36:34 +08:00
var m = Matrix4F_s.Identity;
var ownerIsFragment = OwnerIsFragment;
if (child is not null)
2020-01-06 19:17:51 +08:00
{
m = new Matrix4F_s(in child._Transform);
2020-01-23 15:36:34 +08:00
}
if (!OwnerIsFragment)
2020-01-23 15:36:34 +08:00
{
m = m with
{
Flags1 = 0U,
Flags2 = 1U,
Flags3 = 1U,
Flags4 = 0U
};
2020-01-23 15:36:34 +08:00
}
ct1[i] = m;
2020-01-06 19:17:51 +08:00
}
ChildrenTransformation1 = ct1;
ChildrenTransformation2 = [];
2020-01-06 19:17:51 +08:00
}
2020-01-03 19:50:23 +08:00
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
var tsph = sph;
var compchilds = Children?.data_items;
if (compchilds == null)
{ return res; }
for (int i = 0; i < compchilds.Length; i++)
{
var c = compchilds[i];
if (c == null)
continue;
2020-01-03 19:50:23 +08:00
tsph.Center = c.TransformInv.Multiply(sph.Center);
var chit = c.SphereIntersect(ref tsph);
chit.Normal = c.Transform.MultiplyRot(chit.Normal);
res.TryUpdate(ref chit);
}
return res;
2017-09-21 18:33:05 +08:00
}
2020-01-03 19:50:23 +08:00
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue)
{
2020-01-03 19:50:23 +08:00
var res = new SpaceRayIntersectResult();
res.HitDist = maxdist;
var tray = ray;
var compchilds = Children?.data_items;
if (compchilds is null)
return res;
2020-01-03 19:50:23 +08:00
for (int i = 0; i < compchilds.Length; i++)
{
var c = compchilds[i];
if (c is null)
continue;
2020-01-03 19:50:23 +08:00
tray.Position = c.TransformInv.Multiply(ray.Position);
tray.Direction = c.TransformInv.MultiplyRot(ray.Direction);
var chit = c.RayIntersect(ref tray, res.HitDist);
chit.Position = c.Transform.Multiply(chit.Position);
chit.Normal = c.Transform.MultiplyRot(chit.Normal);
res.TryUpdate(in chit);
2020-01-03 19:50:23 +08:00
}
return res;
}
2020-01-03 19:50:23 +08:00
2020-01-11 16:20:14 +08:00
public bool DeleteChild(Bounds child)
{
if (Children?.data_items != null)
{
var children = Children.data_items.ToList();
var transforms1 = ChildrenTransformation1?.ToList();
var transforms2 = ChildrenTransformation2?.ToList();
var bboxes = ChildrenBoundingBoxes?.ToList();
var flags1 = ChildrenFlags1?.ToList();
var flags2 = ChildrenFlags2?.ToList();
var idx = children.IndexOf(child);
if (idx >= 0)
{
children.RemoveAt(idx);
transforms1?.RemoveAt(idx);
transforms2?.RemoveAt(idx);
bboxes?.RemoveAt(idx);
flags1?.RemoveAt(idx);
flags2?.RemoveAt(idx);
Children.data_items = children.ToArray();
ChildrenTransformation1 = transforms1?.ToArray();
ChildrenTransformation2 = transforms2?.ToArray();
ChildrenBoundingBoxes = bboxes?.ToArray();
ChildrenFlags1 = flags1?.ToArray();
ChildrenFlags2 = flags2?.ToArray();
BuildBVH();
return true;
}
}
return false;
}
2020-01-13 13:25:52 +08:00
public void AddChild(Bounds child)
{
if (Children is null)
Children = new ResourcePointerArray64<Bounds>();
2020-01-13 13:25:52 +08:00
var children = Children.data_items?.ToList() ?? new List<Bounds>();
2020-01-23 15:36:34 +08:00
var transforms1 = ChildrenTransformation1?.ToList() ?? new List<Matrix4F_s>();
var transforms2 = ChildrenTransformation2?.ToList() ?? new List<Matrix4F_s>();
2020-01-13 13:25:52 +08:00
var bboxes = ChildrenBoundingBoxes?.ToList() ?? new List<AABB_s>();
var flags1 = ChildrenFlags1?.ToList();
var flags2 = ChildrenFlags2?.ToList();
var idx = children.Count;
child.Parent = this;
children.Add(child);
2020-01-23 15:36:34 +08:00
transforms1.Add(Matrix4F_s.Identity);
transforms2.Add(Matrix4F_s.Identity);
2020-01-13 13:25:52 +08:00
bboxes.Add(new AABB_s());//will get updated later
flags1?.Add(new BoundCompositeChildrenFlags());
flags2?.Add(new BoundCompositeChildrenFlags());
Children.data_items = children.ToArray();
ChildrenTransformation1 = transforms1.ToArray();
ChildrenTransformation2 = transforms2.ToArray();
ChildrenBoundingBoxes = bboxes.ToArray();
ChildrenFlags1 = flags1?.ToArray();
ChildrenFlags2 = flags2?.ToArray();
BuildBVH();
UpdateChildrenBounds();
}
2017-09-21 18:33:05 +08:00
}
2020-01-03 19:50:23 +08:00
public enum BoundPolygonType : byte
2017-09-21 18:33:05 +08:00
{
Triangle = 0,
Sphere = 1,
Capsule = 2,
Box = 3,
Cylinder = 4,
}
[TC(typeof(EXP))]
public abstract class BoundPolygon : IMetaXmlItem
2017-09-21 18:33:05 +08:00
{
public BoundPolygonType Type { get; set; }
2019-12-31 21:00:04 +08:00
public BoundGeometry Owner { get; set; } //for browsing/editing convenience
2020-01-03 19:50:23 +08:00
public BoundMaterial_s Material
{
get
{
2020-01-06 19:17:51 +08:00
if (MaterialCustom.HasValue) return MaterialCustom.Value;
return Owner?.GetMaterial(Index) ?? new BoundMaterial_s();
}
set
{
MaterialCustom = value;
2020-01-03 19:50:23 +08:00
}
}
2020-01-06 19:17:51 +08:00
public BoundMaterial_s? MaterialCustom; //for editing, when assigning a new material.
2020-01-16 15:03:24 +08:00
public int MaterialIndex
{
get { return Owner?.GetMaterialIndex(Index) ?? -1; }
}
2020-01-12 19:53:57 +08:00
public Vector3[] VertexPositions
{
get
{
var inds = VertexIndices;
var va = new Vector3[inds.Length];
if (Owner != null)
{
for (int i = 0; i < inds.Length; i++)
{
va[i] = Owner.GetVertexPos(inds[i]);
}
}
return va;
}
set
{
if (value == null) return;
var inds = VertexIndices;
if (Owner != null)
{
var imax = Math.Min(inds.Length, value.Length);
for (int i = 0; i < imax; i++)
{
Owner.SetVertexPos(inds[i], value[i]);
}
}
}
}
2019-12-31 21:00:04 +08:00
public int Index { get; set; } //for editing convenience, not stored
2020-01-06 19:17:51 +08:00
public abstract Vector3 BoxMin { get; }
public abstract Vector3 BoxMax { get; }
2020-01-01 21:16:49 +08:00
public abstract Vector3 Scale { get; set; }
public abstract Vector3 Position { get; set; }
public abstract Quaternion Orientation { get; set; }
2020-01-11 16:20:14 +08:00
public abstract int[] VertexIndices { get; set; }
2020-01-07 17:51:53 +08:00
public abstract BoundVertexRef NearestVertex(Vector3 p);
2020-01-09 23:12:16 +08:00
public abstract void GatherVertices(Dictionary<BoundVertex, int> verts);
2017-09-21 18:33:05 +08:00
public abstract void Read(byte[] bytes, int offset);
public abstract void Write(BinaryWriter bw);
2020-01-16 15:03:24 +08:00
public abstract void WriteXml(StringBuilder sb, int indent);
public abstract void ReadXml(XmlNode node);
public virtual string Title => $"{Type} {Index}";
public override string ToString() => Type.ToString();
2017-09-21 18:33:05 +08:00
}
[TC(typeof(EXP))] public class BoundPolygonTriangle : BoundPolygon
2017-09-21 18:33:05 +08:00
{
public float triArea { get; set; }
public ushort triIndex1 { get; set; }
public ushort triIndex2 { get; set; }
public ushort triIndex3 { get; set; }
public short edgeIndex1 { get; set; }
public short edgeIndex2 { get; set; }
public short edgeIndex3 { get; set; }
2020-01-11 16:20:14 +08:00
public int vertIndex1 { get { return (triIndex1 & 0x7FFF); } set { triIndex1 = (ushort)((value & 0x7FFF) + (vertFlag1 ? 0x8000 : 0)); } }
public int vertIndex2 { get { return (triIndex2 & 0x7FFF); } set { triIndex2 = (ushort)((value & 0x7FFF) + (vertFlag2 ? 0x8000 : 0)); } }
public int vertIndex3 { get { return (triIndex3 & 0x7FFF); } set { triIndex3 = (ushort)((value & 0x7FFF) + (vertFlag3 ? 0x8000 : 0)); } }
2020-01-03 19:50:23 +08:00
public bool vertFlag1 { get { return (triIndex1 & 0x8000) > 0; } set { triIndex1 = (ushort)(vertIndex1 + (value ? 0x8000 : 0)); } }
public bool vertFlag2 { get { return (triIndex2 & 0x8000) > 0; } set { triIndex2 = (ushort)(vertIndex2 + (value ? 0x8000 : 0)); } }
public bool vertFlag3 { get { return (triIndex3 & 0x8000) > 0; } set { triIndex3 = (ushort)(vertIndex3 + (value ? 0x8000 : 0)); } }
2017-09-21 18:33:05 +08:00
2019-12-31 21:00:04 +08:00
public Vector3 Vertex1
{
get => Owner?.GetVertexPos(vertIndex1) ?? Vector3.Zero;
set => Owner?.SetVertexPos(vertIndex1, value);
2019-12-31 21:00:04 +08:00
}
public Vector3 Vertex2
{
get => Owner?.GetVertexPos(vertIndex2) ?? Vector3.Zero;
set => Owner?.SetVertexPos(vertIndex2, value);
2019-12-31 21:00:04 +08:00
}
public Vector3 Vertex3
{
get => Owner?.GetVertexPos(vertIndex3) ?? Vector3.Zero;
set => Owner?.SetVertexPos(vertIndex3, value);
2019-12-31 21:00:04 +08:00
}
2017-09-21 18:33:05 +08:00
public override Vector3 BoxMin => Vector3.Min(Vector3.Min(Vertex1, Vertex2), Vertex3);
public override Vector3 BoxMax => Vector3.Max(Vector3.Max(Vertex1, Vertex2), Vertex3);
2020-01-01 21:16:49 +08:00
public override Vector3 Scale
{
get
{
2020-01-07 22:02:51 +08:00
if (ScaleCached.HasValue) return ScaleCached.Value;
ScaleCached = Vector3.One;
2020-01-01 21:16:49 +08:00
return Vector3.One;
}
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var v3 = Vertex3;
var cen = (v1 + v2 + v3) * (1.0f / 3.0f);
var trans = value / Scale;
var ori = Orientation;
var orinv = Quaternion.Invert(ori);
Vertex1 = cen + ori.Multiply(trans * orinv.Multiply(v1 - cen));
Vertex2 = cen + ori.Multiply(trans * orinv.Multiply(v2 - cen));
Vertex3 = cen + ori.Multiply(trans * orinv.Multiply(v3 - cen));
ScaleCached = value;
2020-01-01 21:16:49 +08:00
}
}
public override Vector3 Position
{
get
{
return (Vertex1 + Vertex2 + Vertex3) * (1.0f / 3.0f);
}
set
{
var offset = value - Position;
Vertex1 += offset;
Vertex2 += offset;
Vertex3 += offset;
}
}
public override Quaternion Orientation
{
get
{
2020-01-07 22:02:51 +08:00
if (OrientationCached.HasValue) return OrientationCached.Value;
var v1 = Vertex1;
var v2 = Vertex2;
var v3 = Vertex3;
var dir = v2 - v1;
var side = Vector3.Cross((v3 - v1), dir);
var up = Vector3.Normalize(Vector3.Cross(dir, side));
var ori = Quaternion.Invert(Quaternion.LookAtRH(Vector3.Zero, side, up));
OrientationCached = ori;
return ori;
2020-01-01 21:16:49 +08:00
}
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var v3 = Vertex3;
var cen = (v1 + v2 + v3) * (1.0f / 3.0f);
var trans = value * Quaternion.Invert(Orientation);
Vertex1 = cen + trans.Multiply(v1 - cen);
Vertex2 = cen + trans.Multiply(v2 - cen);
Vertex3 = cen + trans.Multiply(v3 - cen);
OrientationCached = value;
2020-01-01 21:16:49 +08:00
}
}
2020-01-07 22:02:51 +08:00
private Quaternion? OrientationCached;
private Vector3? ScaleCached;
2020-01-01 21:16:49 +08:00
2020-01-11 16:20:14 +08:00
public override int[] VertexIndices
{
get => [vertIndex1, vertIndex2, vertIndex3];
2020-01-11 16:20:14 +08:00
set
{
if (value?.Length >= 3)
{
vertIndex1 = value[0];
vertIndex2 = value[1];
vertIndex3 = value[2];
}
}
}
2020-01-07 17:51:53 +08:00
public override BoundVertexRef NearestVertex(Vector3 p)
{
var d1 = (p - Vertex1).Length();
var d2 = (p - Vertex2).Length();
var d3 = (p - Vertex3).Length();
if ((d1 <= d2) && (d1 <= d3)) return new BoundVertexRef(vertIndex1, d1);
if (d2 <= d3) return new BoundVertexRef(vertIndex2, d2);
return new BoundVertexRef(vertIndex3, d3);
}
2020-01-09 23:12:16 +08:00
public override void GatherVertices(Dictionary<BoundVertex, int> verts)
{
if (Owner != null)
{
verts[Owner.GetVertexObject(vertIndex1)] = vertIndex1;
verts[Owner.GetVertexObject(vertIndex2)] = vertIndex2;
verts[Owner.GetVertexObject(vertIndex3)] = vertIndex3;
}
}
2020-01-07 17:51:53 +08:00
2020-01-11 16:20:14 +08:00
public void SetEdgeIndex(int edgeid, short polyindex)
{
switch (edgeid)
{
case 1:
edgeIndex1 = polyindex;
break;
case 2:
edgeIndex2 = polyindex;
break;
case 3:
edgeIndex3 = polyindex;
break;
default:
break;
}
}
2017-09-21 18:33:05 +08:00
public BoundPolygonTriangle()
{
Type = BoundPolygonType.Triangle;
}
public override void Read(byte[] bytes, int offset)
{
triArea = BitConverter.ToSingle(bytes, offset + 0);
triIndex1 = BitConverter.ToUInt16(bytes, offset + 4);
triIndex2 = BitConverter.ToUInt16(bytes, offset + 6);
triIndex3 = BitConverter.ToUInt16(bytes, offset + 8);
edgeIndex1 = BitConverter.ToInt16(bytes, offset + 10);
edgeIndex2 = BitConverter.ToInt16(bytes, offset + 12);
edgeIndex3 = BitConverter.ToInt16(bytes, offset + 14);
}
public override void Write(BinaryWriter bw)
{
bw.Write(triArea);
bw.Write(triIndex1);
bw.Write(triIndex2);
bw.Write(triIndex3);
bw.Write(edgeIndex1);
bw.Write(edgeIndex2);
bw.Write(edgeIndex3);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" v3=\"{4}\" f1=\"{5}\" f2=\"{6}\" f3=\"{7}\"",
Type,
MaterialIndex,
vertIndex1,
vertIndex2,
vertIndex3,
vertFlag1 ? 1 : 0,
vertFlag2 ? 1 : 0,
vertFlag3 ? 1 : 0
);
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
{
Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s();
vertIndex1 = Xml.GetIntAttribute(node, "v1");
vertIndex2 = Xml.GetIntAttribute(node, "v2");
vertIndex3 = Xml.GetIntAttribute(node, "v3");
vertFlag1 = Xml.GetIntAttribute(node, "f1") != 0;
vertFlag2 = Xml.GetIntAttribute(node, "f2") != 0;
vertFlag3 = Xml.GetIntAttribute(node, "f3") != 0;
}
2017-09-21 18:33:05 +08:00
public override string ToString()
{
return base.ToString() + ": " + vertIndex1.ToString() + ", " + vertIndex2.ToString() + ", " + vertIndex3.ToString();
}
}
[TC(typeof(EXP))] public class BoundPolygonSphere : BoundPolygon
2017-09-21 18:33:05 +08:00
{
public ushort sphereType { get; set; }
public ushort sphereIndex { get; set; }
public float sphereRadius { get; set; }
public uint unused0 { get; set; }
public uint unused1 { get; set; }
2020-01-06 19:17:51 +08:00
public override Vector3 BoxMin
{
get
{
return Position - sphereRadius;
}
}
public override Vector3 BoxMax
{
get
{
return Position + sphereRadius;
}
}
2020-01-01 21:16:49 +08:00
public override Vector3 Scale
{
get
{
2020-01-07 22:02:51 +08:00
return new Vector3(sphereRadius);
2020-01-01 21:16:49 +08:00
}
set
{
2020-01-07 22:02:51 +08:00
sphereRadius = value.X;
2020-01-01 21:16:49 +08:00
}
}
public override Vector3 Position
2019-12-31 21:00:04 +08:00
{
get { return (Owner != null) ? Owner.GetVertexPos(sphereIndex) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(sphereIndex, value); }
}
2020-01-01 21:16:49 +08:00
public override Quaternion Orientation
{
get
{
return Quaternion.Identity;
}
set
{
}
}
2019-12-31 21:00:04 +08:00
2020-01-11 16:20:14 +08:00
public override int[] VertexIndices
{
get
{
return new[] { (int)sphereIndex };
}
set
{
if (value?.Length >= 1)
{
sphereIndex = (ushort)value[0];
}
}
}
2020-01-07 17:51:53 +08:00
public override BoundVertexRef NearestVertex(Vector3 p)
{
return new BoundVertexRef(sphereIndex, sphereRadius);
}
2020-01-09 23:12:16 +08:00
public override void GatherVertices(Dictionary<BoundVertex, int> verts)
{
if (Owner != null)
{
verts[Owner.GetVertexObject(sphereIndex)] = sphereIndex;
}
}
2020-01-07 17:51:53 +08:00
2017-09-21 18:33:05 +08:00
public BoundPolygonSphere()
{
Type = BoundPolygonType.Sphere;
}
public override void Read(byte[] bytes, int offset)
{
sphereType = BitConverter.ToUInt16(bytes.AsSpan(offset));
sphereIndex = BitConverter.ToUInt16(bytes.AsSpan(offset + 2));
sphereRadius = BitConverter.ToSingle(bytes.AsSpan(offset + 4));
unused0 = BitConverter.ToUInt32(bytes.AsSpan(offset + 8));
unused1 = BitConverter.ToUInt32(bytes.AsSpan(offset + 12));
2017-09-21 18:33:05 +08:00
}
public override void Write(BinaryWriter bw)
{
bw.Write(sphereType);
bw.Write(sphereIndex);
bw.Write(sphereRadius);
bw.Write(unused0);
bw.Write(unused1);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
var s = $"{Type} m=\"{MaterialIndex}\" v=\"{sphereIndex}\" radius=\"{FloatUtil.ToString(sphereRadius)}\"";
2020-01-16 15:03:24 +08:00
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
{
Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s();
sphereIndex = (ushort)Xml.GetUIntAttribute(node, "v");
sphereRadius = Xml.GetFloatAttribute(node, "radius");
}
public override string ToString() => $"{base.ToString()}: {sphereIndex}, {sphereRadius}";
2017-09-21 18:33:05 +08:00
}
[TC(typeof(EXP))] public class BoundPolygonCapsule : BoundPolygon
2020-01-03 19:50:23 +08:00
{
public ushort capsuleType { get; set; }
public ushort capsuleIndex1 { get; set; }
public float capsuleRadius { get; set; }
public ushort capsuleIndex2 { get; set; }
2017-09-21 18:33:05 +08:00
public ushort unused0 { get; set; }
public uint unused1 { get; set; }
2019-12-31 21:00:04 +08:00
public Vector3 Vertex1
{
get => Owner?.GetVertexPos(capsuleIndex1) ?? Vector3.Zero;
set => Owner?.SetVertexPos(capsuleIndex1, value);
2019-12-31 21:00:04 +08:00
}
public Vector3 Vertex2
{
get => Owner?.GetVertexPos(capsuleIndex2) ?? Vector3.Zero;
set => Owner?.SetVertexPos(capsuleIndex2, value);
2019-12-31 21:00:04 +08:00
}
public override Vector3 BoxMin => Vector3.Min(Vertex1, Vertex2) - capsuleRadius;
public override Vector3 BoxMax => Vector3.Max(Vertex1, Vertex2) + capsuleRadius;
2020-01-01 21:16:49 +08:00
public override Vector3 Scale
{
get
{
2020-01-07 22:02:51 +08:00
if (ScaleCached.HasValue) return ScaleCached.Value;
ScaleCached = Vector3.One;
2020-01-01 21:16:49 +08:00
return Vector3.One;
}
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var cen = (v1 + v2) * 0.5f;
var trans = value / Scale;
var ori = Orientation;
var orinv = Quaternion.Invert(ori);
Vertex1 = cen + ori.Multiply(trans * orinv.Multiply(v1 - cen));
Vertex2 = cen + ori.Multiply(trans * orinv.Multiply(v2 - cen));
capsuleRadius = trans.X * capsuleRadius;
ScaleCached = value;
2020-01-01 21:16:49 +08:00
}
}
public override Vector3 Position
{
get => (Vertex1 + Vertex2) * 0.5f;
2020-01-01 21:16:49 +08:00
set
{
var offset = value - Position;
Vertex1 += offset;
Vertex2 += offset;
}
}
public override Quaternion Orientation
{
get
{
2020-01-07 22:02:51 +08:00
if (OrientationCached.HasValue) return OrientationCached.Value;
var v1 = Vertex1;
var v2 = Vertex2;
var dir = v2 - v1;
var up = Vector3.Normalize(dir.GetPerpVec());
var ori = Quaternion.Invert(Quaternion.LookAtRH(Vector3.Zero, dir, up));
OrientationCached = ori;
return ori;
2020-01-01 21:16:49 +08:00
}
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var cen = (v1 + v2) * 0.5f;
var trans = value * Quaternion.Invert(Orientation);
Vertex1 = cen + trans.Multiply(v1 - cen);
Vertex2 = cen + trans.Multiply(v2 - cen);
OrientationCached = value;
2020-01-01 21:16:49 +08:00
}
}
2020-01-07 22:02:51 +08:00
private Quaternion? OrientationCached;
private Vector3? ScaleCached;
2020-01-01 21:16:49 +08:00
2020-01-11 16:20:14 +08:00
public override int[] VertexIndices
{
get
{
return new[] { (int)capsuleIndex1, (int)capsuleIndex2 };
}
set
{
if (value?.Length >= 2)
{
capsuleIndex1 = (ushort)value[0];
capsuleIndex2 = (ushort)value[1];
}
}
}
2020-01-07 17:51:53 +08:00
public override BoundVertexRef NearestVertex(Vector3 p)
{
var d1 = (p - Vertex1).Length();
var d2 = (p - Vertex2).Length();
if (d1 <= d2) return new BoundVertexRef(capsuleIndex1, d1);
return new BoundVertexRef(capsuleIndex2, d2);
}
2020-01-09 23:12:16 +08:00
public override void GatherVertices(Dictionary<BoundVertex, int> verts)
{
if (Owner != null)
{
verts[Owner.GetVertexObject(capsuleIndex1)] = capsuleIndex1;
verts[Owner.GetVertexObject(capsuleIndex2)] = capsuleIndex2;
}
}
2020-01-07 17:51:53 +08:00
2020-01-03 19:50:23 +08:00
public BoundPolygonCapsule()
2017-09-21 18:33:05 +08:00
{
2020-01-03 19:50:23 +08:00
Type = BoundPolygonType.Capsule;
2017-09-21 18:33:05 +08:00
}
public override void Read(byte[] bytes, int offset)
{
2020-01-03 19:50:23 +08:00
capsuleType = BitConverter.ToUInt16(bytes, offset + 0);
capsuleIndex1 = BitConverter.ToUInt16(bytes, offset + 2);
capsuleRadius = BitConverter.ToSingle(bytes, offset + 4);
capsuleIndex2 = BitConverter.ToUInt16(bytes, offset + 8);
2017-09-21 18:33:05 +08:00
unused0 = BitConverter.ToUInt16(bytes, offset + 10);
unused1 = BitConverter.ToUInt32(bytes, offset + 12);
}
public override void Write(BinaryWriter bw)
2020-01-03 19:50:23 +08:00
{
bw.Write(capsuleType);
bw.Write(capsuleIndex1);
bw.Write(capsuleRadius);
bw.Write(capsuleIndex2);
bw.Write(unused0);
bw.Write(unused1);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" radius=\"{4}\"",
Type,
MaterialIndex,
2020-01-16 15:03:24 +08:00
capsuleIndex1,
capsuleIndex2,
FloatUtil.ToString(capsuleRadius)
);
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
{
Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s();
capsuleIndex1 = (ushort)Xml.GetUIntAttribute(node, "v1");
capsuleIndex2 = (ushort)Xml.GetUIntAttribute(node, "v2");
capsuleRadius = Xml.GetFloatAttribute(node, "radius");
}
2020-01-03 19:50:23 +08:00
public override string ToString()
{
return base.ToString() + ": " + capsuleIndex1.ToString() + ", " + capsuleIndex2.ToString() + ", " + capsuleRadius.ToString();
}
2017-09-21 18:33:05 +08:00
}
2020-01-03 19:50:23 +08:00
[TC(typeof(EXP))] public class BoundPolygonBox : BoundPolygon
2017-09-21 18:33:05 +08:00
{
2020-01-03 19:50:23 +08:00
public uint boxType { get; set; }
public short boxIndex1 { get; set; }
public short boxIndex2 { get; set; }
public short boxIndex3 { get; set; }
public short boxIndex4 { get; set; }
public uint unused0 { get; set; }
public Vector3 Vertex1
2017-09-21 18:33:05 +08:00
{
2020-01-03 19:50:23 +08:00
get { return (Owner != null) ? Owner.GetVertexPos(boxIndex1) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(boxIndex1, value); }
2017-09-21 18:33:05 +08:00
}
2020-01-03 19:50:23 +08:00
public Vector3 Vertex2
2017-09-21 18:33:05 +08:00
{
2020-01-03 19:50:23 +08:00
get { return (Owner != null) ? Owner.GetVertexPos(boxIndex2) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(boxIndex2, value); }
}
public Vector3 Vertex3
{
get { return (Owner != null) ? Owner.GetVertexPos(boxIndex3) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(boxIndex3, value); }
}
public Vector3 Vertex4
{
get { return (Owner != null) ? Owner.GetVertexPos(boxIndex4) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(boxIndex4, value); }
}
2020-01-06 19:17:51 +08:00
public override Vector3 BoxMin
{
get
{
return Vector3.Min(Vector3.Min(Vector3.Min(Vertex1, Vertex2), Vertex3), Vertex4);
}
}
public override Vector3 BoxMax
{
get
{
return Vector3.Max(Vector3.Max(Vector3.Max(Vertex1, Vertex2), Vertex3), Vertex4);
}
}
2020-01-03 19:50:23 +08:00
public override Vector3 Scale
{
get
{
2020-01-07 22:02:51 +08:00
if (ScaleCached.HasValue) return ScaleCached.Value;
ScaleCached = Vector3.One;
2020-01-03 19:50:23 +08:00
return Vector3.One;
}
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var v3 = Vertex3;
var v4 = Vertex4;
var cen = (v1 + v2 + v3 + v4) * 0.25f;
var trans = value / Scale;
var ori = Orientation;
var orinv = Quaternion.Invert(ori);
Vertex1 = cen + ori.Multiply(trans * orinv.Multiply(v1 - cen));
Vertex2 = cen + ori.Multiply(trans * orinv.Multiply(v2 - cen));
Vertex3 = cen + ori.Multiply(trans * orinv.Multiply(v3 - cen));
Vertex4 = cen + ori.Multiply(trans * orinv.Multiply(v4 - cen));
ScaleCached = value;
}
2017-09-21 18:33:05 +08:00
}
2020-01-03 19:50:23 +08:00
public override Vector3 Position
2017-09-21 18:33:05 +08:00
{
2020-01-03 19:50:23 +08:00
get
{
return (Vertex1 + Vertex2 + Vertex3 + Vertex4) * 0.25f;
}
set
{
var offset = value - Position;
Vertex1 += offset;
Vertex2 += offset;
Vertex3 += offset;
Vertex4 += offset;
}
}
public override Quaternion Orientation
{
get
{
2020-01-07 22:02:51 +08:00
if (OrientationCached.HasValue) return OrientationCached.Value;
var v1 = Vertex1;
var v2 = Vertex2;
var v3 = Vertex3;
var v4 = Vertex4;
var dir = (v1+v4) - (v2+v3);
var up = Vector3.Normalize((v3+v4) - (v1+v2));
var ori = Quaternion.Invert(Quaternion.LookAtRH(Vector3.Zero, dir, up));
OrientationCached = ori;
return ori;
2020-01-03 19:50:23 +08:00
}
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var v3 = Vertex3;
var v4 = Vertex4;
var cen = (v1 + v2 + v3 + v4) * 0.25f;
var trans = value * Quaternion.Invert(Orientation);
Vertex1 = cen + trans.Multiply(v1 - cen);
Vertex2 = cen + trans.Multiply(v2 - cen);
Vertex3 = cen + trans.Multiply(v3 - cen);
Vertex4 = cen + trans.Multiply(v4 - cen);
OrientationCached = value;
2020-01-03 19:50:23 +08:00
}
}
2020-01-07 22:02:51 +08:00
private Quaternion? OrientationCached;
private Vector3? ScaleCached;
2017-09-21 18:33:05 +08:00
2020-01-11 16:20:14 +08:00
public override int[] VertexIndices
{
get
{
return new[] { (int)boxIndex1, (int)boxIndex2, (int)boxIndex3, (int)boxIndex4 };
}
set
{
if (value?.Length >= 2)
{
boxIndex1 = (short)value[0];
boxIndex2 = (short)value[1];
boxIndex3 = (short)value[2];
boxIndex4 = (short)value[3];
}
}
}
2020-01-07 17:51:53 +08:00
public override BoundVertexRef NearestVertex(Vector3 p)
{
var d1 = (p - Vertex1).Length();
var d2 = (p - Vertex2).Length();
var d3 = (p - Vertex3).Length();
var d4 = (p - Vertex4).Length();
if ((d1 <= d2) && (d1 <= d3) && (d1 <= d4)) return new BoundVertexRef(boxIndex1, d1);
if ((d2 <= d3) && (d2 <= d4)) return new BoundVertexRef(boxIndex2, d2);
if (d3 <= d4) return new BoundVertexRef(boxIndex3, d3);
return new BoundVertexRef(boxIndex4, d4);
}
2020-01-09 23:12:16 +08:00
public override void GatherVertices(Dictionary<BoundVertex, int> verts)
{
if (Owner != null)
{
verts[Owner.GetVertexObject(boxIndex1)] = boxIndex1;
verts[Owner.GetVertexObject(boxIndex2)] = boxIndex2;
verts[Owner.GetVertexObject(boxIndex3)] = boxIndex3;
verts[Owner.GetVertexObject(boxIndex4)] = boxIndex4;
}
}
2020-01-07 17:51:53 +08:00
2020-01-03 19:50:23 +08:00
public BoundPolygonBox()
{
Type = BoundPolygonType.Box;
}
public override void Read(byte[] bytes, int offset)
{
boxType = BitConverter.ToUInt32(bytes, offset + 0);
boxIndex1 = BitConverter.ToInt16(bytes, offset + 4);
boxIndex2 = BitConverter.ToInt16(bytes, offset + 6);
boxIndex3 = BitConverter.ToInt16(bytes, offset + 8);
boxIndex4 = BitConverter.ToInt16(bytes, offset + 10);
unused0 = BitConverter.ToUInt32(bytes, offset + 12);
}
public override void Write(BinaryWriter bw)
{
bw.Write(boxType);
bw.Write(boxIndex1);
bw.Write(boxIndex2);
bw.Write(boxIndex3);
bw.Write(boxIndex4);
bw.Write(unused0);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" v3=\"{4}\" v4=\"{5}\"",
Type,
MaterialIndex,
2020-01-16 15:03:24 +08:00
boxIndex1,
boxIndex2,
boxIndex3,
boxIndex4
);
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
{
Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s();
boxIndex1 = (short)Xml.GetIntAttribute(node, "v1");
boxIndex2 = (short)Xml.GetIntAttribute(node, "v2");
boxIndex3 = (short)Xml.GetIntAttribute(node, "v3");
boxIndex4 = (short)Xml.GetIntAttribute(node, "v4");
}
2020-01-03 19:50:23 +08:00
public override string ToString()
{
return base.ToString() + ": " + boxIndex1.ToString() + ", " + boxIndex2.ToString() + ", " + boxIndex3.ToString() + ", " + boxIndex4.ToString();
}
}
[TC(typeof(EXP))] public class BoundPolygonCylinder : BoundPolygon
{
public ushort cylinderType { get; set; }
public ushort cylinderIndex1 { get; set; }
public float cylinderRadius { get; set; }
public ushort cylinderIndex2 { get; set; }
public ushort unused0 { get; set; }
public uint unused1 { get; set; }
2017-09-21 18:33:05 +08:00
2020-01-03 19:50:23 +08:00
public Vector3 Vertex1
{
get { return (Owner != null) ? Owner.GetVertexPos(cylinderIndex1) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(cylinderIndex1, value); }
}
public Vector3 Vertex2
{
get { return (Owner != null) ? Owner.GetVertexPos(cylinderIndex2) : Vector3.Zero; }
set { if (Owner != null) Owner.SetVertexPos(cylinderIndex2, value); }
2017-09-21 18:33:05 +08:00
}
2020-01-06 19:17:51 +08:00
public override Vector3 BoxMin
{
get
{
return Vector3.Min(Vertex1, Vertex2) - cylinderRadius;//not perfect but meh
}
}
public override Vector3 BoxMax
{
get
{
return Vector3.Max(Vertex1, Vertex2) + cylinderRadius;//not perfect but meh
}
}
2020-01-03 19:50:23 +08:00
public override Vector3 Scale
2017-09-21 18:33:05 +08:00
{
2020-01-03 19:50:23 +08:00
get
{
2020-01-07 22:02:51 +08:00
if (ScaleCached.HasValue) return ScaleCached.Value;
ScaleCached = Vector3.One;
2020-01-03 19:50:23 +08:00
return Vector3.One;
}
2020-01-03 19:50:23 +08:00
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var cen = (v1 + v2) * 0.5f;
var trans = value / Scale;
var ori = Orientation;
var orinv = Quaternion.Invert(ori);
Vertex1 = cen + ori.Multiply(trans * orinv.Multiply(v1 - cen));
Vertex2 = cen + ori.Multiply(trans * orinv.Multiply(v2 - cen));
cylinderRadius = trans.X * cylinderRadius;
ScaleCached = value;
}
2020-01-03 19:50:23 +08:00
}
public override Vector3 Position
{
get
{
2020-01-03 19:50:23 +08:00
return (Vertex1 + Vertex2) * 0.5f;
}
set
{
var offset = value - Position;
Vertex1 += offset;
Vertex2 += offset;
}
2020-01-03 19:50:23 +08:00
}
public override Quaternion Orientation
{
get
{
2020-01-07 22:02:51 +08:00
if (OrientationCached.HasValue) return OrientationCached.Value;
var v1 = Vertex1;
var v2 = Vertex2;
var dir = v2 - v1;
var up = Vector3.Normalize(dir.GetPerpVec());
var ori = Quaternion.Invert(Quaternion.LookAtRH(Vector3.Zero, dir, up));
OrientationCached = ori;
return ori;
}
2020-01-03 19:50:23 +08:00
set
{
2020-01-07 22:02:51 +08:00
var v1 = Vertex1;
var v2 = Vertex2;
var cen = (v1 + v2) * 0.5f;
var trans = value * Quaternion.Invert(Orientation);
Vertex1 = cen + trans.Multiply(v1 - cen);
Vertex2 = cen + trans.Multiply(v2 - cen);
OrientationCached = value;
}
2017-09-21 18:33:05 +08:00
}
2020-01-07 22:02:51 +08:00
private Quaternion? OrientationCached;
private Vector3? ScaleCached;
2020-01-11 16:20:14 +08:00
public override int[] VertexIndices
{
get
{
return new[] { (int)cylinderIndex1, (int)cylinderIndex2 };
}
set
{
if (value?.Length >= 2)
{
cylinderIndex1 = (ushort)value[0];
cylinderIndex2 = (ushort)value[1];
}
}
}
2020-01-07 17:51:53 +08:00
public override BoundVertexRef NearestVertex(Vector3 p)
{
var d1 = (p - Vertex1).Length();
var d2 = (p - Vertex2).Length();
if (d1 <= d2) return new BoundVertexRef(cylinderIndex1, d1);
return new BoundVertexRef(cylinderIndex2, d2);
}
2020-01-09 23:12:16 +08:00
public override void GatherVertices(Dictionary<BoundVertex, int> verts)
{
if (Owner != null)
{
verts[Owner.GetVertexObject(cylinderIndex1)] = cylinderIndex1;
verts[Owner.GetVertexObject(cylinderIndex2)] = cylinderIndex2;
}
}
2020-01-07 17:51:53 +08:00
2020-01-03 19:50:23 +08:00
public BoundPolygonCylinder()
{
Type = BoundPolygonType.Cylinder;
}
public override void Read(byte[] bytes, int offset)
{
cylinderType = BitConverter.ToUInt16(bytes, offset + 0);
cylinderIndex1 = BitConverter.ToUInt16(bytes, offset + 2);
cylinderRadius = BitConverter.ToSingle(bytes, offset + 4);
cylinderIndex2 = BitConverter.ToUInt16(bytes, offset + 8);
unused0 = BitConverter.ToUInt16(bytes, offset + 10);
unused1 = BitConverter.ToUInt32(bytes, offset + 12);
}
public override void Write(BinaryWriter bw)
{
bw.Write(cylinderType);
bw.Write(cylinderIndex1);
bw.Write(cylinderRadius);
bw.Write(cylinderIndex2);
bw.Write(unused0);
bw.Write(unused1);
}
2020-01-16 15:03:24 +08:00
public override void WriteXml(StringBuilder sb, int indent)
{
var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" radius=\"{4}\"",
Type,
MaterialIndex,
2020-01-16 15:03:24 +08:00
cylinderIndex1,
cylinderIndex2,
FloatUtil.ToString(cylinderRadius)
);
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
{
Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s();
cylinderIndex1 = (ushort)Xml.GetUIntAttribute(node, "v1");
cylinderIndex2 = (ushort)Xml.GetUIntAttribute(node, "v2");
cylinderRadius = Xml.GetFloatAttribute(node, "radius");
}
2020-01-03 19:50:23 +08:00
public override string ToString()
{
return base.ToString() + ": " + cylinderIndex1.ToString() + ", " + cylinderIndex2.ToString() + ", " + cylinderRadius.ToString();
}
}
2020-01-07 17:51:53 +08:00
[TC(typeof(EXP))] public struct BoundEdgeRef : IEquatable<BoundEdgeRef>
//convenience struct for updating edge indices
2020-01-11 16:20:14 +08:00
{
public int Vertex1 { get; set; }
public int Vertex2 { get; set; }
public BoundEdgeRef(int i1, int i2)
{
Vertex1 = Math.Min(i1, i2);
Vertex2 = Math.Max(i1, i2);
}
public override bool Equals(object? obj)
{
return obj is BoundEdgeRef @ref && Equals(@ref);
}
public bool Equals(BoundEdgeRef other)
{
return Vertex1 == other.Vertex1 &&
Vertex2 == other.Vertex2;
}
public override int GetHashCode()
{
return HashCode.Combine(Vertex1, Vertex2);
}
public static bool operator ==(BoundEdgeRef left, BoundEdgeRef right)
{
return left.Equals(right);
}
public static bool operator !=(BoundEdgeRef left, BoundEdgeRef right)
{
return !(left == right);
}
2020-01-11 16:20:14 +08:00
}
[TC(typeof(EXP))] public class BoundEdge //convenience class for updating edge indices
{
public BoundPolygonTriangle Triangle1 { get; set; }
public BoundPolygonTriangle Triangle2 { get; set; }
public int EdgeID1 { get; set; }
public int EdgeID2 { get; set; }
public BoundEdge(BoundPolygonTriangle t1, int e1)
{
Triangle1 = t1;
EdgeID1 = e1;
}
}
2020-01-07 17:51:53 +08:00
[TC(typeof(EXP))] public struct BoundVertexRef //convenience struct for BoundPolygon.NearestVertex and SpaceRayIntersectResult
{
public int Index { get; set; }
public float Distance { get; set; }
public BoundVertexRef(int index, float dist)
{
Index = index;
Distance = dist;
}
}
[TC(typeof(EXP))]
public class BoundVertex //class for editing convenience, to hold a reference to a BoundGeometry vertex
2020-01-07 17:51:53 +08:00
{
public BoundGeometry Owner { get; set; }
public int Index { get; set; }
public Vector3 Position
{
get { return (Owner != null) ? Owner.GetVertexPos(Index) : Vector3.Zero; }
set { Owner?.SetVertexPos(Index, value); }
2020-01-07 17:51:53 +08:00
}
public BoundMaterialColour Colour
{
get { return (Owner != null) ? Owner.GetVertexColour(Index) : new BoundMaterialColour(); }
set { Owner?.SetVertexColour(Index, value); }
2020-01-07 17:51:53 +08:00
}
public BoundVertex(BoundGeometry owner, int index)
{
Owner = owner;
Index = index;
}
public virtual string Title => $"Vertex {Index}";
2020-01-07 17:51:53 +08:00
}
[TC(typeof(EXP))]
public readonly struct BoundVertex_s(in Vector3 v)
2020-01-03 19:50:23 +08:00
{
public short X { get; init; } = (short)v.X;
public short Y { get; init; } = (short)v.Y;
public short Z { get; init; } = (short)v.Z;
2020-01-06 19:17:51 +08:00
public Vector3 Vector
{
get => new Vector3(X, Y, Z);
2020-01-06 19:17:51 +08:00
}
2020-01-03 19:50:23 +08:00
}
2020-01-01 21:16:49 +08:00
2023-07-28 05:48:41 +08:00
[TC(typeof(EXP))] public class BoundGeomOctants : ResourceSystemBlock
2020-01-03 19:50:23 +08:00
{
public uint[] Counts { get; set; } = new uint[8];
public uint[][] Items { get; private set; } = new uint[8][];
2020-01-01 21:16:49 +08:00
2020-01-03 19:50:23 +08:00
public override long BlockLength
{
2020-01-03 19:50:23 +08:00
get
{
long len = 128; // (8*(4 + 8)) + 32
for (int i = 0; i < 8; i++)
{
len += (Counts[i] * 4);
}
return len;
}
}
2020-01-03 19:50:23 +08:00
public override void Read(ResourceDataReader reader, params object[] parameters)
{
2020-01-06 19:17:51 +08:00
if ((parameters?.Length ?? 0) < 1)
2020-01-03 19:50:23 +08:00
{ return; } //shouldn't happen!
ulong ptr = (ulong)parameters[0]; //pointer array pointer
for (int i = 0; i < 8; i++)
{
Counts[i] = reader.ReadUInt32();
2020-01-03 19:50:23 +08:00
}
ulong[] ptrlist = reader.ReadUlongsAt(ptr, 8, false);
//if (ptr != (ulong)reader.Position)
//{ }//no hit
//ptr += 64;
for (int i = 0; i < 8; i++)
{
Items[i] = reader.ReadUintsAt(ptrlist[i], Counts[i], false);
//if (ptrlist[i] != ptr)
//{ ptr = ptrlist[i]; }//no hit
//ptr += (Counts[i] * 4);
}
//reader.Position = (long)ptr;
//var b = reader.ReadBytes(32);
//for (int i = 0; i < b.Length; i++)
//{
// if (b[i] != 0)
// { }//no hit
//}
2020-01-03 19:50:23 +08:00
}
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
var ptr = writer.Position + 96;
for (int i = 0; i < 8; i++)
{
writer.Write(Counts[i]);
}
for (int i = 0; i < 8; i++)
2020-01-03 19:50:23 +08:00
{
writer.Write((ulong)ptr);
ptr += (Counts[i] * 4);
}
for (int i = 0; i < 8; i++)
{
var items = (i < Items.Length) ? Items[i] : null;
if (items == null)
{ continue; }
var c = Counts[i];
for (int n = 0; n < c; n++)
2020-01-03 19:50:23 +08:00
{
var v = (n < items.Length) ? items[n] : 0;
writer.Write(v);
2020-01-03 19:50:23 +08:00
}
}
writer.Write(new byte[32]);
2020-01-03 19:50:23 +08:00
}
public void UpdateCounts()
{
for (int i = 0; i < 8; i++)
2020-01-03 19:50:23 +08:00
{
Counts[i] = (i < (Items?.Length ?? 0)) ? (uint)(Items[i]?.Length ?? 0) : 0;
}
}
2017-09-21 18:33:05 +08:00
}
2020-01-03 19:50:23 +08:00
2020-01-06 19:17:51 +08:00
[Flags] public enum EBoundCompositeFlags : uint
{
2020-01-06 19:17:51 +08:00
NONE = 0u,
UNKNOWN = 1u,
MAP_WEAPON = 1u << 1,
MAP_DYNAMIC = 1u << 2,
MAP_ANIMAL = 1u << 3,
MAP_COVER = 1u << 4,
MAP_VEHICLE = 1u << 5,
VEHICLE_NOT_BVH = 1u << 6,
VEHICLE_BVH = 1u << 7,
VEHICLE_BOX = 1u << 8,
PED = 1u << 9,
RAGDOLL = 1u << 10,
ANIMAL = 1u << 11,
ANIMAL_RAGDOLL = 1u << 12,
OBJECT = 1u << 13,
OBJECT_ENV_CLOTH = 1u << 14,
PLANT = 1u << 15,
PROJECTILE = 1u << 16,
EXPLOSION = 1u << 17,
PICKUP = 1u << 18,
FOLIAGE = 1u << 19,
FORKLIFT_FORKS = 1u << 20,
TEST_WEAPON = 1u << 21,
TEST_CAMERA = 1u << 22,
TEST_AI = 1u << 23,
TEST_SCRIPT = 1u << 24,
TEST_VEHICLE_WHEEL = 1u << 25,
GLASS = 1u << 26,
MAP_RIVER = 1u << 27,
SMOKE = 1u << 28,
UNSMASHED = 1u << 29,
MAP_STAIRS = 1u << 30,
MAP_DEEP_SURFACE = 1u << 31,
}
[TC(typeof(EXP))]
public struct BoundCompositeChildrenFlags
2017-09-21 18:33:05 +08:00
{
public EBoundCompositeFlags Flags1 { get; set; }
public EBoundCompositeFlags Flags2 { get; set; }
public override readonly string ToString()
2017-09-21 18:33:05 +08:00
{
return $"{Flags1}, {Flags2}";
2017-09-21 18:33:05 +08:00
}
}
2020-01-03 19:50:23 +08:00
[TC(typeof(EXP))] public class BVH : ResourceSystemBlock
2017-09-21 18:33:05 +08:00
{
public override long BlockLength => 128;
2017-09-21 18:33:05 +08:00
// structure data
public ResourceSimpleList64b_s<BVHNode_s> Nodes { get; set; }
2017-09-21 18:33:05 +08:00
public uint Unknown_10h { get; set; } // 0x00000000
public uint Unknown_14h { get; set; } // 0x00000000
public uint Unknown_18h { get; set; } // 0x00000000
public uint Unknown_1Ch { get; set; } // 0x00000000
2018-03-10 22:27:13 +08:00
public Vector4 BoundingBoxMin { get; set; }
public Vector4 BoundingBoxMax { get; set; }
public Vector4 BoundingBoxCenter { get; set; }
public Vector4 QuantumInverse { get; set; }
public Vector4 Quantum { get; set; } // bounding box dimension / 2^16
public ResourceSimpleList64_s<BVHTreeInfo_s> Trees { get; set; }
2017-09-21 18:33:05 +08:00
/// <summary>
/// Reads the data-block from a stream.
/// </summary>
public override void Read(ResourceDataReader reader, params object[] parameters)
{
// read structure data
this.Nodes = reader.ReadBlock<ResourceSimpleList64b_s<BVHNode_s>>();
2017-09-21 18:33:05 +08:00
this.Unknown_10h = reader.ReadUInt32();
this.Unknown_14h = reader.ReadUInt32();
this.Unknown_18h = reader.ReadUInt32();
this.Unknown_1Ch = reader.ReadUInt32();
2020-01-22 20:36:34 +08:00
this.BoundingBoxMin = reader.ReadVector4();
this.BoundingBoxMax = reader.ReadVector4();
this.BoundingBoxCenter = reader.ReadVector4();
this.QuantumInverse = reader.ReadVector4();
this.Quantum = reader.ReadVector4();
this.Trees = reader.ReadBlock<ResourceSimpleList64_s<BVHTreeInfo_s>>();
2017-09-21 18:33:05 +08:00
}
/// <summary>
/// Writes the data-block to a stream.
/// </summary>
public override void Write(ResourceDataWriter writer, params object[] parameters)
{
// write structure data
writer.WriteBlock(this.Nodes);
2017-09-21 18:33:05 +08:00
writer.Write(this.Unknown_10h);
writer.Write(this.Unknown_14h);
writer.Write(this.Unknown_18h);
writer.Write(this.Unknown_1Ch);
writer.Write(this.BoundingBoxMin);
writer.Write(this.BoundingBoxMax);
writer.Write(this.BoundingBoxCenter);
writer.Write(this.QuantumInverse);
writer.Write(this.Quantum);
writer.WriteBlock(this.Trees);
2017-09-21 18:33:05 +08:00
}
/// <summary>
/// Returns a list of data blocks which are referenced by this block.
/// </summary>
public override IResourceBlock[] GetReferences()
{
//var list = new List<IResourceBlock>();
//if (Nodes != null) list.Add(Nodes);
2017-09-21 18:33:05 +08:00
//if (Trees != null) list.Add(Trees);
return [];
2017-09-21 18:33:05 +08:00
}
public override (long, IResourceBlock)[] GetParts()
2017-09-21 18:33:05 +08:00
{
return new (long, IResourceBlock)[] {
(0x0, Nodes),
(0x70, Trees)
2017-09-21 18:33:05 +08:00
};
}
}
[TC(typeof(EXP))]
public struct BVHTreeInfo_s
2017-09-21 18:33:05 +08:00
{
public short MinX { get; set; }
public short MinY { get; set; }
public short MinZ { get; set; }
public short MaxX { get; set; }
public short MaxY { get; set; }
public short MaxZ { get; set; }
public short NodeIndex1 { get; set; }
public short NodeIndex2 { get; set; }
2020-01-06 19:17:51 +08:00
public Vector3 Min
{
readonly get { return new Vector3(MinX, MinY, MinZ); }
2020-01-06 19:17:51 +08:00
set { MinX = (short)value.X; MinY = (short)value.Y; MinZ = (short)value.Z; }
}
public Vector3 Max
{
readonly get { return new Vector3(MaxX, MaxY, MaxZ); }
2020-01-06 19:17:51 +08:00
set { MaxX = (short)value.X; MaxY = (short)value.Y; MaxZ = (short)value.Z; }
}
public override readonly string ToString()
2017-09-21 18:33:05 +08:00
{
return $"{NodeIndex1}, {NodeIndex2} ({NodeIndex2 - NodeIndex1} nodes)";
2017-09-21 18:33:05 +08:00
}
}
[TC(typeof(EXP))]
public struct BVHNode_s
2017-09-21 18:33:05 +08:00
{
public short MinX { get; set; }
public short MinY { get; set; }
public short MinZ { get; set; }
public short MaxX { get; set; }
public short MaxY { get; set; }
public short MaxZ { get; set; }
2020-01-01 21:16:49 +08:00
public short ItemId { get; set; }
public short ItemCount { get; set; }
2017-09-21 18:33:05 +08:00
2020-01-06 19:17:51 +08:00
public Vector3 Min
{
readonly get { return new Vector3(MinX, MinY, MinZ); }
2020-01-06 19:17:51 +08:00
set { MinX = (short)value.X; MinY = (short)value.Y; MinZ = (short)value.Z; }
}
public Vector3 Max
{
readonly get { return new Vector3(MaxX, MaxY, MaxZ); }
2020-01-06 19:17:51 +08:00
set { MaxX = (short)value.X; MaxY = (short)value.Y; MaxZ = (short)value.Z; }
}
public override readonly string ToString()
2017-09-21 18:33:05 +08:00
{
return $"{ItemId}: {ItemCount}";
2017-09-21 18:33:05 +08:00
}
}
2020-01-06 19:17:51 +08:00
public class BVHBuilder
{
public static int MaxNodeItemCount = 4; //item threshold: 1 for composites, 4 for geometries
public static int MaxTreeNodeCount = 127; //max number of nodes found in any tree
public static BVH? Build(List<BVHBuilderItem> items, int itemThreshold)
2020-01-06 19:17:51 +08:00
{
if (items is null)
return null;
2020-01-06 19:17:51 +08:00
var bvh = new BVH();
var min = new Vector3(float.MaxValue);
var max = new Vector3(float.MinValue);
using var nodes = new PooledList<BVHBuilderNode>();
using var trees = new PooledList<BVHBuilderNode>();
var iteml = new List<BVHBuilderItem>(items.Count);
foreach(var item in items.AsSpan())
2020-01-06 19:17:51 +08:00
{
if (item is null)
continue;
iteml.Add(item);
Vector3.Min(ref min, ref item.Min, out min);
Vector3.Max(ref max, ref item.Max, out max);
2020-01-06 19:17:51 +08:00
}
//for (int i = 0; i < items.Count; i++)
//{
// var item = items[i];
// if (item is null)
// continue;
// iteml.Add(item);
// Vector3.Min(ref min, ref item.Min, out min);
// Vector3.Max(ref max, ref item.Max, out max);
//}
2020-01-06 19:17:51 +08:00
var cen = (min + max) * 0.5f;
bvh.BoundingBoxMin = new Vector4(min, float.NaN);
bvh.BoundingBoxMax = new Vector4(max, float.NaN);
bvh.BoundingBoxCenter = new Vector4(cen, float.NaN);
bvh.Quantum = new Vector4(Vector3.Max((min - cen).Abs(), (max - cen).Abs()) / 32767.0f, float.NaN);
bvh.QuantumInverse = new Vector4(1.0f / bvh.Quantum.XYZ(), float.NaN);
var root = new BVHBuilderNode();
root.Items = iteml;
2020-01-06 19:17:51 +08:00
root.Build(itemThreshold);
root.GatherNodes(nodes);
root.GatherTrees(trees);
if (itemThreshold > 1) //need to reorder items, since they need to be grouped by node for the node's item index
{
items.Clear();
foreach (var node in nodes.Span)
2020-01-06 19:17:51 +08:00
{
if (node.Items != null)
{
foreach (var item in node.Items.AsSpan())
2020-01-06 19:17:51 +08:00
{
item.Index = items.Count;
items.Add(item);
}
}
}
}
else //don't need to reorder items, since nodes only have one item and one item index
{ }
using var bvhtrees = new PooledList<BVHTreeInfo_s>(trees.Count);
using var bvhnodes = new PooledList<BVHNode_s>(nodes.Count);
2020-01-06 19:17:51 +08:00
var qi = bvh.QuantumInverse.XYZ();
var c = bvh.BoundingBoxCenter.XYZ();
foreach(var node in nodes.Span)
2020-01-06 19:17:51 +08:00
{
var id = ((node.Items?.Count ?? 0) > 0) ? node.Items[0].Index : 0;
var tn = node.TotalNodes;
var bn = new BVHNode_s();
bn.Min = (node.Min - c) * qi;
bn.Max = (node.Max - c) * qi;
bn.ItemCount = (short)((tn <= 1) ? node.TotalItems : 0);
bn.ItemId = (short)((tn <= 1) ? id : node.TotalNodes);
bvhnodes.Add(bn);
}
foreach(var tree in trees.Span)
2020-01-06 19:17:51 +08:00
{
var bt = new BVHTreeInfo_s();
bt.Min = (tree.Min - c) * qi;
bt.Max = (tree.Max - c) * qi;
bt.NodeIndex1 = (short)tree.Index;
bt.NodeIndex2 = (short)(tree.Index + tree.TotalNodes);
bvhtrees.Add(bt);
}
var nodecount = bvhnodes.Count;
if (itemThreshold <= 1) //for composites, capacity needs to be (numchildren*2)+1, with empty nodes filling up the space..
{
var capacity = (items.Count * 2) + 1;
var emptynode = new BVHNode_s();
emptynode.ItemId = 1;
while (bvhnodes.Count < capacity)
{
bvhnodes.Add(emptynode);
}
}
2020-01-06 19:17:51 +08:00
bvh.Nodes = new ResourceSimpleList64b_s<BVHNode_s>();
bvh.Nodes.data_items = bvhnodes.ToArray();
bvh.Nodes.EntriesCount = (uint)nodecount;
2020-01-06 19:17:51 +08:00
bvh.Trees = new ResourceSimpleList64_s<BVHTreeInfo_s>();
bvh.Trees.data_items = bvhtrees.ToArray();
return bvh;
}
public static BVHBuilderNode[] Unbuild(BVH bvh)
{
if ((bvh?.Trees?.data_items == null) || (bvh?.Nodes?.data_items == null)) return null;
var nodes = new List<BVHBuilderNode>();
foreach (var tree in bvh.Trees.data_items)
{
var bnode = new BVHBuilderNode();
bnode.Unbuild(bvh, tree.NodeIndex1, tree.NodeIndex2);
nodes.Add(bnode);
//MaxTreeNodeCount = Math.Max(MaxTreeNodeCount, tree.NodeIndex2 - tree.NodeIndex1);
}
return nodes.ToArray();
}
}
public class BVHBuilderNode
{
public List<BVHBuilderNode>? Children;
public List<BVHBuilderItem>? Items;
2020-01-06 19:17:51 +08:00
public Vector3 Min;
public Vector3 Max;
public int Index;
public int TotalNodes
{
get
{
int c = 1;
if (Children is not null)
2020-01-06 19:17:51 +08:00
{
foreach (var child in Children.AsSpan())
2020-01-06 19:17:51 +08:00
{
c += child.TotalNodes;
}
}
return c;
}
}
public int TotalItems
{
get
{
int c = Items?.Count ?? 0;
if (Children is not null)
2020-01-06 19:17:51 +08:00
{
foreach (var child in Children.AsSpan())
2020-01-06 19:17:51 +08:00
{
c += child.TotalItems;
}
}
return c;
}
}
public void Build(int itemThreshold)
{
UpdateMinMax();
if (Items == null)
return;
if (Items.Count <= itemThreshold)
return;
2020-01-06 19:17:51 +08:00
var avgsum = Vector3.Zero;
foreach (var item in Items.AsSpan())
2020-01-06 19:17:51 +08:00
{
avgsum += item.Min;
avgsum += item.Max;
}
var avg = avgsum * (0.5f / Items.Count);
int countx = 0, county = 0, countz = 0;
foreach (var item in Items.AsSpan())
2020-01-06 19:17:51 +08:00
{
var icen = (item.Min + item.Max) * 0.5f;
if (icen.X < avg.X) countx++;
if (icen.Y < avg.Y) county++;
if (icen.Z < avg.Z) countz++;
}
var target = Items.Count / 2.0f;
var dx = Math.Abs(target - countx);
var dy = Math.Abs(target - county);
var dz = Math.Abs(target - countz);
int axis = -1;
if ((dx <= dy) && (dx <= dz)) axis = 0; //x seems best
else if (dy <= dz) axis = 1; //y seems best
else axis = 2; //z seems best
var l1 = new List<BVHBuilderItem>();
var l2 = new List<BVHBuilderItem>();
foreach (var item in Items.AsSpan())
2020-01-06 19:17:51 +08:00
{
var icen = (item.Min + item.Max) * 0.5f;
bool s = false;
s = axis switch
2020-01-06 19:17:51 +08:00
{
1 => (icen.Y > avg.Y),
2 => (icen.Z > avg.Z),
_ => (icen.X > avg.X),
};
2020-01-06 19:17:51 +08:00
if (s) l1.Add(item);
else l2.Add(item);
}
if (l1.Count == 0 || l2.Count == 0) //don't get stuck in a stack overflow...
2020-01-06 19:17:51 +08:00
{
var l3 = new List<BVHBuilderItem>();//we can recover from this...
l3.AddRange(l1);
l3.AddRange(l2);
if (l3.Count > 0)
{
l3.Sort((a, b) =>
{
var c = a.Min.CompareTo(in b.Min);
if (c != 0)
return c;
return a.Max.CompareTo(in b.Max);
2020-01-06 19:17:51 +08:00
});
l1.Clear();
l2.Clear();
var hidx = l3.Count / 2;
for (int i = 0; i < hidx; i++) l1.Add(l3[i]);
for (int i = hidx; i < l3.Count; i++) l2.Add(l3[i]);
}
else
{ return; }//nothing to see here?
}
Items = null;
Children = new List<BVHBuilderNode>();
var n1 = new BVHBuilderNode();
n1.Items = l1;
n1.Build(itemThreshold);
Children.Add(n1);
var n2 = new BVHBuilderNode();
n2.Items = l2;
n2.Build(itemThreshold);
Children.Add(n2);
Children.Sort((a, b) =>
{
return b.TotalItems.CompareTo(a.TotalItems);
}); //is this necessary?
}
public void UpdateMinMax()
{
var min = new Vector3(float.MaxValue);
var max = new Vector3(float.MinValue);
if (Items is not null)
2020-01-06 19:17:51 +08:00
{
foreach (var item in Items.AsSpan())
2020-01-06 19:17:51 +08:00
{
Vector3.Min(ref min, ref item.Min, out min);
Vector3.Max(ref max, ref item.Max, out max);
2020-01-06 19:17:51 +08:00
}
}
if (Children is not null)
2020-01-06 19:17:51 +08:00
{
foreach (var child in Children.AsSpan())
2020-01-06 19:17:51 +08:00
{
child.UpdateMinMax();
Vector3.Min(ref min, ref child.Min, out min);
Vector3.Max(ref max, ref child.Max, out max);
2020-01-06 19:17:51 +08:00
}
}
Min = min;
Max = max;
}
public void GatherNodes(IList<BVHBuilderNode> nodes)
2020-01-06 19:17:51 +08:00
{
Index = nodes.Count;
nodes.Add(this);
if (Children is not null)
2020-01-06 19:17:51 +08:00
{
foreach (var child in Children.AsSpan())
2020-01-06 19:17:51 +08:00
{
child.GatherNodes(nodes);
}
}
}
public void GatherTrees(IList<BVHBuilderNode> trees)
2020-01-06 19:17:51 +08:00
{
if ((TotalNodes > BVHBuilder.MaxTreeNodeCount) && ((Children?.Count ?? 0) > 0))
{
foreach (var child in Children.AsSpan())
2020-01-06 19:17:51 +08:00
{
child.GatherTrees(trees);
}
}
else
{
trees.Add(this);
}
}
public void Unbuild(BVH bvh, int nodeIndex1, int nodeIndex2)
{
var q = bvh.Quantum.XYZ();
var c = bvh.BoundingBoxCenter.XYZ();
int nodeind = nodeIndex1;
int lastind = nodeIndex2;
while (nodeind < lastind)
{
var node = bvh.Nodes.data_items[nodeind];
if (node.ItemCount <= 0) //intermediate node with child nodes
{
Children = new List<BVHBuilderNode>();
var cind1 = nodeind + 1;
var lcind = nodeind + node.ItemId; //(child node count)
while (cind1 < lcind)
{
var cnode = bvh.Nodes.data_items[cind1];
var ccount = (cnode.ItemCount <= 0) ? cnode.ItemId : 1;
var cind2 = cind1 + ccount;
var chi = new BVHBuilderNode();
chi.Unbuild(bvh, cind1, cind2);
Children.Add(chi);
cind1 = cind2;
}
Console.WriteLine($"Children.Count: {Children.Count}; cind1: {nodeind + 1}; lcind: {nodeind + node.ItemId}");
2020-01-06 19:17:51 +08:00
nodeind += node.ItemId;
}
else //leaf node, with polygons
{
Items = new List<BVHBuilderItem>(node.ItemCount);
2020-01-06 19:17:51 +08:00
for (int i = 0; i < node.ItemCount; i++)
{
var item = new BVHBuilderItem();
item.Index = node.ItemId + i;
Items.Add(item);
}
//BVHBuilder.MaxNodeItemCount = Math.Max(BVHBuilder.MaxNodeItemCount, node.ItemCount);
nodeind++;
}
Min = node.Min * q + c;
Max = node.Max * q + c;
}
}
public override string ToString()
{
var fstr = (Children != null) ? $"{TotalNodes}, 0 - " : (Items != null) ? ($"i, {TotalItems} - ") : "error!";
var cstr = (Children != null) ? $"{Children.Count} children" : "";
var istr = (Items != null) ? $"{Items.Count} items" : "";
if (string.IsNullOrEmpty(cstr))
return fstr + istr;
if (string.IsNullOrEmpty(istr))
return fstr + cstr;
return $"{cstr}, {istr}";
2020-01-06 19:17:51 +08:00
}
}
public class BVHBuilderItem
{
public Vector3 Min;
public Vector3 Max;
public int Index;
public Bounds Bounds;
public BoundPolygon Polygon;
}
2017-09-21 18:33:05 +08:00
2020-01-03 19:50:23 +08:00
[Flags] public enum EBoundMaterialFlags : ushort
{
NONE = 0,
FLAG_STAIRS = 1,
FLAG_NOT_CLIMBABLE = 1 << 1,
FLAG_SEE_THROUGH = 1 << 2,
FLAG_SHOOT_THROUGH = 1 << 3,
FLAG_NOT_COVER = 1 << 4,
FLAG_WALKABLE_PATH = 1 << 5,
FLAG_NO_CAM_COLLISION = 1 << 6,
FLAG_SHOOT_THROUGH_FX = 1 << 7,
FLAG_NO_DECAL = 1 << 8,
FLAG_NO_NAVMESH = 1 << 9,
FLAG_NO_RAGDOLL = 1 << 10,
FLAG_VEHICLE_WHEEL = 1 << 11,
FLAG_NO_PTFX = 1 << 12,
FLAG_TOO_STEEP_FOR_PLAYER = 1 << 13,
FLAG_NO_NETWORK_SPAWN = 1 << 14,
FLAG_NO_CAM_COLLISION_ALLOW_CLIPPING = 1 << 15,
}
[TC(typeof(EXP))] public struct BoundMaterial_s : IMetaXmlItem, IEquatable<BoundMaterial_s>
2020-01-03 19:50:23 +08:00
{
public uint Data1;
public uint Data2;
public BoundsMaterialType Type
{
get => (BoundsMaterialType)(Data1 & 0xFFu);
set => Data1 = ((Data1 & 0xFFFFFF00u) | ((byte)value & 0xFFu));
}
public byte ProceduralId
{
get => (byte)((Data1 >> 8) & 0xFFu);
set => Data1 = ((Data1 & 0xFFFF00FFu) | ((value & 0xFFu) << 8));
}
public byte RoomId
{
get => (byte)((Data1 >> 16) & 0x1Fu);
set => Data1 = ((Data1 & 0xFFE0FFFFu) | ((value & 0x1Fu) << 16));
}
public byte PedDensity
{
get => (byte)((Data1 >> 21) & 0x7u);
set => Data1 = ((Data1 & 0xFF1FFFFFu) | ((value & 0x7u) << 21));
}
public EBoundMaterialFlags Flags
{
get => (EBoundMaterialFlags)(((Data1 >> 24) & 0xFFu) | ((Data2 & 0xFFu) << 8));
set
{
Data1 = (Data1 & 0x00FFFFFFu) | (((ushort)value & 0x00FFu) << 24);
Data2 = (Data2 & 0xFFFFFF00u) | (((ushort)value & 0xFF00u) >> 8);
}
}
public byte MaterialColorIndex
{
get => (byte)((Data2 >> 8) & 0xFFu);
2020-01-16 15:03:24 +08:00
set => Data2 = ((Data2 & 0xFFFF00FFu) | ((value & 0xFFu) << 8));
2020-01-03 19:50:23 +08:00
}
public ushort Unk4
{
get => (ushort)((Data2 >> 16) & 0xFFFFu);
set => Data2 = ((Data2 & 0x0000FFFFu) | ((value & 0xFFFFu) << 16));
}
2020-01-16 15:03:24 +08:00
public void WriteXml(StringBuilder sb, int indent)
{
YbnXml.ValueTag(sb, indent, "Type", Type.Index.ToString());
YbnXml.ValueTag(sb, indent, "ProceduralID", ProceduralId.ToString());
YbnXml.ValueTag(sb, indent, "RoomID", RoomId.ToString());
YbnXml.ValueTag(sb, indent, "PedDensity", PedDensity.ToString());
YbnXml.StringTag(sb, indent, "Flags", Flags.ToString());
YbnXml.ValueTag(sb, indent, "MaterialColourIndex", MaterialColorIndex.ToString());
YbnXml.ValueTag(sb, indent, "Unk", Unk4.ToString());
}
public void ReadXml(XmlNode node)
{
Type = (byte)Xml.GetChildUIntAttribute(node, "Type", "value");
ProceduralId = (byte)Xml.GetChildUIntAttribute(node, "ProceduralID", "value");
RoomId = (byte)Xml.GetChildUIntAttribute(node, "RoomID", "value");
PedDensity = (byte)Xml.GetChildUIntAttribute(node, "PedDensity", "value");
Flags = Xml.GetChildEnumInnerText<EBoundMaterialFlags>(node, "Flags");
MaterialColorIndex = (byte)Xml.GetChildUIntAttribute(node, "MaterialColourIndex", "value");
Unk4 = (ushort)Xml.GetChildUIntAttribute(node, "Unk", "value");
}
2020-01-03 19:50:23 +08:00
public override string ToString()
{
return Data1.ToString() + ", " + Data2.ToString() + ", "
+ Type.ToString() + ", " + ProceduralId.ToString() + ", " + RoomId.ToString() + ", " + PedDensity.ToString() + ", "
+ Flags.ToString() + ", " + MaterialColorIndex.ToString() + ", " + Unk4.ToString();
}
public override bool Equals(object? obj)
{
return obj is BoundMaterial_s s && Equals(s);
}
public bool Equals(BoundMaterial_s other)
{
return Data1 == other.Data1 &&
Data2 == other.Data2;
}
public override int GetHashCode()
{
return HashCode.Combine(Data1, Data2);
}
public static bool operator ==(BoundMaterial_s left, BoundMaterial_s right)
{
return left.Equals(right);
}
public static bool operator !=(BoundMaterial_s left, BoundMaterial_s right)
{
return !(left == right);
}
2020-01-03 19:50:23 +08:00
}
[TC(typeof(EXP))] public struct BoundMaterialColour
{
public byte R { get; set; }
public byte G { get; set; }
public byte B { get; set; }
public byte A { get; set; } //GIMS EVO saves this as "opacity" 0-100
public override string ToString()
{
//return Type.ToString() + ", " + Unk0.ToString() + ", " + Unk1.ToString() + ", " + Unk2.ToString();
return R.ToString() + ", " + G.ToString() + ", " + B.ToString() + ", " + A.ToString();
}
}
[TC(typeof(EXP))] public struct BoundsMaterialType
2017-09-21 18:33:05 +08:00
{
public byte Index { get; set; }
public BoundsMaterialData MaterialData
{
get
{
return BoundsMaterialTypes.GetMaterial(this);
}
}
public override string ToString()
{
return BoundsMaterialTypes.GetMaterialName(this);
}
public static implicit operator byte(BoundsMaterialType matType)
{
return matType.Index; //implicit conversion
}
public static implicit operator BoundsMaterialType(byte b)
{
return new BoundsMaterialType() { Index = b };
}
2017-09-21 18:33:05 +08:00
}
[TC(typeof(EXP))] public class BoundsMaterialData
2017-09-21 18:33:05 +08:00
{
public string Name { get; set; }
public string Filter { get; set; }
public string FXGroup { get; set; }
public string VFXDisturbanceType { get; set; }
public string RumbleProfile { get; set; }
public string ReactWeaponType { get; set; }
public string Friction { get; set; }
public string Elasticity { get; set; }
public string Density { get; set; }
public string TyreGrip { get; set; }
public string WetGrip { get; set; }
public string TyreDrag { get; set; }
public string TopSpeedMult { get; set; }
public string Softness { get; set; }
public string Noisiness { get; set; }
public string PenetrationResistance { get; set; }
public string SeeThru { get; set; }
public string ShootThru { get; set; }
public string ShootThruFX { get; set; }
public string NoDecal { get; set; }
public string Porous { get; set; }
public string HeatsTyre { get; set; }
public string Material { get; set; }
public Color Colour { get; set; }
2017-09-21 18:33:05 +08:00
public override string ToString()
{
return Name;
}
}
public static class BoundsMaterialTypes
{
private static Dictionary<string, Color> ColourDict;
2020-01-03 19:50:23 +08:00
public static List<BoundsMaterialData> Materials;
2017-09-21 18:33:05 +08:00
public static void Init(GameFileCache gameFileCache)
{
var rpfman = gameFileCache.RpfMan;
var dic = new Dictionary<string,Color>();
string filename2 = "common.rpf\\data\\effects\\materialfx.dat";
string txt2 = rpfman.GetFileUTF8Text(filename2);
AddMaterialfxDat(txt2, dic);
ColourDict = dic;
2017-09-21 18:33:05 +08:00
var list = new List<BoundsMaterialData>();
string filename = "common.rpf\\data\\materials\\materials.dat";
if (gameFileCache.EnableDlc)
{
filename = "update\\update.rpf\\common\\data\\materials\\materials.dat";
}
string txt = rpfman.GetFileUTF8Text(filename);
AddMaterialsDat(txt, list);
Materials = list;
}
//Only gets the colors
private static void AddMaterialfxDat(string txt, Dictionary<string, Color> dic)
2017-09-21 18:33:05 +08:00
{
dic.Clear();
if (txt == null) return;
string[] lines = txt.Split('\n');
string startLine = "MTLFX_TABLE_START";
string endLine = "MTLFX_TABLE_END";
for (int i = 1; i < lines.Length; i++)
2017-09-21 18:33:05 +08:00
{
var line = lines[i];
2017-09-21 18:33:05 +08:00
if (line[0] == '#') continue;
2023-11-12 01:59:17 +08:00
if (line.StartsWith(startLine, StringComparison.OrdinalIgnoreCase)) continue;
if (line.StartsWith(endLine, StringComparison.OrdinalIgnoreCase)) break;
2017-09-21 18:33:05 +08:00
string[] parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 5) continue; // FXGroup R G B ...
int cp = 0;
Color c = new Color();
c.A = 0xFF;
string fxgroup = string.Empty;
for (int p = 0; p < parts.Length; p++)
{
string part = parts[p].Trim();
if (string.IsNullOrWhiteSpace(part)) continue;
switch (cp)
{
case 0: fxgroup = part; break;
case 1: c.R = byte.Parse(part); break;
case 2: c.G = byte.Parse(part); break;
case 3: c.B = byte.Parse(part); break;
}
cp++;
}
dic.Add(fxgroup, c);
2017-09-21 18:33:05 +08:00
}
}
private static void AddMaterialsDat(string txt, List<BoundsMaterialData> list)
{
list.Clear();
if (txt == null) return;
string[] lines = txt.Split('\n');
for (int i = 1; i < lines.Length; i++)
{
var line = lines[i];
if (line.Length < 20) continue;
if (line[0] == '#') continue;
string[] parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 10) continue;
int cp = 0;
BoundsMaterialData d = new BoundsMaterialData();
for (int p = 0; p < parts.Length; p++)
{
string part = parts[p].Trim();
if (string.IsNullOrWhiteSpace(part)) continue;
switch (cp)
{
case 0: d.Name = part; break;
case 1: d.Filter = part; break;
case 2: d.FXGroup = part; break;
case 3: d.VFXDisturbanceType = part; break;
case 4: d.RumbleProfile = part; break;
case 5: d.ReactWeaponType = part; break;
case 6: d.Friction = part; break;
case 7: d.Elasticity = part; break;
case 8: d.Density = part; break;
case 9: d.TyreGrip = part; break;
case 10: d.WetGrip = part; break;
case 11: d.TyreDrag = part; break;
case 12: d.TopSpeedMult = part; break;
case 13: d.Softness = part; break;
case 14: d.Noisiness = part; break;
case 15: d.PenetrationResistance = part; break;
case 16: d.SeeThru = part; break;
case 17: d.ShootThru = part; break;
case 18: d.ShootThruFX = part; break;
case 19: d.NoDecal = part; break;
case 20: d.Porous = part; break;
case 21: d.HeatsTyre = part; break;
case 22: d.Material = part; break;
}
cp++;
}
if (cp != 23)
{ }
Color c;
if ((ColourDict != null) && (ColourDict.TryGetValue(d.FXGroup, out c)))
2017-09-21 18:33:05 +08:00
{
d.Colour = c;
}
else
{
d.Colour = new Color(0xFFCCCCCC);
2017-09-21 18:33:05 +08:00
}
list.Add(d);
}
//StringBuilder sb = new StringBuilder();
//foreach (var d in list)
//{
// sb.AppendLine(d.Name);
//}
//string names = sb.ToString();
}
public static BoundsMaterialData GetMaterial(BoundsMaterialType type)
{
if (Materials == null) return null;
if (type.Index >= Materials.Count) return null;
return Materials[type.Index];
}
2018-06-08 00:42:41 +08:00
public static BoundsMaterialData GetMaterial(byte index)
{
if (Materials == null) return null;
if ((int)index >= Materials.Count) return null;
return Materials[index];
}
2017-09-21 18:33:05 +08:00
public static string GetMaterialName(BoundsMaterialType type)
{
var m = GetMaterial(type);
if (m == null) return string.Empty;
return m.Name;
}
public static Color GetMaterialColour(BoundsMaterialType type)
2017-09-21 18:33:05 +08:00
{
var m = GetMaterial(type);
if (m == null) return new Color(0xFFCCCCCC);
2017-09-21 18:33:05 +08:00
return m.Colour;
}
}
}