├── .gitattributes
├── .gitignore
├── .gitignore.bak
├── 73-110.png
├── FAQ.md
├── Format.md
├── LICENSE
├── R3.Core
├── Algorithm
│ └── GraphRelaxation.cs
├── Control
│ ├── Mouse.cs
│ └── RotationHandler4D.cs
├── Drawing
│ ├── ColorUtil.cs
│ ├── Coordinates.cs
│ ├── GraphicsUtils.cs
│ ├── ImageGrid.cs
│ └── TextureHelper.cs
├── Formats
│ ├── PovRay.cs
│ ├── STL.cs
│ ├── SVG.cs
│ ├── VEF.cs
│ └── VRML.cs
├── Geometry
│ ├── Circle.cs
│ ├── Euclidean2D.cs
│ ├── Euclidean3D.cs
│ ├── EuclideanModels.cs
│ ├── Geometry2D.cs
│ ├── Hyperbolic2D.cs
│ ├── HyperbolicModels.cs
│ ├── Mesh.cs
│ ├── NearTree.cs
│ ├── Polygon.cs
│ ├── Polytope.cs
│ ├── Slicer.cs
│ ├── Sphere.cs
│ ├── Spherical2D.cs
│ ├── SphericalModels.cs
│ ├── Sterographic.cs
│ ├── Surface.cs
│ ├── Tile.cs
│ ├── Tiling.cs
│ ├── Torus.cs
│ ├── Transformable.cs
│ ├── UltraInf.cs
│ ├── Vector3D.cs
│ └── VectorND.cs
├── Honeycombs
│ ├── H3.cs
│ ├── H3Fundamental.cs
│ ├── H3Supp.cs
│ ├── H3Utils.cs
│ ├── Honeycomb.cs
│ ├── Lamp.cs
│ ├── R3.cs
│ └── S3.cs
├── Math
│ ├── DonHatch.cs
│ ├── Golden.cs
│ ├── Graph.cs
│ ├── Infinity.cs
│ ├── Isometry.cs
│ ├── Matrix4D.cs
│ ├── Mobius.cs
│ ├── Statistics.cs
│ └── Utils.cs
├── Properties
│ └── AssemblyInfo.cs
├── R3.Core.csproj
├── Shapeways
│ ├── H3Fundamental.cs
│ ├── Shapeways.cs
│ └── ShapewaysSandbox.cs
└── UI
│ ├── Attributes.cs
│ └── TrackBarValueEditor.cs
├── README.md
├── TilingBot
├── Animate.cs
├── App.config
├── Meta.Numerics
│ └── Meta.Numerics.dll
├── Mosaic.cs
├── Persistence.cs
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── Schwarz_Christoffel.cs
├── Test.cs
├── Tiler.cs
├── TilingBot.csproj
├── TilingBot.sln
├── Tweet.cs
├── Util.cs
└── packages.config
└── things to work on.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.suo
3 | TilingBot/.vs
4 | TilingBot/obj
5 | TilingBot/bin
6 | TilingBot/packages
7 | TilingBot/script
8 | TilingBot/working
9 | TilingBot/TilingBot.csproj.user
10 | R3.Core/.vs
11 | R3.Core/obj
12 | R3.Core/bin
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore.bak:
--------------------------------------------------------------------------------
1 |
2 | *.suo
3 | TilingBot/.vs
4 | TilingBot/obj
5 | TilingBot/bin
6 | TilingBot/packages
7 | TilingBot/script
8 | TilingBot/working
9 | TilingBot/TilingBot.csproj.user
10 |
--------------------------------------------------------------------------------
/73-110.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/roice3/TilingBot/1b4d13fcdc16c7c59b49fc9faea8aea3706ced2b/73-110.png
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Question
2 |
3 | I most often get a flavor of this comment/question...
4 |
5 | * I want to learn more about this!
6 | * Can you teach me how this is generated?
7 | * What resources can I use to learn about non-Euclidean geometry?
8 |
9 | ## Intro
10 |
11 | TilingBot creates images of kalideoscopic tilings, that is, tilings generated by repeatedly reflecting an image in mirrors. I've seen this process is called "folding". [Roy Wiggins](@RoyWiggins) wrote some nice blog articles, with code, about how this works.
12 |
13 | * [Folding Tilings](http://roy.red/posts/folding-tilings/)
14 | * [Spherical and Hyperbolic Tilings](http://roy.red/posts/uniting-spherical-and-hyperbolic-tilings/)
15 |
16 | I gave a [talk at ICERM](https://icerm.brown.edu/video_archive/?play=2017) with a good bit of discussion about TilingBot.
17 |
18 |
19 | ## Code resources
20 |
21 | The [TilingBot source code](https://github.com/roice3/TilingBot) can be a resource, but it can be a bit to dig through. A much more distilled version of this same approach is the [python code](https://commons.wikimedia.org/wiki/User:Tamfang/programs) that Anton Sherwood used to generate many of the images on Wikipedia.
22 |
23 | Another good resource along these lines are shaders, like [this one](https://www.shadertoy.com/view/wlGSWc) by Mathew Arcus, because all of the code is available for inspection.
24 |
25 | I've seen two main approaches to coding hyperbolic tilings, each with advantages and disadvantages. Many (including myself) started with the Poincare disk model and the circle inversion formula to generate tessellations. The second approach is to work in the hyperboloid model, which is analogous in many ways to coding spherical tilings on a sphere. Here is [a twitter thread](https://twitter.com/ZenoRogue/status/1135595254596407296) discussing tradeoffs.
26 |
27 |
28 | ## Books: Hyperbolic Geometry
29 |
30 | [Visual Complex Analysis](https://amzn.to/2Xg9vPA), by Tristan Needham
31 |
32 | This is a bit of a commitment but I also can't recommend the book enough. The first six chapters give a good foundation for non-Euclidean geometry. It is not coding oriented, but it would provide all the mathematics necessary to get started. It's how I first started learning about the geometry, the models, Mobius transformations, etc.
33 |
34 |
35 | ## Books: Tilings
36 |
37 | [Tessellations: Mathematics, Art, and Recreation](https://amzn.to/38tWkU6) by Robert Fathauer. TilingBot makes an appearance in this one.
38 |
39 | [Tilings and Patterns](https://amzn.to/3LpmRRb), aka the tiling Bible.
40 |
41 |
42 | ## Other
43 |
44 | There are many [Mathematica demonstrations](https://demonstrations.wolfram.com/topic.html?topic=Hyperbolic+Geometry&limit=20) on hyperbolic geometry.
45 |
46 | Let me know if there are other good resources you think should be listed here.
--------------------------------------------------------------------------------
/Format.md:
--------------------------------------------------------------------------------
1 | # Format Field Descriptions
2 |
3 |
4 |
5 | - P and Q
6 | -
7 | Determines the [P,Q] symmetry group. For a regular {P,Q} tiling, this means we have P-gons, with Q meeting at each vertex.
8 |
9 | The choice of this will determine the geometry, depending on the value of (P-2)*(Q-2).
10 |
11 |
12 | - Centering
13 | -
14 | Controls how the tiling is centered.
15 |
16 | - General (controlled by the Mobius property)
17 | - Fundamental_Triangle_Vertex1
18 | - Fundamental_Triangle_Vertex2
19 | - Fundamental_Triangle_Vertex3
20 | - Vertex (centers on the generating vertex)
21 |
22 |
23 |
24 | - ColoringOption
25 | -
26 | An integer, which determines some main approaches for coloring.
27 |
28 | - 0: Coloring assigned to polygon types.
29 | - 1: Edges colored according to where vertex lies in fundamental triangle.
30 | - 2: Color assigned to tiling and intensity of edges fade.
31 | - 3: Coloring altered by number of reflections.
32 | - 4: Used for importing pictures as the background. Not user friendly at all.
33 |
34 |
35 |
36 | - Colors
37 | -
38 | An array of colors. The meaning of these depends on the ColoringOption setting.
39 |
40 | The format of a color comes from the DataContractSerializer for the C# System.Drawing.Color class. The easiest way to configure will be with [known colors](https://docs.microsoft.com/en-us/dotnet/api/system.drawing.knowncolor?view=net-6.0)
41 |
42 |
43 | - SphericalModel
44 | -
45 | For spherical tilings, the following options are possible:
46 |
47 | - Sterographic
48 | - Gnomonic
49 | - Azimuthal_Equidistant
50 | - Azimuthal_EqualArea
51 | - Equirectangular
52 | - Mercator
53 | - Orthographic
54 | - Sinusoidal
55 | - PeirceQuincuncial
56 |
57 |
58 | - EuclideanModel
59 | -
60 | For euclidean tilings, the following options are possible:
61 |
62 | - Isometric
63 | - Conformal
64 | - Disk
65 | - UpperHalfPlane
66 | - Spiral
67 | - Loxodromic
68 |
69 |
70 | - HyperbolicModel
71 | -
72 | For hyperbolic tilings, the following options are possible:
73 |
74 | - Poincare
75 | - Klein
76 | - Pseudosphere
77 | - Hyperboloid
78 | - Band
79 | - UpperHalfPlane
80 | - Orthographic
81 | - Square
82 | - InvertedPoincare
83 | - Joukowsky
84 | - Ring
85 | - Azimuthal_Equidistant
86 | - Azimuthal_EqualArea
87 | - Schwarz_Christoffel
88 |
89 |
90 | - GeodesicLevels
91 | -
92 | If > 1, can be used to denote the number of recusive divisions for a "geodesic sphere" or "geodesic saddle".
93 |
94 | NOTES!
95 | * This setting will only apply if P = 3.
96 | * Not currently supported for Euclidean tilings.
97 |
98 |
99 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Roice Nelson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/R3.Core/Control/Mouse.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Control
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.Windows.Forms;
8 |
9 | ///
10 | /// Data passed along with a click.
11 | ///
12 | public class ClickData
13 | {
14 | public ClickData( int x, int y ) { X = x; Y = y; }
15 | public int X { get; set; }
16 | public int Y { get; set; }
17 | public MouseButtons Button { get; set; }
18 | }
19 |
20 | ///
21 | /// Data passed along with a drag.
22 | ///
23 | public class DragData
24 | {
25 | ///
26 | /// The actual drag location.
27 | ///
28 | public int X { get; set; }
29 | public int Y { get; set; }
30 |
31 | ///
32 | /// The drag amount, in rectangular coords.
33 | ///
34 | public float XDiff { get; set; }
35 | public float YDiff { get; set; }
36 | public float XPercent { get; set; }
37 | public float YPercent { get; set; }
38 |
39 | ///
40 | /// The drag amount, in polar coords
41 | /// Rotation is in radians.
42 | ///
43 | public float Rotation { get; set; }
44 | public float Radial { get; set; }
45 | public float RadialPercent { get; set; }
46 |
47 | public MouseButtons Button { get; set; }
48 | public bool ShiftDown;
49 | public bool CtrlDown;
50 | }
51 |
52 | ///
53 | /// Class for doing drag/click logic.
54 | /// It handles some of the nuances of puzzle based inputs.
55 | /// For example, we can't simply handle the Click event on an a draw control,
56 | /// because if you drag, that still fires when lifting the mouse button.
57 | ///
58 | public class MouseHandler
59 | {
60 | public MouseHandler()
61 | {
62 | m_dragging = m_spinning = m_skipClick = false;
63 | m_downX = m_downY = -1;
64 | m_lastX = m_lastY = 0;
65 | }
66 |
67 | public int LastX { get { return m_lastX; } }
68 | public int LastY { get { return m_lastY; } }
69 |
70 | public void Setup( Control drawSurface )
71 | {
72 | m_drawSurface = drawSurface;
73 | m_drawSurface.MouseDown += new MouseEventHandler( this.MouseDown );
74 | m_drawSurface.MouseMove += new MouseEventHandler( this.MouseMove );
75 | m_drawSurface.MouseUp += new MouseEventHandler( this.MouseUp );
76 | }
77 |
78 | public void SetClickHandler( Action clickHandler )
79 | {
80 | m_clickHandler = clickHandler;
81 | }
82 |
83 | public void SetDragHandler( Action dragHandler )
84 | {
85 | m_dragHandler = dragHandler;
86 | }
87 |
88 | public void SetSpinHandler( Action spinHandler )
89 | {
90 | m_spinHandler = spinHandler;
91 | }
92 |
93 | //
94 | // Event handlers.
95 | //
96 |
97 | private void MouseDown( Object sender, MouseEventArgs e )
98 | {
99 | m_downX = e.X;
100 | m_downY = e.Y;
101 |
102 | // If we are spinning, don't let this turn into a click.
103 | if( m_spinning )
104 | m_skipClick = true;
105 |
106 | m_dragging = m_spinning = false;
107 | }
108 |
109 | private void MouseMove( Object sender, MouseEventArgs e )
110 | {
111 | // Make sure we have the focus.
112 | m_drawSurface.Select();
113 |
114 | // Are we starting a drag?
115 | // NOTE: The mousedown checks make sure we had a mouse down call and fixes a problem I was seeing
116 | // where the view would reset when you loaded in a log file.
117 | if( !m_dragging && e.Button != MouseButtons.None &&
118 | -1 != m_downX && -1 != m_downY &&
119 | ((Math.Abs( e.X - m_downX ) > SystemInformation.DragSize.Width / 2) ||
120 | (Math.Abs( e.Y - m_downY ) > SystemInformation.DragSize.Height / 2)) )
121 | {
122 | StartDrag();
123 |
124 | // Fake the original mouse position so we will get some drag motion immediately.
125 | m_lastX = m_downX;
126 | m_lastY = m_downY;
127 | }
128 |
129 | // Are we dragging?
130 | if( m_dragging )
131 | {
132 | PerformDrag( e.X, e.Y, e.Button );
133 |
134 | // This is so we can check if we want to start spinning.
135 | m_stopWatch.Restart();
136 | }
137 |
138 | m_lastX = e.X;
139 | m_lastY = e.Y;
140 | }
141 |
142 | private readonly Stopwatch m_stopWatch = new Stopwatch();
143 |
144 | private void MouseUp( Object sender, MouseEventArgs e )
145 | {
146 | // NOTE: The mousedown checks make sure we had a mouse down call and fixes a problem I was seeing
147 | // where where unintended sticker clicks could happen when loading a log file.
148 | if( -1 == m_downX || -1 == m_downY )
149 | return;
150 |
151 | m_downX = m_downY = -1;
152 |
153 | // Figure out if we were dragging, and if the drag is done.
154 | if( m_dragging )
155 | {
156 | if( Form.MouseButtons == MouseButtons.None )
157 | {
158 | FinishDrag();
159 |
160 | // Using elapsed time works much better than checking how many pixels moved (as MC4D does).
161 | m_spinning = m_stopWatch.ElapsedMilliseconds < 50;
162 | m_stopWatch.Stop();
163 | System.Diagnostics.Trace.WriteLine( string.Format( "Spinning = {0}, Elapsed = {1}",
164 | m_spinning, m_stopWatch.ElapsedMilliseconds ) );
165 | if( m_spinning && m_spinHandler != null )
166 | m_spinHandler();
167 | }
168 |
169 | m_skipClick = false;
170 | return;
171 | }
172 |
173 | // Past here, the mouse-up represents a click.
174 | if( e.Button == MouseButtons.Left ||
175 | e.Button == MouseButtons.Right )
176 | {
177 | if( !m_skipClick && m_clickHandler != null )
178 | {
179 | ClickData clickData = new ClickData( e.X, e.Y );
180 | clickData.Button = e.Button;
181 | m_clickHandler( clickData );
182 | }
183 |
184 | m_skipClick = false;
185 | }
186 | }
187 |
188 | //
189 | // Drag helpers
190 | //
191 |
192 | void StartDrag()
193 | {
194 | m_dragging = true;
195 | m_drawSurface.Capture = true;
196 | }
197 |
198 | void PerformDrag( int x, int y, MouseButtons btn )
199 | {
200 | if( m_dragHandler == null )
201 | return;
202 |
203 | DragData dragData = new DragData();
204 | dragData.X = x;
205 | dragData.Y = y;
206 | dragData.XDiff = x - m_lastX;
207 | dragData.YDiff = y - m_lastY;
208 |
209 | // This is the increment we moved, scaled to the window size.
210 | dragData.XPercent = (float)( dragData.XDiff ) / (float)m_drawSurface.Width;
211 | dragData.YPercent = (float)( dragData.YDiff ) / (float)m_drawSurface.Height;
212 |
213 | // How much we rotated relative to center.
214 | double x1 = m_lastX - m_drawSurface.Width / 2, y1 = m_drawSurface.Height / 2 - m_lastY;
215 | double x2 = x - m_drawSurface.Width / 2, y2 = m_drawSurface.Height / 2 - y;
216 | double angle1 = Math.Atan2( y1, x1 );
217 | double angle2 = Math.Atan2( y2, x2 );
218 | dragData.Rotation = (float)angle2 - (float)angle1;
219 |
220 | // Our radial change.
221 | double r1 = Math.Sqrt( x1 * x1 + y1 * y1 );
222 | double r2 = Math.Sqrt( x2 * x2 + y2 * y2 );
223 | dragData.Radial = (float)r2 - (float)r1;
224 | dragData.RadialPercent = (float)r2 / (float)r1;
225 |
226 | dragData.Button = btn;
227 | dragData.ShiftDown = this.ShiftDown;
228 | dragData.CtrlDown = this.CtrlDown;
229 |
230 | m_dragHandler( dragData );
231 | }
232 |
233 | void FinishDrag()
234 | {
235 | m_drawSurface.Capture = false;
236 | m_dragging = false;
237 | }
238 |
239 | public bool IsSpinning
240 | {
241 | get { return m_spinning; }
242 | set { m_spinning = value; }
243 | }
244 |
245 | ///
246 | /// The control we'll be handling mouse input for.
247 | ///
248 | private Control m_drawSurface { get; set; }
249 |
250 | ///
251 | /// Tracking variables
252 | ///
253 | private int m_downX;
254 | private int m_downY;
255 | private bool m_dragging;
256 | private bool m_spinning;
257 | private bool m_skipClick;
258 | private int m_lastX;
259 | private int m_lastY;
260 |
261 | Action m_clickHandler;
262 | Action m_dragHandler;
263 | Action m_spinHandler;
264 |
265 | private bool CtrlDown
266 | {
267 | get
268 | {
269 | return (Form.ModifierKeys & Keys.Control) == Keys.Control;
270 | }
271 | }
272 |
273 | private bool ShiftDown
274 | {
275 | get
276 | {
277 | return (Form.ModifierKeys & Keys.Shift) == Keys.Shift;
278 | }
279 | }
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/R3.Core/Control/RotationHandler4D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Control
2 | {
3 | using R3.Math;
4 | using System;
5 |
6 | ///
7 | /// Class which manages 4D view rotations
8 | ///
9 | public class RotationHandler4D
10 | {
11 | public RotationHandler4D()
12 | {
13 | Current4dView = null;
14 | }
15 |
16 | public RotationHandler4D( Matrix4D initialMatrix )
17 | {
18 | Current4dView = initialMatrix;
19 | }
20 |
21 | private Matrix4D ViewMat4d = Matrix4D.Identity();
22 |
23 | ///
24 | /// The current viewpoint.
25 | ///
26 | public Matrix4D Current4dView
27 | {
28 | get
29 | {
30 | return ViewMat4d;
31 | }
32 | set
33 | {
34 | if( value == null )
35 | {
36 | ViewMat4d = Matrix4D.Identity();
37 | return;
38 | }
39 |
40 | ViewMat4d = Matrix4D.GramSchmidt( value ); // Orthonormalize
41 | }
42 | }
43 |
44 | ///
45 | /// Handles updating our rotation matrices based on mouse dragging.
46 | ///
47 | public void MouseDragged( double dx, double dy,
48 | bool xz_yz, bool xw_yw, bool xy_zw )
49 | {
50 | Matrix4D spinDelta = new Matrix4D();
51 |
52 | // Sensitivity.
53 | dx *= 0.012;
54 | dy *= 0.012;
55 |
56 | if( xz_yz )
57 | {
58 | spinDelta[0,2] += dx;
59 | spinDelta[2,0] -= dx;
60 |
61 | spinDelta[1,2] += dy;
62 | spinDelta[2,1] -= dy;
63 | }
64 |
65 | if( xw_yw )
66 | {
67 | spinDelta[0,3] -= dx;
68 | spinDelta[3,0] += dx;
69 |
70 | spinDelta[1,3] -= dy;
71 | spinDelta[3,1] += dy;
72 | }
73 |
74 | if( xy_zw )
75 | {
76 | spinDelta[0,1] += dx;
77 | spinDelta[1,0] -= dx;
78 |
79 | spinDelta[3,2] -= dy;
80 | spinDelta[2,3] += dy;
81 | }
82 |
83 | ApplySpinDelta( spinDelta );
84 | }
85 |
86 | private void ApplySpinDelta( Matrix4D spinDelta )
87 | {
88 | Matrix4D delta = Matrix4D.Identity() + spinDelta;
89 | delta = Matrix4D.GramSchmidt( delta ); // Orthonormalize
90 | ViewMat4d = delta * ViewMat4d;
91 | ViewMat4d = Matrix4D.GramSchmidt( ViewMat4d ); // Orthonormalize
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/R3.Core/Drawing/ColorUtil.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Core
2 | {
3 | using R3.Geometry;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Drawing;
7 | using System.Linq;
8 | using Math = System.Math;
9 |
10 | public static class ColorUtil
11 | {
12 | // Takes Hue value as input, returns RGB vector.
13 | // Copied from POV-Ray
14 | public static Vector3D CH2RGB( double H )
15 | {
16 | double R = 0, G = 0, B = 0;
17 | if( H >= 0 && H < 120 )
18 | {
19 | R = (120 - H) / 60;
20 | G = (H - 0) / 60;
21 | B = 0;
22 | }
23 | else if( H >= 120 && H < 240 )
24 | {
25 | R = 0;
26 | G = (240 - H) / 60;
27 | B = (H - 120) / 60;
28 | }
29 | else if( H >= 240 && H <= 360 )
30 | {
31 | R = (H - 240) / 60;
32 | G = 0;
33 | B = (360 - H) / 60;
34 | }
35 |
36 | return new Vector3D(
37 | Math.Min( R, 1 ),
38 | Math.Min( G, 1 ),
39 | Math.Min( B, 1 ) );
40 | }
41 |
42 | // Copied from POV-Ray
43 | // Putting this here for speed. It was too expensive to do this at render time in POV-Ray.
44 | public static Vector3D CHSL2RGB( Vector3D hsl )
45 | {
46 | Vector3D ones = new Vector3D( 1, 1, 1 );
47 |
48 | double H = hsl.X;
49 | double S = hsl.Y;
50 | double L = hsl.Z;
51 | Vector3D SatRGB = CH2RGB( H );
52 | Vector3D Col = 2 * S * SatRGB + (1 - S) * ones;
53 | Vector3D rgb;
54 | if( L < 0.5 )
55 | rgb = L * Col;
56 | else
57 | rgb = (1 - L) * Col + (2 * L - 1) * ones;
58 |
59 | return rgb;
60 | }
61 |
62 | public static Color AvgColor( List colors )
63 | {
64 | if( colors.Count == 0 )
65 | return Color.FromArgb( 0, 0, 0, 0 );
66 |
67 | int a = (int)colors.Select( c => (double)c.A ).Average();
68 | int r = (int)colors.Select( c => (double)c.R ).Average();
69 | int g = (int)colors.Select( c => (double)c.G ).Average();
70 | int b = (int)colors.Select( c => (double)c.B ).Average();
71 | return Color.FromArgb( a, r, g, b );
72 | }
73 |
74 | public static Color AvgColorSquare( List colors )
75 | {
76 | if( colors.Count == 0 )
77 | return Color.FromArgb( 0, 0, 0, 0 );
78 |
79 | int a = (int)Math.Sqrt( colors.Select( c => (double)c.A * c.A ).Average() );
80 | int r = (int)Math.Sqrt( colors.Select( c => (double)c.R * c.R ).Average() );
81 | int g = (int)Math.Sqrt( colors.Select( c => (double)c.G * c.G ).Average() );
82 | int b = (int)Math.Sqrt( colors.Select( c => (double)c.B * c.B ).Average() );
83 | return Color.FromArgb( a, r, g, b );
84 | }
85 |
86 | public static Color InterpColor( Color c1, Color c2, double input )
87 | {
88 | System.Func interp = ( i1, i2, d ) =>
89 | {
90 | return (int)( (double)i1 + d * (double)( i2 - i1 ) );
91 | };
92 |
93 | int a = interp( c1.A, c2.A, input );
94 | int r = interp( c1.R, c2.R, input );
95 | int g = interp( c1.G, c2.G, input );
96 | int b = interp( c1.B, c2.B, input );
97 | return Color.FromArgb( a, r, g, b );
98 | }
99 |
100 | public static Color Inverse( Color c )
101 | {
102 | return Color.FromArgb( 255, 255 - c.R, 255 - c.G, 255 - c.B );
103 | }
104 |
105 | public static Color FromRGB( Vector3D rgb )
106 | {
107 | if( rgb.DNE )
108 | return Color.FromArgb( 0, 255, 255, 255 );
109 |
110 | rgb *= 255;
111 | return Color.FromArgb( 255, (int)rgb.X, (int)rgb.Y, (int)rgb.Z );
112 | }
113 |
114 | public static Color AdjustH( Color c, double h )
115 | {
116 | Vector3D hsl = new Vector3D( c.GetHue(), c.GetSaturation(), c.GetBrightness() );
117 | hsl.X = h;
118 | Vector3D rgb = CHSL2RGB( hsl );
119 | return FromRGB( rgb );
120 | }
121 |
122 | public static Color AdjustS( Color c, double s )
123 | {
124 | Vector3D hsl = new Vector3D( c.GetHue(), c.GetSaturation(), c.GetBrightness() );
125 | hsl.Y = s;
126 | Vector3D rgb = CHSL2RGB( hsl );
127 | return FromRGB( rgb );
128 | }
129 |
130 | public static Color AdjustL( Color c, double l )
131 | {
132 | if( l > 1 )
133 | l = 1;
134 | if( l < 0 )
135 | l = 0;
136 |
137 | Vector3D hsl = new Vector3D( c.GetHue(), c.GetSaturation(), c.GetBrightness() );
138 | hsl.Z = l;
139 | Vector3D rgb = CHSL2RGB( hsl );
140 | return FromRGB( rgb );
141 | }
142 |
143 | ///
144 | /// This will calculate the color along a hexagon on the edges of an RGB cube.
145 | /// incrementsUntilRepeat is the value where we return to the starting point of the hexagon (white).
146 | /// increments is used as the distance-along-hexagon parameter.
147 | ///
148 | public static Color ColorAlongHexagon( int incrementsUntilRepeat, int increments )
149 | {
150 | // Bring to main hexagon (handle looping)
151 | increments += (int)(.0 * incrementsUntilRepeat); // an offset along the color hexagon
152 | increments = increments % incrementsUntilRepeat;
153 |
154 | // 0 to 6, so we can have each edge of the hexagon live in a unit interval.
155 | double distAlongHex = (double)increments * 6 / incrementsUntilRepeat;
156 |
157 | Func subtractive = d => (int)(255.0 * (1.0 - d));
158 | Func addative = d => (int)(255.0 * d);
159 |
160 | bool blue = true;
161 | if( blue )
162 | {
163 | if( distAlongHex < 1 )
164 | return Color.FromArgb( 255, subtractive( distAlongHex ), 255, 255 );
165 | distAlongHex--;
166 | if( distAlongHex < 1 )
167 | return Color.FromArgb( 255, 0, subtractive( distAlongHex ), 255 );
168 | distAlongHex--;
169 | if( distAlongHex < 1 )
170 | return Color.FromArgb( 255, 0, 0, subtractive( distAlongHex ) );
171 | distAlongHex--;
172 | if( distAlongHex < 1 )
173 | return Color.FromArgb( 255, addative( distAlongHex ), 0, 0 );
174 | distAlongHex--;
175 | if( distAlongHex < 1 )
176 | return Color.FromArgb( 255, 255, addative( distAlongHex ), 0 );
177 | distAlongHex--;
178 | if( distAlongHex < 1 )
179 | return Color.FromArgb( 255, 255, 255, addative( distAlongHex ) );
180 | }
181 | else
182 | {
183 | if( distAlongHex < 1 )
184 | return Color.FromArgb( 255, 255, 255, subtractive( distAlongHex ) );
185 | distAlongHex--;
186 | if( distAlongHex < 1 )
187 | return Color.FromArgb( 255, 255, subtractive( distAlongHex ), 0 );
188 | distAlongHex--;
189 | if( distAlongHex < 1 )
190 | return Color.FromArgb( 255, subtractive( distAlongHex ), 0, 0 );
191 | distAlongHex--;
192 | if( distAlongHex < 1 )
193 | return Color.FromArgb( 255, 0, 0, addative( distAlongHex ) );
194 | distAlongHex--;
195 | if( distAlongHex < 1 )
196 | return Color.FromArgb( 255, 0, addative( distAlongHex ), 255 );
197 | distAlongHex--;
198 | if( distAlongHex < 1 )
199 | return Color.FromArgb( 255, addative( distAlongHex ), 255, 255 );
200 | }
201 |
202 | throw new System.Exception( "Bad impl" );
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/R3.Core/Drawing/Coordinates.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Drawing
2 | {
3 | using R3.Geometry;
4 |
5 | ///
6 | /// Class to help converting from Image/Canvas coords <-> absolute coords.
7 | ///
8 | public class ImageSpace
9 | {
10 | ///
11 | /// Takes in width/height of an Image/Canvas.
12 | /// For a bitmap, this is number of pixels.
13 | ///
14 | public ImageSpace( double width, double height )
15 | {
16 | m_width = width;
17 | m_height = height;
18 | }
19 | private double m_width;
20 | private double m_height;
21 |
22 | // The bounds in model space.
23 | public double XMin { get; set; }
24 | public double XMax { get; set; }
25 | public double YMin { get; set; }
26 | public double YMax { get; set; }
27 |
28 | ///
29 | /// Returns a screen width from an absolute width.
30 | ///
31 | public double Width( double width )
32 | {
33 | double percent = width / ( XMax - XMin );
34 | return ( percent * m_width );
35 | }
36 |
37 | ///
38 | /// Returns a screen height from an absolute height.
39 | ///
40 | public double Height( double height )
41 | {
42 | double percent = height / ( YMax - YMin );
43 | return ( percent * m_height );
44 | }
45 |
46 | ///
47 | /// Returns a screen pixel from an absolute location.
48 | /// NOTE: We don't return a 'Point' because it is different in Forms/Silverlight.
49 | ///
50 | public Vector3D Pixel( Vector3D point )
51 | {
52 | double xPercent = ( point.X - XMin ) / ( XMax - XMin );
53 | double yPercent = ( point.Y - YMin ) / ( YMax - YMin );
54 | double x = ( xPercent * m_width );
55 | double y = m_height - ( yPercent * m_height );
56 | return new Vector3D( x, y, 0 );
57 | }
58 |
59 | ///
60 | /// Returns an absolute location from a screen pixel.
61 | /// NOTE: We don't take in a 'Point' because it is different in Forms/Silverlight.
62 | ///
63 | public Vector3D Point( Vector3D Pixel )
64 | {
65 | return new Vector3D(
66 | XMin + ( Pixel.X / m_width ) * ( XMax - XMin ),
67 | YMax - ( Pixel.Y / m_height ) * ( YMax - YMin ),
68 | 0 );
69 | }
70 |
71 | ///
72 | /// VectorNDs are assumed to be 4D.
73 | ///
74 | public VectorND Pixel( VectorND point )
75 | {
76 | Vector3D result = Pixel( new Vector3D( point.X[0], point.X[1], point.X[2] ) );
77 | return new VectorND( new double[] { result.X, result.Y, result.Z, 0 } );
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/R3.Core/Drawing/GraphicsUtils.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Drawing
2 | {
3 | using R3.Geometry;
4 | using System.Drawing;
5 |
6 | public class DrawUtils
7 | {
8 | static public void DrawCircle( Circle c, Graphics g, ImageSpace i )
9 | {
10 | using( Pen pen = new Pen( Color.Black, 1.0f ) )
11 | DrawCircle( c, g, i, pen );
12 | }
13 |
14 | static private Rectangle? Rect( Circle c, ImageSpace i )
15 | {
16 | if( double.IsInfinity( c.Radius ) )
17 | return null;
18 |
19 | Vector3D upperLeft = i.Pixel( new Vector3D( c.Center.X - c.Radius, c.Center.Y + c.Radius, 0 ) );
20 | double width = i.Width( c.Radius * 2 );
21 | double height = i.Height( c.Radius * 2 );
22 | Rectangle rect = new Rectangle( (int)upperLeft.X, (int)upperLeft.Y, (int)width, (int)height );
23 | return rect;
24 | }
25 |
26 | static public void DrawCircle( Circle c, Graphics g, ImageSpace i, Pen p )
27 | {
28 | Rectangle? rect = Rect( c, i );
29 | if( rect == null )
30 | return;
31 | g.DrawEllipse( p, rect.Value );
32 | }
33 |
34 | static public void DrawFilledCircle( Circle c, Graphics g, ImageSpace i, Brush b )
35 | {
36 | Rectangle? rect = Rect( c, i );
37 | if( rect == null )
38 | return;
39 | g.FillEllipse( b, rect.Value );
40 | }
41 |
42 | static public void DrawLine( Vector3D p1, Vector3D p2, Graphics g, ImageSpace i, Pen p )
43 | {
44 | g.DrawLine( p, VecToPoint( p1, i ), VecToPoint( p2, i ) );
45 | }
46 |
47 | static public void DrawTriangle( Mesh.Triangle triangle, Graphics g, ImageSpace i )
48 | {
49 | using( Pen pen = new Pen( Color.Black, 1.0f ) )
50 | {
51 | g.DrawLine( pen, VecToPoint( triangle.a, i ), VecToPoint( triangle.b, i ) );
52 | g.DrawLine( pen, VecToPoint( triangle.b, i ), VecToPoint( triangle.c, i ) );
53 | g.DrawLine( pen, VecToPoint( triangle.c, i ), VecToPoint( triangle.a, i ) );
54 | }
55 | }
56 |
57 | static private Point VecToPoint( Vector3D vec, ImageSpace i )
58 | {
59 | Vector3D temp = i.Pixel( vec );
60 | return new Point( (int)temp.X, (int)temp.Y );
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/R3.Core/Drawing/ImageGrid.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Drawing
2 | {
3 | using System.Drawing;
4 | using System.Drawing.Imaging;
5 | using System.IO;
6 |
7 | ///
8 | /// This will take a list of images and resize/combine them into a single image grid.
9 | ///
10 | public class ImageGrid
11 | {
12 | public class Settings
13 | {
14 | public Settings()
15 | {
16 | Columns = 4;
17 | Rows = 2;
18 | Width = 4*1000 + 3*hGap;
19 | Height = 2*1000 + 1*vGap;
20 | FileName = "output.png";
21 | }
22 |
23 | public int vGap = 500;
24 | public int hGap = 250;
25 |
26 | public int Columns { get; set; }
27 | public int Rows { get; set; }
28 | public int Width { get; set; }
29 | public int Height { get; set; }
30 | public string Directory { get; set; }
31 |
32 | ///
33 | /// The filenames of the input images (just leaf names).
34 | /// We will fill out result by rows.
35 | ///
36 | public string[] InputImages { get; set; }
37 |
38 | ///
39 | /// The output file.
40 | ///
41 | public string FileName { get; set; }
42 | }
43 |
44 | public void Generate( Settings s )
45 | {
46 | Bitmap image = new Bitmap( s.Width, s.Height );
47 | Graphics g = Graphics.FromImage( image );
48 | g.Clear( Color.Black );
49 |
50 | int tileWidth = 300;//s.Width / s.Columns;
51 | int tileHeight = 300;// s.Height / s.Rows;
52 | int vGap = s.vGap, hGap = s.hGap;
53 | Size tileSize = new Size( tileWidth, tileHeight );
54 |
55 | int currentRow = 0, currentCol = 0;
56 | foreach( string imageName in s.InputImages )
57 | {
58 | string fullFileName = Path.Combine( s.Directory, imageName );
59 | Bitmap original = new Bitmap( fullFileName );
60 |
61 | // Resize
62 | Bitmap tile = new Bitmap( original, tileSize );
63 |
64 | // Copy to location.
65 | for( int i=0; i= s.Columns )
77 | {
78 | currentCol = 0;
79 | currentRow++;
80 | }
81 | if( currentRow >= s.Rows )
82 | break;
83 | }
84 |
85 | image.Save( s.FileName, ImageFormat.Png );
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/R3.Core/Formats/STL.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Core
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | using R3.Geometry;
9 | using System.IO;
10 |
11 | public class STL
12 | {
13 | public static void SaveMeshToSTL( Mesh mesh, String fileName )
14 | {
15 | using( StreamWriter sw = File.CreateText( fileName ) )
16 | WriteInternal( mesh, sw );
17 | }
18 |
19 | public static void AppendMeshToSTL( Mesh mesh, String fileName )
20 | {
21 | using( StreamWriter sw = File.AppendText( fileName ) )
22 | WriteInternal( mesh, sw );
23 | }
24 |
25 | public static void AppendMeshToSTL( Mesh mesh, StreamWriter sw )
26 | {
27 | WriteInternal( mesh, sw );
28 | }
29 |
30 | private static void WriteInternal( Mesh mesh, StreamWriter sw )
31 | {
32 | sw.WriteLine( "solid" );
33 | foreach( Mesh.Triangle tri in mesh.Triangles )
34 | WriteTriangle( sw, tri );
35 | sw.WriteLine( "endsolid" );
36 | }
37 |
38 | private static void WriteTriangle( StreamWriter sw, Mesh.Triangle tri )
39 | {
40 | Vector3D v1 = tri.b - tri.a;
41 | Vector3D v2 = tri.c - tri.a;
42 |
43 | // See http://en.wikipedia.org/wiki/STL_format#The_Facet_Normal
44 | // about how to do the normals correctly.
45 | //Vector3D n = v1.Cross( v2 );
46 | Vector3D n = new Vector3D( 0, 0, 1 );
47 | n.Normalize();
48 |
49 | sw.WriteLine( " facet normal {0:e6} {1:e6} {2:e6}", n.X, n.Y, n.Z );
50 | sw.WriteLine( " outer loop" );
51 | sw.WriteLine( " vertex {0:e6} {1:e6} {2:e6}", tri.a.X, tri.a.Y, tri.a.Z );
52 | sw.WriteLine( " vertex {0:e6} {1:e6} {2:e6}", tri.b.X, tri.b.Y, tri.b.Z );
53 | sw.WriteLine( " vertex {0:e6} {1:e6} {2:e6}", tri.c.X, tri.c.Y, tri.c.Z );
54 | sw.WriteLine( " endloop" );
55 | sw.WriteLine( " endfacet" );
56 |
57 | /*
58 | " facet normal {0:e6} {1:e6} {2:e6}" -f $n
59 | " outer loop"
60 | " vertex {0:e6} {1:e6} {2:e6}" -f $v1
61 | " vertex {0:e6} {1:e6} {2:e6}" -f $v2
62 | " vertex {0:e6} {1:e6} {2:e6}" -f $v3
63 | " endloop"
64 | " endfacet"
65 |
66 | facet normal 0.000000e+000 0.000000e+000 -1.000000e+000
67 | outer loop
68 | vertex 4.500000e-001 4.500000e-001 4.500000e-001
69 | vertex 4.500000e-001 7.500000e-001 4.500000e-001
70 | vertex 7.500000e-001 7.500000e-001 4.500000e-001
71 | endloop
72 | endfacet
73 | */
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/R3.Core/Formats/SVG.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Core
2 | {
3 | using R3.Geometry;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Xml.Linq;
7 |
8 | public class SVG
9 | {
10 | public static void WritePolygons( string file, List polygons )
11 | {
12 | // Getting units right was troublesome.
13 | // See http://stackoverflow.com/questions/1346922/svg-and-dpi-absolute-units-and-user-units-inkscape-vs-firefox-vs-imagemagick/2306752#2306752
14 |
15 | XDocument xDocument =
16 | new XDocument( new XElement( "svg",
17 | new XAttribute( "width", @"5in" ),
18 | new XAttribute( "height", @"5in" ),
19 | new XAttribute( "viewBox", @"0 0 5 5" ),
20 | new XElement( "g",
21 | new XAttribute( "style", @"fill:none;stroke:#0000FF;stroke-width:0.00005in" ),
22 | new XElement( "circle",
23 | new XAttribute( "cx", "0" ),
24 | new XAttribute( "cy", "0" ),
25 | new XAttribute( "r", "1" )
26 | ),
27 | polygons.Select( poly => XPolygon( poly ) )
28 | )));
29 |
30 | xDocument.Save( file );
31 | }
32 |
33 | public static void WriteSegments( string file, List segs )
34 | {
35 | XDocument xDocument =
36 | new XDocument( new XElement( "svg",
37 | new XAttribute( "width", @"5in" ),
38 | new XAttribute( "height", @"5in" ),
39 | new XAttribute( "viewBox", @"0 0 5 5" ),
40 | new XElement( "g",
41 | new XAttribute( "style", @"fill:none;stroke:#0000FF;stroke-width:0.00005in" ),
42 | new XElement( "circle",
43 | new XAttribute( "cx", "0" ),
44 | new XAttribute( "cy", "0" ),
45 | new XAttribute( "r", "1" )
46 | ),
47 | segs.Select( s => XSegment( s ) )
48 | ) ) );
49 |
50 | xDocument.Save( file );
51 | }
52 |
53 | private static XElement XPolygon( Polygon poly )
54 | {
55 | // This makes the unit disk have a 1 inch radius.
56 | // The 72 / 25.4 is because Inkscape templates use pt for their main SVG units.
57 | const double scale = 1;//( 254 / 2 ) * ( 72 / 25.4 );
58 |
59 | string coords = "";
60 | for( int i=0; i System.Math.PI;
70 | bool sweepDirection = !seg.Clockwise;
71 | coords += "A " + FormatDouble( seg.Radius, scale ) + "," + FormatDouble( seg.Radius, scale ) + " 0 " +
72 | FormatBool( largeArc ) + FormatBool( sweepDirection ) + FormatPoint( seg.P2, scale );
73 | }
74 | else
75 | {
76 | coords += "L " + FormatPoint( seg.P2, scale );
77 | }
78 | }
79 |
80 | return new XElement( "path",
81 | new XAttribute( "d", coords )
82 | );
83 | }
84 |
85 | private static XElement XSegment( Segment seg )
86 | {
87 | Polygon poly = new Polygon();
88 | poly.Segments.Add( seg );
89 | return XPolygon( poly );
90 | }
91 |
92 | private static string FormatBool( bool value )
93 | {
94 | return value ? "1 " : "0 ";
95 | }
96 |
97 | private static string FormatPoint( Vector3D p, double scale )
98 | {
99 | return FormatDouble( p.X, scale ) + "," + FormatDouble( p.Y, scale ) + " ";
100 | }
101 |
102 | private static string FormatDouble( double d, double scale )
103 | {
104 | return string.Format( "{0:F6}", d * scale );
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/R3.Core/Formats/VEF.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Core
2 | {
3 | using R3.Geometry;
4 | using R3.Math;
5 | using System.Collections.Generic;
6 | using System.IO;
7 |
8 | public class VEF
9 | {
10 | ///
11 | /// The vertices.
12 | ///
13 | public GoldenVector4D[] Vertices { get; private set; }
14 |
15 | ///
16 | /// The edges.
17 | ///
18 | public GraphEdge[] Edges { get; private set; }
19 |
20 | ///
21 | /// Load from a VEF file.
22 | ///
23 | ///
24 | public void Load( string fileName )
25 | {
26 | IEnumerator lines = File.ReadLines( fileName ).GetEnumerator();
27 |
28 | lines.MoveNext();
29 | int numVertices = int.Parse( lines.Current );
30 |
31 | List vertices = new List();
32 | for( int i = 0; i < numVertices; i++ )
33 | {
34 | lines.MoveNext();
35 | GoldenVector4D v = new GoldenVector4D();
36 | v.ReadVector( lines.Current );
37 | vertices.Add( v );
38 | }
39 |
40 | lines.MoveNext();
41 | int numEdges = int.Parse( lines.Current );
42 | List edges = new List();
43 | for( int i = 0; i < numEdges; i++ )
44 | {
45 | lines.MoveNext();
46 | GraphEdge e = new GraphEdge();
47 | e.ReadEdge( lines.Current );
48 | edges.Add( e );
49 | }
50 |
51 | this.Vertices = vertices.ToArray();
52 | this.Edges = edges.ToArray();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Euclidean2D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using Math = System.Math;
4 | using R3.Core;
5 | using System.Diagnostics;
6 |
7 | public static class Euclidean2D
8 | {
9 | ///
10 | /// Returns the counterclock angle between two vectors (between 0 and 2*pi)
11 | /// NOTE: A unique counter clockwise angle really only makes sense onces you've picked a plane normal direction.
12 | /// So as coded, this function is really only intended to be used with 2D vector inputs.
13 | ///
14 | public static double AngleToCounterClock( Vector3D v1, Vector3D v2 )
15 | {
16 | double angle = Math.Atan2( v2.Y, v2.X ) - Math.Atan2( v1.Y, v1.X );
17 | if( angle < 0 )
18 | return angle + 2 * Math.PI;
19 | return angle;
20 | }
21 |
22 | ///
23 | /// Returns the clockwise angle between two vectors (between 0 and 2*pi)
24 | /// NOTE: A unique clockwise angle really only makes sense onces you've picked a plane normal direction.
25 | /// So as coded, this function is really only intended to be used with 2D vector inputs.
26 | ///
27 | public static double AngleToClock( Vector3D v1, Vector3D v2 )
28 | {
29 | double result = AngleToCounterClock( v1, v2 );
30 | return ( 2 * Math.PI - result );
31 | }
32 |
33 | public static double DistancePointLine( Vector3D p, Vector3D lineP1, Vector3D lineP2 )
34 | {
35 | // The line vector
36 | Vector3D v1 = lineP2 - lineP1;
37 | double lineMag = v1.Abs();
38 | if( Tolerance.Zero( lineMag ) )
39 | {
40 | // Line definition points are the same.
41 | Debug.Assert( false );
42 | return double.NaN;
43 | }
44 |
45 | Vector3D v2 = p - lineP1;
46 | double distance = ( v1.Cross( v2 ) ).Abs() / lineMag;
47 | return distance;
48 | }
49 |
50 | public static Vector3D ProjectOntoLine( Vector3D p, Vector3D lineP1, Vector3D lineP2 )
51 | {
52 | Vector3D v1 = lineP2 - lineP1;
53 | double lineMag = v1.Abs();
54 | if( Tolerance.Zero( lineMag ) )
55 | {
56 | Debug.Assert( false );
57 | return new Vector3D();
58 | }
59 | v1.Normalize();
60 |
61 | Vector3D v2 = p - lineP1;
62 | double distanceAlongLine = v2.Dot( v1 );
63 | return lineP1 + v1 * distanceAlongLine;
64 | }
65 |
66 | public static int IntersectionLineLine( Vector3D p1, Vector3D p2,
67 | Vector3D p3, Vector3D p4, out Vector3D intersection )
68 | {
69 | intersection = new Vector3D();
70 |
71 | Vector3D n1 = p2 - p1;
72 | Vector3D n2 = p4 - p3;
73 |
74 | // Intersect?
75 | // XXX - Handle the case where lines are one and the same separately?
76 | // (infinite interesection points)
77 | if( Tolerance.Zero( n1.Cross( n2 ).Abs() ) )
78 | return 0;
79 |
80 | double d3 = DistancePointLine( p3, p1, p2 );
81 | double d4 = DistancePointLine( p4, p1, p2 );
82 |
83 | // Distances on the same side?
84 | // This tripped me up.
85 | double a3 = AngleToClock( p3 - p1, n1 );
86 | double a4 = AngleToClock( p4 - p1, n1 );
87 | bool sameSide = a3 > Math.PI ? a4 > Math.PI : a4 <= Math.PI;
88 |
89 | double factor = sameSide ?
90 | d3 / ( d3 - d4 ) :
91 | d3 / ( d3 + d4 );
92 | intersection = p3 + n2 * factor;
93 |
94 | // XXX - Unfortunately, this is happening sometimes.
95 | if( !Tolerance.Zero( DistancePointLine( intersection, p1, p2 ) ) )
96 | {
97 | //Debug.Assert( false );
98 | }
99 |
100 | return 1;
101 | }
102 |
103 | public static int IntersectionCircleCircle( Circle c1, Circle c2, out Vector3D p1, out Vector3D p2 )
104 | {
105 | p1 = new Vector3D();
106 | p2 = new Vector3D();
107 |
108 | // A useful page describing the cases in this function is:
109 | // http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/2circle/
110 | // Maybe here now? http://paulbourke.net/geometry/circlesphere/
111 |
112 | // Vector and distance between the centers.
113 | Vector3D v = c2.Center - c1.Center;
114 | double d = v.Abs();
115 | double r1 = c1.Radius;
116 | double r2 = c2.Radius;
117 |
118 | // Circle centers coincident.
119 | if( Tolerance.Zero( d ) )
120 | {
121 | if( Tolerance.Equal( r1, r2 ) )
122 | return -1;
123 | else
124 | return 0;
125 | }
126 |
127 | // We should be able to normalize at this point.
128 | if( !v.Normalize() )
129 | {
130 | Debug.Assert( false );
131 | return 0;
132 | }
133 |
134 | // No intersection points.
135 | // First case is disjoint circles.
136 | // Second case is where one circle contains the other.
137 | if( Tolerance.GreaterThan( d, r1 + r2 ) ||
138 | Tolerance.LessThan( d, Math.Abs( r1 - r2 ) ) )
139 | return 0;
140 |
141 | // One intersection point.
142 | if( Tolerance.Equal( d, r1 + r2 ) ||
143 | Tolerance.Equal( d, Math.Abs( r1 - r2 ) ) )
144 | {
145 | p1 = c1.Center + v * r1;
146 | return 1;
147 | }
148 |
149 | // There must be two intersection points.
150 | p1 = p2 = v * r1;
151 | double temp = ( r1 * r1 - r2 * r2 + d * d ) / ( 2 * d );
152 | double angle = Math.Acos( temp / r1 );
153 | Debug.Assert( !Tolerance.Zero( angle ) && !Tolerance.Equal( angle, Math.PI ) );
154 | p1.RotateXY( angle );
155 | p2.RotateXY( -angle );
156 | p1 += c1.Center;
157 | p2 += c1.Center;
158 | return 2;
159 | }
160 |
161 | public static int IntersectionLineCircle( Vector3D lineP1, Vector3D lineP2, Circle circle,
162 | out Vector3D p1, out Vector3D p2 )
163 | {
164 | p1 = new Vector3D();
165 | p2 = new Vector3D();
166 |
167 | // Distance from the circle center to the closest point on the line.
168 | double d = DistancePointLine( circle.Center, lineP1, lineP2 );
169 |
170 | // No intersection points.
171 | double r = circle.Radius;
172 | if( d > r )
173 | return 0;
174 |
175 | // One intersection point.
176 | p1 = ProjectOntoLine( circle.Center, lineP1, lineP2 );
177 | if( Tolerance.Equal( d, r ) )
178 | return 1;
179 |
180 | // Two intersection points.
181 | // Special case when the line goes through the circle center,
182 | // because we can see numerical issues otherwise.
183 | //
184 | // I had further issues where my default tolerance was too strict for this check.
185 | // The line was close to going through the center and the second block was used,
186 | // so I had to loosen the tolerance used by my comparison macros.
187 | if( Tolerance.Zero( d ) )
188 | {
189 | Vector3D line = lineP2 - lineP1;
190 | line.Normalize();
191 | line *= r;
192 | p1 = circle.Center + line;
193 | p2 = circle.Center - line;
194 | }
195 | else
196 | {
197 | // To origin.
198 | p1 -= circle.Center;
199 |
200 | p1.Normalize();
201 | p1 *= r;
202 | p2 = p1;
203 | double angle = Math.Acos( d / r );
204 | p1.RotateXY( angle );
205 | p2.RotateXY( -angle );
206 |
207 | // Back out.
208 | p1 += circle.Center;
209 | p2 += circle.Center;
210 | }
211 | return 2;
212 | }
213 |
214 | ///
215 | /// Reflects a point in a line defined by two points.
216 | ///
217 | public static Vector3D ReflectPointInLine( Vector3D input, Vector3D p1, Vector3D p2 )
218 | {
219 | Vector3D p = Euclidean2D.ProjectOntoLine( input, p1, p2 );
220 | return input + ( p - input ) * 2;
221 | }
222 |
223 | public static bool SameSideOfLine( Vector3D lineP1, Vector3D lineP2, Vector3D test1, Vector3D test2 )
224 | {
225 | Vector3D d = lineP2 - lineP1;
226 | Vector3D t1 = (test1 - lineP1).Cross( d );
227 | Vector3D t2 = (test2 - lineP1).Cross( d );
228 | bool pos1 = t1.Z > 0;
229 | bool pos2 = t2.Z > 0;
230 | return !(pos1 ^ pos2);
231 | }
232 | }
233 | }
--------------------------------------------------------------------------------
/R3.Core/Geometry/Euclidean3D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using System.Diagnostics;
4 | using R3.Core;
5 | using Math = System.Math;
6 |
7 | public static class Euclidean3D
8 | {
9 | public static double DistancePointLine( Vector3D n1, Vector3D p1, Vector3D point )
10 | {
11 | // Check to make sure that n1 is not degenerate.
12 | if( Tolerance.Zero( n1.MagSquared() ) )
13 | return double.NaN;
14 |
15 | return ( ( point - p1 ).Cross( n1 ) ).Abs() / n1.Abs();
16 | }
17 |
18 | public static double DistancePointPlane( Vector3D normalVector, Vector3D planePoint, Vector3D point )
19 | {
20 | // Check to make sure that plane is not degenerate.
21 | if( Tolerance.Zero( normalVector.MagSquared() ) )
22 | return double.NaN;
23 |
24 | // Here is the distance (signed depending on which side of the plane we are on).
25 | return ( point - planePoint ).Dot( normalVector ) / normalVector.Abs();
26 | }
27 |
28 | public static Vector3D ProjectOntoLine( Vector3D nl, Vector3D pl, Vector3D point )
29 | {
30 | // http://gamedev.stackexchange.com/a/72529
31 | // A + dot(AP,AB) / dot(AB,AB) * AB
32 | Vector3D AP = point - pl;
33 | Vector3D AB = nl;
34 | return pl + AB * AP.Dot( AB ) / AB.Dot( AB );
35 | }
36 |
37 | public static Vector3D ProjectOntoPlane( Vector3D normalVector, Vector3D planePoint, Vector3D point )
38 | {
39 | if( !normalVector.Normalize() )
40 | throw new System.ArgumentException( "Invalid normal vector." );
41 |
42 | double dist = DistancePointPlane( normalVector, planePoint, point );
43 | normalVector *= dist;
44 | return point - normalVector;
45 | }
46 |
47 | public static double DistanceLineLine( Vector3D n1, Vector3D p1, Vector3D n2, Vector3D p2 )
48 | {
49 | // Check to make sure that neither of the normal vectors are degenerate.
50 | if( Tolerance.Zero( n1.MagSquared() ) || Tolerance.Zero( n2.MagSquared() ) )
51 | return double.NaN;
52 |
53 | Vector3D plane = n1.Cross( n2 );
54 |
55 | // Case where the lines are parallel (magnitude of the cross product will be 0).
56 | if( Tolerance.Zero( plane.MagSquared() ) )
57 | return DistancePointLine( n1, p1, p2 );
58 |
59 | return DistancePointPlane( plane, p1, p2 );
60 | }
61 |
62 | ///
63 | /// Checks if a point is anywhere on a segment.
64 | ///
65 | public static bool PointOnSegment( Vector3D s1, Vector3D s2, Vector3D point )
66 | {
67 | // Look for a degenerate triangle.
68 | double d1 = ( point - s1 ).MagSquared();
69 | double d2 = ( s2 - point ).MagSquared();
70 | double d3 = ( s2 - s1 ).MagSquared();
71 | return Tolerance.Equal( d1 + d2, d3 );
72 | }
73 |
74 | ///
75 | /// Checks to see if two segments intersect.
76 | /// This does not actually calculate the intersection point.
77 | /// It uses information from the following paper:
78 | /// http://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf
79 | ///
80 | public static bool DoSegmentsIntersect( Vector3D a1, Vector3D a2, Vector3D b1, Vector3D b2 )
81 | {
82 | /* Our approach.
83 | (1) Check if endpoints are on other segment. Note that this also checks if A endpoints equal B endpoints.
84 | (2) Calc line-line distance. If > 0, we don't intersect.
85 | (3) At this point, we only have to deal with non-parallel case in the paper, and only
86 | need to determine if we are in "region 0" (we intersect) or not (we don't)
87 | */
88 |
89 | // (1)
90 | if( PointOnSegment( a1, a2, b1 ) ||
91 | PointOnSegment( a1, a2, b2 ) ||
92 | PointOnSegment( b1, b2, a1 ) ||
93 | PointOnSegment( b1, b2, a2 ) )
94 | return true;
95 |
96 | // (2)
97 | Vector3D ma = a2 - a1;
98 | Vector3D mb = b2 - b1;
99 | if( Tolerance.GreaterThan( DistanceLineLine( ma, a1, mb, b1 ), 0 ) )
100 | return false;
101 |
102 | // (3)
103 | Vector3D D = a1 - b1;
104 | double a = ma.Dot( ma );
105 | double b = -ma.Dot( mb );
106 | double c = mb.Dot( mb );
107 | double d = ma.Dot( D );
108 | double e = -mb.Dot( D );
109 |
110 | double det = a * c - b * b;
111 | double s = b * e - c * d;
112 | double t = b * d - a * e;
113 | if( s >= 0 && s <= det &&
114 | t >= 0 && t <= det )
115 | return true;
116 |
117 | return false;
118 | }
119 |
120 | ///
121 | /// Calculate a plane normal after a transformation function is applied
122 | /// to the points.
123 | ///
124 | public static Vector3D NormalFrom3Points( Vector3D p1, Vector3D p2, Vector3D p3,
125 | System.Func transform )
126 | {
127 | Vector3D p1t = transform( p1 );
128 | Vector3D p2t = transform( p2 );
129 | Vector3D p3t = transform( p3 );
130 | return NormalFrom3Points( p1t, p2t, p3t );
131 | }
132 |
133 | public static Vector3D NormalFrom3Points( Vector3D p1, Vector3D p2, Vector3D p3 )
134 | {
135 | Vector3D v1 = p1 - p3;
136 | Vector3D v2 = p2 - p3;
137 | Vector3D normal = v1.Cross( v2 );
138 | normal.Normalize();
139 | return normal;
140 | }
141 |
142 | public static double TriangleAreaAfterTransform( ref Vector3D p1, ref Vector3D p2, ref Vector3D p3,
143 | System.Func transform )
144 | {
145 | p1 = transform( p1 );
146 | p2 = transform( p2 );
147 | p3 = transform( p3 );
148 |
149 | Vector3D v1 = p1 - p3;
150 | Vector3D v2 = p2 - p3;
151 | return 0.5 * v1.Cross( v2 ).Abs();
152 | }
153 |
154 | public static double MaxTriangleEdgeLengthAfterTransform( ref Vector3D p1, ref Vector3D p2, ref Vector3D p3,
155 | System.Func transform )
156 | {
157 | p1 = transform( p1 );
158 | p2 = transform( p2 );
159 | p3 = transform( p3 );
160 |
161 | double l1Squared = (p2 - p1).MagSquared();
162 | double l2Squared = (p3 - p2).MagSquared();
163 | double l3Squared = (p1 - p3).MagSquared();
164 | return Math.Sqrt(
165 | Math.Max( l1Squared, Math.Max( l2Squared, l3Squared ) )
166 | );
167 | }
168 |
169 | public static bool Coplanar( Vector3D[] points )
170 | {
171 | throw new System.NotImplementedException();
172 | }
173 |
174 | public static Vector3D IntersectionPlaneLine( Vector3D planeNormal, Vector3D planePoint, Vector3D nl, Vector3D pl )
175 | {
176 | double signedDistance = DistancePointPlane( planeNormal, planePoint, pl );
177 | planeNormal.Normalize();
178 |
179 | Vector3D closest = pl - planeNormal * signedDistance;
180 | Vector3D v1 = closest - pl;
181 | Vector3D v2 = nl;
182 | double angle = v1.AngleTo( v2 );
183 |
184 | nl.Normalize();
185 | return pl + nl * signedDistance / Math.Cos( angle );
186 |
187 | // XXX - needs improvement.
188 | /*
189 | Vector3D v1 = closest - pl;
190 | Vector3D v2 = nl;
191 | double angle = v1.AngleTo( v2 );
192 | Vector3D axis = v1.Cross( v2 );
193 | v1.RotateAboutAxis( axis, -angle );
194 | v1 /= Math.Cos( angle );
195 | return pl + v1;*/
196 | }
197 |
198 | public static int IntersectionSphereLine( out Vector3D int1, out Vector3D int2, Vector3D sphereCenter, double sphereRadius, Vector3D nl, Vector3D pl )
199 | {
200 | int1 = int2 = Vector3D.DneVector();
201 |
202 | // First find the distance between the sphere center and the line.
203 | // This will allow us to easily determine if there are 0, 1, or 2 intersection points.
204 | double distance = DistancePointLine( nl, pl, sphereCenter );
205 | if( double.IsNaN( distance ) )
206 | return -1;
207 |
208 | // Handle the special case where the line goes through the sphere center.
209 | if( Tolerance.Zero( distance ) )
210 | {
211 | if( Tolerance.Zero( sphereRadius ) )
212 | {
213 | // There is one intersection point (the sphere center).
214 | int1 = sphereCenter;
215 | return 1;
216 | }
217 | else
218 | {
219 | // There are 2 intersection points.
220 | Vector3D tempDV = nl;
221 | tempDV.Normalize();
222 | tempDV *= sphereRadius;
223 | int1 = sphereCenter + tempDV;
224 | int2 = sphereCenter - tempDV;
225 | return 2;
226 | }
227 | }
228 |
229 | // Handle the non-intersecting case.
230 | if( distance > sphereRadius )
231 | return 0;
232 |
233 | // Find a normalized direction vector from the sphere center to the closest point on the line.
234 | // This will help to determine the intersection points for the remaining cases.
235 | Vector3D vector = (pl - sphereCenter).Cross( nl ).Cross( nl ) * -1;
236 | if( ! vector.Normalize() )
237 | {
238 | return -1;
239 | }
240 |
241 | // Scale the direction vector to the sphere radius.
242 | vector *= sphereRadius;
243 |
244 | // Handle the case of 1 intersection.
245 | if( Tolerance.Equal( distance, sphereRadius ) )
246 | {
247 | // We just need to add the vector to the center.
248 | vector += sphereCenter;
249 | int1 = vector;
250 | return 1;
251 | }
252 |
253 | // Handle the case of 2 intersections.
254 | if( distance < sphereRadius )
255 | {
256 | // We need to rotate the vector by an angle +- alpha,
257 | // where cos( alpha ) = distance / sphereRadius;
258 | Debug.Assert( !Tolerance.Zero( sphereRadius ) );
259 | double alpha = Utils.RadiansToDegrees( Math.Acos( distance / sphereRadius ) );
260 |
261 | // Rotation vector.
262 | Vector3D rotationVector = (pl - sphereCenter).Cross( nl );
263 | Vector3D vector1 = vector, vector2 = vector;
264 | vector1.RotateAboutAxis( rotationVector, alpha );
265 | vector2.RotateAboutAxis( rotationVector, -1 * alpha );
266 |
267 | // Here are the intersection points.
268 | int1 = vector1 + sphereCenter;
269 | int2 = vector2 + sphereCenter;
270 |
271 | return 2;
272 | }
273 |
274 | Debug.Assert( false );
275 | return -1;
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/EuclideanModels.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Geometry;
4 | using R3.Math;
5 | using System.Numerics;
6 | using Math = System.Math;
7 |
8 | public enum EuclideanModel
9 | {
10 | Isometric,
11 | Conformal,
12 | Disk,
13 | UpperHalfPlane,
14 | Spiral,
15 | Loxodromic,
16 | }
17 |
18 | public class EuclideanModels
19 | {
20 | public static Vector3D DiskToIsometric( Vector3D v )
21 | {
22 | // ZZZ - Check that this is correct (it's quite close if not!)
23 | return SphericalModels.StereoToGnomonic( v );
24 | }
25 |
26 | public static Vector3D UpperHalfPlaneToIsometric( Vector3D v )
27 | {
28 | v = HyperbolicModels.UpperToPoincare( v );
29 | v = SphericalModels.StereoToGnomonic( v );
30 | return v;
31 | }
32 |
33 | ///
34 | /// https://geometricolor.wordpress.com/2017/08/25/spirals/
35 | ///
36 | public static Vector3D SpiralToIsometric( Vector3D v, int p, int m, int n )
37 | {
38 | Complex vc = v.ToComplex();
39 | v = new Vector3D( Math.Log( vc.Magnitude ), vc.Phase );
40 |
41 | Vector3D e1 = new Vector3D( 0, 1 );
42 | Vector3D e2;
43 | switch( p )
44 | {
45 | case 3:
46 | e2 = new Vector3D(); break;
47 | case 4:
48 | e2 = new Vector3D(); break;
49 | case 6:
50 | e2 = new Vector3D(); break;
51 | default:
52 | throw new System.ArgumentException();
53 | }
54 |
55 | double scale = Math.Sqrt( m * m + n * n );
56 | double a = Euclidean2D.AngleToClock( e1, new Vector3D( m, n ) );
57 |
58 | v.RotateXY( a ); // Rotate
59 | v *= scale; // Scale
60 |
61 | v *= Math.Sqrt( 2 ) * Geometry2D.EuclideanHypotenuse / ( 2 * Math.PI );
62 | v.RotateXY( Math.PI / p );
63 | return v;
64 | }
65 |
66 | public static Vector3D LoxodromicToIsometric( Vector3D v, int p, int m, int n )
67 | {
68 | Mobius mob = Mobius.CreateFromIsometry( Geometry.Spherical, 0, new System.Numerics.Complex( 1, 0 ) );
69 | v = mob.Apply( v );
70 | return SpiralToIsometric( v, p, m, n );
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Geometry2D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using Math = System.Math;
4 | using R3.Core;
5 | using R3.Math;
6 | using System.Diagnostics;
7 |
8 | public enum Geometry
9 | {
10 | Spherical,
11 | Euclidean,
12 | Hyperbolic
13 | }
14 |
15 | public static class PlatonicSolids
16 | {
17 | public static int NumFacets( int p, int q )
18 | {
19 | if( p == 3 && q == 3 )
20 | return 4;
21 | else if( p == 4 && q == 3 )
22 | return 6;
23 | else if( p == 5 && q == 3 )
24 | return 12;
25 | else if( p == 3 && q == 4 )
26 | return 8;
27 | else if( p == 3 && q == 5 )
28 | return 20;
29 | else
30 | throw new System.ArgumentException();
31 | }
32 | }
33 |
34 | public static class Geometry2D
35 | {
36 | public static double PiOverNSafe( double n )
37 | {
38 | return n == -1 ? 0 : Math.PI / n;
39 | }
40 |
41 | // Returns the geometry induced by a polygon with p points, q meeting at a vertex.
42 | public static Geometry GetGeometry( double p, double q )
43 | {
44 | double test = 1.0 / p + 1.0 / q;
45 | if( test > 0.5 )
46 | return Geometry.Spherical;
47 | else if( Tolerance.Equal( test, 0.5 ) )
48 | return Geometry.Euclidean;
49 |
50 | return Geometry.Hyperbolic;
51 | }
52 |
53 | public static double EuclideanHypotenuse = 1.0 / 3; // ZZZ - ??????????
54 | public static double DiskRadius = 1;
55 |
56 | public static double GetNormalizedCircumRadius( int p, double q )
57 | {
58 | double hypot = GetTriangleHypotenuse( p, q );
59 | switch( Geometry2D.GetGeometry( p, q ) )
60 | {
61 | case Geometry.Spherical:
62 | return Spherical2D.s2eNorm( hypot ) * DiskRadius;
63 |
64 | case Geometry.Euclidean:
65 | return EuclideanHypotenuse;
66 |
67 | case Geometry.Hyperbolic:
68 | {
69 | if( Infinity.IsInfinite( hypot ) )
70 | return DiskRadius;
71 |
72 | return DonHatch.h2eNorm( hypot ) * DiskRadius;
73 | }
74 | }
75 |
76 | Debug.Assert( false );
77 | return 1;
78 | }
79 |
80 | ///
81 | /// In the induced geometry.
82 | ///
83 | public static double GetTriangleHypotenuse( int p, double q )
84 | {
85 | Geometry g = Geometry2D.GetGeometry( p, q );
86 | if( g == Geometry.Euclidean )
87 | return EuclideanHypotenuse;
88 |
89 | // We have a 2,q,p triangle, where the right angle alpha
90 | // is opposite the hypotenuse (the length we want).
91 | double alpha = Math.PI / 2;
92 | double beta = PiOverNSafe( q );
93 | double gamma = PiOverNSafe( p );
94 | return GetTriangleSide( g, alpha, beta, gamma );
95 | }
96 |
97 | ///
98 | /// Get the side length opposite angle PI/P,
99 | /// In the induced geometry.
100 | ///
101 | public static double GetTrianglePSide( int p, int q )
102 | {
103 | Geometry g = Geometry2D.GetGeometry( p, q );
104 |
105 | double alpha = Math.PI / 2;
106 | double beta = PiOverNSafe( q );
107 | double gamma = PiOverNSafe( p ); // The one we want.
108 | if( g == Geometry.Euclidean )
109 | return EuclideanHypotenuse * Math.Sin( gamma );
110 | return GetTriangleSide( g, gamma, beta, alpha );
111 | }
112 |
113 | ///
114 | /// Get the side length opposite angle PI/Q,
115 | /// In the induced geometry.
116 | ///
117 | public static double GetTriangleQSide( int p, int q )
118 | {
119 | Geometry g = Geometry2D.GetGeometry( p, q );
120 |
121 | double alpha = Math.PI / 2;
122 | double beta = PiOverNSafe( q ); // The one we want.
123 | double gamma = PiOverNSafe( p );
124 | if( g == Geometry.Euclidean )
125 | return EuclideanHypotenuse * Math.Sin( beta );
126 |
127 | return GetTriangleSide( g, beta, gamma, alpha );
128 | }
129 |
130 | ///
131 | /// Get the length of the side of a triangle opposite alpha, given the three angles of the triangle.
132 | /// NOTE: This does not work in Euclidean geometry!
133 | ///
134 | public static double GetTriangleSide( Geometry g, double alpha, double beta, double gamma )
135 | {
136 | switch( g )
137 | {
138 | case Geometry.Spherical:
139 | {
140 | // Spherical law of cosines
141 | return Math.Acos( ( Math.Cos( alpha ) + Math.Cos( beta ) * Math.Cos( gamma ) ) / ( Math.Sin( beta ) * Math.Sin( gamma ) ) );
142 | }
143 | case Geometry.Euclidean:
144 | {
145 | // Not determined in this geometry.
146 | Debug.Assert( false );
147 | return 0.0;
148 | }
149 | case Geometry.Hyperbolic:
150 | {
151 | // Hyperbolic law of cosines
152 | // http://en.wikipedia.org/wiki/Hyperbolic_law_of_cosines
153 | return DonHatch.acosh( ( Math.Cos( alpha ) + Math.Cos( beta ) * Math.Cos( gamma ) ) / ( Math.Sin( beta ) * Math.Sin( gamma ) ) );
154 | }
155 | }
156 |
157 | // Not determined in this geometry.
158 | Debug.Assert( false );
159 | return 0.0;
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Hyperbolic2D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Math;
4 |
5 | public static class Hyperbolic2D
6 | {
7 | ///
8 | /// Offsets a vector by a hyperbolic distance.
9 | ///
10 | public static Vector3D Offset( Vector3D v, double hDist )
11 | {
12 | double mag = v.Abs();
13 | mag = DonHatch.h2eNorm( DonHatch.e2hNorm( mag ) + hDist );
14 | v.Normalize();
15 | v *= mag;
16 | return v;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/HyperbolicModels.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Math;
4 | using System;
5 | using System.Numerics;
6 |
7 | public enum HyperbolicModel
8 | {
9 | Poincare,
10 | Klein,
11 | Pseudosphere,
12 | Hyperboloid,
13 | Band,
14 | UpperHalfPlane,
15 | Orthographic,
16 | Square,
17 | InvertedPoincare,
18 | Joukowsky,
19 | Ring,
20 | Azimuthal_Equidistant,
21 | Azimuthal_EqualArea,
22 | Schwarz_Christoffel
23 | }
24 |
25 | public class HyperbolicModels
26 | {
27 | public static Vector3D PoincareToKlein( Vector3D p )
28 | {
29 | double mag = 2 / (1 + p.Dot( p ));
30 | return p * mag;
31 | }
32 |
33 | public static double KleinToPoincare( double magSquared )
34 | {
35 | double dot = magSquared;
36 | if (dot > 1) // This avoids some NaN problems I saw.
37 | dot = 1;
38 | return (1 - Math.Sqrt( 1 - dot )) / dot;
39 | }
40 |
41 | public static Vector3D KleinToPoincare( Vector3D k )
42 | {
43 | double dot = k.Dot( k );
44 | return k * KleinToPoincare( dot );
45 | }
46 |
47 | public static Mobius Upper
48 | {
49 | get
50 | {
51 | Cache();
52 | return m_upper;
53 | }
54 | }
55 | public static Mobius UpperInv
56 | {
57 | get
58 | {
59 | Cache();
60 | return m_upperInv;
61 | }
62 | }
63 |
64 | ///
65 | /// This was needed for performance. We don't want this Mobius transform calculated repeatedly.
66 | ///
67 | private static void Cache()
68 | {
69 | if( m_cached )
70 | return;
71 |
72 | Mobius m1 = new Mobius(), m2 = new Mobius();
73 | m2.Isometry( Geometry.Euclidean, 0, new Complex( 0, -1 ) );
74 | m1.UpperHalfPlane();
75 | m_upper = m2 * m1;
76 | m_upperInv = m_upper.Inverse();
77 |
78 | m_cached = true;
79 | }
80 | private static bool m_cached = false;
81 | private static Mobius m_upper, m_upperInv;
82 |
83 |
84 | public static Vector3D PoincareToUpper( Vector3D v )
85 | {
86 | v = Upper.Apply( v );
87 | return v;
88 | }
89 |
90 | public static Vector3D UpperToPoincare( Vector3D v )
91 | {
92 | v = UpperInv.Apply( v );
93 | return v;
94 | }
95 |
96 | public static Vector3D PoincareToOrtho( Vector3D v )
97 | {
98 | // This may not be correct.
99 | // Should probably project to hyperboloid, then remove z coord.
100 | return SphericalModels.StereoToGnomonic( v );
101 | }
102 |
103 | public static Vector3D OrthoToPoincare( Vector3D v )
104 | {
105 | return SphericalModels.GnomonicToStereo( v );
106 | }
107 |
108 | public static Vector3D PoincareToBand( Vector3D v )
109 | {
110 | Complex z = v.ToComplex();
111 | z = 2 / Math.PI * Complex.Log( ( 1 + z ) / ( 1 - z ) );
112 | return Vector3D.FromComplex( z );
113 | }
114 |
115 | public static Vector3D BandToPoincare( Vector3D v )
116 | {
117 | Complex vc = v.ToComplex();
118 | vc = ( Complex.Exp( Math.PI * vc / 2 ) - 1 ) / ( Complex.Exp( Math.PI * vc / 2 ) + 1 );
119 | return Vector3D.FromComplex( vc );
120 | }
121 |
122 | /// The Euclidean period of the tiling in the band model.
123 | public static Vector3D BandToRing( Vector3D v, double P, int k )
124 | {
125 | Complex vc = v.ToComplex();
126 | Complex i = new Complex( 0, 1 );
127 | vc = Complex.Exp( 2*Math.PI*i*(vc+i)/(k*P) );
128 | return Vector3D.FromComplex( vc );
129 | }
130 |
131 | /// The Euclidean period of the tiling in the band model.
132 | public static Vector3D RingToBand( Vector3D v, double P, int k)
133 | {
134 | Complex vc = v.ToComplex();
135 | Complex i = new Complex( 0, 1 );
136 | vc = -P*k*i*Complex.Log( vc ) / (2*Math.PI) - i;
137 | return Vector3D.FromComplex( vc );
138 | }
139 |
140 | public static Vector3D RingToPoincare( Vector3D v, double P, int k)
141 | {
142 | Vector3D b = RingToBand( v, P, k );
143 | return BandToPoincare( b );
144 | }
145 |
146 | public static Vector3D JoukowskyToPoincare( Vector3D v, Vector3D cen )
147 | {
148 | Complex w = v.ToComplex();
149 |
150 | // Conformally map disk to ellipse with a > 1 and b = 1;
151 | // https://math.stackexchange.com/questions/1582608/conformally-mapping-an-ellipse-into-the-unit-circle
152 | // https://www.physicsforums.com/threads/conformal-mapping-unit-circle-ellipse.218014/
153 | double a = 1.0;
154 | double b = -cen.Abs();
155 | double alpha = ( a + b ) / 2;
156 | double beta = ( a - b ) / 2;
157 |
158 | // disk -> ellipse
159 | // Complex result = alpha * z + beta / z;
160 |
161 | double off = cen.Abs();
162 | System.Func foil = z =>
163 | {
164 | //w *= 1 + Math.Sqrt( 2 );
165 | //Vector3D cen = new Vector3D( -off, -off );
166 | double rad = 1 + off;// cen.Dist( new Vector3D( 1, 0 ) );
167 | z *= rad;
168 | z += cen.ToComplex();
169 | return z;
170 | };
171 |
172 | // ellipse->disk
173 | Complex temp = Complex.Sqrt( w * w - 4 * alpha * beta );
174 | if( w.Real > 0 )
175 | temp *= -1;
176 | w = ( w + temp ) / ( 2 * alpha );
177 | return Vector3D.FromComplex( w );
178 |
179 | /*Complex r1 = ( w + Complex.Sqrt( w * w - alpha * beta ) ) / alpha;
180 | Complex r2 = ( w - Complex.Sqrt( w * w - alpha * beta ) ) / alpha;
181 | r1 = foil( r1 );
182 | r2 = foil( r2 );
183 | if( r1.Magnitude <= 1 ) // Pick the root that puts us inside the disk.
184 | {
185 | w = r1;
186 | }
187 | else
188 | {
189 | w = r2;
190 | }
191 |
192 | return Vector3D.FromComplex( w );*/
193 | }
194 |
195 | public static Vector3D EquidistantToPoincare( Vector3D p )
196 | {
197 | Vector3D result = p;
198 | result.Normalize();
199 | result *= EquidistantToPoincare( p.Abs() );
200 | return result;
201 | }
202 |
203 | private static double EquidistantToPoincare( double dist )
204 | {
205 | return DonHatch.h2eNorm( dist );
206 | }
207 |
208 | public static Vector3D EqualAreaToPoincare( Vector3D p )
209 | {
210 | Vector3D result = p;
211 | result.Normalize();
212 | result *= EqualAreaToPoincare( p.Abs() );
213 | return result;
214 | }
215 |
216 | private static double EqualAreaToPoincare( double dist )
217 | {
218 | double h = 2 * DonHatch.asinh( dist );
219 | return DonHatch.h2eNorm( h );
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/NearTree.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Math;
4 | using System.Collections.Generic;
5 |
6 | /*
7 | This code was adapted from an article and written by Larry Andrews
8 | in the November 2001 issue of C/C++ Users Journal.
9 | http://drdobbs.com/184401449
10 |
11 | It is a class for the nearest neighbor problem based on partition trees.
12 |
13 | NOTE: This class is meant for static data sets, where a near tree can be
14 | built up once and then used for many lookups. Time to build the tree
15 | is proportional to n*log(n). Lookup times are proportional to log(n).
16 | */
17 |
18 | public enum Metric
19 | {
20 | Spherical,
21 | Euclidean,
22 | Hyperbolic
23 | // GraphDist, etc. (anything that obeys the triangle inequality).
24 | }
25 |
26 | public class NearTreeObject
27 | {
28 | public object ID { get; set; }
29 | public Vector3D Location { get; set; }
30 | }
31 |
32 | public class NearTree
33 | {
34 | public NearTree()
35 | {
36 | Reset( Metric.Euclidean );
37 | }
38 |
39 | public NearTree( Metric m )
40 | {
41 | Reset( m );
42 | }
43 |
44 | public static Metric GtoM( Geometry g )
45 | {
46 | switch( g )
47 | {
48 | case Geometry.Spherical:
49 | return Metric.Spherical;
50 | case Geometry.Euclidean:
51 | return Metric.Euclidean;
52 | case Geometry.Hyperbolic:
53 | return Metric.Hyperbolic;
54 | }
55 |
56 | return Metric.Euclidean;
57 | }
58 |
59 | public void Reset( Metric m )
60 | {
61 | this.Metric = m;
62 |
63 | m_pLeftBranch = m_pRightBranch = null;
64 | m_pLeft = m_pRight = null;
65 |
66 | m_maxLeft = double.MinValue;
67 | m_maxRight = double.MinValue;
68 | }
69 |
70 | ///
71 | /// The distance metric to use.
72 | ///
73 | public Metric Metric { get; set; }
74 |
75 | // Left/right objects stored in this node.
76 | private NearTreeObject m_pLeft;
77 | private NearTreeObject m_pRight;
78 |
79 | // Longest distance from the left/right
80 | // objects to anything below it in the tree.
81 | private double m_maxLeft;
82 | private double m_maxRight;
83 |
84 | // Tree descending from the left/right.
85 | private NearTree m_pLeftBranch;
86 | private NearTree m_pRightBranch;
87 |
88 | ///
89 | /// Inserts an object into the neartree.
90 | ///
91 | public void InsertObject( NearTreeObject nearTreeObject )
92 | {
93 | double tempRight = 0;
94 | double tempLeft = 0;
95 | if( m_pRight != null )
96 | {
97 | tempRight = Dist( nearTreeObject.Location, m_pRight.Location );
98 | tempLeft = Dist( nearTreeObject.Location, m_pLeft.Location );
99 | }
100 |
101 | if( m_pLeft == null )
102 | m_pLeft = nearTreeObject;
103 | else if( m_pRight == null )
104 | m_pRight = nearTreeObject;
105 | else if( tempLeft > tempRight )
106 | {
107 | if( m_pRightBranch == null )
108 | m_pRightBranch = new NearTree( this.Metric );
109 |
110 | // Note: that the next line assumes that m_maxRight
111 | // is negative for a new node.
112 | if( m_maxRight < tempRight )
113 | m_maxRight = tempRight;
114 | m_pRightBranch.InsertObject( nearTreeObject );
115 | }
116 | else
117 | {
118 | if( m_pLeftBranch == null )
119 | m_pLeftBranch = new NearTree( this.Metric );
120 |
121 | // Note: that the next line assumes that m_maxLeft
122 | // is negative for a new node.
123 | if( m_maxLeft < tempLeft )
124 | m_maxLeft = tempLeft;
125 | m_pLeftBranch.InsertObject( nearTreeObject );
126 | }
127 | }
128 |
129 | ///
130 | /// Finds the nearest neighbor to a location, and
131 | /// withing a specified search radius (returns false if none found).
132 | ///
133 | public bool FindNearestNeighbor( out NearTreeObject closest, Vector3D location, double searchRadius )
134 | {
135 | closest = null;
136 | return FindNearestNeighborRecursive( ref closest, location, ref searchRadius );
137 | }
138 |
139 | ///
140 | /// Finds all the objects withing a certain radius of some location (returns false if none found).
141 | ///
142 | public bool FindCloseObjects( out NearTreeObject[] closeObjects, Vector3D location, double searchRadius )
143 | {
144 | List result = new List();
145 | bool found = 0 != FindCloseObjectsRecursive( ref result, location, searchRadius );
146 | closeObjects = result.ToArray();
147 | return found;
148 | }
149 |
150 | private bool FindNearestNeighborRecursive( ref NearTreeObject closest, Vector3D location, ref double searchRadius )
151 | {
152 | double tempRadius = 0;
153 | bool bRet = false;
154 |
155 | // First test each of the left and right positions to see
156 | // if one holds a point nearer than the nearest so far.
157 | if( m_pLeft != null )
158 | {
159 | tempRadius = Dist( location, m_pLeft.Location );
160 | if( tempRadius <= searchRadius )
161 | {
162 | searchRadius = tempRadius;
163 | closest = m_pLeft;
164 | bRet = true;
165 | }
166 | }
167 | if( m_pRight != null )
168 | {
169 | tempRadius = Dist( location, m_pRight.Location );
170 | if( tempRadius <= searchRadius )
171 | {
172 | searchRadius = tempRadius;
173 | closest = m_pRight;
174 | bRet = true;
175 | }
176 | }
177 |
178 | // Now we test to see if the branches below might hold an
179 | // object nearer than the best so far found. The triangle
180 | // rule is used to test whether it's even necessary to descend.
181 | if( m_pLeftBranch != null )
182 | {
183 | if( (searchRadius + m_maxLeft) >= Dist( location, m_pLeft.Location ) )
184 | {
185 | bRet |= m_pLeftBranch.FindNearestNeighborRecursive( ref closest, location, ref searchRadius );
186 | }
187 | }
188 | if( m_pRightBranch != null )
189 | {
190 | if( (searchRadius + m_maxRight) >= Dist( location, m_pRight.Location ) )
191 | {
192 | bRet |= m_pRightBranch.FindNearestNeighborRecursive( ref closest, location, ref searchRadius );
193 | }
194 | }
195 |
196 | return bRet;
197 | }
198 |
199 | private long FindCloseObjectsRecursive( ref List closeObjects, Vector3D location, double searchRadius )
200 | {
201 | long lReturn = 0;
202 |
203 | // First test each of the left and right positions to see
204 | // if one holds a point nearer than the search radius.
205 | if( ( m_pLeft != null ) && ( Dist( location, m_pLeft.Location ) <= searchRadius ) )
206 | {
207 | closeObjects.Add( m_pLeft );
208 | lReturn++;
209 | }
210 | if( ( m_pRight != null ) && ( Dist( location, m_pRight.Location ) <= searchRadius ) )
211 | {
212 | closeObjects.Add( m_pRight );
213 | lReturn++;
214 | }
215 |
216 | //
217 | // Now we test to see if the branches below might hold an
218 | // object nearer than the search radius. The triangle rule
219 | // is used to test whether it's even necessary to descend.
220 | //
221 | if( ( m_pLeftBranch != null ) && ( ( searchRadius + m_maxLeft ) >= Dist( location, m_pLeft.Location ) ) )
222 | {
223 | lReturn += m_pLeftBranch.FindCloseObjectsRecursive( ref closeObjects, location, searchRadius );
224 | }
225 | if( ( m_pRightBranch != null ) && ( ( searchRadius + m_maxRight ) >= Dist( location, m_pRight.Location ) ) )
226 | {
227 | lReturn += m_pRightBranch.FindCloseObjectsRecursive( ref closeObjects, location, searchRadius );
228 | }
229 |
230 | return lReturn;
231 | }
232 |
233 | // Gets the distance between two points.
234 | private double Dist( Vector3D p1, Vector3D p2 )
235 | {
236 | switch( this.Metric )
237 | {
238 | case Metric.Spherical:
239 | {
240 | // ZZZ - Is it too expensive to build up a mobius every time?
241 | // I wonder if there is a better way.
242 | Mobius m = new Mobius();
243 | m.Isometry( Geometry.Spherical, 0, -p1 );
244 | Vector3D temp = m.Apply( p2 );
245 | return Spherical2D.e2sNorm( temp.Abs() );
246 | }
247 | case Metric.Euclidean:
248 | {
249 | return ( p2 - p1 ).Abs();
250 | }
251 | case Metric.Hyperbolic:
252 | {
253 | // ZZZ - Is it too expensive to build up a mobius every time?
254 | // I wonder if there is a better way.
255 | Mobius m = new Mobius();
256 | m.Isometry( Geometry.Hyperbolic, 0, -p1 );
257 | Vector3D temp = m.Apply( p2 );
258 | return DonHatch.e2hNorm( temp.Abs() );
259 | }
260 | }
261 |
262 | throw new System.NotImplementedException();
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Spherical2D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using Math = System.Math;
4 | using R3.Core;
5 | using System.Diagnostics;
6 |
7 | public static class Spherical2D
8 | {
9 | // The next two methods mimic Don's stuff for the hyperbolic plane.
10 | public static double
11 | s2eNorm( double sNorm )
12 | {
13 | //if( double.IsNaN( sNorm ) )
14 | // return 1.0;
15 | return Math.Tan( .5 * sNorm );
16 | }
17 |
18 | public static double
19 | e2sNorm( double eNorm )
20 | {
21 | return 2 * Math.Atan( eNorm );
22 | }
23 |
24 | ///
25 | /// Sphere geometry is implicit. A radius 1 sphere with the center at the origin.
26 | ///
27 | public static Vector3D SphereToPlane( Vector3D spherePoint )
28 | {
29 | Vector3D projected = spherePoint.CentralProject( 1.0 );
30 | return projected;
31 | }
32 |
33 | ///
34 | /// Sphere geometry is implicit. A radius 1 sphere with the center at the origin.
35 | ///
36 | public static Vector3D PlaneToSphere( Vector3D planePoint )
37 | {
38 | planePoint.Z = 0; // Just to be safe.
39 | double magSquared = planePoint.MagSquared();
40 | Vector3D result = new Vector3D(
41 | 2 * planePoint.X / ( 1.0 + magSquared ),
42 | 2 * planePoint.Y / ( 1.0 + magSquared ),
43 | ( magSquared - 1.0 ) / ( magSquared + 1.0 ) );
44 | return result;
45 | }
46 |
47 | ///
48 | /// Calculates the two poles of a great circle defined by two points.
49 | ///
50 | public static void GreatCirclePole( Vector3D sphereCenter, Vector3D p1, Vector3D p2,
51 | out Vector3D pole1, out Vector3D pole2 )
52 | {
53 | double sphereRadius = p1.Dist( sphereCenter );
54 | Debug.Assert( Tolerance.Equal( sphereRadius, p2.Dist( sphereCenter ) ) );
55 |
56 | Vector3D v1 = p1 - sphereCenter;
57 | Vector3D v2 = p2 - sphereCenter;
58 | pole1 = v1.Cross( v2 ) + sphereCenter;
59 | pole2 = v2.Cross( v1 ) + sphereCenter;
60 | }
61 |
62 | ///
63 | /// Same as above, but with implicit sphere geometry. A radius 1 sphere with the center at the origin.
64 | ///
65 | public static void GreatCirclePole( Vector3D p1, Vector3D p2,
66 | out Vector3D pole1, out Vector3D pole2 )
67 | {
68 | GreatCirclePole( new Vector3D( 0, 0, 0 ), p1, p2, out pole1, out pole2 );
69 | }
70 |
71 | ///
72 | /// Spherical distance between two points on the plane.
73 | ///
74 | public static double SDist( Vector3D p1, Vector3D p2 )
75 | {
76 | p1 = PlaneToSphere( p1 );
77 | p2 = PlaneToSphere( p2 );
78 | return p1.AngleTo( p2 );
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/SphericalModels.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Math;
4 | using System;
5 | using System.Numerics;
6 |
7 | public enum SphericalModel
8 | {
9 | Sterographic,
10 | Gnomonic,
11 | Azimuthal_Equidistant,
12 | Azimuthal_EqualArea,
13 | Equirectangular,
14 | Mercator,
15 | Orthographic,
16 | Sinusoidal,
17 | PeirceQuincuncial,
18 | }
19 |
20 | public class SphericalModels
21 | {
22 | public static Vector3D StereoToGnomonic( Vector3D p )
23 | {
24 | Vector3D sphere = Sterographic.PlaneToSphere( p );
25 |
26 | // We can only represent the lower hemisphere.
27 | if( sphere.Z >= 0 )
28 | {
29 | sphere.Z = 0;
30 | sphere.Normalize();
31 | sphere *= Infinity.FiniteScale;
32 | return sphere;
33 | }
34 |
35 | double z = sphere.Z;
36 | sphere.Z = 0;
37 | return -sphere * m_gScale / z;
38 | }
39 |
40 | public static Vector3D GnomonicToStereo( Vector3D g )
41 | {
42 | g /= m_gScale;
43 | double dot = g.Dot( g ); // X^2 + Y^2
44 | double z = -1 / Math.Sqrt( dot + 1 );
45 | return g*z / (z - 1);
46 | }
47 |
48 | public static Vector3D StereoToEqualVolume( Vector3D p )
49 | {
50 | Vector3D result = p;
51 | result.Normalize();
52 | result *= StereoToEqualVolume( p.Abs() );
53 | return result;
54 | }
55 |
56 | private static double StereoToEqualVolume( double dist )
57 | {
58 | if( Infinity.IsInfinite( dist ) )
59 | return 1;
60 |
61 | double dot = dist * dist; // X^2 + Y^2 + Z^2
62 | double w = (dot - 1) / (dot + 1);
63 |
64 | w = -w; // Because I derived formula from north pole.
65 | double t = Math.PI / 2 - w * Math.Sqrt( 1 - w * w ) - Math.Asin( w );
66 | double r = Math.Pow( t * 3 / 2, 1.0 / 3 );
67 | return r;
68 | }
69 |
70 | public static Vector3D StereoToEqualArea( Vector3D p )
71 | {
72 | Vector3D result = p;
73 | result.Normalize();
74 | result *= StereoToEqualArea( p.Abs() );
75 | return result;
76 | }
77 |
78 | ///
79 | /// https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection
80 | ///
81 | private static double StereoToEqualArea( double dist )
82 | {
83 | if( Infinity.IsInfinite( dist ) )
84 | return 1;
85 |
86 | double dot = dist * dist; // X^2 + Y^2 + Z^2
87 | double w = (dot - 1) / (dot + 1);
88 |
89 | double r = Math.Sqrt( 2 * (1 + w) );
90 | return r/2;
91 | }
92 |
93 | public static Vector3D EqualAreaToStereo( Vector3D p )
94 | {
95 | Vector3D result = p;
96 | result.Normalize();
97 | result *= EqualAreaToStereo( p.Abs() );
98 | return result;
99 | }
100 |
101 | ///
102 | /// https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection
103 | ///
104 | private static double EqualAreaToStereo( double dist )
105 | {
106 | if( dist > 1 )
107 | throw new System.ArgumentException();
108 |
109 | // We have dist normalized between 0 and 1, so this formula is slightly
110 | // different than on Wikipedia, where dist ranges up to 2.
111 | Vector3D v = new Vector3D( 1, 2*Math.Acos( dist ), 0 );
112 | v = Sterographic.SphereToPlane( SphericalCoords.SphericalToCartesian( v ) );
113 | return v.Abs();
114 | }
115 |
116 | public static Vector3D StereoToEquidistant( Vector3D p )
117 | {
118 | Vector3D result = p;
119 | result.Normalize();
120 | result *= StereoToEquidistant( p.Abs() );
121 | return result;
122 | }
123 |
124 | private static double StereoToEquidistant( double dist )
125 | {
126 | if( Infinity.IsInfinite( dist ) )
127 | return 1;
128 |
129 | double dot = dist * dist; // X^2 + Y^2 + Z^2
130 | double w = (dot - 1) / (dot + 1);
131 |
132 | double x = Math.Sqrt( 1 - w * w );
133 | double r = Euclidean2D.AngleToCounterClock( new Vector3D( 0, -1 ), new Vector3D( x, w ) );
134 | return r / Math.PI;
135 | }
136 |
137 | public static Vector3D EquidistantToStereo( Vector3D p )
138 | {
139 | Vector3D result = p;
140 | result.Normalize();
141 | result *= EquidistantToStereo( p.Abs() );
142 | return result;
143 | }
144 |
145 | private static double EquidistantToStereo( double dist )
146 | {
147 | if( dist > 1 )
148 | throw new System.ArgumentException();
149 |
150 | Vector3D v = new Vector3D( 0, -1 );
151 | v.RotateXY( dist * Math.PI );
152 | v = Sterographic.SphereToPlane( new Vector3D( v.X, 0, v.Y ) );
153 | return v.Abs();
154 | }
155 |
156 | public static Vector3D EquirectangularToStereo( Vector3D v )
157 | {
158 | // http://mathworld.wolfram.com/EquirectangularProjection.html
159 | // y is the latitude
160 | // x is the longitude
161 | // Assume inputs go from -1 to 1.
162 | Vector3D spherical = new Vector3D( 1, Math.PI / 2 * (1 - v.Y), v.X * Math.PI );
163 | Vector3D onBall = SphericalCoords.SphericalToCartesian( spherical );
164 | return Sterographic.SphereToPlane( onBall );
165 | }
166 |
167 | public static Vector3D SinusoidalToStereo(Vector3D v)
168 | {
169 | double lat = Math.PI / 2 * ( 1 - v.Y );
170 | Vector3D spherical = new Vector3D( 1, lat, Math.PI * v.X / Math.Cos( lat - Math.PI / 2 ) );
171 | Vector3D onBall = SphericalCoords.SphericalToCartesian( spherical );
172 | return Sterographic.SphereToPlane( onBall );
173 | }
174 |
175 | ///
176 | /// 2-dimensional function.
177 | /// http://archive.bridgesmathart.org/2013/bridges2013-217.pdf
178 | ///
179 | public static Vector3D MercatorToStereo( Vector3D v )
180 | {
181 | v *= Math.PI; // Input is [-1,1]
182 | double lat = 2 * Math.Atan( Math.Exp( v.Y ) ) - Math.PI / 2;
183 | double inclination = lat + Math.PI / 2;
184 | Vector3D spherical = new Vector3D( 1, inclination, v.X );
185 | Vector3D onBall = SphericalCoords.SphericalToCartesian( spherical );
186 | return Sterographic.SphereToPlane( onBall );
187 | }
188 |
189 | ///
190 | /// 2-dimensional function.
191 | /// ZZZ - Should make this general.
192 | ///
193 | public static Vector3D OrthographicToStereo( Vector3D v )
194 | {
195 | // We can only do the projection for half of the sphere.
196 | double t = v.X * v.X + v.Y * v.Y;
197 | if( t > 1 )
198 | t = 1;
199 | v.Z = Math.Sqrt( 1 - t );
200 | return Sterographic.SphereToPlane( v );
201 | }
202 |
203 | private static double m_gScale = 0.5;
204 | }
205 | }
--------------------------------------------------------------------------------
/R3.Core/Geometry/Sterographic.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using R3.Geometry;
5 | using Math = System.Math;
6 |
7 | public static class Sterographic
8 | {
9 | public static Vector3D PlaneToSphereSafe( Vector3D planePoint )
10 | {
11 | if( Infinity.IsInfinite( planePoint ) )
12 | return new Vector3D( 0, 0, 1 );
13 |
14 | return PlaneToSphere( planePoint );
15 | }
16 |
17 | public static Vector3D PlaneToSphere( Vector3D planePoint )
18 | {
19 | planePoint.Z = 0;
20 | double dot = planePoint.Dot( planePoint ); // X^2 + Y^2
21 | return new Vector3D(
22 | 2 * planePoint.X / ( dot + 1 ),
23 | 2 * planePoint.Y / ( dot + 1 ),
24 | ( dot - 1 ) / ( dot + 1 ) );
25 | }
26 |
27 | public static Vector3D SphereToPlane( Vector3D spherePoint )
28 | {
29 | double z = spherePoint.Z;
30 | return new Vector3D(
31 | spherePoint.X / ( 1 - z ),
32 | spherePoint.Y / ( 1 - z ) );
33 | }
34 |
35 | public static Vector3D R3toS3( Vector3D p )
36 | {
37 | if( Infinity.IsInfinite( p ) )
38 | return new Vector3D( 0, 0, 0, 1 );
39 |
40 | p.W = 0;
41 | double dot = p.Dot( p ); // X^2 + Y^2 + Z^2
42 | return new Vector3D(
43 | 2 * p.X / ( dot + 1 ),
44 | 2 * p.Y / ( dot + 1 ),
45 | 2 * p.Z / ( dot + 1 ),
46 | ( dot - 1 ) / ( dot + 1 ) );
47 | }
48 |
49 | public static Vector3D S3toR3( Vector3D p )
50 | {
51 | double w = p.W;
52 | if( Tolerance.Equal( w, 1 ) )
53 | return Vector3D.DneVector();
54 |
55 | return new Vector3D(
56 | p.X / ( 1 - w ),
57 | p.Y / ( 1 - w ),
58 | p.Z / ( 1 - w ) );
59 | }
60 |
61 | ///
62 | /// See http://en.wikipedia.org/wiki/Poincar%C3%A9_disk_model#Relation_to_the_hyperboloid_model
63 | ///
64 | public static Vector3D PlaneToHyperboloid( Vector3D planePoint )
65 | {
66 | double temp = planePoint.X * planePoint.X + planePoint.Y * planePoint.Y;
67 | return new Vector3D(
68 | 2*planePoint.X / ( 1 - temp ),
69 | 2*planePoint.Y / ( 1 - temp ),
70 | ( 1 + temp ) / ( 1 - temp ) );
71 | }
72 |
73 | public static Vector3D HyperboloidToPlane( Vector3D hyperboloidPoint )
74 | {
75 | double z = hyperboloidPoint.Z;
76 | return new Vector3D(
77 | hyperboloidPoint.X / ( 1 + z ),
78 | hyperboloidPoint.Y / ( 1 + z ) );
79 | }
80 |
81 | public static Vector3D PoincareBallToHyperboloid( Vector3D planePoint )
82 | {
83 | double temp = planePoint.Dot( planePoint );
84 | return new Vector3D(
85 | 2 * planePoint.X / ( 1 - temp ),
86 | 2 * planePoint.Y / ( 1 - temp ),
87 | 2 * planePoint.Z / ( 1 - temp ),
88 | ( 1 + temp ) / ( 1 - temp ) );
89 | }
90 |
91 | public static Vector3D HyperboloidToPoincareBall( Vector3D hyperboloidPoint )
92 | {
93 | double z = hyperboloidPoint.Z;
94 | return new Vector3D(
95 | hyperboloidPoint.X / ( 1 + z ),
96 | hyperboloidPoint.Y / ( 1 + z ),
97 | hyperboloidPoint.Z / ( 1 + z ) );
98 | }
99 |
100 | public static void NormalizeToHyperboloid( ref Vector3D v )
101 | {
102 | double normSquared = v.Z * v.Z - ( v.X * v.X + v.Y * v.Y );
103 | double norm = Math.Sqrt( normSquared );
104 | v /= norm;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Surface.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Math;
4 | using System.Numerics;
5 | using Math = System.Math;
6 |
7 | public class Surface
8 | {
9 | ///
10 | /// Transform a mesh in the Poincare model to Dini's surface.
11 | ///
12 | public static Mesh Dini( Mesh mesh )
13 | {
14 | System.Func transform = v =>
15 | {
16 | //v = DiskToUpper( v );
17 | //v.Y = Math.Log( v.Y );
18 |
19 | //if( v.Y < 1 || v.Y > 10 )
20 | // return Infinity.InfinityVector;
21 | //if( v.X < -Math.PI || v.X > Math.PI )
22 | //if( v.X < -3*Math.PI || v.X > 3*Math.PI )
23 | // return Infinity.InfinityVector;
24 |
25 | //v.Y = Math.Log( v.Y );
26 | //return v;
27 | return Dini( v );
28 | };
29 |
30 | Mesh result = new Mesh();
31 | for( int i=0; i
62 | /// From the virtual math museum
63 | ///
64 | public static Vector3D Dini2( Vector3D disk )
65 | {
66 | Vector3D uv = DiskToUpper( disk );
67 | double u = Math.Log( uv.Y );
68 | //double v = DonHatch.acosh( 1 + ( Math.Pow( uv.X, 2 ) + 0 ) / ( 2 * Math.Pow( uv.Y, 2 ) ) ) ;
69 | //if( uv.X < 0 )
70 | // v *= -1;
71 | double v = uv.X;
72 | if( u <= -4 || u > 4 ||
73 | v < -6 * Math.PI || v > 6 * Math.PI )
74 | return Infinity.InfinityVector;
75 |
76 | double psi = 0.5;
77 | psi *= Math.PI;
78 | double sinpsi = Math.Sin( psi );
79 | double cospsi = Math.Cos( psi );
80 | double g = (u - cospsi * v) / sinpsi;
81 | double s = Math.Exp( g );
82 | double r = (2 * sinpsi) / (s + 1 / s);
83 | double t = r * (s - 1 / s) * 0.5;
84 |
85 | return new Vector3D( u - t, r * Math.Cos( v ), r * Math.Sin( v ) );
86 | }
87 |
88 | public static Vector3D Dini( Vector3D uv, double a, double b )
89 | {
90 | uv = DiskToUpper( uv );
91 |
92 | // Eq 1.86 on p36 of book Backlund and Darboux Transformations
93 | double eta = Math.PI / 2 -Math.PI / 20;
94 | //double eta = Math.PI / 2;
95 | double p = 1; // curvature
96 | double x = DonHatch.acosh( uv.Y ); // Used info on mathworld for tractrix to figure this out.
97 | //double x = DonHatch.acosh( Math.Exp( DonHatch.acosh( ( uv.Y * uv.Y + 1 ) / ( 2 * uv.Y ) ) ) );
98 | //double x = Math.Log( uv.Y );
99 | double y = uv.X;
100 |
101 | double pSinEta = p * Math.Sin( eta );
102 | double chi = (x - y * Math.Cos( eta )) / pSinEta;
103 |
104 | if( x <= -4 || x > 4 ||
105 | y < -3 * Math.PI || y > 3 * Math.PI )
106 | return Infinity.InfinityVector;
107 |
108 | Vector3D result = new Vector3D(
109 | pSinEta * Sech( chi ) * Math.Cos( y / p ),
110 | pSinEta * Sech( chi ) * Math.Sin( y / p ),
111 | x - pSinEta * Math.Tanh( chi ) );
112 | return result;
113 |
114 | /*
115 | System.Func tractrix = new System.Func(
116 | ( t ) =>
117 | {
118 | //return new Complex( t - Math.Tanh( t ), 1.0 / Math.Cosh( t ) );
119 | return new Complex( - Math.Sqrt( 1 - 1 / (t*t) ) + DonHatch.acosh( t ), 1.0 / t );
120 | } );
121 |
122 | double logy = Math.Log( uv.Y );
123 | //Complex tract = tractrix( logy );
124 | Complex tract = tractrix( uv.Y );
125 | return new Vector3D(
126 | a * Math.Cos( uv.X ) * tract.Imaginary,
127 | a * Math.Sin( uv.X ) * tract.Imaginary,
128 | a * tract.Real + b * uv.X );
129 | */
130 |
131 | /*
132 | return new Vector3D(
133 | a * Math.Cos( uv.X ) / Math.Cosh( uv.Y ),
134 | a * Math.Sin( uv.X ) / Math.Cosh( uv.Y ),
135 | a * (uv.Y - Math.Tanh( uv.Y )) + b * uv.X ); */
136 |
137 | /*return new Vector3D(
138 | a * Math.Cos( uv.X ) * Math.Sin( uv.Y ),
139 | a * Math.Sin( uv.X ) * Math.Sin( uv.Y ),
140 | a * (Math.Cos( uv.Y ) + Math.Log( Math.Tan( 0.5 * uv.Y ) )) + b * uv.X );*/
141 | }
142 |
143 | private static Vector3D DiskToUpper( Vector3D input )
144 | {
145 | Mobius m = new Mobius();
146 | m.UpperHalfPlane();
147 | return m.Apply( input );
148 | }
149 |
150 | /*
151 | {
152 | double a = 1;
153 | double b = m_settings.RotationRate;
154 | System.Func dinis = new System.Func( uv =>
155 | {
156 | Vector3D result =
157 |
158 | Matrix4D rot = m_mouseMotion.RotHandler4D.Current4dView;
159 | return (TransformFunc( rot ))( result );
160 | } );
161 |
162 | int count = 50;
163 | double uInc = Math.PI * 12 / count;
164 | double vInc = Math.PI / 2 / count;
165 | for( int i = 0; i < count; i++ )
166 | for( int j = 1; j < count - 1; j++ )
167 | {
168 | Polygon poly = Polygon.FromPoints( new Vector3D[]
169 | {
170 | dinis( new Vector3D( i * uInc, j * vInc ) ),
171 | dinis( new Vector3D( (i+1) * uInc, j * vInc ) ),
172 | dinis( new Vector3D( (i+1) * uInc, (j+1) * vInc ) ),
173 | dinis( new Vector3D( i * uInc, (j+1) * vInc ) ),
174 | } );
175 | GLUtils.DrawPolygon( poly, Color.White );
176 | }
177 | }
178 | */
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Torus.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using Math = System.Math;
4 | using System.Diagnostics;
5 | using System.Drawing;
6 |
7 | ///
8 | /// Class to generate tori on a 3-sphere
9 | ///
10 | public class Torus
11 | {
12 | ///
13 | /// The things that define us.
14 | ///
15 | public class Parameters
16 | {
17 | public Parameters()
18 | {
19 | NumSegments1 = NumSegments2 = 50;
20 | TubeRadius1 = 0.5;
21 | Radius = 1.0;
22 | }
23 |
24 | ///
25 | /// The number of segments to generate in the first direction of the torus surface.
26 | ///
27 | public int NumSegments1 { get; set; }
28 |
29 | ///
30 | /// The number of segments to generate in the second direction of the torus surface.
31 | ///
32 | public int NumSegments2 { get; set; }
33 |
34 | ///
35 | /// The first tube radius of our torus.
36 | /// NOTES:
37 | /// - The second tube radius is determined by this and the 3-sphere radius.
38 | /// - This radius must be less than or equal the 3-sphere radius
39 | /// - If equal 0 or equal to the 3-sphere radius, one tube will be empty (torus will be a line).
40 | ///
41 | public double TubeRadius1 { get; set; }
42 |
43 | ///
44 | /// The radius of our 3-sphere
45 | ///
46 | public double Radius { get; set; }
47 | }
48 |
49 | public Parameters Params { get; set; }
50 |
51 | ///
52 | /// Our vertices.
53 | /// NOTE: Not realy a Vector3D here (need to rename my class).
54 | ///
55 | public Vector3D[][] Vertices { get; set; }
56 |
57 | ///
58 | /// Size our Vertices matrix.
59 | ///
60 | private void InitVerts()
61 | {
62 | int n1 = this.Params.NumSegments1;
63 | int n2 = this.Params.NumSegments2;
64 |
65 | Vertices = new Vector3D[n1][];
66 | for( int i = 0; i < n1; i++ )
67 | Vertices[i] = new Vector3D[n2];
68 | }
69 |
70 | ///
71 | /// Special case of CreateTorus for the Clifford Torus.
72 | ///
73 | public static Torus CreateClifford( Parameters parameters )
74 | {
75 | parameters.TubeRadius1 = parameters.Radius / 2;
76 | return CreateTorus( parameters );
77 | }
78 |
79 | ///
80 | /// Calculates a torus which divides the 3-sphere in two.
81 | ///
82 | public static Torus CreateTorus( Parameters parameters )
83 | {
84 | Torus t = new Torus();
85 | t.Params = parameters;
86 | t.InitVerts();
87 |
88 | // Shorter var names for inputs.
89 | int n1 = parameters.NumSegments1;
90 | int n2 = parameters.NumSegments2;
91 | double r = parameters.Radius;
92 | double r1 = parameters.TubeRadius1;
93 |
94 | // Calc r2.
95 | double r2 = r - r1;
96 | if( r2 < 0 )
97 | r2 = 0;
98 |
99 | r1 *= Math.Sqrt( 2 );
100 | r2 *= Math.Sqrt( 2 );
101 |
102 | double angleInc1 = 2 * Math.PI / n1;
103 | double angleInc2 = 2 * Math.PI / n2;
104 |
105 | double angle1 = 0;
106 | for( int i = 0; i < n1; i++ )
107 | {
108 | double angle2 = 0;
109 | for( int j = 0; j < n2; j++ )
110 | {
111 | t.Vertices[i][j].X = r1 * Math.Cos( angle1 );
112 | t.Vertices[i][j].Y = r1 * Math.Sin( angle1 );
113 | t.Vertices[i][j].Z = r2 * Math.Cos( angle2 );
114 | t.Vertices[i][j].W = r2 * Math.Sin( angle2 );
115 | angle2 += angleInc2;
116 | }
117 | angle1 += angleInc1;
118 | }
119 |
120 | return t;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/Transformable.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Math;
4 | using System.Numerics;
5 |
6 | ///
7 | /// For objects that can be transformed.
8 | ///
9 | public interface ITransformable
10 | {
11 | // ZZZ - Make this take in an ITransform?
12 | void Transform( Mobius m );
13 | void Transform( Isometry m );
14 | }
15 |
16 | ///
17 | /// For objects that can transform.
18 | ///
19 | public interface ITransform
20 | {
21 | Vector3D Apply( Vector3D input );
22 | Complex Apply( Complex input );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/R3.Core/Geometry/VectorND.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using Math = System.Math;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using R3.Core;
7 |
8 | public class VectorND
9 | {
10 | public VectorND( int dimension )
11 | {
12 | X = new double[dimension];
13 | }
14 |
15 | public VectorND( double[] components )
16 | {
17 | X = components;
18 | }
19 |
20 | public VectorND( Vector3D v )
21 | {
22 | X = new double[4];
23 | for( int i = 0; i < 4; i++ )
24 | X[i] = v[i];
25 | }
26 |
27 | public Vector3D ToVec3D()
28 | {
29 | return new Vector3D( X[0], X[1], X[2], X[3] );
30 | }
31 |
32 | public VectorND Clone()
33 | {
34 | return new VectorND( (double[])this.X.Clone() );
35 | }
36 |
37 | public int Dimension
38 | {
39 | get { return X.Length; }
40 | set { X = new double[value]; }
41 | }
42 |
43 | public double[] X { get; set; }
44 |
45 | public static VectorND operator /( VectorND v, double s )
46 | {
47 | double[] components = new double[v.Dimension];
48 |
49 | for( int i = 0; i < components.Length; i++ )
50 | components[i] = v.X[i] / s;
51 |
52 | return new VectorND( components );
53 | }
54 |
55 | public void Divide( double s )
56 | {
57 | for( int i = 0; i < Dimension; i++ )
58 | X[i] /= s;
59 | }
60 |
61 | public static VectorND operator *( VectorND v, double s )
62 | {
63 | double[] components = new double[v.Dimension];
64 |
65 | for( int i = 0; i < components.Length; i++ )
66 | components[i] = v.X[i] * s;
67 |
68 | return new VectorND( components );
69 | }
70 |
71 | public static VectorND operator *( double s, VectorND v )
72 | {
73 | return v * s;
74 | }
75 |
76 | public static VectorND operator +( VectorND v1, VectorND v2 )
77 | {
78 | Debug.Assert( v1.Dimension == v2.Dimension );
79 | double[] components = new double[v1.Dimension];
80 |
81 | for( int i = 0; i < components.Length; i++ )
82 | components[i] = v1.X[i] + v2.X[i];
83 |
84 | return new VectorND( components );
85 | }
86 |
87 | public static VectorND operator -( VectorND v )
88 | {
89 | double[] components = new double[v.Dimension];
90 |
91 | for( int i = 0; i < components.Length; i++ )
92 | components[i] = -v.X[i];
93 |
94 | return new VectorND( components );
95 | }
96 |
97 | public static VectorND operator -( VectorND v1, VectorND v2 )
98 | {
99 | return v1 + ( -v2 );
100 | }
101 |
102 | public double Dot( VectorND v )
103 | {
104 | double dot = 0;
105 | for( int i = 0; i < this.Dimension; i++ )
106 | dot += this.X[i] * v.X[i];
107 | return dot;
108 | }
109 |
110 | public bool Normalize()
111 | {
112 | double magnitude = Abs;
113 | if( Tolerance.Zero( magnitude ) )
114 | return false;
115 | Divide( magnitude );
116 | return true;
117 | }
118 |
119 | public double MagSquared
120 | {
121 | get
122 | {
123 | double result = 0;
124 | foreach( double x in this.X )
125 | result += x * x;
126 | return result;
127 | }
128 | }
129 |
130 | public double Abs
131 | {
132 | get
133 | {
134 | return Math.Sqrt( MagSquared );
135 | }
136 | }
137 |
138 | public double Dist( VectorND v )
139 | {
140 | return ( this - v ).Abs;
141 | }
142 |
143 | public bool IsOrigin
144 | {
145 | get
146 | {
147 | foreach( double x in this.X )
148 | if( !Tolerance.Zero( x ) )
149 | return false;
150 | return true;
151 | }
152 | }
153 |
154 | ///
155 | /// 4D -> 3D projection.
156 | ///
157 | public VectorND ProjectTo3D( double cameraDist )
158 | {
159 | double denominator = cameraDist - X[3];
160 | if( Tolerance.Zero( denominator ) )
161 | denominator = 0;
162 |
163 | // Make points with a negative denominator invalid.
164 | if( denominator < 0 )
165 | denominator = 0;
166 |
167 | VectorND result = new VectorND( new double[] {
168 | X[0] * cameraDist / denominator,
169 | X[1] * cameraDist / denominator,
170 | X[2] * cameraDist / denominator, 0 });
171 | return result;
172 | }
173 |
174 | ///
175 | /// 3D -> 2D projection.
176 | ///
177 | public VectorND ProjectTo2D( double cameraDist )
178 | {
179 | double denominator = cameraDist - X[2];
180 | if( Tolerance.Zero( denominator ) )
181 | denominator = 0;
182 |
183 | // Make points with a negative denominator invalid.
184 | if( denominator < 0 )
185 | denominator = 0;
186 |
187 | VectorND result = new VectorND( new double[] {
188 | X[0] * cameraDist / denominator,
189 | X[1] * cameraDist / denominator,
190 | 0, 0 } );
191 | return result;
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/R3.Core/Honeycombs/H3Fundamental.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using Math = System.Math;
7 |
8 | ///
9 | /// This class generates H3 honeycombs via a fundamental region.
10 | ///
11 | public class H3Fundamental
12 | {
13 | ///
14 | /// Class for the fundamental tetrahedron.
15 | ///
16 | public class Tet
17 | {
18 | public Tet( Vector3D center, Vector3D face, Vector3D edge, Vector3D vertex )
19 | {
20 | Verts[0] = center;
21 | Verts[1] = face;
22 | Verts[2] = edge;
23 | Verts[3] = vertex;
24 | CalcFaces();
25 | }
26 |
27 | // The order of these 4 vertices will be
28 | // Center,Face,Edge,Vertex of the parent cell.
29 | public Vector3D[] Verts = new Vector3D[4];
30 |
31 | public Sphere[] Faces
32 | {
33 | get { return m_faces; }
34 | }
35 | private Sphere[] m_faces;
36 |
37 | private void CalcFaces()
38 | {
39 | m_faces = new Sphere[]
40 | {
41 | // Orientation is important! CCW from outside
42 | // XXX - Broken for tets with ideal verts.
43 | H3Models.Ball.OrthogonalSphereInterior( Verts[0], Verts[1], Verts[2] ),
44 | H3Models.Ball.OrthogonalSphereInterior( Verts[0], Verts[3], Verts[1] ),
45 | H3Models.Ball.OrthogonalSphereInterior( Verts[0], Verts[2], Verts[3] ),
46 | H3Models.Ball.OrthogonalSphereInterior( Verts[1], Verts[3], Verts[2] )
47 | };
48 | }
49 |
50 | public Vector3D ID
51 | {
52 | get
53 | {
54 | Vector3D result = new Vector3D();
55 | foreach( Vector3D v in Verts )
56 | result += v;
57 | return result;
58 | }
59 | }
60 |
61 | public Tet Clone()
62 | {
63 | return new Tet( Verts[0], Verts[1], Verts[2], Verts[3] );
64 | }
65 |
66 | public void Reflect( Sphere sphere )
67 | {
68 | for( int i=0; i<4; i++ )
69 | Verts[i] = sphere.ReflectPoint( Verts[i] );
70 | CalcFaces();
71 | }
72 | }
73 |
74 | public class TetEqualityComparer : IEqualityComparer
75 | {
76 | public bool Equals( Tet t1, Tet t2 )
77 | {
78 | return t1.ID.Compare( t2.ID, m_tolerance );
79 | }
80 |
81 | public int GetHashCode( Tet t )
82 | {
83 | return t.ID.GetHashCode();
84 | }
85 |
86 | private double m_tolerance = 0.0001;
87 | }
88 |
89 | public static void Generate( EHoneycomb honeycomb, H3.Settings settings )
90 | {
91 | // XXX - Block the same as in H3. Share code better.
92 | H3.Cell template = null;
93 | {
94 | int p, q, r;
95 | Honeycomb.PQR( honeycomb, out p, out q, out r );
96 |
97 | // Get data we need to generate the honeycomb.
98 | Polytope.Projection projection = Polytope.Projection.FaceCentered;
99 | double phi, chi, psi;
100 | H3.HoneycombData( honeycomb, out phi, out chi, out psi );
101 |
102 | H3.SetupCentering( honeycomb, settings, phi, chi, psi, ref projection );
103 |
104 | Tiling tiling = new Tiling();
105 | TilingConfig config = new TilingConfig( p, q );
106 | tiling.GenerateInternal( config, projection );
107 |
108 | H3.Cell first = new H3.Cell( p, H3.GenFacets( tiling ) );
109 | first.ToSphere(); // Work in ball model.
110 | first.ScaleToCircumSphere( 1.0 );
111 | first.ApplyMobius( settings.Mobius );
112 |
113 | template = first;
114 | }
115 |
116 | // Center
117 | Vector3D center = template.Center;
118 |
119 | // Face
120 | H3.Cell.Facet facet = template.Facets[0];
121 | Sphere s = H3Models.Ball.OrthogonalSphereInterior( facet.Verts[0], facet.Verts[1], facet.Verts[2] );
122 | Vector3D face = s.Center;
123 | face.Normalize();
124 | face *= DistOriginToOrthogonalSphere( s.Radius );
125 |
126 | // Edge
127 | Circle3D c;
128 | H3Models.Ball.OrthogonalCircleInterior( facet.Verts[0], facet.Verts[1], out c );
129 | Vector3D edge = c.Center;
130 | edge.Normalize();
131 | edge *= DistOriginToOrthogonalSphere( c.Radius );
132 |
133 | // Vertex
134 | Vector3D vertex = facet.Verts[0];
135 |
136 | Tet fundamental = new Tet( center, face, edge, vertex );
137 |
138 | // Recurse.
139 | int level = 1;
140 | Dictionary completedTets = new Dictionary( new TetEqualityComparer() );
141 | completedTets.Add( fundamental, level );
142 | List tets = new List();
143 | tets.Add( fundamental );
144 | ReflectRecursive( level, tets, completedTets, settings );
145 |
146 | Shapeways mesh = new Shapeways();
147 | foreach( KeyValuePair kvp in completedTets )
148 | {
149 | if( Utils.Odd( kvp.Value ) )
150 | continue;
151 |
152 | Tet tet = kvp.Key;
153 |
154 | // XXX - really want sphere surfaces here.
155 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[0], tet.Verts[1], tet.Verts[2] ) );
156 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[0], tet.Verts[3], tet.Verts[1] ) );
157 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[0], tet.Verts[2], tet.Verts[3] ) );
158 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[1], tet.Verts[3], tet.Verts[2] ) );
159 | }
160 |
161 | mesh.Mesh.Scale( settings.Scale );
162 | STL.SaveMeshToSTL( mesh.Mesh, H3.m_baseDir + "fundamental" + ".stl" );
163 | }
164 |
165 | private static double DistOriginToOrthogonalSphere( double r )
166 | {
167 | // http://mathworld.wolfram.com/OrthogonalCircles.html
168 | double d = Math.Sqrt( 1 + r * r );
169 | return d - r;
170 | }
171 |
172 | private static void ReflectRecursive( int level, List tets, Dictionary completedTets, H3.Settings settings )
173 | {
174 | // Breadth first recursion.
175 |
176 | if( 0 == tets.Count )
177 | return;
178 |
179 | level++;
180 |
181 | List reflected = new List();
182 |
183 | foreach( Tet tet in tets )
184 | {
185 | foreach( Sphere facetSphere in tet.Faces )
186 | {
187 | if( facetSphere == null )
188 | throw new System.Exception( "Unexpected." );
189 |
190 | if( completedTets.Count > settings.MaxCells )
191 | return;
192 |
193 | Tet newTet = tet.Clone();
194 | newTet.Reflect( facetSphere );
195 | if( completedTets.Keys.Contains( newTet ) ||
196 | !TetOk( newTet ) )
197 | continue;
198 |
199 | reflected.Add( newTet );
200 | completedTets.Add( newTet, level );
201 | }
202 | }
203 |
204 | ReflectRecursive( level, reflected, completedTets, settings );
205 | }
206 |
207 | private static bool TetOk( Tet tet )
208 | {
209 | double cutoff = 0.95;
210 | foreach( Vector3D v in tet.Verts )
211 | if( Tolerance.GreaterThan( v.Z, 0 ) ||
212 | v.Abs() > cutoff )
213 | return false;
214 |
215 | return true;
216 | }
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/R3.Core/Honeycombs/H3Supp.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using R3.Core;
6 |
7 | ///
8 | /// This class is used to generate the really exotic H3 honeycombs,
9 | /// the {4,4,4}, {3,6,3}, and {6,3,6}.
10 | ///
11 | public class H3Supp
12 | {
13 | ///
14 | /// Our approach will be:
15 | /// (1) Generate a portion of one cell.
16 | /// (2) Reflect all facets in the central facet, to get things filled-in inside the central facet. (Trim small edges here?)
17 | /// (3) Copy this region around the plane, and go back to step (2) if density is not high enough.
18 | /// (4) Map to Ball, trimming edges that become too small.
19 | /// NOTE: All verts are on the boundary, so we can reflect around
20 | // in circles on the plane at infinity, rather than spheres.
21 | ///
22 | public static void GenerateExotic( EHoneycomb honeycomb, H3.Settings settings )
23 | {
24 | settings.AngularThickness = 0.17;
25 |
26 | Tiling tiling;
27 | Tile baseTile;
28 | GetAssociatedTiling( honeycomb, out tiling, out baseTile );
29 |
30 | List edges = new List();
31 | foreach( Segment seg in baseTile.Boundary.Segments )
32 | edges.Add( new H3.Cell.Edge( seg.P1, seg.P2 ) );
33 |
34 | settings.Position = Polytope.Projection.FaceCentered;
35 | double scale = 1;
36 | Vector3D offset = new Vector3D();
37 | if( settings.Position == Polytope.Projection.FaceCentered )
38 | {
39 | scale = FaceCenteredScale( baseTile.VertexCircle );
40 | offset = new Vector3D();
41 | }
42 | else if( settings.Position == Polytope.Projection.EdgeCentered )
43 | {
44 | scale = EdgeCenteredScale( baseTile );
45 | offset = baseTile.Boundary.Segments[0].Midpoint;
46 | }
47 |
48 | int iterations = m_params.Iterations;
49 | for( int i=0; i edgeDict = edges.ToDictionary( e => e, e => 1 );
56 | H3.RemoveDanglingEdgesRecursive( edgeDict );
57 | edges = edgeDict.Keys.ToList();
58 | }
59 |
60 | string outputFileName = H3.m_baseDir + Honeycomb.String( honeycomb, false );
61 | System.IO.File.Delete( outputFileName );
62 |
63 | if( m_params.Output == H3.Output.STL )
64 | {
65 | outputFileName += ".stl";
66 |
67 | // Now mesh the edges.
68 | Shapeways mesh = new Shapeways();
69 | foreach( H3.Cell.Edge edge in edges )
70 | {
71 | // Append to the file vs. writing out all at once because I was running out of memory otherwise.
72 | mesh = new Shapeways();
73 | int div;
74 | H3Models.Ball.LODThin( edge.Start, edge.End, out div );
75 | mesh.Div = div;
76 | H3.Util.AddToMeshInternal( mesh, edge.Start, edge.End );
77 | mesh.Mesh.Scale( settings.Scale );
78 | STL.AppendMeshToSTL( mesh.Mesh, outputFileName );
79 | }
80 | }
81 | else
82 | {
83 | outputFileName += ".pov";
84 | PovRay.WriteH3Edges( new PovRay.Parameters()
85 | {
86 | AngularThickness = settings.AngularThickness,
87 | Halfspace = settings.Halfspace,
88 | ThinEdges = settings.ThinEdges,
89 | },
90 | edges.ToArray(), outputFileName );
91 | }
92 | }
93 |
94 | private class Params
95 | {
96 | public Params() { Setup(); }
97 | public int Iterations;
98 | public int MaxTiles;
99 | public double UhsCutoff;
100 | public double BallCutoff;
101 | public bool RemoveDangling;
102 | public H3.Output Output = H3.Output.POVRay;
103 |
104 | private void Setup()
105 | {
106 | if( Output == H3.Output.STL )
107 | {
108 | Iterations = 2;
109 | MaxTiles = 500;
110 | UhsCutoff = 0.002;
111 | BallCutoff = 0.0158;
112 | RemoveDangling = true;
113 | //UhsCutoff = 0.02;
114 | //BallCutoff = 0.015;
115 | BallCutoff = 0.03; // 363
116 | }
117 | else
118 | {
119 | Iterations = 10;
120 | MaxTiles = 750;
121 | UhsCutoff = 0.001;
122 | BallCutoff = 0.01;
123 | RemoveDangling = false;
124 | }
125 | }
126 | }
127 | private static Params m_params = new Params();
128 |
129 | public static bool IsExotic( EHoneycomb honeycomb )
130 | {
131 | return
132 | honeycomb == EHoneycomb.H444 ||
133 | honeycomb == EHoneycomb.H636 ||
134 | honeycomb == EHoneycomb.H363;
135 | }
136 |
137 | private static void GetPQ( EHoneycomb honeycomb, out int p, out int q )
138 | {
139 | int r;
140 | Honeycomb.PQR( honeycomb, out p, out q, out r );
141 | }
142 |
143 | private static void GetAssociatedTiling( EHoneycomb honeycomb, out Tiling tiling, out Tile baseTile )
144 | {
145 | int p, q;
146 | GetPQ( honeycomb, out p, out q );
147 | TilingConfig tilingConfig = new TilingConfig( p, q, maxTiles: m_params.MaxTiles );
148 | tiling = new Tiling();
149 | tiling.Generate( tilingConfig );
150 |
151 | baseTile = Tiling.CreateBaseTile( tilingConfig );
152 | }
153 |
154 | ///
155 | /// Helper to do one step of reflections.
156 | /// Returns a new list of region edges.
157 | ///
158 | private static List DoOneStep( List regionEdges, Tiling tiling, Circle region )
159 | {
160 | HashSet newEdges = new HashSet( new H3.Cell.EdgeEqualityComparer() );
161 | foreach( Tile tile in tiling.Tiles )
162 | {
163 | foreach( H3.Cell.Edge edge in regionEdges )
164 | {
165 | H3.Cell.Edge toAdd = null;
166 | if( !Tolerance.Zero( tile.Center.Abs() ) )
167 | {
168 | // Translate
169 | // The isometry is necessary for the 363, but seems to mess up 636
170 | Vector3D start = tile.Isometry.Apply( edge.Start );
171 | Vector3D end = tile.Isometry.Apply( edge.End );
172 | //Vector3D start = edge.Start + tile.Center;
173 | //Vector3D end = edge.End + tile.Center;
174 |
175 | // Reflect
176 | start = region.ReflectPoint( start );
177 | end = region.ReflectPoint( end );
178 |
179 | toAdd = new H3.Cell.Edge( start, end );
180 | }
181 | else
182 | toAdd = edge;
183 |
184 | if( EdgeOkUHS( toAdd, region ) )
185 | newEdges.Add( toAdd );
186 | }
187 | }
188 |
189 | return newEdges.ToList();
190 | }
191 |
192 | private static bool EdgeOkUHS( H3.Cell.Edge edge, Circle region )
193 | {
194 | if( Tolerance.GreaterThan( edge.Start.Abs(), region.Radius ) ||
195 | Tolerance.GreaterThan( edge.End.Abs(), region.Radius ) )
196 | return false;
197 |
198 | return EdgeOk( edge, m_params.UhsCutoff );
199 | }
200 |
201 | private static bool EdgeOkBall( H3.Cell.Edge edge )
202 | {
203 | return EdgeOk( edge, m_params.BallCutoff );
204 | }
205 |
206 | private static bool EdgeOk( H3.Cell.Edge edge, double cutoff )
207 | {
208 | return edge.Start.Dist( edge.End ) > cutoff;
209 | }
210 |
211 | private static List CopyAndProject( List regionEdges, Tiling tiling, double scale, Vector3D offset )
212 | {
213 | HashSet newEdges = new HashSet( new H3.Cell.EdgeEqualityComparer() );
214 | //foreach( Tile tile in tiling.Tiles ) // Needed for doing full ball (rather than just half of it)
215 | Tile tile = tiling.Tiles.First();
216 | {
217 | foreach( H3.Cell.Edge edge in regionEdges )
218 | {
219 | // Translation
220 | // The isometry is necessary for the 363, but seems to mess up 636
221 | Vector3D start = tile.Isometry.Apply( edge.Start ) + offset;
222 | Vector3D end = tile.Isometry.Apply( edge.End ) + offset;
223 | //Vector3D start = edge.Start + tile.Center + offset;
224 | //Vector3D end = edge.End + tile.Center + offset;
225 |
226 | // Scaling
227 | start *= scale;
228 | end *= scale;
229 |
230 | // Projections
231 | start = H3Models.UHSToBall( start );
232 | end = H3Models.UHSToBall( end );
233 |
234 | H3.Cell.Edge transformed = new H3.Cell.Edge( start, end );
235 | if( EdgeOkBall( transformed ) )
236 | newEdges.Add( transformed );
237 | }
238 | }
239 |
240 | return newEdges.ToList();
241 | }
242 |
243 | private static double FaceCenteredScale( Circle vertexCircle )
244 | {
245 | // The radius is the height of the face in UHS.
246 | // We need to scale this to be at (0,0,1)
247 | return 1.0 / vertexCircle.Radius;
248 | }
249 |
250 | private static double EdgeCenteredScale( Tile baseTile )
251 | {
252 | Segment seg = baseTile.Boundary.Segments[0];
253 | return 1.0 / ( seg.Length / 2 );
254 | }
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/R3.Core/Honeycombs/Honeycomb.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using R3.Math;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using Math = System.Math;
9 |
10 | public enum EHoneycomb
11 | {
12 | // Euclidean
13 | H434,
14 |
15 | // Hyperbolic
16 | H435,
17 | H534,
18 | H535,
19 | H353,
20 |
21 | // From here down, the exotic ones.
22 | H336,
23 | H436,
24 | H536,
25 | H344,
26 |
27 | // From here down, the really exotic ones.
28 | H444,
29 | H363,
30 | H636,
31 |
32 | // Lorentzian
33 | H337,
34 | H33I,
35 | // ...
36 | }
37 |
38 | public class Honeycomb
39 | {
40 | public static string String( EHoneycomb honeycomb, bool dual )
41 | {
42 | switch( honeycomb )
43 | {
44 | case EHoneycomb.H435:
45 | return dual ? "{5,3,4}" : "{4,3,5}";
46 | case EHoneycomb.H534:
47 | return dual ? "{4,3,5}" : "{5,3,4}";
48 | case EHoneycomb.H535:
49 | return "{5,3,5}";
50 | case EHoneycomb.H353:
51 | return "{3,5,3}";
52 | case EHoneycomb.H336:
53 | return dual ? "{6,3,3}" : "{3,3,6}";
54 | case EHoneycomb.H436:
55 | return dual ? "{6,3,4}" : "{4,3,6}";
56 | case EHoneycomb.H536:
57 | return dual ? "{6,3,5}" : "{5,3,6}";
58 | case EHoneycomb.H344:
59 | return dual ? "{4,4,3}" : "{3,4,4}";
60 | case EHoneycomb.H444:
61 | return "{4,4,4}";
62 | case EHoneycomb.H636:
63 | return "{6,3,6}";
64 | case EHoneycomb.H363:
65 | return "{3,6,3}";
66 | case EHoneycomb.H337:
67 | return dual ? "{7,3,3}" : "{3,3,7}";
68 | case EHoneycomb.H33I:
69 | return dual ? "{inf,3,3}" : "{3,3,inf}";
70 | }
71 |
72 | throw new System.ArgumentException( "Unknown honeycomb type" );
73 | }
74 |
75 | public static void PQR( EHoneycomb honeycomb, out int p, out int q, out int r )
76 | {
77 | switch( honeycomb )
78 | {
79 | case EHoneycomb.H435:
80 | p = 4; q = 3; r = 5; return;
81 | case EHoneycomb.H534:
82 | p = 5; q = 3; r = 4; return;
83 | case EHoneycomb.H535:
84 | p = 5; q = 3; r = 5; return;
85 | case EHoneycomb.H353:
86 | p = 3; q = 5; r = 3; return;
87 | case EHoneycomb.H336:
88 | p = 3; q = 3; r = 6; return;
89 | case EHoneycomb.H436:
90 | p = 4; q = 3; r = 6; return;
91 | case EHoneycomb.H536:
92 | p = 5; q = 3; r = 6; return;
93 | case EHoneycomb.H344:
94 | p = 3; q = 4; r = 4; return;
95 | case EHoneycomb.H444:
96 | p = 4; q = 4; r = 4; return;
97 | case EHoneycomb.H363:
98 | p = 3; q = 6; r = 3; return;
99 | case EHoneycomb.H636:
100 | p = 6; q = 3; r = 6; return;
101 | case EHoneycomb.H337:
102 | p = 3; q = 3; r = 7; return;
103 | case EHoneycomb.H33I:
104 | p = 3; q = 3; r = -1; return;
105 | }
106 |
107 | throw new System.ArgumentException();
108 | }
109 |
110 | public static double InRadius( EHoneycomb honeycomb )
111 | {
112 | int p, q, r;
113 | PQR( honeycomb, out p, out q, out r );
114 | return InRadius( p, q, r );
115 | }
116 |
117 | public static double MidRadius( EHoneycomb honeycomb )
118 | {
119 | int p, q, r;
120 | PQR( honeycomb, out p, out q, out r );
121 | return MidRadius( p, q, r );
122 | }
123 |
124 | public static double CircumRadius( EHoneycomb honeycomb )
125 | {
126 | int p, q, r;
127 | PQR( honeycomb, out p, out q, out r );
128 | return CircumRadius( p, q, r );
129 | }
130 |
131 | public static double EdgeLength( EHoneycomb honeycomb )
132 | {
133 | int p, q, r;
134 | PQR( honeycomb, out p, out q, out r );
135 | return EdgeLength( p, q, r );
136 | }
137 |
138 | public static Geometry GetGeometry( int p, int q, int r )
139 | {
140 | double t1 = Math.Sin( PiOverNSafe( p ) ) * Math.Sin( PiOverNSafe( r ) );
141 | double t2 = Math.Cos( PiOverNSafe( q ) );
142 |
143 | if( Tolerance.Equal( t1, t2 ) )
144 | return Geometry.Euclidean;
145 |
146 | if( Tolerance.GreaterThan( t1, t2 ) )
147 | return Geometry.Spherical;
148 |
149 | return Geometry.Hyperbolic;
150 | }
151 |
152 | ///
153 | /// Returns the in-radius, in the induced geometry.
154 | ///
155 | public static double InRadius( int p, int q, int r )
156 | {
157 | double pip = PiOverNSafe( p );
158 | double pir = PiOverNSafe( r );
159 |
160 | double pi_hpq = Pi_hpq( p, q );
161 | double inRadius = Math.Sin( pip ) * Math.Cos( pir ) / Math.Sin( pi_hpq );
162 |
163 | switch( GetGeometry( p, q, r ) )
164 | {
165 | case Geometry.Hyperbolic:
166 | return DonHatch.acosh( inRadius );
167 | case Geometry.Spherical:
168 | return Math.Acos( inRadius );
169 | }
170 |
171 | throw new System.NotImplementedException();
172 | }
173 |
174 | ///
175 | /// Returns the mid-radius, in the induced geometry.
176 | ///
177 | public static double MidRadius( int p, int q, int r )
178 | {
179 | double pir = PiOverNSafe( r );
180 |
181 | double inRadius = InRadius( p, q, r );
182 | double midRadius = DonHatch.sinh( inRadius ) / Math.Sin( pir );
183 |
184 | switch( GetGeometry( p, q, r ) )
185 | {
186 | case Geometry.Hyperbolic:
187 | return DonHatch.asinh( midRadius );
188 | case Geometry.Spherical:
189 | return Math.Asin( midRadius );
190 | }
191 |
192 | throw new System.NotImplementedException();
193 | }
194 |
195 | ///
196 | /// Returns the circum-radius, in the induced geometry.
197 | ///
198 | public static double CircumRadius( int p, int q, int r )
199 | {
200 | double pip = PiOverNSafe( p );
201 | double piq = PiOverNSafe( q );
202 | double pir = PiOverNSafe( r );
203 |
204 | double pi_hpq = Pi_hpq( p, q );
205 | double pi_hqr = Pi_hpq( q, r );
206 | double circumRadius = Math.Cos( pip ) * Math.Cos( piq ) * Math.Cos( pir ) / ( Math.Sin( pi_hpq ) * Math.Sin( pi_hqr ) );
207 |
208 | switch( GetGeometry( p, q, r ) )
209 | {
210 | case Geometry.Hyperbolic:
211 | return DonHatch.acosh( circumRadius );
212 | case Geometry.Spherical:
213 | return Math.Acos( circumRadius );
214 | }
215 |
216 | throw new System.NotImplementedException();
217 | }
218 |
219 | public static double EdgeLength( int p, int q, int r )
220 | {
221 | double pip = PiOverNSafe( p );
222 | double pir = PiOverNSafe( r );
223 |
224 | double pi_hqr = Pi_hpq( q, r );
225 | double edgeLength = 2 * DonHatch.acosh( Math.Cos( pip ) * Math.Sin( pir ) / Math.Sin( pi_hqr ) );
226 | return edgeLength;
227 | }
228 |
229 | private static double Pi_hpq( int p, int q )
230 | {
231 | double pi = Math.PI;
232 | double pip = PiOverNSafe( p );
233 | double piq = PiOverNSafe( q );
234 |
235 | double temp = Math.Pow( Math.Cos( pip ), 2 ) + Math.Pow( Math.Cos( piq ), 2 );
236 | double hab = pi / Math.Acos( Math.Sqrt( temp ) );
237 |
238 | // Infinity safe.
239 | double pi_hpq = pi / hab;
240 | if( Infinity.IsInfinite( hab ) )
241 | pi_hpq = 0;
242 |
243 | return pi_hpq;
244 | }
245 |
246 | public static double PiOverNSafe( int n )
247 | {
248 | return n == -1 ? 0 : Math.PI / n;
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/R3.Core/Honeycombs/Lamp.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using R3.Math;
5 | using System.Collections.Generic;
6 | using System.Diagnostics;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Numerics;
10 | using Math = System.Math;
11 |
12 | internal class Lamp
13 | {
14 | private static Inventory m_inventory = new Inventory();
15 |
16 | ///
17 | /// Function I was using for lamp project, probably a bit out of date.
18 | ///
19 | public static void AddToMeshLamp( Shapeways mesh, Vector3D v1, Vector3D v2 )
20 | {
21 | // need to get these from CalcBallArc
22 | Vector3D center = Vector3D.DneVector();
23 | double radius = double.NaN;
24 | Vector3D normal = Vector3D.DneVector();
25 | double angleTot = double.NaN;
26 |
27 | double length1 = Scale( 2.1 ); // 12-end piece
28 | //double length1 = Scale( 1.6 ); // 6-end piece
29 | //double length1 = Scale( 1.4 ); // 4-end piece
30 | double length2 = Scale( 0.5 );
31 |
32 | double outerRadStart = Scale( 0.0625 / 2 );
33 | double outerRadEnd = Scale( 0.25 / 2 );
34 |
35 | System.Func outerSizeFunc = v =>
36 | {
37 | double angle = (v1 - center).AngleTo( v - center );
38 | double len = radius * angle;
39 | return outerRadStart + (outerRadEnd - outerRadStart) * (len / length1);
40 | };
41 |
42 | System.Func outerSizeFunc2 = v =>
43 | {
44 | double angle = (v2 - center).AngleTo( v - center );
45 | double len = radius * angle;
46 | return outerRadStart + (outerRadEnd - outerRadStart) * (len / length1);
47 | };
48 |
49 | System.Func innerSizeFunc = v =>
50 | {
51 | // Very slightly bigger than 1/8 inch OD.
52 | return Scale( 0.13 / 2 );
53 | };
54 |
55 | Vector3D[] outerPoints = Shapeways.CalcArcPoints( center, radius, v1, normal, length1 / radius );
56 | Vector3D[] innerPoints = Shapeways.CalcArcPoints( center, radius, outerPoints[outerPoints.Length - 1], normal * -1, length2 / radius );
57 | mesh.AddCornucopia( outerPoints, outerSizeFunc, innerPoints, innerSizeFunc );
58 |
59 | outerPoints = Shapeways.CalcArcPoints( center, radius, v2, normal * -1, length1 / radius );
60 | innerPoints = Shapeways.CalcArcPoints( center, radius, outerPoints[outerPoints.Length - 1], normal, length2 / radius );
61 | mesh.AddCornucopia( outerPoints, outerSizeFunc2, innerPoints, innerSizeFunc );
62 |
63 | m_inventory.AddRod( Rod.Create( radius, angleTot ) );
64 | }
65 |
66 | private static double Scale( double rad )
67 | {
68 | // This is ball model radius of final object
69 | double scale = 9;
70 | return rad / scale;
71 | }
72 | }
73 |
74 | internal class Rod
75 | {
76 | public static Rod Create( double radius, double angle )
77 | {
78 | Rod r = new Rod();
79 | r.Radius = radius;
80 | r.Length = radius * angle;
81 | return r;
82 | }
83 |
84 | public double Radius { get; set; }
85 | public double Length { get; set; }
86 |
87 | public static bool operator ==( Rod r1, Rod r2 )
88 | {
89 | return r1.Compare( r2, Tolerance.Threshold );
90 | }
91 |
92 | public static bool operator !=( Rod r1, Rod r2 )
93 | {
94 | return !(r1 == r2);
95 | }
96 |
97 | public override bool Equals( object obj )
98 | {
99 | Rod r = (Rod)obj;
100 | return (r == this);
101 | }
102 |
103 | public override int GetHashCode()
104 | {
105 | double inverse = 1 / Tolerance.Threshold;
106 | int decimals = (int)Math.Log10( inverse );
107 |
108 | return
109 | Math.Round( Radius, decimals ).GetHashCode() ^
110 | Math.Round( Length, decimals ).GetHashCode();
111 | }
112 |
113 | public bool Compare( Rod other, double threshold )
114 | {
115 | return (Tolerance.Equal( Radius, other.Radius, threshold ) &&
116 | Tolerance.Equal( Length, other.Length, threshold ));
117 | }
118 | }
119 |
120 | internal class Inventory
121 | {
122 | public Dictionary Rods = new Dictionary();
123 |
124 | public void AddRod( Rod rod )
125 | {
126 | int num;
127 | if( Rods.TryGetValue( rod, out num ) )
128 | num++;
129 | else
130 | num = 1;
131 | Rods[rod] = num;
132 | }
133 |
134 | public double TotalLength
135 | {
136 | get
137 | {
138 | double result = 0;
139 | foreach( KeyValuePair kvp in Rods )
140 | result += kvp.Key.Length * kvp.Value;
141 | return result;
142 | }
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/R3.Core/Honeycombs/R3.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using R3.Math;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using Math = System.Math;
9 |
10 | // NOTE: Wanted to name this class R3 (parallel to H3/S3), but namespace problems happened.
11 | public class Euclidean
12 | {
13 | public static Vector3D[] GeodesicPoints( Vector3D v1, Vector3D v2 )
14 | {
15 | int div = 4;
16 | Segment seg = Segment.Line( v1, v2 );
17 | Vector3D[] result = seg.Subdivide( div );
18 | return result;
19 | }
20 |
21 | public static void GenEuclidean()
22 | {
23 | Shapeways mesh = new Shapeways();
24 | HashSet completed = new HashSet( new H3.Cell.EdgeEqualityComparer() );
25 |
26 | int count = 2;
27 | for( int i = -count; i < count; i++ )
28 | for( int j = -count; j < count; j++ )
29 | for( int k = -count; k < count; k++ )
30 | {
31 | // Offset
32 | double io = i + 0.5;
33 | double jo = j + 0.5;
34 | double ko = k + 0.5;
35 | Vector3D p = new Vector3D( io, jo, ko );
36 |
37 | // Add a sphere for this point.
38 | Sphere s = new Sphere() { Center = p, Radius = 0.05 };
39 | mesh.AddSphere( s.Center, s.Radius );
40 |
41 | // Do every edge emanating from this point.
42 | AddEuclideanEdge( mesh, completed, p, new Vector3D( io + 1, jo, ko ) );
43 | AddEuclideanEdge( mesh, completed, p, new Vector3D( io - 1, jo, ko ) );
44 | AddEuclideanEdge( mesh, completed, p, new Vector3D( io, jo + 1, ko ) );
45 | AddEuclideanEdge( mesh, completed, p, new Vector3D( io, jo - 1, ko ) );
46 | AddEuclideanEdge( mesh, completed, p, new Vector3D( io, jo, ko + 1 ) );
47 | AddEuclideanEdge( mesh, completed, p, new Vector3D( io, jo, ko - 1 ) );
48 | }
49 |
50 | STL.SaveMeshToSTL( mesh.Mesh, "434.stl" );
51 | //PovRay.WriteEdges( new PovRay.Parameters() { AngularThickness = .05 }, Geometry.Euclidean, completed.ToArray(), "434.pov", append: false );
52 | }
53 |
54 | private static void AddEuclideanEdge( Shapeways mesh, HashSet completed, Vector3D start, Vector3D end )
55 | {
56 | double cutoff = 1.75;
57 | if( Math.Abs( start.X ) > cutoff || Math.Abs( start.Y ) > cutoff || Math.Abs( start.Z ) > cutoff ||
58 | Math.Abs( end.X ) > cutoff || Math.Abs( end.Y ) > cutoff || Math.Abs( end.Z ) > cutoff )
59 | return;
60 |
61 | if( mesh != null )
62 | {
63 | AddEuclideanEdgeToMesh( mesh, completed, start, end );
64 | return;
65 | }
66 |
67 | completed.Add( new H3.Cell.Edge( start, end ) );
68 | }
69 |
70 | private static void AddEuclideanEdgeToMesh( Shapeways mesh, HashSet completed, Vector3D start, Vector3D end )
71 | {
72 | H3.Cell.Edge edge = new H3.Cell.Edge( start, end );
73 | if( completed.Contains( edge ) )
74 | return;
75 |
76 | Shapeways tempMesh = new Shapeways();
77 | Segment seg = Segment.Line( start, end );
78 |
79 | int div = 20 - (int)(start.Abs() * 4);
80 | if( div < 1 )
81 | div = 1;
82 |
83 | tempMesh.AddCurve( seg.Subdivide( div ), .05 );
84 | //Transform( tempMesh.Mesh );
85 |
86 | mesh.Mesh.Triangles.AddRange( tempMesh.Mesh.Triangles );
87 | completed.Add( edge );
88 | }
89 |
90 | private static void Transform( Mesh mesh )
91 | {
92 | Sphere sphere = new Sphere();
93 | sphere.Radius = 0.1;
94 | for( int i = 0; i < mesh.Triangles.Count; i++ )
95 | {
96 | mesh.Triangles[i] = new Mesh.Triangle(
97 | sphere.ReflectPoint( mesh.Triangles[i].a ),
98 | sphere.ReflectPoint( mesh.Triangles[i].b ),
99 | sphere.ReflectPoint( mesh.Triangles[i].c ) );
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/R3.Core/Math/DonHatch.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Math
2 | {
3 | using Math = System.Math;
4 |
5 | // This code from Don Hatch.
6 | public static class DonHatch
7 | {
8 | public static double
9 | expm1( double x )
10 | {
11 | double u = Math.Exp(x);
12 | if (u == 1.0)
13 | return x;
14 | if (u-1.0 == -1.0)
15 | return -1;
16 | return (u-1.0)*x/Math.Log(u);
17 | }
18 |
19 | public static double
20 | log1p( double x )
21 | {
22 | double u = 1.0+x;
23 | return Math.Log(u) - ((u-1.0)-x)/u;
24 | }
25 |
26 | public static double
27 | tanh( double x )
28 | {
29 | double u = expm1(x);
30 | return u / (u*(u+2.0)+2.0) * (u+2.0);
31 | }
32 |
33 | public static double
34 | atanh( double x )
35 | {
36 | return .5 * log1p(2.0*x/(1.0-x));
37 | }
38 |
39 | public static double
40 | sinh( double x )
41 | {
42 | double u = expm1( x );
43 | return .5 * u / ( u + 1 ) * ( u + 2 );
44 | }
45 |
46 | public static double
47 | asinh( double x )
48 | {
49 | return log1p(x * (1.0 + x / (Math.Sqrt(x*x+1.0)+1.0)));
50 | }
51 |
52 | public static double
53 | cosh( double x )
54 | {
55 | double e_x = Math.Exp(x);
56 | return (e_x + 1.0/e_x) * .5;
57 | }
58 |
59 | public static double
60 | acosh( double x )
61 | {
62 | return 2 * Math.Log( Math.Sqrt( ( x + 1 ) * .5 ) + Math.Sqrt( ( x - 1 ) * .5 ) );
63 | }
64 |
65 | // hyperbolic to euclidean norm (distance from 0,0) in Poincare disk.
66 | public static double
67 | h2eNorm( double hNorm )
68 | {
69 | if( double.IsNaN( hNorm ) )
70 | return 1.0;
71 | return tanh(.5*hNorm);
72 | }
73 |
74 | public static double
75 | e2hNorm( double eNorm )
76 | {
77 | return 2*atanh(eNorm);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/R3.Core/Math/Graph.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Math
2 | {
3 | public struct GraphEdge
4 | {
5 | public GraphEdge( int v1, int v2 )
6 | : this()
7 | {
8 | // Keep it ordered
9 | if( v1 < v2 )
10 | {
11 | V1 = v1;
12 | V2 = v2;
13 | }
14 | else
15 | {
16 | V1 = v2;
17 | V2 = v1;
18 | }
19 | }
20 |
21 | public int V1 { get; set; }
22 | public int V2 { get; set; }
23 |
24 | ///
25 | /// Given a vertex index, find the vertex at the other end of the edge.
26 | ///
27 | public int Opposite( int idx )
28 | {
29 | return idx == V1 ? V2 : V1;
30 | }
31 |
32 | ///
33 | /// vZome VEF format.
34 | ///
35 | public void ReadEdge( string line )
36 | {
37 | //string[] split = line.Split( '\t' );
38 | string[] split = line.Split( new char[] { '\t', ' ' }, System.StringSplitOptions.RemoveEmptyEntries );
39 | V1 = int.Parse( split[0] );
40 | V2 = int.Parse( split[1] );
41 | }
42 |
43 | ///
44 | /// vZome VEF format.
45 | ///
46 | public string WriteEdge()
47 | {
48 | return V1.ToString() + "\t" + V2.ToString();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/R3.Core/Math/Infinity.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using System.Numerics;
4 |
5 | ///
6 | /// Class with some hackish methods for dealing with points projected to infinite.
7 | ///
8 | public static class Infinity
9 | {
10 | public static Vector3D InfinityVector = new Vector3D( double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity );
11 | public static Complex InfinityComplex = new Complex( double.PositiveInfinity, double.PositiveInfinity );
12 | public static Vector3D LargeFiniteVector = new Vector3D( FiniteScale, FiniteScale, FiniteScale );
13 |
14 | public const double FiniteScale = 10000;
15 | public const double InfiniteScale = 500000;
16 |
17 | public static bool IsInfinite( Vector3D input )
18 | {
19 | // XXX - ugly hack I'd like to improve.
20 | return
21 | IsInfinite( input.X ) ||
22 | IsInfinite( input.Y ) ||
23 | IsInfinite( input.Z ) ||
24 | IsInfinite( input.W ) ||
25 | input.Abs() > InfiniteScale;
26 | }
27 |
28 | public static bool IsInfinite( Complex input )
29 | {
30 | return
31 | IsInfinite( input.Real ) ||
32 | IsInfinite( input.Imaginary );
33 | }
34 |
35 | public static bool IsInfinite( double input )
36 | {
37 | return
38 | double.IsNaN( input ) ||
39 | double.IsInfinity( input ) ||
40 | input >= InfiniteScale;
41 | }
42 |
43 | public static Vector3D InfinitySafe( Vector3D input )
44 | {
45 | if( Infinity.IsInfinite( input ) )
46 | return Infinity.LargeFiniteVector;
47 | return input;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/R3.Core/Math/Matrix4D.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Math
2 | {
3 | using R3.Geometry;
4 | using System;
5 | using Math = System.Math;
6 |
7 | public class Matrix4D
8 | {
9 | public Matrix4D()
10 | {
11 | Initialize();
12 | }
13 |
14 | public Matrix4D( double[,] data )
15 | {
16 | Initialize();
17 | for( int i=0; i<4; i++ )
18 | for( int j=0; j<4; j++ )
19 | Data[i][j] = data[i,j];
20 | }
21 |
22 | public Matrix4D( Vector3D[] rows )
23 | {
24 | Initialize();
25 | for( int i=0; i<4; i++ )
26 | Data[i] = new double[] { rows[i].X, rows[i].Y, rows[i].Z, rows[i].W };
27 | }
28 |
29 | private void Initialize()
30 | {
31 | Data = new double[4][];
32 | for( int i=0; i<4; i++ )
33 | Data[i] = new double[4];
34 | }
35 |
36 | public double[][] Data { get; set; }
37 |
38 | public Matrix4D Clone()
39 | {
40 | Matrix4D result = new Matrix4D();
41 | for( int i=0; i<4; i++ )
42 | for( int j=0; j<4; j++ )
43 | result.Data[i][j] = this.Data[i][j];
44 | return result;
45 | }
46 |
47 | public static Matrix4D Identity()
48 | {
49 | Matrix4D result = new Matrix4D();
50 | for( int i=0; i<4; i++ )
51 | result[i,i] = 1;
52 | return result;
53 | }
54 |
55 | ///
56 | /// Mixing multidim and jagged array notation here, but whatevs.
57 | ///
58 | public double this[int i, int j]
59 | {
60 | get
61 | {
62 | return Data[i][j];
63 | }
64 | set
65 | {
66 | Data[i][j] = value;
67 | }
68 | }
69 |
70 | public Vector3D this[int i]
71 | {
72 | get
73 | {
74 | return new Vector3D( Data[i] );
75 | }
76 | set
77 | {
78 | Data[i] = new double[] { value.X, value.Y, value.Z, value.W };
79 | }
80 | }
81 |
82 | public static Matrix4D operator +( Matrix4D m1, Matrix4D m2 )
83 | {
84 | Matrix4D result = new Matrix4D();
85 | for( int i=0; i<4; i++ )
86 | for( int j=0; j<4; j++ )
87 | result[i, j] = m1[i, j] + m2[i, j];
88 | return result;
89 | }
90 |
91 | public static Matrix4D operator *( Matrix4D m1, Matrix4D m2 )
92 | {
93 | Matrix4D result = new Matrix4D();
94 | for( int i=0; i<4; i++ )
95 | for( int j=0; j<4; j++ )
96 | for( int k=0; k<4; k++ )
97 | result[i, j] += m1[i, k] * m2[k, j];
98 | return result;
99 | }
100 |
101 | public static Matrix4D operator *( Matrix4D m, double s )
102 | {
103 | Matrix4D result = new Matrix4D();
104 | for( int i=0; i<4; i++ )
105 | for( int j=0; j<4; j++ )
106 | result[i, j] = m[i, j] * s;
107 | return result;
108 | }
109 |
110 | public static Matrix4D Transpose( Matrix4D m )
111 | {
112 | Matrix4D result = new Matrix4D();
113 | for( int i=0; i<4; i++ )
114 | for( int j=0; j<4; j++ )
115 | result[i, j] = m[j, i];
116 | return result;
117 | }
118 |
119 | ///
120 | /// http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
121 | ///
122 | public double Determinant
123 | {
124 | get
125 | {
126 | double det =
127 | Data[0][3] * Data[1][2] * Data[2][1] * Data[3][0] - Data[0][2] * Data[1][3] * Data[2][1] * Data[3][0] - Data[0][3] * Data[1][1] * Data[2][2] * Data[3][0] + Data[0][1] * Data[1][3] * Data[2][2] * Data[3][0] +
128 | Data[0][2] * Data[1][1] * Data[2][3] * Data[3][0] - Data[0][1] * Data[1][2] * Data[2][3] * Data[3][0] - Data[0][3] * Data[1][2] * Data[2][0] * Data[3][1] + Data[0][2] * Data[1][3] * Data[2][0] * Data[3][1] +
129 | Data[0][3] * Data[1][0] * Data[2][2] * Data[3][1] - Data[0][0] * Data[1][3] * Data[2][2] * Data[3][1] - Data[0][2] * Data[1][0] * Data[2][3] * Data[3][1] + Data[0][0] * Data[1][2] * Data[2][3] * Data[3][1] +
130 | Data[0][3] * Data[1][1] * Data[2][0] * Data[3][2] - Data[0][1] * Data[1][3] * Data[2][0] * Data[3][2] - Data[0][3] * Data[1][0] * Data[2][1] * Data[3][2] + Data[0][0] * Data[1][3] * Data[2][1] * Data[3][2] +
131 | Data[0][1] * Data[1][0] * Data[2][3] * Data[3][2] - Data[0][0] * Data[1][1] * Data[2][3] * Data[3][2] - Data[0][2] * Data[1][1] * Data[2][0] * Data[3][3] + Data[0][1] * Data[1][2] * Data[2][0] * Data[3][3] +
132 | Data[0][2] * Data[1][0] * Data[2][1] * Data[3][3] - Data[0][0] * Data[1][2] * Data[2][1] * Data[3][3] - Data[0][1] * Data[1][0] * Data[2][2] * Data[3][3] + Data[0][0] * Data[1][1] * Data[2][2] * Data[3][3];
133 | return det;
134 | }
135 | }
136 |
137 | ///
138 | /// Gram-Schmidt orthonormalize
139 | ///
140 | public static Matrix4D GramSchmidt( Matrix4D input )
141 | {
142 | Matrix4D result = input;
143 | for( int i=0; i<4; i++ )
144 | {
145 | for( int j=0; j
161 | /// Gram-Schmidt orthonormalize
162 | ///
163 | public static Matrix4D GramSchmidt( Matrix4D input,
164 | Func innerProduct, Func normalize )
165 | {
166 | Matrix4D result = input;
167 | for( int i=0; i<4; i++ )
168 | {
169 | for( int j=i+1; j<4; j++ )
170 | {
171 | Vector3D iVec = result[i];
172 | Vector3D jVec = result[j];
173 | iVec -= innerProduct( iVec, jVec ) * jVec;
174 | result[i] = iVec;
175 | }
176 | result[i] = normalize( result[i] );
177 | }
178 |
179 | return result;
180 | }
181 |
182 | ///
183 | /// Rotate a vector with this matrix.
184 | ///
185 | public Vector3D RotateVector( Vector3D input )
186 | {
187 | Vector3D result = new Vector3D();
188 | Vector3D copy = new Vector3D( new double[] { input.X, input.Y, input.Z, input.W } );
189 | for( int i = 0; i < 4; i++ )
190 | {
191 | result[i] =
192 | copy[0] * this[i, 0] +
193 | copy[1] * this[i, 1] +
194 | copy[2] * this[i, 2] +
195 | copy[3] * this[i, 3];
196 | }
197 | return result;
198 | }
199 |
200 | ///
201 | /// Returns a matrix which will rotate in a coordinate plane by an angle in radians.
202 | ///
203 | public static Matrix4D MatrixToRotateinCoordinatePlane( double angle, int c1, int c2 )
204 | {
205 | Matrix4D result = Matrix4D.Identity();
206 | result[c1, c1] = Math.Cos( angle );
207 | result[c1, c2] = -Math.Sin( angle );
208 | result[c2, c1] = Math.Sin( angle );
209 | result[c2, c2] = Math.Cos( angle );
210 | return result;
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/R3.Core/Math/Statistics.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Math
2 | {
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | // http://www.codeproject.com/Articles/42492/Using-LINQ-to-Calculate-Basic-Statistics
7 | // Add more from there as needed.
8 | public static class Statistics
9 | {
10 | public static double Median( this IEnumerable source )
11 | {
12 | return ElementAtPercentage( source, 0.5 );
13 | }
14 |
15 | ///
16 | /// This is like Median, but allows you to grab the element an arbitrary percentage along.
17 | /// percentage should be between 0 and 1.
18 | ///
19 | public static double ElementAtPercentage( this IEnumerable source, double percentage )
20 | {
21 | var sortedList = from number in source
22 | orderby number
23 | select number;
24 |
25 | int count = sortedList.Count();
26 | int itemIndex = (int)( (double)count * percentage );
27 | if( count % 2 == 0 ) // Even number of items.
28 | return ( sortedList.ElementAt( itemIndex ) +
29 | sortedList.ElementAt( itemIndex - 1 ) ) / 2;
30 |
31 | // Odd number of items.
32 | return sortedList.ElementAt( itemIndex );
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/R3.Core/Math/Utils.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Core
2 | {
3 | using R3.Geometry;
4 | using System.Collections.Generic;
5 | using Math = System.Math;
6 |
7 | public static class Tolerance
8 | {
9 | //public static readonly double Threshold = 0.0000001;
10 | public static readonly double Threshold = 0.000001; // Made less strict to avoid some problems near Poincare boundary.
11 |
12 | public static bool Equal( double d1, double d2 )
13 | {
14 | return Zero( d1 - d2 );
15 | }
16 |
17 | public static bool Zero( double d )
18 | {
19 | return Zero( d, Threshold );
20 | }
21 |
22 | public static bool LessThan( double d1, double d2 )
23 | {
24 | return LessThan( d1, d2, Threshold );
25 | }
26 |
27 | public static bool GreaterThan( double d1, double d2 )
28 | {
29 | return GreaterThan( d1, d2, Threshold );
30 | }
31 |
32 | public static bool LessThanOrEqual( double d1, double d2 )
33 | {
34 | return d1 <= ( d2 + Threshold );
35 | }
36 |
37 | public static bool GreaterThanOrEqual( double d1, double d2 )
38 | {
39 | return d1 >= ( d2 - Threshold );
40 | }
41 |
42 | public static bool Equal( double d1, double d2, double threshold )
43 | {
44 | return Zero( d1 - d2, threshold );
45 | }
46 |
47 | public static bool Zero( double d, double threshold )
48 | {
49 | return ( ( d > -threshold ) && ( d < threshold ) ) ? true : false;
50 | }
51 |
52 | public static bool LessThan( double d1, double d2, double threshold )
53 | {
54 | return d1 < ( d2 - threshold );
55 | }
56 |
57 | public static bool GreaterThan( double d1, double d2, double threshold )
58 | {
59 | return d1 > ( d2 + threshold );
60 | }
61 | }
62 |
63 | public class DoubleEqualityComparer : IEqualityComparer
64 | {
65 | public DoubleEqualityComparer() { }
66 |
67 | public DoubleEqualityComparer( double tol )
68 | {
69 | m_tolerance = tol;
70 | }
71 |
72 | public bool Equals( double d1, double d2 )
73 | {
74 | if( Infinity.IsInfinite( d1 ) && Infinity.IsInfinite( d2 ) )
75 | return true;
76 |
77 | return Tolerance.Equal( d1, d2 );
78 | }
79 |
80 | public int GetHashCode( double d )
81 | {
82 | if( Infinity.IsInfinite( d ) )
83 | return double.PositiveInfinity.GetHashCode();
84 |
85 | double inverse = 1 / m_tolerance;
86 | int decimals = (int)Math.Log10( inverse );
87 | return Math.Round( d, decimals ).GetHashCode();
88 | }
89 |
90 | private double m_tolerance = Tolerance.Threshold;
91 | }
92 |
93 | public static class Utils
94 | {
95 | ///
96 | /// Converts a value from degrees to radians.
97 | ///
98 | public static double DegreesToRadians( double value )
99 | {
100 | return ( value / 180 * System.Math.PI );
101 | }
102 |
103 | ///
104 | /// Converts a value from radians to degrees.
105 | ///
106 | public static double RadiansToDegrees( double value )
107 | {
108 | return ( value / System.Math.PI * 180 );
109 | }
110 |
111 | // ZZZ - Make templated
112 | public static bool Even( int value )
113 | {
114 | return 0 == value % 2;
115 | }
116 |
117 | // ZZZ - Make templated
118 | public static bool Odd( int value )
119 | {
120 | return !Even( value );
121 | }
122 |
123 | public static void SwapPoints( ref Vector3D p1, ref Vector3D p2 )
124 | {
125 | Swap( ref p1, ref p2 );
126 | }
127 |
128 | public static void Swap( ref Type t1, ref Type t2 )
129 | {
130 | Type t = t1;
131 | t1 = t2;
132 | t2 = t;
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/R3.Core/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle( "R3.Core" )]
9 | [assembly: AssemblyDescription( "" )]
10 | [assembly: AssemblyConfiguration( "" )]
11 | [assembly: AssemblyCompany( "R3 Productions" )]
12 | [assembly: AssemblyProduct( "R3.Core" )]
13 | [assembly: AssemblyCopyright( "Copyright © R3 Productions 2011" )]
14 | [assembly: AssemblyTrademark( "" )]
15 | [assembly: AssemblyCulture( "" )]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible( false )]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid( "81a7b307-f96e-4a2c-a63f-40525d4fdde7" )]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion( "1.0.0.0" )]
36 | [assembly: AssemblyFileVersion( "1.0.0.0" )]
37 |
--------------------------------------------------------------------------------
/R3.Core/R3.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}
9 | Library
10 | Properties
11 | R3.Core
12 | R3.Core
13 | v4.7.2
14 | 512
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | false
26 |
27 |
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 | false
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
119 |
--------------------------------------------------------------------------------
/R3.Core/Shapeways/H3Fundamental.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using Math = System.Math;
7 |
8 | ///
9 | /// This class generates H3 honeycombs via a fundamental region.
10 | ///
11 | public class H3Fundamental
12 | {
13 | ///
14 | /// Class for the fundamental tetrahedron.
15 | ///
16 | public class Tet
17 | {
18 | public Tet( Vector3D center, Vector3D face, Vector3D edge, Vector3D vertex )
19 | {
20 | Verts[0] = center;
21 | Verts[1] = face;
22 | Verts[2] = edge;
23 | Verts[3] = vertex;
24 | CalcFaces();
25 | }
26 |
27 | // The order of these 4 vertices will be
28 | // Center,Face,Edge,Vertex of the parent cell.
29 | public Vector3D[] Verts = new Vector3D[4];
30 |
31 | public Sphere[] Faces
32 | {
33 | get { return m_faces; }
34 | }
35 | private Sphere[] m_faces;
36 |
37 | private void CalcFaces()
38 | {
39 | m_faces = new Sphere[]
40 | {
41 | // Orientation is important! CCW from outside
42 | // XXX - Broken for tets with ideal verts.
43 | H3Models.Ball.OrthogonalSphereInterior( Verts[0], Verts[1], Verts[2] ),
44 | H3Models.Ball.OrthogonalSphereInterior( Verts[0], Verts[3], Verts[1] ),
45 | H3Models.Ball.OrthogonalSphereInterior( Verts[0], Verts[2], Verts[3] ),
46 | H3Models.Ball.OrthogonalSphereInterior( Verts[1], Verts[3], Verts[2] )
47 | };
48 | }
49 |
50 | public Vector3D ID
51 | {
52 | get
53 | {
54 | Vector3D result = new Vector3D();
55 | foreach( Vector3D v in Verts )
56 | result += v;
57 | return result;
58 | }
59 | }
60 |
61 | public Tet Clone()
62 | {
63 | return new Tet( Verts[0], Verts[1], Verts[2], Verts[3] );
64 | }
65 |
66 | public void Reflect( Sphere sphere )
67 | {
68 | for( int i=0; i<4; i++ )
69 | Verts[i] = sphere.ReflectPoint( Verts[i] );
70 | CalcFaces();
71 | }
72 | }
73 |
74 | public class TetEqualityComparer : IEqualityComparer
75 | {
76 | public bool Equals( Tet t1, Tet t2 )
77 | {
78 | return t1.ID.Compare( t2.ID, m_tolerance );
79 | }
80 |
81 | public int GetHashCode( Tet t )
82 | {
83 | return t.ID.GetHashCode();
84 | }
85 |
86 | private double m_tolerance = 0.0001;
87 | }
88 |
89 | public static void Generate( EHoneycomb honeycomb, H3.Settings settings )
90 | {
91 | // XXX - Block the same as in H3. Share code better.
92 | H3.Cell template = null;
93 | {
94 | int p, q, r;
95 | Honeycomb.PQR( honeycomb, out p, out q, out r );
96 |
97 | // Get data we need to generate the honeycomb.
98 | Polytope.Projection projection = Polytope.Projection.FaceCentered;
99 | double phi, chi, psi;
100 | H3.HoneycombData( honeycomb, out phi, out chi, out psi );
101 |
102 | H3.SetupCentering( honeycomb, settings, phi, chi, psi, ref projection );
103 |
104 | Tiling tiling = new Tiling();
105 | TilingConfig config = new TilingConfig( p, q );
106 | tiling.GenerateInternal( config, projection );
107 |
108 | H3.Cell first = new H3.Cell( p, H3.GenFacets( tiling ) );
109 | first.ToSphere(); // Work in ball model.
110 | first.ScaleToCircumSphere( 1.0 );
111 | first.ApplyMobius( settings.Mobius );
112 |
113 | template = first;
114 | }
115 |
116 | // Center
117 | Vector3D center = template.Center;
118 |
119 | // Face
120 | H3.Cell.Facet facet = template.Facets[0];
121 | Sphere s = H3Models.Ball.OrthogonalSphereInterior( facet.Verts[0], facet.Verts[1], facet.Verts[2] );
122 | Vector3D face = s.Center;
123 | face.Normalize();
124 | face *= DistOriginToOrthogonalSphere( s.Radius );
125 |
126 | // Edge
127 | Circle3D c;
128 | H3Models.Ball.OrthogonalCircleInterior( facet.Verts[0], facet.Verts[1], out c );
129 | Vector3D edge = c.Center;
130 | edge.Normalize();
131 | edge *= DistOriginToOrthogonalSphere( c.Radius );
132 |
133 | // Vertex
134 | Vector3D vertex = facet.Verts[0];
135 |
136 | Tet fundamental = new Tet( center, face, edge, vertex );
137 |
138 | // Recurse.
139 | int level = 1;
140 | Dictionary completedTets = new Dictionary( new TetEqualityComparer() );
141 | completedTets.Add( fundamental, level );
142 | List tets = new List();
143 | tets.Add( fundamental );
144 | ReflectRecursive( level, tets, completedTets, settings );
145 |
146 | Shapeways mesh = new Shapeways();
147 | foreach( KeyValuePair kvp in completedTets )
148 | {
149 | if( Utils.Odd( kvp.Value ) )
150 | continue;
151 |
152 | Tet tet = kvp.Key;
153 |
154 | // XXX - really want sphere surfaces here.
155 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[0], tet.Verts[1], tet.Verts[2] ) );
156 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[0], tet.Verts[3], tet.Verts[1] ) );
157 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[0], tet.Verts[2], tet.Verts[3] ) );
158 | mesh.Mesh.Triangles.Add( new Mesh.Triangle( tet.Verts[1], tet.Verts[3], tet.Verts[2] ) );
159 | }
160 |
161 | mesh.Mesh.Scale( settings.Scale );
162 | STL.SaveMeshToSTL( mesh.Mesh, H3.m_baseDir + "fundamental" + ".stl" );
163 | }
164 |
165 | private static double DistOriginToOrthogonalSphere( double r )
166 | {
167 | // http://mathworld.wolfram.com/OrthogonalCircles.html
168 | double d = Math.Sqrt( 1 + r * r );
169 | return d - r;
170 | }
171 |
172 | private static void ReflectRecursive( int level, List tets, Dictionary completedTets, H3.Settings settings )
173 | {
174 | // Breadth first recursion.
175 |
176 | if( 0 == tets.Count )
177 | return;
178 |
179 | level++;
180 |
181 | List reflected = new List();
182 |
183 | foreach( Tet tet in tets )
184 | {
185 | foreach( Sphere facetSphere in tet.Faces )
186 | {
187 | if( facetSphere == null )
188 | throw new System.Exception( "Unexpected." );
189 |
190 | if( completedTets.Count > settings.MaxCells )
191 | return;
192 |
193 | Tet newTet = tet.Clone();
194 | newTet.Reflect( facetSphere );
195 | if( completedTets.Keys.Contains( newTet ) ||
196 | !TetOk( newTet ) )
197 | continue;
198 |
199 | reflected.Add( newTet );
200 | completedTets.Add( newTet, level );
201 | }
202 | }
203 |
204 | ReflectRecursive( level, reflected, completedTets, settings );
205 | }
206 |
207 | private static bool TetOk( Tet tet )
208 | {
209 | double cutoff = 0.95;
210 | foreach( Vector3D v in tet.Verts )
211 | if( Tolerance.GreaterThan( v.Z, 0 ) ||
212 | v.Abs() > cutoff )
213 | return false;
214 |
215 | return true;
216 | }
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/R3.Core/Shapeways/ShapewaysSandbox.cs:
--------------------------------------------------------------------------------
1 | namespace R3.Geometry
2 | {
3 | using R3.Core;
4 | using R3.Drawing;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using Math = System.Math;
8 |
9 | public static class ShapewaysSandbox
10 | {
11 | public static void GenPolyhedron()
12 | {
13 | Tiling tiling;
14 | int p = 3;
15 | int q = 6;
16 | GetAssociatedTiling( p, q, 5000, out tiling );
17 |
18 | double overallScale = 12.5; // 2.5 cm = 1 in diameter
19 |
20 | Shapeways mesh = new Shapeways();
21 | foreach( Tile tile in tiling.Tiles )
22 | foreach( Segment seg in tile.Boundary.Segments )
23 | {
24 | double tilingScale = 0.75;
25 | seg.Scale( new Vector3D(), tilingScale );
26 |
27 | Vector3D v1 = Sterographic.PlaneToSphereSafe( seg.P1 );
28 | Vector3D v2 = Sterographic.PlaneToSphereSafe( seg.P2 );
29 | //if( v1.Dist( v2 ) < 0.01 )
30 | // continue;
31 | if( SphericalCoords.CartesianToSpherical( v1 ).Y < Math.PI / 12 &&
32 | SphericalCoords.CartesianToSpherical( v2 ).Y < Math.PI / 12 )
33 | continue;
34 |
35 | double dist = v1.Dist( v2 );
36 | int divisions = 2 + (int)( dist * 20 );
37 |
38 | Vector3D[] points = seg.Subdivide( divisions );
39 | points = points.Select( v => Sterographic.PlaneToSphereSafe( v ) ).ToArray();
40 | mesh.AddCurve( points, v => SizeFunc( v, overallScale ) );
41 | }
42 |
43 | mesh.Mesh.Scale( overallScale );
44 |
45 | string outputFileName = @"d:\temp\" + p + q + ".stl";
46 | STL.SaveMeshToSTL( mesh.Mesh, outputFileName );
47 | }
48 |
49 | private static void GetAssociatedTiling( int p, int q, int maxTiles, out Tiling tiling )
50 | {
51 | TilingConfig tilingConfig = new TilingConfig( p, q, maxTiles: maxTiles );
52 | tiling = new Tiling();
53 | tiling.GenerateInternal( tilingConfig, p == 6 ? Polytope.Projection.VertexCentered : Polytope.Projection.FaceCentered );
54 | }
55 |
56 | private static double SizeFunc( Vector3D v, double overallScale )
57 | {
58 | //return .6 / 2 / overallScale;
59 |
60 | // Silver min wall is 0.6
61 | // Silver min wire is 0.8 (supported) or 1.0 (unsupported).
62 |
63 | double min = 0.55 / 2;
64 | double max = 1.5 / 2;
65 |
66 | // for caps
67 | //double min = 0.71 / 2;
68 | //double max = 0.5 / 2; // 36
69 | //double max = 0.35 / 2; // 63
70 |
71 | Vector3D s = SphericalCoords.CartesianToSpherical( v );
72 | double angle = s.Y / Math.PI; // angle 0 to 1
73 | double result = min + ( max - min ) * angle;
74 | return result / overallScale;
75 | }
76 |
77 | public static void GenCapWithHole()
78 | {
79 | Shapeways mesh = new Shapeways();
80 |
81 | double overallScale = 12.5; // 2.5 cm = 1 in diameter
82 |
83 | // Make hole 2 mm
84 | double startAngle = Math.Asin( 2.0 / 2 / overallScale );
85 | double endAngle = Math.PI / 8; // Slightly larger than hole above.
86 |
87 | int div = 75;
88 | double angleInc = (endAngle - startAngle) / div;
89 | for( int i=0; i pointsP1 = new List
();
103 | List pointsP2 = new List();
104 | List pointsN1 = new List();
105 | List pointsN2 = new List();
106 | for( int j=0; j circlePoints = new List
();
140 | int div = 30;
141 | for( int i=0; i pointInDirAtDist = (dir, hDist) =>
55 | {
56 | dir.Normalize();
57 | dir *= DonHatch.h2eNorm( hDist );
58 | return dir;
59 | };
60 |
61 | List
distancesSmoothed = new List();
62 | List anglesSmoothed = new List();
63 | for( int i=0; i pointInDirAtDist( pEnd, d ) ).ToArray();
71 |
72 | // Build up a set of Mobius transformations.
73 | Mobius trans = new Mobius(), rot = new Mobius(), runningStep = Mobius.Identity();
74 | trans.Geodesic( settings.Geometry, pStart.ToComplex(), pEnd.ToComplex() );
75 | rot.Isometry( Geometry.Euclidean, Math.PI / 2, new Complex() );
76 | List mList = new List();
77 | List globalEdges = new List();
78 | List completedEdges = new List();
79 | for( int s=0; s<=5; s++ )
80 | {
81 | for( int i = 0; i < numFrames * 2; i++ )
82 | {
83 | List frameEdges = new List();
84 | Mobius m = new Mobius();
85 | if( i < numFrames )
86 | {
87 | Vector3D pCurrent = pointsSmoothed[i];
88 | m.Geodesic( settings.Geometry, pStart.ToComplex(), pCurrent.ToComplex() );
89 | mList.Add( runningStep * m );
90 | frameEdges.Add( new H3.Cell.Edge( runningStep.Apply( pStart ), runningStep.Apply( pCurrent ) ) );
91 | }
92 | else
93 | {
94 | m.Isometry( Geometry.Euclidean, anglesSmoothed[i-numFrames], new Complex() );
95 | mList.Add( runningStep * trans * m );
96 | frameEdges.Add( new H3.Cell.Edge( runningStep.Apply( pStart ), runningStep.Apply( pEnd ) ) );
97 | }
98 | frameEdges.AddRange( completedEdges );
99 | globalEdges.Add( frameEdges.ToArray() );
100 | }
101 | completedEdges.Add( new H3.Cell.Edge( runningStep.Apply( pStart ), runningStep.Apply( pEnd ) ) );
102 | runningStep *= trans * rot;
103 | }
104 | */
105 |
106 | double ew = settings.EdgeWidth;
107 |
108 | for( int i = 0; i < numFrames; i++ )
109 | {
110 | string numString = i.ToString();
111 | numString = numString.PadLeft( 3, '0' );
112 | settings.FileName = "frame" + numString + ".png";
113 | Console.WriteLine( settings.FileName );
114 | double frac = (double)i / numFrames;
115 |
116 | // Setup the Mobius.
117 | Vector3D pCurrent = points[i];
118 | Mobius m = Mobius.Identity(), mInitOff = Mobius.Identity(), mInitRot = Mobius.Identity(), mCentering = Mobius.Identity(), mModel = Mobius.Identity();
119 | //mInitRot = Mobius.CreateFromIsometry( Geometry.Euclidean, frac * 2 * Math.PI, new Complex() );
120 | //settings.Centering = Tiler.Centering.Fundamental_Triangle_Vertex2;
121 | //mCentering = settings.CenteringMobius();
122 | double rot = frac * 2 * Math.PI / 2;
123 | settings.ImageRot = -rot;
124 |
125 | //mInitOff = OffsetMobius( settings );
126 | mInitRot = Mobius.CreateFromIsometry( Geometry.Euclidean, -Math.PI / 4, new Complex() );
127 |
128 | //m.Isometry( settings.Geometry, Math.PI / 4, pCurrent.ToComplex() );
129 | //m.Geodesic( settings.Geometry, pStart.ToComplex(), pCurrent.ToComplex() );
130 | //m = mCentering * m * mCentering.Inverse();
131 |
132 | // Rotation
133 | double xOff = OffsetInModel( settings, 0, 0, 1 );
134 | m = RotAboutPoint( settings.Geometry, new Vector3D( 0, 0 ), rot );
135 | //m = RotAboutPoint( settings.Geometry, new Vector3D( xOff, 0 ),/* -frac * Math.PI*/ -frac * 2 * Math.PI / settings.Q );
136 | //m = LimitRot( settings, 0*Math.PI/4, 2*frac );
137 | //m = RotAboutPoint( settings.Geometry, new Vector3D(0,0), frac * 2 * Math.PI / settings.P );
138 | //m = mList[i];
139 |
140 | settings.Anim = frac;// Util.Smoothed( frac, 1.0 );
141 |
142 | // Change edge width.
143 | //double fact = 1 + settings.Anim * 20;
144 | //settings.EdgeWidth = ew * fact;
145 |
146 | //settings.GlobalEdges = globalEdges[i];
147 |
148 | Console.WriteLine( Tweet.Format( settings ) + "\n" );
149 | string newPath = Path.Combine( Persistence.AnimDir, settings.FileName );
150 | if( File.Exists( newPath ) )
151 | continue;
152 |
153 | // Need to do this when animating where edges need recalc.
154 | settings.Init();
155 |
156 | //mModel = RotAboutPoint( Geometry.Spherical, new Vector3D( 1, 0 ), 2 * Math.PI * frac );
157 | //Mobius mZoom = Mobius.Scale( 1.0 / ( 1.0 - Util.Smoothed( 2*frac, 0.8 ) ) );
158 | //mModel *= mZoom;
159 |
160 | // We will control the Mobius transformation ourself.
161 | // NOTE: Needs to be done after Init call above, or it will get clobbered.
162 | settings.Mobius = m * mCentering * mInitOff * mInitRot * mModel;
163 | settings.Centering = Tiler.Centering.General;
164 | //settings.Mobius = RotAboutPoint( settings, new Vector3D(), Math.PI / 6 );
165 |
166 | Program.MakeTiling( settings );
167 |
168 | File.Delete( newPath );
169 | File.Move( settings.FileName, newPath );
170 |
171 | if( i == 0 )
172 | {
173 | string settingsPath = Path.Combine( Persistence.AnimDir, "settings.xml" );
174 | Persistence.SaveSettings( settings, settingsPath );
175 | }
176 | }
177 | }
178 |
179 | static public double OffsetInSpace(Tiler.Settings settings, double p = 0, double q = 0, double r = 1)
180 | {
181 | return
182 | p * Geometry2D.GetTrianglePSide( settings.P, settings.Q ) +
183 | q * Geometry2D.GetTriangleQSide( settings.P, settings.Q ) +
184 | r * Geometry2D.GetTriangleHypotenuse( settings.P, settings.Q );
185 | }
186 |
187 | static public double OffsetInModel(Tiler.Settings settings, double p = 0, double q = 0, double r = 1)
188 | {
189 | double off = OffsetInSpace( settings, p, q, r );
190 | switch( settings.Geometry )
191 | {
192 | case Geometry.Spherical:
193 | off = Spherical2D.s2eNorm( off );
194 | break;
195 | case Geometry.Hyperbolic:
196 | off = DonHatch.h2eNorm( off );
197 | break;
198 | }
199 | return off;
200 | }
201 |
202 | static public Mobius OffsetMobius(Tiler.Settings settings, double p = 0, double q = 0, double r = 1)
203 | {
204 | double off = OffsetInModel( settings, p, q, r );
205 | return Mobius.CreateFromIsometry( settings.Geometry, 0, new Complex( off, 0 ) );
206 | }
207 |
208 | ///
209 | /// initialRot is used to take a particular point to infinity. When 0, this point is (0,1) in the ball.
210 | /// off is used to do the limit rotation by doing a tranlation in the UHP.
211 | ///
212 | static public Mobius LimitRot(Tiler.Settings settings, double initialRot, double off)
213 | {
214 | Mobius mRot = new Mobius(), mOff = new Mobius();
215 | mRot.Isometry( Geometry.Euclidean, initialRot, new Complex() );
216 | mOff.Isometry( Geometry.Euclidean, 0, new Complex( off, 0 ) );
217 | return mRot.Inverse() * HyperbolicModels.UpperInv * mOff * HyperbolicModels.Upper * mRot;
218 | }
219 |
220 | static public Mobius RotAboutPoint(Geometry g, Vector3D p, double rot)
221 | {
222 | Mobius m1 = new Mobius(), m2 = new Mobius();
223 | m1.Isometry( g, 0, p );
224 | m2.Isometry( g, rot, new Complex() );
225 | return m1 * m2 * m1.Inverse();
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/TilingBot/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TilingBot/Meta.Numerics/Meta.Numerics.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/roice3/TilingBot/1b4d13fcdc16c7c59b49fc9faea8aea3706ced2b/TilingBot/Meta.Numerics/Meta.Numerics.dll
--------------------------------------------------------------------------------
/TilingBot/Mosaic.cs:
--------------------------------------------------------------------------------
1 | namespace TilingBot
2 | {
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.Drawing.Imaging;
6 | using System.IO;
7 | using R3.Core;
8 | using R3.Drawing;
9 | using R3.Geometry;
10 |
11 | internal class Mosaic
12 | {
13 | public void Generate()
14 | {
15 | string mosaicDir = @"D:\GitHub\TilingBot\TilingBot\working\mosaic";
16 | string[] files = EnumFiles( new string[] { @"D:\GitHub\TilingBot\TilingBot\working\experiment graveyard\mosaic" } );
17 | //GenImages( mosaicDir, files );
18 | //return;
19 |
20 | // Load image info.
21 | mosaicDir = @"D:\GitHub\TilingBot\TilingBot\working\mosaic";
22 | var images = LoadImages( mosaicDir );
23 | //return;
24 |
25 | string sourceImage = @"D:\GitHub\TilingBot\TilingBot\working\tet_50.png";
26 | Bitmap source = new Bitmap( sourceImage );
27 |
28 | List inputImages = new List();
29 | for( int x = 0; x < source.Width; x++ )
30 | for( int y = 0; y < source.Height; y++ )
31 | {
32 | Color c = source.GetPixel( x, y );
33 | ImageInfo closest = FindClosest( c, images.Values );
34 | closest.Used = true;
35 | string fn = Path.GetFileName( closest.Path );
36 | inputImages.Add( fn );
37 | System.Diagnostics.Trace.WriteLine( fn + "\t" + c.GetBrightness() + "\t" + closest.AvgBrightness + "\t" + c.GetSaturation() + "\t" + closest.AvgSat );
38 | }
39 |
40 | // Create output image.
41 | int numTiles = source.Width;
42 | //numTiles = 10;
43 | int gridSpace = 30;
44 | int tileSize = 300;
45 | int size = numTiles * tileSize + ( numTiles + 1 ) * gridSpace;
46 |
47 | ImageGrid.Settings settings = new ImageGrid.Settings()
48 | {
49 | Directory = mosaicDir,
50 | Rows = numTiles,
51 | Columns = numTiles,
52 | Width = size,
53 | Height = size,
54 | hGap = gridSpace,
55 | vGap = gridSpace,
56 | InputImages = inputImages.ToArray(),
57 | FileName = "mosaic.png"
58 | };
59 |
60 | ImageGrid grid = new ImageGrid();
61 | grid.Generate( settings );
62 | }
63 |
64 | private ImageInfo FindClosest( Color c, IEnumerable images )
65 | {
66 | // Use brightness and saturation as a measure of closeness.
67 | double satScale = 0.4;
68 | double cBrightness = c.GetBrightness();
69 | double cSat = c.GetSaturation() * satScale;
70 | System.Func distFn = ( brightness, sat ) =>
71 | {
72 | sat *= satScale;
73 | return new Vector3D( cBrightness, cSat ).Dist( new Vector3D( brightness, sat ) );
74 | };
75 |
76 | double dist = double.MaxValue;
77 |
78 | ImageInfo closest = null;
79 | foreach( ImageInfo image in images )
80 | {
81 | if( image.Used )
82 | continue;
83 | if( !File.Exists( image.Path ) )
84 | continue;
85 |
86 | double d = distFn( image.AvgBrightness, image.AvgSat );
87 | if( d < dist )
88 | {
89 | dist = d;
90 | closest = image;
91 | }
92 | }
93 | return closest;
94 | }
95 |
96 | private string[] EnumFiles( string[] directories )
97 | {
98 | List list = new List();
99 | foreach( string dir in directories )
100 | list.AddRange( Directory.EnumerateFiles( dir, "*.xml", SearchOption.AllDirectories ) );
101 | return list.ToArray();
102 | }
103 |
104 | private void GenImages( string mosaicDir, string[] files )
105 | {
106 | List cData = new List();
107 | //cData.Add( new int[] { 40, 4 } );
108 | cData.Add( new int[] { 30, 18 } );
109 | cData.Add( new int[] { 40, 36 } );
110 | cData.Add( new int[] { 20, 5 } );
111 |
112 | int count = 4000;//0;
113 | foreach( string file in files )
114 | {
115 | Tiler.Settings settings = Persistence.LoadSettings( file );
116 | if( settings.Geometry != Geometry.Hyperbolic )
117 | continue;
118 |
119 | //if( settings.ColoringOption != 3 )
120 | // continue;
121 | //foreach( int[] data in cData )
122 | {
123 | //settings.ColoringData = new int[] { settings.ColoringData == null ? 0 : settings.ColoringData[0], data[0], data[1] };
124 | foreach( HyperbolicModel hModel in this.hModels )
125 | {
126 | settings.HyperbolicModel = hModel;
127 | Program.StandardInputs( settings );
128 | GenImage( mosaicDir, settings, count++ );
129 | }
130 | }
131 | }
132 | }
133 |
134 | private HyperbolicModel[] hModels
135 | {
136 | get
137 | {
138 | return new HyperbolicModel[]
139 | {
140 | HyperbolicModel.Poincare,
141 | HyperbolicModel.Klein,
142 | HyperbolicModel.Band,
143 | HyperbolicModel.UpperHalfPlane,
144 | HyperbolicModel.Orthographic,
145 | HyperbolicModel.Square,
146 | HyperbolicModel.InvertedPoincare,
147 | HyperbolicModel.Joukowsky,
148 | //Ring,
149 | HyperbolicModel.Azimuthal_Equidistant,
150 | };
151 | }
152 | }
153 |
154 | private void GenImage( string mosaicDir, Tiler.Settings settings, int count )
155 | {
156 | string numString = count.ToString();
157 | numString = numString.PadLeft( 4, '0' );
158 | settings.FileName = Path.Combine( mosaicDir, "" + numString + ".png" );
159 | if( File.Exists( settings.FileName ) )
160 | return;
161 |
162 | Program.MakeTiling( settings );
163 | }
164 |
165 | private Dictionary LoadImages( string dir )
166 | {
167 | HashSet dupChecker = new HashSet( new DoubleEqualityComparer( 0.0001 ) );
168 | Dictionary images = new Dictionary();
169 | string cached = Path.Combine( dir, "list.txt" );
170 | if( File.Exists( cached ) )
171 | {
172 | string[] lines = File.ReadAllLines( cached );
173 | foreach( string line in lines )
174 | {
175 | string[] split = line.Split( '\t' );
176 | ImageInfo ii = new ImageInfo()
177 | {
178 | Path = split[0],
179 | AvgBrightness = double.Parse( split[1] ),
180 | AvgSat = double.Parse( split[2] )
181 | };
182 | if( dupChecker.Add( ii.AvgBrightness ) )
183 | images[ii.Path] = ii;
184 | }
185 | }
186 | else
187 | {
188 | foreach( string file in Directory.EnumerateFiles( dir, "*.png", SearchOption.TopDirectoryOnly ) )
189 | {
190 | ImageInfo ii = LoadBitmap( file );
191 | images[ii.Path] = ii;
192 | }
193 | }
194 |
195 | return images;
196 | }
197 |
198 | public class ImageInfo
199 | {
200 | public Bitmap Bitmap { get; set; }
201 | public string Path { get; set; }
202 | public double AvgBrightness { get; set; }
203 | public double AvgSat { get; set; }
204 |
205 | public bool Used = false;
206 | }
207 |
208 | public static ImageInfo LoadBitmap( string path )
209 | {
210 | double avgBrightness = 0, avgSat = 0;
211 | Bitmap bitmap = new Bitmap( path );
212 | for( int x = 0; x < bitmap.Width; x++ )
213 | for( int y = 0; y < bitmap.Height; y++ )
214 | {
215 | Color c = bitmap.GetPixel( x, y );
216 | avgBrightness += c.GetBrightness();
217 | avgSat += c.GetSaturation();
218 | }
219 |
220 | int numPixels = bitmap.Width * bitmap.Height;
221 | avgBrightness /= numPixels;
222 | avgSat /= numPixels;
223 | bitmap.Dispose();
224 |
225 | System.Diagnostics.Trace.WriteLine( path + "\t" + avgBrightness + "\t" + avgSat );
226 | return new ImageInfo()
227 | {
228 | //Bitmap = bitmap,
229 | Path = path,
230 | AvgBrightness = avgBrightness,
231 | AvgSat = avgSat
232 | };
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/TilingBot/Persistence.cs:
--------------------------------------------------------------------------------
1 | namespace TilingBot
2 | {
3 | using R3.Geometry;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Runtime.Serialization;
7 | using System.Xml;
8 |
9 | public class Persistence
10 | {
11 | public static string WorkingDir
12 | {
13 | get
14 | {
15 | if( !string.IsNullOrEmpty( m_working ) )
16 | return m_working;
17 |
18 | string working = "working";
19 | string current = Directory.GetCurrentDirectory();
20 | string dir = Path.Combine( current, working );
21 | if( Directory.Exists( dir ) )
22 | return dir;
23 |
24 | // Diff directory for development.
25 | string dev = Directory.GetParent( current ).Parent.FullName;
26 | return Path.Combine( dev, working );
27 | }
28 | set
29 | {
30 | m_working = value;
31 | }
32 | }
33 | private static string m_working = string.Empty;
34 |
35 | public static void Move( string fileSansExtension, string from, string to )
36 | {
37 | string[] files = new string[] { fileSansExtension + ".png", fileSansExtension + ".xml" };
38 | foreach( string file in files )
39 | {
40 | File.Move(
41 | Path.Combine( from, file ),
42 | Path.Combine( to, file ) );
43 | }
44 | }
45 |
46 | public static string AnimDir
47 | {
48 | get
49 | {
50 | string anim = "anim";
51 | return Path.Combine( WorkingDir, anim );
52 | }
53 | }
54 |
55 | public static string QueueDir
56 | {
57 | get
58 | {
59 | string queue = "queue";
60 | return Path.Combine( WorkingDir, queue );
61 | }
62 | }
63 |
64 | public static string QueueFile
65 | {
66 | get
67 | {
68 | return Path.Combine( WorkingDir, "queue.txt" );
69 | }
70 | }
71 |
72 | public static string NextInQueue()
73 | {
74 | string[] queue = File.ReadAllLines( QueueFile );
75 | if( queue.Length > 0 )
76 | return queue[0];
77 |
78 | return RandomQueueFile();
79 | }
80 |
81 | public static string RandomQueueFile()
82 | {
83 | string[] files = System.IO.Directory.GetFiles( QueueDir, "*.xml", SearchOption.TopDirectoryOnly );
84 | if( files.Length == 0 )
85 | return string.Empty;
86 |
87 | System.Random rand = new System.Random();
88 | int idx = rand.Next( 0, files.Length );
89 | return Path.GetFileNameWithoutExtension( files[idx] );
90 | }
91 |
92 | public static void PopQueue()
93 | {
94 | string[] queue = File.ReadAllLines( QueueFile );
95 | File.WriteAllLines( QueueFile, queue.Skip( 1 ) );
96 | }
97 |
98 | public static void SaveSettings( Tiler.Settings settings, string path )
99 | {
100 | XmlWriterSettings writerSettings = new XmlWriterSettings();
101 | writerSettings.OmitXmlDeclaration = true;
102 | writerSettings.Indent = true;
103 | using( var writer = XmlWriter.Create( path, writerSettings ) )
104 | {
105 | DataContractSerializer dcs = new DataContractSerializer( settings.GetType() );
106 | dcs.WriteObject( writer, settings );
107 | }
108 | }
109 |
110 | public static Tiler.Settings LoadSettings( string path )
111 | {
112 | XmlReaderSettings readingSettings = new XmlReaderSettings();
113 | readingSettings.IgnoreWhitespace = true;
114 | using( var reader = XmlReader.Create( path, readingSettings ) )
115 | {
116 | DataContractSerializer dcs = new DataContractSerializer( typeof( Tiler.Settings ) );
117 | return (Tiler.Settings)dcs.ReadObject( reader, verifyObjectName: false );
118 | }
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/TilingBot/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle( "TilingBot" )]
9 | [assembly: AssemblyDescription( "" )]
10 | [assembly: AssemblyConfiguration( "" )]
11 | [assembly: AssemblyCompany( "HP Inc." )]
12 | [assembly: AssemblyProduct( "TilingBot" )]
13 | [assembly: AssemblyCopyright( "Copyright © HP Inc. 2018" )]
14 | [assembly: AssemblyTrademark( "" )]
15 | [assembly: AssemblyCulture( "" )]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible( false )]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid( "c83ddaf0-ad95-4d0c-bdd8-27aaf2991239" )]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion( "1.0.0.0" )]
36 | [assembly: AssemblyFileVersion( "1.0.0.0" )]
37 |
--------------------------------------------------------------------------------
/TilingBot/Schwarz_Christoffel.cs:
--------------------------------------------------------------------------------
1 | namespace TilingBot
2 | {
3 | using System.Numerics;
4 | using Math = System.Math;
5 |
6 | ///
7 | /// Ported this code from Matthew Arcus at https://www.shadertoy.com/view/tsfyRj
8 | ///
9 | internal class Schwarz_Christoffel
10 | {
11 | private static double binomial( double a, int n )
12 | {
13 | double s = 1.0;
14 | for( int i = n; i >= 1; i--, a-- )
15 | {
16 | s *= a / i;
17 | }
18 | return s;
19 | }
20 |
21 | // The Lanczos approximation, should only be good for z >= 0.5,
22 | // but we get the right answers anyway.
23 | private static double gamma( double z )
24 | {
25 | double[] p = new double[8]
26 | {
27 | 676.5203681218851,
28 | -1259.1392167224028,
29 | 771.32342877765313,
30 | -176.61502916214059,
31 | 12.507343278686905,
32 | -0.13857109526572012,
33 | 9.9843695780195716e-6,
34 | 1.5056327351493116e-7
35 | };
36 | z -= 1.0;
37 | double x = 0.99999999999980993; // Unnecessary precision
38 | for( int i = 0; i < 8; i++ )
39 | {
40 | double pval = p[i];
41 | x += pval / ( z + i + 1 );
42 | }
43 | double t = z + 8.0 - 0.5;
44 | return Math.Sqrt( 2.0 * Math.PI ) * Math.Pow( t, z + 0.5 ) * Math.Exp( -t ) * x;
45 | }
46 |
47 | // The Beta function
48 | private static double B( double a, double b )
49 | {
50 | return ( gamma( a ) * gamma( b ) ) / gamma( a + b );
51 | }
52 |
53 | // Original Octave/Matlab code for main function:
54 | // w=z(inZ).*( 1-cn(1)*h+(-cn(2)+(K+1)*cn(1)^2)*h.^2+
55 | // (-cn(3)+(3*K+2)*(cn(1)*cn(2)-(K+1)/2*cn(1)^3))*h.^3+
56 | // (-cn(4)+(2*K+1)*(2*cn(1)*cn(3)+cn(2)^2-(4*K+3)*(cn(1)^2*cn(2)-(K+1)/3*cn(1)^4)))*h.^4+
57 | // (-cn(5)+(5*K+2)*(cn(1)*cn(4)+cn(2)*cn(3)+(5*K+3)*(-.5*cn(1)^2*cn(3)-.5*cn(1)*cn(2)^2+
58 | // (5*K+4)*(cn(1)^3*cn(2)/6-(K+1)*cn(1)^5/24))))*h.^5./(1+h/C^K) );
59 |
60 | public static Complex inversesc( Complex z, int K_ )
61 | {
62 | double K = K_;
63 |
64 | double[] cn = new double[6];
65 | for( int n = 1; n <= 5; n++ )
66 | {
67 | cn[n] = binomial( (double)n - 1.0 + 2.0 / K, n ) / ( 1 + n * K ); // Series Coefficients
68 | }
69 | double C = B( 1.0 / K, 1.0 - 2.0 / K ) / K; // Scale factor
70 | z *= C; // Scale polygon to have diameter 1
71 | Complex h = Complex.Pow( z, K );
72 | double T1 = -cn[1];
73 | double T2 = -cn[2] + ( K + 1 ) * Math.Pow( cn[1], 2.0 );
74 | double T3 = -cn[3] + ( 3 * K + 2 ) * ( cn[1] * cn[2] - ( K + 1 ) / 2.0 * Math.Pow( cn[1], 3.0 ) );
75 | double T4 = -cn[4] + ( 2 * K + 1 ) * ( 2.0 * cn[1] * cn[3] + Math.Pow( cn[2], 2.0 ) - ( 4 * K + 3 ) *
76 | ( Math.Pow( cn[1], 2.0 ) * cn[2] - ( K + 1 ) / 3.0 * Math.Pow( cn[1], 4.0 ) ) );
77 | double T5 = -cn[5] + ( 5 * K + 2 ) * ( cn[1] * cn[4] + cn[2] * cn[3] + ( 5 * K + 3 ) *
78 | ( -0.5 * Math.Pow( cn[1], 2.0 ) * cn[3] - 0.5 * cn[1] * Math.Pow( cn[2], 2.0 ) + ( 5 * K + 4 ) *
79 | ( Math.Pow( cn[1], 3.0 ) * cn[2] / 6.0 - ( K + 1 ) * Math.Pow( cn[1], 5.0 ) / 24.0 ) ) );
80 | Complex X = new Complex( 1, 0 ) + h / Math.Pow( C, K );
81 | Complex w = Complex.Multiply( z, new Complex( 1, 0 ) + T1 * h + T2 * Complex.Pow( h, 2 ) + T3 * Complex.Pow( h, 3 ) + T4 * Complex.Pow( h, 4 ) + Complex.Divide( T5 * Complex.Pow( h, 5 ), X ) );
82 | return w;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/TilingBot/Test.cs:
--------------------------------------------------------------------------------
1 | namespace TilingBot
2 | {
3 | using R3.Geometry;
4 | using R3.Math;
5 | using System.Drawing;
6 | using System.IO;
7 | using Color = System.Drawing.Color;
8 |
9 | ///
10 | /// This can be used to override the randomization in a tiling.
11 | /// Mainly used during development when adding new features.
12 | ///
13 | internal static class Test
14 | {
15 | public static bool IsTesting = false;
16 |
17 | public static void InputsTesting( ref Tiler.Settings settings )
18 | {
19 | if( !IsTesting )
20 | return;
21 |
22 | settings = Persistence.LoadSettings( Path.Combine( Persistence.WorkingDir, "2018-5-17_22-45-53.xml" ) );
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TilingBot/TilingBot.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {C83DDAF0-AD95-4D0C-BDD8-27AAF2991239}
8 | Exe
9 | TilingBot
10 | TilingBot
11 | v4.8
12 | 512
13 | true
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 | packages\linqtotwitter.4.2.1\lib\net45\LinqToTwitter.AspNet.dll
38 |
39 |
40 | packages\linqtotwitter.4.2.1\lib\net45\LinqToTwitter.net.dll
41 |
42 |
43 | Meta.Numerics\Meta.Numerics.dll
44 |
45 |
46 |
47 |
48 |
49 |
50 | packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll
51 |
52 |
53 | packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll
54 |
55 |
56 | packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll
57 |
58 |
59 | packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | {8d56ab30-6e10-4d66-b10e-0dcc4a0f4051}
88 | R3.Core
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/TilingBot/TilingBot.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32210.238
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TilingBot", "TilingBot.csproj", "{C83DDAF0-AD95-4D0C-BDD8-27AAF2991239}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "R3.Core", "..\R3.Core\R3.Core.csproj", "{8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {C83DDAF0-AD95-4D0C-BDD8-27AAF2991239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {C83DDAF0-AD95-4D0C-BDD8-27AAF2991239}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {C83DDAF0-AD95-4D0C-BDD8-27AAF2991239}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {C83DDAF0-AD95-4D0C-BDD8-27AAF2991239}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {C79FE67D-26A9-42D3-A857-9F022FBB1E72}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/TilingBot/Util.cs:
--------------------------------------------------------------------------------
1 | namespace TilingBot
2 | {
3 | using Meta.Numerics.Functions;
4 | using R3.Geometry;
5 | using R3.Math;
6 | using System.Linq;
7 | using System.Numerics;
8 | using Math = System.Math;
9 |
10 | public class Util
11 | {
12 | // Normalizing barycentric coords amounts to making sure the 4 coords add to 1.
13 | public static Vector3D BaryNormalize( Vector3D b )
14 | {
15 | return b / (b.X + b.Y + b.Z);
16 | }
17 |
18 | // Bary Coords to Euclidean
19 | public static Vector3D BaryToEuclidean( Vector3D[] kv, Vector3D b )
20 | {
21 | Vector3D result =
22 | kv[0] * b.X + kv[1] * b.Y + kv[2] * b.Z;
23 | return result;
24 | }
25 |
26 | // So that we can leverage Euclidean barycentric coordinates, we will first convert our simplex to the Klein model.
27 | // We will need to take care to properly convert back to the Ball as needed.
28 | public static Vector3D[] KleinVerts( Geometry g, Vector3D[] conformalVerts )
29 | {
30 | return conformalVerts.Select( v =>
31 | {
32 | switch( g )
33 | {
34 | case Geometry.Spherical:
35 | return SphericalModels.StereoToGnomonic( v );
36 | case Geometry.Euclidean:
37 | return v;
38 | case Geometry.Hyperbolic:
39 | return HyperbolicModels.PoincareToKlein( v );
40 | }
41 |
42 | throw new System.ArgumentException();
43 | } ).ToArray();
44 | }
45 |
46 | public static Vector3D ToConformal( Geometry g, Vector3D[] kv, Vector3D b )
47 | {
48 | Vector3D klein = Util.BaryToEuclidean( kv, b );
49 | switch( g )
50 | {
51 | case Geometry.Spherical:
52 | return SphericalModels.GnomonicToStereo( klein );
53 | case Geometry.Euclidean:
54 | return klein;
55 | case Geometry.Hyperbolic:
56 | return HyperbolicModels.KleinToPoincare( klein );
57 | }
58 |
59 | throw new System.ArgumentException();
60 | }
61 |
62 | public static double Interp( double frac, double start, double end )
63 | {
64 | return start + ( end - start ) * frac;
65 | }
66 |
67 | public static double Smoothed( double frac, double start, double end )
68 | {
69 | double newFrac = Smoothed( frac, 1.0 );
70 | return Interp( newFrac, start, end );
71 | }
72 |
73 | public static double Smoothed( double frac, double max )
74 | {
75 | return (max / 2.0) * (-Math.Cos( Math.PI * frac ) + 1);
76 | }
77 |
78 | ///
79 | /// https://paramanands.blogspot.com/2011/01/elliptic-functions-complex-variables.html#.W14TVtJKiUl
80 | /// equation 14.
81 | ///
82 | public static Complex JacobiCn( Complex u, double k )
83 | {
84 | // k prime
85 | double k_ = Math.Sqrt( 1 - k * k );
86 |
87 | double cnx = AdvancedMath.JacobiCn( u.Real, k );
88 | double snx = AdvancedMath.JacobiSn( u.Real, k );
89 | double dnx = AdvancedMath.JacobiDn( u.Real, k );
90 | double cny = AdvancedMath.JacobiCn( u.Imaginary, k_ );
91 | double sny = AdvancedMath.JacobiSn( u.Imaginary, k_ );
92 | double dny = AdvancedMath.JacobiDn( u.Imaginary, k_ );
93 |
94 | double denom = cny * cny + k * k * snx * snx * sny * sny;
95 | double real = cnx * cny / denom;
96 | double imag = -snx * dnx * sny * dny / denom;
97 |
98 | return new Complex( real, imag );
99 | }
100 |
101 | ///
102 | /// See this paper: http://archive.bridgesmathart.org/2016/bridges2016-179.pdf
103 | /// This logically belongs in HyperbolicModels.cs, but I didn't want the dependency on Meta.Numerics in R3.Core.
104 | ///
105 | public static Vector3D SquareToPoincare( Vector3D s )
106 | {
107 | double K_e = AdvancedMath.EllipticF( Math.PI / 2, 1.0 / Math.Sqrt( 2 ) );
108 | Complex a = new Complex( 1, -1 ) / Math.Sqrt( 2 );
109 | Complex b = new Complex( 1, 1 ) / 2;
110 | Complex z = s;
111 |
112 | Complex result = a * JacobiCn( K_e * ( b * z - 1 ), 1 / Math.Sqrt( 2 ) );
113 | return Vector3D.FromComplex( result );
114 | }
115 |
116 | public static Vector3D PeirceToStereo( Vector3D p )
117 | {
118 | return SquareToPoincare( p );
119 | }
120 |
121 | //
122 | // Hyperbolic utility functions.
123 | // ZZZ - Move to shared location.
124 | //
125 |
126 | public static double LorentzDot( Vector3D v1, Vector3D v2 )
127 | {
128 | return -( v1.X * v2.X + v1.Y * v2.Y - v1.Z * v2.Z );
129 | }
130 |
131 | public static double DotInGeometry( Geometry g, Vector3D v1, Vector3D v2 )
132 | {
133 | if( g == Geometry.Hyperbolic )
134 | return LorentzDot( v1, v2 );
135 |
136 | return v1.Dot( v2 );
137 | }
138 |
139 | public static void NormalizeInGeometry( Geometry g, ref Vector3D v )
140 | {
141 | switch( g )
142 | {
143 | case Geometry.Spherical:
144 | v.Normalize();
145 | break;
146 | case Geometry.Euclidean:
147 | throw new System.NotImplementedException();
148 | case Geometry.Hyperbolic:
149 | Sterographic.NormalizeToHyperboloid( ref v );
150 | break;
151 | }
152 | }
153 |
154 | public static Vector3D Centroid( Geometry g, Vector3D[] conformalVerts )
155 | {
156 | if( g == Geometry.Euclidean )
157 | {
158 | Vector3D result = new Vector3D();
159 | foreach( Vector3D v in conformalVerts )
160 | result += v;
161 | return result / conformalVerts.Length;
162 | }
163 |
164 | Vector3D[] verts = conformalVerts.Select( v =>
165 | {
166 | switch( g )
167 | {
168 | case Geometry.Spherical:
169 | return Sterographic.PlaneToSphereSafe( v );
170 | case Geometry.Hyperbolic:
171 | return Sterographic.PlaneToHyperboloid( v );
172 | }
173 |
174 | throw new System.ArgumentException();
175 | } ).ToArray();
176 |
177 | // https://math.stackexchange.com/a/2173370/300001
178 | Vector3D sum = new Vector3D();
179 | for( int i = 0; i < verts.Length; i++ )
180 | sum += verts[i];
181 | Vector3D centroid = sum / Math.Sqrt( DotInGeometry( g, sum, sum ) );
182 | NormalizeInGeometry( g, ref centroid );
183 |
184 | switch( g )
185 | {
186 | case Geometry.Spherical:
187 | return Sterographic.SphereToPlane( centroid );
188 | case Geometry.Hyperbolic:
189 | return Sterographic.HyperboloidToPlane( centroid );
190 | }
191 |
192 | throw new System.ArgumentException();
193 | }
194 |
195 | public static Vector3D EdgeToPlane( Geometry g, H3.Cell.Edge edge )
196 | {
197 | if( g != Geometry.Hyperbolic )
198 | throw new System.NotImplementedException();
199 |
200 | Vector3D b1, b2;
201 | H3Models.Ball.GeodesicIdealEndpoints( edge.Start, edge.End, out b1, out b2 );
202 | if( ( ( b2 + b1 ) / 2 ).IsOrigin )
203 | {
204 | Vector3D lineNormal = b2 - b1;
205 | lineNormal.RotateXY( Math.PI / 2 );
206 | lineNormal.Normalize();
207 | return new Vector3D( lineNormal.X, lineNormal.Y, lineNormal.Z, 0 );
208 | }
209 |
210 | Vector3D center, normal;
211 | double radius, angleTot;
212 | H3Models.Ball.Geodesic( edge.Start, edge.End, out center, out radius, out normal, out angleTot );
213 |
214 | Vector3D closest = H3Models.Ball.ClosestToOrigin( new Circle3D() { Center = center, Radius = radius, Normal = normal } );
215 | Vector3D closestKlein = HyperbolicModels.PoincareToKlein( closest );
216 | center.Normalize();
217 | return new Vector3D( center.X, center.Y, center.Z, closestKlein.Abs() );
218 | }
219 |
220 | // In hyperboloid model
221 | public static Vector3D PlaneDualPoint( Geometry g, Vector3D planeKlein )
222 | {
223 | if( g != Geometry.Hyperbolic )
224 | throw new System.NotImplementedException();
225 |
226 | if( Math.Abs( planeKlein.W ) < 1e-7 )
227 | {
228 | return new Vector3D( planeKlein.X, planeKlein.Y, 0.0 );
229 | }
230 |
231 | double inv = 1.0 / planeKlein.W;
232 | //Vector3D dual = new Vector3D( planeKlein.X * inv, planeKlein.Y * inv, planeKlein.Z * inv, 1.0 );
233 | Vector3D dual = new Vector3D( planeKlein.X * inv, planeKlein.Y * inv, 1.0 ); // Turn into 2D, would be nice to be more general.
234 |
235 | //NormalizeInGeometry( g, ref dual );
236 | // Ugh, sign convention of function above messing me up.
237 | System.Func lorentzNormalize = v =>
238 | {
239 | double normSquared = Math.Abs( LorentzDot( v, v ) );
240 | double norm = Math.Sqrt( normSquared );
241 | v /= norm;
242 | return v;
243 | };
244 |
245 | dual = lorentzNormalize( dual );
246 |
247 | return dual;
248 | }
249 |
250 | // In hyperboloid model
251 | public static double GeodesicPlaneHSDF( Vector3D samplePoint, Vector3D dualPoint, double offset = 0 )
252 | {
253 | double dot = -DotInGeometry( Geometry.Hyperbolic, samplePoint, dualPoint );
254 | return DonHatch.asinh( dot ) - offset;
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/TilingBot/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/things to work on.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/roice3/TilingBot/1b4d13fcdc16c7c59b49fc9faea8aea3706ced2b/things to work on.txt
--------------------------------------------------------------------------------