parent
c4d442ebd7
commit
6a7ad27f73
17 changed files with 1750 additions and 994 deletions
@ -1,129 +0,0 @@ |
|||||||
using System.Security.Cryptography; |
|
||||||
using System.Text; |
|
||||||
using SharpDX; |
|
||||||
using SparkClient.Model.Enums; |
|
||||||
|
|
||||||
namespace SparkClient.Model.Entity; |
|
||||||
/// <summary> |
|
||||||
/// 三角形 |
|
||||||
/// </summary> |
|
||||||
public class Viewport3DTriangleEntity |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// 点1 |
|
||||||
/// </summary> |
|
||||||
public Vector3 Point1 { get; set; } |
|
||||||
/// <summary> |
|
||||||
/// 点2 |
|
||||||
/// </summary> |
|
||||||
public Vector3 Point2 { get; set; } |
|
||||||
/// <summary> |
|
||||||
/// 点3 |
|
||||||
/// </summary> |
|
||||||
public Vector3 Point3 { get; set; } |
|
||||||
/// <summary> |
|
||||||
/// 三角形代码[生成] |
|
||||||
/// 按顺序:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 拼接后使用生成大写16位md5 |
|
||||||
/// </summary> |
|
||||||
public String TriangleCode { get; set; } |
|
||||||
/// <summary> |
|
||||||
/// 面代码 |
|
||||||
/// 由多个三角形组成的面的代码:entity1、entity2组成了一个正方形,那么他俩的TriangleCode属性值一致 |
|
||||||
/// </summary> |
|
||||||
public String PlaneCode { get; set; } |
|
||||||
/// <summary> |
|
||||||
/// 面类型 |
|
||||||
/// 比如这个面时钻石的腰部分,那么type统一,当面类型为err时则该面是一个异常面 |
|
||||||
/// 可选值:PlaneType |
|
||||||
/// </summary> |
|
||||||
public PlaneType PlaneType { get; set; } |
|
||||||
|
|
||||||
public static Viewport3DTriangleEntity CreateByString(string str) |
|
||||||
{ |
|
||||||
if (string.IsNullOrWhiteSpace(str)) |
|
||||||
{ |
|
||||||
throw new ArgumentException("Input string cannot be null or empty"); |
|
||||||
} |
|
||||||
|
|
||||||
// 分割为四段 |
|
||||||
var parts = str.Split('|'); |
|
||||||
if (parts.Length != 4) |
|
||||||
{ |
|
||||||
throw new ArgumentException("Input string format is incorrect. Expected 4 parts separated by '|'."); |
|
||||||
} |
|
||||||
|
|
||||||
// 解析第一段:坐标 |
|
||||||
var coordinates = parts[0].Split(';'); |
|
||||||
if (coordinates.Length != 3) |
|
||||||
{ |
|
||||||
throw new ArgumentException("Coordinates part must contain exactly 3 points separated by ';'."); |
|
||||||
} |
|
||||||
|
|
||||||
var points = coordinates.Select(ParseVector3).ToArray(); |
|
||||||
|
|
||||||
// 解析第二段:TriangleCode [内部标记] |
|
||||||
|
|
||||||
// 解析第三段:PlaneCode |
|
||||||
string planeCode = parts[2]; |
|
||||||
if (string.IsNullOrWhiteSpace(planeCode)) |
|
||||||
{ |
|
||||||
throw new ArgumentException("PlaneCode cannot be null or empty"); |
|
||||||
} |
|
||||||
|
|
||||||
// 解析第四段:PlaneType |
|
||||||
if (!int.TryParse(parts[3], out int planeTypeValue) || !Enum.IsDefined(typeof(PlaneType), planeTypeValue)) |
|
||||||
{ |
|
||||||
throw new ArgumentException("Invalid PlaneType value"); |
|
||||||
} |
|
||||||
PlaneType planeType = (PlaneType)planeTypeValue; |
|
||||||
|
|
||||||
// 生成 TriangleCode 格式:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 生成 MD5 |
|
||||||
string concatenatedPoints = string.Join(";", points.Select(p => $"{p.X},{p.Y},{p.Z}")); |
|
||||||
string generatedTriangleCode = GenerateMD5Hash(concatenatedPoints); |
|
||||||
|
|
||||||
return new Viewport3DTriangleEntity |
|
||||||
{ |
|
||||||
Point1 = points[0], |
|
||||||
Point2 = points[1], |
|
||||||
Point3 = points[2], |
|
||||||
TriangleCode = generatedTriangleCode, |
|
||||||
PlaneCode = planeCode, |
|
||||||
PlaneType = planeType |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// public static Viewport3DTriangleEntity CreateByJsonStr(string str) |
|
||||||
// { |
|
||||||
// |
|
||||||
// } |
|
||||||
|
|
||||||
private static Vector3 ParseVector3(string coordinate) |
|
||||||
{ |
|
||||||
var values = coordinate.Split(','); |
|
||||||
if (values.Length != 3) |
|
||||||
{ |
|
||||||
throw new ArgumentException("Each coordinate must contain exactly 3 values separated by ','"); |
|
||||||
} |
|
||||||
|
|
||||||
if (!float.TryParse(values[0], out float x) || |
|
||||||
!float.TryParse(values[1], out float y) || |
|
||||||
!float.TryParse(values[2], out float z)) |
|
||||||
{ |
|
||||||
throw new ArgumentException("Coordinate values must be valid floating-point numbers"); |
|
||||||
} |
|
||||||
|
|
||||||
return new Vector3(x, y, z); |
|
||||||
} |
|
||||||
|
|
||||||
public static string GenerateMD5Hash(string input) |
|
||||||
{ |
|
||||||
using (var md5 = MD5.Create()) |
|
||||||
{ |
|
||||||
var inputBytes = Encoding.UTF8.GetBytes(input); |
|
||||||
var hashBytes = md5.ComputeHash(inputBytes); |
|
||||||
|
|
||||||
// 转换为大写的16位十六进制字符串 |
|
||||||
return string.Concat(hashBytes.Take(8).Select(b => b.ToString("X2"))); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,825 +0,0 @@ |
|||||||
using System.IO; |
|
||||||
using System.Windows.Media; |
|
||||||
using System.Windows.Media.Media3D; |
|
||||||
using SharpDX.Direct3D11; |
|
||||||
using SparkClient.Model.Entity; |
|
||||||
|
|
||||||
namespace SparkClient.Model.Helper; |
|
||||||
|
|
||||||
using HelixToolkit.Wpf.SharpDX; |
|
||||||
using SharpDX; |
|
||||||
using System.Collections.Generic; |
|
||||||
|
|
||||||
public class Viewport3DHelper |
|
||||||
{ |
|
||||||
// 加载环境贴图 |
|
||||||
// public static Stream stream = |
|
||||||
// File.Open("D:\\WorkSpace\\spark\\HelixToolkitSharpDX-Demo\\morning_racing_circuit_16k.dds", FileMode.Open); |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 生成一个多边形钻石 |
|
||||||
/// </summary> |
|
||||||
/// <param name="viewport">控件</param> |
|
||||||
/// <param name="sides">边数</param> |
|
||||||
public static void GenerateDiamond(Viewport3DX viewport, int sides) |
|
||||||
{ |
|
||||||
// 启用法线生成 |
|
||||||
var meshBuilder = new MeshBuilder(true, false); |
|
||||||
|
|
||||||
//设置抗锯齿 |
|
||||||
viewport.RenderHost.MSAA = MSAALevel.Maximum; |
|
||||||
|
|
||||||
|
|
||||||
double radius = 3.2915; |
|
||||||
double smallRadius = radius / 2; |
|
||||||
double topHeight = 1.67; |
|
||||||
double bottomHeight = 4.43; |
|
||||||
double smallOffset = 1.25; |
|
||||||
|
|
||||||
var largeVertices = new List<Vector3>(); |
|
||||||
var smallVertices = new List<Vector3>(); |
|
||||||
|
|
||||||
var edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
||||||
|
|
||||||
// 生成大八边形顶点 |
|
||||||
for (int i = 0; i < sides; i++) |
|
||||||
{ |
|
||||||
double angle = i * Math.PI / (sides / 2); |
|
||||||
largeVertices.Add(new Vector3((float)(radius * Math.Cos(angle)), (float)topHeight, |
|
||||||
(float)(radius * Math.Sin(angle)))); |
|
||||||
} |
|
||||||
|
|
||||||
// 生成小八边形顶点 |
|
||||||
for (int i = 0; i < sides; i++) |
|
||||||
{ |
|
||||||
double angle = i * Math.PI / (sides / 2); |
|
||||||
smallVertices.Add(new Vector3((float)(smallRadius * Math.Cos(angle)), (float)(topHeight + smallOffset), |
|
||||||
(float)(smallRadius * Math.Sin(angle)))); |
|
||||||
} |
|
||||||
|
|
||||||
var bottomPoint = new Vector3(0, (float)(topHeight - bottomHeight), 0); |
|
||||||
var topPoint = new Vector3(0, (float)topHeight, 0); |
|
||||||
|
|
||||||
// 生成底部面 |
|
||||||
for (int i = 0; i < sides; i++) |
|
||||||
{ |
|
||||||
int nextIndex = (i + 1) % sides; |
|
||||||
|
|
||||||
// 计算法线 |
|
||||||
// Vector3 edge1 = largeVertices[nextIndex] - bottomPoint; |
|
||||||
// Vector3 edge2 = largeVertices[i] - bottomPoint; |
|
||||||
// Vector3 normal = Vector3.Cross(edge1, edge2); |
|
||||||
// normal.Normalize(); |
|
||||||
|
|
||||||
|
|
||||||
List<Vector3> fanPositions2 = new List<Vector3>() |
|
||||||
{ bottomPoint, largeVertices[nextIndex], largeVertices[i] }; |
|
||||||
List<Vector3> fanNormals2 = new List<Vector3>() |
|
||||||
{ new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; |
|
||||||
meshBuilder.AddTriangleFan(fanPositions2, fanNormals2); |
|
||||||
// meshBuilder.AddTriangle(bottomPoint, largeVertices[nextIndex], largeVertices[i]); |
|
||||||
// meshBuilder.AddTriangle(topPoint, largeVertices[i], largeVertices[nextIndex]); |
|
||||||
|
|
||||||
|
|
||||||
// 添加边框线 |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(bottomPoint, largeVertices[nextIndex])); |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[nextIndex], largeVertices[i])); |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[i], bottomPoint)); |
|
||||||
} |
|
||||||
|
|
||||||
// 生成法线并添加小八边形的面 |
|
||||||
var smallNormals = new List<Vector3>(); |
|
||||||
for (int i = 0; i < smallVertices.Count; i++) |
|
||||||
{ |
|
||||||
smallNormals.Add(new Vector3(0, 1, 0)); // 小八边形法线向上 |
|
||||||
} |
|
||||||
|
|
||||||
meshBuilder.AddTriangleFan(smallVertices, smallNormals); |
|
||||||
|
|
||||||
// 生成大八边形和小八边形的连接面 |
|
||||||
for (int i = 0; i < sides; i++) |
|
||||||
{ |
|
||||||
int nextIndex = (i + 1) % sides; |
|
||||||
|
|
||||||
|
|
||||||
// 计算法线 |
|
||||||
// Vector3 edge1 = largeVertices[nextIndex] - largeVertices[i]; |
|
||||||
// Vector3 edge2 = smallVertices[nextIndex] - largeVertices[i]; |
|
||||||
// Vector3 normal1 = Vector3.Cross(edge1, edge2); |
|
||||||
// normal1.Normalize(); |
|
||||||
// |
|
||||||
// Vector3 edge3 = smallVertices[nextIndex] - largeVertices[i]; |
|
||||||
// Vector3 edge4 = smallVertices[i] - largeVertices[i]; |
|
||||||
// Vector3 normal2 = Vector3.Cross(edge3, edge4); |
|
||||||
// normal2.Normalize(); |
|
||||||
|
|
||||||
|
|
||||||
List<Vector3> fanPositions1 = new List<Vector3>() |
|
||||||
{ largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex] }; |
|
||||||
List<Vector3> fanNormals1 = new List<Vector3>() |
|
||||||
{ new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; |
|
||||||
meshBuilder.AddTriangleFan(fanPositions1, fanNormals1); |
|
||||||
|
|
||||||
//meshBuilder.AddTriangle(largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex]); |
|
||||||
|
|
||||||
List<Vector3> fanPositions2 = new List<Vector3>() |
|
||||||
{ largeVertices[i], smallVertices[nextIndex], smallVertices[i] }; |
|
||||||
List<Vector3> fanNormals2 = new List<Vector3>() |
|
||||||
{ new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; |
|
||||||
meshBuilder.AddTriangleFan(fanPositions2, fanNormals2); |
|
||||||
//meshBuilder.AddTriangle(largeVertices[i], smallVertices[nextIndex], smallVertices[i]); |
|
||||||
|
|
||||||
// 添加边框线 |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[i], largeVertices[nextIndex])); |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[nextIndex], smallVertices[nextIndex])); |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(smallVertices[nextIndex], smallVertices[i])); |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(smallVertices[i], largeVertices[i])); |
|
||||||
} |
|
||||||
|
|
||||||
var mesh = meshBuilder.ToMeshGeometry3D(); |
|
||||||
|
|
||||||
|
|
||||||
var material = new PBRMaterial |
|
||||||
{ |
|
||||||
AlbedoColor = new Color4(0.11f, 0.56f, 1f, 0.6f), // 纯白色 |
|
||||||
MetallicFactor = 0.0, |
|
||||||
RoughnessFactor = 0.01, |
|
||||||
ReflectanceFactor = 1.0, |
|
||||||
ClearCoatStrength = 1.0, |
|
||||||
ClearCoatRoughness = 0.0, |
|
||||||
// RenderEnvironmentMap = true, |
|
||||||
// IrradianceMap = TextureModel.Create(stream), // 环境贴图 |
|
||||||
}; |
|
||||||
|
|
||||||
// 设置材质的采样器状态 |
|
||||||
material.SurfaceMapSampler = new SamplerStateDescription |
|
||||||
{ |
|
||||||
Filter = Filter.MinMagMipLinear, |
|
||||||
AddressU = TextureAddressMode.Wrap, |
|
||||||
AddressV = TextureAddressMode.Wrap, |
|
||||||
AddressW = TextureAddressMode.Wrap |
|
||||||
}; |
|
||||||
|
|
||||||
//边框 |
|
||||||
var lineBuilder = new LineBuilder(); |
|
||||||
foreach (var line in edgeLines) |
|
||||||
{ |
|
||||||
lineBuilder.AddLine(line.Item1, line.Item2); |
|
||||||
} |
|
||||||
|
|
||||||
var edgeLinesModel = new LineGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = lineBuilder.ToLineGeometry3D(), |
|
||||||
Color = System.Windows.Media.Colors.Black, |
|
||||||
Thickness = 1.0 |
|
||||||
}; |
|
||||||
|
|
||||||
viewport.Items.Add(edgeLinesModel); |
|
||||||
|
|
||||||
var geometryModel = new MeshGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = mesh, |
|
||||||
Material = material |
|
||||||
}; |
|
||||||
|
|
||||||
viewport.Items.Add(geometryModel); |
|
||||||
|
|
||||||
viewport.ZoomExtents(); |
|
||||||
} |
|
||||||
|
|
||||||
public static void GenerateModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities) |
|
||||||
{ |
|
||||||
var meshBuilder = new MeshBuilder(true, false); |
|
||||||
viewport.RenderHost.MSAA = MSAALevel.Maximum; |
|
||||||
foreach (var entity in entities) |
|
||||||
{ |
|
||||||
List<Vector3> vertices = new List<Vector3>(){entity.Point1, entity.Point2, entity.Point3}; |
|
||||||
meshBuilder.AddPolygon(vertices); |
|
||||||
} |
|
||||||
|
|
||||||
var mesh = meshBuilder.ToMeshGeometry3D(); |
|
||||||
var material = new PBRMaterial |
|
||||||
{ |
|
||||||
AlbedoColor = new Color4(0.11f, 0.56f, 1f, 0.6f), |
|
||||||
MetallicFactor = 0.0, |
|
||||||
RoughnessFactor = 0.01, |
|
||||||
ReflectanceFactor = 1.0, |
|
||||||
ClearCoatStrength = 1.0, |
|
||||||
ClearCoatRoughness = 0.0, |
|
||||||
}; |
|
||||||
|
|
||||||
// 设置材质的采样器状态 |
|
||||||
material.SurfaceMapSampler = new SamplerStateDescription |
|
||||||
{ |
|
||||||
Filter = Filter.MinMagMipLinear, |
|
||||||
AddressU = TextureAddressMode.Wrap, |
|
||||||
AddressV = TextureAddressMode.Wrap, |
|
||||||
AddressW = TextureAddressMode.Wrap |
|
||||||
}; |
|
||||||
var geometryModel = new MeshGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = mesh, |
|
||||||
Material = material |
|
||||||
}; |
|
||||||
viewport.Items.Add(geometryModel); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 传入一个三角形,找到关联面 |
|
||||||
/// </summary> |
|
||||||
/// <param name="viewport">控件</param> |
|
||||||
/// <param name="triangleIndex">三角形顶点</param> |
|
||||||
/// <param name="selColor">选择主体颜色</param> |
|
||||||
/// <param name="linkColor">选择关联颜色(null不渲染)</param> |
|
||||||
/// <param name="borderColor">选择边框颜色(null不渲染)</param> |
|
||||||
/// <returns></returns> |
|
||||||
public static List<GeometryModel3D> HighlightAssociatedFaces(Viewport3DX viewport, |
|
||||||
Tuple<Vector3, Vector3, Vector3> triangleIndex, Color4 selColor, Color4? linkColor = null, |
|
||||||
Color4? borderColor = null) |
|
||||||
{ |
|
||||||
List<GeometryModel3D> result = new List<GeometryModel3D>(); |
|
||||||
//选中平面上的点(实现选中高亮、边框线) |
|
||||||
List<Vector3> selFaceVector = new List<Vector3>(); |
|
||||||
selFaceVector.Add(triangleIndex.Item1); |
|
||||||
selFaceVector.Add(triangleIndex.Item2); |
|
||||||
selFaceVector.Add(triangleIndex.Item3); |
|
||||||
|
|
||||||
foreach (var item in viewport.Items) |
|
||||||
{ |
|
||||||
if (item is MeshGeometryModel3D geometryModel) |
|
||||||
{ |
|
||||||
var geometry = geometryModel.Geometry; |
|
||||||
if (geometry != null) |
|
||||||
{ |
|
||||||
var positions = geometry.Positions; // 顶点列表 |
|
||||||
var indices = geometry.Indices; // 索引列表 |
|
||||||
|
|
||||||
// 确保 Indices 是三角形格式 |
|
||||||
for (int i = 0; i < indices.Count; i += 3) |
|
||||||
{ |
|
||||||
// 获取三角形的三个顶点索引 |
|
||||||
int index0 = indices[i]; |
|
||||||
int index1 = indices[i + 1]; |
|
||||||
int index2 = indices[i + 2]; |
|
||||||
|
|
||||||
// 获取三角形的三个顶点坐标 |
|
||||||
var vertex0 = positions[index0]; |
|
||||||
var vertex1 = positions[index1]; |
|
||||||
var vertex2 = positions[index2]; |
|
||||||
|
|
||||||
List<Vector3> temp = new List<Vector3>() { vertex0, vertex1, vertex2 }; |
|
||||||
|
|
||||||
if (ArePointsOnSamePlane(triangleIndex, temp)) |
|
||||||
{ |
|
||||||
//所有点共面,两种情况,三角形自己,三角形相连在同一个平面 |
|
||||||
if (temp.Count == 3 && temp.Contains(triangleIndex.Item1) && |
|
||||||
temp.Contains(triangleIndex.Item2) && temp.Contains(triangleIndex.Item3)) |
|
||||||
continue; |
|
||||||
foreach (var vector in temp) |
|
||||||
{ |
|
||||||
// if(!selFaceVector.Contains(vector)) |
|
||||||
selFaceVector.Add(vector); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//当前选中的平面 |
|
||||||
var meshBuilder = new MeshBuilder(true, false); |
|
||||||
List<Vector3> fanNormals2 = new List<Vector3>(); |
|
||||||
foreach (var vertex in selFaceVector) |
|
||||||
fanNormals2.Add(new Vector3(0, 1, 0)); |
|
||||||
meshBuilder.AddTriangleFan(selFaceVector, fanNormals2); |
|
||||||
var mesh = meshBuilder.ToMeshGeometry3D(); |
|
||||||
|
|
||||||
var material = new PBRMaterial |
|
||||||
{ |
|
||||||
AlbedoColor = selColor, // 纯白色 |
|
||||||
MetallicFactor = 0, |
|
||||||
RoughnessFactor = 1, |
|
||||||
ReflectanceFactor = 0.5, |
|
||||||
}; |
|
||||||
var geometryModel1 = new MeshGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = mesh, |
|
||||||
Material = material |
|
||||||
}; |
|
||||||
|
|
||||||
//当前选中平面边框线 |
|
||||||
var edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
||||||
var lengths = new List<double>(); // 长度列表 |
|
||||||
var angles = new List<double>(); // 夹角列表 |
|
||||||
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector); |
|
||||||
List<Vector3> sortedVectors = uniqueVectors.ToList(); |
|
||||||
Vector3 center = GetCentroid(sortedVectors); |
|
||||||
// 按极角顺时针排序 |
|
||||||
sortedVectors.Sort((v1, v2) => |
|
||||||
{ |
|
||||||
double angle1 = Math.Atan2(v1.Z - center.Z, v1.X - center.X); // 计算第一个点的角度 |
|
||||||
double angle2 = Math.Atan2(v2.Z - center.Z, v2.X - center.X); // 计算第二个点的角度 |
|
||||||
return angle1.CompareTo(angle2); // 按角度从小到大排序 |
|
||||||
}); |
|
||||||
|
|
||||||
for (int i = 0; i < sortedVectors.Count - 1; i++) |
|
||||||
{ |
|
||||||
var nowItem = sortedVectors[i]; |
|
||||||
var nextItem = sortedVectors[i + 1]; |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(nowItem, nextItem)); |
|
||||||
// 计算当前线段的长度 |
|
||||||
lengths.Add((nextItem - nowItem).Length()); |
|
||||||
} |
|
||||||
|
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(sortedVectors.Last(), sortedVectors.First())); |
|
||||||
lengths.Add((sortedVectors.Last() - sortedVectors.First()).Length()); |
|
||||||
//计算夹角 |
|
||||||
for (int i = 0; i < sortedVectors.Count; i++) |
|
||||||
{ |
|
||||||
int prevIndex = (i - 1 + sortedVectors.Count) % sortedVectors.Count; |
|
||||||
int nextIndex = (i + 1) % sortedVectors.Count; |
|
||||||
|
|
||||||
// 获取三点 |
|
||||||
var p1 = sortedVectors[prevIndex]; |
|
||||||
var p2 = sortedVectors[i]; |
|
||||||
var p3 = sortedVectors[nextIndex]; |
|
||||||
|
|
||||||
// 计算夹角 |
|
||||||
var v1 = p2 - p1; |
|
||||||
var v2 = p3 - p2; |
|
||||||
double angle = AngleBetween(v1, v2); |
|
||||||
angles.Add(angle); // 夹角,单位是度 |
|
||||||
} |
|
||||||
|
|
||||||
if (sortedVectors.Count == lengths.Count && sortedVectors.Count == angles.Count) |
|
||||||
{ |
|
||||||
for (int i = 0; i < sortedVectors.Count; i++) |
|
||||||
{ |
|
||||||
var point = sortedVectors[i]; |
|
||||||
var pointNext = i + 1 >= sortedVectors.Count ? sortedVectors.First() : sortedVectors[i + 1]; |
|
||||||
var lengthText = $"L: {lengths[i]:F2}"; |
|
||||||
var angleText = $"Angle: {angles[i]:F2}°"; |
|
||||||
|
|
||||||
var lengthTextPoint = (pointNext + point) / 2; |
|
||||||
|
|
||||||
// 在合适位置显示文本 |
|
||||||
var text3DL = DisplayText3D(lengthText, |
|
||||||
new Vector3(lengthTextPoint.X, lengthTextPoint.Y + 0.5f, lengthTextPoint.Z)); |
|
||||||
var text3DA = DisplayText3D(angleText, new Vector3(point.X, point.Y + 0.5f, point.Z + 0.5f)); |
|
||||||
viewport.Items.Add(text3DL); |
|
||||||
viewport.Items.Add(text3DA); |
|
||||||
result.Add(text3DL); |
|
||||||
result.Add(text3DA); |
|
||||||
} |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
// 如果列表长度不一致,给出提示信息 |
|
||||||
Console.WriteLine("The lengths or angles list size does not match the sorted vectors list."); |
|
||||||
} |
|
||||||
|
|
||||||
var lineBuilder = new LineBuilder(); |
|
||||||
foreach (var line in edgeLines) |
|
||||||
{ |
|
||||||
lineBuilder.AddLine(line.Item1, line.Item2); |
|
||||||
} |
|
||||||
|
|
||||||
var edgeLinesModel = new LineGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = lineBuilder.ToLineGeometry3D(), |
|
||||||
Color = System.Windows.Media.Colors.Red, |
|
||||||
Thickness = 1.0 |
|
||||||
}; |
|
||||||
|
|
||||||
viewport.Items.Add(edgeLinesModel); |
|
||||||
viewport.Items.Add(geometryModel1); |
|
||||||
|
|
||||||
|
|
||||||
result.Add(geometryModel1); |
|
||||||
result.Add(edgeLinesModel); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 遍历视图中的所有三角形 |
|
||||||
/// </summary> |
|
||||||
/// <param name="viewport"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public static List<Tuple<Vector3, Vector3, Vector3>> ForeachViewPortTriangle(Viewport3DX viewport) |
|
||||||
{ |
|
||||||
List<Tuple<Vector3, Vector3, Vector3>> result = new List<Tuple<Vector3, Vector3, Vector3>>(); |
|
||||||
foreach (var item in viewport.Items) |
|
||||||
{ |
|
||||||
if (item is MeshGeometryModel3D geometryModel) |
|
||||||
{ |
|
||||||
var geometry = geometryModel.Geometry; |
|
||||||
if (geometry != null) |
|
||||||
{ |
|
||||||
var positions = geometry.Positions; // 顶点列表 |
|
||||||
var indices = geometry.Indices; // 索引列表 |
|
||||||
|
|
||||||
// 确保 Indices 是三角形格式 |
|
||||||
for (int i = 0; i < indices.Count; i += 3) |
|
||||||
{ |
|
||||||
// 获取三角形的三个顶点索引 |
|
||||||
int index0 = indices[i]; |
|
||||||
int index1 = indices[i + 1]; |
|
||||||
int index2 = indices[i + 2]; |
|
||||||
|
|
||||||
// 获取三角形的三个顶点坐标 |
|
||||||
var vertex0 = positions[index0]; |
|
||||||
var vertex1 = positions[index1]; |
|
||||||
var vertex2 = positions[index2]; |
|
||||||
result.Add(new Tuple<Vector3, Vector3, Vector3>(vertex0, vertex1, vertex2)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
private static BillboardTextModel3D DisplayText3D(string text, Vector3 position) |
|
||||||
{ |
|
||||||
var billboardTextModel = new BillboardTextModel3D(); |
|
||||||
var billboardText = new BillboardText3D(); |
|
||||||
billboardText.TextInfo.Add(new TextInfo(text, position) |
|
||||||
{ |
|
||||||
Foreground = Color.Green, |
|
||||||
Scale = 0.5f, |
|
||||||
}); |
|
||||||
billboardTextModel.Geometry = billboardText; |
|
||||||
return billboardTextModel; |
|
||||||
} |
|
||||||
|
|
||||||
public static float AngleBetween(Vector3 v1, Vector3 v2) |
|
||||||
{ |
|
||||||
// 计算两个向量的点积 |
|
||||||
float dotProduct = Vector3.Dot(v1, v2); |
|
||||||
|
|
||||||
// 计算每个向量的模长 |
|
||||||
float magnitudeV1 = v1.Length(); |
|
||||||
float magnitudeV2 = v2.Length(); |
|
||||||
|
|
||||||
// 防止除以零错误 |
|
||||||
if (magnitudeV1 == 0 || magnitudeV2 == 0) |
|
||||||
{ |
|
||||||
return 0f; |
|
||||||
} |
|
||||||
|
|
||||||
// 计算夹角的余弦值 |
|
||||||
float cosTheta = dotProduct / (magnitudeV1 * magnitudeV2); |
|
||||||
|
|
||||||
// 限制cosTheta的值范围在 -1 到 1 之间,以防计算机浮点误差 |
|
||||||
cosTheta = Math.Max(-1f, Math.Min(1f, cosTheta)); |
|
||||||
|
|
||||||
// 返回角度,单位是度 |
|
||||||
return (float)(Math.Acos(cosTheta) * (180.0 / Math.PI)); |
|
||||||
} |
|
||||||
|
|
||||||
// 计算点集合的中心点(几何质心) |
|
||||||
private static Vector3 GetCentroid(List<Vector3> vectors) |
|
||||||
{ |
|
||||||
float x = vectors.Sum(v => v.X) / vectors.Count; |
|
||||||
float y = vectors.Sum(v => v.Y) / vectors.Count; |
|
||||||
float z = vectors.Sum(v => v.Z) / vectors.Count; |
|
||||||
return new Vector3(x, y, z); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 判断集合元素是狗共面 |
|
||||||
/// </summary> |
|
||||||
/// <param name="triangle"></param> |
|
||||||
/// <param name="points"></param> |
|
||||||
/// <returns></returns> |
|
||||||
public static bool ArePointsOnSamePlane(Tuple<Vector3, Vector3, Vector3> triangle, List<Vector3> points) |
|
||||||
{ |
|
||||||
// 获取三角形的三个点 |
|
||||||
var P1 = triangle.Item1; |
|
||||||
var P2 = triangle.Item2; |
|
||||||
var P3 = triangle.Item3; |
|
||||||
|
|
||||||
// 计算平面的法线 |
|
||||||
Vector3 V1 = P2 - P1; // 向量P1P2 |
|
||||||
Vector3 V2 = P3 - P1; // 向量P1P3 |
|
||||||
Vector3 normal = Vector3.Cross(V1, V2); // 叉积得到平面法线 |
|
||||||
|
|
||||||
// 对每个点进行检查 |
|
||||||
foreach (var point in points) |
|
||||||
{ |
|
||||||
if (!IsPointInPlane(P1, normal, point)) |
|
||||||
{ |
|
||||||
return false; // 如果有点不在平面上,返回false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return true; // 如果所有点都在平面上,返回true |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// 判断点是否在平面上 |
|
||||||
/// </summary> |
|
||||||
/// <returns></returns> |
|
||||||
private static bool IsPointInPlane(Vector3 planePoint, Vector3 normal, Vector3 point) |
|
||||||
{ |
|
||||||
// 计算点到平面的距离 |
|
||||||
// 点到平面的距离公式:d = (point - planePoint) · normal / |normal| |
|
||||||
float distance = Vector3.Dot(point - planePoint, normal) / normal.Length(); |
|
||||||
|
|
||||||
// 如果距离为0,说明点在平面上 |
|
||||||
return Math.Abs(distance) < 1e-6; // 容忍一个小的误差 |
|
||||||
} |
|
||||||
|
|
||||||
public static void GenerateLightingForModel(Viewport3DX viewport) |
|
||||||
{ |
|
||||||
// 移除现有的灯光 |
|
||||||
List<Light3D> lights = new List<Light3D>(); |
|
||||||
foreach (var item in viewport.Items) |
|
||||||
{ |
|
||||||
if (item is Light3D) |
|
||||||
{ |
|
||||||
lights.Add(item as Light3D); |
|
||||||
} |
|
||||||
} |
|
||||||
lights.ForEach(item => viewport.Items.Remove(item)); |
|
||||||
|
|
||||||
// 获取场景中最大的模型 |
|
||||||
var models = viewport.Items.OfType<MeshGeometryModel3D>(); |
|
||||||
if (!models.Any()) return; |
|
||||||
|
|
||||||
var largestModel = models |
|
||||||
.OrderByDescending(m => GetBoundingBoxVolume(m.Geometry.Bound)) |
|
||||||
.FirstOrDefault(); |
|
||||||
|
|
||||||
if (largestModel == null) return; |
|
||||||
|
|
||||||
// 获取最大模型的边界立方体 |
|
||||||
var boundingBox = largestModel.Geometry.Bound; |
|
||||||
var size = boundingBox.Size; |
|
||||||
var center = boundingBox.Center; |
|
||||||
|
|
||||||
// 计算 Bounding Box 的八个顶点 |
|
||||||
var corners = new List<Point3D> |
|
||||||
{ |
|
||||||
boundingBox.Minimum.ToPoint3D(), // 左下后 |
|
||||||
new Point3D(boundingBox.Maximum.X, boundingBox.Minimum.Y, boundingBox.Minimum.Z), // 右下后 |
|
||||||
new Point3D(boundingBox.Minimum.X, boundingBox.Maximum.Y, boundingBox.Minimum.Z), // 左上后 |
|
||||||
new Point3D(boundingBox.Minimum.X, boundingBox.Minimum.Y, boundingBox.Maximum.Z), // 左下前 |
|
||||||
boundingBox.Maximum.ToPoint3D(), // 右上前 |
|
||||||
new Point3D(boundingBox.Maximum.X, boundingBox.Maximum.Y, boundingBox.Minimum.Z), // 右上后 |
|
||||||
new Point3D(boundingBox.Minimum.X, boundingBox.Maximum.Y, boundingBox.Maximum.Z), // 左上前 |
|
||||||
new Point3D(boundingBox.Maximum.X, boundingBox.Minimum.Y, boundingBox.Maximum.Z) // 右下前 |
|
||||||
}; |
|
||||||
|
|
||||||
// 在每个顶点处添加光源 |
|
||||||
for (int i = 0; i < corners.Count; i++) |
|
||||||
{ |
|
||||||
var corner = corners[i]; |
|
||||||
|
|
||||||
// 设置点光源的颜色和强度,使得八个点的光源具有不同效果 |
|
||||||
var color = i % 2 == 0 ? Colors.LightGoldenrodYellow : Colors.LightSkyBlue; |
|
||||||
|
|
||||||
// 添加点光源 |
|
||||||
var pointLight = new PointLight3D |
|
||||||
{ |
|
||||||
Position = corner, |
|
||||||
Color = color, |
|
||||||
Range = (float)size.Length() * 4 // 光照范围覆盖整个模型 |
|
||||||
}; |
|
||||||
|
|
||||||
viewport.Items.Add(pointLight); |
|
||||||
} |
|
||||||
|
|
||||||
// 添加顶部的多处光源(从上向下照射,模拟太阳光) |
|
||||||
double topLightDistance = size.Y * 1.2; // 适当调整顶部光源的高度 |
|
||||||
var topLightPositions = new List<Point3D> |
|
||||||
{ |
|
||||||
new Point3D(center.X, boundingBox.Maximum.Y + topLightDistance, center.Z), |
|
||||||
new Point3D(boundingBox.Minimum.X - size.X / 3, boundingBox.Maximum.Y + topLightDistance, center.Z), |
|
||||||
new Point3D(boundingBox.Maximum.X + size.X / 3, boundingBox.Maximum.Y + topLightDistance, center.Z) |
|
||||||
}; |
|
||||||
|
|
||||||
foreach (var position in topLightPositions) |
|
||||||
{ |
|
||||||
var topLight = new PointLight3D |
|
||||||
{ |
|
||||||
Position = position, |
|
||||||
Color = Colors.LightGoldenrodYellow, |
|
||||||
Range = (float)size.Length() * 0.5, // 增加光的照射范围 |
|
||||||
Attenuation = new Vector3D(1, 0.1f, 0.05f) // 控制光的衰减,使光在距离内更有效 |
|
||||||
}; |
|
||||||
viewport.Items.Add(topLight); |
|
||||||
} |
|
||||||
// 添加环境光以柔化整体效果并增加亮度 |
|
||||||
var ambientLight = new AmbientLight3D |
|
||||||
{ |
|
||||||
Color = Colors.LightGray |
|
||||||
}; |
|
||||||
viewport.Items.Add(ambientLight); |
|
||||||
|
|
||||||
|
|
||||||
var ambientLigh1t = new AmbientLight3D |
|
||||||
{ |
|
||||||
Color = Colors.Gray // 设置环境光颜色 |
|
||||||
}; |
|
||||||
viewport.Items.Add(ambientLigh1t); |
|
||||||
|
|
||||||
|
|
||||||
// 添加环境光以柔化整体效果并增加亮度 |
|
||||||
// var ambientLight = new AmbientLight3D |
|
||||||
// { |
|
||||||
// Color = Colors.LightGray |
|
||||||
// }; |
|
||||||
// viewport.Items.Add(ambientLight); |
|
||||||
// 添加底部的光源(从底部中心向上照射) |
|
||||||
// var bottomLightPosition = new Point3D(center.X, boundingBox.Minimum.Y - 1.0f, center.Z); // 位于底部中心点下方 |
|
||||||
// var bottomLight = new PointLight3D |
|
||||||
// { |
|
||||||
// Position = bottomLightPosition, |
|
||||||
// Color = Colors.White, // 白色光源 |
|
||||||
// Range = (float)size.Length() * 2 // 设置光照范围 |
|
||||||
// }; |
|
||||||
// viewport.Items.Add(bottomLight); |
|
||||||
} |
|
||||||
|
|
||||||
// 辅助方法:获取边界体积 |
|
||||||
private static double GetBoundingBoxVolume(BoundingBox bound) |
|
||||||
{ |
|
||||||
var size = bound.Size; |
|
||||||
return size.X * size.Y * size.Z; |
|
||||||
} |
|
||||||
|
|
||||||
public static List<Viewport3DTriangleEntity> InitDemo(Viewport3DX viewport) |
|
||||||
{ |
|
||||||
List<Viewport3DTriangleEntity> entities = new List<Viewport3DTriangleEntity>(); |
|
||||||
try |
|
||||||
{ |
|
||||||
using (FileStream fileStream = new FileStream(AppDomain.CurrentDomain.BaseDirectory + @"Resource\DimDemo.txt", FileMode.Open, FileAccess.Read)) |
|
||||||
using (StreamReader reader = new StreamReader(fileStream)) |
|
||||||
{ |
|
||||||
string line; |
|
||||||
while ((line = reader.ReadLine()) != null) |
|
||||||
{ |
|
||||||
if(string.IsNullOrWhiteSpace(line))continue; |
|
||||||
if(line.StartsWith("-- "))continue; |
|
||||||
entities.Add(Viewport3DTriangleEntity.CreateByString(line)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
catch (Exception ex) |
|
||||||
{ |
|
||||||
Console.WriteLine($"读取文件时出错:{ex.Message}"); |
|
||||||
} |
|
||||||
GenerateModelByEntity(viewport, entities); |
|
||||||
GenerateLightingForModel(viewport); |
|
||||||
|
|
||||||
return entities; |
|
||||||
} |
|
||||||
|
|
||||||
public static List<GeometryModel3D> GenerateEmissiveModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities, Color4 emissiveColor) |
|
||||||
{ |
|
||||||
// 创建 MeshBuilder,启用法线生成 |
|
||||||
var meshBuilder = new MeshBuilder(true, false); |
|
||||||
viewport.RenderHost.MSAA = MSAALevel.Maximum; |
|
||||||
|
|
||||||
// 通过实体构建三角形面 |
|
||||||
foreach (var entity in entities) |
|
||||||
{ |
|
||||||
List<Vector3> vertices = new List<Vector3>() { entity.Point1, entity.Point2, entity.Point3 }; |
|
||||||
meshBuilder.AddPolygon(vertices); |
|
||||||
} |
|
||||||
|
|
||||||
// 生成网格 |
|
||||||
var mesh = meshBuilder.ToMeshGeometry3D(); |
|
||||||
|
|
||||||
// 创建自发光材质 |
|
||||||
var material = new PBRMaterial |
|
||||||
{ |
|
||||||
AlbedoColor = new Color4(0, 0, 0, 1.0f), // 黑色,避免其他光照影响 |
|
||||||
EmissiveColor = emissiveColor, // 自发光颜色 |
|
||||||
MetallicFactor = 0.0, // 非金属 |
|
||||||
RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 |
|
||||||
ReflectanceFactor = 0.0, // 无反射 |
|
||||||
ClearCoatStrength = 0.0, // 无清漆效果 |
|
||||||
ClearCoatRoughness = 1.0, // 高粗糙度 |
|
||||||
}; |
|
||||||
|
|
||||||
// 设置材质的采样器状态 |
|
||||||
material.SurfaceMapSampler = new SamplerStateDescription |
|
||||||
{ |
|
||||||
Filter = Filter.MinMagMipLinear, |
|
||||||
AddressU = TextureAddressMode.Wrap, |
|
||||||
AddressV = TextureAddressMode.Wrap, |
|
||||||
AddressW = TextureAddressMode.Wrap |
|
||||||
}; |
|
||||||
|
|
||||||
// 创建几何模型 |
|
||||||
var geometryModel = new MeshGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = mesh, |
|
||||||
Material = material |
|
||||||
}; |
|
||||||
|
|
||||||
// 将几何模型添加到视图中 |
|
||||||
viewport.Items.Add(geometryModel); |
|
||||||
List<GeometryModel3D> result = new List<GeometryModel3D>(); |
|
||||||
result.Add(geometryModel); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
public static List<GeometryModel3D> GenerateLineTextModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities) |
|
||||||
{ |
|
||||||
List<GeometryModel3D> result = new List<GeometryModel3D>(); |
|
||||||
List<Vector3> selFaceVector = new List<Vector3>(); |
|
||||||
foreach (var entity in entities) |
|
||||||
{ |
|
||||||
selFaceVector.Add(entity.Point1); |
|
||||||
selFaceVector.Add(entity.Point2); |
|
||||||
selFaceVector.Add(entity.Point3); |
|
||||||
} |
|
||||||
var edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
||||||
var lengths = new List<double>(); // 长度列表 |
|
||||||
var angles = new List<double>(); // 夹角列表 |
|
||||||
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector); |
|
||||||
List<Vector3> sortedVectors = uniqueVectors.ToList(); |
|
||||||
Vector3 center = GetCentroid(sortedVectors); |
|
||||||
sortedVectors.Sort((v1, v2) => |
|
||||||
{ |
|
||||||
double angle1 = Math.Atan2(v1.Z - center.Z, v1.X - center.X); // 计算第一个点的角度 |
|
||||||
double angle2 = Math.Atan2(v2.Z - center.Z, v2.X - center.X); // 计算第二个点的角度 |
|
||||||
return angle1.CompareTo(angle2); // 按角度从小到大排序 |
|
||||||
}); |
|
||||||
for (int i = 0; i < sortedVectors.Count - 1; i++) |
|
||||||
{ |
|
||||||
var nowItem = sortedVectors[i]; |
|
||||||
var nextItem = sortedVectors[i + 1]; |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(nowItem, nextItem)); |
|
||||||
// 计算当前线段的长度 |
|
||||||
lengths.Add((nextItem - nowItem).Length()); |
|
||||||
} |
|
||||||
edgeLines.Add(new Tuple<Vector3, Vector3>(sortedVectors.Last(), sortedVectors.First())); |
|
||||||
lengths.Add((sortedVectors.Last() - sortedVectors.First()).Length()); |
|
||||||
//计算夹角 |
|
||||||
for (int i = 0; i < sortedVectors.Count; i++) |
|
||||||
{ |
|
||||||
int prevIndex = (i - 1 + sortedVectors.Count) % sortedVectors.Count; |
|
||||||
int nextIndex = (i + 1) % sortedVectors.Count; |
|
||||||
|
|
||||||
// 获取三点 |
|
||||||
var p1 = sortedVectors[prevIndex]; |
|
||||||
var p2 = sortedVectors[i]; |
|
||||||
var p3 = sortedVectors[nextIndex]; |
|
||||||
|
|
||||||
// 计算夹角 |
|
||||||
var v1 = p2 - p1; |
|
||||||
var v2 = p3 - p2; |
|
||||||
double angle = AngleBetween(v1, v2); |
|
||||||
angles.Add(angle); // 夹角,单位是度 |
|
||||||
} |
|
||||||
if (sortedVectors.Count == lengths.Count && sortedVectors.Count == angles.Count) |
|
||||||
{ |
|
||||||
for (int i = 0; i < sortedVectors.Count; i++) |
|
||||||
{ |
|
||||||
var point = sortedVectors[i]; |
|
||||||
var pointNext = i + 1 >= sortedVectors.Count ? sortedVectors.First() : sortedVectors[i + 1]; |
|
||||||
var lengthText = $"L: {lengths[i]:F2}"; |
|
||||||
var angleText = $"Angle: {angles[i]:F2}°"; |
|
||||||
|
|
||||||
var lengthTextPoint = (pointNext + point) / 2; |
|
||||||
|
|
||||||
// 在合适位置显示文本 |
|
||||||
var text3DL = DisplayText3D(lengthText, |
|
||||||
new Vector3(lengthTextPoint.X, lengthTextPoint.Y + 0.5f, lengthTextPoint.Z)); |
|
||||||
var text3DA = DisplayText3D(angleText, new Vector3(point.X, point.Y + 0.5f, point.Z + 0.5f)); |
|
||||||
viewport.Items.Add(text3DL); |
|
||||||
viewport.Items.Add(text3DA); |
|
||||||
result.Add(text3DL); |
|
||||||
result.Add(text3DA); |
|
||||||
} |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
// 如果列表长度不一致,给出提示信息 |
|
||||||
Console.WriteLine("The lengths or angles list size does not match the sorted vectors list."); |
|
||||||
} |
|
||||||
var lineBuilder = new LineBuilder(); |
|
||||||
foreach (var line in edgeLines) |
|
||||||
{ |
|
||||||
lineBuilder.AddLine(line.Item1, line.Item2); |
|
||||||
} |
|
||||||
|
|
||||||
var edgeLinesModel = new LineGeometryModel3D |
|
||||||
{ |
|
||||||
Geometry = lineBuilder.ToLineGeometry3D(), |
|
||||||
Color = System.Windows.Media.Colors.Red, |
|
||||||
Thickness = 1.0 |
|
||||||
}; |
|
||||||
|
|
||||||
viewport.Items.Add(edgeLinesModel); |
|
||||||
result.Add(edgeLinesModel); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,6 @@ |
|||||||
|
namespace SparkClient.Views.UserControl.ViewportData.Entity; |
||||||
|
|
||||||
|
public class MeasurementsDataEntity |
||||||
|
{ |
||||||
|
|
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
using SharpDX; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Enum; |
||||||
|
|
||||||
|
namespace SparkClient.Views.UserControl.ViewportData.Entity; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 三角形模型实体 |
||||||
|
/// </summary> |
||||||
|
public class Viewport3DTriangleEntity |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// 点1 |
||||||
|
/// </summary> |
||||||
|
public Vector3 Point1 { get; set; } |
||||||
|
/// <summary> |
||||||
|
/// 点2 |
||||||
|
/// </summary> |
||||||
|
public Vector3 Point2 { get; set; } |
||||||
|
/// <summary> |
||||||
|
/// 点3 |
||||||
|
/// </summary> |
||||||
|
public Vector3 Point3 { get; set; } |
||||||
|
/// <summary> |
||||||
|
/// 三角形代码[生成] |
||||||
|
/// 按顺序:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 拼接后使用生成大写16位md5 |
||||||
|
/// </summary> |
||||||
|
public String TriangleCode { get; set; } |
||||||
|
/// <summary> |
||||||
|
/// 面代码 |
||||||
|
/// 由多个三角形组成的面的代码:entity1、entity2组成了一个正方形,那么他俩的TriangleCode属性值一致 |
||||||
|
/// </summary> |
||||||
|
public String PlaneCode { get; set; } |
||||||
|
/// <summary> |
||||||
|
/// 面类型 |
||||||
|
/// 比如这个面时钻石的腰部分,那么type统一,当面类型为err时则该面是一个异常面 |
||||||
|
/// 可选值:PlaneType |
||||||
|
/// </summary> |
||||||
|
public PlaneType PlaneType { get; set; } |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
using System.ComponentModel; |
||||||
|
|
||||||
|
namespace SparkClient.Views.UserControl.ViewportData.Enum; |
||||||
|
|
||||||
|
public enum PlaneType |
||||||
|
{ |
||||||
|
[Description("瑕疵面")] |
||||||
|
Error = -1, |
||||||
|
[Description("腰")] |
||||||
|
Girdle = 0, |
||||||
|
[Description("冠部")] |
||||||
|
Crown = 1, |
||||||
|
[Description("亭部")] |
||||||
|
Pavilion = 2, |
||||||
|
[Description("台面")] |
||||||
|
TableFacet = 11, |
||||||
|
[Description("冠部主刻面(风筝面)")] |
||||||
|
UpperMainFacet = 12, |
||||||
|
[Description("星刻面")] |
||||||
|
StarFacet = 13, |
||||||
|
[Description("上腰面")] |
||||||
|
UpperGirdleFacet = 14, |
||||||
|
[Description("亭部主刻面")] |
||||||
|
PavilionMainFacet = 21, |
||||||
|
[Description("下腰面")] |
||||||
|
LowerGirdleFact = 22, |
||||||
|
[Description("底尖(或底小面)")] |
||||||
|
Culet = 23, |
||||||
|
[Description("其他")] |
||||||
|
Other = 99 |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
using System.Security.Cryptography; |
||||||
|
using System.Text; |
||||||
|
using Newtonsoft.Json; |
||||||
|
using SharpDX; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Entity; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Enum; |
||||||
|
|
||||||
|
namespace SparkClient.Views.UserControl.ViewportData.Helper; |
||||||
|
|
||||||
|
public class CommonHelper |
||||||
|
{ |
||||||
|
public static Viewport3DTriangleEntity CreateByJsonStr(string json) |
||||||
|
{ |
||||||
|
// 解析 JSON 数据 |
||||||
|
var jsonObject = JsonConvert.DeserializeObject<dynamic>(json); |
||||||
|
if (jsonObject == null) throw new ArgumentException("Json object is null"); |
||||||
|
// 提取坐标 |
||||||
|
var coords = jsonObject.coords.ToObject<dynamic[]>(); |
||||||
|
if (coords.Length < 3) |
||||||
|
{ |
||||||
|
throw new ArgumentException("The input JSON does not have enough points to form a triangle."); |
||||||
|
} |
||||||
|
|
||||||
|
// 创建三角形顶点 |
||||||
|
var point1 = new Vector3((float)coords[0].x, (float)coords[0].z, (float)coords[0].y); |
||||||
|
var point2 = new Vector3((float)coords[1].x, (float)coords[1].z, (float)coords[1].y); |
||||||
|
var point3 = new Vector3((float)coords[2].x, (float)coords[2].z, (float)coords[2].y); |
||||||
|
// var point1 = new Vector3((float)coords[0].x, (float)coords[0].y, (float)coords[0].z); |
||||||
|
// var point2 = new Vector3((float)coords[1].x, (float)coords[1].y, (float)coords[1].z); |
||||||
|
// var point3 = new Vector3((float)coords[2].x, (float)coords[2].y, (float)coords[2].z); |
||||||
|
|
||||||
|
// 提取 PlaneCode 和 PlaneType |
||||||
|
string planeCode = jsonObject.facet_id ?? (string)jsonObject.facet_id; |
||||||
|
PlaneType planeType = (PlaneType)(int)jsonObject.facet_type; |
||||||
|
|
||||||
|
// 生成 TriangleCode |
||||||
|
var triangleCode = GenerateTriangleCode(point1, point2, point3); |
||||||
|
|
||||||
|
// 创建并返回实体对象 |
||||||
|
return new Viewport3DTriangleEntity |
||||||
|
{ |
||||||
|
Point1 = point1, |
||||||
|
Point2 = point2, |
||||||
|
Point3 = point3, |
||||||
|
TriangleCode = triangleCode, |
||||||
|
PlaneCode = planeCode, |
||||||
|
PlaneType = planeType |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 生成三角形签名 |
||||||
|
/// </summary> |
||||||
|
/// <param name="p1"></param> |
||||||
|
/// <param name="p2"></param> |
||||||
|
/// <param name="p3"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static string GenerateTriangleCode(Vector3 p1, Vector3 p2, Vector3 p3) |
||||||
|
{ |
||||||
|
var concatenated = $"{p1.X},{p1.Y},{p1.Z};{p2.X},{p2.Y},{p2.Z};{p3.X},{p3.Y},{p3.Z}"; |
||||||
|
return GenerateMd5Hash(concatenated); |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 文本转32大写MD5 |
||||||
|
/// </summary> |
||||||
|
/// <param name="input"></param> |
||||||
|
/// <returns></returns> |
||||||
|
private static string GenerateMd5Hash(string input) |
||||||
|
{ |
||||||
|
using (var md5 = MD5.Create()) |
||||||
|
{ |
||||||
|
var inputBytes = Encoding.UTF8.GetBytes(input); |
||||||
|
var hashBytes = md5.ComputeHash(inputBytes); |
||||||
|
return string.Concat(hashBytes.Select(b => b.ToString("X2"))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,873 @@ |
|||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.IO; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media; |
||||||
|
using System.Windows.Media.Animation; |
||||||
|
using System.Windows.Media.Imaging; |
||||||
|
using System.Windows.Media.Media3D; |
||||||
|
using HelixToolkit.Wpf.SharpDX; |
||||||
|
using SharpDX; |
||||||
|
using SharpDX.Direct3D11; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Enum; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Entity; |
||||||
|
using Color = SharpDX.Color; |
||||||
|
using GeometryModel3D = HelixToolkit.Wpf.SharpDX.GeometryModel3D; |
||||||
|
|
||||||
|
namespace SparkClient.Views.UserControl.ViewportData.Helper; |
||||||
|
|
||||||
|
public class ViewportHelperPro |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// 对指定类型的面进行标色 |
||||||
|
/// </summary> |
||||||
|
/// <param name="planeType"></param> |
||||||
|
/// <param name="color"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static MeshGeometryModel3D GenerateTypePanelHot(PlaneType planeType, Color4? color = null) |
||||||
|
{ |
||||||
|
var entities = ViewportManager.ViewportTriangle.Where(e=>e.PlaneType==planeType).ToList(); |
||||||
|
return GenerateModelByEntity(entities, color??ViewportManager.Red); |
||||||
|
} |
||||||
|
|
||||||
|
#region 已经确定和调整好的方法 |
||||||
|
/// <summary> |
||||||
|
/// 通过三角形实体集合生成面模型(生成并添加) |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
/// <param name="entities"></param> |
||||||
|
public static MeshGeometryModel3D GenerateModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities, Color4? color = null) |
||||||
|
{ |
||||||
|
var geometryModel = GenerateModelByEntity(entities, color); |
||||||
|
viewport.Items.Add(geometryModel); |
||||||
|
return geometryModel; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 通过三角形实体集合生成面模型(只生成不添加) |
||||||
|
/// </summary> |
||||||
|
/// <param name="entities"></param> |
||||||
|
/// <param name="color"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static MeshGeometryModel3D GenerateModelByEntity(List<Viewport3DTriangleEntity> entities, Color4? color = null) |
||||||
|
{ |
||||||
|
var meshBuilder = new MeshBuilder(true, false); |
||||||
|
foreach (var entity in entities) |
||||||
|
{ |
||||||
|
meshBuilder.AddPolygon(new List<Vector3>() { entity.Point1, entity.Point2, entity.Point3 }); |
||||||
|
} |
||||||
|
var mesh = meshBuilder.ToMeshGeometry3D(); |
||||||
|
|
||||||
|
var material = new PBRMaterial |
||||||
|
{ |
||||||
|
AlbedoColor = ViewportManager.Black, // 黑色,避免其他光照影响 |
||||||
|
EmissiveColor =color ?? ViewportManager.LightGray , // LightGray #D3D3D3 |
||||||
|
MetallicFactor = 0.0, // 非金属 |
||||||
|
RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 |
||||||
|
ReflectanceFactor = 0.0, // 无反射 |
||||||
|
ClearCoatStrength = 0.0, // 无清漆效果 |
||||||
|
ClearCoatRoughness = 1.0, // 高粗糙度 |
||||||
|
SurfaceMapSampler = new SamplerStateDescription |
||||||
|
{ |
||||||
|
Filter = Filter.MinMagMipLinear, |
||||||
|
AddressU = TextureAddressMode.Wrap, |
||||||
|
AddressV = TextureAddressMode.Wrap, |
||||||
|
AddressW = TextureAddressMode.Wrap |
||||||
|
}, |
||||||
|
// NormalMap = texture |
||||||
|
}; |
||||||
|
return new MeshGeometryModel3D |
||||||
|
{ |
||||||
|
Geometry = mesh, |
||||||
|
Material = material, |
||||||
|
}; |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 保存模型截图 |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
/// <param name="filePath"></param> |
||||||
|
public static void SaveViewportAsImage(Viewport3DX viewport, string filePath) |
||||||
|
{ |
||||||
|
// 定义图像大小 |
||||||
|
int width = (int)viewport.ActualWidth; |
||||||
|
int height = (int)viewport.ActualHeight; |
||||||
|
|
||||||
|
// 创建 RenderTargetBitmap,捕获 Viewport3DX 的内容 |
||||||
|
var renderTargetBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); |
||||||
|
renderTargetBitmap.Render(viewport); |
||||||
|
|
||||||
|
// 使用 PngBitmapEncoder 保存为 PNG 文件 |
||||||
|
var encoder = new PngBitmapEncoder(); |
||||||
|
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap)); |
||||||
|
|
||||||
|
using (var fileStream = new FileStream(filePath, FileMode.Create)) |
||||||
|
{ |
||||||
|
encoder.Save(fileStream); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 导出模型 |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
/// <param name="filePath"></param> |
||||||
|
public static void ExportModelsToStl(Viewport3DX viewport, string filePath) |
||||||
|
{ |
||||||
|
// 打开文件写入流 |
||||||
|
using (StreamWriter writer = new StreamWriter(filePath)) |
||||||
|
{ |
||||||
|
writer.WriteLine("solid exportedModel"); |
||||||
|
foreach (var model in viewport.Items.OfType<MeshGeometryModel3D>()) |
||||||
|
{ |
||||||
|
if (model.Geometry is HelixToolkit.Wpf.SharpDX.Geometry3D geometry) |
||||||
|
{ |
||||||
|
var positions = geometry.Positions; |
||||||
|
var indices = geometry.Indices; |
||||||
|
|
||||||
|
// 每三个索引构成一个三角形 |
||||||
|
for (int i = 0; i < indices.Count; i += 3) |
||||||
|
{ |
||||||
|
int index0 = indices[i]; |
||||||
|
int index1 = indices[i + 1]; |
||||||
|
int index2 = indices[i + 2]; |
||||||
|
|
||||||
|
var p0 = positions[index0]; |
||||||
|
var p1 = positions[index1]; |
||||||
|
var p2 = positions[index2]; |
||||||
|
|
||||||
|
// 计算法线 |
||||||
|
var normal = CalculateNormal(p0, p1, p2); |
||||||
|
|
||||||
|
// 写入三角形信息到 STL 文件 |
||||||
|
writer.WriteLine($" facet normal {normal.X} {normal.Z} {normal.Y}"); |
||||||
|
writer.WriteLine(" outer loop"); |
||||||
|
writer.WriteLine($" vertex {p0.X} {p0.Z} {p0.Y}"); |
||||||
|
writer.WriteLine($" vertex {p1.X} {p1.Z} {p1.Y}"); |
||||||
|
writer.WriteLine($" vertex {p2.X} {p2.Z} {p2.Y}"); |
||||||
|
writer.WriteLine(" endloop"); |
||||||
|
writer.WriteLine(" endfacet"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 写入 STL 文件结束 |
||||||
|
writer.WriteLine("endsolid exportedModel"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 通过三角形实体集合生成每个面的边框线 |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
/// <param name="entities"></param> |
||||||
|
public static List<LineGeometryModel3D> GentrateLineByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities, Color4? color = null, double thickness = 1.0) |
||||||
|
{ |
||||||
|
List<LineGeometryModel3D> result = GentrateLineByEntity(entities, color, thickness); |
||||||
|
result.ForEach(e => viewport.Items.Add(e)); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 通过三角形实体集合生成每个面的边框线(只生成不添加) |
||||||
|
/// </summary> |
||||||
|
/// <param name="entities"></param> |
||||||
|
/// <param name="color"></param> |
||||||
|
/// <param name="thickness"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<LineGeometryModel3D> GentrateLineByEntity(List<Viewport3DTriangleEntity> entities, Color4? color = null, double thickness = 1.0) |
||||||
|
{ |
||||||
|
List<LineGeometryModel3D> result = new List<LineGeometryModel3D>(); |
||||||
|
//按面分组,腰面特殊单独生成 |
||||||
|
List<Viewport3DTriangleEntity> waistList = entities |
||||||
|
.Where(entity => entity.PlaneType == PlaneType.Girdle) |
||||||
|
.ToList(); |
||||||
|
|
||||||
|
Dictionary<string, List<Viewport3DTriangleEntity>> feactList = entities |
||||||
|
.Where(entity => entity.PlaneType != PlaneType.Girdle) |
||||||
|
.GroupBy(entity => entity.PlaneCode) |
||||||
|
.ToDictionary(group => group.Key, group => group.ToList()); |
||||||
|
|
||||||
|
foreach (var item in feactList) |
||||||
|
{ |
||||||
|
//GenerateLineTextModelByEntity(viewport, item.Value, Viewport3DManager.Gray); |
||||||
|
List<Vector3> temp = new List<Vector3>(); |
||||||
|
foreach (var entity in item.Value) |
||||||
|
{ |
||||||
|
temp.Add(entity.Point1); |
||||||
|
temp.Add(entity.Point2); |
||||||
|
temp.Add(entity.Point3); |
||||||
|
} |
||||||
|
result.Add(DisplayLineModel3D(VectorClockwiseSort(new HashSet<Vector3>(temp).ToList()), color??ViewportManager.LineColor, thickness)); |
||||||
|
} |
||||||
|
|
||||||
|
//腰 - 特殊 |
||||||
|
List<Vector3> selFaceVector = new List<Vector3>(); |
||||||
|
if (waistList.Count > 0) |
||||||
|
{ |
||||||
|
foreach (var entity in waistList) |
||||||
|
{ |
||||||
|
selFaceVector.Add(entity.Point1); |
||||||
|
selFaceVector.Add(entity.Point2); |
||||||
|
selFaceVector.Add(entity.Point3); |
||||||
|
} |
||||||
|
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector); |
||||||
|
float maxY = uniqueVectors.Max(v => v.Y); |
||||||
|
float minY = uniqueVectors.Min(v => v.Y); |
||||||
|
float mid = (maxY + minY) / 2; |
||||||
|
ViewportManager.GirdleTopLines.Clear(); |
||||||
|
ViewportManager.GirdleBottomLines.Clear(); |
||||||
|
foreach (var vector in uniqueVectors) |
||||||
|
{ |
||||||
|
if(vector.Y < mid) |
||||||
|
ViewportManager.GirdleBottomLines.Add(vector); |
||||||
|
else |
||||||
|
ViewportManager.GirdleTopLines.Add(vector); |
||||||
|
} |
||||||
|
result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleBottomLines), color??ViewportManager.LineColor, thickness)); |
||||||
|
result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleTopLines), color??ViewportManager.LineColor, thickness)); |
||||||
|
|
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 显示线段长度mm |
||||||
|
/// </summary> |
||||||
|
/// <param name="entities">三角形集合</param> |
||||||
|
/// <param name="textColor">文字颜色</param> |
||||||
|
/// <param name="showAll">是否显示全部(false:只显示不重复)</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<GeometryModel3D> GenerateLineTextModels(List<Viewport3DTriangleEntity> entities, |
||||||
|
Color4? textColor = null,bool showAll = false) |
||||||
|
{ |
||||||
|
var result = new List<GeometryModel3D>(); |
||||||
|
var selFaceVector = entities |
||||||
|
.SelectMany(entity => new[] { entity.Point1, entity.Point2, entity.Point3 }) |
||||||
|
.Distinct() |
||||||
|
.ToList(); |
||||||
|
var uniqueLines = new HashSet<string>(); |
||||||
|
var sortedVectors = VectorClockwiseSort(selFaceVector); |
||||||
|
for (int i = 0; i < sortedVectors.Count; i++) |
||||||
|
{ |
||||||
|
var current = sortedVectors[i]; |
||||||
|
var next = sortedVectors[(i + 1) % sortedVectors.Count]; |
||||||
|
|
||||||
|
double length = (next - current).Length(); |
||||||
|
string lineKey = $"{length:F2}"; |
||||||
|
if (showAll == false) |
||||||
|
{ |
||||||
|
if (uniqueLines.Contains(lineKey)) continue; |
||||||
|
uniqueLines.Add(lineKey); |
||||||
|
} |
||||||
|
var midPoint = (current + next) / 2; |
||||||
|
var text = $"{length:F2}mm"; |
||||||
|
var textY = midPoint.Y > ViewportManager.CenterVector.Y ? midPoint.Y + 0.3f : midPoint.Y - 0.3f; |
||||||
|
var lengthTextModel = DisplayText3D(text, new Vector3(midPoint.X, textY, midPoint.Z),next - current, textColor); |
||||||
|
result.Add(lengthTextModel); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 生成选择的视图() |
||||||
|
/// </summary> |
||||||
|
/// <param name="triangleCode">三角形ID</param> |
||||||
|
/// <param name="selType">生成范围</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<GeometryModel3D> GentrateChosenView(string triangleCode, params SelShowType[] selType) |
||||||
|
{ |
||||||
|
var res = ViewportManager.ViewportTriangle.Find(e => triangleCode.Equals(e.TriangleCode)); |
||||||
|
if(res != null) |
||||||
|
return GentrateChosenView(res, selType); |
||||||
|
return new List<GeometryModel3D>(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 生成选择的视图(只生成,不添加!!) |
||||||
|
/// </summary> |
||||||
|
/// <param name="entity">选中的实体</param> |
||||||
|
/// <param name="selType">生成范围</param> |
||||||
|
/// <returns></returns> |
||||||
|
/// <exception cref="Exception"></exception> |
||||||
|
public static List<GeometryModel3D> GentrateChosenView(Viewport3DTriangleEntity entity, params SelShowType[] selType) |
||||||
|
{ |
||||||
|
if (entity == null || entity.TriangleCode == null || entity.TriangleCode.Length == 0) |
||||||
|
{ |
||||||
|
throw new Exception("Invalid Viewport3DTriangleEntity object!"); |
||||||
|
} |
||||||
|
|
||||||
|
if (selType == null || selType.Length == 0) |
||||||
|
{ |
||||||
|
throw new Exception("Invalid SelShowType collection"); |
||||||
|
} |
||||||
|
List<GeometryModel3D> result = new List<GeometryModel3D>(); |
||||||
|
|
||||||
|
//选中的面 |
||||||
|
var selPanel = ViewportManager.ViewportTriangle.Where(e => entity.PlaneCode.Equals(e.PlaneCode)).ToList(); |
||||||
|
//选中同类的面 |
||||||
|
var selPanelType = ViewportManager.ViewportTriangle.Where(e => entity.PlaneType.Equals(e.PlaneType)).ToList(); |
||||||
|
selPanel.ForEach(e=>selPanelType.Remove(e)); |
||||||
|
foreach (var type in selType) |
||||||
|
{ |
||||||
|
switch (type) |
||||||
|
{ |
||||||
|
case SelShowType.SelPanel: |
||||||
|
//选中面的高亮 |
||||||
|
result.Add(GenerateModelByEntity(selPanel, ViewportManager.DarkKhaki)); |
||||||
|
break; |
||||||
|
case SelShowType.Border: |
||||||
|
//选中面边框的高亮 |
||||||
|
result.AddRange(GentrateLineByEntity(selPanel, ViewportManager.Black)); |
||||||
|
break; |
||||||
|
case SelShowType.IsTypePanel: |
||||||
|
//选中面的同类面高亮 |
||||||
|
result.Add(GenerateModelByEntity(selPanelType, ViewportManager.LtGoldenrodYello)); |
||||||
|
break; |
||||||
|
case SelShowType.LengthText: |
||||||
|
//选中面 每条边长度标记 |
||||||
|
if(PlaneType.Girdle == entity.PlaneType)break; |
||||||
|
result.AddRange(GenerateLineTextModels(selPanel)); |
||||||
|
break; |
||||||
|
case SelShowType.BorderAngle: |
||||||
|
//选中面 每条边向内的夹角 |
||||||
|
if(PlaneType.Girdle == entity.PlaneType)break; |
||||||
|
result.AddRange(GenerateLineAngleTextModels(selPanel)); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
//生成的需要添加的元素 |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 生成线段夹角文本(不绘制) |
||||||
|
/// </summary> |
||||||
|
/// <param name="entities">线集合</param> |
||||||
|
/// <param name="textColor">文本颜色(默认红)</param> |
||||||
|
/// <param name="showAll">是否显示全部(默认否)</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<GeometryModel3D> GenerateLineAngleTextModels(List<Viewport3DTriangleEntity> entities, |
||||||
|
Color4? textColor = null, bool showAll = false) |
||||||
|
{ |
||||||
|
var result = new List<GeometryModel3D>(); |
||||||
|
var uniqueAngles = new HashSet<string>(); |
||||||
|
var selFaceVector = entities |
||||||
|
.SelectMany(entity => new[] { entity.Point1, entity.Point2, entity.Point3 }) |
||||||
|
.Distinct() |
||||||
|
.ToList(); |
||||||
|
|
||||||
|
var sortedVectors = VectorClockwiseSort(selFaceVector); |
||||||
|
|
||||||
|
for (int i = 0; i < sortedVectors.Count; i++) |
||||||
|
{ |
||||||
|
int prevIndex = (i - 1 + sortedVectors.Count) % sortedVectors.Count; |
||||||
|
int nextIndex = (i + 1) % sortedVectors.Count; |
||||||
|
|
||||||
|
var p1 = sortedVectors[prevIndex]; |
||||||
|
var p2 = sortedVectors[i]; |
||||||
|
var p3 = sortedVectors[nextIndex]; |
||||||
|
|
||||||
|
// 向量 |
||||||
|
var v1 = Vector3.Normalize(p1 - p2); |
||||||
|
var v2 = Vector3.Normalize(p3 - p2); |
||||||
|
|
||||||
|
// 计算内角(考虑方向性) |
||||||
|
double angle = Math.Acos(Vector3.Dot(v1, v2)) * (180 / Math.PI); |
||||||
|
|
||||||
|
string angleKey = $"{angle:F2}"; // 唯一标识 |
||||||
|
if (showAll == false) |
||||||
|
{ |
||||||
|
if (uniqueAngles.Contains(angleKey)) continue; |
||||||
|
uniqueAngles.Add(angleKey); |
||||||
|
} |
||||||
|
|
||||||
|
// 显示内角 |
||||||
|
var text = $"{angle:F2}°"; |
||||||
|
var textY = p2.Y > ViewportManager.CenterVector.Y ? p2.Y + 0.3f : p2.Y - 0.3f; |
||||||
|
var angleTextModel = DisplayText3D(text, new Vector3(p2.X, textY, p2.Z), textColor); |
||||||
|
result.Add(angleTextModel); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 旋转主体 |
||||||
|
/// </summary> |
||||||
|
/// <param name="axis">中心</param> |
||||||
|
/// <param name="hasLine">是否包含线</param> |
||||||
|
/// <param name="speed">旋转时间 秒</param> |
||||||
|
public static void RotateModel(Vector3D axis, bool hasLine = true, int speed = 5) |
||||||
|
{ |
||||||
|
// 设置旋转的中心点和旋转轴 |
||||||
|
var rotateTransform = new RotateTransform3D(); |
||||||
|
var rotation = new AxisAngleRotation3D(axis, 0); // 初始角度为 0 |
||||||
|
rotateTransform.Rotation = rotation; |
||||||
|
rotateTransform.CenterX = ViewportManager.CenterVector.X; |
||||||
|
rotateTransform.CenterY = ViewportManager.CenterVector.Y; |
||||||
|
rotateTransform.CenterZ = ViewportManager.CenterVector.Z; |
||||||
|
|
||||||
|
// 将旋转变换应用到模型 |
||||||
|
ViewportManager.MainModel3D.Transform = rotateTransform; |
||||||
|
if(hasLine) |
||||||
|
ViewportManager.MainModelLines.ForEach(e => e.Transform = rotateTransform); |
||||||
|
// 创建旋转动画 |
||||||
|
var rotateAnimation = new DoubleAnimation |
||||||
|
{ |
||||||
|
From = 0, |
||||||
|
To = 360, |
||||||
|
Duration = new Duration(TimeSpan.FromSeconds(speed)), |
||||||
|
FillBehavior = FillBehavior.HoldEnd |
||||||
|
}; |
||||||
|
|
||||||
|
// 应用动画 |
||||||
|
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, rotateAnimation); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 在场景中添加文字 |
||||||
|
/// </summary> |
||||||
|
/// <param name="text">文字</param> |
||||||
|
/// <param name="position">位置</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static BillboardTextModel3D DisplayText3D(string text, Vector3 position, Color4? color = null) |
||||||
|
{ |
||||||
|
var billboardTextModel = new BillboardTextModel3D(); |
||||||
|
var billboardText = new BillboardText3D(); |
||||||
|
billboardText.TextInfo.Add(new TextInfo(text, position) |
||||||
|
{ |
||||||
|
Foreground = color??ViewportManager.Red, |
||||||
|
Scale = 0.5f, |
||||||
|
}); |
||||||
|
billboardTextModel.Geometry = billboardText; |
||||||
|
return billboardTextModel; |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 在场景中添加文字 |
||||||
|
/// </summary> |
||||||
|
/// <param name="text"></param> |
||||||
|
/// <param name="position"></param> |
||||||
|
/// <param name="direction"></param> |
||||||
|
/// <param name="color"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static BillboardTextModel3D DisplayText3D(string text, Vector3 position, Vector3 direction, Color4? color = null) |
||||||
|
{ |
||||||
|
// 计算线段方向的角度 |
||||||
|
double angle = Math.Atan2(direction.Y, direction.X) * (180 / Math.PI); |
||||||
|
|
||||||
|
var billboardTextModel = new BillboardTextModel3D() |
||||||
|
{ |
||||||
|
Geometry = new BillboardText3D() |
||||||
|
{ |
||||||
|
TextInfo = new ObservableCollection<TextInfo>() |
||||||
|
{ |
||||||
|
new TextInfo(text, position){ |
||||||
|
Foreground = color ?? ViewportManager.Red, |
||||||
|
Scale = 0.5f, |
||||||
|
Offset = new Vector2(0.2f, 0.2f), |
||||||
|
// Angle = (float)angle // 设置文字的旋转角度 |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
DepthBias = -99, |
||||||
|
SlopeScaledDepthBias = 1.0f, |
||||||
|
IsDepthClipEnabled = true |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
return billboardTextModel; |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 生成线对象 |
||||||
|
/// </summary> |
||||||
|
/// <param name="points">点集合(自动闭环)</param> |
||||||
|
/// <param name="lineColor">线颜色</param> |
||||||
|
/// <param name="thickness">线粗细 默认1</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static LineGeometryModel3D DisplayLineModel3D(List<Vector3> points, Color4 lineColor, |
||||||
|
double thickness = 1.0) |
||||||
|
{ |
||||||
|
var edgeLines = new List<Tuple<Vector3, Vector3>>(); |
||||||
|
for (int i = 0; i < points.Count - 1; i++) |
||||||
|
{ |
||||||
|
var nowItem = points[i]; |
||||||
|
var nextItem = points[i + 1]; |
||||||
|
edgeLines.Add(new Tuple<Vector3, Vector3>(nowItem, nextItem)); |
||||||
|
|
||||||
|
} |
||||||
|
edgeLines.Add(new Tuple<Vector3, Vector3>(points.Last(), points.First())); |
||||||
|
return DisplayLineModel3D(edgeLines, lineColor, thickness); |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 在正方向生成相机 |
||||||
|
/// </summary> |
||||||
|
/// <param name="positiveDirection"></param> |
||||||
|
/// <param name="boundingBox"></param> |
||||||
|
/// <param name="fieldOfView"></param> |
||||||
|
/// <param name="aspectRatio"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static HelixToolkit.Wpf.SharpDX.PerspectiveCamera CalculateCamera(Vector3 positiveDirection, BoundingBox boundingBox, double fieldOfView = 45, double aspectRatio = 16.0 / 9.0) |
||||||
|
{ |
||||||
|
// 单位化正方向 |
||||||
|
var normalizedDirection = Vector3.Normalize(positiveDirection); |
||||||
|
|
||||||
|
// 计算模型中心和最大尺寸 |
||||||
|
var center = boundingBox.Center; // 包围盒中心 |
||||||
|
var maxDimension = boundingBox.Size.Length(); // 包围盒对角线长度 |
||||||
|
|
||||||
|
// 计算视场角的一半(弧度制) |
||||||
|
var halfFov = Math.PI * fieldOfView / 360.0; |
||||||
|
|
||||||
|
// 计算相机距离 |
||||||
|
var distance = maxDimension / (2 * Math.Tan(halfFov)); |
||||||
|
|
||||||
|
// 相机位置和朝向 |
||||||
|
var cameraPosition = center - normalizedDirection * (float)distance; // 沿正方向放置相机 |
||||||
|
var lookDirection = center - cameraPosition; // 朝向模型中心 |
||||||
|
|
||||||
|
|
||||||
|
// 创建并返回相机 |
||||||
|
return new HelixToolkit.Wpf.SharpDX.PerspectiveCamera |
||||||
|
{ |
||||||
|
Position = cameraPosition.ToPoint3D(), |
||||||
|
LookDirection = lookDirection.ToVector3D(), |
||||||
|
UpDirection = new Vector3D(0, 1, 0), // 默认全局 Y 轴为上方向 |
||||||
|
FieldOfView = fieldOfView, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 绘制箭头(相机指向模型中心) |
||||||
|
/// </summary> |
||||||
|
/// <param name="cameraPosition">相机位置</param> |
||||||
|
/// <param name="modelCenter">中心位置</param> |
||||||
|
/// <param name="modelBounds">模型</param> |
||||||
|
/// <param name="totalLength">箭头总长度 D:5</param> |
||||||
|
/// <param name="cylinderRatio">圆柱部分占比 0.7</param> |
||||||
|
/// <param name="diameter">箭头直径 1</param> |
||||||
|
/// <param name="headDiameterRatio">圆锥直径与圆柱直径的比例 1.5</param> |
||||||
|
/// <param name="padding">模型边界范围 1</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static MeshGeometryModel3D CreateArrow( |
||||||
|
Vector3 cameraPosition, |
||||||
|
Vector3 modelCenter, |
||||||
|
BoundingBox modelBounds, |
||||||
|
float totalLength = 2f, // 箭头总长度 |
||||||
|
float cylinderRatio = 0.7f,// 圆柱部分占比 |
||||||
|
float diameter = 0.5f, // 箭头直径 |
||||||
|
float headDiameterRatio = 1f, // 圆锥直径与圆柱直径的比例 |
||||||
|
float padding = 2.0f |
||||||
|
) |
||||||
|
{ |
||||||
|
// 计算模型的半径(对角线的一半) |
||||||
|
var modelSize = modelBounds.Size; |
||||||
|
var modelRadius = modelSize.Length() / 2; |
||||||
|
|
||||||
|
// 计算方向向量(从相机位置指向模型中心) |
||||||
|
var direction = Vector3.Normalize(modelCenter - cameraPosition); |
||||||
|
|
||||||
|
// 调整箭头起点,避免与模型交集 |
||||||
|
var adjustedStart = modelCenter - direction * (modelRadius + padding); |
||||||
|
|
||||||
|
// 计算圆柱和圆锥部分长度 |
||||||
|
float cylinderLength = totalLength * cylinderRatio; |
||||||
|
float coneLength = totalLength - cylinderLength; |
||||||
|
|
||||||
|
// 箭头圆柱终点 |
||||||
|
var cylinderEnd = adjustedStart + direction * cylinderLength; |
||||||
|
|
||||||
|
// 箭头终点(圆锥部分终点) |
||||||
|
var arrowEnd = cylinderEnd + direction * coneLength; |
||||||
|
|
||||||
|
// 创建箭头几何体 |
||||||
|
var builder = new MeshBuilder(); |
||||||
|
|
||||||
|
// 添加圆柱部分 |
||||||
|
builder.AddCylinder(adjustedStart, cylinderEnd, diameter, 20); |
||||||
|
|
||||||
|
// 添加圆锥部分 |
||||||
|
builder.AddCone(cylinderEnd, arrowEnd, diameter * headDiameterRatio, true, 20); |
||||||
|
|
||||||
|
// 设置箭头自发光材质 |
||||||
|
var material = new PhongMaterial |
||||||
|
{ |
||||||
|
DiffuseColor = Color.Red, // 箭头的基本颜色 |
||||||
|
EmissiveColor = Color.Red // 箭头自发光颜色 |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 创建 3D 模型 |
||||||
|
return new MeshGeometryModel3D |
||||||
|
{ |
||||||
|
Geometry = builder.ToMeshGeometry3D(), |
||||||
|
Material = material |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 生成线对象 |
||||||
|
/// </summary> |
||||||
|
/// <param name="points">线段集合</param> |
||||||
|
/// <param name="lineColor">线段颜色</param> |
||||||
|
/// <param name="thickness">线段粗细</param> |
||||||
|
/// <returns></returns> |
||||||
|
public static LineGeometryModel3D DisplayLineModel3D(List<Tuple<Vector3, Vector3>> points, Color4 lineColor, |
||||||
|
double thickness = 1.0) |
||||||
|
{ |
||||||
|
var lineBuilder = new LineBuilder(); |
||||||
|
foreach (var line in points) |
||||||
|
{ |
||||||
|
lineBuilder.AddLine(line.Item1, line.Item2); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
var edgeLinesModel = new LineGeometryModel3D |
||||||
|
{ |
||||||
|
Geometry = lineBuilder.ToLineGeometry3D(), |
||||||
|
Color = lineColor.ToColor(), |
||||||
|
Thickness = thickness, |
||||||
|
}; |
||||||
|
return edgeLinesModel; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 根据视图中模型生成光照 |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<Light3D> GenerateLightingForModel(Viewport3DX viewport) |
||||||
|
{ |
||||||
|
|
||||||
|
List<Light3D> result = new List<Light3D>(); |
||||||
|
|
||||||
|
var models = viewport.Items.OfType<MeshGeometryModel3D>(); |
||||||
|
if (!models.Any()) throw new Exception("Model in view not found"); |
||||||
|
var largestModel = models |
||||||
|
.OrderByDescending(m => GetBoundingBoxVolume(m.Geometry.Bound)) |
||||||
|
.FirstOrDefault(); |
||||||
|
if (largestModel == null) throw new Exception("Model in view not found"); |
||||||
|
var boundingBox = largestModel.Geometry.Bound; |
||||||
|
var size = boundingBox.Size; |
||||||
|
var center = boundingBox.Center; |
||||||
|
ViewportManager.CenterVector = center; |
||||||
|
ViewportManager.ModelBounds = boundingBox; |
||||||
|
var yao = CalculateCenter(ViewportManager.GirdleTopLines, ViewportManager.GirdleBottomLines); |
||||||
|
var corners = new List<Point3D> |
||||||
|
{ |
||||||
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Minimum.Z-0.5), // 右下后 |
||||||
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Minimum.Z-0.5), // 右下后 |
||||||
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Maximum.Z+0.5), // 左下前 |
||||||
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Maximum.Z+0.5), // 右下前 |
||||||
|
|
||||||
|
// new Point3D(boundingBox.Minimum.X-1, yao.Y, boundingBox.Minimum.Z-1), // 右下后 |
||||||
|
// new Point3D(boundingBox.Maximum.X+1, yao.Y, boundingBox.Minimum.Z-1), // 右下后 |
||||||
|
// new Point3D(boundingBox.Minimum.X-1, yao.Y, boundingBox.Maximum.Z+1), // 左下前 |
||||||
|
// new Point3D(boundingBox.Maximum.X+1, yao.Y, boundingBox.Maximum.Z+1), // 右下前 |
||||||
|
|
||||||
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Minimum.Z-0.5), // 左上后 |
||||||
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4,boundingBox.Maximum.Z+0.5), // 右上前 |
||||||
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Minimum.Z-0.5), // 右上后 |
||||||
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Maximum.Z+0.5), // 左上前 |
||||||
|
}; |
||||||
|
for (int i = 0; i < corners.Count; i++) |
||||||
|
{ |
||||||
|
var corner = corners[i]; |
||||||
|
var color = i % 2 == 0 ? Colors.LightGoldenrodYellow : Colors.LightSkyBlue; |
||||||
|
var pointLight = new PointLight3D |
||||||
|
{ |
||||||
|
Position = corner, |
||||||
|
Color = color, |
||||||
|
Range = (float)size.Length() |
||||||
|
}; |
||||||
|
result.Add(pointLight); |
||||||
|
} |
||||||
|
// var topLightPositions = new List<Point3D> |
||||||
|
// { |
||||||
|
// new Point3D(center.X, center.Y, center.Z), |
||||||
|
// }; |
||||||
|
// foreach (var position in topLightPositions) |
||||||
|
// { |
||||||
|
// var topLight = new PointLight3D |
||||||
|
// { |
||||||
|
// Position = position, |
||||||
|
// Color = Colors.LightGoldenrodYellow, |
||||||
|
// Range = (float)size.Length() * 0.5, // 增加光的照射范围 |
||||||
|
// Attenuation = new Vector3D(1, 0.1f, 0.05f) // 控制光的衰减,使光在距离内更有效 |
||||||
|
// }; |
||||||
|
// result.Add(topLight); |
||||||
|
// } |
||||||
|
// 添加环境光以柔化整体效果并增加亮度 |
||||||
|
result.Add(new AmbientLight3D |
||||||
|
{ |
||||||
|
Color = Colors.LightGray |
||||||
|
}); |
||||||
|
|
||||||
|
result.Add(new AmbientLight3D |
||||||
|
{ |
||||||
|
Color = Colors.Gray // 设置环境光颜色 |
||||||
|
}); |
||||||
|
|
||||||
|
RemoveLightingInViewport(viewport); |
||||||
|
result.ForEach(e => viewport.Items.Add(e)); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public static Vector3 GetCenterOfTriangles(List<Viewport3DTriangleEntity> triangles) |
||||||
|
{ |
||||||
|
if (triangles == null || triangles.Count == 0) |
||||||
|
throw new ArgumentException("The list of triangles cannot be null or empty."); |
||||||
|
|
||||||
|
// 累加所有顶点的坐标 |
||||||
|
Vector3 total = Vector3.Zero; |
||||||
|
int vertexCount = 0; |
||||||
|
|
||||||
|
foreach (var triangle in triangles) |
||||||
|
{ |
||||||
|
total += triangle.Point1; |
||||||
|
total += triangle.Point2; |
||||||
|
total += triangle.Point3; |
||||||
|
vertexCount += 3; // 每个三角形有3个顶点 |
||||||
|
} |
||||||
|
|
||||||
|
// 计算平均坐标 |
||||||
|
return total / vertexCount; |
||||||
|
} |
||||||
|
|
||||||
|
public static Vector3 CalculateCenter(List<Vector3> girdleTopLines, List<Vector3> girdleBottomLines) |
||||||
|
{ |
||||||
|
// 计算 GirdleTopLines 的中心点 |
||||||
|
Vector3 topCenter = GetCenter(girdleTopLines); |
||||||
|
|
||||||
|
// 计算 GirdleBottomLines 的中心点 |
||||||
|
Vector3 bottomCenter = GetCenter(girdleBottomLines); |
||||||
|
|
||||||
|
// 计算两个中心点的中点作为垂直方向的中心点 |
||||||
|
Vector3 centerInVertical = new Vector3( |
||||||
|
(topCenter.X + bottomCenter.X) / 2, |
||||||
|
(topCenter.Y + bottomCenter.Y) / 2, |
||||||
|
(topCenter.Z + bottomCenter.Z) / 2 |
||||||
|
); |
||||||
|
|
||||||
|
return centerInVertical; |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 删除视图中的照明 |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
public static void RemoveLightingInViewport(Viewport3DX viewport) |
||||||
|
{ |
||||||
|
List<Light3D> lights = new List<Light3D>(); |
||||||
|
foreach (var item in viewport.Items) |
||||||
|
{ |
||||||
|
if (item is Light3D light3D) |
||||||
|
{ |
||||||
|
lights.Add(light3D); |
||||||
|
} |
||||||
|
} |
||||||
|
lights.ForEach(item => viewport.Items.Remove(item)); |
||||||
|
} |
||||||
|
|
||||||
|
#region 私有方法 |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 向量按中心点顺时针排序 |
||||||
|
/// </summary> |
||||||
|
/// <param name="points"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<Vector3> VectorClockwiseSort(List<Vector3> points) |
||||||
|
{ |
||||||
|
Vector3 center = GetCentroid(points); |
||||||
|
return VectorClockwiseSort(points, center); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 向量按中心点顺时针排序 |
||||||
|
/// </summary> |
||||||
|
/// <param name="points"></param> |
||||||
|
/// <param name="center"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static List<Vector3> VectorClockwiseSort(List<Vector3> points, Vector3 center) |
||||||
|
{ |
||||||
|
points.Sort((v1, v2) => |
||||||
|
{ |
||||||
|
double angle1 = Math.Atan2(v1.Z - center.Z, v1.X - center.X); |
||||||
|
double angle2 = Math.Atan2(v2.Z - center.Z, v2.X - center.X); |
||||||
|
return angle1.CompareTo(angle2); |
||||||
|
}); |
||||||
|
return points; |
||||||
|
} |
||||||
|
|
||||||
|
private static Vector3 GetCenter(List<Vector3> points) |
||||||
|
{ |
||||||
|
float x = points.Average(p => p.X); |
||||||
|
float y = points.Average(p => p.Y); |
||||||
|
float z = points.Average(p => p.Z); |
||||||
|
return new Vector3(x, y, z); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 计算点集合的中心点(几何质心) |
||||||
|
/// </summary> |
||||||
|
/// <param name="vectors"></param> |
||||||
|
/// <returns></returns> |
||||||
|
public static Vector3 GetCentroid(List<Vector3> vectors) |
||||||
|
{ |
||||||
|
float x = vectors.Sum(v => v.X) / vectors.Count; |
||||||
|
float y = vectors.Sum(v => v.Y) / vectors.Count; |
||||||
|
float z = vectors.Sum(v => v.Z) / vectors.Count; |
||||||
|
return new Vector3(x, y, z); |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 计算夹角度数 |
||||||
|
/// </summary> |
||||||
|
/// <param name="v1"></param> |
||||||
|
/// <param name="v2"></param> |
||||||
|
/// <returns></returns> |
||||||
|
private static float AngleBetween(Vector3 v1, Vector3 v2) |
||||||
|
{ |
||||||
|
// 计算两个向量的点积 |
||||||
|
float dotProduct = Vector3.Dot(v1, v2); |
||||||
|
|
||||||
|
// 计算每个向量的模长 |
||||||
|
float magnitudeV1 = v1.Length(); |
||||||
|
float magnitudeV2 = v2.Length(); |
||||||
|
|
||||||
|
// 防止除以零错误 |
||||||
|
if (magnitudeV1 == 0 || magnitudeV2 == 0) |
||||||
|
{ |
||||||
|
return 0f; |
||||||
|
} |
||||||
|
|
||||||
|
// 计算夹角的余弦值 |
||||||
|
float cosTheta = dotProduct / (magnitudeV1 * magnitudeV2); |
||||||
|
|
||||||
|
// 限制cosTheta的值范围在 -1 到 1 之间,以防计算机浮点误差 |
||||||
|
cosTheta = Math.Max(-1f, Math.Min(1f, cosTheta)); |
||||||
|
|
||||||
|
// 返回角度,单位是度 |
||||||
|
return (float)(Math.Acos(cosTheta) * (180.0 / Math.PI)); |
||||||
|
} |
||||||
|
|
||||||
|
private static double GetBoundingBoxVolume(BoundingBox bound) |
||||||
|
{ |
||||||
|
var size = bound.Size; |
||||||
|
return size.X * size.Y * size.Z; |
||||||
|
} |
||||||
|
|
||||||
|
private static Vector3 CalculateNormal(Vector3 p0, Vector3 p1, Vector3 p2) |
||||||
|
{ |
||||||
|
var u = p1 - p0; |
||||||
|
var v = p2 - p0; |
||||||
|
return Vector3.Cross(u, v); |
||||||
|
} |
||||||
|
#endregion |
||||||
|
|
||||||
|
} |
@ -0,0 +1,355 @@ |
|||||||
|
using HelixToolkit.Wpf.SharpDX; |
||||||
|
using SharpDX; |
||||||
|
using SharpDX.Direct3D11; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Entity; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Enum; |
||||||
|
|
||||||
|
namespace SparkClient.Views.UserControl.ViewportData.Helper; |
||||||
|
|
||||||
|
public class ViewportManager |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// 视图中三角形实体们 |
||||||
|
/// </summary> |
||||||
|
public static List<Viewport3DTriangleEntity> ViewportTriangle = new List<Viewport3DTriangleEntity>(); |
||||||
|
/// <summary> |
||||||
|
/// 模型正方向(从模型中心出发的方向) |
||||||
|
/// </summary> |
||||||
|
public static Vector3 PositiveDirection = new Vector3(0, 0, 0); |
||||||
|
/// <summary> |
||||||
|
/// 模型中心点 |
||||||
|
/// </summary> |
||||||
|
public static Vector3 CenterVector = new Vector3(0, 0, 0); |
||||||
|
/// <summary> |
||||||
|
/// 腰的顶部线 |
||||||
|
/// </summary> |
||||||
|
public static List<Vector3> GirdleTopLines = new List<Vector3>(); |
||||||
|
/// <summary> |
||||||
|
/// 腰的底部线 |
||||||
|
/// </summary> |
||||||
|
public static List<Vector3> GirdleBottomLines = new List<Vector3>(); |
||||||
|
/// <summary> |
||||||
|
/// 模型盒子 |
||||||
|
/// </summary> |
||||||
|
public static BoundingBox ModelBounds = new BoundingBox(); |
||||||
|
/// <summary> |
||||||
|
/// 模型主体 |
||||||
|
/// </summary> |
||||||
|
public static MeshGeometryModel3D MainModel3D = null; |
||||||
|
/// <summary> |
||||||
|
/// 模型边框线 |
||||||
|
/// </summary> |
||||||
|
public static List<LineGeometryModel3D> MainModelLines = new List<LineGeometryModel3D>(); |
||||||
|
/// <summary> |
||||||
|
/// 模型光照 |
||||||
|
/// </summary> |
||||||
|
public static List<Light3D> MainModelLighting = new List<Light3D>(); |
||||||
|
/// <summary> |
||||||
|
/// 模型控件对象映射 |
||||||
|
/// </summary> |
||||||
|
private static Viewport3DX _viewport; |
||||||
|
public static void SetViewport3D(Viewport3DX viewport3D) |
||||||
|
{ |
||||||
|
if (_viewport == null) |
||||||
|
_viewport = viewport3D; |
||||||
|
|
||||||
|
} |
||||||
|
#region 模型选中交互管理 |
||||||
|
//是否双击选中 |
||||||
|
public static bool DoubleClickSelect = true; |
||||||
|
//开启选中边框线 |
||||||
|
public static bool DoubleClickSelectShowBorder = true; |
||||||
|
//选中边框长度文字 |
||||||
|
public static bool DoubleClickSelectShowBorderLength = false; |
||||||
|
//选中边框夹角文字 |
||||||
|
public static bool DoubleClickSelectShowBorderAngle = false; |
||||||
|
//选中同类面 |
||||||
|
public static bool DoubleClickSelectShowPlaneType = true; |
||||||
|
//选中三角形代码 |
||||||
|
public static string ChooseTriangleCode = string.Empty; |
||||||
|
//通过选中添加的元素 |
||||||
|
public static List<GeometryModel3D> ChooseAddModels = new List<GeometryModel3D>(); |
||||||
|
/// <summary> |
||||||
|
/// 重置选中的面 |
||||||
|
/// </summary> |
||||||
|
public static void ResetChooseAddModels() |
||||||
|
{ |
||||||
|
if (string.IsNullOrWhiteSpace(ChooseTriangleCode)) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
ClearDicModels(); |
||||||
|
HashSet<SelShowType> models = new HashSet<SelShowType>(); |
||||||
|
models.Add(SelShowType.SelPanel); |
||||||
|
if(DoubleClickSelectShowBorder) models.Add(SelShowType.Border); |
||||||
|
if (DoubleClickSelectShowBorderLength) models.Add(SelShowType.LengthText); |
||||||
|
if (DoubleClickSelectShowBorderAngle) models.Add(SelShowType.BorderAngle); |
||||||
|
if (DoubleClickSelectShowPlaneType) models.Add(SelShowType.IsTypePanel); |
||||||
|
ChooseAddModels.AddRange( ViewportHelperPro.GentrateChosenView(ChooseTriangleCode, models.ToArray())); |
||||||
|
ChooseAddModels.ForEach(e => _viewport.Items.Add(e)); |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 清除所有的选中效果 |
||||||
|
/// </summary> |
||||||
|
public static void ClearDicModels() |
||||||
|
{ |
||||||
|
foreach (var item in ChooseAddModels) |
||||||
|
{ |
||||||
|
_viewport.Items.Remove(item); |
||||||
|
} |
||||||
|
ChooseAddModels.Clear(); |
||||||
|
} |
||||||
|
#endregion |
||||||
|
|
||||||
|
#region 统一控制方法 |
||||||
|
|
||||||
|
public static MeshGeometryModel3D PointTowardsTheFrontModel = new MeshGeometryModel3D(); |
||||||
|
public static void PointTowardsTheFront(bool isPoint) |
||||||
|
{ |
||||||
|
if (isPoint) |
||||||
|
{ |
||||||
|
var camera = ViewportHelperPro.CalculateCamera(PositiveDirection, ModelBounds); |
||||||
|
PointTowardsTheFrontModel = ViewportHelperPro.CreateArrow(camera.Position.ToVector3(), ModelBounds.Center, ModelBounds); |
||||||
|
_viewport.Items.Add(PointTowardsTheFrontModel); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
_viewport.Items.Remove(PointTowardsTheFrontModel); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static MeshGeometryModel3D MarkFacesModel = new MeshGeometryModel3D(); |
||||||
|
public static void MarkSpecificFaces(bool isMark) |
||||||
|
{ |
||||||
|
if (isMark) |
||||||
|
{ |
||||||
|
MarkFacesModel = ViewportHelperPro.GenerateTypePanelHot(PlaneType.Error); |
||||||
|
_viewport.Items.Add(MarkFacesModel); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
_viewport.Items.Remove(MarkFacesModel); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 初始化加载模型 |
||||||
|
/// </summary> |
||||||
|
/// <param name="viewport"></param> |
||||||
|
/// <param name="entities"></param> |
||||||
|
public static void LoadModelByEntities(List<Viewport3DTriangleEntity> entities) |
||||||
|
{ |
||||||
|
if (entities.Count == 0 && ViewportTriangle.Count != 0) |
||||||
|
{ |
||||||
|
entities.AddRange(ViewportTriangle); |
||||||
|
} |
||||||
|
|
||||||
|
ViewportTriangle.Clear(); |
||||||
|
ViewportTriangle.AddRange(entities); |
||||||
|
MainModel3D = ViewportHelperPro.GenerateModelByEntity(_viewport, entities); |
||||||
|
MainModelLines = ViewportHelperPro.GentrateLineByEntity(_viewport, entities); |
||||||
|
MainModelLighting = ViewportHelperPro.GenerateLightingForModel(_viewport); |
||||||
|
//切换相机视角 |
||||||
|
_viewport.Camera = ViewportHelperPro.CalculateCamera(PositiveDirection, ModelBounds); |
||||||
|
_viewport.RenderHost.MSAA = MSAALevel.Maximum; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 是否显示主体模型 |
||||||
|
/// </summary> |
||||||
|
/// <param name="isShow"></param> |
||||||
|
public static void ShowMainModel3D(bool isShow) |
||||||
|
{ |
||||||
|
if (MainModel3D == null) return; |
||||||
|
if (_viewport == null) return; |
||||||
|
if (isShow) |
||||||
|
{ |
||||||
|
if(_viewport.Items.Contains(MainModel3D)) |
||||||
|
return; |
||||||
|
else |
||||||
|
_viewport.Items.Add(MainModel3D); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
_viewport.Items.Remove(MainModel3D); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void ShowMainModelLines(bool isShow) |
||||||
|
{ |
||||||
|
if (_viewport == null) return; |
||||||
|
if (isShow) |
||||||
|
{ |
||||||
|
MainModelLines.ForEach(e => |
||||||
|
{ |
||||||
|
if(!_viewport.Items.Contains(e)) |
||||||
|
_viewport.Items.Add(e); |
||||||
|
}); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
MainModelLines.ForEach(e => _viewport.Items.Remove(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void ShowMainModelLighting(bool isShow) |
||||||
|
{ |
||||||
|
if (_viewport == null) return; |
||||||
|
if (isShow) |
||||||
|
{ |
||||||
|
MainModelLighting.ForEach(e => |
||||||
|
{ |
||||||
|
if(!_viewport.Items.Contains(e)) |
||||||
|
_viewport.Items.Add(e); |
||||||
|
}); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
MainModelLighting.ForEach(e => _viewport.Items.Remove(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
public static void Translucent(bool isShow) |
||||||
|
{ |
||||||
|
if (_viewport == null) return; |
||||||
|
if (isShow) |
||||||
|
{ |
||||||
|
MainModel3D.Material = _glassMaterial; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
MainModel3D.Material = _material; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void TranslucentTex(bool isShow) |
||||||
|
{ |
||||||
|
if (_viewport == null) return; |
||||||
|
if (isShow) |
||||||
|
{ |
||||||
|
string texturePath2 = "pack://Appliction:,,,/Res/morning_racing_circuit_16k.dds"; |
||||||
|
|
||||||
|
var texture2 = TextureModel.Create(texturePath2); |
||||||
|
|
||||||
|
var material = new PBRMaterial |
||||||
|
{ |
||||||
|
AlbedoColor = new SharpDX.Color4(0.0f, 1.0f, 1.0f, 1.0f), // 白色基色 |
||||||
|
MetallicFactor = 0.0f, |
||||||
|
RoughnessFactor = 0.5f, |
||||||
|
ReflectanceFactor = 0.2f, |
||||||
|
RenderEnvironmentMap = false, |
||||||
|
// AlbedoMap = texture, |
||||||
|
IrradianceMap = texture2, |
||||||
|
RenderIrradianceMap = true, |
||||||
|
}; |
||||||
|
MainModel3D.Material = material; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
MainModel3D.Material = _material; |
||||||
|
} |
||||||
|
} |
||||||
|
public static void LockCameraView(bool isShow) |
||||||
|
{ |
||||||
|
/*Generate Ruler |
||||||
|
* FixedPosition:用于静态视角,用户无法互动。 |
||||||
|
* WalkAround:用于用户自由控制相机位置。 |
||||||
|
* Inspect:用于观察和检查物体,用户可以旋转和缩放,但不能平移。(默认) |
||||||
|
*/ |
||||||
|
// Viewport3Dx.CameraMode = CameraMode.Inspect; |
||||||
|
if (_viewport == null) return; |
||||||
|
if (isShow) |
||||||
|
{ |
||||||
|
_viewport.CameraMode = CameraMode.FixedPosition; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
_viewport.CameraMode = CameraMode.Inspect; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
|
||||||
|
#region 预制颜色 |
||||||
|
/// <summary> |
||||||
|
/// 模型面颜色 |
||||||
|
/// </summary> |
||||||
|
public static Color4 LightGray = new Color4(0.7372f, 0.8235f, 0.93f, 1.0f); |
||||||
|
/// <summary> |
||||||
|
/// 模型面边框颜色 |
||||||
|
/// </summary> |
||||||
|
public static Color4 LineColor = new Color4(0.4117f, 0.3490f, 0.8039f, 1.0f); |
||||||
|
/// <summary> |
||||||
|
/// 选中面高亮色 |
||||||
|
/// </summary> |
||||||
|
public static Color4 DarkKhaki = new Color4(0.7411f, 0.71764f, 0.4196f, 1.0f); |
||||||
|
/// <summary> |
||||||
|
/// 选中面同类面的高亮色 |
||||||
|
/// </summary> |
||||||
|
public static Color4 LtGoldenrodYello = new Color4(0.98039f, 0.98039f, 0.813529f, 1.0f); |
||||||
|
/// <summary> |
||||||
|
/// 选中面高亮的边框色 |
||||||
|
/// </summary> |
||||||
|
public static Color4 Black = new Color4(0f, 0f, 0f, 1.0f); |
||||||
|
/// <summary> |
||||||
|
/// 文字颜色 |
||||||
|
/// </summary> |
||||||
|
public static Color4 SandyBrown = new Color4(0.95686f, 0.64313f, 0.37647f, 1.0f); |
||||||
|
public static Color4 Red = new Color4(1f, 0.0f, 0.0f, 1.0f); |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 材质:半透明蓝 |
||||||
|
/// </summary> |
||||||
|
private static PBRMaterial _glassMaterial = new PBRMaterial |
||||||
|
{ |
||||||
|
// 半透明蓝色 |
||||||
|
AlbedoColor = new Color4(0.0f, 0.0f, 1.0f, 0.5f), |
||||||
|
// 设置为非金属 |
||||||
|
MetallicFactor = 0.0, |
||||||
|
// 光滑表面 |
||||||
|
RoughnessFactor = 0.05, |
||||||
|
// 环境光遮蔽 |
||||||
|
AmbientOcclusionFactor = 1.0, |
||||||
|
// 强反射 |
||||||
|
ReflectanceFactor = 0.9, |
||||||
|
// 清漆效果(类似折射效果) |
||||||
|
ClearCoatStrength = 0.8, |
||||||
|
ClearCoatRoughness = 0.05, |
||||||
|
// 启用环境贴图以增强反射 |
||||||
|
RenderEnvironmentMap = true, |
||||||
|
// 启用阴影效果 |
||||||
|
RenderShadowMap = true |
||||||
|
}; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 默认灰色自发光 |
||||||
|
/// </summary> |
||||||
|
private static PBRMaterial _material = new PBRMaterial |
||||||
|
{ |
||||||
|
AlbedoColor = Black, // 黑色,避免其他光照影响 |
||||||
|
EmissiveColor = LightGray , // LightGray #D3D3D3 |
||||||
|
MetallicFactor = 0.0, // 非金属 |
||||||
|
RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 |
||||||
|
ReflectanceFactor = 0.0, // 无反射 |
||||||
|
ClearCoatStrength = 0.0, // 无清漆效果 |
||||||
|
ClearCoatRoughness = 1.0, // 高粗糙度 |
||||||
|
SurfaceMapSampler = new SamplerStateDescription |
||||||
|
{ |
||||||
|
Filter = Filter.MinMagMipLinear, |
||||||
|
AddressU = TextureAddressMode.Wrap, |
||||||
|
AddressV = TextureAddressMode.Wrap, |
||||||
|
AddressW = TextureAddressMode.Wrap |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#endregion |
||||||
|
} |
||||||
|
public enum SelShowType |
||||||
|
{ |
||||||
|
Border = 0, |
||||||
|
LengthText = 1, |
||||||
|
BorderAngle = 2, |
||||||
|
IsTypePanel = 3, |
||||||
|
SelPanel = 4, |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
using System.Text.Json.Nodes; |
||||||
|
using SharpDX; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Entity; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Enum; |
||||||
|
using SparkClient.Views.UserControl.ViewportData.Helper; |
||||||
|
|
||||||
|
namespace SparkClient.Views.UserControl.ViewportData; |
||||||
|
|
||||||
|
public class ViewportData |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// 钻石编码 |
||||||
|
/// </summary> |
||||||
|
public String DiamondCode { get; set; } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 钻石数据 |
||||||
|
/// </summary> |
||||||
|
public String DiamondData { get; set; } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// 初始化构造 |
||||||
|
/// </summary> |
||||||
|
/// <param name="diamondCode">钻石编码</param> |
||||||
|
/// <param name="diamondData">钻石数据</param> |
||||||
|
public ViewportData(string diamondCode, string diamondData) |
||||||
|
{ |
||||||
|
DiamondCode = diamondCode; |
||||||
|
DiamondData = diamondData; |
||||||
|
} |
||||||
|
/// <summary> |
||||||
|
/// 空白构造 |
||||||
|
/// </summary> |
||||||
|
public ViewportData() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public void LoadData() |
||||||
|
{ |
||||||
|
JsonNode? json = null; |
||||||
|
try |
||||||
|
{ |
||||||
|
json = JsonNode.Parse(DiamondData); |
||||||
|
} |
||||||
|
catch |
||||||
|
{ |
||||||
|
throw new Exception("DiamondData is invalid"); |
||||||
|
} |
||||||
|
|
||||||
|
if(json == null) throw new Exception("DiamondData is invalid"); |
||||||
|
|
||||||
|
try |
||||||
|
{ |
||||||
|
List<Viewport3DTriangleEntity> facets = new List<Viewport3DTriangleEntity>(); |
||||||
|
if (json.AsObject()["facets"] != null) |
||||||
|
{ |
||||||
|
JsonArray jsonArray = json.AsObject()["facets"].AsArray(); |
||||||
|
foreach (var item in jsonArray) |
||||||
|
{ |
||||||
|
var value = CommonHelper.CreateByJsonStr(item.ToString()); |
||||||
|
facets.Add(value); |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
throw new Exception("facets is invalid"); |
||||||
|
} |
||||||
|
|
||||||
|
if (json.AsObject()["positive_direction"] != null) |
||||||
|
{ |
||||||
|
JsonObject positive = json.AsObject()["positive_direction"].AsObject(); |
||||||
|
ViewportManager.PositiveDirection.X = float.Parse(positive["x"]?.ToString() ?? "1.0"); |
||||||
|
ViewportManager.PositiveDirection.Y = float.Parse(positive["z"]?.ToString() ?? "0"); |
||||||
|
ViewportManager.PositiveDirection.Z = float.Parse(positive["y"]?.ToString() ?? "0"); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
ViewportManager.PositiveDirection.X = 1.0f; |
||||||
|
ViewportManager.PositiveDirection.Y = 0f; |
||||||
|
ViewportManager.PositiveDirection.Z = 0; |
||||||
|
} |
||||||
|
|
||||||
|
var midZ = facets.SelectMany(e => new[] { e.Point1.X, e.Point2.X, e.Point3.X }).OrderBy(z => Math.Abs(z)) |
||||||
|
.First(); |
||||||
|
List<Viewport3DTriangleEntity> facetsFinal = new List<Viewport3DTriangleEntity>(); |
||||||
|
foreach (var item in facets) |
||||||
|
{ |
||||||
|
var data = ViewportHelperPro.VectorClockwiseSort(new List<Vector3> |
||||||
|
{ item.Point1, item.Point2, item.Point3 }); |
||||||
|
|
||||||
|
if (item.PlaneType == PlaneType.StarFacet || item.PlaneType == PlaneType.UpperGirdleFacet || |
||||||
|
item.PlaneType == PlaneType.UpperMainFacet || item.PlaneType == PlaneType.TableFacet) |
||||||
|
{ |
||||||
|
item.Point1 = data[0]; |
||||||
|
item.Point2 = data[1]; |
||||||
|
item.Point3 = data[2]; |
||||||
|
} |
||||||
|
else if (item.PlaneType == PlaneType.Girdle) |
||||||
|
{ |
||||||
|
var center = ViewportHelperPro.GetCentroid(data); |
||||||
|
data.Sort((v1, v2) => |
||||||
|
{ |
||||||
|
double angle1 = Math.Atan2(v1.Y - center.Y, v1.X - center.X); |
||||||
|
double angle2 = Math.Atan2(v2.Y - center.Y, v2.X - center.X); |
||||||
|
return angle1.CompareTo(angle2); |
||||||
|
}); |
||||||
|
if (center.Z >= midZ) |
||||||
|
{ |
||||||
|
|
||||||
|
item.Point1 = data[0]; |
||||||
|
item.Point2 = data[1]; |
||||||
|
item.Point3 = data[2]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
item.Point1 = data[2]; |
||||||
|
item.Point2 = data[1]; |
||||||
|
item.Point3 = data[0]; |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
item.Point1 = data[2]; |
||||||
|
item.Point2 = data[1]; |
||||||
|
item.Point3 = data[0]; |
||||||
|
} |
||||||
|
|
||||||
|
item.TriangleCode = CommonHelper.GenerateTriangleCode(item.Point1, item.Point2, item.Point3); |
||||||
|
facetsFinal.Add(item); |
||||||
|
} |
||||||
|
|
||||||
|
ViewportManager.ViewportTriangle.AddRange(facetsFinal); |
||||||
|
// ViewportManager.LoadModelByEntities(facetsFinal); |
||||||
|
} |
||||||
|
catch (Exception ex) |
||||||
|
{ |
||||||
|
throw new Exception(ex.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue