Collisions editing progress

This commit is contained in:
dexy 2020-01-07 20:51:53 +11:00
parent edba2cfae9
commit 3c60801e84
12 changed files with 1113 additions and 7 deletions

View File

@ -73,6 +73,10 @@ namespace CodeWalker.GameFiles
{
return false;
}
public bool RemoveVertex(BoundVertex v)
{
return false;
}
}
}

View File

@ -859,6 +859,9 @@ namespace CodeWalker.GameFiles
private ResourceSystemStructBlock<BoundMaterialColour> MaterialColoursBlock = null;
private ResourceSystemStructBlock<byte> PolygonMaterialIndicesBlock = null;
private BoundVertex[] VertexObjects = null; //for use by the editor, created as needed by GetVertexObject()
/// <summary>
/// Reads the data-block from a stream.
/// </summary>
@ -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;
}
}
/// <summary>
@ -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; }

View File

@ -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;

View File

@ -152,6 +152,12 @@
<Compile Include="Project\Panels\EditYbnBoundsPanel.Designer.cs">
<DependentUpon>EditYbnBoundsPanel.cs</DependentUpon>
</Compile>
<Compile Include="Project\Panels\EditYbnBoundVertexPanel.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Project\Panels\EditYbnBoundVertexPanel.Designer.cs">
<DependentUpon>EditYbnBoundVertexPanel.cs</DependentUpon>
</Compile>
<Compile Include="Project\Panels\EditYbnPanel.cs">
<SubType>Form</SubType>
</Compile>
@ -645,6 +651,9 @@
<EmbeddedResource Include="Project\Panels\EditYbnBoundsPanel.resx">
<DependentUpon>EditYbnBoundsPanel.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Project\Panels\EditYbnBoundVertexPanel.resx">
<DependentUpon>EditYbnBoundVertexPanel.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Project\Panels\EditYbnPanel.resx">
<DependentUpon>EditYbnPanel.cs</DependentUpon>
</EmbeddedResource>

View File

@ -0,0 +1,128 @@
namespace CodeWalker.Project.Panels
{
partial class EditYbnBoundVertexPanel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,409 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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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=
</value>
</data>
</root>

View File

@ -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);

View File

@ -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)
{

View File

@ -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

View File

@ -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;

View File

@ -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)