diff --git a/OSCADSharp/OSCADSharp.ConsoleTests/Program.cs b/OSCADSharp/OSCADSharp.ConsoleTests/Program.cs index 778f730..1516b69 100644 --- a/OSCADSharp/OSCADSharp.ConsoleTests/Program.cs +++ b/OSCADSharp/OSCADSharp.ConsoleTests/Program.cs @@ -13,11 +13,17 @@ namespace OSCADSharp.ConsoleTests { static void Main(string[] args) { - var cube = new Cube(); - var sphere = new Sphere().Translate(0, 0, 2); - var hull = cube.Hull(sphere); + var obj = new Cube(5, 10, 20).Mirror(0, 0, 1).Mirror(0, 1, 0) + .Rotate(15, -45, 120).Translate(-20, 10, 15).Rotate(90, 15, 25) + .Translate(-10, -20, -20).Rotate(-90, -90, -45); - string script = hull.ToString(); + var pos = obj.Position(); + var cyl1 = new Cylinder(1, 100, true).Translate(pos); + var cyl2 = new Cylinder(1, 100, true).Rotate(0, 90, 0).Translate(pos); + var cyl3 = new Cylinder(1, 100, true).Rotate(90, 0, 0).Translate(pos); + var axisHelper = cyl1.Union(cyl2, cyl3).Color("Red"); + + string script = obj.Union(axisHelper).ToString(); File.WriteAllLines("test.scad", new string[] { script.ToString() }); //Console.ReadKey(); diff --git a/OSCADSharp/OSCADSharp.UnitTests/CubeTests.cs b/OSCADSharp/OSCADSharp.UnitTests/CubeTests.cs index 9af5d5f..2968f8a 100644 --- a/OSCADSharp/OSCADSharp.UnitTests/CubeTests.cs +++ b/OSCADSharp/OSCADSharp.UnitTests/CubeTests.cs @@ -53,5 +53,37 @@ namespace OSCADSharp.UnitTests Assert.IsTrue(script.StartsWith("cube(")); Assert.IsTrue(script.EndsWith(");")); } + + [TestMethod] + public void Cube_InitialPositionForNonCenteredCubeIsHalfLengthWidthAndHeight() + { + var cube = new Cube(10, 10, 10); + + Assert.IsTrue(cube.Position() == new Vector3(5, 5, 5)); + } + + [TestMethod] + public void Cube_InitialPositionIfCenteredIsOrigin() + { + var cube = new Cube(25, 25, 25, true); + + Assert.AreEqual(new Vector3(), cube.Position()); + } + + [TestMethod] + public void Cube_PositionMovesWithCubeOnTranslate() + { + var cube = new Cube(50, 50, 50).Translate(10, 10, 0); + + Assert.AreEqual(new Vector3(35, 35, 25), cube.Position()); + } + + [TestMethod] + public void Cube_PositionMovesWithCubeOnNegativeTranslate() + { + var cube = new Cube(50, 50, 50, true).Translate(-5, 0, -15); + + Assert.AreEqual(new Vector3(-5, 0, -15), cube.Position()); + } } } diff --git a/OSCADSharp/OSCADSharp.UnitTests/CylinderTests.cs b/OSCADSharp/OSCADSharp.UnitTests/CylinderTests.cs index ff3f83b..78396c5 100644 --- a/OSCADSharp/OSCADSharp.UnitTests/CylinderTests.cs +++ b/OSCADSharp/OSCADSharp.UnitTests/CylinderTests.cs @@ -23,5 +23,21 @@ namespace OSCADSharp.UnitTests Assert.IsTrue(script.Contains("h = 12.1")); Assert.IsTrue(script.Contains("center = true")); } + + [TestMethod] + public void Cylinder_UncenteredPositionZValueIsHalfTheHeight() + { + var cylinder = new Cylinder(3, 40); + + Assert.AreEqual(new Vector3(0, 0, 20), cylinder.Position()); + } + + [TestMethod] + public void Cylinder_CenteredCylinderPositionIsZero() + { + var cylinder = new Cylinder(5, 20, true); + + Assert.AreEqual(new Vector3(), cylinder.Position()); + } } } diff --git a/OSCADSharp/OSCADSharp.UnitTests/DifferenceTests.cs b/OSCADSharp/OSCADSharp.UnitTests/DifferenceTests.cs new file mode 100644 index 0000000..16b5c31 --- /dev/null +++ b/OSCADSharp/OSCADSharp.UnitTests/DifferenceTests.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OSCADSharp.Solids; + +namespace OSCADSharp.UnitTests +{ + [TestClass] + public class DifferenceTests + { + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Difference_PositionThrowsNotSupportedException() + { + var diff = new Sphere().Difference(new Cube()); + + var pos = diff.Position(); + } + } +} diff --git a/OSCADSharp/OSCADSharp.UnitTests/InterpolationTests.cs b/OSCADSharp/OSCADSharp.UnitTests/InterpolationTests.cs new file mode 100644 index 0000000..f64e3d8 --- /dev/null +++ b/OSCADSharp/OSCADSharp.UnitTests/InterpolationTests.cs @@ -0,0 +1,130 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OSCADSharp.Solids; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OSCADSharp.UnitTests +{ + [TestClass] + public class InterpolationTests + { + //Positive X rotation + [TestMethod] + public void Interpolation_RotateOnXAxis() + { + var cube = new Cube(9, 9, 9).Rotate(90, 0, 0); + + //Rotating on X axis by 90 should shift the center of the cube on the negative Y quadrant + Assert.AreEqual(new Vector3(4.5, -4.5, 4.5), cube.Position()); + } + + //Negative X rotation + [TestMethod] + public void Interpolation_NegativeRotationOnXAxis() + { + var cube = new Cube(11, 11, 11).Rotate(-90, 0, 0); + + //Rotating on X axis by -90 should shift the center of the cube on the negative Z quadrant + Assert.AreEqual(new Vector3(5.5, 5.5, -5.5), cube.Position()); + } + + //Y Rotation + [TestMethod] + public void Interpolation_PositiveYRotationWithTallCube() + { + var cube = new Cube(10, 12, 23).Rotate(0, 90, 0); + + //Rotating on Y axis by 90 should shift the center of the cube on the negative Z quadrant + Assert.AreEqual(new Vector3(11.5, 6, -5), cube.Position()); + } + + //Negative Y rotation + [TestMethod] + public void Interpolation_NegativeYRotationWithWideCube() + { + var cube = new Cube(10, 30, 15).Rotate(0, -90, 0); + + //Rotating on Y axis by -90 should shift the center of the cube on the negative X quadrant + Assert.AreEqual(new Vector3(-7.5, 15, 5), cube.Position()); + } + + //Z Rotation + [TestMethod] + public void Interpolation_ZRotationWithLongCube() + { + var cube = new Cube(10, 5, 2).Rotate(0, 0, 115); + + //Rotating on Z axis by 90 should shift the center of the cube on the negative X quadrant + Assert.AreEqual(new Vector3(-4.37886077629512, 3.4749932808315, 1), cube.Position()); + } + + //Negative Z rotation + [TestMethod] + public void Interpolation_NegativeZRotation() + { + var cube = new Cube(10, 5, 2).Rotate(0, 0, -95); + + //Rotating on Z axis by 90 should shift the center of the cube on the negative Y quadrant + Assert.AreEqual(new Vector3(2.05470803149107, -5.19886284732787, 1), cube.Position()); + } + + //Centered rotation (no change) + [TestMethod] + public void Interpolation_CenteredCubePositionNotUpdatedWhenRotated() + { + var cube = new Cube(5, 20, 20, true).Rotate(15, -120, 270); + + Assert.AreEqual(new Vector3(), cube.Position()); + } + + //X and Y rotation + [TestMethod] + public void Interpolation_XAndYRotation() + { + var cube = new Cube(5, 5, 5).Rotate(120, 45, 0); + + Assert.AreEqual(new Vector3(2.41481456572267, -3.4150635094611, -1.12071934021007), cube.Position()); + } + + //Y and Z rotation + [TestMethod] + public void Interpolation_YandZRotation() + { + var cube = new Cube(13, 13, 13).Rotate(0, 270, -35); + + Assert.AreEqual(new Vector3(-1.59624145159665, 9.05273512416025, 6.5), cube.Position()); + } + + //X and Z rotation + [TestMethod] + public void Interpolation_XandZRotation() + { + var cube = new Cube(13, 13, 13).Rotate(-145, 0, 190); + + Assert.AreEqual(new Vector3(-6.67843481376553, 0.443277802376793, -9.05273512416025), cube.Position()); + } + + //X, Y and Z rotation + [TestMethod] + public void Interpolation_XYZRotation() + { + var cube = new Cube(13, 13, 13).Rotate(90, 37.5, -180); + + Assert.AreEqual(new Vector3(-9.11374600044971, 6.5, 1.19984742333634), cube.Position()); + } + + [TestMethod] + public void Interpolation_PositionAfterLotsOfOperations() + { + var obj = new Cube(5, 10, 20).Mirror(0, 0, 1).Mirror(0, 1, 0) + .Rotate(15, -45, 120).Translate(-20, 10, 15).Rotate(90, 15, 25) + .Translate(-10, -20, -20).Rotate(-90, -90, -45); + + var position = obj.Position(); + Assert.AreEqual(new Vector3(-21.7567866493247, 28.2686425980997, -21.6189570529939), position); + } + } +} diff --git a/OSCADSharp/OSCADSharp.UnitTests/IntersectionTests.cs b/OSCADSharp/OSCADSharp.UnitTests/IntersectionTests.cs new file mode 100644 index 0000000..fadd682 --- /dev/null +++ b/OSCADSharp/OSCADSharp.UnitTests/IntersectionTests.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OSCADSharp.Solids; + +namespace OSCADSharp.UnitTests +{ + [TestClass] + public class IntersectionTests + { + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Intersection_PositionThrowsNotSupportedException() + { + var obj = new Sphere().Intersection(new Text3D("Sup")); + + var pos = obj.Position(); + } + } +} diff --git a/OSCADSharp/OSCADSharp.UnitTests/MinkowskiTests.cs b/OSCADSharp/OSCADSharp.UnitTests/MinkowskiTests.cs new file mode 100644 index 0000000..abbc9c7 --- /dev/null +++ b/OSCADSharp/OSCADSharp.UnitTests/MinkowskiTests.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OSCADSharp.Solids; + +namespace OSCADSharp.UnitTests +{ + [TestClass] + public class MinkowskiTests + { + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Minkowski_PositionThrowsNotSupportedException() + { + var obj = new Cylinder().Intersection(new Sphere()).Translate(0, 5, 5); + + var pos = obj.Position(); + } + } +} diff --git a/OSCADSharp/OSCADSharp.UnitTests/MirrorTests.cs b/OSCADSharp/OSCADSharp.UnitTests/MirrorTests.cs new file mode 100644 index 0000000..dc2747b --- /dev/null +++ b/OSCADSharp/OSCADSharp.UnitTests/MirrorTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OSCADSharp.Solids; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OSCADSharp.UnitTests +{ + [TestClass] + public class MirrorTests + { + [TestMethod] + public void Mirror_SingleAxisMirrorInvertsPosition() + { + var cube = new Cube(5, 10, 20); + var xMirror = cube.Clone().Mirror(1, 0, 0); + var yMirror = cube.Clone().Mirror(0, 1, 0); + var zMirror = cube.Clone().Mirror(0, 0, 1); + + var pos = cube.Position().Clone(); + pos.X = -pos.X; + Assert.AreEqual(pos, xMirror.Position()); + + pos = cube.Position().Clone(); + pos.Y = -pos.Y; + Assert.AreEqual(pos, yMirror.Position()); + + pos = cube.Position().Clone(); + pos.Z = -pos.Z; + Assert.AreEqual(pos, zMirror.Position()); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Mirror_MultiAxisPositionThrowsNotSupportedException() + { + var cube = new Cube(5, 10, 20); + + var pos = cube.Mirror(1, 1, 0).Position(); + } + } +} diff --git a/OSCADSharp/OSCADSharp.UnitTests/OSCADSharp.UnitTests.csproj b/OSCADSharp/OSCADSharp.UnitTests/OSCADSharp.UnitTests.csproj index 31cac6c..f66616f 100644 --- a/OSCADSharp/OSCADSharp.UnitTests/OSCADSharp.UnitTests.csproj +++ b/OSCADSharp/OSCADSharp.UnitTests/OSCADSharp.UnitTests.csproj @@ -55,10 +55,16 @@ + + + + + + diff --git a/OSCADSharp/OSCADSharp.UnitTests/SphereTests.cs b/OSCADSharp/OSCADSharp.UnitTests/SphereTests.cs index 292fe69..1e87047 100644 --- a/OSCADSharp/OSCADSharp.UnitTests/SphereTests.cs +++ b/OSCADSharp/OSCADSharp.UnitTests/SphereTests.cs @@ -69,5 +69,13 @@ namespace OSCADSharp.UnitTests Assert.IsTrue(sphere.IsSameAs(clone)); } + + [TestMethod] + public void Sphere_PositionIsAtZero() + { + var sphere = new Sphere(); + + Assert.AreEqual(new Vector3(), sphere.Position()); + } } } diff --git a/OSCADSharp/OSCADSharp.UnitTests/Text3DTests.cs b/OSCADSharp/OSCADSharp.UnitTests/Text3DTests.cs new file mode 100644 index 0000000..e8ebcf3 --- /dev/null +++ b/OSCADSharp/OSCADSharp.UnitTests/Text3DTests.cs @@ -0,0 +1,22 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OSCADSharp.Solids; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OSCADSharp.UnitTests +{ + [TestClass] + public class Text3DTests + { + [TestMethod] + public void Text_PositionIsCentered() + { + var text = new Text3D("Bom chicka bow wow"); + + Assert.AreEqual(new Vector3(), text.Position()); + } + } +} diff --git a/OSCADSharp/OSCADSharp/Booleans/Difference.cs b/OSCADSharp/OSCADSharp/Booleans/Difference.cs index 3877265..bd66924 100644 --- a/OSCADSharp/OSCADSharp/Booleans/Difference.cs +++ b/OSCADSharp/OSCADSharp/Booleans/Difference.cs @@ -18,6 +18,11 @@ namespace OSCADSharp.Booleans /// public Difference(IEnumerable children) : base("difference()", children) { - } + } + + public override Vector3 Position() + { + throw new NotSupportedException("Position is not supported on Differenced objects."); + } } } diff --git a/OSCADSharp/OSCADSharp/Booleans/Intersection.cs b/OSCADSharp/OSCADSharp/Booleans/Intersection.cs index a67f010..f5c0f72 100644 --- a/OSCADSharp/OSCADSharp/Booleans/Intersection.cs +++ b/OSCADSharp/OSCADSharp/Booleans/Intersection.cs @@ -19,5 +19,10 @@ namespace OSCADSharp.Booleans public Intersection(IEnumerable children) : base("intersection()", children) { } + + public override Vector3 Position() + { + throw new NotSupportedException("Position is not supported on Intersected objects."); + } } } diff --git a/OSCADSharp/OSCADSharp/OSCADObject.cs b/OSCADSharp/OSCADSharp/OSCADObject.cs index d3034a0..4ee907e 100644 --- a/OSCADSharp/OSCADSharp/OSCADObject.cs +++ b/OSCADSharp/OSCADSharp/OSCADObject.cs @@ -204,6 +204,16 @@ namespace OSCADSharp #endregion #region Utility Methods + /// + /// Returns the computed position of this object. + /// + /// For some objects that are the aggregate of many operations or + /// multiple children, this may be an approximation or average + /// of the position. + /// + /// + public abstract Vector3 Position(); + /// /// Creates a copy of this object and all of its children /// diff --git a/OSCADSharp/OSCADSharp/OSCADSharp.csproj b/OSCADSharp/OSCADSharp/OSCADSharp.csproj index edbee13..16dc350 100644 --- a/OSCADSharp/OSCADSharp/OSCADSharp.csproj +++ b/OSCADSharp/OSCADSharp/OSCADSharp.csproj @@ -41,6 +41,7 @@ + @@ -62,7 +63,7 @@ - + diff --git a/OSCADSharp/OSCADSharp/Scripting/MultiBlockStatementObject.cs b/OSCADSharp/OSCADSharp/Scripting/MultiBlockStatementObject.cs index 0abf646..c3a4cea 100644 --- a/OSCADSharp/OSCADSharp/Scripting/MultiBlockStatementObject.cs +++ b/OSCADSharp/OSCADSharp/Scripting/MultiBlockStatementObject.cs @@ -43,5 +43,11 @@ namespace OSCADSharp.Scripting return new MultiBlockStatementObject(this.outerStatement, childClones); } + + public override Vector3 Position() + { + var positions = this.children.Select(child => child.Position()); + return Vector3.Average(positions.ToArray()); + } } } diff --git a/OSCADSharp/OSCADSharp/Solids/Cube.cs b/OSCADSharp/OSCADSharp/Solids/Cube.cs index 2264b2c..6945e1a 100644 --- a/OSCADSharp/OSCADSharp/Solids/Cube.cs +++ b/OSCADSharp/OSCADSharp/Solids/Cube.cs @@ -76,6 +76,21 @@ namespace OSCADSharp.Solids Center = this.Center }; } + + public override Vector3 Position() + { + Vector3 position; + if(this.Center == false) + { + position = new Vector3(this.Size.X / 2, this.Size.Y / 2, this.Size.Z / 2); + } + else + { + position = new Vector3(); + } + + return position; + } #endregion } } diff --git a/OSCADSharp/OSCADSharp/Solids/Cylinder.cs b/OSCADSharp/OSCADSharp/Solids/Cylinder.cs index 22f6bbc..aaf6899 100644 --- a/OSCADSharp/OSCADSharp/Solids/Cylinder.cs +++ b/OSCADSharp/OSCADSharp/Solids/Cylinder.cs @@ -137,6 +137,21 @@ namespace OSCADSharp.Solids Center = this.Center }; } + + public override Vector3 Position() + { + Vector3 position; + if (this.Center == false) + { + position = new Vector3(0, 0, this.Height / 2); + } + else + { + position = new Vector3(); + } + + return position; + } #endregion } } diff --git a/OSCADSharp/OSCADSharp/Solids/Sphere.cs b/OSCADSharp/OSCADSharp/Solids/Sphere.cs index e3c16c2..6316efc 100644 --- a/OSCADSharp/OSCADSharp/Solids/Sphere.cs +++ b/OSCADSharp/OSCADSharp/Solids/Sphere.cs @@ -80,6 +80,11 @@ namespace OSCADSharp.Solids Radius = this.Radius }; } + + public override Vector3 Position() + { + return new Vector3(); + } #endregion } } diff --git a/OSCADSharp/OSCADSharp/Solids/Text3D.cs b/OSCADSharp/OSCADSharp/Solids/Text3D.cs index 2a69ba0..1468921 100644 --- a/OSCADSharp/OSCADSharp/Solids/Text3D.cs +++ b/OSCADSharp/OSCADSharp/Solids/Text3D.cs @@ -59,6 +59,7 @@ namespace OSCADSharp.Solids public string Language { get; set; } #endregion + #region Constructors /// /// Creates 3d text with the default parameters @@ -115,6 +116,11 @@ namespace OSCADSharp.Solids sb.Append("\""); appendIfValueNotNullOrEmpty("size", this.Size?.ToString(), sb); + // Text is always centered in OSCADSharp to ensure correctness of + // position interpolation + appendIfValueNotNullOrEmpty("halign", "\"center\"", sb); + appendIfValueNotNullOrEmpty("valign", "\"center\"", sb); + appendIfValueNotNullOrEmpty("font", this.Font, sb); appendIfValueNotNullOrEmpty("spacing", this.Spacing?.ToString(), sb); appendIfValueNotNullOrEmpty("direction", this.TextDirection?.ToString(), sb); @@ -125,6 +131,18 @@ namespace OSCADSharp.Solids var formatter = new SingleBlockFormatter(String.Format("linear_extrude(height = {0})", 1), sb.ToString()); return formatter.ToString(); } + + /// + /// In reaction to the need for this value to be correct, halign and valign will always + /// be "center" by default, since non-centered text would vary dramatically in position based upon + /// the font of the text + /// - MLS 2/15/2016 + /// + /// + public override Vector3 Position() + { + return new Vector3(); + } #endregion } } diff --git a/OSCADSharp/OSCADSharp/Spatial/Matrix.cs b/OSCADSharp/OSCADSharp/Spatial/Matrix.cs new file mode 100644 index 0000000..981acd8 --- /dev/null +++ b/OSCADSharp/OSCADSharp/Spatial/Matrix.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OSCADSharp.Spatial +{ + /// + /// An n-dimensional Matrix for performing operations on doubles that + /// represent spatial positions + /// + internal class Matrix + { + #region Fields/Properties + private double[] values; + + internal int ColumnCount { get; private set; } + internal int RowCount { get; private set; } + + internal double[] GetValues() + { + return this.values; + } + #endregion + + #region Constructors + internal Matrix(double[] values, int numRows, int numColumns) + { + this.values = values; + this.RowCount = numRows; + this.ColumnCount = numColumns; + } + + internal Matrix(List values, int numColumns) + { + this.values = values.ToArray(); + this.ColumnCount = numColumns; + this.RowCount = values.Count / numColumns; + } + #endregion + + #region Public API + internal Matrix Multiply(Matrix other) + { + double[] otherValues = other.GetValues(); + List result = new List(); + int currentRowInResult = 0; + + //Iterate over each row in this matrix + for (int row = 0; row < this.RowCount; row++) + { + //And iterate over each column in the other matrix + for (int column = 0; column < other.ColumnCount; column++) + { + // Multiply item in the current row on the left, by each item in column right + // and add it to the result in the corresponding row/column + for (int leftMatrixColumn = 0; leftMatrixColumn < this.ColumnCount; leftMatrixColumn++) + { + result.Add(0); + result[currentRowInResult * other.ColumnCount + column] += + this.values[row * this.ColumnCount + leftMatrixColumn] * + otherValues[leftMatrixColumn * other.ColumnCount + column]; + } + } + + currentRowInResult++; + } + + return new Matrix(result, other.ColumnCount); + } + #endregion + + #region Static Transformation Matrices + private static readonly double piOver180 = Math.PI / 180; + private static double toRadians(double degrees) + { + return piOver180 * degrees; + } + + private static readonly Matrix identity = new Matrix(new double[] { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }, 4, 4); + internal static Matrix Identity { get { return identity; } } + + /// + /// Gets a transformation matrix for performing rotations on the X-Axis + /// (Assuming you are in a right-handed 3D world) + /// + /// Degrees of rotation + /// Transformation matrix to perform the rotation + internal static Matrix XRotation(double angle) + { + if (angle == 0) + return Identity; + + double radAngle = toRadians(angle); + double[] rotationArr = new double[] { + 1 , 0, 0, 0, + 0, Math.Cos(radAngle), Math.Sin(radAngle), 0, + 0, -Math.Sin(radAngle), Math.Cos(radAngle), 0, + 0, 0, 0, 1 + }; + + return new Matrix(rotationArr, 4, 4); + } + + /// + /// Gets a transformation matrix for performing rotations on the Y-Axis + /// (Assuming you are in a right-handed 3D world) + /// + /// Degrees of rotation + /// Transformation matrix to perform the rotation + internal static Matrix YRotation(double angle) + { + if (angle == 0) + return Identity; + + double radAngle = toRadians(angle); + double[] rotationArr = new double[] { + Math.Cos(radAngle), 0, -Math.Sin(radAngle), 0, + 0, 1, 0, 0, + Math.Sin(radAngle), 0, Math.Cos(radAngle), 0, + 0, 0, 0, 1 + }; + + return new Matrix(rotationArr, 4, 4); + } + + /// + /// Gets a transformation matrix for performing rotations on the Z-Axis + /// (Assuming you are in a right-handed 3D world) + /// + /// Degrees of rotation + /// Transformation matrix to perform the rotation + internal static Matrix ZRotation(double angle) + { + if (angle == 0) + return Identity; + + double radAngle = toRadians(angle); + double[] rotationArr = new double[] { + Math.Cos(radAngle), Math.Sin(radAngle), 0, 0, + -Math.Sin(radAngle), Math.Cos(radAngle), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + + return new Matrix(rotationArr, 4, 4); + } + + /// + /// Gets a point's position after rotations have been applied + /// + /// Point to rotate + /// + /// + /// + /// Point after rotation + internal static Vector3 GetRotatedPoint(Vector3 point, double xAngle, double yAngle, double zAngle) + { + var x = XRotation(-xAngle).Multiply(point.ToMatrix()); + var y = YRotation(-yAngle).Multiply(x); + var z = ZRotation(-zAngle).Multiply(y).GetValues(); + return new Vector3(z[0], z[1], z[2]); + } + #endregion + } +} diff --git a/OSCADSharp/OSCADSharp/Spatial/Vector3.cs b/OSCADSharp/OSCADSharp/Spatial/Vector3.cs new file mode 100644 index 0000000..82f4e65 --- /dev/null +++ b/OSCADSharp/OSCADSharp/Spatial/Vector3.cs @@ -0,0 +1,157 @@ +using OSCADSharp.Spatial; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OSCADSharp +{ + /// + /// A Three-Dimensional vector + /// + /// Can be used to represent a direction, or a point in space + /// + public class Vector3 + { + #region Attributes + public double X { get; set; } + public double Y { get; set; } + public double Z { get; set; } + #endregion + + public Vector3(double x = 0, double y = 0, double z = 0) + { + this.X = x; + this.Y = y; + this.Z = z; + } + + /// + /// Negates the values of this vector, returning an inverse of it + /// + /// A negated vector + public Vector3 Negate() + { + return new Vector3(-this.X, -this.Y, -this.Z); + } + + /// + /// Creates a copy of this vector that's a new instance + /// with the same values + /// + /// A clone of this vector + public Vector3 Clone() + { + return new Vector3(this.X, this.Y, this.Z); + } + + /// + /// Returns the average position of the provided positions + /// + /// + /// + public static Vector3 Average(params Vector3[] positions) + { + if(positions == null || positions.Length == 0) + { + return null; + } + else if (positions.Length == 1) + { + return positions[0]; + } + + var sum = new Vector3(); + + foreach (var pos in positions) + { + sum += pos; + } + + return new Vector3(sum.X / positions.Length, sum.Y / positions.Length, sum.Z / positions.Length); + } + + /// + /// Returns the unit vector for this vector + /// + /// + public Vector3 Normalize() + { + if(this.X == 0 && this.Y == 0 && this.Z == 0) + { + return this; + } + + double sum = Math.Abs(this.X) + Math.Abs(this.Y) + Math.Abs(this.Z); + return new Vector3(this.X / sum, this.Y / sum, this.Z / sum); + } + + public double Dot(Vector3 other) + { + return this.X * other.X + this.Y * other.Y + this.Z * other.Z; + } + + #region Operators/Overrides + public override bool Equals(object obj) + { + return this.GetHashCode() == obj.GetHashCode(); + } + + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + public static bool operator ==(Vector3 left, Vector3 right) + { + return left.X == right.X && + left.Y == right.Y && + left.Z == right.Z; + } + + public static bool operator !=(Vector3 left, Vector3 right) + { + return !(left.X == right.X && + left.Y == right.Y && + left.Z == right.Z); + } + + public static Vector3 operator +(Vector3 left, Vector3 right) + { + return new Vector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + } + + public static Vector3 operator -(Vector3 left, Vector3 right) + { + return new Vector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + } + + public static Vector3 operator *(Vector3 left, Vector3 right) + { + return new Vector3(left.X * right.X, left.Y * right.Y, left.Z * right.Z); + } + + public static Vector3 operator *(Vector3 left, double right) + { + return new Vector3(left.X * right, left.Y * right, left.Z * right); + } + + public static Vector3 operator *(double left, Vector3 right) + { + return new Vector3(left * right.X, left * right.Y, left * right.Z); + } + #endregion + + internal Matrix ToMatrix() + { + double[] coords = { this.X, this.Y, this.Z, 0 }; + return new Matrix(coords, 4, 1); + } + + public override string ToString() + { + return String.Format("[X: {0}, Y: {1}, Z: {2}]", this.X.ToString(), this.Y.ToString(), this.Z.ToString()); + } + } +} diff --git a/OSCADSharp/OSCADSharp/Transforms/ColoredObject.cs b/OSCADSharp/OSCADSharp/Transforms/ColoredObject.cs index af988aa..6c6a047 100644 --- a/OSCADSharp/OSCADSharp/Transforms/ColoredObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/ColoredObject.cs @@ -50,5 +50,10 @@ namespace OSCADSharp.Transforms { return new ColoredObject(obj, this.ColorName, this.Opacity); } + + public override Vector3 Position() + { + return this.obj.Position(); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/LinearExtrudedObject.cs b/OSCADSharp/OSCADSharp/Transforms/LinearExtrudedObject.cs index a6744e2..d1506b2 100644 --- a/OSCADSharp/OSCADSharp/Transforms/LinearExtrudedObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/LinearExtrudedObject.cs @@ -36,7 +36,6 @@ namespace OSCADSharp.Transforms this.children.Add(obj); } - public override OSCADObject Clone() { return new LinearExtrudedObject(this.obj.Clone(), this.Height); @@ -48,5 +47,10 @@ namespace OSCADSharp.Transforms var formatter = new SingleBlockFormatter(extrudeCommand, this.obj.ToString()); return formatter.ToString(); } + + public override Vector3 Position() + { + throw new NotSupportedException(); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/MinkowskiedObject.cs b/OSCADSharp/OSCADSharp/Transforms/MinkowskiedObject.cs index 1af5640..3b4693c 100644 --- a/OSCADSharp/OSCADSharp/Transforms/MinkowskiedObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/MinkowskiedObject.cs @@ -15,6 +15,11 @@ namespace OSCADSharp.Transforms public MinkowskiedObject(IEnumerable children) : base("minkowski()", children) { - } + } + + public override Vector3 Position() + { + throw new NotSupportedException("Position is not supported on Minkowskied objects."); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/MirroredObject.cs b/OSCADSharp/OSCADSharp/Transforms/MirroredObject.cs index 7d7f22d..05f4dc7 100644 --- a/OSCADSharp/OSCADSharp/Transforms/MirroredObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/MirroredObject.cs @@ -1,4 +1,5 @@ using OSCADSharp.Scripting; +using OSCADSharp.Spatial; using System; using System.Collections.Generic; using System.Linq; @@ -49,5 +50,29 @@ namespace OSCADSharp.Transforms { return new MirroredObject(obj, this.Normal); } + + // TODO: This will yield incorrect positions if mirroring on multiple axes + // fix mirrored positions for multiple-axis mirroring + public override Vector3 Position() + { + if (this.isMoreThanOneAxis()) + { + throw new NotSupportedException("Getting the position of an object that's been mirrored on more than one axis is not currently supported."); + } + + var pos = obj.Position(); + + double x = this.Normal.X != 0 ? pos.X * -1 : pos.X; + double y = this.Normal.Y != 0 ? pos.Y * -1 : pos.Y; + double z = this.Normal.Z != 0 ? pos.Z * -1 : pos.Z; + + return new Vector3(x, y, z); + } + + private bool isMoreThanOneAxis() + { + return (this.Normal.X != 0 && (this.Normal.Y != 0 || this.Normal.Z != 0)) || + (this.Normal.Y != 0 && (this.Normal.X != 0 || this.Normal.Z != 0)); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/ResizedObject.cs b/OSCADSharp/OSCADSharp/Transforms/ResizedObject.cs index c058ea8..506bdb3 100644 --- a/OSCADSharp/OSCADSharp/Transforms/ResizedObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/ResizedObject.cs @@ -48,5 +48,10 @@ namespace OSCADSharp.Transforms { return new ResizedObject(obj, this.Size); } + + public override Vector3 Position() + { + return obj.Position(); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/RotatedObject.cs b/OSCADSharp/OSCADSharp/Transforms/RotatedObject.cs index e21f975..c83aa95 100644 --- a/OSCADSharp/OSCADSharp/Transforms/RotatedObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/RotatedObject.cs @@ -1,4 +1,5 @@ using OSCADSharp.Scripting; +using OSCADSharp.Spatial; using System; using System.Collections.Generic; using System.Linq; @@ -48,5 +49,10 @@ namespace OSCADSharp.Transforms { return new RotatedObject(obj, this.Angle); } + + public override Vector3 Position() + { + return Matrix.GetRotatedPoint(this.obj.Position(), this.Angle.X, this.Angle.Y, this.Angle.Z); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/ScaledObject.cs b/OSCADSharp/OSCADSharp/Transforms/ScaledObject.cs index aadeade..ed30e60 100644 --- a/OSCADSharp/OSCADSharp/Transforms/ScaledObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/ScaledObject.cs @@ -48,5 +48,10 @@ namespace OSCADSharp.Transforms { return new ScaledObject(obj, this.ScaleFactor); } + + public override Vector3 Position() + { + return obj.Position(); + } } } diff --git a/OSCADSharp/OSCADSharp/Transforms/TranslatedObject.cs b/OSCADSharp/OSCADSharp/Transforms/TranslatedObject.cs index 636319c..e9f0c81 100644 --- a/OSCADSharp/OSCADSharp/Transforms/TranslatedObject.cs +++ b/OSCADSharp/OSCADSharp/Transforms/TranslatedObject.cs @@ -45,5 +45,10 @@ namespace OSCADSharp.Transforms { return new TranslatedObject(obj, this.Vector); } + + public override Vector3 Position() + { + return this.obj.Position() + this.Vector; + } } } diff --git a/OSCADSharp/OSCADSharp/Vector3.cs b/OSCADSharp/OSCADSharp/Vector3.cs deleted file mode 100644 index 72a9ae1..0000000 --- a/OSCADSharp/OSCADSharp/Vector3.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace OSCADSharp -{ - /// - /// A Three-Dimensional vector - /// - /// Can be used to represent a direction, or a point in space - /// - public class Vector3 - { - #region Attributes - public double X { get; set; } - public double Y { get; set; } - public double Z { get; set; } - #endregion - - public Vector3(double x = 0, double y = 0, double z = 0) - { - this.X = x; - this.Y = y; - this.Z = z; - } - - /// - /// Negates the values of this vector, returning an inverse of it - /// - /// A negated vector - public Vector3 Negate() - { - return new Vector3(-this.X, -this.Y, -this.Z); - } - - /// - /// Creates a copy of this vector that's a new instance - /// with the same values - /// - /// A clone of this vector - public Vector3 Clone() - { - return new Vector3(this.X, this.Y, this.Z); - } - - #region Operators - public static Vector3 operator +(Vector3 left, Vector3 right) - { - return new Vector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z); - } - - public static Vector3 operator -(Vector3 left, Vector3 right) - { - return new Vector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z); - } - #endregion - - public override string ToString() - { - return String.Format("[X: {0}, Y: {1}, Z: {2}]", this.X.ToString(), this.Y.ToString(), this.Z.ToString()); - } - } -}