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 partial right circular cone with its 015 base parallel to the xz-plane and its apex on the positive y-axis. 016<p> 017 By a partial cone we mean a cone over a circular sector of the 018 cone's base and also cutting off the top part of the cone (the 019 part between the apex and a circle of latitude) leaving a frustum 020 of the (partial) cone. 021 022 @see Cone 023 @see ConeFrustum 024 @see CircleSector 025 @see DiskSector 026 @see RingSector 027 @see CylinderSector 028 @see SphereSector 029 @see TorusSector 030*/ 031public class ConeSector extends Model implements MeshMaker 032{ 033 public final double r; 034 public final double h; 035 public final double t; 036 public final double theta1; 037 public final double theta2; 038 public final int n; 039 public final int k; 040 041 /** 042 Create half of a right circular cone with its base in the xz-plane, 043 a base radius of 1, height 1, and apex on the positive y-axis. 044 */ 045 public ConeSector( ) 046 { 047 this(1, 1, Math.PI/2, 3*Math.PI/2, 15, 8); 048 } 049 050 051 /** 052 Create a part of the cone with its base in the xz-plane, 053 a base radius of {@code r}, height {@code h}, and apex 054 on the y-axis. 055 <p> 056 The partial cone is a cone over the circular sector 057 from angle {@code theta1} to angle {@code theta2} (in the 058 counterclockwise direction). In other words, the (partial) 059 circles of latitude in the model extend from angle 060 {@code theta1} to angle {@code theta2} (in the 061 counterclockwise direction). 062 <p> 063 The last two parameters determine the number of lines of longitude 064 and the number of (partial) circles of latitude in the model. 065 <p> 066 If there are {@code n} circles of latitude in the model (including 067 the bottom edge), then each line of longitude will have {@code n} 068 line segments. If there are {@code k} lines of longitude, then each 069 (partial) circle of latitude will have {@code k-1} line segments. 070 <p> 071 There must be at least four lines of longitude and at least 072 one circle of latitude. 073 074 @param r radius of the base in the xz-plane 075 @param h height of the apex on the y-axis 076 @param theta1 beginning longitude angle of the sector (in radians) 077 @param theta2 ending longitude angle of the sector (in radians) 078 @param n number of circles of latitude around the cone 079 @param k number of lines of longitude 080 @throws IllegalArgumentException if {@code n} is less than 2 081 @throws IllegalArgumentException if {@code k} is less than 4 082 */ 083 public ConeSector(final double r, 084 final double h, 085 final double theta1, final double theta2, 086 final int n, final int k) 087 { 088 this(r, h, h, theta1, theta2, n+1, k); 089 } 090 091 092 /** 093 Create a part of the cone with its base in the xz-plane, 094 a base radius of {@code r}, height {@code h}, and apex 095 on the y-axis. 096 <p> 097 If {@code 0 < t < h}, then the partial cone is a frustum 098 with its base in the xz-plane and the top of the frustum at 099 {@code y = t}. 100 <p> 101 The partial cone is a cone over the circular sector 102 from angle {@code theta1} to angle {@code theta2} (in the 103 counterclockwise direction). In other words, the (partial) 104 circles of latitude in the model extend from angle 105 {@code theta1} to angle {@code theta2} (in the 106 counterclockwise direction). 107 <p> 108 The last two parameters determine the number of lines of longitude 109 (not counting one edge of any removed sector) and the number of 110 (partial) circles of latitude (not counting the top edge of the 111 frustum) in the model. 112 <p> 113 If there are {@code n} circles of latitude in the model (including 114 the bottom edge but not the top edge of the frustum), then each 115 line of longitude will have {@code n+1} line segments. If there are 116 {@code k} lines of longitude (not counting one edge of any removed 117 sector), then each (partial) circle of latitude will have {@code k} 118 line segments. 119 <p> 120 There must be at least four lines of longitude and at least 121 two circles of latitude. 122 123 @param r radius of the base in the xz-plane 124 @param h height of the apex on the y-axis 125 @param t top of the frustum of the come 126 @param theta1 beginning longitude angle of the sector (in radians) 127 @param theta2 ending longitude angle of the sector (in radians) 128 @param n number of circles of latitude around the cone 129 @param k number of lines of longitude 130 @throws IllegalArgumentException if {@code n} is less than 2 131 @throws IllegalArgumentException if {@code k} is less than 4 132 @throws IllegalArgumentException if {@code h} is less than {@code t} 133 */ 134 public ConeSector(final double r, 135 final double h, 136 final double t, 137 double theta1, double theta2, 138 final int n, final int k) 139 { 140 super(String.format("Cone Sector(%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d)", 141 r, h, t, theta1, theta2, n, k)); 142 143 if (n < 2) 144 throw new IllegalArgumentException("n must be greater than 1"); 145 if (k < 4) 146 throw new IllegalArgumentException("k must be greater than 3"); 147 if (h < t) 148 throw new IllegalArgumentException("h must be greater than or equal to t"); 149 150 theta1 = theta1 % (2*Math.PI); 151 theta2 = theta2 % (2*Math.PI); 152 if (theta1 < 0) theta1 = 2*Math.PI + theta1; 153 if (theta2 < 0) theta2 = 2*Math.PI + theta2; 154 if (theta2 <= theta1) theta2 = theta2 + 2*Math.PI; 155 156 this.r = r; 157 this.h = h; 158 this.t = t; 159 this.theta1 = theta1; 160 this.theta2 = theta2; 161 this.n = n; 162 this.k = k; 163 164 // Create the cone's geometry. 165 166 final double deltaH = t / (n - 1), 167 deltaTheta = (theta2 - theta1) / (k - 1); 168 169 // An array of indexes to be used to create line segments. 170 final int[][] indexes = new int[n][k]; 171 172 // Create all the vertices (from the bottom up). 173 int index = 0; 174 for (int j = 0; j < k; ++j) // choose an angle of longitude 175 { 176 final double c = Math.cos(theta1 + j * deltaTheta), 177 s = Math.sin(theta1 + j * deltaTheta); 178 for (int i = 0; i < n; ++i) // choose a circle of latitude 179 { 180 final double slantRadius = r * (1 - i * deltaH / h); 181 addVertex( new Vertex(slantRadius * c, 182 i * deltaH, 183 slantRadius * s) ); 184 indexes[i][j] = index++; 185 } 186 } 187 addVertex( new Vertex(0, 0, 0) ); // bottom center 188 final int bottomCenterIndex = index; 189 ++index; 190 191 // Create all the horizontal (partial) circles of latitude around the cone. 192 for (int i = 0; i < n; ++i) 193 { 194 for (int j = 0; j < k - 1; ++j) 195 { 196 addPrimitive(new LineSegment(indexes[i][j], indexes[i][j+1])); 197 } 198 } 199 200 // Create the slanted lines of longitude from the base to the 201 // top circle of latitude, and the triangle fan in the base. 202 for (int j = 0; j < k; ++j) 203 { 204 addPrimitive(new LineSegment(bottomCenterIndex, indexes[0][j])); 205 206 for (int i = 0; i < n - 1; ++i) 207 { 208 addPrimitive(new LineSegment(indexes[i][j], indexes[i+1][j])); 209 } 210 } 211 } 212 213 214 215 // Implement the MeshMaker interface (three methods). 216 @Override public int getHorzCount() {return n;} 217 218 @Override public int getVertCount() {return k;} 219 220 @Override 221 public ConeSector remake(final int n, final int k) 222 { 223 return new ConeSector(this.r, 224 this.h, 225 this.t, 226 this.theta1, this.theta2, 227 n, k); 228 } 229}//ConeSector