001/* 002 * Renderer Models. The MIT License. 003 * Copyright (c) 2022 rlkraft@pnw.edu 004 * See LICENSE for details. 005*/ 006 007package renderer.models_L; 008 009import renderer.scene.*; 010import renderer.scene.primitives.*; 011import renderer.scene.util.MeshMaker; 012 013/** 014 Create a wireframe model of a torus. 015<p> 016 See <a href="https://en.wikipedia.org/wiki/Torus" target="_top"> 017 https://en.wikipedia.org/wiki/Torus</a> 018<p> 019 This torus is the surface of revolution generated by revolving 020 the circle in the xy-plane with radius {@code r2} and center 021 {@code (r1,0,0)} around the y-axis. We are assuming that {@code r1 > r2}. 022<p> 023 Here are parametric equations for the circle in the xy-plane with 024 radius {@code r2} and center {@code (r1,0,0)} and parameterized 025 starting from the top, with parameter {@code 0 <= phi <= 2*PI}. 026 <pre>{@code 027 x(phi) = r1 + r2 * sin(phi) 028 y(phi) = r2 * cos(phi) 029 z(phi) = 0 030 }</pre> 031 Here is the 3D rotation matrix that rotates around the y-axis 032 by {@code theta} radians with {@code 0 <= theta <= 2*PI}. 033 <pre>{@code 034 [ cos(theta) 0 sin(theta)] 035 [ 0 1 0 ] 036 [-sin(theta) 0 cos(theta)] 037 }</pre> 038 If we multiply the rotation matrix with the circle parameterization, 039 we get a parameterization of the torus. 040 <pre>{@code 041 [ cos(theta) 0 sin(theta)] [r1 + r2 * sin(phi)] 042 [ 0 1 0 ] * [ r2 * cos(phi)] 043 [-sin(theta) 0 cos(theta)] [ 0 ] 044 045 = ( r1*cos(theta) + r2*cos(theta)*sin(phi). 046 r2*cos(phi), 047 -r1*sin(theta) - r2*sin(theta)*sin(phi) ) 048 049 = ( (r1 + r2*sin(phi)) * cos(theta), 050 r2*cos(phi), 051 -(r1 + r2*sin(phi)) * sin(theta) ) 052 }</pre> 053 See 054 <a href="https://en.wikipedia.org/wiki/Torus#Geometry" target="_top"> 055 https://en.wikipedia.org/wiki/Torus#Geometry</a> 056 057 @see TorusSector 058*/ 059public class Torus extends Model implements MeshMaker 060{ 061 public final double r1; 062 public final double r2; 063 public final int n; 064 public final int k; 065 066 /** 067 Create a torus with a circle of revolution with radius 3/4 068 and a cross section circle (circle of longitude) 069 with radius 1/4. 070 */ 071 public Torus( ) 072 { 073 this(0.75, 0.25, 12, 16); 074 } 075 076 077 /** 078 Create a torus with a circle of revolution with radius {@code r1} 079 and a cross section circle (circle of longitude) with radius 080 {@code r2}. 081 082 @param r1 radius of the circle of revolution 083 @param r2 radius of the cross section circle (circle of longitude) 084 */ 085 public Torus(final double r1, final double r2) 086 { 087 this(r1, r2, 12, 16); 088 } 089 090 091 /** 092 Create a torus with a circle of revolution with radius {@code r1} 093 and a cross section circle (circle of longitude) with radius 094 {@code r2}. 095 <p> 096 The last two parameters determine the number of circles of 097 longitude and the number of circles of latitude in the model. 098 <p> 099 If there are {@code n} circles of latitude, then each circle 100 of longitude will have {@code n} line segments. 101 If there are {@code k} circles of longitude, then each circle 102 of latitude will have {@code k} line segments. 103 <p> 104 There must be at least three circles of longitude and at least 105 three circles of latitude. 106 107 @param r1 radius of the circle of revolution 108 @param r2 radius of the cross section circle (circle of longitude) 109 @param n number of circles of latitude 110 @param k number of circles of longitude 111 @throws IllegalArgumentException if {@code n} is less than 3 112 @throws IllegalArgumentException if {@code k} is less than 3 113 */ 114 public Torus(final double r1, final double r2, 115 final int n, final int k) 116 { 117 super(String.format("Torus(%.2f,%.2f,%d,%d)", r1, r2, n, k)); 118 119 if (n < 3) 120 throw new IllegalArgumentException("n must be greater than 2"); 121 if (k < 3) 122 throw new IllegalArgumentException("k must be greater than 2"); 123 124 this.r1 = r1; 125 this.r2 = r2; 126 this.n = n; 127 this.k = k; 128 129 // Create the torus's geometry. 130 131 final double deltaPhi = (2 * Math.PI) / n, 132 deltaTheta = (2 * Math.PI) / k; 133 134 // An array of vertices to be used to create line segments. 135 final Vertex[][] v = new Vertex[n][k]; 136 137 // Create all the vertices. 138 for (int j = 0; j < k; ++j) // choose a rotation around the y-axis 139 { 140 final double c1 = Math.cos(j * deltaTheta), 141 s1 = Math.sin(j * deltaTheta); 142 for (int i = 0; i < n; ++i) // go around a cross section circle 143 { 144 final double c2 = Math.cos(i * deltaPhi), 145 s2 = Math.sin(i * deltaPhi); 146 v[i][j] = new Vertex( (r1 + r2*s2) * c1, 147 r2*c2, 148 -(r1 + r2*s2) * s1 ); 149 } 150 } 151 152 // Add all of the vertices to this model. 153 for (int i = 0; i < n; ++i) 154 { 155 for (int j = 0; j < k; ++j) 156 { 157 addVertex( v[i][j] ); 158 } 159 } 160 161 // Create the vertical cross-section circles. 162 for (int j = 0; j < k; ++j) // choose a rotation around the y-axis 163 { 164 for (int i = 0; i < n - 1; ++i) // go around a cross section circle 165 { // v[i][j] v[i+1][j] 166 addPrimitive(new LineSegment( (i * k) + j, ((i+1) * k) + j )); 167 } 168 // close the circle 169 addPrimitive(new LineSegment( ((n-1) * k) + j, (0 * k) + j )); 170 } // v[n-1][j] v[0][j] 171 172 // Create all the horizontal circles around the torus. 173 for (int i = 0; i < n; ++i) //choose a rotation around the cross section 174 { 175 for (int j = 0; j < k - 1; ++j) // go around a horizontal circle 176 { // v[i][j] v[i][j+1] 177 addPrimitive(new LineSegment( (i * k) + j, (i * k) + (j+1) )); 178 } 179 // close the circle 180 addPrimitive(new LineSegment( (i * k) + (k-1), (i * k) + 0 )); 181 } // v[i][k-1] v[i][0] 182 } 183 184 185 186 // Implement the MeshMaker interface (three methods). 187 @Override public int getHorzCount() {return n;} 188 189 @Override public int getVertCount() {return k;} 190 191 @Override 192 public Torus remake(final int n, final int k) 193 { 194 return new Torus(this.r1, this.r2, 195 n, k); 196 } 197}//Torus