private void RenderLine(Vector3 start, Vector3 end, Plane plane, Color color, ICamera camera, Graphics graphics) { var line = new Line(start, end); var cls = line.ClassifyAgainstPlane(plane); if (cls == PlaneClassification.Back) { return; } if (cls == PlaneClassification.Spanning) { var isect = plane.GetIntersectionPoint(line, true); var first = plane.OnPlane(line.Start) > 0 ? line.Start : line.End; if (!isect.HasValue) { return; } line = new Line(first, isect.Value); } var st = camera.WorldToScreen(line.Start); var en = camera.WorldToScreen(line.End); using (var p = new Pen(color, 2)) { graphics.DrawLine(p, st.X, st.Y, en.X, en.Y); } }
public void FaceLineIntersectionTest() { var plane = new Plane(new Coordinate(0, 0, 1), 100); var face = new Face(1) {Plane = plane}; var coords = new[] { new Coordinate(-100, -100, 100), new Coordinate(100, -100, 100), new Coordinate(100, 100, 100), new Coordinate(-100, 100, 100) }; face.Vertices.AddRange(coords.Select(x => new Vertex(x, face))); face.CalculateTextureCoordinates(true); var passLine = new Line(new Coordinate(0, 0, 0), new Coordinate(0, 0, 200)); var reversePassLine = passLine.Reverse(); var failSegment = new Line(new Coordinate(0, 0, 0), new Coordinate(0, 0, 50)); var failLine = new Line(new Coordinate(0, 0, 0), new Coordinate(1, 0, 0)); var outsideFaceLine = new Line(new Coordinate(200, 0, 0), new Coordinate(200, 0, 200)); var pass1 = face.GetIntersectionPoint(passLine); var fail1 = face.GetIntersectionPoint(reversePassLine); var fail2 = face.GetIntersectionPoint(failSegment); var fail3 = face.GetIntersectionPoint(failLine); var fail4 = face.GetIntersectionPoint(outsideFaceLine); Assert.IsNotNull(pass1); Assert.IsNull(fail1); Assert.IsNull(fail2); Assert.IsNull(fail3); Assert.IsNull(fail4); }
public override void Dispose() { _plane = null; _objects = null; _parents = null; base.Dispose(); }
public void PlaneConstructionTest() { var p1 = new Coordinate(-100, -100, 100); var p2 = new Coordinate(100, -100, 100); var p3 = new Coordinate(100, 100, 100); var p4 = new Coordinate(0, 0, 0); var refPlane = new Plane(new Coordinate(0, 0, 1), 100); var plane = new Plane(p3, p2, p1); var o1 = refPlane.OnPlane(p1); var o2 = plane.OnPlane(p1); var o3 = refPlane.OnPlane(p4); var o4 = plane.OnPlane(p4); Assert.IsTrue(o1 == 0); Assert.IsTrue(o2 == 0); Assert.IsFalse(o3 == 0); Assert.IsFalse(o4 == 0); Assert.AreEqual(refPlane.A, plane.A); Assert.AreEqual(refPlane.B, plane.B); Assert.AreEqual(refPlane.C, plane.C); Assert.AreEqual(refPlane.D, plane.D); var plane2 = new Plane(new Coordinate(-192, 704, 192), new Coordinate(-192, 320, 192), new Coordinate(-192, 320, -192)); }
private void RenderLine(Vector3 start, Vector3 end, Plane plane, Color color, ICamera camera, I2DRenderer im) { var line = new Line(start, end); var cls = line.ClassifyAgainstPlane(plane); if (cls == PlaneClassification.Back) { return; } if (cls == PlaneClassification.Spanning) { var isect = plane.GetIntersectionPoint(line, true); var first = plane.OnPlane(line.Start) > 0 ? line.Start : line.End; if (!isect.HasValue) { return; } line = new Line(first, isect.Value); } var st = camera.WorldToScreen(line.Start); var en = camera.WorldToScreen(line.End); im.AddLine(st.ToVector2(), en.ToVector2(), color, 2); }
public static void AlignWithTexture(this Texture tex, Plane currentPlane, Plane alignToPlane, Texture alignToTexture) { // Get reference values for the axes var refU = alignToTexture.UAxis; var refV = alignToTexture.VAxis; // Reference points in the texture plane to use for shifting later on var refX = alignToTexture.UAxis * alignToTexture.XShift * alignToTexture.XScale; var refY = alignToTexture.VAxis * alignToTexture.YShift * alignToTexture.YScale; // Two non-parallel planes intersect at an edge. We want the textures on this face // to line up with the textures on the provided face. To do this, we rotate the texture // normal on the provided face around the intersection edge to get the new texture axes. // Then we rotate the texture reference point around this edge as well to get the new shift values. // The scale values on both faces will always end up being the same value. // Find the intersection edge vector var intersectionEdge = alignToPlane.Normal.Cross(currentPlane.Normal); // If the planes are parallel, the texture doesn't need any rotation - just different shift values. if (Math.Abs(intersectionEdge.Length()) > 0.01f) { // Create a plane using the intersection edge as the normal var intersectionPlane = new Plane(intersectionEdge, 0); var intersect = Plane.Intersect(alignToPlane, currentPlane, intersectionPlane); if (intersect != null) { // Since the intersection plane is perpendicular to both face planes, we can find the angle // between the two planes (the align plane and the plane of this face) by projecting // the normals of the planes onto the perpendicular plane and taking the cross product. // Project the two normals onto the perpendicular plane var apNormal = intersectionPlane.Project(alignToPlane.Normal).Normalise(); var cpNormal = intersectionPlane.Project(currentPlane.Normal).Normalise(); // Get the angle between the projected normals var dot = Math.Round(apNormal.Dot(cpNormal), 4); var angle = (float)Math.Acos(dot); // A.B = cos(angle) // Rotate the texture axis by the angle around the intersection edge var transform = Matrix4x4.CreateFromAxisAngle(intersectionEdge.Normalise(), angle); refU = transform.Transform(refU); refV = transform.Transform(refV); // Rotate the texture reference points as well, but around the intersection line, not the origin refX = transform.Transform(refX + intersect.Value) - intersect.Value; refY = transform.Transform(refY + intersect.Value) - intersect.Value; } } // Convert the reference points back to get the final values tex.Rotation = 0; tex.UAxis = refU; tex.VAxis = refV; tex.XShift = refU.Dot(refX) / alignToTexture.XScale; tex.YShift = refV.Dot(refY) / alignToTexture.YScale; tex.XScale = alignToTexture.XScale; tex.YScale = alignToTexture.YScale; }
public Clip(IEnumerable<Solid> objects, Plane plane, bool keepFront, bool keepBack) { _objects = objects.Where(x => x.IsValid()).ToList(); _plane = plane; _keepFront = keepFront; _keepBack = keepBack; _firstRun = true; }
/// <summary> /// Determines if this line is behind, in front, or spanning a plane. /// </summary> /// <param name="p">The plane to test against</param> /// <returns>A PlaneClassification value.</returns> public PlaneClassification ClassifyAgainstPlane(Plane p) { var start = p.OnPlane(Start); var end = p.OnPlane(End); if (start == 0 && end == 0) return PlaneClassification.OnPlane; if (start <= 0 && end <= 0) return PlaneClassification.Back; if (start >= 0 && end >= 0) return PlaneClassification.Front; return PlaneClassification.Spanning; }
/// <summary> /// Same as Plane.GetClosestAxisToNormal(), but prioritises the axes Z, X, Y. /// </summary> /// <param name="plane">Input plane</param> /// <returns>Vector3.UnitX, Vector3.UnitY, or Vector3.UnitZ depending on the plane's normal</returns> private static Vector3 QuakeEdClosestAxisToNormal(Plane plane) { var norm = plane.Normal.Absolute(); if (norm.Z >= norm.X && norm.Z >= norm.Y) { return(Vector3.UnitZ); } if (norm.X >= norm.Y) { return(Vector3.UnitX); } return(Vector3.UnitY); }
/// <summary> /// Determines if this polygon is behind, in front, or spanning a plane. /// </summary> /// <param name="p">The plane to test against</param> /// <returns>A PlaneClassification value.</returns> public PlaneClassification ClassifyAgainstPlane(Plane p) { int front = 0, back = 0, onplane = 0, count = Vertices.Count; foreach (var test in Vertices.Select(p.OnPlane)) { // Vertices on the plane are both in front and behind the plane in this context if (test <= 0) back++; if (test >= 0) front++; if (test == 0) onplane++; } if (onplane == count) return PlaneClassification.OnPlane; if (front == count) return PlaneClassification.Front; if (back == count) return PlaneClassification.Back; return PlaneClassification.Spanning; }
/// <summary> /// Creates a polygon from a plane and a radius. /// Expands the plane to the radius size to create a large polygon with 4 vertices. /// </summary> /// <param name="plane">The polygon plane</param> /// <param name="radius">The polygon radius</param> public Polygon(Plane plane, decimal radius = 1000000m) { Plane = plane; // Get aligned up and right axes to the plane var direction = Plane.GetClosestAxisToNormal(); var tempV = direction == Coordinate.UnitZ ? -Coordinate.UnitY : -Coordinate.UnitZ; var up = tempV.Cross(Plane.Normal).Normalise(); var right = Plane.Normal.Cross(up).Normalise(); Vertices = new List<Coordinate> { plane.PointOnPlane + right + up, // Top right plane.PointOnPlane - right + up, // Top left plane.PointOnPlane - right - up, // Bottom left plane.PointOnPlane + right - up, // Bottom right }; Expand(radius); }
private void AddLine(CircleType type, Vector3 start, Vector3 end, Plane test, CachedLines cache) { var line = new Line(start, end); var cls = line.ClassifyAgainstPlane(test); if (cls == PlaneClassification.Back) { return; } if (cls == PlaneClassification.Spanning) { var isect = test.GetIntersectionPoint(line, true); var first = test.OnPlane(line.Start) > 0 ? line.Start : line.End; if (isect.HasValue) { line = new Line(first, isect.Value); } } cache.Cache[type].Add(new Line(cache.Viewport.Camera.WorldToScreen(line.Start), cache.Viewport.Camera.WorldToScreen(line.End))); }
private void PerformClip(MapDocument document) { var objects = document.Selection.OfType <Solid>().ToList(); if (!objects.Any()) { return; } var plane = new Plane(_clipPlanePoint1.Value, _clipPlanePoint2.Value, _clipPlanePoint3.Value); var clip = new Transaction(); var found = false; foreach (var solid in objects) { solid.Split(document.Map.NumberGenerator, plane, out var backSolid, out var frontSolid); found = true; // Remove the clipped solid clip.Add(new Detatch(solid.Hierarchy.Parent.ID, solid)); if (_side != ClipSide.Back && frontSolid != null) { // Add front solid clip.Add(new Attach(solid.Hierarchy.Parent.ID, frontSolid)); } if (_side != ClipSide.Front && backSolid != null) { // Add back solid clip.Add(new Attach(solid.Hierarchy.Parent.ID, backSolid)); } } if (found) { MapDocumentOperation.Perform(document, clip); } }
public void PlaneLineIntersectionTest() { var plane = new Plane(new Coordinate(0, 0, 1), 100); var passLine = new Line(new Coordinate(0, 0, 0), new Coordinate(0, 0, 200)); var reversePassLine = passLine.Reverse(); var failSegment = new Line(new Coordinate(0, 0, 0), new Coordinate(0, 0, 50)); var failLine = new Line(new Coordinate(0, 0, 0), new Coordinate(1, 0, 0)); var pass1 = plane.GetIntersectionPoint(passLine); var pass2 = plane.GetIntersectionPoint(reversePassLine, true); var pass3 = plane.GetIntersectionPoint(failSegment, false, true); var fail1 = plane.GetIntersectionPoint(reversePassLine); var fail2 = plane.GetIntersectionPoint(failSegment); var fail3 = plane.GetIntersectionPoint(failLine); Assert.IsNotNull(pass1); Assert.IsNotNull(pass2); Assert.IsNotNull(pass3); Assert.IsNull(fail1); Assert.IsNull(fail2); Assert.IsNull(fail3); }
/// <summary> /// Creates a polygon from a list of points /// </summary> /// <param name="vertices">The vertices of the polygon</param> public Polygon(IEnumerable<Coordinate> vertices) { Vertices = vertices.ToList(); Plane = new Plane(Vertices[0], Vertices[1], Vertices[2]); Simplify(); }
/// <summary> /// Splits this polygon by a clipping plane, discarding the front side. /// The original polygon is modified to be the back side of the split. /// </summary> /// <param name="clip">The clipping plane</param> public void Split(Plane clip) { Polygon front, back; if (Split(clip, out back, out front)) { Unclone(back); } }
/// <summary> /// Splits this solid into two solids by intersecting against a plane. /// </summary> /// <param name="plane">The splitting plane</param> /// <param name="back">The back side of the solid</param> /// <param name="front">The front side of the solid</param> /// <param name="generator">The IDGenerator to use</param> /// <returns>True if the plane splits the solid, false if the plane doesn't intersect</returns> public bool Split(Plane plane, out Solid back, out Solid front, IDGenerator generator) { back = front = null; // Check that this solid actually spans the plane var classify = Faces.Select(x => x.ClassifyAgainstPlane(plane)).Distinct().ToList(); if (classify.All(x => x != PlaneClassification.Spanning)) { if (classify.Any(x => x == PlaneClassification.Back)) back = this; else if (classify.Any(x => x == PlaneClassification.Front)) front = this; return false; } var backPlanes = new List<Plane> { plane }; var frontPlanes = new List<Plane> { new Plane(-plane.Normal, -plane.DistanceFromOrigin) }; foreach (var face in Faces) { var classification = face.ClassifyAgainstPlane(plane); if (classification != PlaneClassification.Back) frontPlanes.Add(face.Plane); if (classification != PlaneClassification.Front) backPlanes.Add(face.Plane); } back = CreateFromIntersectingPlanes(backPlanes, generator); front = CreateFromIntersectingPlanes(frontPlanes, generator); CopyBase(back, generator); CopyBase(front, generator); front.Faces.Union(back.Faces).ToList().ForEach(x => { x.Texture = Faces[0].Texture.Clone(); x.AlignTextureToFace(); x.Colour = Colour; }); // Restore textures (match the planes up on each face) foreach (var orig in Faces) { foreach (var face in back.Faces) { var classification = face.ClassifyAgainstPlane(orig.Plane); if (classification != PlaneClassification.OnPlane) continue; face.Texture = orig.Texture.Clone(); break; } foreach (var face in front.Faces) { var classification = face.ClassifyAgainstPlane(orig.Plane); if (classification != PlaneClassification.OnPlane) continue; face.Texture = orig.Texture.Clone(); break; } } front.Faces.Union(back.Faces).ToList().ForEach(x => x.CalculateTextureCoordinates(true)); return true; }
public override void MouseDown(ViewportBase viewport, ViewportEvent e) { // switch (_state) { case SketchState.None: // nothin break; case SketchState.Ready: if (e.Button != MouseButtons.Left) break; _drawing = new Box(_intersection, _intersection); _state = SketchState.DrawingBase; break; case SketchState.DrawingBase: if (e.Button == MouseButtons.Right) { // Cancel _state = SketchState.None; _drawing = null; } else if (e.Button == MouseButtons.Left) { _drawing = new Box(_drawing.Start, _intersection); _volumePlane = new Plane(new Coordinate(_drawing.End.X, _drawing.Start.Y, _drawing.Start.Z), _drawing.End, _drawing.End + _currentFace.Plane.Normal); _state = SketchState.DrawingVolume; } break; case SketchState.DrawingVolume: if (e.Button == MouseButtons.Right) { _state = SketchState.DrawingBase; _volumePlane = null; } else if (e.Button == MouseButtons.Left) { CreateBrush(new Box(new[] {_drawing.Start, _drawing.End})); _drawing = null; _volumePlane = null; _state = SketchState.None; } break; default: throw new ArgumentOutOfRangeException(); } }
public virtual void Transform(IUnitTransformation transform, TransformFlags flags) { foreach (var t in Vertices) { t.Location = transform.Transform(t.Location); } Plane = new Plane(Vertices[0].Location, Vertices[1].Location, Vertices[2].Location); Colour = Colour; if (flags.HasFlag(TransformFlags.TextureScalingLock) && Texture.Texture != null) { // Make a best-effort guess of retaining scaling. All bets are off during skew operations. // Transform the current texture axes var origin = transform.Transform(Coordinate.Zero); var ua = transform.Transform(Texture.UAxis) - origin; var va = transform.Transform(Texture.VAxis) - origin; // Multiply the scales by the magnitudes (they were normals before the transform operation) Texture.XScale *= ua.VectorMagnitude(); Texture.YScale *= va.VectorMagnitude(); } { // Transform the texture axes and move them back to the origin var origin = transform.Transform(Coordinate.Zero); var ua = transform.Transform(Texture.UAxis) - origin; var va = transform.Transform(Texture.VAxis) - origin; // Only do the transform if the axes end up being not perpendicular // Otherwise just make a best-effort guess, same as the scaling lock if (Math.Abs(ua.Dot(va)) < 0.0001m && DMath.Abs(Plane.Normal.Dot(ua.Cross(va).Normalise())) > 0.0001m) { Texture.UAxis = ua; Texture.VAxis = va; } else { AlignTextureToFace(); } if (flags.HasFlag(TransformFlags.TextureLock) && Texture.Texture != null) { // Check some original reference points to see how the transform mutates them var scaled = (transform.Transform(Coordinate.One) - transform.Transform(Coordinate.Zero)).VectorMagnitude(); var original = (Coordinate.One - Coordinate.Zero).VectorMagnitude(); // Ignore texture lock when the transformation contains a scale if (DMath.Abs(scaled - original) <= 0.01m) { // Calculate the new shift values based on the UV values of the vertices var vtx = Vertices[0]; Texture.XShift = Texture.Texture.Width * vtx.TextureU - (vtx.Location.Dot(Texture.UAxis)) / Texture.XScale; Texture.YShift = Texture.Texture.Height * vtx.TextureV - (vtx.Location.Dot(Texture.VAxis)) / Texture.YScale; } } } CalculateTextureCoordinates(true); UpdateBoundingBox(); }
public void AlignTextureWithFace(Face face) { // Get reference values for the axes var refU = face.Texture.UAxis; var refV = face.Texture.VAxis; // Reference points in the texture plane to use for shifting later on var refX = face.Texture.UAxis * face.Texture.XShift * face.Texture.XScale; var refY = face.Texture.VAxis * face.Texture.YShift * face.Texture.YScale; // Two non-parallel planes intersect at an edge. We want the textures on this face // to line up with the textures on the provided face. To do this, we rotate the texture // normal on the provided face around the intersection edge to get the new texture axes. // Then we rotate the texture reference point around this edge as well to get the new shift values. // The scale values on both faces will always end up being the same value. // Find the intersection edge vector var intersectionEdge = face.Plane.Normal.Cross(Plane.Normal); // Create a plane using the intersection edge as the normal var intersectionPlane = new Plane(intersectionEdge, 0); // If the planes are parallel, the texture doesn't need any rotation - just different shift values. var intersect = Plane.Intersect(face.Plane, Plane, intersectionPlane); if (intersect != null) { var texNormal = face.Texture.GetNormal(); // Since the intersection plane is perpendicular to both face planes, we can find the angle // between the two planes (the original texture plane and the plane of this face) by projecting // the normals of the planes onto the perpendicular plane and taking the cross product. // Project the two normals onto the perpendicular plane var ptNormal = intersectionPlane.Project(texNormal).Normalise(); var ppNormal = intersectionPlane.Project(Plane.Normal).Normalise(); // Get the angle between the projected normals var dot = Math.Round(ptNormal.Dot(ppNormal), 4); var angle = DMath.Acos(dot); // A.B = cos(angle) // Rotate the texture axis by the angle around the intersection edge var transform = new UnitRotate(angle, new Line(Coordinate.Zero, intersectionEdge)); refU = transform.Transform(refU); refV = transform.Transform(refV); // Rotate the texture reference points as well, but around the intersection line, not the origin refX = transform.Transform(refX + intersect) - intersect; refY = transform.Transform(refY + intersect) - intersect; } // Convert the reference points back to get the final values Texture.Rotation = 0; Texture.UAxis = refU; Texture.VAxis = refV; Texture.XShift = refU.Dot(refX) / face.Texture.XScale; Texture.YShift = refV.Dot(refY) / face.Texture.YScale; Texture.XScale = face.Texture.XScale; Texture.YScale = face.Texture.YScale; CalculateTextureCoordinates(true); }
public void TestClip() { var all = _document.Map.WorldSpawn.FindAll().OfType<Solid>().ToList(); var plane = new Plane(Coordinate.UnitZ, Coordinate.Zero); TestAction(new Clip(all, plane)); }
public bool EquivalentTo(Plane other, decimal delta = 0.0001m) { return Normal.EquivalentTo(other.Normal, delta) && Math.Abs(DistanceFromOrigin - other.DistanceFromOrigin) < delta; }
public bool Equals(Plane other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Equals(other.Normal, Normal) && other.DistanceFromOrigin == DistanceFromOrigin; }
private void UpdateCache(IViewport viewport, PerspectiveCamera camera) { var ccl = camera.EyeLocation; var ccla = camera.Position + camera.Direction; var cache = _cachedLines.FirstOrDefault(x => x.Viewport == viewport); if (cache == null) { cache = new CachedLines(viewport); _cachedLines.Add(cache); } if (ccl == cache.CameraLocation && ccla == cache.CameraLookAt && cache.PivotPoint == _pivotPoint && cache.Width == viewport.Width && cache.Height == viewport.Height) { return; } var origin = _pivotPoint; var distance = (ccl - origin).Length(); if (distance <= 1) { return; } cache.CameraLocation = ccl; cache.CameraLookAt = ccla; cache.PivotPoint = _pivotPoint; cache.Width = viewport.Width; cache.Height = viewport.Height; var normal = (ccl - origin).Normalise(); var right = normal.Cross(Vector3.UnitZ).Normalise(); var up = normal.Cross(right).Normalise(); var plane = new Plane(normal, origin.Dot(normal)); const float sides = 32; var diff = (2 * Math.PI) / sides; var radius = 0.15f * distance; cache.Cache[CircleType.Outer].Clear(); cache.Cache[CircleType.X].Clear(); cache.Cache[CircleType.Y].Clear(); cache.Cache[CircleType.Z].Clear(); for (var i = 0; i < sides; i++) { var cos1 = (float)Math.Cos(diff * i); var sin1 = (float)Math.Sin(diff * i); var cos2 = (float)Math.Cos(diff * (i + 1)); var sin2 = (float)Math.Sin(diff * (i + 1)); // outer circle AddLine(CircleType.Outer, origin + right * cos1 * radius * 1.2f + up * sin1 * radius * 1.2f, origin + right * cos2 * radius * 1.2f + up * sin2 * radius * 1.2f, plane, cache); cos1 *= radius; sin1 *= radius; cos2 *= radius; sin2 *= radius; // X/Y plane = Z axis AddLine(CircleType.Z, origin + Vector3.UnitX * cos1 + Vector3.UnitY * sin1, origin + Vector3.UnitX * cos2 + Vector3.UnitY * sin2, plane, cache); // Y/Z plane = X axis AddLine(CircleType.X, origin + Vector3.UnitY * cos1 + Vector3.UnitZ * sin1, origin + Vector3.UnitY * cos2 + Vector3.UnitZ * sin2, plane, cache); // X/Z plane = Y axis AddLine(CircleType.Y, origin + Vector3.UnitZ * cos1 + Vector3.UnitX * sin1, origin + Vector3.UnitZ * cos2 + Vector3.UnitX * sin2, plane, cache); } }
private void RenderCircleTypeNone(PerspectiveCamera camera, Graphics graphics) { var center = _pivotPoint; var origin = new Vector3(center.X, center.Y, center.Z); var distance = (camera.EyeLocation - origin).Length(); if (distance <= 1) { return; } // Ensure points that can't be projected properly don't get rendered var screenOrigin = camera.WorldToScreen(origin); var sop = new PointF(screenOrigin.X, screenOrigin.Y); var rec = new RectangleF(-200, -200, camera.Width + 400, camera.Height + 400); if (!rec.Contains(sop)) { return; } var radius = 0.15f * distance; var normal = Vector3.Normalize(Vector3.Subtract(camera.EyeLocation, origin)); var right = Vector3.Normalize(Vector3.Cross(normal, Vector3.UnitZ)); var up = Vector3.Normalize(Vector3.Cross(normal, right)); graphics.SmoothingMode = SmoothingMode.HighQuality; const int sides = 32; const float diff = (float)(2 * Math.PI) / sides; for (var i = 0; i < sides; i++) { var cos1 = (float)Math.Cos(diff * i); var sin1 = (float)Math.Sin(diff * i); var cos2 = (float)Math.Cos(diff * (i + 1)); var sin2 = (float)Math.Sin(diff * (i + 1)); var line = new Line( origin + right * cos1 * radius + up * sin1 * radius, origin + right * cos2 * radius + up * sin2 * radius ); var st = camera.WorldToScreen(line.Start); var en = camera.WorldToScreen(line.End); graphics.DrawLine(Pens.DarkGray, st.X, st.Y, en.X, en.Y); line = new Line( origin + right * cos1 * radius * 1.2f + up * sin1 * radius * 1.2f, origin + right * cos2 * radius * 1.2f + up * sin2 * radius * 1.2f ); st = camera.WorldToScreen(line.Start); en = camera.WorldToScreen(line.End); graphics.DrawLine(_mouseOver == CircleType.Outer ? Pens.White : Pens.LightGray, st.X, st.Y, en.X, en.Y); } var plane = new Plane(normal, Vector3.Dot(origin, normal)); for (var i = 0; i < sides; i++) { var cos1 = (float)Math.Cos(diff * i) * radius; var sin1 = (float)Math.Sin(diff * i) * radius; var cos2 = (float)Math.Cos(diff * (i + 1)) * radius; var sin2 = (float)Math.Sin(diff * (i + 1)) * radius; RenderLine( (origin + Vector3.UnitX * cos1 + Vector3.UnitY * sin1), (origin + Vector3.UnitX * cos2 + Vector3.UnitY * sin2), plane, _mouseOver == CircleType.Z ? Color.Blue : Color.DarkBlue, camera, graphics); RenderLine( (origin + Vector3.UnitY * cos1 + Vector3.UnitZ * sin1), (origin + Vector3.UnitY * cos2 + Vector3.UnitZ * sin2), plane, _mouseOver == CircleType.X ? Color.Red : Color.DarkRed, camera, graphics); RenderLine( (origin + Vector3.UnitZ * cos1 + Vector3.UnitX * sin1), (origin + Vector3.UnitZ * cos2 + Vector3.UnitX * sin2), plane, _mouseOver == CircleType.Y ? Color.Lime : Color.LimeGreen, camera, graphics); } graphics.SmoothingMode = SmoothingMode.Default; }
internal static Task ConvertBox(BufferBuilder builder, IMapObject obj, Box box) { // It's always a box, these numbers are known const uint numVertices = 4 * 6; // Pack the indices like this [ solid1 ... solidn ] [ wireframe1 ... wireframe n ] const uint numSolidIndices = 36; const uint numWireframeIndices = numVertices * 2; var points = new VertexStandard[numVertices]; var indices = new uint[numSolidIndices + numWireframeIndices]; var c = obj.IsSelected ? Color.Red : obj.Data.GetOne <ObjectColor>()?.Color ?? Color.Magenta; var colour = new Vector4(c.R, c.G, c.B, c.A) / 255f; var flags = obj.IsSelected ? VertexFlags.SelectiveTransformed : VertexFlags.None; var vi = 0u; var si = 0u; var wi = numSolidIndices; foreach (var face in box.GetBoxFaces()) { var offs = vi; var normal = new Plane(face[0], face[1], face[2]).Normal; foreach (var v in face) { points[vi++] = new VertexStandard { Position = v, Colour = colour, Normal = normal, Texture = Vector2.Zero, Tint = Vector4.One, Flags = flags | VertexFlags.FlatColour }; } // Triangles - [0 1 2] ... [0 n-1 n] for (uint i = 2; i < 4; i++) { indices[si++] = offs; indices[si++] = offs + i - 1; indices[si++] = offs + i; } // Lines - [0 1] ... [n-1 n] [n 0] for (uint i = 0; i < 4; i++) { indices[wi++] = offs + i; indices[wi++] = offs + (i == 4 - 1 ? 0 : i + 1); } } var origin = obj.Data.GetOne <Origin>()?.Location ?? box.Center; var groups = new List <BufferGroup>(); if (!obj.Data.OfType <IContentsReplaced>().Any(x => x.ContentsReplaced)) { groups.Add(new BufferGroup(PipelineType.TexturedOpaque, CameraType.Perspective, 0, numSolidIndices)); } groups.Add(new BufferGroup(PipelineType.Wireframe, obj.IsSelected ? CameraType.Both : CameraType.Orthographic, numSolidIndices, numWireframeIndices)); builder.Append(points, indices, groups); // Also push the untransformed wireframe when selected if (obj.IsSelected) { for (var i = 0; i < points.Length; i++) { points[i].Flags = VertexFlags.None; } var untransformedIndices = indices.Skip((int)numSolidIndices); builder.Append(points, untransformedIndices, new[] { new BufferGroup(PipelineType.Wireframe, CameraType.Both, 0, numWireframeIndices) }); } return(Task.FromResult(0)); }
protected override void Render(MapDocument document, BufferBuilder builder, ResourceCollector resourceCollector) { base.Render(document, builder, resourceCollector); if (_state != ClipState.None && _clipPlanePoint1 != null && _clipPlanePoint2 != null && _clipPlanePoint3 != null) { // Draw the lines var p1 = _clipPlanePoint1.Value; var p2 = _clipPlanePoint2.Value; var p3 = _clipPlanePoint3.Value; builder.Append( new [] { new VertexStandard { Position = p1, Colour = Vector4.One, Tint = Vector4.One }, new VertexStandard { Position = p2, Colour = Vector4.One, Tint = Vector4.One }, new VertexStandard { Position = p3, Colour = Vector4.One, Tint = Vector4.One }, }, new uint [] { 0, 1, 1, 2, 2, 0 }, new [] { new BufferGroup(PipelineType.Wireframe, CameraType.Both, 0, 6) } ); if (!p1.EquivalentTo(p2) && !p2.EquivalentTo(p3) && !p1.EquivalentTo(p3) && !document.Selection.IsEmpty) { var plane = new Plane(p1, p2, p3); var pp = plane.ToPrecisionPlane(); // Draw the clipped solids var faces = new List <Polygon>(); foreach (var solid in document.Selection.OfType <Solid>().ToList()) { var s = solid.ToPolyhedron().ToPrecisionPolyhedron(); s.Split(pp, out var back, out var front); if (_side != ClipSide.Front && back != null) { faces.AddRange(back.Polygons.Select(x => x.ToStandardPolygon())); } if (_side != ClipSide.Back && front != null) { faces.AddRange(front.Polygons.Select(x => x.ToStandardPolygon())); } } var verts = new List <VertexStandard>(); var indices = new List <int>(); foreach (var polygon in faces) { var c = verts.Count; verts.AddRange(polygon.Vertices.Select(x => new VertexStandard { Position = x, Colour = Vector4.One, Tint = Vector4.One })); for (var i = 0; i < polygon.Vertices.Count; i++) { indices.Add(c + i); indices.Add(c + (i + 1) % polygon.Vertices.Count); } } builder.Append( verts, indices.Select(x => (uint)x), new[] { new BufferGroup(PipelineType.Wireframe, CameraType.Both, 0, (uint)indices.Count) } ); // Draw the clipping plane var poly = new DataStructures.Geometric.Precision.Polygon(pp); var bbox = document.Selection.GetSelectionBoundingBox(); var point = bbox.Center; foreach (var boxPlane in bbox.GetBoxPlanes()) { var proj = boxPlane.Project(point); var dist = (point - proj).Length() * 0.1f; var pln = new Plane(boxPlane.Normal, proj + boxPlane.Normal * Math.Max(dist, 100)).ToPrecisionPlane(); if (poly.Split(pln, out var b, out _)) { poly = b; } } verts.Clear(); indices.Clear(); var clipPoly = poly.ToStandardPolygon(); var colour = Color.FromArgb(64, Color.Turquoise).ToVector4(); // Add the face in both directions so it renders on both sides var polies = new[] { clipPoly.Vertices.ToList(), clipPoly.Vertices.Reverse().ToList() }; foreach (var p in polies) { var offs = verts.Count; verts.AddRange(p.Select(x => new VertexStandard { Position = x, Colour = Vector4.One, Tint = colour, Flags = VertexFlags.FlatColour })); for (var i = 2; i < clipPoly.Vertices.Count; i++) { indices.Add(offs); indices.Add(offs + i - 1); indices.Add(offs + i); } } builder.Append( verts, indices.Select(x => (uint)x), new[] { new BufferGroup(PipelineType.TexturedAlpha, CameraType.Perspective, p1, 0, (uint)indices.Count) } ); } } }
public virtual void Flip() { Vertices.Reverse(); Plane = new Plane(Vertices[0].Location, Vertices[1].Location, Vertices[2].Location); UpdateBoundingBox(); }
public virtual void Transform(IUnitTransformation transform, TransformFlags flags) { foreach (var t in Vertices) { t.Location = transform.Transform(t.Location); } Plane = new Plane(Vertices[0].Location, Vertices[1].Location, Vertices[2].Location); Colour = Colour; if (flags.HasFlag(TransformFlags.TextureScalingLock) && Texture.Texture != null) { // Make a best-effort guess of retaining scaling. All bets are off during skew operations. // Transform the current texture axes var origin = transform.Transform(Coordinate.Zero); var ua = transform.Transform(Texture.UAxis) - origin; var va = transform.Transform(Texture.VAxis) - origin; // Multiply the scales by the magnitudes (they were normals before the transform operation) Texture.XScale *= ua.VectorMagnitude(); Texture.YScale *= va.VectorMagnitude(); } if (flags.HasFlag(TransformFlags.TextureLock) && Texture.Texture != null) { // Transform the texture axes and move them back to the origin var origin = transform.Transform(Coordinate.Zero); var ua = transform.Transform(Texture.UAxis) - origin; var va = transform.Transform(Texture.VAxis) - origin; // Only do the transform if the axes end up being not perpendicular // Otherwise just make a best-effort guess, same as the scaling lock if (Math.Abs(ua.Dot(va)) < 0.0001m) { Texture.UAxis = ua; Texture.VAxis = va; } // Calculate the new shift values based on the UV values of the vertices var vtx = Vertices[0]; Texture.XShift = Texture.Texture.Width * vtx.TextureU - (vtx.Location.Dot(Texture.UAxis)) / Texture.XScale; Texture.YShift = Texture.Texture.Height * vtx.TextureV - (vtx.Location.Dot(Texture.VAxis)) / Texture.YScale; } else { // During rotate/skew operations we'll mess up the texture axes, just reset them. AlignTextureToFace(); } CalculateTextureCoordinates(); UpdateBoundingBox(); }
protected static Coordinate GetIntersectionPoint(IList<Coordinate> coordinates, Line line, bool ignoreDirection = false) { var plane = new Plane(coordinates[0], coordinates[1], coordinates[2]); var intersect = plane.GetIntersectionPoint(line, ignoreDirection); if (intersect == null) return null; // http://paulbourke.net/geometry/insidepoly/ // The angle sum will be 2 * PI if the point is inside the face double sum = 0; for (var i = 0; i < coordinates.Count; i++) { var i1 = i; var i2 = (i + 1) % coordinates.Count; // Translate the vertices so that the intersect point is on the origin var v1 = coordinates[i1] - intersect; var v2 = coordinates[i2] - intersect; var m1 = v1.VectorMagnitude(); var m2 = v2.VectorMagnitude(); var nom = m1 * m2; if (nom < 0.001m) { // intersection is at a vertex return intersect; } sum += Math.Acos((double)(v1.Dot(v2) / nom)); } var delta = Math.Abs(sum - Math.PI * 2); return (delta < 0.001d) ? intersect : null; }
private static IEnumerable <Face> CalculateDecalGeometry(Entity entity, TextureItem decal, MapDocument document, ICollection <long> solidIds) { if (decal == null || entity.Hierarchy.Parent == null) { yield break; // Texture not found } var boxRadius = Vector3.One * 4; // Decals apply to all faces that intersect within an 8x8x8 bounding box // centered at the origin of the decal var box = new Box(entity.Origin - boxRadius, entity.Origin + boxRadius); // Get the faces that intersect with the decal's radius var lines = box.GetBoxLines().ToList(); var faces = GetBoxIntersections(document, box) .OfType <Solid>() .SelectMany(x => x.Faces.Select(f => new { Solid = x, Face = f })) .Where(x => { var p = new Polygon(x.Face.Vertices); return(lines.Any(l => p.GetIntersectionPoint(l, true) != null)); }); foreach (var sf in faces) { var solid = sf.Solid; var face = sf.Face; solidIds.Add(solid.ID); // Project the decal onto the face var center = face.Plane.Project(entity.Origin); var texture = face.Texture.Clone(); texture.Name = decal.Name; texture.XShift = -decal.Width / 2f; texture.YShift = -decal.Height / 2f; var decalFace = new Face(0) { Plane = face.Plane, Texture = texture }; // Re-project the vertices in case the texture axes are not on the face plane var xShift = face.Texture.UAxis * face.Texture.XScale * decal.Width / 2; var yShift = face.Texture.VAxis * face.Texture.YScale * decal.Height / 2; var verts = new[] { face.Plane.Project(center + xShift - yShift), // Bottom Right face.Plane.Project(center + xShift + yShift), // Top Right face.Plane.Project(center - xShift + yShift), // Top Left face.Plane.Project(center - xShift - yShift) // Bottom Left }; // Because the texture axes don't have to align to the face, we might have a reversed face here // If so, reverse the points to get a valid face for the plane. // TODO: Is there a better way to do this? var vertPlane = new Plane(verts[0], verts[1], verts[2]); if (!face.Plane.Normal.EquivalentTo(vertPlane.Normal)) { Array.Reverse(verts); } decalFace.Vertices.AddRange(verts); // Calculate the X and Y shift bases on the first vertex location (assuming U/V of first vertex is zero) - we dont want these to change var vtx = decalFace.Vertices[0]; decalFace.Texture.XShift = -(vtx.Dot(decalFace.Texture.UAxis)) / decalFace.Texture.XScale; decalFace.Texture.YShift = -(vtx.Dot(decalFace.Texture.VAxis)) / decalFace.Texture.YScale; // Next, the decal geometry needs to be clipped to the face so it doesn't spill into the void var poly = new Polygon(decalFace.Vertices).ToPrecisionPolygon(); foreach (var f in solid.Faces.Except(new[] { decalFace })) { poly.Split(f.Plane.ToPrecisionPlane(), out var back, out _); poly = back ?? poly; } var newFace = poly.ToStandardPolygon(); decalFace.Vertices.Clear(); decalFace.Vertices.AddRange(newFace.Vertices); // Add a tiny bit to the normal axis to ensure the decal is rendered in front of the face var normalAdd = face.Plane.Normal * 0.2f; decalFace.Transform(Matrix4x4.CreateTranslation(normalAdd)); yield return(decalFace); } }
public override void ToolSelected(bool preventHistory) { _state = SketchState.None; _currentFace = _cloneFace = null; _intersection = null; _drawing = null; _volumePlane = null; }
public void Flip() { Vertices.Reverse(); Plane = new Plane(-Plane.Normal, Plane.PointOnPlane); }
/// <summary> /// Expands this plane's points outwards from the origin by a radius value. /// </summary> /// <param name="radius">The distance the points will be from the origin after expanding</param> public void Expand(decimal radius) { // 1. Center the polygon at the world origin // 2. Normalise all the vertices // 3. Multiply them by the radius // 4. Move the polygon back to the original origin var origin = GetOrigin(); Vertices = Vertices.Select(x => (x - origin).Normalise() * radius + origin).ToList(); Plane = new Plane(Vertices[0], Vertices[1], Vertices[2]); }
/// <summary> /// Splits this polygon by a clipping plane, returning the back and front planes. /// The original polygon is not modified. /// </summary> /// <param name="clip">The clipping plane</param> /// <param name="back">The back polygon</param> /// <param name="front">The front polygon</param> /// <returns>True if the split was successful</returns> public bool Split(Plane clip, out Polygon back, out Polygon front) { Polygon cFront, cBack; return Split(clip, out back, out front, out cBack, out cFront); }
/// <summary> /// Splits this polygon by a clipping plane, returning the back and front planes. /// The original polygon is not modified. /// </summary> /// <param name="clip">The clipping plane</param> /// <param name="back">The back polygon</param> /// <param name="front">The front polygon</param> /// <returns>True if the split was successful</returns> public bool Split(Plane clip, out Polygon back, out Polygon front) { // If the polygon doesn't span the plane, return false. var classify = ClassifyAgainstPlane(clip); if (classify != PlaneClassification.Spanning) { back = front = null; if (classify == PlaneClassification.Back) back = this; else if (classify == PlaneClassification.Front) front = this; return false; } // Get the new front and back vertices var backVerts = new List<Coordinate>(); var frontVerts = new List<Coordinate>(); var prev = 0; for (var i = 0; i <= Vertices.Count; i++) { var end = Vertices[i % Vertices.Count]; var cls = clip.OnPlane(end); // Check plane crossing if (i > 0 && cls != 0 && prev != 0 && prev != cls) { // This line end point has crossed the plane // Add the line intersect to the var start = Vertices[i - 1]; var line = new Line(start, end); var isect = clip.GetIntersectionPoint(line, true); if (isect == null) throw new Exception("Expected intersection, got null."); frontVerts.Add(isect); backVerts.Add(isect); } // Add original points if (i < Vertices.Count) { // OnPlane points get put in both polygons, doesn't generate split if (cls >= 0) frontVerts.Add(end); if (cls <= 0) backVerts.Add(end); } prev = cls; } back = new Polygon(backVerts); front = new Polygon(frontVerts); return true; }
public Plane PropertyPlane(string name) { var prop = this[name]; var defaultValue = new Plane(Coordinate.UnitZ, 0); if (prop == null || prop.Count(c => c == ' ') != 8) return defaultValue; var split = prop.Replace("(", "").Replace(")", "").Split(' '); decimal x1, x2, x3, y1, y2, y3, z1, z2, z3; if (decimal.TryParse(split[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x1) && decimal.TryParse(split[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y1) && decimal.TryParse(split[2], NumberStyles.Float, CultureInfo.InvariantCulture, out z1) && decimal.TryParse(split[3], NumberStyles.Float, CultureInfo.InvariantCulture, out x2) && decimal.TryParse(split[4], NumberStyles.Float, CultureInfo.InvariantCulture, out y2) && decimal.TryParse(split[5], NumberStyles.Float, CultureInfo.InvariantCulture, out z2) && decimal.TryParse(split[6], NumberStyles.Float, CultureInfo.InvariantCulture, out x3) && decimal.TryParse(split[7], NumberStyles.Float, CultureInfo.InvariantCulture, out y3) && decimal.TryParse(split[8], NumberStyles.Float, CultureInfo.InvariantCulture, out z3)) { return new Plane( new Coordinate(x1, y1, z1).Round(), new Coordinate(x2, y2, z2).Round(), new Coordinate(x3, y3, z3).Round()); } return defaultValue; }
/// <summary> /// Transforms all the points in the polygon. /// </summary> /// <param name="transform">The transformation to perform</param> public void Transform(IUnitTransformation transform) { Vertices = Vertices.Select(transform.Transform).ToList(); Plane = new Plane(Vertices[0], Vertices[1], Vertices[2]); }
public override void MouseDown(ViewportBase viewport, ViewportEvent e) { // switch (_state) { case SketchState.None: // nothin break; case SketchState.Ready: if (e.Button != MouseButtons.Left) break; _base = new Polygon(_currentFace.Plane, 1); _base.Transform(new UnitTranslate(_intersection - _base.Vertices[0])); _state = SketchState.DrawingBase; break; case SketchState.DrawingBase: if (e.Button == MouseButtons.Right) { // Cancel _state = SketchState.None; _base = null; } else if (e.Button == MouseButtons.Left) { ExpandBase(_intersection); _volumePlane = new Plane(_base.Vertices[1], _base.Vertices[2], _base.Vertices[2] + _base.Plane.Normal); _state = SketchState.DrawingVolume; } break; case SketchState.DrawingVolume: if (e.Button == MouseButtons.Right) { _state = SketchState.DrawingBase; _volumePlane = null; } else if (e.Button == MouseButtons.Left) { var diff = _intersection - _base.Vertices[2]; var sign = _base.Plane.OnPlane(_intersection) < 0 ? -1 : 1; _depth = diff.VectorMagnitude() * sign; CreateBrush(_base, _depth); _base = null; _volumePlane = null; _state = SketchState.None; } break; default: throw new ArgumentOutOfRangeException(); } }
private void Split(object sender) { var face = GetSplitFace(); if (face == null) return; var solid = face.Parent; var sel = MainTool.Points.Where(x => x.IsSelected).ToList(); var p1 = sel[0]; var p2 = sel[1]; if (p1.IsMidPoint) AddAdjacentPoint(face, p1); if (p2.IsMidPoint) AddAdjacentPoint(face, p2); var polygon = new Polygon(face.Vertices.Select(x => x.Location)); var clip = new Plane(p1.Coordinate, p2.Coordinate, p1.Coordinate + face.Plane.Normal * 10); Polygon back, front; polygon.Split(clip, out back, out front); if (back == null || front == null) return; solid.Faces.Remove(face); face.Parent = null; CreateFace(back, solid, face); CreateFace(front, solid, face); solid.UpdateBoundingBox(); MainTool.SetDirty(true, true); }
public void CalculateDecalGeometry() { _decalGeometry = new List<Face>(); if (Decal == null) return; // Texture not found var boxRadius = Coordinate.One * 4; // Decals apply to all faces that intersect within an 8x8x8 bounding box // centered at the origin of the decal var box = new Box(Origin - boxRadius, Origin + boxRadius); var root = GetRoot(Parent); // Get the faces that intersect with the decal's radius var faces = root.GetAllNodesIntersectingWith(box).OfType<Solid>() .SelectMany(x => x.Faces).Where(x => x.IntersectsWithBox(box)); var idg = new IDGenerator(); // Dummy generator foreach (var face in faces) { // Project the decal onto the face var center = face.Plane.Project(Origin); var texture = face.Texture.Clone(); texture.Name = Decal.Name; texture.Texture = Decal; texture.XShift = -Decal.Width / 2m; texture.YShift = -Decal.Height / 2m; var decalFace = new Face(idg.GetNextFaceID()) { Colour = Colour, IsSelected = IsSelected, IsHidden = IsCodeHidden, Plane = face.Plane, Texture = texture }; // Re-project the vertices in case the texture axes are not on the face plane var xShift = face.Texture.UAxis * face.Texture.XScale * Decal.Width / 2; var yShift = face.Texture.VAxis * face.Texture.YScale * Decal.Height / 2; var verts = new[] { new Vertex(face.Plane.Project(center + xShift - yShift), decalFace), // Bottom Right new Vertex(face.Plane.Project(center + xShift + yShift), decalFace), // Top Right new Vertex(face.Plane.Project(center - xShift + yShift), decalFace), // Top Left new Vertex(face.Plane.Project(center - xShift - yShift), decalFace) // Bottom Left }; // Because the texture axes don't have to align to the face, we might have a reversed face here // If so, reverse the points to get a valid face for the plane. // TODO: Is there a better way to do this? var vertPlane = new Plane(verts[0].Location, verts[1].Location, verts[2].Location); if (!face.Plane.Normal.EquivalentTo(vertPlane.Normal)) { Array.Reverse(verts); } decalFace.Vertices.AddRange(verts); decalFace.UpdateBoundingBox(); // Calculate the X and Y shift bases on the first vertex location (assuming U/V of first vertex is zero) - we dont want these to change var vtx = decalFace.Vertices[0]; decalFace.Texture.XShift = -(vtx.Location.Dot(decalFace.Texture.UAxis)) / decalFace.Texture.XScale; decalFace.Texture.YShift = -(vtx.Location.Dot(decalFace.Texture.VAxis)) / decalFace.Texture.YScale; decalFace.CalculateTextureCoordinates(); // Next, the decal geometry needs to be clipped to the face so it doesn't spill into the void // Create a fake solid out of the decal geometry and clip it against all the brush planes var fake = CreateFakeDecalSolid(decalFace); foreach (var f in face.Parent.Faces.Except(new[] { face })) { Solid back, front; fake.Split(f.Plane, out back, out front, idg); fake = back ?? fake; } // Extract out the original face decalFace = fake.Faces.First(x => x.Plane.EquivalentTo(face.Plane, 0.05m)); // Add a tiny bit to the normal axis to ensure the decal is rendered in front of the face var normalAdd = face.Plane.Normal * 0.2m; decalFace.Transform(new UnitTranslate(normalAdd), TransformFlags.TextureLock); _decalGeometry.Add(decalFace); } }