├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── code ├── HyperbolicModels │ ├── Catacombs.cs │ ├── Coloring.cs │ ├── CoxeterImages.cs │ ├── Experiments │ │ ├── Acrohedron.cs │ │ ├── H3_Hopf.cs │ │ ├── H3_Ruled.cs │ │ ├── HoneycombCircles.cs │ │ ├── Lawson.cs │ │ ├── S3_Hopf.cs │ │ ├── S3_Minimal.cs │ │ ├── Sandbox.cs │ │ ├── SphericalTrig.cs │ │ ├── ThreeFifty.cs │ │ └── Tree.cs │ ├── HoneycombGen.cs │ ├── HoneycombGen_old.cs │ ├── HoneycombPaper.cs │ ├── HyperbolicModels.csproj │ ├── HyperbolicModels.csproj.user │ ├── HyperbolicModels.sln │ ├── Nonuniform.cs │ ├── PointGroups.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Recurse.cs │ ├── Sections.cs │ ├── Settings.cs │ ├── Simplex.cs │ ├── StlGen.cs │ ├── Utils │ │ ├── DataContractHelper.cs │ │ └── Util.cs │ ├── ViewPath.cs │ ├── app.config │ ├── packages.config │ └── sample_settings │ │ ├── settings_PovRay.xml │ │ └── settings_UHS.xml └── R3 │ └── 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 └── povray └── wikipedia ├── H3_paracompact_batch.pov ├── H3_paracompact_equi.pov └── H3_sample.pov /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | code/HyperbolicModels/.vs 46 | code/HyperbolicModels/*.suo 47 | code/HyperbolicModels/packages/ 48 | code/HyperbolicModels/bin 49 | code/HyperbolicModels/obj 50 | code/R3/R3.Core/bin 51 | code/R3/R3.Core/obj 52 | *.suo 53 | *.vsp 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Honeycombs 2 | Code for visualizing spherical, euclidean, and hyperbolic honeycombs in various ways: 3 | * Upper half space boundary images for hyperideal hyperbolic honeycombs 4 | * POV-Ray definition files for regular/uniform honeycombs 5 | * STL models for Shapeways 6 | 7 | This code is a companion to the paper "Visualizing Hyperbolic Honeycombs" by Roice Nelson and Henry Segerman (http://arxiv.org/abs/1511.02851). The code itself was authored by Roice. Images generated by this software are available for bulk download at www.hyperbolichoneycombs.org. 8 | 9 | ## How to generate UHS boundary images 10 | Run the latest released Windows binary, and by default it will generate a 1200x1200 boundary image of the {3,3,7} honeycomb. 11 | 12 | There is an editable "settings_UHS.xml" file in the same directory which can be used to change the honeycomb and some other settings (image size and bounds). I attempted to make the format self-explanatory. I can add support for more settings (say coloring, transformations, etc.) if there is interest. 13 | 14 | ## How to generate POV-Ray images 15 | Run the latest released Windows binary, and by default it will generate a large (~250MB) POV-Ray definition file for the 4333-0001 uniform honeycomb, composed of octahedra and tetrahedra, see: https://en.wikipedia.org/wiki/Hyperbolic_tetrahedral-octahedral_honeycomb. You will then be able to render the resulting geometry by running the H3_sample.pov file in the free ray tracing program POV-Ray (www.povray.org). 16 | 17 | There is an editable "settings_PovRay.xml" file in the same directory which can be used to change the honeycomb and some other settings. Again, I attempted to make the format self-explanatory and can add support for more settings if there is interest. 18 | 19 | I used this example because it was requested specifically, but it also demonstrates how to configure all 6 angles of the fundamental simplex for honeycombs with loop and branched Coxeter graphs. However, this "Goursat tetrahedron" feature only works for honeycombs with finite vertices. For linear Coxeter graphs, you can also specify just 3 numbers for the dihedral angles as in the UHS sample above. Linear graphs will work for paracompact honeycombs as well. They even work for some hyperideal honeycombs, but not all yet, so venture into that world at your own risk. Note that the 4333-0100, 4333-0010, and 4333-0001 are all the same honeycomb, just from different viewing perspectives. 20 | 21 | A final note: 22 | Resolution of the edges is something I always seem to have to tweak (POV-Ray's sphere_sweep primitive is finicky), and I'm still trying to decide the best way to expose this. 23 | 24 | ## How to generate STL models for Shapeways 25 | _Abandon all hope, ye who enter here._ I'm not planning to make this friendly, so for now folks will be stuck digging into the code. 26 | 27 | ## A quick pointer to some of the important functions for working with H^3 28 | I wrote up the following doc for someone who had questions on the code base. Hope it can be helpful for others. 29 | 30 | https://docs.google.com/document/d/1vUQPHvO4zOy5S1fyQIWMcy7vyzTvu5OYrUI6jmjpG90/edit?usp=sharing 31 | 32 | ## Dependencies 33 | MathNet.Numerics (though this dependency could be easily removed). 34 | 35 | ## Disclaimer 36 | Some areas are an overgrown garden in need of weeding, i.e. refactoring/reorganization, but I still wanted to publish it along with our paper for any who might like to try to recreate calculations we've done. There is no UI, and I made use of commenting in some files to control particular outputs I was going for. Furthermore, the R3.Core library contains code not related to honeycombs - that library was mainly developed as part of the MagicTile project, but I haven't cleaned out irrelevant R3.Core code here. 37 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Coloring.cs: -------------------------------------------------------------------------------- 1 | namespace R3.Drawing 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Drawing; 6 | using System.Linq; 7 | using R3.Geometry; 8 | 9 | internal static class Coloring 10 | { 11 | /// 12 | /// This will calculate the color along a hexagon on the edges of an RGB cube. 13 | /// incrementsUntilRepeat is the value where we return to the starting point of the hexagon (white). 14 | /// increments is used as the distance-along-hexagon parameter. 15 | /// 16 | public static Color ColorAlongHexagon( int incrementsUntilRepeat, double increments ) 17 | { 18 | //if( 0 == increments ) 19 | // return Color.FromArgb( 255, 187, 23, 23 ); 20 | //return Color.FromArgb( 0, 255, 255, 255 ); 21 | 22 | //int temp = (increments - 2) * 125 + 80; 23 | /*int temp = increments * 40; 24 | if( temp < 0 ) 25 | temp = 0; 26 | if( temp > 255 ) 27 | temp = 255; 28 | Color gray = Color.FromArgb( 255, temp, temp, temp ); 29 | Color c = increments > 2 ? Color.White : gray; 30 | return c;*/ 31 | 32 | //464 33 | //increments = (int)( Math.Pow( (double)increments, 1.35 ) ); 34 | 35 | // Bring to main hexagon (handle looping) 36 | increments = increments % incrementsUntilRepeat; 37 | 38 | // 0 to 6, so we can have each edge of the hexagon live in a unit interval. 39 | double distAlongHex = increments * 6 / incrementsUntilRepeat; 40 | 41 | // 464 42 | // Give the first 43 | //double percentage = (double)increments / incrementsUntilRepeat; 44 | // Sigmoid! http://en.wikipedia.org/wiki/Sigmoid_function 45 | //distAlongHex = ( 1 / ( 1 + Math.Exp( -percentage ) ) ) ) * 6; 46 | //distAlongHex = percentage * 6; 47 | 48 | Func subtractive = d => (int)( 255.0 * ( 1.0 - d ) ); 49 | Func addative = d => (int)( 255.0 * d ); 50 | 51 | bool blue = true; 52 | if( blue ) 53 | { 54 | if( distAlongHex < 1 ) 55 | return Color.FromArgb( 255, subtractive( distAlongHex ), 255, 255 ); 56 | distAlongHex--; 57 | if( distAlongHex < 1 ) 58 | return Color.FromArgb( 255, 0, subtractive( distAlongHex ), 255 ); 59 | distAlongHex--; 60 | if( distAlongHex < 1 ) 61 | return Color.FromArgb( 255, 0, 0, subtractive( distAlongHex ) ); 62 | distAlongHex--; 63 | if( distAlongHex < 1 ) 64 | return Color.FromArgb( 255, addative( distAlongHex ), 0, 0 ); 65 | distAlongHex--; 66 | if( distAlongHex < 1 ) 67 | return Color.FromArgb( 255, 255, addative( distAlongHex ), 0 ); 68 | distAlongHex--; 69 | if( distAlongHex < 1 ) 70 | return Color.FromArgb( 255, 255, 255, addative( distAlongHex ) ); 71 | } 72 | else 73 | { 74 | if( distAlongHex < 1 ) 75 | return Color.FromArgb( 255, 255, 255, subtractive( distAlongHex ) ); 76 | distAlongHex--; 77 | if( distAlongHex < 1 ) 78 | return Color.FromArgb( 255, 255, subtractive( distAlongHex ), 0 ); 79 | distAlongHex--; 80 | if( distAlongHex < 1 ) 81 | return Color.FromArgb( 255, subtractive( distAlongHex ), 0, 0 ); 82 | distAlongHex--; 83 | if( distAlongHex < 1 ) 84 | return Color.FromArgb( 255, 0, 0, addative( distAlongHex ) ); 85 | distAlongHex--; 86 | if( distAlongHex < 1 ) 87 | return Color.FromArgb( 255, 0, addative( distAlongHex ), 255 ); 88 | distAlongHex--; 89 | if( distAlongHex < 1 ) 90 | return Color.FromArgb( 255, addative( distAlongHex ), 255, 255 ); 91 | } 92 | 93 | throw new System.Exception( "Bad impl" ); 94 | } 95 | 96 | /// 97 | /// This method allows calculating a color based on a color scaling input. 98 | /// This was the way I did this for a long time and for many images, until working with Henry on coloring. 99 | /// Keeping this code here for reference. 100 | /// 101 | public static Color DepthColor( int depth, double colorScaling, bool invert = false ) 102 | { 103 | int scaling = 50; // 50 good. // XXX - make a setting. 104 | scaling = 150; 105 | //scaling = 130; 106 | //scaling = 60; 107 | scaling = (int)colorScaling; 108 | 109 | /* tuned 733 110 | int mag = 0; 111 | switch( depth ) 112 | { 113 | case 1: mag = 150; break; 114 | case 2: mag = 350; break; 115 | case 3: mag = 465; break; 116 | case 4: mag = 400; break; 117 | case 5: mag = 200; break; 118 | case 6: mag = 100; break; 119 | } */ 120 | 121 | int wraps = 0; 122 | int mag = depth * scaling; 123 | //mag = (int)( System.Math.Pow( depth, colorScaling ) * scaling ); 124 | int c1 = mag, c2 = 0, c3 = 0; 125 | while( mag > 0 ) // Comment this line out for no color wrapping. 126 | { 127 | c1 = mag; c2 = c3 = 0; 128 | if( c1 > 255 ) 129 | { 130 | c1 = 255; 131 | mag -= 255; 132 | c2 = mag; 133 | } 134 | if( c2 > 255 ) 135 | { 136 | c2 = 255; 137 | mag -= 255; 138 | c3 = mag; 139 | } 140 | if( c3 > 255 ) 141 | { 142 | c3 = 255; 143 | wraps++; 144 | } 145 | 146 | mag -= 255; 147 | } 148 | 149 | System.Func min75 = i => 255 - i < 75 ? 75 : 255 - i; 150 | System.Func max180 = i => i > 180 ? 180 : i; 151 | 152 | Color c = Color.White; 153 | 154 | //Color c = Color.FromArgb( 255, min75( c3 ), 255 - c1, 255 - c2 ); // purple red 155 | //Color c = Color.FromArgb( 255, min75( c3 ), 255 - c2, 255 - c1 ); // *yellow->red 156 | 157 | //Color c = Color.FromArgb( 255, 255 - c1, min75( c3 ), 255 - c2 ); // blue then green 158 | //Color c = Color.FromArgb( 255, 255 - c2, min75( c3 ), 255 - c1 ); // green->yellow 159 | 160 | int modulo = wraps % 2; 161 | switch( modulo ) 162 | { 163 | case 0: 164 | c = Color.FromArgb( 255, 255 - c1, 255 - c2, min75( c3 ) ); // *blue 165 | break; 166 | case 1: 167 | c = Color.FromArgb( 255, c1, c2, max180( c3 ) ); 168 | break; 169 | case 2: 170 | c = Color.FromArgb( 255, 255 - c2, min75( c3 ), 255 - c1 ); // green->yellow 171 | break; 172 | case 3: 173 | c = Color.FromArgb( 255, min75( c3 ), 255 - c2, 255 - c1 ); // *yellow->red 174 | break; 175 | } 176 | 177 | // c = Color.FromArgb( 255, 255 - c1, 255 - c2, min75( c3 ) ); // *blue 178 | //Color c = Color.FromArgb( 255, 255 - c1, 255 - c1, min75( c1 ) ); // *gray with tint of blue 179 | //Color c = Color.FromArgb( 255, min75( c1 ), 255 - c1, 255 - c1 ); 180 | 181 | //Color c = Color.FromArgb( 255, 255 - c1, 255 - c2, 255 - c3 ); 182 | //c = depth >= 4 ? Color.White : c; 183 | 184 | /* 185 | int temp = (depth-3) * 40; 186 | if( temp < 0 ) temp = 0; 187 | if( temp > 255 ) temp = 255; 188 | Color gray = Color.FromArgb( 255, temp, temp, temp ); 189 | c = depth < 3 ? Color.Black : gray; 190 | */ 191 | 192 | if( invert ) 193 | return Inverse( c ); 194 | 195 | return c; 196 | } 197 | 198 | public static Color Inverse( Color c ) 199 | { 200 | return Color.FromArgb( 255, 255 - c.R, 255 - c.G, 255 - c.B ); 201 | } 202 | 203 | public static Vector3D ToVec( Color c ) 204 | { 205 | return new Vector3D( (double)c.R / 255, (double)c.G / 255, (double)c.B / 255 ); 206 | } 207 | 208 | public static Color AvgColor( List colors ) 209 | { 210 | //if( colors.Contains( Color.White ) ) 211 | // return Color.White; 212 | 213 | int a = (int)colors.Select( c => (double)c.A ).Average(); 214 | int r = (int)colors.Select( c => (double)c.R ).Average(); 215 | int g = (int)colors.Select( c => (double)c.G ).Average(); 216 | int b = (int)colors.Select( c => (double)c.B ).Average(); 217 | return Color.FromArgb( a, r, g, b ); 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Experiments/H3_Ruled.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using R3.Core; 4 | using R3.Geometry; 5 | using R3.Math; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Numerics; 9 | 10 | using Math = System.Math; 11 | 12 | public class H3Ruled 13 | { 14 | public void GenPovRay() 15 | { 16 | //H3.Cell.Edge[] fibers = Helicoid(); 17 | H3.Cell.Edge[] fibers = Hyperboloid(); 18 | PovRay.WriteH3Edges( new PovRay.Parameters { AngularThickness = 0.015 }, fibers, "ruled.pov" ); 19 | } 20 | 21 | public H3.Cell.Edge[] Hyperboloid() 22 | { 23 | // Draw to circles of fibers, then twist them. 24 | List fiberList = new List(); 25 | 26 | Vector3D cen = new Vector3D( 0, 0, 0.5 ); 27 | double rad = .3; 28 | Circle3D c1 = new Circle3D { Center = cen, Radius = rad }; 29 | Circle3D c2 = new Circle3D { Center = -cen, Radius = rad }; 30 | 31 | int n = 50; 32 | Vector3D[] points1 = c1.Subdivide( n ); 33 | Vector3D[] points2 = c2.Subdivide( n ); 34 | 35 | double twist = 2 * Math.PI / 3; 36 | for( int i = 0; i < points2.Length; i++ ) 37 | { 38 | points2[i].RotateXY( twist ); 39 | 40 | Vector3D e1, e2; 41 | H3Models.Ball.GeodesicIdealEndpoints( points1[i], points2[i], out e1, out e2 ); 42 | 43 | e1 = Transform( e1 ); 44 | e2 = Transform( e2 ); 45 | 46 | fiberList.Add( new H3.Cell.Edge( e1, e2 ) ); 47 | } 48 | 49 | return fiberList.ToArray(); 50 | } 51 | 52 | public H3.Cell.Edge[] Helicoid() 53 | { 54 | List fiberList = new List(); 55 | 56 | // These two params affect each other (changing numFibers will affect rotation rate). 57 | double rotationRate = Math.PI / 78.5; 58 | int numFibers = 1000; 59 | 60 | // Note: we need to increment a constant hyperbolic distance each step. 61 | int count = 0; 62 | double max = DonHatch.e2hNorm( 0.998 ); 63 | double offset = max * 2 / (numFibers - 1); 64 | for( double z_h = -max; z_h <= max; z_h += offset ) 65 | { 66 | double z = DonHatch.h2eNorm( z_h ); 67 | 68 | Sphere s = H3Models.Ball.OrthogonalSphereInterior( new Vector3D( 0, 0, z ) ); 69 | Circle3D c = H3Models.Ball.IdealCircle( s ); 70 | 71 | // Two endpoints of our fiber. 72 | Vector3D v1 = new Vector3D( c.Radius, 0, c.Center.Z ); 73 | Vector3D v2 = new Vector3D( -c.Radius, 0, c.Center.Z ); 74 | 75 | v1.RotateXY( rotationRate * count ); 76 | v2.RotateXY( rotationRate * count ); 77 | 78 | v1 = Transform( v1 ); 79 | v2 = Transform( v2 ); 80 | 81 | Vector3D t = Transform( new Vector3D( 0, 0, z ) ); 82 | double cutoff = 0.995; 83 | if( t.Abs() > cutoff ) 84 | continue; 85 | 86 | fiberList.Add( new H3.Cell.Edge( v1, v2, order: false ) ); 87 | count++; 88 | } 89 | 90 | return fiberList.ToArray(); 91 | } 92 | 93 | public static Vector3D Transform( Vector3D v ) 94 | { 95 | double angle = Math.PI / 3; // Kind of weird, and not really controllable. 96 | v.RotateAboutAxis( new Vector3D( 1, 0 ), angle ); 97 | 98 | Mobius m = new Mobius(); 99 | m.Isometry( Geometry.Hyperbolic, 0, new Complex( 0, 0.5 ) ); 100 | v = H3Models.TransformHelper( v, m ); 101 | 102 | v.RotateAboutAxis( new Vector3D( 1, 0 ), -angle ); 103 | return v; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Experiments/Lawson.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using R3.Geometry; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Math = System.Math; 7 | using R3.Core; 8 | 9 | public class Lawson 10 | { 11 | public Lawson( int m, int k ) 12 | { 13 | m_m = m; 14 | m_k = k; 15 | } 16 | 17 | private int m_m; 18 | private int m_k; 19 | 20 | public void Gen() 21 | { 22 | Vector3D[] verts = Verts( m_m, m_k ); 23 | Sphere[] tet = Tetrahedron( verts ); 24 | Quad quad = new Quad() { Verts = verts }; 25 | 26 | // We need to avoid infinities. 27 | //tet = tet.Select( s => H3Models.UHSToBall( s ) ).ToArray(); 28 | //for( int i=0; i edges = new List(); 36 | foreach( Quad q in quads ) 37 | { 38 | q.R3toS3(); 39 | edges.AddRange( q.GenEdges() ); 40 | } 41 | 42 | string filename = string.Format( "lawson_{0}_{1}.pov", m_m, m_k ); 43 | PovRay.WriteEdges( new PovRay.Parameters() { AngularThickness = 0.01 }, 44 | Geometry.Spherical, edges.ToArray(), filename, append: false ); 45 | } 46 | 47 | private class Quad 48 | { 49 | public Vector3D[] Verts; 50 | 51 | public Vector3D ID 52 | { 53 | get 54 | { 55 | if( Verts == null || Verts.Length != 4 ) 56 | throw new System.Exception( "Quad not initialized." ); 57 | 58 | Vector3D ID = new Vector3D(); 59 | for( int i = 0; i < Verts.Length; i++ ) 60 | { 61 | Vector3D vert = Verts[i]; 62 | if( Infinity.IsInfinite( vert ) ) 63 | ID += new Vector3D( 100, 100, 100 ); 64 | else 65 | ID += vert; 66 | } 67 | return ID; 68 | } 69 | } 70 | 71 | public Quad Clone() 72 | { 73 | Quad newQuad = new Quad(); 74 | newQuad.Verts = (Vector3D[])Verts.Clone(); 75 | return newQuad; 76 | } 77 | 78 | public void R3toS3() 79 | { 80 | for( int i = 0; i < Verts.Length; i++ ) 81 | Verts[i] = Sterographic.R3toS3( Verts[i] ); 82 | } 83 | 84 | public H3.Cell.Edge[] GenEdges() 85 | { 86 | List result = new List(); 87 | result.AddRange( GenEdgesInternal( Verts[0], Verts[1], Verts[3], Verts[2] ) ); // tricky to figure out indices 88 | result.AddRange( GenEdgesInternal( Verts[0], Verts[3], Verts[1], Verts[2] ) ); 89 | return result.ToArray(); 90 | } 91 | 92 | private H3.Cell.Edge[] GenEdgesInternal( Vector3D v1, Vector3D v2, Vector3D v3, Vector3D v4 ) 93 | { 94 | int div = 5; 95 | List result = new List(); 96 | for( int i = 0; i <= div; i++ ) 97 | { 98 | Vector3D start = v1 + ( v2 - v1 ) * i / div; 99 | Vector3D end = v3 + ( v4 - v3 ) * i / div; 100 | start.Normalize(); 101 | end.Normalize(); 102 | 103 | start = Sterographic.S3toR3( start ); 104 | end = Sterographic.S3toR3( end ); 105 | 106 | if( Infinity.IsInfinite( start ) ) 107 | start = end * 10; 108 | if( Infinity.IsInfinite( end ) ) 109 | end = start * 10; 110 | 111 | result.Add( new H3.Cell.Edge( start, end ) ); 112 | } 113 | return result.ToArray(); 114 | } 115 | } 116 | 117 | /// 118 | /// Calculate the 4 points defining the fundamental geodesic quadrilateral. 119 | /// 120 | private static Vector3D[] Verts( int m, int k ) 121 | { 122 | double dist1 = Math.PI / ( m + 1 ); 123 | double dist2 = Math.PI / ( k + 1 ); 124 | 125 | Vector3D p4 = new Vector3D( 1, 0 ); 126 | p4.RotateXY( dist2 ); 127 | 128 | return new Vector3D[] 129 | { 130 | new Vector3D(), 131 | new Vector3D( 1, 0 ), 132 | new Vector3D( 0, 0, Spherical2D.s2eNorm( dist1 ) ), 133 | p4 134 | }; 135 | } 136 | 137 | /// 138 | /// Calculate the surfaces of the tetrahedron for the quadrilateral 139 | /// 140 | private static Sphere[] Tetrahedron( Vector3D[] quad ) 141 | { 142 | // NOTE: For planes, the convention is that the normal vector points "outward" 143 | 144 | // The only non-planar sphere is incident with the last two points of the quad, 145 | // as well as the antipode of the last point. Fig 1 in Lawson's paper. 146 | Circle3D c = new Circle3D( quad[3], quad[2], -quad[3] ); 147 | 148 | Vector3D plane3 = quad[3]; 149 | plane3.RotateXY( Math.PI / 2 ); 150 | 151 | return new Sphere[] 152 | { 153 | Sphere.Plane( new Vector3D( 0, -1 ) ), 154 | Sphere.Plane( new Vector3D( 0, 0, -1 ) ), 155 | Sphere.Plane( plane3 ), 156 | new Sphere( c.Center, c.Radius ) 157 | }; 158 | } 159 | 160 | private static Quad[] CalcQuads( Sphere[] mirrors, Quad start ) 161 | { 162 | List allQuads = new List(); 163 | allQuads.Add( start ); 164 | HashSet completed = new HashSet( new Vector3D[] { start.ID } ); 165 | ReflectEdgesRecursive( mirrors, new Quad[] { start }, allQuads, completed ); 166 | return allQuads.ToArray(); 167 | } 168 | 169 | private static void ReflectEdgesRecursive( Sphere[] mirrors, Quad[] quads, 170 | List allQuads, HashSet completed ) 171 | { 172 | if( 0 == quads.Length ) 173 | return; 174 | 175 | List newQuads = new List(); 176 | 177 | foreach( Quad quad in quads ) 178 | //foreach( Sphere mirror in mirrors ) 179 | { 180 | Sphere mirror = mirrors[3]; 181 | Quad newQuad = quad.Clone(); 182 | 183 | for( int i = 0; i < newQuad.Verts.Length; i++ ) 184 | newQuad.Verts[i] = mirror.ReflectPoint( newQuad.Verts[i] ); 185 | 186 | if( completed.Add( newQuad.ID ) ) 187 | { 188 | // Haven't seen this yet, so 189 | // we'll need to recurse on it. 190 | allQuads.Add( newQuad ); 191 | newQuads.Add( newQuad ); 192 | } 193 | } 194 | 195 | //ReflectEdgesRecursive( mirrors, newQuads.ToArray(), allQuads, completed ); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Experiments/S3_Hopf.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using R3.Core; 4 | using R3.Geometry; 5 | using R3.Math; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using Math = System.Math; 10 | 11 | public static class S3_Hopf 12 | { 13 | 14 | public static void Experiment() 15 | { 16 | TilingConfig config = new TilingConfig( 4, 3 ); 17 | Tiling tiling = new Tiling(); 18 | tiling.Generate( config ); 19 | 20 | HashSet completed = new HashSet( new H3.Cell.EdgeEqualityComparer() ); 21 | 22 | string fileName = "hopf.pov"; 23 | using( StreamWriter sw = File.CreateText( fileName ) ) 24 | { 25 | Tile[] tiles = tiling.Tiles.ToArray(); 26 | //foreach( Tile t in tiling.Tiles ) 27 | foreach( Tile t in new Tile[] { tiles[0] } ) 28 | { 29 | foreach( Segment seg in t.Boundary.Segments ) 30 | { 31 | H3.Cell.Edge e = new H3.Cell.Edge( seg.P1, seg.P2 ); 32 | if( completed.Contains( e ) ) 33 | continue; 34 | 35 | HopfLink( sw, 36 | Sterographic.PlaneToSphereSafe( e.Start ), 37 | Sterographic.PlaneToSphereSafe( e.End ), anti: false ); 38 | completed.Add( e ); 39 | } 40 | } 41 | } 42 | } 43 | 44 | /// 45 | /// Hopf Link between two points on S^2. 46 | /// 47 | public static void HopfLink( StreamWriter sw, Vector3D s2_1, Vector3D s2_2, bool anti ) 48 | { 49 | Vector3D[] circlePoints; 50 | string circleString; 51 | circlePoints = OneHopfCircleProjected( s2_1, anti ); 52 | circleString = PovRay.EdgeSphereSweep( circlePoints, SizeFunc ); 53 | sw.WriteLine( circleString ); 54 | circlePoints = OneHopfCircleProjected( s2_2, anti ); 55 | circleString = PovRay.EdgeSphereSweep( circlePoints, SizeFunc ); 56 | sw.WriteLine( circleString ); 57 | 58 | Mesh mesh = new Mesh(); 59 | Vector3D[] interpolated = S3.GeodesicPoints( s2_1, s2_2 ); 60 | for( int i = 0; i < interpolated.Length - 1; i++ ) 61 | { 62 | Vector3D v1 = interpolated[i]; 63 | Vector3D v2 = interpolated[i + 1]; 64 | Vector3D[] p1 = OneHopfCircleProjected( v1, anti ); 65 | Vector3D[] p2 = OneHopfCircleProjected( v2, anti ); 66 | 67 | for( int j = 0; j < p1.Length-1; j++ ) 68 | { 69 | Mesh.Triangle t1 = new Mesh.Triangle( p1[j], p1[j + 1], p2[j] ); 70 | Mesh.Triangle t2 = new Mesh.Triangle( p2[j], p1[j + 1], p2[j + 1] ); 71 | mesh.Triangles.Add( t1 ); 72 | mesh.Triangles.Add( t2 ); 73 | } 74 | } 75 | 76 | PovRay.WriteMesh( sw, mesh, append: true ); 77 | } 78 | 79 | private static Vector3D[] OneHopfCircleProjected( Vector3D s2Point, bool anti ) 80 | { 81 | Vector3D[] circlePoints = S3.OneHopfCircle( s2Point, anti ); 82 | for( int i = 0; i < circlePoints.Length; i++ ) 83 | circlePoints[i] = Sterographic.S3toR3( circlePoints[i] ); 84 | return circlePoints; 85 | } 86 | 87 | private static Sphere SizeFunc( Vector3D v ) 88 | { 89 | Vector3D c; 90 | double r; 91 | H3Models.Ball.DupinCyclideSphere( v, .04 / 2, Geometry.Spherical, out c, out r ); 92 | return new Sphere() { Center = c, Radius = r }; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Experiments/Tree.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using R3.Core; 7 | using R3.Drawing; 8 | using R3.Geometry; 9 | 10 | internal class Tree 11 | { 12 | public static void Create( HoneycombDef def, string filename) 13 | { 14 | int p = def.P; 15 | int q = def.Q; 16 | int r = def.R; 17 | 18 | double scale = 5.0; 19 | Vector3D cen = HoneycombPaper.InteriorPointBall; 20 | 21 | Sphere[] simplex = SimplexCalcs.Mirrors( p, q, r, moveToBall: false ); 22 | 23 | // Apply transformations. 24 | simplex = simplex.Select( s => 25 | { 26 | Sphere.ScaleSphere( s, scale ); 27 | return H3Models.UHSToBall( s ); 28 | } ).ToArray(); 29 | 30 | for( int i = 0; i < 4; i++ ) 31 | if( simplex[i].IsPointInside( cen ) ) 32 | simplex[i].Invert = true; 33 | 34 | Sphere[] simplexForColorScale = SimplexCalcs.Mirrors( p, q, r, moveToBall: true ); 35 | CoxeterImages.Settings temp = HoneycombPaper.AutoCalcScale( def, simplexForColorScale ); 36 | int maxDepth = (int)temp.ColorScaling; 37 | 38 | bool ball = true; 39 | bool dual = false; 40 | H3.Cell[] simplicesFinal = HoneycombPaper.GenCell( simplex, null, cen, ball, dual ); 41 | 42 | simplicesFinal = simplicesFinal.Where( s => s.Depths[0] < 1 ).ToArray(); 43 | //simplicesFinal = simplicesFinal.Where( s => s.) 44 | 45 | // Output the facets. 46 | using( StreamWriter sw = File.CreateText( filename ) ) // We need to reuse this StreamWriter (vs. calling AppendSimplex) for performance. 47 | { 48 | sw.WriteLine( "#include \"hyper_ball.pov\"" ); 49 | int[] include = new int[] { 0 }; 50 | foreach( H3.Cell cell in simplicesFinal ) 51 | { 52 | Sphere[] facets = cell.Facets.Select( f => f.Sphere ).ToArray(); 53 | int depth = cell.Depths[0] + 1; 54 | Color c = Coloring.ColorAlongHexagon( maxDepth, depth ); 55 | PovRay.AddSimplex( sw, facets, cell.Center, include, filename, Coloring.ToVec( c ) ); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /code/HyperbolicModels/HyperbolicModels.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {85062372-5ABE-4C92-B39A-CA93B3AF7257} 9 | Exe 10 | Properties 11 | HyperbolicModels 12 | HyperbolicModels 13 | v4.8 14 | 15 | 16 | 512 17 | 18 | 19 | x86 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | x86 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | x64 41 | bin\x64\Debug\ 42 | 43 | 44 | x64 45 | bin\x64\Release\ 46 | 47 | 48 | 49 | False 50 | packages\MathNet.Numerics.Signed.3.13.1\lib\net40\MathNet.Numerics.dll 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 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051} 97 | R3.Core 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /code/HyperbolicModels/HyperbolicModels.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /code/HyperbolicModels/HyperbolicModels.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}") = "HyperbolicModels", "HyperbolicModels.csproj", "{85062372-5ABE-4C92-B39A-CA93B3AF7257}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "R3.Core", "..\R3\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 | Debug|Mixed Platforms = Debug|Mixed Platforms 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|Mixed Platforms = Release|Mixed Platforms 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Debug|Any CPU.ActiveCfg = Debug|x86 21 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 22 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Debug|Mixed Platforms.Build.0 = Debug|x64 23 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Debug|x86.ActiveCfg = Debug|x86 24 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Debug|x86.Build.0 = Debug|x86 25 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Release|Any CPU.ActiveCfg = Release|x86 26 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Release|Mixed Platforms.ActiveCfg = Release|x64 27 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Release|Mixed Platforms.Build.0 = Release|x64 28 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Release|x86.ActiveCfg = Release|x86 29 | {85062372-5ABE-4C92-B39A-CA93B3AF7257}.Release|x86.Build.0 = Release|x86 30 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 33 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|Mixed Platforms.Build.0 = Debug|x64 34 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|Mixed Platforms.ActiveCfg = Release|x64 38 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|Mixed Platforms.Build.0 = Release|x64 39 | {8D56AB30-6E10-4D66-B10E-0DCC4A0F4051}.Release|x86.ActiveCfg = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {3131E9EC-4ADC-4073-B732-855E0087380B} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Nonuniform.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using R3.Core; 7 | using R3.Geometry; 8 | 9 | internal class Nonuniform 10 | { 11 | /// 12 | /// Wendy's 77 13 | /// 14 | public static void Wendy( Simplex simplex, H3.Cell.Edge[] edges ) 15 | { 16 | H3.Cell startingCell = null; 17 | 18 | Vector3D start = startingCell.Verts.First(); 19 | 20 | Func findAntipode = input => 21 | { 22 | Vector3D antipode = new Vector3D(); 23 | double max = double.MinValue; 24 | foreach( Vector3D v in startingCell.Verts ) 25 | { 26 | double d = H3Models.Ball.HDist( v, input ); 27 | if( d > max ) 28 | { 29 | max = d; 30 | antipode = v; 31 | } 32 | } 33 | return antipode; 34 | }; 35 | 36 | H3.Cell.Edge[] diagonals = new H3.Cell.Edge[] { new H3.Cell.Edge( start, findAntipode( start ) ) }; 37 | diagonals = Recurse.CalcEdges( simplex.Facets, diagonals, new Recurse.Settings() { Threshold = 0.9983 } ); 38 | 39 | // diagonals includes too much at this point (it includes all icosahedra diagonals, but we only want one diagonal from each cell). 40 | // We need to begin at 4 start points, and branch out from each to find the ones we want. 41 | 42 | var vertsToDiagonals = FindConnectedEdges( diagonals ); 43 | var connectedEdges = FindConnectedEdges( edges ); 44 | 45 | // Get all edges (not diagonals) connected to start. 46 | List connectedToStart = connectedEdges[start]; 47 | Vector3D startOpp = connectedToStart[0].Opp( start ); 48 | List connectedToStartOpp = connectedEdges[startOpp]; 49 | 50 | // We need to pick 4 of these edges, arranged in a tetrahedron for our starting points. 51 | List startingPoints = new List(); 52 | 53 | List distances = new List(); 54 | 55 | // View1 56 | //startingPoints.Add( start ); 57 | //foreach( Vector3D v in connectedToStartOpp.Select( e => e.Opp( startOpp ) ) ) 58 | // distances.Add( H3Models.Ball.HDist( startingPoints.First(), v ) ); 59 | //startingPoints.Add( connectedToStartOpp[10].Opp( startOpp ) ); 60 | //startingPoints.Add( connectedToStartOpp[13].Opp( startOpp ) ); 61 | //startingPoints.Add( connectedToStartOpp[14].Opp( startOpp ) ); 62 | 63 | // View2 64 | startingPoints.Add( startOpp ); 65 | foreach( Vector3D v in connectedToStart.Select( e => e.Opp( start ) ) ) 66 | distances.Add( H3Models.Ball.HDist( startingPoints.First(), v ) ); 67 | startingPoints.Add( connectedToStart[10].Opp( start ) ); 68 | startingPoints.Add( connectedToStart[13].Opp( start ) ); 69 | startingPoints.Add( connectedToStart[14].Opp( start ) ); 70 | 71 | distances.Clear(); 72 | distances.Add( H3Models.Ball.HDist( startingPoints[1], startingPoints[2] ) ); 73 | distances.Add( H3Models.Ball.HDist( startingPoints[1], startingPoints[3] ) ); 74 | distances.Add( H3Models.Ball.HDist( startingPoints[2], startingPoints[3] ) ); 75 | distances.Add( H3Models.Ball.HDist( startingPoints[0], startingPoints[1] ) ); 76 | distances.Add( H3Models.Ball.HDist( startingPoints[0], startingPoints[2] ) ); 77 | distances.Add( H3Models.Ball.HDist( startingPoints[0], startingPoints[3] ) ); 78 | double dist = 3.097167; 79 | 80 | Func RemoveVerts = starting => 81 | { 82 | List keepers = new List(); 83 | HashSet removedVerts = new HashSet(); 84 | Recurse.BranchAlongVerts( starting.ToArray(), vertsToDiagonals, removedVerts ); 85 | foreach( H3.Cell.Edge e in edges ) 86 | { 87 | if( removedVerts.Contains( e.Start ) || removedVerts.Contains( e.End ) ) 88 | continue; 89 | keepers.Add( e ); 90 | } 91 | return keepers.ToArray(); 92 | }; 93 | 94 | edges = RemoveVerts( startingPoints.ToArray() ); 95 | 96 | bool done = false; 97 | while( !done ) 98 | { 99 | done = true; 100 | var newConnectedEdges = FindConnectedEdges( edges ); 101 | foreach( Vector3D v in newConnectedEdges.Keys ) 102 | { 103 | List oldEdgeList = connectedEdges[v]; 104 | List newEdgeList = newConnectedEdges[v]; 105 | 106 | // Only work edges that were full originally. 107 | if( oldEdgeList.Count != 20 ) 108 | continue; 109 | 110 | // We need at least two to find the rest. 111 | int newCount = newEdgeList.Count; 112 | if( newCount > 16 && newCount < 19 ) 113 | { 114 | List removed = oldEdgeList.Except( newEdgeList, new H3.Cell.EdgeEqualityComparer() ).ToList(); 115 | 116 | H3.Cell.Edge[] toTrim = newEdgeList.FindAll( e => 117 | { 118 | foreach( H3.Cell.Edge alreadyRemoved in removed ) 119 | { 120 | double d = H3Models.Ball.HDist( alreadyRemoved.Opp( v ), e.Opp( v ) ); 121 | if( !Tolerance.Equal( dist, d, 0.00001 ) ) 122 | return false; 123 | } 124 | 125 | return true; 126 | } ).ToArray(); 127 | 128 | edges = RemoveVerts( toTrim.Select( e => e.Opp( v ) ).ToArray() ); 129 | done = false; 130 | } 131 | 132 | if( newCount == 20 ) 133 | done = false; 134 | } 135 | } 136 | } 137 | 138 | private static Dictionary> FindConnectedEdges( H3.Cell.Edge[] edges ) 139 | { 140 | var result = new Dictionary>(); 141 | System.Action addOne = ( v, e ) => 142 | { 143 | List edgeList; 144 | if( !result.TryGetValue( v, out edgeList ) ) 145 | edgeList = new List(); 146 | 147 | edgeList.Add( e ); 148 | result[v] = edgeList; 149 | }; 150 | 151 | foreach( H3.Cell.Edge edge in edges ) 152 | { 153 | addOne( edge.Start, edge ); 154 | addOne( edge.End, edge ); 155 | } 156 | 157 | return result; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | class Program 8 | { 9 | /* 10 | Known problems: 11 | * Orthoscheme code may not be working out of the box for: 12 | - spherical honeycombs 13 | - honeycomb with hyperideal cells 14 | */ 15 | 16 | static void Main( string[] args ) 17 | { 18 | try 19 | { 20 | List filenames = new List(); 21 | if( args.Length > 0 && 22 | File.Exists( args[0] ) ) 23 | { 24 | filenames.Add( args[0] ); 25 | } 26 | else 27 | { 28 | filenames = Directory.EnumerateFiles( ".", "*.xml", SearchOption.TopDirectoryOnly ).ToList(); 29 | } 30 | 31 | // Go through any settings files. 32 | foreach( string filename in filenames ) 33 | { 34 | Settings settings = LoadSettings( filename ); 35 | if( settings == null ) 36 | continue; 37 | 38 | // Boundary images. 39 | if( settings.UhsBoundary != null ) 40 | { 41 | Log( "\nGenerating UHS boundary image for the following honeycomb:\n" + settings.HoneycombString ); 42 | Log( "\nSettings...\n" + settings.UhsBoundary.DisplayString ); 43 | HoneycombPaper.OneImage( settings ); 44 | } 45 | 46 | // POV-Ray definition files. 47 | if( settings.PovRay != null ) 48 | { 49 | Log( "\nGenerating POV-Ray definition file for the following honeycomb:\n" + settings.HoneycombString ); 50 | Log( "\nSettings...\n" + settings.PovRay.DisplayString ); 51 | 52 | if( settings.Angles.Length == 3 ) 53 | HoneycombGen.OneHoneycombOrthoscheme( settings ); 54 | else if( settings.Angles.Length == 6 ) 55 | HoneycombGen.OneHoneycombGoursat( settings ); 56 | } 57 | } 58 | } 59 | catch( System.Exception ex ) 60 | { 61 | Log( ex.Message + "\n" + ex.StackTrace ); 62 | } 63 | } 64 | 65 | public static Settings LoadSettings( string filename ) 66 | { 67 | //DataContractHelper.SaveToXml( Defaults, filename ); 68 | if( !File.Exists( filename ) ) 69 | return Defaults; 70 | 71 | try 72 | { 73 | return (Settings)DataContractHelper.LoadFromXml( typeof( Settings ), filename ); 74 | } 75 | catch( System.Exception e ) 76 | { 77 | Log( string.Format( "Failed to load settings from file '{0}', so skipping.\n{1}", e.Message ) ); 78 | return null; 79 | } 80 | } 81 | 82 | public static void Log( string message ) 83 | { 84 | System.Diagnostics.Trace.WriteLine( message ); 85 | System.Console.WriteLine( message ); 86 | } 87 | 88 | public static Settings Defaults 89 | { 90 | get 91 | { 92 | Settings settings = new Settings(); 93 | settings.Angles = new int[] { 3, 3, 7 }; 94 | settings.UhsBoundary = new UhsBoundarySettings() { Bounds = 1.0, ImageHeight = 1200, ImageWidth = 1200 }; 95 | settings.PovRay = new PovRaySettings() { Active = new int[] { 1, 0, 0, 0 }, NumEdges = (int)5e5, EdgeWidth = 0.02 }; 96 | return settings; 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /code/HyperbolicModels/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( "HyperbolicModels" )] 9 | [assembly: AssemblyDescription( "" )] 10 | [assembly: AssemblyConfiguration( "" )] 11 | [assembly: AssemblyCompany( "Microsoft" )] 12 | [assembly: AssemblyProduct( "HyperbolicModels" )] 13 | [assembly: AssemblyCopyright( "Copyright © Microsoft 2014" )] 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( "81558d9e-6b4c-4c3c-b247-235361bb71b4" )] 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 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Sections.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Linq; 8 | using R3.Core; 9 | using R3.Drawing; 10 | using R3.Geometry; 11 | using R3.Math; 12 | 13 | public class Sections 14 | { 15 | public void AnimationSections( Settings config ) 16 | { 17 | HoneycombDef imageData = new HoneycombDef( config.P, config.Q, config.R ); 18 | int p = imageData.P, q = imageData.Q, r = imageData.R; 19 | 20 | string filename = imageData.FormatFilename(); 21 | 22 | Sphere[] mirrors = SimplexCalcs.Mirrors( p, q, r ); 23 | double bounds = 1.0; //config.UhsBoundary.Bounds; 24 | bounds = 9.0; 25 | 26 | // Calculate the color scale. 27 | int size = 200; 28 | CoxeterImages.Settings settings = new CoxeterImages.Settings() 29 | { 30 | Honeycomb = imageData, 31 | Width = size, 32 | Height = size, 33 | Bounds = bounds, 34 | Mirrors = mirrors, 35 | FileName = imageData.FormatFilename(), 36 | }; 37 | 38 | CoxeterImages imageCalculator = new CoxeterImages(); 39 | //imageCalculator.AutoCalcScale( settings ); 40 | if( settings.ColorScaling < 1 ) 41 | settings.ColorScaling = 15; 42 | settings.ColorScaling = 11; 43 | 44 | Program.Log( "\nGenerating sections..." ); 45 | size = 500; 46 | settings.Width = size; 47 | settings.Height = size; 48 | settings.FileName = filename; 49 | 50 | double max = Spherical2D.e2sNorm( 15 ); 51 | double min = Spherical2D.e2sNorm( 1.0 / 15 ); 52 | DonHatch.e2hNorm( max ); 53 | int numSteps = 1800; // 1 minute 54 | double step = (max - min) / numSteps; 55 | for( int i = 0; i < 1; i++ ) 56 | { 57 | Program.Log( "\nSection " + i ); 58 | imageCalculator.m_z = 1.0 / 0.5; 59 | Spherical2D.s2eNorm( min + step * i ); 60 | DonHatch.h2eNorm( step * i ); 61 | settings.FileName = string.Format( "533_{0:D4}.png", i ); 62 | imageCalculator.GenImage( settings ); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | 8 | [DataContract( Namespace = "" )] 9 | public class Settings 10 | { 11 | public string HoneycombString 12 | { 13 | get 14 | { 15 | if( Angles.Length == 6 ) 16 | return "Goursat domain with dihedrals " + 17 | string.Join( ",", Angles ); 18 | 19 | return string.Format( "{{{0},{1},{2}}}", P, Q, R ); 20 | } 21 | } 22 | 23 | /// 24 | /// This is only here to pretty up the xml persistence. 25 | /// 26 | [DataMember] 27 | private string Dihedrals 28 | { 29 | get 30 | { 31 | return SaveToString( Angles ); 32 | } 33 | set 34 | { 35 | Angles = LoadFromString( value ); 36 | } 37 | } 38 | 39 | public int[] Angles { get; set; } 40 | 41 | [DataMember] 42 | public UhsBoundarySettings UhsBoundary { get; set; } 43 | 44 | [DataMember] 45 | public PovRaySettings PovRay { get; set; } 46 | 47 | public int P { get { return Angles[0]; } } 48 | public int Q { get { return Angles[1]; } } 49 | public int R { get { return Angles[2]; } } 50 | 51 | internal static string SaveToString( int[] vals ) 52 | { 53 | return string.Join( ",", vals ); 54 | } 55 | 56 | internal static int[] LoadFromString( string str ) 57 | { 58 | return str.Split( new char[] { ',' } ).Select( s => int.Parse( s ) ).ToArray(); 59 | } 60 | 61 | public string FileName( string extension ) 62 | { 63 | string fileName = string.Format( "{0}-{1}-{2}", 64 | NorI( P ), NorI( Q ), NorI( R ) ); 65 | if( !string.IsNullOrEmpty( extension ) ) 66 | fileName += "." + extension; 67 | return fileName; 68 | } 69 | 70 | private static string NorI( int n ) 71 | { 72 | return n == -1 ? "i" : n.ToString(); 73 | } 74 | } 75 | 76 | [DataContract( Namespace = "" )] 77 | public class UhsBoundarySettings 78 | { 79 | public string DisplayString 80 | { 81 | get 82 | { 83 | return string.Format( 84 | "Image Width: {0}\nImage Height: {1}\nBounds: {2}", 85 | ImageWidth, ImageHeight, Bounds ); 86 | } 87 | } 88 | 89 | /// 90 | /// The image width, in pixels. 91 | /// 92 | [DataMember] 93 | public int ImageWidth { get; set; } 94 | 95 | /// 96 | /// The image height, in pixels. 97 | /// 98 | [DataMember] 99 | public int ImageHeight { get; set; } 100 | 101 | /// 102 | /// The bounds of the image. 103 | /// Default is 1.0, and larger values will effectively zoom out. 104 | /// 105 | [DataMember] 106 | public double Bounds { get; set; } 107 | } 108 | 109 | [DataContract( Namespace = "" )] 110 | public class PovRaySettings 111 | { 112 | public string DisplayString 113 | { 114 | get 115 | { 116 | return string.Format( 117 | "Number of Edges: {0}\nEdge Width: {1}\nActiveMirrors: {2}", 118 | NumEdges, EdgeWidth, ActiveMirrors ); 119 | } 120 | } 121 | 122 | /// 123 | /// The active mirrors, 0-indexed. Array only contains active mirrors, so e.g. 124 | /// Wikipedia's 1010 would end up as { 0, 2 } 125 | /// 126 | public int[] Active { get; set; } 127 | 128 | /// 129 | /// This is only here to pretty up the xml persistence. 130 | /// 131 | [DataMember] 132 | private string ActiveMirrors 133 | { 134 | get 135 | { 136 | StringBuilder sb = new StringBuilder( "0000" ); 137 | foreach( int a in Active ) 138 | sb[a] = '1'; 139 | return sb.ToString(); 140 | } 141 | set 142 | { 143 | List active = new List(); 144 | for( int i = 0; i < 4; i++ ) 145 | if( value[i] == '1' ) 146 | active.Add( i ); 147 | Active = active.ToArray(); 148 | } 149 | } 150 | 151 | /// 152 | /// The number of edges to include in the output. 153 | /// 154 | [DataMember] 155 | public int NumEdges { get; set; } 156 | 157 | /// 158 | /// The width of edges, in a euclidean metric at the ball origin. 159 | /// 160 | [DataMember] 161 | public double EdgeWidth { get; set; } 162 | 163 | // Other config possibilities... 164 | // Resolution. 165 | // Colors 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Utils/DataContractHelper.cs: -------------------------------------------------------------------------------- 1 | namespace HyperbolicModels 2 | { 3 | using System.IO; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using System.Xml; 7 | 8 | /// 9 | /// Class with useful methods for saving/loading objects. 10 | /// 11 | public class DataContractHelper 12 | { 13 | public static void SaveToXml( object obj, string filename ) 14 | { 15 | using( var writer = XmlWriter.Create( filename, WriterSettings ) ) 16 | { 17 | DataContractSerializer dcs = new DataContractSerializer( obj.GetType() ); 18 | dcs.WriteObject( writer, obj ); 19 | } 20 | } 21 | 22 | public static object LoadFromXml( System.Type objectType, string filename ) 23 | { 24 | using( var reader = XmlReader.Create( filename, ReaderSettings ) ) 25 | { 26 | DataContractSerializer dcs = new DataContractSerializer( objectType ); 27 | return dcs.ReadObject( reader, verifyObjectName: false ); 28 | } 29 | } 30 | 31 | public static string SaveToString( object obj ) 32 | { 33 | StringBuilder sb = new StringBuilder(); 34 | using( XmlWriter writer = XmlWriter.Create( sb, WriterSettings ) ) 35 | { 36 | DataContractSerializer dcs = new DataContractSerializer( obj.GetType() ); 37 | dcs.WriteObject( writer, obj ); 38 | } 39 | return sb.ToString(); 40 | } 41 | 42 | public static object LoadFromString( System.Type objectType, string saved ) 43 | { 44 | using( StringReader sr = new StringReader( saved ) ) 45 | using( XmlReader reader = XmlReader.Create( sr, ReaderSettings ) ) 46 | { 47 | DataContractSerializer dcs = new DataContractSerializer( objectType ); 48 | return dcs.ReadObject( reader, verifyObjectName: false ); 49 | } 50 | } 51 | 52 | public static XmlWriterSettings WriterSettings 53 | { 54 | get 55 | { 56 | XmlWriterSettings settings = new XmlWriterSettings(); 57 | settings.OmitXmlDeclaration = true; 58 | settings.Indent = true; 59 | return settings; 60 | } 61 | } 62 | 63 | public static XmlReaderSettings ReaderSettings 64 | { 65 | get 66 | { 67 | XmlReaderSettings settings = new XmlReaderSettings(); 68 | settings.IgnoreWhitespace = true; 69 | return settings; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /code/HyperbolicModels/Utils/Util.cs: -------------------------------------------------------------------------------- 1 | namespace R3.Geometry 2 | { 3 | using R3.Core; 4 | using Math = System.Math; 5 | 6 | public static class Util 7 | { 8 | public static double PiOverNSafe( int n ) 9 | { 10 | return Honeycomb.PiOverNSafe( n ); 11 | } 12 | 13 | public static Geometry GetGeometry( int p, int q, int r ) 14 | { 15 | return Honeycomb.GetGeometry( p, q, r ); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/HyperbolicModels/ViewPath.cs: -------------------------------------------------------------------------------- 1 | namespace R3.Geometry 2 | { 3 | using MathNet.Numerics.Interpolation; 4 | using R3.Geometry; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | public class ViewPath 9 | { 10 | public double Time { get; set; } 11 | 12 | public int Step { get; set; } 13 | 14 | public Vector3D Location 15 | { 16 | get 17 | { 18 | return new Vector3D( 19 | locX.Interpolate( Time ), 20 | locY.Interpolate( Time ), 21 | locZ.Interpolate( Time ) ); 22 | } 23 | } 24 | 25 | public Vector3D LookAt 26 | { 27 | get 28 | { 29 | Vector3D deriv = new Vector3D( 30 | locX.Differentiate( Time ), 31 | locY.Differentiate( Time ), 32 | locZ.Differentiate( Time ) ); 33 | deriv.Normalize(); 34 | return deriv; 35 | 36 | /*return new Vector3D( 37 | lookX.Interpolate( Time ), 38 | lookY.Interpolate( Time ), 39 | lookZ.Interpolate( Time ) ); */ 40 | } 41 | } 42 | 43 | CubicSpline locX, locY, locZ; 44 | //CubicSpline lookX, lookY, lookZ; 45 | 46 | /// 47 | /// Initialize our path with a sequence of locations and lookAt directions. 48 | /// 49 | public void Initialize( IEnumerable locations, IEnumerable lookAts ) 50 | { 51 | int count = locations.Count(); 52 | //if( lookAts.Count() != count ) 53 | // throw new System.ArgumentException(); 54 | double[] times = Enumerable.Range( 0, count ).Select( i=> (double)i/(count-1) ).ToArray(); 55 | 56 | //.6, 0, -.8 57 | locX = CubicSpline.InterpolateBoundaries( times, locations.Select( v => v.X ), SplineBoundaryCondition.FirstDerivative, .6, SplineBoundaryCondition.FirstDerivative, .6 ); 58 | locY = CubicSpline.InterpolateBoundaries( times, locations.Select( v => v.Y ), SplineBoundaryCondition.FirstDerivative, 0, SplineBoundaryCondition.FirstDerivative, 0 ); 59 | locZ = CubicSpline.InterpolateBoundaries( times, locations.Select( v => v.Z ), SplineBoundaryCondition.FirstDerivative, -.8, SplineBoundaryCondition.FirstDerivative, -.8 ); 60 | 61 | locX = CubicSpline.InterpolateNatural( times, locations.Select( v => v.X ) ); 62 | locY = CubicSpline.InterpolateNatural( times, locations.Select( v => v.Y ) ); 63 | locZ = CubicSpline.InterpolateNatural( times, locations.Select( v => v.Z ) ); 64 | /*lookX = CubicSpline.InterpolateNatural( times, lookAts.Select( v => v.X ) ); 65 | lookY = CubicSpline.InterpolateNatural( times, lookAts.Select( v => v.Y ) ); 66 | lookZ = CubicSpline.InterpolateNatural( times, lookAts.Select( v => v.Z ) );*/ 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /code/HyperbolicModels/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /code/HyperbolicModels/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /code/HyperbolicModels/sample_settings/settings_PovRay.xml: -------------------------------------------------------------------------------- 1 | 2 | 2,4,3,2,3,3 3 | 4 | 0001 5 | 0.02 6 | 250000 7 | 8 | 9 | -------------------------------------------------------------------------------- /code/HyperbolicModels/sample_settings/settings_UHS.xml: -------------------------------------------------------------------------------- 1 | 2 | 3,3,7 3 | 4 | 5 | 1 6 | 1200 7 | 1200 8 | 9 | 10 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | } -------------------------------------------------------------------------------- /code/R3/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 | public static Vector3D SpiralToIsometric( Vector3D v, int p, int m, int n ) 34 | { 35 | Complex vc = v.ToComplex(); 36 | v = new Vector3D( Math.Log( vc.Magnitude ), vc.Phase ); 37 | 38 | Vector3D e1 = new Vector3D( 0, 1 ); 39 | Vector3D e2; 40 | switch( p ) 41 | { 42 | case 3: 43 | e2 = new Vector3D(); break; 44 | case 4: 45 | e2 = new Vector3D(); break; 46 | case 6: 47 | e2 = new Vector3D(); break; 48 | default: 49 | throw new System.ArgumentException(); 50 | } 51 | 52 | double scale = Math.Sqrt( m * m + n * n ); 53 | double a = Euclidean2D.AngleToClock( new Vector3D( 0, 1 ), new Vector3D( m, n ) ); 54 | 55 | v.RotateXY( a ); // Rotate 56 | v *= scale; // Scale 57 | 58 | v *= Math.Sqrt( 2 ) * Geometry2D.EuclideanHypotenuse / ( 2 * Math.PI ); 59 | v.RotateXY( Math.PI / 4 ); 60 | return v; 61 | } 62 | 63 | public static Vector3D LoxodromicToIsometric( Vector3D v, int p, int m, int n ) 64 | { 65 | Mobius mob = Mobius.CreateFromIsometry( Geometry.Spherical, 0, new System.Numerics.Complex( 1, 0 ) ); 66 | v = mob.Apply( v ); 67 | return SpiralToIsometric( v, p, m, n ); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 = 0.9; 154 | double b = 1.0; 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 | Complex z = ( w + temp ) / ( 2 * alpha ); 177 | */ 178 | 179 | Complex r1 = w + Complex.Sqrt( w * w - 1 ); 180 | Complex r2 = w - Complex.Sqrt( w * w - 1 ); 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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | } -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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.8 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 | x64 38 | bin\x64\Debug\ 39 | 40 | 41 | x64 42 | bin\x64\Release\ 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 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 127 | -------------------------------------------------------------------------------- /code/R3/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 | -------------------------------------------------------------------------------- /code/R3/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; 5 | #declare lookAt = 0; 6 | 7 | //background { CHSL2RGB( <220, 1, .15> ) } 8 | 9 | light_source { lookFrom White } 10 | global_settings 11 | { 12 | assumed_gamma 1.5 13 | max_trace_level 10 14 | } 15 | 16 | camera 17 | { 18 | //spherical 19 | //fisheye 20 | //ultra_wide_angle 21 | //angle 180 22 | //angle 270 23 | //angle 360 24 | 25 | location lookFrom 26 | sky<0,0,1> // z up 27 | right <-4/3,0,0> // right-handed coordinate system 28 | look_at lookAt 29 | } 30 | 31 | #declare fin = finish 32 | { 33 | specular .5 34 | } 35 | 36 | #declare tex = texture 37 | { 38 | pigment { White } 39 | //pigment { Black } 40 | finish { fin } 41 | } 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /povray/wikipedia/H3_paracompact_equi.pov: -------------------------------------------------------------------------------- 1 | #include "colors.inc" 2 | 3 | 4 | #declare lookFrom = 0; 5 | #declare lookAt = <0,0,-1>; 6 | 7 | //background { CHSL2RGB( <220, 1, .15> ) } 8 | 9 | light_source { lookFrom White } 10 | global_settings 11 | { 12 | assumed_gamma 1.5 13 | max_trace_level 10 14 | } 15 | 16 | camera 17 | { 18 | spherical 19 | angle 360 // horizontal 20 | 180 // vertical(optional) 21 | location lookFrom 22 | look_at lookAt 23 | sky<0,0,-1> 24 | right<-2,0,0> 25 | } 26 | 27 | #declare fin = finish 28 | { 29 | specular .5 30 | } 31 | 32 | #declare tex = texture 33 | { 34 | pigment { White } 35 | //pigment { Black } 36 | finish { fin } 37 | } 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /povray/wikipedia/H3_sample.pov: -------------------------------------------------------------------------------- 1 | #include "colors.inc" 2 | 3 | 4 | #declare lookFrom = 0; 5 | #declare lookAt = -z; // Use this for loop graphs 6 | #declare lookAt = -x; // Use this when defining the honeycomb with 6 angles 7 | 8 | 9 | //background { CHSL2RGB( <220, 1, .15> ) } 10 | background { White } 11 | 12 | 13 | light_source { lookFrom White } 14 | global_settings 15 | { 16 | assumed_gamma 1.5 17 | max_trace_level 10 18 | } 19 | 20 | camera 21 | { 22 | /* // Spherical images can be made by uncommenting this. 23 | spherical 24 | angle 360 // horizontal 25 | 180 // vertical(optional) 26 | */ 27 | location lookFrom 28 | look_at lookAt 29 | sky<0,0,-1> 30 | right<-4/3,0,0> // Change to chosen aspect ratio. 31 | //right<-2,0,0> // For spherical images. 32 | } 33 | 34 | #declare fin = finish 35 | { 36 | specular .5 37 | } 38 | 39 | #declare tex = texture 40 | { 41 | //pigment { White } 42 | pigment { Gray40 } 43 | finish { fin } 44 | } 45 | 46 | // If you generate a different honeycomb, change this include! 47 | #include "2-4-3-2-3-3_0001.pov" 48 | 49 | --------------------------------------------------------------------------------