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 sphere centered at the origin. 015<p> 016 See <a href="https://en.wikipedia.org/wiki/Sphere" target="_top"> 017 https://en.wikipedia.org/wiki/Sphere</a> 018<p> 019 A sphere of radius {@code r} is the surface of revolution generated by 020 revolving a half-circle in the xy-plane with radius {@code r} and center 021 {@code (0,0,0)} around the y-axis. 022<p> 023 Here are parametric equations for the right half-circle in the xy-plane with 024 radius {@code r} and center {@code (0,0,0)}, parameterized from the top down. 025 <pre>{@code 026 x(phi) = r * sin(phi) \ 027 y(phi) = r * cos(phi) |- 0 <= phi <= PI 028 z(phi) = 0 / 029 }</pre> 030 Here is the 3D rotation matrix that rotates around the y-axis 031 by {@code theta} radians, {@code 0 <= theta <= 2*PI} 032 <pre>{@code 033 [ cos(theta) 0 sin(theta)] 034 [ 0 1 0 ] 035 [-sin(theta) 0 cos(theta)] 036 }</pre> 037 If we multiply the rotation matrix with the half-circle 038 parameterization, we get a parameterization of the sphere. 039 <pre>{@code 040 [ cos(theta) 0 sin(theta)] [r * sin(phi)] 041 [ 0 1 0 ] * [r * cos(phi)] 042 [-sin(theta) 0 cos(theta)] [ 0 ] 043 044 = ( r * sin(phi) * cos(theta). \ 045 r * cos(phi), |- 0<=theta<=2*PI, 0<=phi<=PI 046 -r * sin(phi) * sin(theta) ) / 047 }</pre> 048 See 049 <a href="https://en.wikipedia.org/wiki/Sphere#Equations_in_three-dimensional_space" target="_top"> 050 https://en.wikipedia.org/wiki/Sphere#Equations_in_three-dimensional_space</a> 051 052 @see SphereSector 053*/ 054public class Sphere extends Model implements MeshMaker 055{ 056 public final double r; 057 public final int n; 058 public final int k; 059 060 /** 061 Create a sphere of radius 1 centered at the origin. 062 */ 063 public Sphere( ) 064 { 065 this(1, 15, 16); 066 } 067 068 069 /** 070 Create a sphere of radius {@code r} centered at the origin. 071 072 @param r radius of the sphere 073 */ 074 public Sphere(final double r) 075 { 076 this(r, 15, 16); 077 } 078 079 080 /** 081 Create a sphere of radius {@code r} centered at the origin. 082 <p> 083 The last two parameters determine the number of half circles 084 of longitude and the number of circles of latitude in the model. 085 <p> 086 If there are {@code k} half circles of longitude, then each circle 087 of latitude will have {@code k} line segments. 088 If there are {@code n} circles of latitude, then each half circle 089 of longitude will have {@code n+1} line segments. 090 <p> 091 There must be at least three half circles of longitude and 092 at least one circle of latitude. 093 094 @param r radius of the sphere 095 @param n number of circles of latitude 096 @param k number of half circles of longitude 097 @throws IllegalArgumentException if {@code n} is less than 1 098 @throws IllegalArgumentException if {@code k} is less than 3 099 */ 100 public Sphere(final double r, final int n, final int k) 101 { 102 super(String.format("Sphere(%.2f,%d,%d)", r, n, k)); 103 104 if (n < 1) 105 throw new IllegalArgumentException("n must be greater than 0"); 106 if (k < 3) 107 throw new IllegalArgumentException("k must be greater than 2"); 108 109 this.r = r; 110 this.n = n; 111 this.k = k; 112 113 // Create the sphere's geometry. 114 115 final double deltaPhi = Math.PI / (n + 1), 116 deltaTheta = (2 * Math.PI) / k; 117 118 // An array of vertices to be used to create line segments. 119 final Vertex[][] v = new Vertex[n][k]; 120 121 // Create all the vertices. 122 for (int j = 0; j < k; ++j) // choose an angle of longitude 123 { 124 final double c1 = Math.cos(j * deltaTheta), 125 s1 = Math.sin(j * deltaTheta); 126 for (int i = 0; i < n; ++i) // choose an angle of latitude 127 { 128 final double c2 = Math.cos(deltaPhi + i * deltaPhi), 129 s2 = Math.sin(deltaPhi + i * deltaPhi); 130 v[i][j] = new Vertex( r * s2 * c1, 131 r * c2, 132 -r * s2 * s1 ); 133 } 134 } 135 final Vertex northPole = new Vertex(0, r, 0), 136 southPole = new Vertex(0, -r, 0); 137 138 // Add all of the vertices to this model. 139 for (int i = 0; i < n; ++i) 140 { 141 for (int j = 0; j < k; ++j) 142 { 143 addVertex( v[i][j] ); 144 } 145 } 146 addVertex(northPole, 147 southPole); 148 final int northPoleIndex = n * k, 149 southPoleIndex = northPoleIndex + 1; 150 151 // Create the horizontal circles of latitude around the sphere. 152 for (int i = 0; i < n; ++i) 153 { 154 for (int j = 0; j < k - 1; ++j) 155 { // v[i][j] v[i][j+1] 156 addPrimitive(new LineSegment( (i * k) + j, (i * k) + (j+1) )); 157 } 158 // close the circle 159 addPrimitive(new LineSegment( (i * k) + (k-1), (i * k) + 0 )); 160 } // v[i][k-1] v[i][0] 161 162 // Create the vertical half-circles of longitude from north to south pole. 163 for (int j = 0; j < k; ++j) 164 { // v[0][j] 165 addPrimitive(new LineSegment( northPoleIndex, (0 * k) + j )); 166 167 for (int i = 0; i < n - 1; ++i) 168 { // v[i][j] v[i+1][j] 169 addPrimitive(new LineSegment( (i * k) + j, ((i+1) * k) + j )); 170 } 171 // v[n-1][j] 172 addPrimitive(new LineSegment( ((n-1) * k) + j, southPoleIndex )); 173 } 174 } 175 176 177 178 // Implement the MeshMaker interface (three methods). 179 @Override public int getHorzCount() {return n;} 180 181 @Override public int getVertCount() {return k;} 182 183 @Override 184 public Sphere remake(final int n, final int k) 185 { 186 return new Sphere(this.r, n, k); 187 } 188}//Sphere