using System; using System.Collections.Generic; using FlaxEngine; using System.IO; using System.Linq; using FlaxEngine.Assertions; using FlaxEngine.Utilities; using Console = Cabrito.Console; namespace Game { public struct Edge { public Vector3 v1, v2; public Edge(Vector3 v1, Vector3 v2) { this.v1 = v1; this.v2 = v2; } public static Edge[] GetEdges(Vector3 v1, Vector3 v2, Vector3 v3) { return new[] { new Edge(v1, v2), new Edge(v2, v3), new Edge(v3, v1), }; } public override bool Equals(object obj) { if (obj is Edge) { var other = (Edge) obj; var d1a = Math.Abs((v1 - other.v1).Length); var d1b = Math.Abs((v1 - other.v2).Length); var d2a = Math.Abs((v2 - other.v2).Length); var d2b = Math.Abs((v2 - other.v1).Length); var eps = 1f; if (d1a < eps && d2a < eps) return true; else if (d1b < eps && d2b < eps) return true; else return false; } return base.Equals(obj); } public static bool operator ==(Edge edge, object obj) { return edge.Equals(obj); } public static bool operator !=(Edge edge, object obj) { return !(edge == obj); } } public class Face { public Vector3 v1, v2, v3; public List halfEdges; public bool visited; public Face(Vector3 v1, Vector3 v2, Vector3 v3) { this.v1 = v1; this.v2 = v2; this.v3 = v3; halfEdges = new List(3); } public Edge[] GetEdges() { return new[] { new Edge(v1, v2), new Edge(v2, v3), new Edge(v3, v1), }; } public float DistanceToPoint(Vector3 point) { Plane plane = new Plane(v1, v2, v3); float distance = (point.X * plane.Normal.X) + (point.Y * plane.Normal.Y) + (point.Z * plane.Normal.Z) + plane.D; return distance / (float) Math.Sqrt( (plane.Normal.X * plane.Normal.X) + (plane.Normal.Y * plane.Normal.Y) + (plane.Normal.Z * plane.Normal.Z)); } } public struct Tetrahedron { public Vector3 v1, v2, v3, v4; public Tetrahedron(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4) { this.v1 = v1; this.v2 = v2; this.v3 = v3; this.v4 = v4; } public Face[] GetFaces() { return new[] { new Face(v1, v2, v3), new Face(v1, v3, v4), new Face(v1, v4, v2), new Face(v2, v4, v3), }; } } public class Q3MapImporter : Script { private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube.map"; //private string mapPath = @"C:\dev\Goake\maps\aerowalk\aerowalk.map"; Model model; public MaterialBase material; const float epsilon = 0.00001f; private List FindHull(List points, Edge edge, Vector3 planeNormal) { // picks the furthest point of the edge, filter out points from the formed triangle, // and recursively repeat this until we have only the outermost points of the convex // hull. if (points.Count == 0) return new List(); // furthest point from edge Vector3 furthest = Vector3.Zero; float furthestDist = 0f; foreach (var vert in points) { var edgeDir = (edge.v2 - edge.v1).Normalized; var closest = edge.v1 + edgeDir * Vector3.Dot(vert - edge.v1, edgeDir); var dist = (vert - closest).Length; if (dist > furthestDist) { furthest = vert; furthestDist = dist; } } List set1 = new List(); List set2 = new List(); var edgeNormal = Vector3.Cross(edge.v2 - edge.v1, planeNormal); for (int i = 0; i < points.Count; i++) { var vert = points[i]; if (vert == edge.v1 || vert == edge.v2 || vert == furthest) continue; var normal1 = new Plane(vert, edge.v1, edge.v2).Normal; var normal2 = new Plane(vert, edge.v2, furthest).Normal; var normal3 = new Plane(vert, furthest, edge.v1).Normal; var d1 = Vector3.Dot(planeNormal, normal1); var d2 = Vector3.Dot(planeNormal, normal2); var d3 = Vector3.Dot(planeNormal, normal3); if ((d1 > 0 && d2 > 0 && d3 > 0) || (d1 < 0 && d2 < 0 && d3 < 0)) { // all triangles formed by these points have similar normals, } else { // normals pointing different directions, eliminate continue; } var norm = (vert - edge.v1).Normalized; var dd = Vector3.Dot(edgeNormal.Normalized, norm); if (dd > epsilon) { set1.Add(vert); } else if (dd < -epsilon) { set2.Add(vert); } } List set = new List(new[] {furthest}); set.AddRange(FindHull(set1, new Edge(edge.v1, furthest), planeNormal)); set.AddRange(FindHull(set2, new Edge(furthest, edge.v2), planeNormal)); return set; } private List QuickHull(List points, Vector3 planeNormal) { // longest pair Vector3 a1 = Vector3.Zero; Vector3 a2 = Vector3.Zero; foreach (var v1 in points) { foreach (var v2 in points) { if ((v2 - v1).LengthSquared > (a2 - a1).LengthSquared) { a1 = v1; a2 = v2; } } } var edgeNormal = Vector3.Cross(a2 - a1, planeNormal); List set1 = new List(); List set2 = new List(); foreach (var vert in points) { if (vert == a1 || vert == a2) continue; var norm = (vert - a1) /*.Normalized*/; var dd = Vector3.Dot(edgeNormal /*.Normalized*/, norm); if (dd > epsilon) { set1.Add(vert); } else if (dd < -epsilon) { set2.Add(vert); } else set1.Add(vert); } List set = new List(new[] {a1, a2}); set.AddRange(FindHull(set1, new Edge(a2, a1), planeNormal)); set.AddRange(FindHull(set2, new Edge(a1, a2), planeNormal)); return set; } private void SortPoints(List points, Vector3 planeNormal) { Vector3 center = Vector3.Zero; foreach (var vert in points) { center += vert; } if (points.Count > 0) center /= points.Count; points.Sort((v1, v2) => { var dot = Vector3.Dot(planeNormal, Vector3.Cross(v1 - center, v2 - center)); if (dot > 0) return 1; else return -1; }); } float PointDistanceFromPlane(Vector3 point, Plane plane) { float distance = (point.X * plane.Normal.X) + (point.Y * plane.Normal.Y) + (point.Z * plane.Normal.Z) + plane.D; return distance / (float) Math.Sqrt( (plane.Normal.X * plane.Normal.X) + (plane.Normal.Y * plane.Normal.Y) + (plane.Normal.Z * plane.Normal.Z)); } private Face[] CreateInitialSimplex(Vector3[] points) { // TODO: more optimal to find first set of points which are not coplanar? // find the longest edge Vector3 v1 = Vector3.Zero; Vector3 v2 = Vector3.Zero; foreach (var p1 in points) { foreach (var p2 in points) { if ((p2 - p1).LengthSquared > (v2 - v1).LengthSquared) { v1 = p1; v2 = p2; } } } Assert.IsTrue(v1 != v2, "a1 != a2"); // find the furthest point from the edge to form a face Vector3 v3 = Vector3.Zero; float furthestDist = 0f; foreach (var point in points) { //if (vert == a1 || vert == a2) // continue; var edgeDir = (v2 - v1).Normalized; var closest = v1 + edgeDir * Vector3.Dot(point - v1, edgeDir); var dist = (point - closest).Length; if (dist > furthestDist) { v3 = point; furthestDist = dist; } } Assert.IsTrue(v3 != v1, "furthest != a1"); Assert.IsTrue(v3 != v2, "furthest != a2"); // find the furthest point from he face Plane plane = new Plane(v1, v2, v3); Vector3 v4 = Vector3.Zero; float fourthDist = 0f; foreach (var point in points) { if (point == v1 || point == v2 || point == v3) continue; float distance = PointDistanceFromPlane(point, plane); if (Math.Abs(distance) > fourthDist) { v4 = point; fourthDist = distance; } } // make sure the tetrahedron is in counter-clockwise order if (fourthDist > 0) { return new Face[] { new Face(v1, v3, v2), new Face(v1, v4, v3), new Face(v1, v2, v4), new Face(v2, v3, v4), }; } else { return new Face[] { new Face(v1, v2, v3), new Face(v1, v3, v4), new Face(v1, v4, v2), new Face(v2, v4, v3), }; } } public class HalfEdge { public Face face; //public Face oppositeFace; public HalfEdge opposite; public HalfEdge previous, next; public Edge edge; //public bool horizonVisited; public HalfEdge(Edge edge, Face face) { this.edge = edge; this.face = face; face.halfEdges.Add(this); } } //http://algolist.ru/maths/geom/convhull/qhull3d.php private List QuickHull2(Vector3[] points) { var tetrahedron = CreateInitialSimplex(points); List> outsideSet = new List>(); foreach (var point in points) { foreach (Face face in tetrahedron) { float distance = face.DistanceToPoint(point); if (Math.Abs(distance) < epsilon) { // point is in the plane, this gets merged distance = distance; } else if (distance > 0) { //side.outsideSet.Add(point); outsideSet.Add(new Tuple(face, point)); break; } } } // all points not in side.outsideSet are inside in "inside" set // create half-edges foreach (var face in tetrahedron) { var halfEdges = new List(3); foreach (var edge in face.GetEdges()) halfEdges.Add(new HalfEdge(edge, face)); for (int i = 0; i < halfEdges.Count; i++) { halfEdges[i].previous = halfEdges[(i + 2) % 3]; halfEdges[i].next = halfEdges[(i + 1) % 3]; } } // verify { var tetrapoints = new List(); foreach (var face in tetrahedron) { foreach (var he in face.halfEdges) { if (!tetrapoints.Contains(he.edge.v1)) tetrapoints.Add(he.edge.v1); } } foreach (var point in tetrapoints) { int foundFaces = 0; foreach (var face in tetrahedron) { if (face.v1 == point) foundFaces++; else if (face.v2 == point) foundFaces++; else if (face.v3 == point) foundFaces++; } Assert.IsTrue(foundFaces == 3, "foundFaces == 3"); } } foreach (var face in tetrahedron) { Assert.IsTrue(face.halfEdges.Count == 3, "side.halfEdges.Count == 3"); foreach (var halfEdge in face.halfEdges) { bool found = false; foreach (var otherFace in tetrahedron) { if (found) break; if (face == otherFace) continue; foreach (var otherHalfEdge in otherFace.halfEdges) { if (otherHalfEdge.opposite != null) continue; if (halfEdge.edge == otherHalfEdge.edge) { halfEdge.opposite = otherHalfEdge; otherHalfEdge.opposite = halfEdge; //halfEdge.oppositeFace = otherFace; //otherHalfEdge.oppositeFace = face; found = true; break; } } } Assert.IsTrue(halfEdge.previous != null, "halfEdge.previous != null"); Assert.IsTrue(halfEdge.next != null, "halfEdge.next != null"); Assert.IsTrue(halfEdge.opposite != null, "halfEdge.opposite != null"); //Assert.IsTrue(halfEdge.oppositeFace != null, "halfEdge.oppositeFace != null"); Assert.IsTrue(halfEdge.opposite.face != null, "halfEdge.opposite.face != null"); //Assert.IsTrue(halfEdge.oppositeFace == halfEdge.opposite.face, "halfEdge.oppositeFace == halfEdge.opposite.face"); } } // grow hull List> unclaimedPoints2 = new List>(); List horizonEdges = new List(); List hullFaces = new List(); hullFaces.AddRange(tetrahedron); // stop when none of the faces have any visible outside points int iterCount = 0; while (outsideSet.Count > 0) { iterCount++; Tuple pointToAdd = null; Face pointFace = null; // get furthest point in outside set /*for (int sideIndex = 0; sideIndex < sides.Count; sideIndex++) { TetrahedronSide side = sides[sideIndex]; if (side.outsideSet.Count == 0) continue; float furthestDist = 0f; foreach (var point in side.outsideSet) { Assert.IsTrue(point != side.face.v1, "point != side.face.v1"); Assert.IsTrue(point != side.face.v2, "point != side.face.v2"); Assert.IsTrue(point != side.face.v3, "point != side.face.v3"); float distance = PointDistanceFromPlane(point, side.plane); if (Math.Abs(distance) > furthestDist) { pointToAdd = point; pointSide = side; furthestDist = distance; } } }*/ float furthestDist = 0f; foreach (var fp in outsideSet) { var face = fp.Item1; var point = fp.Item2; float distance = face.DistanceToPoint(point); if (Math.Abs(distance) > furthestDist) { pointToAdd = fp; pointFace = face; furthestDist = distance; } } Assert.IsTrue(pointToAdd != null, "pointToAdd != null"); outsideSet.Remove(pointToAdd); foreach (var face in hullFaces) face.visited = false; var hullFacesNew = new List(); AddPointToHull(pointToAdd.Item2, pointFace, unclaimedPoints2, outsideSet, horizonEdges, hullFacesNew); // remove lit/seen/visited faces, their points were added to unclaimed points for (int i = 0; i < hullFaces.Count; i++) { if (hullFaces[i].visited) { hullFaces.RemoveAt(i); i--; } } hullFaces.AddRange(hullFacesNew); //outsideSet.AddRange(unclaimedPoints2); //unclaimedPoints2.Clear(); outsideSet = new List>(unclaimedPoints2); unclaimedPoints2.Clear(); if (iterCount >= 3) break; if (hullFaces.Count > 1000 || iterCount > 1000) Assert.Fail("overflow"); } List hullPoints = new List(hullFaces.Count * 3); foreach (var face in hullFaces) { hullPoints.Add(face.v1); hullPoints.Add(face.v2); hullPoints.Add(face.v3); } return hullPoints; } private void AddPointToHull(Vector3 point, Face face, List> unclaimedPoints2, List> outsideSet, List horizonEdges, List hullFaces) { horizonEdges.Clear(); CalculateHorizon(face, point, unclaimedPoints2, outsideSet, horizonEdges, face.halfEdges[0]); // create new faces if (horizonEdges.Count > 0) { List newFaces = new List(); HalfEdge first = horizonEdges.First(); HalfEdge prev = null; foreach (var edge in horizonEdges) { var newFace = new Face(point, edge.edge.v1, edge.edge.v2); var newEdges = new List(); foreach (var ne in newFace.GetEdges()) newEdges.Add(new HalfEdge(ne, newFace)); for (int i = 0; i < newEdges.Count; i++) { newEdges[i].previous = newEdges[(i + 2) % 3]; newEdges[i].next = newEdges[(i + 1) % 3]; } if (prev != null) { var prevEdge = newFaces.Last().halfEdges.Last(); var lastEdge = newEdges.First(); lastEdge.opposite = prevEdge; prevEdge.opposite = lastEdge; } //edge.face = newFace; newEdges[1].opposite = edge.opposite; newFaces.Add(newFace); prev = edge; } if (prev != null) { var lastEdge = newFaces.Last().halfEdges.Last(); var firstEdge = newFaces.First().halfEdges.First(); lastEdge.opposite = firstEdge; firstEdge.opposite = lastEdge; //first.previous.opposite = prev.next; //prev.next.opposite = first.previous; } hullFaces.AddRange(newFaces); // verify foreach (var newFace in hullFaces) { Assert.IsTrue(newFace.halfEdges.Count == 3, "AddPointToHull: side.halfEdges.Count == 3"); foreach (var halfEdge in newFace.halfEdges) { /*bool found = false; foreach (var otherFace in hullFaces) { if (found) break; if (newFace == otherFace) continue; foreach (var otherHalfEdge in otherFace.halfEdges) { if (otherHalfEdge.opposite != null) continue; if (halfEdge.edge == otherHalfEdge.edge) { halfEdge.opposite = otherHalfEdge; otherHalfEdge.opposite = halfEdge; //halfEdge.oppositeFace = otherFace; //otherHalfEdge.oppositeFace = face; found = true; break; } } }*/ Assert.IsTrue(halfEdge.previous != null, "AddPointToHull: halfEdge.previous != null"); Assert.IsTrue(halfEdge.next != null, "AddPointToHull: halfEdge.next != null"); Assert.IsTrue(halfEdge.opposite != null, "AddPointToHull: halfEdge.opposite != null"); //Assert.IsTrue(halfEdge.oppositeFace != null, "halfEdge.oppositeFace != null"); Assert.IsTrue(halfEdge.opposite.face != null, "AddPointToHull: halfEdge.opposite.face != null"); //Assert.IsTrue(halfEdge.oppositeFace == halfEdge.opposite.face, "halfEdge.oppositeFace == halfEdge.opposite.face"); } } } } // calculates the outermost edges of the geometry seen from the eyePoint private void CalculateHorizon(Face face, Vector3 eyePoint, List> unclaimedPoints2, List> outsideSet, List horizonEdges, HalfEdge currentEdge) { face.visited = true; // move outside points of this face to unclaimed points foreach (var set in outsideSet) { if (set.Item1 == face) unclaimedPoints2.Add(set); } HalfEdge startingEdge = currentEdge; do { Face oppositeFace = currentEdge.opposite.face; if (!oppositeFace.visited) { if (oppositeFace.DistanceToPoint(eyePoint) > epsilon) { // positive distance means this is visible CalculateHorizon(oppositeFace, eyePoint, unclaimedPoints2, outsideSet, horizonEdges, currentEdge.opposite); } else { if (!horizonEdges.Contains(currentEdge)) horizonEdges.Add(currentEdge); } } currentEdge = currentEdge.next; } while (currentEdge != startingEdge); } public override void OnStart() { byte[] mapChars = File.ReadAllBytes(mapPath); var root = MapParser.Parse(mapChars); const float cs = 300f; Vector3[] cubePoints = new[] { new Vector3(-cs, -cs, -cs), new Vector3(cs, -cs, -cs), new Vector3(-cs, cs, -cs), new Vector3(cs, cs, -cs), new Vector3(-cs, -cs, cs), new Vector3(cs, -cs, cs), new Vector3(-cs, cs, cs), new Vector3(cs, cs, cs), }; Vector3[] cubeVerts = new[] { cubePoints[2], cubePoints[3], cubePoints[1], cubePoints[2], cubePoints[1], cubePoints[0], cubePoints[0], cubePoints[1], cubePoints[4], cubePoints[1], cubePoints[5], cubePoints[4], cubePoints[7], cubePoints[5], cubePoints[1], cubePoints[7], cubePoints[1], cubePoints[3], cubePoints[6], cubePoints[7], cubePoints[3], cubePoints[6], cubePoints[3], cubePoints[2], cubePoints[4], cubePoints[6], cubePoints[2], cubePoints[4], cubePoints[2], cubePoints[0], cubePoints[4], cubePoints[5], cubePoints[6], cubePoints[5], cubePoints[7], cubePoints[6], }; Vector3[] cubeVerts2 = new[] { cubePoints[2], cubePoints[3], cubePoints[1], cubePoints[0], cubePoints[0], cubePoints[1], cubePoints[4], cubePoints[5], cubePoints[7], cubePoints[5], cubePoints[1], cubePoints[3], cubePoints[6], cubePoints[7], cubePoints[3], cubePoints[2], cubePoints[4], cubePoints[6], cubePoints[2], cubePoints[0], cubePoints[4], cubePoints[5], cubePoints[6], cubePoints[7], }; List vertices = new List(); // convert planes vertices = QuickHull2(cubePoints); //Assert.IsTrue(vertices.Count == 36, "vertices.Count == 36, count: " + vertices.Count); if (false) foreach (var brush in root.entities[0].brushes) { List brushVertices = new List(cubeVerts); //foreach (var plane in new [] { brush.planes.First() }) foreach (var plane in brush.planes.Skip(0).Take(1)) //foreach (var plane in brush.planes) { Plane p = new Plane(plane.v1, plane.v2, plane.v3); List clippedVerts = new List(); List newBrushVertices = new List(); List faceVertices = new List(); if (true) { Func isFront = (f) => f > 0; Func isBack = (f) => f < 0; for (int i = 0; i < brushVertices.Count; i++) { int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1); Vector3 start = brushVertices[i]; Vector3 end = brushVertices[i2]; var d1 = (start.X * p.Normal.X) + (start.Y * p.Normal.Y) + (start.Z * p.Normal.Z) + p.D; var d2 = (end.X * p.Normal.X) + (end.Y * p.Normal.Y) + (end.Z * p.Normal.Z) + p.D; if (isBack(d1)) { // include back faceVertices.Add(start); } if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2)) { //if (isFront(d2)) { // include clip Ray ray2 = new Ray(start, (end - start).Normalized); if (p.Intersects(ref ray2, out Vector3 intersect2)) { faceVertices.Add(intersect2); clippedVerts.Add(intersect2); } else d1 = d1; } } if (i % 3 == 2) { if (faceVertices.Count == 4) { // quad, triangulize /*newBrushVertices.Add(faceVertices[0]); newBrushVertices.Add(faceVertices[1]); newBrushVertices.Add(faceVertices[2]); newBrushVertices.Add(faceVertices[2]); newBrushVertices.Add(faceVertices[3]); newBrushVertices.Add(faceVertices[0]);*/ var hullPoints = QuickHull(faceVertices, -p.Normal); Assert.IsTrue(hullPoints.Count > 3, "QuickHull reduced quad to triangle!"); SortPoints(hullPoints, p.Normal); for (int j = 1; j <= hullPoints.Count - 2; j++) { Vector3 v1 = hullPoints[0]; Vector3 v2 = hullPoints[j]; Vector3 v3 = hullPoints[j + 1]; var normal = new Plane(v1, v2, v3).Normal; if (Vector3.Dot(p.Normal, normal) < 0) { v2 = hullPoints[j + 1]; v3 = hullPoints[j]; } newBrushVertices.Add(v1); newBrushVertices.Add(v2); newBrushVertices.Add(v3); } } else if (faceVertices.Count == 3) { // ok, either nothing was clipped or two got clipped newBrushVertices.AddRange(faceVertices); } else if (faceVertices.Count == 0) { // ok, everything got clipped d1 = d1; } else d1 = d1; faceVertices.Clear(); } } if (true) { List clippedVerts2 = new List(); foreach (var v in clippedVerts) { if (!clippedVerts2.Any(x => (v - x).Length < 0.01)) clippedVerts2.Add(v); } var hullPoints = QuickHull(clippedVerts2, -p.Normal); SortPoints(hullPoints, p.Normal); for (int i = 1; i <= hullPoints.Count - 2; i++) { Vector3 v1 = hullPoints[0]; Vector3 v2 = hullPoints[i]; Vector3 v3 = hullPoints[i + 1]; var normal = new Plane(v1, v2, v3).Normal; if (Vector3.Dot(p.Normal, normal) < 0) { v2 = hullPoints[i + 1]; v3 = hullPoints[i]; } newBrushVertices.Add(v1); newBrushVertices.Add(v2); newBrushVertices.Add(v3); } /*var brushPoints = QuickHull(newBrushVertices); newBrushVertices.Clear(); for (int i = 1; i <= brushPoints.Count - 2; i++) { Vector3 v1 = hullPoints[0]; Vector3 v2 = hullPoints[i]; Vector3 v3 = hullPoints[i+1]; var normal = new Plane(v1, v2, v3).Normal; if (Vector3.Dot(p.Normal, normal) < 0) { v2 = hullPoints[i + 1]; v3 = hullPoints[i]; } newBrushVertices.Add(v1); newBrushVertices.Add(v2); newBrushVertices.Add(v3); //flip = !flip; }*/ } } Assert.IsTrue(newBrushVertices.Count % 3 == 0, "invalid amount of vertices: " + newBrushVertices.Count); Assert.IsTrue(newBrushVertices.Count > 0, "brush was clipped completely, vertices: " + newBrushVertices.Count); brushVertices.Clear(); brushVertices.AddRange(newBrushVertices); Console.Print("plane verts: " + newBrushVertices.Count); } vertices.AddRange(brushVertices); } /*Actor levelActor = Actor.AddChild(); levelActor.Name = "LevelGeometry"; int brushIndex = 1; foreach (var brush in root.entities[0].brushes) { BoxBrush boxBrush = levelActor.AddChild(); boxBrush.Size = new Vector3(1000f); boxBrush.Mode = BrushMode.Additive; boxBrush.Name = "Brush " + brushIndex; int planeIndex = 1; foreach (var brushPlane in brush.planes) { Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3); BoxBrush planeBrush = levelActor.AddChild(); planeBrush.Mode = BrushMode.Subtractive; planeBrush.Position = -plane.Normal * plane.D; planeBrush.Orientation = Quaternion.LookRotation(plane.Normal); planeBrush.Position = planeBrush.Position - (planeBrush.Transform.Forward * 1500f); planeBrush.Size = new Vector3(3000f); planeBrush.Name = "ClipPlane " + brushIndex + " - " + planeIndex; planeIndex++; } break; brushIndex++; } levelActor.Orientation = Quaternion.Euler(90f, 0, 0); Scene.BuildCSG(200);*/ model = Content.CreateVirtualAsset(); model.SetupLODs(new int[] {1}); { var mesh = model.LODs[0].Meshes[0]; List triangles = new List(vertices.Count); for (int i = 0; i < vertices.Count; i++) triangles.Add(i); Console.Print("verts: " + vertices.Count); mesh.UpdateMesh(vertices.ToArray(), triangles.ToArray(), vertices.ToArray()); } StaticModel childModel = Actor.AddChild(); childModel.Name = "MapModel"; childModel.Model = model; childModel.SetMaterial(0, material); } public override void OnEnable() { // Here you can add code that needs to be called when script is enabled (eg. register for events) } public override void OnDisable() { // Here you can add code that needs to be called when script is disabled (eg. unregister from events) } public override void OnUpdate() { // Here you can add code that needs to be called every frame } public override void OnDestroy() { Destroy(ref model); base.OnDestroy(); } } }