From 3c60801e841ecf89f13b45f8b610d962f861ac1c Mon Sep 17 00:00:00 2001 From: dexy Date: Tue, 7 Jan 2020 20:51:53 +1100 Subject: [PATCH] Collisions editing progress --- .../GameFiles/FileTypes/YbnFile.cs | 4 + CodeWalker.Core/GameFiles/Resources/Bounds.cs | 123 +++++- CodeWalker.Core/World/Space.cs | 2 + CodeWalker.csproj | 9 + .../EditYbnBoundVertexPanel.Designer.cs | 128 ++++++ Project/Panels/EditYbnBoundVertexPanel.cs | 114 +++++ Project/Panels/EditYbnBoundVertexPanel.resx | 409 ++++++++++++++++++ Project/Panels/ProjectExplorerPanel.cs | 36 ++ Project/ProjectForm.cs | 147 ++++++- Project/UndoStep.cs | 56 +++ Utils/MapUtils.cs | 37 ++ WorldForm.cs | 55 ++- 12 files changed, 1113 insertions(+), 7 deletions(-) create mode 100644 Project/Panels/EditYbnBoundVertexPanel.Designer.cs create mode 100644 Project/Panels/EditYbnBoundVertexPanel.cs create mode 100644 Project/Panels/EditYbnBoundVertexPanel.resx diff --git a/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs index a1e1a4f..8c8f543 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs @@ -73,6 +73,10 @@ namespace CodeWalker.GameFiles { return false; } + public bool RemoveVertex(BoundVertex v) + { + return false; + } } } diff --git a/CodeWalker.Core/GameFiles/Resources/Bounds.cs b/CodeWalker.Core/GameFiles/Resources/Bounds.cs index 1d737e1..552f2ec 100644 --- a/CodeWalker.Core/GameFiles/Resources/Bounds.cs +++ b/CodeWalker.Core/GameFiles/Resources/Bounds.cs @@ -859,6 +859,9 @@ namespace CodeWalker.GameFiles private ResourceSystemStructBlock MaterialColoursBlock = null; private ResourceSystemStructBlock PolygonMaterialIndicesBlock = null; + private BoundVertex[] VertexObjects = null; //for use by the editor, created as needed by GetVertexObject() + + /// /// Reads the data-block from a stream. /// @@ -1009,6 +1012,24 @@ namespace CodeWalker.GameFiles } } + 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 Vector3 GetVertex(int index) { return ((index >= 0) && (index < Vertices.Length)) ? Vertices[index] : Vector3.Zero; @@ -1026,6 +1047,18 @@ namespace CodeWalker.GameFiles 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; + } + } + /// @@ -1423,7 +1456,9 @@ namespace CodeWalker.GameFiles { res.HitDist = polyhittestdist; res.Hit = true; + res.Position = (ray.Position + ray.Direction * polyhittestdist); res.Normal = n1; + res.HitVertex = (polygon != null) ? polygon.NearestVertex(res.Position) : new BoundVertexRef(-1, float.MaxValue); res.HitPolygon = polygon; res.HitBounds = this; res.Material = polygon.Material; @@ -2231,6 +2266,7 @@ namespace CodeWalker.GameFiles public abstract Vector3 Scale { get; set; } public abstract Vector3 Position { get; set; } public abstract Quaternion Orientation { get; set; } + public abstract BoundVertexRef NearestVertex(Vector3 p); public abstract void Read(byte[] bytes, int offset); public abstract void Write(BinaryWriter bw); public virtual string Title @@ -2327,6 +2363,16 @@ namespace CodeWalker.GameFiles } } + 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); + } + public BoundPolygonTriangle() { Type = BoundPolygonType.Triangle; @@ -2404,6 +2450,11 @@ namespace CodeWalker.GameFiles } } + public override BoundVertexRef NearestVertex(Vector3 p) + { + return new BoundVertexRef(sphereIndex, sphereRadius); + } + public BoundPolygonSphere() { Type = BoundPolygonType.Sphere; @@ -2497,6 +2548,14 @@ namespace CodeWalker.GameFiles } } + 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); + } + public BoundPolygonCapsule() { Type = BoundPolygonType.Capsule; @@ -2604,6 +2663,18 @@ namespace CodeWalker.GameFiles } } + 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); + } + public BoundPolygonBox() { Type = BoundPolygonType.Box; @@ -2699,6 +2770,14 @@ namespace CodeWalker.GameFiles } } + 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); + } + public BoundPolygonCylinder() { Type = BoundPolygonType.Cylinder; @@ -2727,6 +2806,49 @@ namespace CodeWalker.GameFiles } } + + [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 + { + public BoundGeometry Owner { get; set; } + public int Index { get; set; } + + public Vector3 Position + { + get { return (Owner != null) ? Owner.GetVertexPos(Index) : Vector3.Zero; } + set { if (Owner != null) Owner.SetVertexPos(Index, value); } + } + public BoundMaterialColour Colour + { + get { return (Owner != null) ? Owner.GetVertexColour(Index) : new BoundMaterialColour(); } + set { if (Owner != null) Owner.SetVertexColour(Index, value); } + } + + public BoundVertex(BoundGeometry owner, int index) + { + Owner = owner; + Index = index; + } + + public virtual string Title + { + get + { + return "Vertex " + Index.ToString(); + } + } + } + [TC(typeof(EXP))] public struct BoundVertex_s { public short X { get; set; } @@ -3449,7 +3571,6 @@ namespace CodeWalker.GameFiles } [TC(typeof(EXP))] public struct BoundMaterialColour { - //public BoundsMaterialType Type { get; set; } public byte R { get; set; } public byte G { get; set; } public byte B { get; set; } diff --git a/CodeWalker.Core/World/Space.cs b/CodeWalker.Core/World/Space.cs index 1d357d0..1089e34 100644 --- a/CodeWalker.Core/World/Space.cs +++ b/CodeWalker.Core/World/Space.cs @@ -2150,6 +2150,7 @@ namespace CodeWalker.World { public bool Hit; public float HitDist; + public BoundVertexRef HitVertex; public BoundPolygon HitPolygon; public Bounds HitBounds; public YbnFile HitYbn; @@ -2167,6 +2168,7 @@ namespace CodeWalker.World { Hit = true; HitDist = r.HitDist; + HitVertex = r.HitVertex; HitPolygon = r.HitPolygon; HitBounds = r.HitBounds; HitYbn = r.HitYbn; diff --git a/CodeWalker.csproj b/CodeWalker.csproj index fcab0ca..6b36860 100644 --- a/CodeWalker.csproj +++ b/CodeWalker.csproj @@ -152,6 +152,12 @@ EditYbnBoundsPanel.cs + + Form + + + EditYbnBoundVertexPanel.cs + Form @@ -645,6 +651,9 @@ EditYbnBoundsPanel.cs + + EditYbnBoundVertexPanel.cs + EditYbnPanel.cs diff --git a/Project/Panels/EditYbnBoundVertexPanel.Designer.cs b/Project/Panels/EditYbnBoundVertexPanel.Designer.cs new file mode 100644 index 0000000..6800946 --- /dev/null +++ b/Project/Panels/EditYbnBoundVertexPanel.Designer.cs @@ -0,0 +1,128 @@ +namespace CodeWalker.Project.Panels +{ + partial class EditYbnBoundVertexPanel + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EditYbnBoundVertexPanel)); + this.PositionTextBox = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.DeleteButton = new System.Windows.Forms.Button(); + this.AddToProjectButton = new System.Windows.Forms.Button(); + this.ColourTextBox = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // PositionTextBox + // + this.PositionTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.PositionTextBox.Location = new System.Drawing.Point(82, 47); + this.PositionTextBox.Name = "PositionTextBox"; + this.PositionTextBox.Size = new System.Drawing.Size(457, 20); + this.PositionTextBox.TabIndex = 1; + this.PositionTextBox.TextChanged += new System.EventHandler(this.PositionTextBox_TextChanged); + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(27, 50); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(47, 13); + this.label5.TabIndex = 0; + this.label5.Text = "Position:"; + // + // DeleteButton + // + this.DeleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.DeleteButton.Location = new System.Drawing.Point(458, 7); + this.DeleteButton.Name = "DeleteButton"; + this.DeleteButton.Size = new System.Drawing.Size(95, 23); + this.DeleteButton.TabIndex = 40; + this.DeleteButton.Text = "Delete Vertex"; + this.DeleteButton.UseVisualStyleBackColor = true; + this.DeleteButton.Click += new System.EventHandler(this.DeleteButton_Click); + // + // AddToProjectButton + // + this.AddToProjectButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.AddToProjectButton.Location = new System.Drawing.Point(357, 7); + this.AddToProjectButton.Name = "AddToProjectButton"; + this.AddToProjectButton.Size = new System.Drawing.Size(95, 23); + this.AddToProjectButton.TabIndex = 39; + this.AddToProjectButton.Text = "Add to Project"; + this.AddToProjectButton.UseVisualStyleBackColor = true; + this.AddToProjectButton.Click += new System.EventHandler(this.AddToProjectButton_Click); + // + // ColourTextBox + // + this.ColourTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ColourTextBox.Location = new System.Drawing.Point(82, 73); + this.ColourTextBox.Name = "ColourTextBox"; + this.ColourTextBox.Size = new System.Drawing.Size(457, 20); + this.ColourTextBox.TabIndex = 3; + this.ColourTextBox.TextChanged += new System.EventHandler(this.ColourTextBox_TextChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(27, 76); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(40, 13); + this.label1.TabIndex = 2; + this.label1.Text = "Colour:"; + // + // EditYbnBoundVertexPanel + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(565, 505); + this.Controls.Add(this.ColourTextBox); + this.Controls.Add(this.label1); + this.Controls.Add(this.DeleteButton); + this.Controls.Add(this.AddToProjectButton); + this.Controls.Add(this.PositionTextBox); + this.Controls.Add(this.label5); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "EditYbnBoundVertexPanel"; + this.Text = "EditYbnBoundVertexPanel"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox PositionTextBox; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Button DeleteButton; + private System.Windows.Forms.Button AddToProjectButton; + private System.Windows.Forms.TextBox ColourTextBox; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/Project/Panels/EditYbnBoundVertexPanel.cs b/Project/Panels/EditYbnBoundVertexPanel.cs new file mode 100644 index 0000000..d6b7cfd --- /dev/null +++ b/Project/Panels/EditYbnBoundVertexPanel.cs @@ -0,0 +1,114 @@ +using CodeWalker.GameFiles; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace CodeWalker.Project.Panels +{ + public partial class EditYbnBoundVertexPanel : ProjectPanel + { + public ProjectForm ProjectForm; + public BoundVertex CollisionVertex { get; set; } + + private bool populatingui = false; + private bool waschanged = false; + + public EditYbnBoundVertexPanel(ProjectForm projectForm) + { + ProjectForm = projectForm; + InitializeComponent(); + } + + public void SetCollisionVertex(BoundVertex v) + { + CollisionVertex = v; + Tag = v; + UpdateFormTitle(); + UpdateUI(); + waschanged = v?.Owner?.HasChanged ?? false; + } + + public void UpdateFormTitleYbnChanged() + { + bool changed = CollisionVertex?.Owner?.HasChanged ?? false; + if (!waschanged && changed) + { + UpdateFormTitle(); + waschanged = true; + } + else if (waschanged && !changed) + { + UpdateFormTitle(); + waschanged = false; + } + } + private void UpdateFormTitle() + { + string fn = CollisionVertex?.Title ?? "untitled"; + Text = fn + ((CollisionVertex?.Owner?.HasChanged ?? false) ? "*" : ""); + } + + + public void UpdateUI() + { + if (CollisionVertex == null) + { + AddToProjectButton.Enabled = false; + DeleteButton.Enabled = false; + PositionTextBox.Text = string.Empty; + ColourTextBox.Text = string.Empty; + } + else + { + populatingui = true; + + PositionTextBox.Text = FloatUtil.GetVector3String(CollisionVertex.Position); + ColourTextBox.Text = CollisionVertex.Colour.ToString(); + + var ybn = CollisionVertex.Owner?.GetRootYbn(); + AddToProjectButton.Enabled = (ybn != null) ? !ProjectForm.YbnExistsInProject(ybn) : false; + DeleteButton.Enabled = !AddToProjectButton.Enabled; + + populatingui = false; + } + } + + private void PositionTextBox_TextChanged(object sender, EventArgs e) + { + if (CollisionVertex == null) return; + if (populatingui) return; + var v = FloatUtil.ParseVector3String(PositionTextBox.Text); + lock (ProjectForm.ProjectSyncRoot) + { + if (CollisionVertex.Position != v) + { + CollisionVertex.Position = v; + ProjectForm.SetYbnHasChanged(true); + } + } + } + + private void ColourTextBox_TextChanged(object sender, EventArgs e) + { + //TODO!! + } + + private void AddToProjectButton_Click(object sender, EventArgs e) + { + ProjectForm.SetProjectItem(CollisionVertex); + ProjectForm.AddCollisionVertexToProject(); + } + + private void DeleteButton_Click(object sender, EventArgs e) + { + ProjectForm.SetProjectItem(CollisionVertex); + ProjectForm.DeleteCollisionVertex(); + } + } +} diff --git a/Project/Panels/EditYbnBoundVertexPanel.resx b/Project/Panels/EditYbnBoundVertexPanel.resx new file mode 100644 index 0000000..1431f6b --- /dev/null +++ b/Project/Panels/EditYbnBoundVertexPanel.resx @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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 + 6P///ysvjWVqtP///////wAAAAAAAAAAAP////D0/0hPpsDG6////////6y02d7k8////3qAx+/z/f// + /wAAAAAAAAAAAAAAAAAAAP///////////////8zT8V5ns1Rcrdzh9f///////////wAAAAAAAAAAAAAA + AAAAAP////////7+/6ix3nmBxFthtmdwu09WqbC54/v9//r8//j6//39/wAAAAAAAAAAAOjt/H6I0FJc + skpSqHF+wRMahFZhs4iT1AsNc1pgrm52v2RsuO/z/gAAAP////////L2/cLJ7rrD64+V4DY+ozU+mYmU + 0X2Hy1hfss7V8urv/PP2/v///wAAAP///+Pp+d/k9////////+Pp/4uR3ysymW14xYOM0fD0/P///+Xq + +ri/6Pj6/wAAAOrv/j5DnbS75P////////////X4/+/0/ubr+/r7/////////9rh+hgZhKGo2QAAAPDz + /eLn+f////j6/2Nqttrg9////+Hn+P3+//3+/1hescLJ6/////L2/eru/AAAAAAAAAAAAP///8rR70tR + p/3+//v8/zY6jNPY7////09WqWpwu////wAAAAAAAAAAAAAAAAAAAAAAAPb4/vr7//////v8/5Wd1eHm + +P////v8//T3/wAAAAAAAAAAAAAAAP//AAD8PwAA/D8AAPwDAACAAwAAgAMAAIAHAADABwAAwAEAAMAB + AAAAAQAAAAEAAAABAAAAAQAAwAcAAOAPAAAoAAAAQAAAAIAAAAABABgAAAAAAAAwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7//f7//P3//P3//P3/ + /f7//v7/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7//P3/ + +fv/+fv/+Pr/+fv/+vv//P3//v//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA/f7/+fr/8/b/7PL/5+3/6e/+9Pf/+vv//v7/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/P3/9/r/6O7/cXe1UVaet7z17fL/+Pr//f3/AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+/z/9Pj/4Oj/NzyCUlOd2dz/6O//9Pf//P3/AAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+vv/8vb/2+P9X2OmREGLnqPd + 4+v/8vb/+/z/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+vv/8fb/ + 1N35bXK1JSRtbHGz5O7/8fX/+/z/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA+vv/8PX/3Ob/U1eaDwtXjZLT4+z/8fX/+/z/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA+vv/8fb/2eP+MjR6AAA+c3i34Or/8fX/+/z/AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+vv/8vb/1d/7MS91AAA1UFSS4On/8vb/+/z/AAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + /v7/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7//P3/+vz/+fv/+Pr/+Pr/+vv//f3//v//AAAAAAAAAAAAAAAA + /v//+fv/9Pf/2+L/SVGtAABsLTaZytL58fX/9/n//f7/AAAAAAAAAAAAAAAA/v7/+/z/9/n/9fj/9vn/ + 9fj/9vj/+vz//f7/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7//f7//f3//f3//f3//v7//v//AAAA + AAAAAAAAAAAAAAAAAAAA+/z/9vn/6e//mZ7gTVarr7bp6/H/9fj/+vv//v7/AAAAAAAAAAAAAAAAAAAA + /v7//f7/+/z/+/z/+/z//P3//v7/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f3/+Pr/9fj/6e7/4+n/8fb/9Pf/+Pr//f3/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7//P3/+fv/+fv/+vv/+Pr/+vv/ + /P3//v7/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7//f7/ + /f3//P3//f7//v7//v//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + ///////4D/////////AH////////8Af////////wB/////////AH////////8Af////////wB/////// + //AH////////8Af////////wB/////////AH////////8AfwP//////wB8Af//+Af/AHgB///wA/8AcA + H///AB/wBgAf//8AD/AGAB///wAH8AYAH///AAPwBAAf//8AA/AEAD///wAD8AQAP///AAPwBAB///+A + A/AEAP///8AD4AAA////4AcAAAH////wDgAAAf/////8AAAH//////gAAAf/////4AAAAf/////gAAAA + /f//+AAAAAAAD//AAAAAAAAH/4AAAAAAAAf/gAAAAAAAB/+AAAAAAAAH/4AAAAAAAAf/gAAAAAAAB/+A + AAAAAAAP/4AAAAAAAB//wAAAAABAf/4HwAAAAYAf8APAAAADgA/gA+AAAAMAA8AD8AAABwADgAP8AAAf + AAOAA/4AAB8AA4ADAAAAAQADgAIAcA4AgAOABgBwDgBAA4AMAGAMADADwDwAYAwAOAfg+ABgBAAeH//4 + AEAEAB////gAwAYAH///+ADABgAf///4AcAGAB////gBwAcAH///+APAB4A////8B+AHwH//////4A// + ///////gD/////////Af//////////////8= + + + \ No newline at end of file diff --git a/Project/Panels/ProjectExplorerPanel.cs b/Project/Panels/ProjectExplorerPanel.cs index 0623f11..bdadc87 100644 --- a/Project/Panels/ProjectExplorerPanel.cs +++ b/Project/Panels/ProjectExplorerPanel.cs @@ -415,6 +415,10 @@ namespace CodeWalker.Project.Panels n = boundsnode.Nodes.Add("Edit Polygon"); n.Name = "EditPoly"; n.Tag = b; //this tag should get updated with the selected poly! + + n = boundsnode.Nodes.Add("Edit Vertex"); + n.Name = "EditVertex"; + n.Tag = b; //this tag should get updated with the selected vertex! } } @@ -1186,6 +1190,15 @@ namespace CodeWalker.Project.Panels polynode.Tag = p; return polynode; } + public TreeNode FindCollisionVertexTreeNode(BoundVertex v) + { + if (v == null) return null; + var ybnnode = FindCollisionBoundsTreeNode(v.Owner); + var vertnode = GetChildTreeNode(ybnnode, "EditVertex"); + if (vertnode == null) return null; + vertnode.Tag = v; + return vertnode; + } public TreeNode FindYndTreeNode(YndFile ynd) { if (ProjectTreeView.Nodes.Count <= 0) return null; @@ -1580,6 +1593,29 @@ namespace CodeWalker.Project.Panels } } } + public void TrySelectCollisionVertexTreeNode(BoundVertex vert) + { + TreeNode tnode = FindCollisionVertexTreeNode(vert); + if (tnode == null) + { + tnode = FindCollisionBoundsTreeNode(vert?.Owner); + } + if (tnode == null) + { + tnode = FindYbnTreeNode(vert?.Owner?.GetRootYbn()); + } + if (tnode != null) + { + if (ProjectTreeView.SelectedNode == tnode) + { + OnItemSelected?.Invoke(vert); + } + else + { + ProjectTreeView.SelectedNode = tnode; + } + } + } public void TrySelectPathNodeTreeNode(YndNode node) { TreeNode tnode = FindPathNodeTreeNode(node); diff --git a/Project/ProjectForm.cs b/Project/ProjectForm.cs index 91c5ffa..4639880 100644 --- a/Project/ProjectForm.cs +++ b/Project/ProjectForm.cs @@ -75,6 +75,7 @@ namespace CodeWalker.Project private YbnFile CurrentYbnFile; private Bounds CurrentCollisionBounds; private BoundPolygon CurrentCollisionPoly; + private BoundVertex CurrentCollisionVertex; private bool renderitems = true; private bool hidegtavmap = false; @@ -413,6 +414,13 @@ namespace CodeWalker.Project (panel) => { panel.SetCollisionPoly(CurrentCollisionPoly); }, //updateFunc (panel) => { return panel.CollisionPoly == CurrentCollisionPoly; }); //findFunc } + public void ShowEditYbnBoundVertexPanel(bool promote) + { + ShowPanel(promote, + () => { return new EditYbnBoundVertexPanel(this); }, //createFunc + (panel) => { panel.SetCollisionVertex(CurrentCollisionVertex); }, //updateFunc + (panel) => { return panel.CollisionVertex == CurrentCollisionVertex; }); //findFunc + } public void ShowEditYndPanel(bool promote) { ShowPanel(promote, @@ -596,6 +604,10 @@ namespace CodeWalker.Project { ShowEditYtypPanel(promote); } + else if (CurrentCollisionVertex != null) + { + ShowEditYbnBoundVertexPanel(promote); + } else if (CurrentCollisionPoly != null) { ShowEditYbnBoundPolyPanel(promote); @@ -707,6 +719,7 @@ namespace CodeWalker.Project CurrentYbnFile = item as YbnFile; CurrentCollisionBounds = item as Bounds; CurrentCollisionPoly = item as BoundPolygon; + CurrentCollisionVertex = item as BoundVertex; CurrentYndFile = item as YndFile; CurrentPathNode = item as YndNode; CurrentYnvFile = item as YnvFile; @@ -788,6 +801,10 @@ namespace CodeWalker.Project { CurrentYtypFile = CurrentEntity?.MloParent?.Archetype?.Ytyp ?? CurrentArchetype?.Ytyp; } + if (CurrentCollisionVertex != null) + { + CurrentCollisionBounds = CurrentCollisionVertex.Owner; + } if (CurrentCollisionPoly != null) { CurrentCollisionBounds = CurrentCollisionPoly.Owner; @@ -3023,7 +3040,11 @@ namespace CodeWalker.Project } CurrentYbnFile = ybn; RefreshUI(); - if (CurrentCollisionPoly != null) + if (CurrentCollisionVertex != null) + { + ProjectExplorer?.TrySelectCollisionVertexTreeNode(CurrentCollisionVertex); + } + else if (CurrentCollisionPoly != null) { ProjectExplorer?.TrySelectCollisionPolyTreeNode(CurrentCollisionPoly); } @@ -3200,6 +3221,75 @@ namespace CodeWalker.Project return true; } + public void AddCollisionVertexToProject() + { + try + { + if (CurrentCollisionVertex == null) return; + + CurrentYbnFile = CurrentCollisionVertex.Owner?.GetRootYbn(); + if (CurrentYbnFile == null) + { + MessageBox.Show("Sorry, only YBN collisions can currently be added to the project. Embedded collisions TODO!"); + return; + } + + if (!YbnExistsInProject(CurrentYbnFile)) + { + var v = CurrentCollisionVertex; + CurrentYbnFile.HasChanged = true; + AddYbnToProject(CurrentYbnFile); + + CurrentCollisionVertex = v; //bug fix for some reason the treeview selects the project node here. + CurrentCollisionBounds = v.Owner; + CurrentYbnFile = v.Owner?.GetRootYbn(); + ProjectExplorer?.TrySelectCollisionVertexTreeNode(v); + } + } + catch + { } + } + public bool DeleteCollisionVertex() + { + if (CurrentCollisionBounds == null) return false; + if (CurrentCollisionVertex == null) return false; + if (CurrentYbnFile == null) return false; + if (CurrentCollisionVertex.Owner != CurrentCollisionBounds) return false; + + if (MessageBox.Show("Are you sure you want to delete this collision vertex, and all attached polygons?\n" + CurrentCollisionVertex.ToString() + "\n\nThis operation cannot be undone. Continue?", "Confirm delete", MessageBoxButtons.YesNo) != DialogResult.Yes) + { + return true; + } + + bool res = false; + if (WorldForm != null) + { + lock (WorldForm.RenderSyncRoot) //don't try to do this while rendering... + { + res = CurrentYbnFile.RemoveVertex(CurrentCollisionVertex); + //WorldForm.SelectItem(null, null, null); + } + } + else + { + res = CurrentYbnFile.RemoveVertex(CurrentCollisionVertex); + } + if (!res) + { + MessageBox.Show("Unable to delete the collision vertex. This shouldn't happen!"); + } + + var delv = CurrentCollisionVertex; + + //ProjectExplorer?.RemoveCollisionVertexTreeNode(CurrentCollisionVertex); + ProjectExplorer?.SetYbnHasChanged(CurrentYbnFile, true); + + ClosePanel((EditYbnBoundVertexPanel p) => { return p.Tag == delv; }); + + CurrentCollisionVertex = null; + + return true; + } @@ -6145,8 +6235,9 @@ namespace CodeWalker.Project var ent = sel.EntityDef; var cargen = sel.CarGenerator; var grassbatch = sel.GrassBatch; + var collvert = sel.CollisionVertex; var collpoly = sel.CollisionPoly; - var collbound = sel.CollisionBounds ?? collpoly?.Owner; + var collbound = sel.CollisionBounds ?? collpoly?.Owner ?? collvert.Owner; var pathnode = sel.PathNode; var pathlink = sel.PathLink; var navpoly = sel.NavPoly; @@ -6205,11 +6296,15 @@ namespace CodeWalker.Project } else if (YbnExistsInProject(ybn)) { - if (collpoly != CurrentCollisionPoly) + if ((collvert != null) && (collvert != CurrentCollisionVertex)) + { + ProjectExplorer?.TrySelectCollisionVertexTreeNode(collvert); + } + else if ((collpoly != null) && (collpoly != CurrentCollisionPoly)) { ProjectExplorer?.TrySelectCollisionPolyTreeNode(collpoly); } - if (collbound != CurrentCollisionBounds) + else if (collbound != CurrentCollisionBounds) { ProjectExplorer?.TrySelectCollisionBoundsTreeNode(collbound); } @@ -6278,6 +6373,7 @@ namespace CodeWalker.Project CurrentCarGen = cargen; CurrentGrassBatch = grassbatch; CurrentYbnFile = ybn; + CurrentCollisionVertex = collvert; CurrentCollisionPoly = collpoly; CurrentCollisionBounds = collbound; CurrentYndFile = ynd; @@ -6330,6 +6426,10 @@ namespace CodeWalker.Project { OnWorldCarGenModified(sel.CarGenerator); } + else if (sel.CollisionVertex != null) + { + OnWorldCollisionVertexModified(sel.CollisionVertex); + } else if (sel.CollisionPoly != null) { OnWorldCollisionPolyModified(sel.CollisionPoly); @@ -6475,6 +6575,45 @@ namespace CodeWalker.Project } } + } + private void OnWorldCollisionVertexModified(BoundVertex vert) + { + var ybn = vert?.Owner?.GetRootYbn(); + if (ybn == null) return; + + CurrentYbnFile = ybn; + + if (CurrentProjectFile == null) + { + NewProject(); + } + + if (!YbnExistsInProject(ybn)) + { + ybn.HasChanged = true; + vert.Owner.HasChanged = true; + AddYbnToProject(ybn); + ProjectExplorer?.TrySelectCollisionVertexTreeNode(vert); + } + + if (vert != CurrentCollisionVertex) + { + CurrentCollisionVertex = vert; + ProjectExplorer?.TrySelectCollisionVertexTreeNode(vert); + } + + if (vert == CurrentCollisionVertex) + { + ShowEditYbnBoundVertexPanel(false); + + //////UpdateCollisionVertexTreeNode(poly); + + if (ybn != null) + { + SetYbnHasChanged(true); + } + } + } private void OnWorldCollisionPolyModified(BoundPolygon poly) { diff --git a/Project/UndoStep.cs b/Project/UndoStep.cs index fc2aee9..4d0a744 100644 --- a/Project/UndoStep.cs +++ b/Project/UndoStep.cs @@ -103,6 +103,10 @@ namespace CodeWalker.Project { bounds[item.CollisionPoly.Owner] = 1; } + if (item.CollisionVertex?.Owner != null) + { + bounds[item.CollisionVertex.Owner] = 1; + } } } @@ -763,6 +767,58 @@ namespace CodeWalker.Project } } + public class CollisionVertexPositionUndoStep : UndoStep + { + public BoundVertex Vertex { get; set; } + public Vector3 StartPosition { get; set; } + public Vector3 EndPosition { get; set; } + + public CollisionVertexPositionUndoStep(BoundVertex vertex, Vector3 startpos, WorldForm wf) + { + Vertex = vertex; + StartPosition = startpos; + EndPosition = vertex?.Position ?? Vector3.Zero; + + UpdateGraphics(wf); + } + + private void Update(WorldForm wf, ref MapSelection sel, Vector3 p) + { + if (Vertex != null) + { + Vertex.Position = p; + } + + if (Vertex != sel.CollisionVertex) wf.SelectCollisionVertex(Vertex); + wf.SetWidgetPosition(p); + + UpdateGraphics(wf); + } + + private void UpdateGraphics(WorldForm wf) + { + if (Vertex?.Owner != null) + { + wf.UpdateCollisionBoundsGraphics(Vertex.Owner); + } + } + + public override void Undo(WorldForm wf, ref MapSelection sel) + { + Update(wf, ref sel, StartPosition); + } + + public override void Redo(WorldForm wf, ref MapSelection sel) + { + Update(wf, ref sel, EndPosition); + } + + public override string ToString() + { + return "Collision Vertex " + (Vertex?.Index.ToString() ?? "") + ": Position"; + } + } + public class PathNodePositionUndoStep : UndoStep diff --git a/Utils/MapUtils.cs b/Utils/MapUtils.cs index a5a7335..45f8638 100644 --- a/Utils/MapUtils.cs +++ b/Utils/MapUtils.cs @@ -188,6 +188,7 @@ namespace CodeWalker public WaterQuad WaterQuad { get; set; } public Bounds CollisionBounds { get; set; } public BoundPolygon CollisionPoly { get; set; } + public BoundVertex CollisionVertex { get; set; } public YnvPoly NavPoly { get; set; } public YnvPoint NavPoint { get; set; } public YnvPortal NavPortal { get; set; } @@ -226,6 +227,7 @@ namespace CodeWalker (WaterQuad != null) || (CollisionBounds != null) || (CollisionPoly != null) || + (CollisionVertex != null) || (NavPoly != null) || (NavPoint != null) || (NavPortal != null) || @@ -264,6 +266,7 @@ namespace CodeWalker || (WaterQuad != mhit.WaterQuad) || (CollisionBounds != mhit.CollisionBounds) || (CollisionPoly != mhit.CollisionPoly) + || (CollisionVertex != mhit.CollisionVertex) || (NavPoly != mhit.NavPoly) || (NavPoint != mhit.NavPoint) || (NavPortal != mhit.NavPortal) @@ -290,6 +293,7 @@ namespace CodeWalker || (WaterQuad != null) || (CollisionBounds != null) || (CollisionPoly != null) + || (CollisionVertex != null) || (NavPoly != null) || (NavPoint != null) || (NavPortal != null) @@ -318,6 +322,7 @@ namespace CodeWalker WaterQuad = null; CollisionBounds = null; CollisionPoly = null; + CollisionVertex = null; NavPoly = null; NavPoint = null; NavPortal = null; @@ -371,6 +376,10 @@ namespace CodeWalker { name = "OccludeModel " + (OccludeModel.Ymap?.Name ?? "") + ": " + OccludeModel.Index.ToString(); } + else if (CollisionVertex != null) + { + name = "Vertex " + CollisionVertex.Index.ToString() + ((CollisionBounds != null) ? (": " + CollisionBounds.GetName()) : string.Empty); + } else if (CollisionPoly != null) { name = "Poly " + CollisionPoly.Index.ToString() + ((CollisionBounds != null) ? (": " + CollisionBounds.GetName()) : string.Empty); @@ -441,6 +450,10 @@ namespace CodeWalker { name = Archetype.Hash.ToString(); } + else if (CollisionVertex != null) + { + name = "Vertex " + CollisionVertex.Index.ToString() + ((CollisionBounds != null) ? (": " + CollisionBounds.GetName()) : string.Empty); + } else if (CollisionPoly != null) { name = "Poly " + CollisionPoly.Index.ToString() + ((CollisionBounds != null) ? (": " + CollisionBounds.GetName()) : string.Empty); @@ -540,6 +553,10 @@ namespace CodeWalker { res = true; } + else if (CollisionVertex != null) + { + res = true; + } else if (CollisionPoly != null) { res = true; @@ -591,6 +608,10 @@ namespace CodeWalker { return CarGenerator.Position; } + else if (CollisionVertex != null) + { + return CollisionVertex.Position; + } else if (CollisionPoly != null) { return CollisionPoly.Position; @@ -646,6 +667,10 @@ namespace CodeWalker { return CarGenerator.Orientation; } + else if (CollisionVertex != null) + { + return Quaternion.Identity; + } else if (CollisionPoly != null) { return Quaternion.Identity; @@ -701,6 +726,10 @@ namespace CodeWalker { return WidgetAxis.Z; } + else if (CollisionVertex != null) + { + return WidgetAxis.None; + } else if (CollisionPoly != null) { return WidgetAxis.XYZ; @@ -756,6 +785,10 @@ namespace CodeWalker { return new Vector3(CarGenerator.CCarGen.perpendicularLength); } + else if (CollisionVertex != null) + { + return Vector3.One; + } else if (CollisionPoly != null) { return Vector3.One; @@ -824,6 +857,10 @@ namespace CodeWalker { PathNode.SetPosition(newpos); } + else if (CollisionVertex != null) + { + CollisionVertex.Position = newpos; + } else if (CollisionPoly != null) { CollisionPoly.Position = newpos; diff --git a/WorldForm.cs b/WorldForm.cs index d065971..8ff4b2b 100644 --- a/WorldForm.cs +++ b/WorldForm.cs @@ -1148,7 +1148,8 @@ namespace CodeWalker break; case MapSelectionMode.Collision: change = change || (LastMouseHit.CollisionBounds != PrevMouseHit.CollisionBounds) - || (LastMouseHit.CollisionPoly != PrevMouseHit.CollisionPoly); + || (LastMouseHit.CollisionPoly != PrevMouseHit.CollisionPoly) + || (LastMouseHit.CollisionVertex != PrevMouseHit.CollisionVertex); break; case MapSelectionMode.NavMesh: change = change || (LastMouseHit.NavPoly != PrevMouseHit.NavPoly) @@ -1252,6 +1253,13 @@ namespace CodeWalker mode = BoundsShaderMode.Sphere; } } + if (CurMouseHit.CollisionVertex != null) + { + var vpos = CurMouseHit.CollisionVertex.Position; + var crpos = camrel + ori.Multiply(vpos); + var vertexSize = 0.1f; + Renderer.RenderSelectionCircle(vpos, vertexSize, 0xFFFFFFFF); + } if (CurMouseHit.CollisionPoly != null) { Renderer.RenderSelectionCollisionPolyOutline(CurMouseHit.CollisionPoly, 0xFFFFFFFF, CurMouseHit.EntityDef); @@ -1554,7 +1562,14 @@ namespace CodeWalker Renderer.WhiteBoxes.Add(wbox); } } - if (selectionItem.CollisionPoly != null) + if (selectionItem.CollisionVertex != null) + { + var vpos = selectionItem.CollisionVertex.Position; + var crpos = camrel + ori.Multiply(vpos); + var vertexSize = 0.1f; + Renderer.RenderSelectionCircle(vpos, vertexSize, cgrn); + } + else if (selectionItem.CollisionPoly != null) { Renderer.RenderSelectionCollisionPolyOutline(selectionItem.CollisionPoly, cgrn, selectionItem.EntityDef); } @@ -2304,6 +2319,17 @@ namespace CodeWalker CurMouseHit.BBOffset = trans; CurMouseHit.BBOrientation = MouseRayCollision.HitBounds?.Transform.ToQuaternion() ?? Quaternion.Identity; CurMouseHit.AABB = new BoundingBox(MouseRayCollision.HitBounds?.BoxMin ?? Vector3.Zero, MouseRayCollision.HitBounds?.BoxMax ?? Vector3.Zero); + + float vertexDist = 0.1f; + if ((MouseRayCollision.HitVertex.Distance < vertexDist) && (MouseRayCollision.HitBounds is BoundGeometry bgeom)) + { + CurMouseHit.CollisionVertex = bgeom.GetVertexObject(MouseRayCollision.HitVertex.Index); + } + else + { + CurMouseHit.CollisionVertex = null; + } + } } } @@ -3575,6 +3601,23 @@ namespace CodeWalker SelectItem(ms); } } + public void SelectCollisionVertex(BoundVertex v) + { + if (v == null) + { + SelectItem(null); + } + else + { + + MapSelection ms = new MapSelection(); + ms.CollisionVertex = v; + + //ms.AABB = new BoundingBox(p.BoundingBoxMin, p.BoundingBoxMax); + + SelectItem(ms); + } + } public void SelectNavPoly(YnvPoly poly) { if (poly == null) @@ -4836,6 +4879,7 @@ namespace CodeWalker var cargen = SelectedItem.CarGenerator; var bounds = SelectedItem.CollisionBounds; var boundpoly = SelectedItem.CollisionPoly; + var boundvertex = SelectedItem.CollisionVertex; var pathnode = SelectedItem.PathNode; var navpoly = SelectedItem.NavPoly; var navpoint = SelectedItem.NavPoint; @@ -4883,6 +4927,13 @@ namespace CodeWalker case WidgetMode.Scale: s = new CarGenScaleUndoStep(cargen, UndoStartScale); break; } } + else if (boundvertex != null) + { + switch (tw.Mode) + { + case WidgetMode.Position: s = new CollisionVertexPositionUndoStep(boundvertex, UndoStartPosition, this); break; + } + } else if (boundpoly != null) { switch (tw.Mode)