1
0
mirror of synced 2024-11-12 02:00:50 +01:00

Add option to smooth normals between multiple meshes

This commit is contained in:
KillzXGaming 2019-06-05 22:02:35 -04:00
parent d8dbefa3af
commit 04c43b8bd5
17 changed files with 458 additions and 3 deletions

Binary file not shown.

View File

@ -75,8 +75,9 @@ namespace Bfres.Structs
ContextMenuStrip.Items.Add(new ToolStripMenuItem("Transform", null, TransformToolAction, Keys.Control | Keys.T));
ContextMenuStrip.Items.Add(new ToolStripMenuItem("Calculate Tangents/Bitangents", null, CalcTansBitansAllShapesAction, Keys.Control | Keys.C));
ContextMenuStrip.Items.Add(new ToolStripMenuItem("Normals", null,
new ToolStripMenuItem("Smooth", null, SmoothNormalsAction),
new ToolStripMenuItem("Recalculate", null, RecalculateNormalsAction)
new ToolStripMenuItem("Smooth (Multiple Meshes)", null, MultiMeshSmoothNormals),
new ToolStripMenuItem("Smooth", null, SmoothNormalsAction),
new ToolStripMenuItem("Recalculate", null, RecalculateNormalsAction)
));
ContextMenuStrip.Items.Add(new ToolStripMenuItem("UVs", null,
@ -181,6 +182,17 @@ namespace Bfres.Structs
UpdateVertexData();
}
public FSHP GetShape(string Name)
{
for (int fshp = 0; fshp < shapes.Count; fshp++)
{
if (shapes[fshp].Text == Name)
return shapes[fshp];
}
return null;
}
public void FlipUvsVertical()
{
foreach (var shape in shapes)
@ -212,6 +224,26 @@ namespace Bfres.Structs
UpdateVertexData();
}
private void MultiMeshSmoothNormals(object sender, EventArgs args)
{
SmoothNormalsMultiMeshForm form = new SmoothNormalsMultiMeshForm();
form.LoadMeshes(GetModelList());
if (form.ShowDialog() == DialogResult.OK)
{
var SelectedMeshes = form.GetSelectedMeshes();
Cursor.Current = Cursors.WaitCursor;
STGenericObject.SmoothNormals(SelectedMeshes);
Cursor.Current = Cursors.Default;
foreach (var shp in shapes)
shp.SaveVertexBuffer(GetResFileU() != null);
UpdateVertexData();
}
}
private void BatchAttributeEdit()
{
AttributeEditor editor = new AttributeEditor();

View File

@ -181,6 +181,7 @@ namespace Bfres.Structs
ContextMenuStrip.Items.Add(uvMenu);
ToolStripMenuItem normalsMenu = new ToolStripMenuItem("Normals");
normalsMenu.DropDownItems.Add(new ToolStripMenuItem("Smooth (Multiple Meshes)", null, MultiMeshSmoothNormals));
normalsMenu.DropDownItems.Add(new ToolStripMenuItem("Smooth", null, SmoothNormals));
normalsMenu.DropDownItems.Add(new ToolStripMenuItem("Invert", null, InvertNormals));
normalsMenu.DropDownItems.Add(new ToolStripMenuItem("Recalculate", null, RecalculateNormals));
@ -287,6 +288,23 @@ namespace Bfres.Structs
UpdateVertexData();
}
private void MultiMeshSmoothNormals(object sender, EventArgs args)
{
SmoothNormalsMultiMeshForm form = new SmoothNormalsMultiMeshForm();
form.LoadMeshes(GetParentModel().GetModelList());
if (form.ShowDialog() == DialogResult.OK)
{
var SelectedMeshes = form.GetSelectedMeshes();
Cursor.Current = Cursors.WaitCursor;
SmoothNormals(SelectedMeshes);
Cursor.Current = Cursors.Default;
SaveVertexBuffer(GetResFileU() != null);
UpdateVertexData();
}
}
private void UVUnwrapPosition(object sender, EventArgs args)
{
Cursor.Current = Cursors.WaitCursor;

View File

@ -0,0 +1,103 @@
namespace FirstPlugin
{
partial class SmoothNormalsMultiMeshForm
{
/// <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()
{
this.components = new System.ComponentModel.Container();
this.treeViewCustom1 = new Switch_Toolbox.Library.TreeViewCustom();
this.stButton1 = new Switch_Toolbox.Library.Forms.STButton();
this.stButton2 = new Switch_Toolbox.Library.Forms.STButton();
this.contentContainer.SuspendLayout();
this.SuspendLayout();
//
// contentContainer
//
this.contentContainer.Controls.Add(this.stButton2);
this.contentContainer.Controls.Add(this.stButton1);
this.contentContainer.Controls.Add(this.treeViewCustom1);
this.contentContainer.Controls.SetChildIndex(this.treeViewCustom1, 0);
this.contentContainer.Controls.SetChildIndex(this.stButton1, 0);
this.contentContainer.Controls.SetChildIndex(this.stButton2, 0);
//
// treeViewCustom1
//
this.treeViewCustom1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.treeViewCustom1.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.treeViewCustom1.CheckBoxes = true;
this.treeViewCustom1.ImageIndex = 0;
this.treeViewCustom1.Location = new System.Drawing.Point(3, 27);
this.treeViewCustom1.Name = "treeViewCustom1";
this.treeViewCustom1.SelectedImageIndex = 0;
this.treeViewCustom1.Size = new System.Drawing.Size(531, 328);
this.treeViewCustom1.TabIndex = 11;
this.treeViewCustom1.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.treeViewCustom1_AfterCheck);
//
// stButton1
//
this.stButton1.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.stButton1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.stButton1.Location = new System.Drawing.Point(459, 361);
this.stButton1.Name = "stButton1";
this.stButton1.Size = new System.Drawing.Size(75, 23);
this.stButton1.TabIndex = 12;
this.stButton1.Text = "Cancel";
this.stButton1.UseVisualStyleBackColor = false;
//
// stButton2
//
this.stButton2.DialogResult = System.Windows.Forms.DialogResult.OK;
this.stButton2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.stButton2.Location = new System.Drawing.Point(378, 361);
this.stButton2.Name = "stButton2";
this.stButton2.Size = new System.Drawing.Size(75, 23);
this.stButton2.TabIndex = 13;
this.stButton2.Text = "Ok";
this.stButton2.UseVisualStyleBackColor = false;
this.stButton2.Click += new System.EventHandler(this.stButton2_Click);
//
// SmoothNormalsMultiMeshForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(549, 398);
this.Name = "SmoothNormalsMultiMeshForm";
this.Text = "Smooth Normals : Select Meshes";
this.contentContainer.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private Switch_Toolbox.Library.TreeViewCustom treeViewCustom1;
private Switch_Toolbox.Library.Forms.STButton stButton2;
private Switch_Toolbox.Library.Forms.STButton stButton1;
}
}

View File

@ -0,0 +1,80 @@
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;
using Switch_Toolbox.Library.Forms;
using Switch_Toolbox.Library;
using Bfres.Structs;
namespace FirstPlugin
{
public partial class SmoothNormalsMultiMeshForm : STForm
{
public SmoothNormalsMultiMeshForm()
{
InitializeComponent();
}
public void LoadMeshes(List<FMDL> Models)
{
treeViewCustom1.BeginUpdate();
for (int fmdl = 0; fmdl < Models.Count; fmdl++)
{
treeViewCustom1.Nodes.Add(new TreeNode(Models[fmdl].Text)
{
Tag = Models[fmdl],
ImageKey = "model",
SelectedImageKey = "model",
});
for (int fshp = 0; fshp < Models[fmdl].shapes.Count; fshp++)
{
treeViewCustom1.Nodes[fmdl].Nodes.Add(new TreeNode(Models[fmdl].shapes[fshp].Text)
{
ImageKey = "mesh",
SelectedImageKey = "mesh",
});
}
}
treeViewCustom1.EndUpdate();
}
public List<STGenericObject> GetSelectedMeshes()
{
List<STGenericObject> Meshes = new List<STGenericObject>();
foreach (TreeNode model in treeViewCustom1.Nodes)
{
foreach (TreeNode shape in model.Nodes)
{
if (shape.Checked)
Meshes.Add(((FMDL)model.Tag).GetShape(shape.Text));
}
}
return Meshes;
}
private void stButton2_Click(object sender, EventArgs e)
{
if (GetSelectedMeshes().Count == 0)
{
DialogResult = DialogResult.None;
MessageBox.Show("Make sure there is atleast one mesh that is checked!");
}
}
private void treeViewCustom1_AfterCheck(object sender, TreeViewEventArgs e)
{
bool HasChildren = e.Node.Nodes.Count > 0;
if (HasChildren)
{
foreach (TreeNode child in e.Node.Nodes)
child.Checked = e.Node.Checked;
}
}
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -279,6 +279,12 @@
<Compile Include="GUI\BFRES\ParamAnim\ParamPatternMaterialEditor.Designer.cs">
<DependentUpon>ParamPatternMaterialEditor.cs</DependentUpon>
</Compile>
<Compile Include="GUI\BFRES\SmoothNormalsMultiMeshForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="GUI\BFRES\SmoothNormalsMultiMeshForm.Designer.cs">
<DependentUpon>SmoothNormalsMultiMeshForm.cs</DependentUpon>
</Compile>
<Compile Include="YAML\YamlAamp.cs" />
<Compile Include="GUI\BCRES\BfresEditor.cs">
<SubType>UserControl</SubType>
@ -1030,6 +1036,9 @@
<EmbeddedResource Include="GUI\BFRES\Shape\BfresShapeEditor.resx">
<DependentUpon>BfresShapeEditor.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="GUI\BFRES\SmoothNormalsMultiMeshForm.resx">
<DependentUpon>SmoothNormalsMultiMeshForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="GUI\BFRES\SubFileEditor.resx">
<DependentUpon>SubFileEditor.cs</DependentUpon>
</EmbeddedResource>

View File

@ -1 +1 @@
e402c1ea3b5c6e74ab734c29926ae6969018fbfc
3779dc3a197c1f391b60c356a4d2114a1d71e61a

View File

@ -332,3 +332,4 @@ C:\Users\Nathan\Documents\GitHub\SwitchToolboxV1\Switch_FileFormatsMain\obj\Rele
C:\Users\Nathan\Documents\GitHub\SwitchToolboxV1\Switch_FileFormatsMain\obj\Release\FirstPlugin.Forms.OdysseyCostumeSelector.resources
C:\Users\Nathan\Documents\GitHub\SwitchToolboxV1\Switch_FileFormatsMain\obj\Release\FirstPlugin.Forms.MaterialPresetDialog.resources
C:\Users\Nathan\Documents\GitHub\SwitchToolboxV1\Switch_FileFormatsMain\obj\Release\FirstPlugin.Forms.ParamPatternMaterialEditor.resources
C:\Users\Nathan\Documents\GitHub\SwitchToolboxV1\Switch_FileFormatsMain\obj\Release\FirstPlugin.SmoothNormalsMultiMeshForm.resources

View File

@ -327,6 +327,98 @@ namespace Switch_Toolbox.Library
}
}
public static void SmoothNormals(List<STGenericObject> Shapes, int DisplayLODIndex = 0)
{
if (Shapes.Count == 0)
return;
//List of duplicate vertices from multiple shapes
//We will normalize each vertex with the same normal value to prevent seams
List<Vertex> DuplicateVerts = new List<Vertex>();
List<Vector3> NotDuplicateVerts = new List<Vector3>();
for (int s = 0; s < Shapes.Count; s++)
{
if (Shapes[s].vertices.Count < 3)
continue;
Vector3[] normals = new Vector3[Shapes[s].vertices.Count];
List<int> f = Shapes[s].lodMeshes[DisplayLODIndex].getDisplayFace();
for (int v = 0; v < Shapes[s].lodMeshes[DisplayLODIndex].displayFaceSize; v += 3)
{
Vertex v1 = Shapes[s].vertices[f[v]];
Vertex v2 = Shapes[s].vertices[f[v + 1]];
Vertex v3 = Shapes[s].vertices[f[v + 2]];
Vector3 nrm = Shapes[s].CalculateNormal(v1, v2, v3);
if (NotDuplicateVerts.Contains(v1.pos)) DuplicateVerts.Add(v1); else NotDuplicateVerts.Add(v1.pos);
if (NotDuplicateVerts.Contains(v2.pos)) DuplicateVerts.Add(v2); else NotDuplicateVerts.Add(v2.pos);
if (NotDuplicateVerts.Contains(v3.pos)) DuplicateVerts.Add(v3); else NotDuplicateVerts.Add(v3.pos);
normals[f[v + 0]] += nrm;
normals[f[v + 1]] += nrm;
normals[f[v + 2]] += nrm;
}
for (int n = 0; n < normals.Length; n++)
{
Shapes[s].vertices[n].nrm = normals[n].Normalized();
}
}
//Smooth normals normally
for (int s = 0; s < Shapes.Count; s++)
{
// Compare each vertex with all the remaining vertices. This might skip some.
for (int i = 0; i < Shapes[s].vertices.Count; i++)
{
Vertex v = Shapes[s].vertices[i];
for (int j = i + 1; j < Shapes[s].vertices.Count; j++)
{
Vertex v2 = Shapes[s].vertices[j];
if (v == v2)
continue;
float dis = (float)Math.Sqrt(Math.Pow(v.pos.X - v2.pos.X, 2) + Math.Pow(v.pos.Y - v2.pos.Y, 2) + Math.Pow(v.pos.Z - v2.pos.Z, 2));
if (dis <= 0f) // Extra smooth
{
Vector3 nn = ((v2.nrm + v.nrm) / 2).Normalized();
v.nrm = nn;
v2.nrm = nn;
}
}
}
}
//Now do the same but for fixing duplicate vertices
for (int s = 0; s < Shapes.Count; s++)
{
//Smooth duplicate normals
for (int i = 0; i < Shapes[s].vertices.Count; i++)
{
Vertex v = Shapes[s].vertices[i];
for (int j = i + 1; j < DuplicateVerts.Count; j++)
{
Vertex v2 = DuplicateVerts[j];
if (v.pos == v2.pos)
{
float dis = (float)Math.Sqrt(Math.Pow(v.pos.X - v2.pos.X, 2) + Math.Pow(v.pos.Y - v2.pos.Y, 2) + Math.Pow(v.pos.Z - v2.pos.Z, 2));
if (dis <= 0f) // Extra smooth
{
Vector3 nn = ((v2.nrm + v.nrm) / 2).Normalized();
v.nrm = nn;
v2.nrm = nn;
}
}
}
}
}
}
public void SmoothNormals()
{
if (vertices.Count < 3)