diff --git a/Geometry.Test/suites/Geometry/Primitives/Arrow.test.cs b/Geometry.Test/suites/Geometry/Primitives/Arrow.test.cs new file mode 100644 index 0000000..7c97fb3 --- /dev/null +++ b/Geometry.Test/suites/Geometry/Primitives/Arrow.test.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using Qkmaxware.Geometry; +using Qkmaxware.Geometry.IO; +using Qkmaxware.Geometry.Primitives; + +namespace Qkmaxware.Testing { + +[TestClass] +public class ArrowTest : PrimitiveTest { + [TestMethod] + public void TestArrow() { + var geom = new Arrow(1, 0.08, 8); + SaveGeometry("arrow", geom); + } +} + +} \ No newline at end of file diff --git a/Geometry/Geometry.csproj b/Geometry/Geometry.csproj index 56df7f4..1f0cddf 100644 --- a/Geometry/Geometry.csproj +++ b/Geometry/Geometry.csproj @@ -8,7 +8,7 @@ Qkmaxware.Geometry - 1.0.5 + 1.0.6 Colin Halseth geometry creation manipulation LICENSE.md diff --git a/Geometry/src/Geometry/Primitives/Arrow.cs b/Geometry/src/Geometry/Primitives/Arrow.cs new file mode 100644 index 0000000..b77efaf --- /dev/null +++ b/Geometry/src/Geometry/Primitives/Arrow.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Qkmaxware.Geometry.Primitives { + +/// +/// Arrow pointing in the +Z axis +/// +public class Arrow : Mesh { + /// + /// Create a new arrow with the given length + /// + /// length of the arrow + /// radius of the arrow head + /// subdivision level + public Arrow(double length = 1, double radius = 0.08, int resolution = 8) { + var innerRadius = 0.6 * radius; + var lineLength = 0.8 * length; + var pointLength = 0.2 * length; + this.AppendRange(new Cylinder( + upperRadius: innerRadius, + lowerRadius: innerRadius, + height: lineLength, + centre: Vec3.K * (lineLength / 2), + resolution: resolution + )); + this.AppendRange(new Cone( + radius: radius, + height: pointLength, + centre: Vec3.K * lineLength, + resolution: resolution + )); + } +} + +} \ No newline at end of file diff --git a/Geometry/src/Geometry/Quat.cs b/Geometry/src/Geometry/Quat.cs index 9c439c6..9865f5b 100644 --- a/Geometry/src/Geometry/Quat.cs +++ b/Geometry/src/Geometry/Quat.cs @@ -368,6 +368,56 @@ public static Quat AngleAxis(Vec3 axis, double angle) { ).Normalized; } + /// + /// Create a quaternion representing a rotation looking in the desired direction + /// + /// look direction + /// rotation + public static Quat LookRotation(Vec3 direction) { + return LookRotation(direction, Vec3.K); + } + + /// + /// Create a quaternion representing a rotation looking in the desired direction + /// + /// look direction + /// planar normal direction + /// rotation + public static Quat LookRotation(Vec3 direction, Vec3 upwards) { + if (direction == Vec3.Zero) + return Quat.Identity; + + if (upwards != direction) { + upwards = upwards.Normalized; + var v = direction + upwards * -Vec3.Dot(upwards, direction); + var q = Quat.FromToRotation(Vec3.J, v); + return Quat.FromToRotation(v, direction) * q; + } else { + return Quat.FromToRotation(Vec3.J, direction); + } + } + + /// + /// Create a rotation from one vector to another + /// + /// first vector + /// second vector + /// rotation from the first to the second + public static Quat FromToRotation(Vec3 u, Vec3 v) { + u = u.Normalized; + v = v.Normalized; + + var k_cos_theta = Vec3.Dot(u,v); + var k = Math.Sqrt(u.SqrLength * v.SqrLength); + + if (k_cos_theta / k == -1) { + // 180 degree rotation on any orthogonal vector + return new Quat(u.Orthogonal.Normalized, 0); + } else { + return new Quat(Vec3.Cross(u,v), k_cos_theta + k).Normalized; + } + } + /// /// Sum of two quaternions /// diff --git a/Geometry/src/Geometry/Vec3.cs b/Geometry/src/Geometry/Vec3.cs index 974f758..a488814 100644 --- a/Geometry/src/Geometry/Vec3.cs +++ b/Geometry/src/Geometry/Vec3.cs @@ -133,6 +133,21 @@ public Vec3 Normalized { /// public Vec3 Flipped => -1 * this; + /// + /// Returns an arbitrary vector orthogonal to this one + /// + /// orthogonal vector + public Vec3 Orthogonal { + get { + var x = Math.Abs(this.X); + var y = Math.Abs(this.Y); + var z = Math.Abs(this.Z); + + var other = x < y ? (x < z ? Vec3.I : Vec3.K) : (y < z ? Vec3.J : Vec3.K); + return Vec3.Cross(this, other); + } + } + /// /// Max component of this vector /// diff --git a/Readme.md b/Readme.md index 64209a3..cf88649 100644 --- a/Readme.md +++ b/Readme.md @@ -27,6 +27,7 @@ Geometry in this library is modeled as a collection of triangular faces whose ve | Torus | Torus with configurable radii | | | Frustum | Pyramidal Frustums | | | Nosecone | Varieties of aerodynamic nosecones | | +| Arrow | Vertically pointing arrow | | | TextMesh | String to mesh based on 3d font character set
Default font based on Blender3D's [BFont](https://github.com/blender/blender/blob/master/release/datafiles/LICENSE-bfont.ttf.txt). | | ## Transformations for Building Geometries diff --git a/docs/img/PrimitiveArrow.png b/docs/img/PrimitiveArrow.png new file mode 100644 index 0000000..5158b55 Binary files /dev/null and b/docs/img/PrimitiveArrow.png differ diff --git a/docs/tutorials/01.Creating.md b/docs/tutorials/01.Creating.md index 162cef9..0f16403 100644 --- a/docs/tutorials/01.Creating.md +++ b/docs/tutorials/01.Creating.md @@ -268,6 +268,21 @@ public static Nosecone Haack (double C, double radius, double height, int resolu +### Arrow +Arrows are pointers looking in the +Z direction. + +**Constructors** +```cs +/// length of the arrow +/// radius of the arrow head +/// subdivision level +public Arrow(double length = 1, double radius = 0.08, int resolution = 8) +``` + +
+ +
+ ### TextMesh TextMesh creates meshes from string data by converting each letter to a 3G geometry. Custom fonts are used to map each character to their appropriate geometry.