I used the code from above to add a circle to a rendered 3D sphere. Those funny looking circles on a flat map look like real circles on a sphere. I borrowed a slider class from some example called Kepler2012. I draw the circle using thread() and I read the warning in the thread() documentation about not using that function to draw or will get inconsistent results. I don’t know how else to do it though.
/*
The sphere is drawn using the example from processing/example/texturesphere
The green circle is drawn based on code from Andres Ruiz
I dont claim anything other than I fused it all together
*/
PImage texmap;
PGraphics texture;
float[] x = new float[3600];
float[] y = new float[3600];
// double GHA = Math.toRadians(162.3366651);
double GHA = Math.toRadians(100);
// double dec = Math.toRadians(42);
double dec = Math.toRadians(-32);
// double dec = mouseY;
double Be = Math.toRadians(35.4000);
double Le = Math.toRadians(26.452837);
double[]vv = new double[3]; //, vy[3], vyz[3];
double[]vy = new double[3];
double[]vyz = new double[3];
double[]wpt = new double[3600];
float[]WPT = new float[3600];
double[][]My = new double[3][3];
double[][]Mz = new double[3][3];
int sDetail = 65; // Sphere detail setting
float rotationX = 0;
float rotationY = 0;
float velocityX = 0;
float velocityY = 0;
//float globeRadius = 600;
float pushBack = -400;
float globeRadius = 600;
float[] cx, cz, sphereX, sphereY, sphereZ;
float sinLUT[];
float cosLUT[];
float SINCOS_PRECISION = 0.5;
int SINCOS_LENGTH = int(360.0 / SINCOS_PRECISION);
color [] colors = new color[7];
float alt = radians(-77.79f);
//Controls controls;
boolean released = false;
Controls controls;
HorizontalControl controlX;
int showControls;
boolean draggingZoomSlider = false;
float zoom = -90.0f;
float tzoom = -100.0f;
void setup() {
size(400, 300, P3D);
//fullScreen(P3D);
smooth();
noFill();
texmap = loadImage("world32k.jpg");
// texmap = loadImage("alphatest.png");
texture = createGraphics(texmap.width, texmap.height);
controls = new Controls();
controlX = new HorizontalControl();
showControls = 1;
initStars();
getPoints();
initializeSphere(sDetail);
}
void draw() {
background(0);
x = new float[3600];
y = new float[3600];
if (mousePressed) {
if( (showControls == 1) && (controls.isZoomSliderEvent(mouseX, mouseY))){ // || ( showControls == 1 && controlX.isZoomSliderEvent(mouseX,mouseY))) {
draggingZoomSlider = true;
zoom = controls.getZoomValue(mouseY);
// tzoom = controlX.getZoomValue(mouseX);
// MousePress - Rotation Adjustment
}/* else if (!draggingZoomSlider) {
// if (released != false){
velocityX += (mouseY-pmouseY) * 0.01;
velocityY -= (mouseX-pmouseX) * 0.01;
// }
} */
else if ( showControls == 1 && controlX.isZoomSliderEvent(mouseX,mouseY)){
// draggingZoomSlider = true;
tzoom = controlX.getZoomValue(mouseX);
// MousePress - Rotation Adjustment
} else if (!draggingZoomSlider) {
if (released = true){
velocityX += (mouseY-pmouseY) * 0.01;
velocityY -= (mouseX-pmouseX) * 0.01;
}
}
}
println(degrees((float)alt));
// initStars();
renderGlobe();
controls.updateZoomSlider(zoom);
controlX.updateZoomSlider(tzoom);
controls.render();
controlX.render();
}
void mouseReleased() {
//released = false;
draggingZoomSlider = false;
if (released == true){
alt = (map(zoom,texture.height-texture.height,texture.height,radians(-90),radians(90)));
// GHA = (map(tzoom, texture.width - texture.width, texture.width, radians(-180),radians(180)));
GHA = (map(tzoom,texture.width - texture.width,texture.width, radians(-180),radians(180)));
initStars();
initializeSphere(sDetail);
thread( "getPoints");
renderGlobe();
}
}
boolean getPoints(){
released = false;
Mz = Rz(Math.toRadians(360.0) - GHA, Mz);
My = Ry(Math.toRadians(90.0) - dec, My);
int w = 0;
for( double L0 = -180.0; L0 <= 180.0; L0 += .1 )
{
vv = VectorSpherical2Cartesian(alt,Math.toRadians(L0) );
vy = MatrixVecProd( My, vv, vy );
vyz = MatrixVecProd( Mz, vy, vyz );
wpt[w] = C2ELat( vyz[0], vyz[1], vyz[2]);
wpt[w+1] = C2ELon( vyz[0], vyz[1], vyz[2]);
WPT = toFloatArray(wpt);
x[w] = map(WPT[w+1],radians(-180) ,radians(180),texture.width, texture.width - texture.width);
y[w] = map(WPT[w],radians(-90),(radians(90)),texture.height,texture.height - texture.height);
texture.beginDraw();
texture.point(x[w],y[w]);
texture.noFill();
texture.stroke(0,255,0);
texture.strokeWeight(3);
texture.beginShape();
texture.curveVertex(x[w],y[w]);
texture.curveVertex(x[w],y[w]);
texture.endShape();
texture.endDraw();
released = true;
}
w++;
return released;
}
//Funcition to convert double[] to float[]
float[] toFloatArray(double[] arr) {
if (arr == null) return null;
int n = arr.length;
float[] ret = new float[n];
for (int i = 0; i < n; i++) {
ret[i] = (float)arr[i];
}
return ret;
}
// end of function to convert double[] to float[]
double[] VectorSpherical2Cartesian(double B, double L){
double v[] = new double[3];
v[0] = Math.cos(B) * Math.cos(L);
v[1] = Math.cos(B) * Math.sin(L);
v[2] = Math.sin(B);
return(v);
}
public double C2ELat( double x, double y, double z )
{
double[]res = new double[3];
res[0] = Math.sqrt( x*x+y*y+z*z); //R
//*B = ASIN(z/(*R));
res[1] = Math.atan2( z, Math.sqrt(x*x+y*y) ); //B
res[2] = Math.atan2( y, x ); //L
return (res[1]);
}
public double C2ELon( double x, double y, double z )
{
double[]res = new double[3];
res[0] = Math.sqrt( x*x+y*y+z*z); //R
res[1] = Math.atan2( z, Math.sqrt(x*x+y*y) ); //B
res[2] = Math.atan2( y, x ); //L
return (res[2]);
}
public double[] E2C( double B, double L, double R )
{
double[]res = new double[3];
res[0] = R*Math.cos((B))*Math.cos((L));
res[1] = R*Math.cos((B))*Math.sin((L));
res[2] = R*Math.sin((B));
return(res);
}
public double[][] Rx( double a, double[][] M ){
M[0][0] = 1.0;
M[1][0] = 0.0;
M[2][0] = 0.0;
M[0][1] = 0.0;
M[1][1] = Math.cos(a); //Math.cos(Math.toRadians(a));
M[2][1] = Math.sin(a); //Math.sin(Math.toRadians(a));
M[0][2] = 0.0;
M[1][2] = -Math.sin(a); //-Math.sin(Math.toRadians(a));
M[2][2] = Math.cos(a); //Math.cos(Math.toRadians(a));
return(M);
}
public double[][] Ry( double a, double[][] M ){
M[0][0] = Math.cos(a);
M[1][0] = 0.0;
M[2][0] = -Math.sin(a);
M[0][1] = 0.0;
M[1][1] = 1.0;
M[2][1] = 0.0;
M[0][2] = Math.sin(a);
M[1][2] = 0.0;
M[2][2] = Math.cos(a);
return(M);
}
public double[][] Rz( double a, double[][] M ){
M[0][0] = Math.cos(a); //Math.cos(a);
M[1][0] = Math.sin(a);
M[2][0] = 0.0;
M[0][1] = -Math.sin(a);
M[1][1] = Math.cos(a);
M[2][1] = 0.0;
M[0][2] = 0.0;
M[1][2] = 0.0;
M[2][2] = 1.0;
return(M);
}
public double[] MatrixVecProd( double[][] A, double[] v, double[] res ) {
int i,j;
int n = 3;
for( i=0; i<n; i++ ) {
res[i] = 0.0;
for( j=0; j<n; j++ ) {
res[i] += A[i][j]*v[j];
}
}
return (res);
}
void initStars(){
texture.beginDraw();
texture.background(texmap);
texture.endDraw();
}
void initializeSphere(int res)
{
sinLUT = new float[SINCOS_LENGTH];
cosLUT = new float[SINCOS_LENGTH];
for (int i = 0; i < SINCOS_LENGTH; i++) {
sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION);
cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION);
}
float delta = (float)SINCOS_LENGTH/res;
float[] cx = new float[res];
float[] cz = new float[res];
// Calc unit circle in XZ plane
for (int i = 0; i < res; i++) {
cx[i] = -cosLUT[(int) (i*delta) % SINCOS_LENGTH];
cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH];
}
// Computing vertexlist vertexlist starts at south pole
int vertCount = res * (res-1) + 2;
int currVert = 0;
// Re-init arrays to store vertices
sphereX = new float[vertCount];
sphereY = new float[vertCount];
sphereZ = new float[vertCount];
float angle_step = (SINCOS_LENGTH*0.5f)/res;
float angle = angle_step;
// Step along Y axis
for (int i = 1; i < res; i++) {
float curradius = sinLUT[(int) angle % SINCOS_LENGTH];
float currY = -cosLUT[(int) angle % SINCOS_LENGTH];
for (int j = 0; j < res; j++) {
sphereX[currVert] = cx[j] * curradius;
sphereY[currVert] = currY;
sphereZ[currVert++] = cz[j] * curradius;
}
angle += angle_step;
}
sDetail = res;
}
// Generic routine to draw textured sphere
void texturedSphere(float r, PGraphics t) {
int v1,v11,v2;
r = (r + 240 ) * 0.33;
beginShape(TRIANGLE_STRIP);
texture(t);
float iu=(float)(t.width-1)/(sDetail);
float iv=(float)(t.height-1)/(sDetail);
float u=0,v=iv;
for (int i = 0; i < sDetail; i++) {
vertex(0, -r, 0,u,0);
vertex(sphereX[i]*r, sphereY[i]*r, sphereZ[i]*r, u, v);
u+=iu;
}
vertex(0, -r, 0,u,0);
vertex(sphereX[0]*r, sphereY[0]*r, sphereZ[0]*r, u, v);
endShape();
// Middle rings
int voff = 0;
for(int i = 2; i < sDetail; i++) {
v1=v11=voff;
voff += sDetail;
v2=voff;
u=0;
beginShape(TRIANGLE_STRIP);
texture(t);
for (int j = 0; j < sDetail; j++) {
vertex(sphereX[v1]*r, sphereY[v1]*r, sphereZ[v1++]*r, u, v);
vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2++]*r, u, v+iv);
u+=iu;
}
// Close each ring
v1=v11;
v2=voff;
vertex(sphereX[v1]*r, sphereY[v1]*r, sphereZ[v1]*r, u, v);
vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2]*r, u, v+iv);
endShape();
v+=iv;
}
u=0;
// Add the northern cap
beginShape(TRIANGLE_STRIP);
texture(t);
for (int i = 0; i < sDetail; i++) {
v2 = voff + i;
vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2]*r, u, v);
vertex(0, r, 0,u,v+iv);
u+=iu;
}
vertex(sphereX[voff]*r, sphereY[voff]*r, sphereZ[voff]*r, u, v);
endShape();
}
void renderGlobe() {
pushMatrix();
translate(width/2, height/2, pushBack);
pushMatrix();
noFill();
stroke(255,200);
strokeWeight(2);
smooth();
popMatrix();
lights();
pushMatrix();
rotateX( radians(-rotationX) );
rotateY( radians( - rotationY) );
fill(200);
noStroke();
textureMode(IMAGE);
texturedSphere(globeRadius, texture);
popMatrix();
popMatrix();
rotationX += velocityX;
rotationY += velocityY;
velocityX *= 0.95;
velocityY *= 0.95;
}
This code below is the Control class:
/*
Kepler Visualization - Controls
GUI controls added by Lon Riesberg, Laboratory for Atmospheric and Space Physics
lon@ieee.org
April, 2012
Current release consists of a vertical slider for zoom control. The slider can be toggled
on/off by pressing the 'c' key.
Slide out controls that map to the other key bindings is currently being implemented and
will be released soon.
*/
class Controls {
int barWidth;
int barX; // x-coordinate of zoom control
int minY, maxY; // y-coordinate range of zoom control
float minZoomValue, maxZoomValue; // values that map onto zoom control
float valuePerY; // zoom value of each y-pixel
int sliderY; // y-coordinate of current slider position
float sliderValue; // value that corresponds to y-coordinate of slider
int sliderWidth, sliderHeight;
int sliderX; // x-coordinate of left-side slider edge
Controls () {
barX = 40;
barWidth = 15;
minY = 40;
maxY = minY + height/3 - sliderHeight/2;
minZoomValue = height - height;
maxZoomValue = height; // 300 percent
valuePerY = (maxZoomValue - minZoomValue) / (maxY - minY);
sliderWidth = 25;
sliderHeight = 10;
sliderX = (barX + (barWidth/2)) - (sliderWidth/2);
sliderValue = minZoomValue;
sliderY = minY;
}
void render() {
// strokeWeight(1.5);
strokeWeight(1);
// stroke(105, 105, 105); // fill(0xff33ff99);
// stroke(0xff33ff99); // fill(0xff33ff99); 0xffff0000
stroke(0xffff0000);
// zoom control bar
fill(0, 0, 0, 0);
rect(barX, minY, barWidth, maxY-minY);
// slider
// fill(105, 105, 105); //0x3300FF00
fill(0xffff0000); // 0xff33ff99//0x3300FF00
rect(sliderX, sliderY, sliderWidth, sliderHeight);
}
float getZoomValue(int y) {
if ((y >= minY) && (y <= (maxY - sliderHeight/2))) {
sliderY = (int) (y - (sliderHeight/2));
if (sliderY < minY) {
sliderY = minY;
}
sliderValue = (y - minY) * valuePerY + minZoomValue;
}
return sliderValue;
}
void updateZoomSlider(float value) {
int tempY = (int) (value / valuePerY) + minY;
if ((tempY >= minY) && (tempY <= (maxY-sliderHeight))) {
sliderValue = value;
sliderY = tempY;
}
}
boolean isZoomSliderEvent(int x, int y) {
int slop = 50; // number of pixels above or below slider that's acceptable. provided for ease of use.
int sliderTop = (int) (sliderY - (sliderHeight/2)) - slop;
int sliderBottom = sliderY + sliderHeight + slop;
return ((x >= sliderX) && (x <= (sliderX + sliderWidth)) && (y >= sliderTop) && (y <= sliderBottom) || draggingZoomSlider );
}
}
This is the HorizontalControl class. I don’t know how else to add a horizontal slider.
/*
I modified this so the slider is horizontal. That gives me a vertical for
tweaking altitude and horizontal for right ascension/longitude
*/
/*
Kepler Visualization - Controls
GUI controls added by Lon Riesberg, Laboratory for Atmospheric and Space Physics
lon@ieee.org
April, 2012
Current release consists of a vertical slider for zoom control. The slider can be toggled
on/off by pressing the 'c' key.
Slide out controls that map to the other key bindings is currently being implemented and
will be released soon.
*/
class HorizontalControl {
int barHeight;
int barY; // y-coordinate of zoom control
int minX, maxX; // x-coordinate range of zoom control
float minZoomValue, maxZoomValue; // values that map onto zoom control
float valuePerX; // zoom value of each y-pixel
int sliderY; // y-coordinate of current slider position
float sliderValue; // value that corresponds to y-coordinate of slider
int sliderWidth, sliderHeight;
int sliderX; // x-coordinate of left-side slider edge
HorizontalControl () {
barY = 15; //40;
barHeight = 40; //15;
minX = 40;
maxX = minX + width/3 - sliderWidth/2;
minZoomValue = texture.width - texture.width;
maxZoomValue = texture.width; // 300 percent
valuePerX = (maxZoomValue - minZoomValue) / (maxX - minX);
sliderWidth = 10; //25;
sliderHeight = 25; //10;
// sliderY = (barY + (barHeight/2)) - (sliderHeight/2);
sliderY = (barY - (sliderHeight/2)) + (barHeight/2);
sliderValue = minZoomValue;
sliderX = minX;
}
void render() {
pushMatrix();
// strokeWeight(1.5);
strokeWeight(1);
// stroke(105, 105, 105); // fill(0xff33ff99);
// stroke(0xff33ff99); // fill(0xff33ff99); 0xffff0000
stroke(0xffff0000);
// zoom control bar
fill(0, 0, 0, 0);
rect(minX,barHeight + height - height/4,maxX-minX, barY );
// rect(maxX-minX, barHeight/2,minX,barY + height - height/4 );
// slider
// fill(105, 105, 105); //0x3300FF00
fill(0xffff0000); // 0xff33ff99//0x3300FF00
rect(sliderX, sliderY + height - height/4 + sliderHeight/2 , sliderWidth, sliderHeight);
popMatrix();
}
float getZoomValue(int x) {
if ((x >= minX) && (x <= (maxX - sliderWidth/2))) {
sliderX = (int) (x - (sliderWidth/2));
if (sliderX < minX) {
sliderX = minX;
}
sliderValue = (x - minX) * valuePerX + minZoomValue;
}
return sliderValue;
}
void updateZoomSlider(float value) {
int tempX = (int) (value / valuePerX) + minX;
if ((tempX >= minX) && (tempX <= (maxX+sliderWidth))) {
sliderValue = value;
sliderX = tempX;
}
}
/* boolean isZoomSliderEvent(int x, int y) {
int slop = 50; // number of pixels above or below slider that's acceptable. provided for ease of use.
int sliderTop = (int) (sliderY - (sliderHeight/2)) - slop;
int sliderBottom = sliderY + sliderHeight + slop;
return ((x >= sliderX) && (x <= (sliderX + sliderWidth)) && (y >= sliderTop) && (y <= sliderBottom) || draggingZoomSlider );
} */
boolean isZoomSliderEvent(int x, int y) {
int slop = 50; // number of pixels above or below slider that's acceptable. provided for ease of use.
int sliderLeft = (int) (sliderX - (sliderWidth/2)) - slop;
int sliderRight = sliderX + sliderWidth + slop;
// return ((y >= sliderY + height - height/4) && (y <= (sliderY + height - height/4 + sliderHeight)) && (x >= sliderLeft) && (x <= sliderRight) || draggingZoomSlider );
return ((y >= sliderY + height - height/4 - sliderHeight/2) && (y <= (sliderY + height - height/4 + sliderHeight*2 )) && (x >= sliderLeft) && (x <= sliderRight) || draggingZoomSlider );
}
}