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; }
/// <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); }
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); } }
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)); }
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); } }
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) } ); } } }
private void RenderCircleTypeNone(PerspectiveCamera camera, I2DRenderer im) { 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)); 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); im.AddLine(st.ToVector2(), en.ToVector2(), Color.DarkGray); 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); var c = _mouseOver == CircleType.Outer ? Color.White : Color.LightGray; im.AddLine(st.ToVector2(), en.ToVector2(), c); } 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, im); RenderLine( (origin + Vector3.UnitY * cos1 + Vector3.UnitZ * sin1), (origin + Vector3.UnitY * cos2 + Vector3.UnitZ * sin2), plane, _mouseOver == CircleType.X ? Color.Red : Color.DarkRed, camera, im); RenderLine( (origin + Vector3.UnitZ * cos1 + Vector3.UnitX * sin1), (origin + Vector3.UnitZ * cos2 + Vector3.UnitX * sin2), plane, _mouseOver == CircleType.Y ? Color.Lime : Color.LimeGreen, camera, im); } }
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); } }