Last active
February 19, 2025 17:13
-
-
Save companje/26dd86c60e86d9044f8a93918a863ecf to your computer and use it in GitHub Desktop.
Rotation lat/lon Sphere and unwarp to texture, cubemap, lens, projections etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.apache.commons.math3.geometry.euclidean.threed.Rotation; | |
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; | |
PGraphics dome, ortho3D, lens, cubemap[] = new PGraphics[6]; | |
PShape globe; | |
PShader shader; | |
PImage tex; | |
float h=512, d=h, hd2=h/2, r=hd2; | |
int cubemapSize=512; | |
Rotation qTo = new Rotation(new Vector3D(0, 0, 1), 0); | |
Rotation qNow = new Rotation(new Vector3D(0, 0, 1), 0); | |
double zRotation; | |
float progress = .5; | |
float zoomScaler = .5; | |
boolean rotationEnabled = true; | |
boolean locationsVisible = true; | |
boolean isCamInSphere = false; | |
GeoPoint nl = new GeoPoint("nl", 52.37, 4.91, color(255, 0, 0)); | |
GeoPoint ny = new GeoPoint("ny", 40.79, -73.96, color(255, 0, 0)); | |
GeoPoint jp = new GeoPoint("jp", 36.14, 137.86, color(0, 255, 0)); | |
GeoPoint north = new GeoPoint("north", 90, 0, color(0, 0, 255)); | |
GeoPoint south = new GeoPoint("south", -90, 0, color(255, 0, 255)); | |
GeoPoint west = new GeoPoint("west", 0, -90, color(0, 255, 255)); | |
GeoPoint east = new GeoPoint("east", 0, 90, color(255, 255, 0)); | |
GeoPoint india = new GeoPoint("india", 22, 77, color(0)); | |
GeoPoint front = new GeoPoint("front", 0, 0, color(255)); | |
GeoPoint places[] = {front, nl, ny, india, jp, north, south}; | |
GeoPoint selectedLocation = null; | |
void setup() { | |
size(1536, 512, P3D); | |
if (h!=height) throw new Error("test"); | |
//tex = createImage(4096, 2048, RGB); | |
tex = createImage(1024, 512, RGB); | |
dome = createGraphics((int)h, (int)h, P3D); | |
ortho3D = createGraphics((int)h, (int)h, P3D); | |
lens = createGraphics((int)h/2, (int)h/2, P3D); | |
globe = createShape(SPHERE, hd2); | |
globe.rotateY(HALF_PI); | |
globe.setStroke(false); | |
globe.setTexture(loadImage("earth.jpg")); //https://gitc.earthdata.nasa.gov/wms/epsg4326/best/?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&BBOX=-90,-180,90,180&CRS=EPSG:4326&WIDTH=2048&HEIGHT=1024&LAYERS=Blue%20Marble&STYLES=&FORMAT=image/jpeg&DPI=144&MAP_RESOLUTION=144&FORMAT_OPTIONS=dpi:144&TRANSPARENT=TRUE&.jpg | |
shader = loadShader("waterworld.glsl"); | |
shader.set("heightmap", loadImage("earth4k_elevation_red_alpha.png")); | |
shader.set("palette", loadImage("palette-org.png")); | |
shader.set("borders", loadImage("borders2k.png")); | |
} | |
void draw() { | |
background(0); | |
shader.set("progress", progress); | |
qNow = lerp(qNow, qTo, .1); | |
renderTexture(cubemap, tex); | |
image(tex, h, 0, 2*h, h); | |
renderOrtho(ortho3D); | |
image(ortho3D, 0, 0); | |
//renderDome(dome); | |
//image(dome, 0, 0); | |
drawLens(); | |
drawTarget(); | |
} | |
void drawLens() { | |
renderLens(lens); | |
pushMatrix(); | |
translate(h+50, 50); | |
rect(0, 0, lens.width, lens.height); | |
image(lens, 0, 0); | |
popMatrix(); | |
} | |
void drawTarget() { | |
noFill(); | |
strokeWeight(2); | |
stroke(0); | |
pushMatrix(); | |
translate(h/2, h/2); | |
ellipse(0, 0, 20, 20); | |
for (int i=0; i<4; i++) { | |
line(5, 0, 15, 0); | |
rotate(HALF_PI); | |
} | |
popMatrix(); | |
} | |
void render(PGraphics pg) { | |
if (rotationEnabled) applyRotation(pg, qNow); | |
pg.background(0); | |
pg.shader(shader); | |
pg.shape(globe); | |
pg.resetShader(); | |
pg.fill(255, 255, 0); | |
pg.textAlign(CENTER); | |
pg.textSize(30); | |
if (locationsVisible) renderLocations(pg); | |
} | |
void renderLocations(PGraphics pg) { | |
float o = isCamInSphere ? -1 : 1; //direction for z-movement towards camera | |
for (GeoPoint l : places) { | |
pg.pushMatrix(); | |
pg.noStroke(); | |
pg.rotateY(radians(l.lon)); | |
pg.rotateX(radians(l.lat)); | |
pg.translate(0, 0, r + o*5); | |
for (int y=-1; y<=1; y++) { | |
for (int x=-1; x<=1; x++) { | |
pg.textAlign(CENTER, CENTER); | |
pg.textSize(24); | |
pg.fill(255); | |
pg.text(l.name, x, y-25); | |
pg.translate(0, 0, o*.01); | |
} | |
} | |
pg.translate(0, 0, o*.01); | |
pg.fill(l.clr); | |
pg.text(l.name, 0, -25); | |
pg.stroke(255); | |
pg.strokeWeight(3); | |
pg.ellipse(0, 0, 15, 15); | |
pg.popMatrix(); | |
} | |
} | |
void mouseDragged() { | |
if (mouseX<h && mouseY<h) { | |
Vector3D from = getMouseOnSphere(pmouseX, pmouseY, h, h); | |
Vector3D to = getMouseOnSphere(mouseX, mouseY, h, h); | |
drag(from, to); | |
} | |
} | |
void selectLocation(GeoPoint p) { | |
selectedLocation = p; | |
qTo = getRotationToPoint(p); | |
println(getZRotationAngle(qTo)); | |
} | |
void mouseWheel(MouseEvent event) { | |
if (keyPressed && key=='t') { | |
progress += event.getCount()*.00001f; | |
progress = constrain(progress, 0, 1); | |
} | |
if (keyPressed && key=='z') { | |
zoomScaler -= event.getCount()*.001f; | |
zoomScaler = constrain(zoomScaler, 0, 1); | |
} | |
} | |
void keyPressed() { | |
if (key=='s') { | |
renderTexture(cubemap, tex); | |
tex.save("tex.png"); | |
println("saved"); | |
} | |
if (key>='0' && key<='6') { | |
selectLocation(places[key-'0']); | |
} | |
if (key=='[') { | |
Rotation z_rot = new Rotation(front, .1, RotationConvention.VECTOR_OPERATOR); | |
qTo = qTo.applyTo(z_rot); | |
} | |
if (key==']') { | |
Rotation z_rot = new Rotation(front, -.1, RotationConvention.VECTOR_OPERATOR); | |
qTo = qTo.applyTo(z_rot); | |
} | |
} | |
static class GeoPoint extends Vector3D { | |
float lat, lon; | |
String name; | |
color clr; | |
GeoPoint(String name, float lat, float lon, color clr) { | |
super( | |
cos(radians(lat)) * sin(-radians(lon)), | |
sin(radians(lat)), | |
cos(radians(lat)) * cos(-radians(lon))); | |
this.lat = lat; | |
this.lon = lon%180; //180 becomes 0 | |
this.name = name; | |
this.clr = clr; | |
} | |
static GeoPoint fromRotation(Rotation qNow, Vector3D front) { | |
Vector3D inverse = qNow.applyInverseTo(front); | |
float lat = degrees(asin((float)inverse.getY())); | |
float lon = degrees(-atan2((float)inverse.getX(), (float)inverse.getZ())); //X&Z swapped | |
return new GeoPoint("", lat, lon, #ffffff); | |
} | |
String toString() { | |
String s = nf1(getX())+","+nf1(getY())+","+nf1(getZ()); | |
return "latlon="+nf1(lat)+","+nf1(lon) + " " + "xyz=("+s+")"; | |
} | |
} | |
void renderLens(PGraphics pg) { | |
locationsVisible = false; | |
rotationEnabled = true; | |
pg.beginDraw(); | |
pg.perspective(); | |
float eyeZ = map(zoomScaler, 0, 1, 310, 400); //280 is echte minimum | |
pg.camera(0, 0, eyeZ, 0, 0, 0, 0, 1, 0); //340 | |
render(pg); | |
pg.endDraw(); | |
} | |
void renderOrtho(PGraphics pg) { | |
locationsVisible = true; | |
rotationEnabled = true; | |
pg.beginDraw(); | |
pg.ortho(-r,r,-r,r,-10,r); | |
pg.camera(0, 0, r, 0, 0, 0, 0, 1, 0); | |
render(pg); | |
pg.endDraw(); | |
} | |
void renderDome(PGraphics pg) { | |
locationsVisible = true; | |
rotationEnabled = true; | |
isCamInSphere = true; | |
float distToCam = 800; //=extreme //regular: 1900; | |
pg.beginDraw(); | |
pg.perspective(atan(hd2/distToCam)*2, 1, distToCam, 10000); //fovy, aspect, zNear, zFar | |
pg.camera(0, 0, -distToCam, 0, 0, 0, 0, 1, 0); | |
pg.scale(-1, 1, 1); | |
render(pg); | |
pg.endDraw(); | |
isCamInSphere = false; | |
} | |
void renderTexture(PGraphics pg[], PImage tex) { //via cubemap | |
locationsVisible = true; | |
rotationEnabled = false; | |
isCamInSphere = true; // deze zou aangepast kunnen worden dat de camera buiten de bol zit. of zit ie dat? ivm zichtbaarheid tekst | |
int c[][] = { | |
{0, 0, 1, 0, -1, 0}, | |
{0, 0, -1, 0, 1, 0}, | |
{0, -1, 0, 1, 0, 0}, | |
{0, 1, 0, -1, 0, 0}, | |
{1, 0, 0, 0, -1, 0}, | |
{-1, 0, 0, 0, 1, 0}}; | |
for (int i = 0; i < 6; i++) { | |
if (pg[i]==null) pg[i] = createGraphics(cubemapSize, cubemapSize, P3D); | |
pg[i].beginDraw(); | |
pg[i].camera(0, 0, 0, c[i][0], c[i][1], c[i][2], c[i][3], c[i][4], c[i][5]); | |
pg[i].perspective(HALF_PI, 1.0, 1, 1000); | |
render(pg[i]); | |
pg[i].endDraw(); | |
pg[i].loadPixels(); | |
} | |
cubemapToEquirectangular(pg, tex); | |
isCamInSphere = false; | |
} | |
PImage cubemapToEquirectangular(PGraphics pg[], PImage tex) { | |
tex.loadPixels(); | |
for (int y=0, w=tex.width, h=tex.height; y < h; y++) { | |
for (int x = 0; x < w; x++) { | |
float u = map(x, 0, w, -PI, PI); | |
float v = map(y, 0, h, HALF_PI, -HALF_PI); | |
PVector dir = new PVector(cos(v) * cos(u), sin(v), cos(v) * sin(u)); | |
tex.pixels[y * w + x] = sampleFromCubemap(pg, dir); | |
} | |
} | |
tex.updatePixels(); | |
return tex; | |
} | |
color sampleFromCubemap(PGraphics pg[], PVector dir) { | |
float ax = abs(dir.x), ay = abs(dir.y), az = abs(dir.z); | |
boolean xGreatest = ax >= ay && ax >= az, yGreatest = ay >= az; | |
int face = xGreatest ? (dir.x > 0 ? 0 : 1) : | |
yGreatest ? (dir.y > 0 ? 2 : 3) : | |
dir.z > 0 ? 4 : 5; | |
float uc = xGreatest ? dir.z / ax : | |
yGreatest ? dir.x / ay : | |
-dir.x / az; | |
float vc = (face % 2 == 0 ? 1 : -1) * | |
(xGreatest ? dir.y / ax : | |
yGreatest ? dir.z / ay : | |
dir.y / az); | |
int px = int(map(uc, -1, 1, 0, pg[face].width - 1)); | |
int py = int(map(vc, -1, 1, 0, pg[face].height - 1)); | |
return pg[face].pixels[py*pg[face].width+px]; | |
} | |
double getZRotationAngle(Rotation rotation) { | |
double[][] m = rotation.getMatrix(); // Haal de rotatiematrix op | |
return Math.atan2(m[1][0], m[0][0]); // Bereken de yaw (rotatie rond de Z-as) atan2(sinYaw, cosYaw) | |
} | |
Rotation getRotationToPoint(Vector3D point) { | |
//dit lijkt aardig te werken!!! Gek genoeg bij india niet helemaal | |
Rotation r = new Rotation(point, front); | |
double a = -getZRotationAngle(r); //yaw | |
Rotation z_rot = new Rotation(front, a, RotationConvention.VECTOR_OPERATOR); | |
r = r.applyTo(z_rot); | |
return r; | |
} | |
void applyRotation(PGraphics pg, Rotation rotation) { | |
Vector3D axis = rotation.getAxis(); | |
float angle = (float)rotation.getAngle(); | |
pg.rotate(-angle, (float)axis.getX(), (float)axis.getY(), (float)axis.getZ()); | |
} | |
Rotation lerp(Rotation start, Rotation end, float t) { | |
return new Rotation(start.getQ0() + t * (end.getQ0() - start.getQ0()), | |
start.getQ1() + t * (end.getQ1() - start.getQ1()), | |
start.getQ2() + t * (end.getQ2() - start.getQ2()), | |
start.getQ3() + t * (end.getQ3() - start.getQ3()), | |
true); | |
} | |
Vector3D getMouseOnSphere(float x, float y, float w, float h) { | |
x = map(x, 0, w, -1, 1); | |
y = map(y, 0, h, -1, 1); | |
float r = x*x+y*y; | |
return new Vector3D(x, y, r>1 ? 0 : sqrt(1-r)).normalize(); | |
} | |
void drag(Vector3D from, Vector3D to) { | |
Vector3D axis = Vector3D.crossProduct(from, to); | |
if (axis.getNorm() > 0) { | |
axis = axis.normalize(); | |
double angle = -Math.acos(Vector3D.dotProduct(from, to)); | |
qTo = qTo.compose(new Rotation(axis, angle), RotationConvention.VECTOR_OPERATOR); | |
} | |
} | |
static String nf1(double f) { | |
if (f==int((float)f)) return int((float)f)+""; | |
else return nf((float)f, 0, 1).replace(",", "."); | |
} |
Author
companje
commented
Feb 19, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment