Skip to content

Instantly share code, notes, and snippets.

@companje
Last active January 3, 2019 16:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save companje/9ebc00b5511a6571d7d8d1a3bd7802c3 to your computer and use it in GitHub Desktop.
Save companje/9ebc00b5511a6571d7d8d1a3bd7802c3 to your computer and use it in GitHub Desktop.
Making progress with TopoGame
import hypermedia.net.*;
UDP udp;
PShape sphere;
Game game;
Globe globe;
Ring ring;
PFont defaultFont;
void setup() {
size(800, 800, P3D);
udp = new UDP(this, 8888);
udp.listen(true);
ring = new Ring();
ring.setup();
globe = new Globe();
globe.setup();
game = new Game(this);
game.setup();
defaultFont = loadFont("SansSerif.plain-13.vlw");
textFont(defaultFont);
}
void draw() {
background(0);
ortho();
translate(width/2, height/2); //center
game.draw();
//game.drawDebug();
}
void mouseDragged() {
PVector from = toSphere(float(pmouseX)/width-.5, float(pmouseY)/width-.5);
PVector to = toSphere(float(mouseX)/width-.5, float(mouseY)/width-.5);
globe.qRel.mult(new Quaternion(from.dot(to), from.cross(to))); //w,xyz
globe.qRel.normalize();
}
void receive( byte[] data ) {
String message = new String( data );
String values[] = split(message, ',');
if (values.length==7) {
for (int i=0; i<3; i++) {
float offset = .7;
float speed = .075 / globe.radius;
float localRotation = radians(45);
float globalRotation = radians(155) - i/3. * TWO_PI;
float x = float(values[i*2+0]);
float y = float(values[i*2+1]);
PVector from = new PVector(offset, 0).rotate(globalRotation);
PVector to = new PVector(x, y).rotate(localRotation);
to.y *= -1;
to.rotate(globalRotation).mult(speed).add(from);
from = toSphere(from);
to = toSphere(to);
globe.qRel.mult(new Quaternion(from.dot(to), from.cross(to))); //w,xyz
globe.qRel.normalize();
}
}
}
class City {
String title,subtitle;
float lat,lon;
}
class Data {
PImage imgOceans, imgCountries, imgContinents;
HashMap<Integer, String> oceans = new HashMap();
HashMap<Integer, String> countries = new HashMap();
HashMap<Integer, String> continents = new HashMap();
ArrayList<City> cities = new ArrayList();
void setup() {
imgCountries = loadImage("countries.png");
imgContinents = loadImage("continents.png");
imgOceans = loadImage("oceans.png");
for (String s : loadStrings("oceans.txt")) {
String items[] = split(s, ",");
oceans.put(unhex(items[0]), items[1]);
}
for (String s : loadStrings("countries.txt")) {
String items[] = split(s, ",");
countries.put(unhex(items[0]), items[1]);
}
for (String s : loadStrings("continents.txt")) {
String items[] = split(s, ",");
continents.put(unhex(items[0]), items[1]);
}
for (String s : loadStrings("cities.txt")) {
String items[] = split(s,",");
City c = new City();
c.title = items[0];
c.lat = float(items[1]);
c.lon = float(items[2]);
c.subtitle = items[3];
}
}
color getColor(float lat, float lon, PImage img) {
int x = (int)map(lon, -180, 180, 0, img.width);
int y = (int)map(lat, 90, -90, 0, img.height);
return img.get(x, y) & 0xffffff;
}
String getTitle(float lat, float lon, PImage img, HashMap list) {
String s = (String)list.get(getColor(lat, lon, img));
return s!=null ? s : "";
}
}
class GameState { // base class
Game game;
GameState(Game game) {
this.game = game;
}
void setup() {
}
void cleanup() {
}
void pause() {
}
void resume() {
}
void update() {
}
void draw() {
}
}
class Game {
Start start = new Start(this);
Intro intro = new Intro(this);
NewQuestion newQuestion = new NewQuestion(this);
FindCity findCity = new FindCity(this);
FindArea findArea = new FindArea(this);
PlaceFound placeFound = new PlaceFound(this);
Finished finished = new Finished(this);
GameOver gameOver = new GameOver(this);
PApplet app;
GameState state;
Heli heli = new Heli();
Data data = new Data();
Game(PApplet app) {
this.app = app;
state = start;
}
void setup() {
data.setup();
heli.setup();
state.setup();
}
void draw() {
state.draw();
}
class Start extends GameState {
Start(Game game) {
super(game);
}
void draw() {
ring.draw();
globe.draw();
heli.heading = globe.heading;
heli.draw();
text("welcome to the game", -width/2+30, -height/2+30);
float lon = globe.qNow.getLongitude();
float lat = globe.qNow.getLatitude();
ring.title = data.getTitle(lat, lon, data.imgCountries, data.countries);
}
}
class Intro extends GameState {
Intro(Game game) {
super(game);
}
}
class NewQuestion extends GameState {
NewQuestion(Game game) {
super(game);
}
}
class FindCity extends GameState {
FindCity(Game game) {
super(game);
}
}
class FindArea extends GameState {
FindArea(Game game) {
super(game);
}
}
class PlaceFound extends GameState {
PlaceFound(Game game) {
super(game);
}
}
class Finished extends GameState {
Finished(Game game) {
super(game);
}
}
class GameOver extends GameState {
GameOver(Game game) {
super(game);
}
}
void drawDebug() {
pushStyle();
pushMatrix();
hint(DISABLE_DEPTH_TEST);
imageMode(CORNER);
translate(-width/2, -height/2);
image(globe.texture, 0, 0, 300, 150);
image(data.imgOceans, 0, 150, 300, 150);
image(data.imgCountries, 0, 300, 300, 150);
image(data.imgContinents, 0, 450, 300, 150);
float lon = globe.qNow.getLongitude();
float lat = globe.qNow.getLatitude();
float x = map(lon, -180, 180, 0, 300);
float y = map(lat, 90, -90, 0, 150);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 5, 5);
ellipse(x, y+150, 5, 5);
ellipse(x, y+300, 5, 5);
ellipse(x, y+450, 5, 5);
fill(255);
text(data.getTitle(lat, lon, data.imgContinents, data.continents), 10, 450+140);
text(data.getTitle(lat, lon, data.imgCountries, data.countries), 10, 300+140);
text(data.getTitle(lat, lon, data.imgOceans, data.oceans), 10, 150+140);
fill(255);
//textSize(20);
float xx=650;
float yy=0;
text("cartesian: " + globe.qNow.getCartesian(), xx, yy+=20);
text("cartesian.length: " +globe.qNow. getCartesian().mag(), xx, yy+=20);
text("lat: " + int(globe.qNow.getLatitude()), xx, yy+=20);
text("lon: " + int(globe.qNow.getLongitude()), xx, yy+=20);
hint(ENABLE_DEPTH_TEST);
popMatrix();
popStyle();
}
}
class Globe {
PShape shape;
PShader shader;
PImage texture;
float radius = 315;
float heading, toHeading;
Quaternion qRel = new Quaternion();
Quaternion qNow = new Quaternion();
void setup() {
sphereDetail(100);
texture = loadImage("earth2k.jpg");
shape = createShape(SPHERE, radius);
shape.setStroke(false);
shape.setTexture(texture);
println("setup");
shader = loadShader("shader.glsl");
}
void update() {
if (!qRel.isIdentity()) { //only update when moved
qNow.mult(qRel);
toHeading = qRel.getHeading();
}
heading = angleLerp(heading, toHeading, .1); //always update
qRel.reset(); //reset movement of this frame
}
void draw() {
update();
pushMatrix();
rotate(qNow);
rotateY(HALF_PI); //rotate texture to latlon=0,0
shader(shader);
shape(shape);
resetShader();
popMatrix();
}
}
class Heli {
PImage blades,body;
int pmillis = 0;
float heading;
float bladeAngle;
void setup() {
blades = loadImage("blades.png");
body = loadImage("body.png");
}
void draw() {
if (millis()-pmillis > 10) {
pmillis = millis();
bladeAngle += TWO_PI/30;
if (bladeAngle>TWO_PI) bladeAngle-=TWO_PI;
}
hint(DISABLE_DEPTH_TEST);
imageMode(CENTER);
pushMatrix();
rotate(heading - HALF_PI);
image(body,0,0);
popMatrix();
pushMatrix();
rotate(bladeAngle); //heading + HALF_PI);
image(blades,0,0);
popMatrix();
hint(ENABLE_DEPTH_TEST);
}
}
static class Quaternion {
float W, X, Y, Z;
Quaternion() {
set(1, 0, 0, 0);
}
Quaternion(float w, float x, float y, float z) {
set(w, x, y, z);
}
Quaternion(float w, PVector v) {
set(w, v.x, v.y, v.z);
}
void set(float w, float x, float y, float z) {
W = w;
X = x;
Y = y;
Z = z;
}
Quaternion mult(Quaternion q) {
float x = q.W * X + q.X * W + q.Y * Z - q.Z * Y;
float y = q.W * Y - q.X * Z + q.Y * W + q.Z * X;
float z = q.W * Z + q.X * Y - q.Y * X + q.Z * W;
float w = q.W * W - q.X * X - q.Y * Y - q.Z * Z;
W = w;
X = x;
Y = y;
Z = z;
return this;
}
Quaternion copy() {
return new Quaternion(W, X, Y, Z);
}
void applyTo(PVector v) {
// nVidia SDK implementation
PVector uv, uuv;
PVector qvec = new PVector(X, Y, Z); //_v.x, _v.y, _v.z);
uv = qvec.cross(v); //uv = qvec ^ v;
uuv = qvec.cross(uv); //uuv = qvec ^ uv;
uv.mult(2.0f * W);
uuv.mult(2.0f);
v.add(uv);
v.add(uuv);
}
Quaternion normalize() {
float norme = sqrt(W*W + X*X + Y*Y + Z*Z);
if (norme == 0.0) {
W = 1.0;
X = Y = Z = 0.0;
} else {
float recip = 1.0/norme;
W *= recip;
X *= recip;
Y *= recip;
Z *= recip;
}
return this;
}
float getAngle() {
float sinhalfangle = sqrt(X*X+Y*Y+Z*Z);
return 2.0 * atan2(sinhalfangle, W);
}
PVector getAxis() {
float sinhalfangle = sqrt(X*X+Y*Y+Z*Z);
if (sinhalfangle>0) {
PVector axis = new PVector(X, Y, Z);
axis.div(sinhalfangle);
return axis;
} else return new PVector(0, 0, 1);
}
/// Set the elements of the Quat to represent a rotation of angle
/// around the axis (x,y,z)
static Quaternion fromRotate( float angle, float x, float y, float z ) {
//angle in Radians!
//const
float epsilon = 0.0000001f;
float length = sqrt( x * x + y * y + z * z ); //was sqrtf
if (length < epsilon) {
// ~zero length axis, so reset rotation to zero.
//*this = ofQuaternion();
return new Quaternion(1, 0, 0, 0);
}
float inversenorm = 1.0f / length;
float coshalfangle = cos( 0.5f * angle );
float sinhalfangle = sin( 0.5f * angle );
float _x = x * sinhalfangle * inversenorm;
float _y = y * sinhalfangle * inversenorm;
float _z = z * sinhalfangle * inversenorm;
float _w = coshalfangle;
return new Quaternion(_x, _y, _z, _w);
}
/// Spherical Linear Interpolation
/// As t goes from 0 to 1, the Quat object goes from "from" to "to"
/// Reference: Shoemake at SIGGRAPH 89
/// See also
/// http://www.gamasutra.com/features/programming/19980703/quaternions_01.htm
static Quaternion slerp(Quaternion from, Quaternion to, float t) {
float epsilon = 0.00001;
float omega, cosomega, sinomega, scale_from, scale_to ;
Quaternion quatTo = to.copy();
// this is a dot product
cosomega = from.X*to.X + from.Y*to.Y + from.Z*to.Z + from.W*to.W;
if ( cosomega < 0.0 ) {
cosomega = -cosomega;
quatTo.X *= -1;
quatTo.Y *= -1;
quatTo.Z *= -1;
quatTo.W *= -1;
}
if ( (1.0 - cosomega) > epsilon ) {
omega = acos(cosomega) ; // 0 <= omega <= Pi (see man acos)
sinomega = sin(omega) ; // this sinomega should always be +ve so
// could try sinomega=sqrt(1-cosomega*cosomega) to avoid a sin()?
scale_from = sin((1.0 - t) * omega) / sinomega ;
scale_to = sin(t * omega) / sinomega ;
} else {
/* --------------------------------------------------
The ends of the vectors are very close
we can use simple linear interpolation - no need
to worry about the "spherical" interpolation
-------------------------------------------------- */
scale_from = 1.0 - t ;
scale_to = t ;
}
//add
return new Quaternion(
from.W * scale_from + quatTo.W * scale_to,
from.X * scale_from + quatTo.X * scale_to,
from.Y * scale_from + quatTo.Y * scale_to,
from.Z * scale_from + quatTo.Z * scale_to
);
}
String toString() {
return W + "," + X + "," + Y + "," + Z;
}
PVector toEulerAngle() { //const Quaterniond& q, double& roll, double& pitch, double& yaw) {
PVector rollPitchYaw = new PVector();
//roll (x-axis rotation)
float sinr_cosp = +2.0 * (W*X + Y*Z);
float cosr_cosp = +1.0 - 2.0 * (X*X + Y*Y);
rollPitchYaw.x = atan2(sinr_cosp, cosr_cosp);
// pitch (y-axis rotation)
float sinp = +2.0 * (W*Y - Z*X);
if (abs(sinp) >= 1)
rollPitchYaw.y = sinp<0 ? -HALF_PI : HALF_PI; //copysign(M_PI / 2, sinp); // use 90 degrees if out of range
else
rollPitchYaw.y = asin(sinp);
// yaw (z-axis rotation)
float siny_cosp = +2.0 * (W*Z + X*Y);
float cosy_cosp = +1.0 - 2.0 * (Y*Y + Z*Z);
rollPitchYaw.z = atan2(siny_cosp, cosy_cosp);
return rollPitchYaw;
}
float getYaw() {
float siny_cosp = +2.0 * (W*Z + X*Y);
float cosy_cosp = +1.0 - 2.0 * (Y*Y + Z*Z);
return atan2(siny_cosp, cosy_cosp);
}
float getLength2() {
return X*X + Y*Y + Z*Z + W*W;
}
PMatrix3D getRotationMatrix() {
//see also:
//https://github.com/processing/processing/blob/master/core/src/processing/core/PMatrix3D.java#L465
//and: openFrameworks: ofMatrix4x4::setRotate(const ofQuaternion& q)
PMatrix3D m = new PMatrix3D(); //new identity matrix created
float length2 = this.getLength2();
if (abs(length2) < EPSILON) {
// m = identity matrix
} else {
float rlength2 = length2 != 1.0 ? 2.0/length2 : 2.0;
float wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2;
// calculate coefficients
x2 = rlength2*X;
y2 = rlength2*Y;
z2 = rlength2*Z;
xx = X * x2;
xy = X * y2;
xz = X * z2;
yy = Y * y2;
yz = Y * z2;
zz = Z * z2;
wx = W * x2;
wy = W * y2;
wz = W * z2;
m.set( 1.0 - (yy + zz), xy + wz, xz - wy, 0,
xy - wz, 1.0 - (xx + zz), yz + wx, 0,
xz + wy, yz - wx, 1.0 - (xx + yy), 0,
0, 0, 0, 1);
}
return m;
}
PVector getCartesian() {
PMatrix3D m = getRotationMatrix();
PVector v = new PVector(0, 0, 1);
return m.mult(v, null);
}
float getLatitude() { //degrees
return degrees(-asin(getCartesian().y));
}
float getLongitude() { //degrees
float lon = degrees(-atan2(getCartesian().z, getCartesian().x))+90;
return (lon>180) ? lon-360 : lon;
}
Quaternion div(Quaternion q) {
mult(new Quaternion().invert(q));
return this;
}
Quaternion invert(Quaternion in) {
float dot = in.getLength2();
dot = dot == 0.0 ? 0.0 : 1.0 / dot;
X = -in.X * dot;
Y = -in.Y * dot;
Z = -in.Z * dot;
W = in.W * dot;
return this;
}
boolean isIdentity() {
return W==1 && X==0 && Y==0 && Z==0;
}
void reset() {
set(1, 0, 0, 0);
}
float getHeading() {
PVector v = new PVector(0, 0, 1);
applyTo(v);
return atan2(v.y, v.x);
}
}
class Ring {
PFont font;
float radius = 420;
String title = "";
void setup() {
font = loadFont("Arial-BoldMT-35.vlw");
}
void draw() {
pushStyle();
noStroke();
fill(128);
ellipse(0, 0, height, height);
fill(255);
textFont(font);
arcTextBoxCenter(title, radius-24-60);
textFont(defaultFont);
popStyle();
}
}
import java.awt.Rectangle;
color hsb(float h, float s, float b) {
if (b==0) return color(0);
if (s==0) return color(b);
h *= 6. / 255;
s /= 255.;
float x = b * (1-s);
float y = b * (1-s*(h-int(h)));
float z = b * (1-s*(1-(h-int(h))));
//float r = (h==0||h==5) ? b : h==1 ? y : (h==4) ? z : x;
switch (int(h)) {
case 0:
return color(b, z, x); //red
case 1:
return color(y, b, x); //green
case 2:
return color(x, b, z);
case 3:
return color(x, y, b); //blue
case 4:
return color(z, x, b);
case 5:
return color(b, x, y); //back to red
}
return color(0);
}
color hsb(float h) {
return hsb(h, 255, 255);
}
void rotate180(PImage img) {
img.loadPixels();
int wh = img.width * img.height;
for (int i=0; i<wh/2; i++) {
color tmp = img.pixels[i];
img.pixels[i] = img.pixels[wh-i-1];
img.pixels[wh-i-1] = tmp;
}
img.updatePixels();
}
PVector clerp(PVector v1, PVector v2, PVector center, float f) { //interpolation on a circle
float r = PVector.sub(v1, center).mag(); //use average magnitude?
PVector v = PVector.lerp(v1, v2, f);
v.sub(center);
v.normalize();
v.mult(r);
v.add(center);
return v;
}
PVector get3PointCircleCenter(float x1, float y1, float x2, float y2, float x3, float y3) {
float ma = (y2-y1)/(x2-x1); //slope
float mb = (y3-y2)/(x3-x2);
float cx = (ma*mb*(y1-y3) + mb*(x1+x2) - ma*(x2+x3)) / (2*(mb-ma) );
float cy = -1*(cx-(x1+x2)/2)/ma + (y1+y2)/2;
return new PVector(cx, cy);
}
PVector get3PointCircleCenter(PVector p1, PVector p2, PVector p3) {
return get3PointCircleCenter(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
}
PVector getCenter(ArrayList<PVector> vectors) {
PVector center = new PVector();
for (PVector v : vectors) {
center.add(v);
}
center.div(vectors.size());
return center;
}
ArrayList<PVector> getReducedPoints(ArrayList<PVector> pointsA, float dist) {
ArrayList<PVector> pointsB = new ArrayList();
if (pointsA.size()==0) return pointsB;
pointsB.add(pointsA.get(0)); //first point
for (int i=1; i<pointsA.size(); i++) {
Boolean hasNeighbourNearby = false;
for (int j=0; j<pointsB.size(); j++) { //check if get(i) far enough from all existing clusters
if (pointsA.get(i).dist(pointsB.get(j)) < dist) {
hasNeighbourNearby = true;
break;
}
}
if (!hasNeighbourNearby) {
pointsB.add(pointsA.get(i));
}
}
return pointsB;
}
//PVector getFarthestPoint(Contour contour, float x, float y) {
// PVector farthest = contour.getConvexHull().getPoints().get(0); //farthest
// for (PVector p : contour.getConvexHull().getPoints()) {
// if (dist(farthest.x, farthest.y, x, y)<dist(p.x, p.y, x, y)) {
// farthest = p;
// }
// }
// return farthest;
//}
PVector getFarthestPoint(ArrayList<PVector> points, float x, float y) {
PVector farthest = points.get(0); //farthest / furthest
for (PVector p : points) {
if (dist(p.x, p.y, x, y) > dist(farthest.x, farthest.y, x, y)) {
farthest = p;
}
}
return farthest;
}
float getCenterToCenterDistance(Rectangle a, Rectangle b) {
return dist((float)a.getCenterX(), (float)a.getCenterY(), (float)b.getCenterX(), (float)b.getCenterY());
}
PVector getClosest(ArrayList<PVector> points, PVector t) {
if (points.size()==0) return null;
PVector closest = points.get(0);
for (PVector p : points) {
if (t.dist(p) < t.dist(closest)) {
closest = p;
}
}
return closest;
}
void arcStrip(float inner, float outer, float start, float end) {
float range=end-start;
float a;
beginShape(TRIANGLE_STRIP);
for (int i=0, n=500; i<n+1; i++) {
a=(i+0.5)/n;
vertex(sin(a*range+start)*inner, cos(a*range+start)*inner);
a=(i+0.0)/n;
vertex(sin(a*range+start)*outer, cos(a*range+start)*outer);
}
endShape();
}
float getArcTextBoxWidth(String txt, float radius) { //returns an angle
float maxAngle = 0;
String lines[] = split(txt, "\n");
for (int l=0; l<lines.length; l++) {
float angle = 0;
radius += textAscent() + textDescent();
for (int i=0; i<lines[l].length(); i++) {
char letter = lines[l].charAt(i);
angle += atan(textWidth(letter) / radius) ;
}
maxAngle = max(angle, maxAngle);
}
return maxAngle;
}
void arcTextBoxLeft(String txt, float radius) {
textAlign(LEFT);
String lines[] = split(txt, "\n");
for (int l=0; l<lines.length; l++) {
float angle = 0;
radius += textAscent() + textDescent(); //tex
for (int i=0; i<lines[l].length(); i++) {
char letter = lines[l].charAt(i);
pushMatrix();
rotate(-angle);
translate(0, radius);
//scale(1, globalScaleY);
text(letter, 0, 0);
popMatrix();
angle += atan(textWidth(letter) / radius) ;
}
}
}
void arcTextBoxCenter(String txt, float radius) {
textAlign(CENTER);
float angle = getArcTextBoxWidth(txt, radius);
pushMatrix();
rotate(angle/2);
arcTextBoxLeft(txt, radius);
popMatrix();
}
void arcTextBoxRight(String txt, float radius) {
textAlign(CENTER);
float angle = getArcTextBoxWidth(txt, radius);
pushMatrix();
rotate(angle);
arcTextBoxLeft(txt, radius);
popMatrix();
}
PShape bendShape(PShape shape, int w, int h, float r, Boolean center, float arcArch) {
float r1 = r;
float r2 = r+h;
int numLines = shape.getVertexCount()/2;
int numCells = numLines-1;
//println("numLines",numLines,numCells);
for (int i=0; i<numLines; i++) {
float f = map(i, 0, numLines-1, 0, 1);
float a1 = f * w/r - (center ? (w/r)/2 : 0);
shape.setVertex(i*2+0, sin(a1)*r1, cos(a1)*r1 - r); // -r = 0,0 at top left
float r3 = lerp(r1, r2, arcArch); //0=arc, 1=arch, .5=in between
float a2 = f * w/r3 - (center ? (w/r3)/2 : 0);
shape.setVertex(i*2+1, sin(a2)*r2, cos(a2)*r2 - r);
}
return shape;
}
PShape shapeFromImage(PImage img, int cellWidth) { //res=cellWidth, numCells dynamic
PShape strip = createShape();
strip.beginShape(QUAD_STRIP);
strip.stroke(255, 255, 0, 50);
strip.noStroke();
int numCells = ceil(img.width / cellWidth);
for (int i=0; i<=numCells; i++) {
float f = float(i)/numCells;
float x = lerp(0, img.width, f);
strip.vertex(x, 0, x, 0);
strip.vertex(x, img.height, x, img.width); //why img.width? Bug in Processing?
}
strip.endShape();
strip.setTexture(img);
return strip;
}
Boolean isClockwise(PVector a, PVector b, PVector c) {
//https://gamedev.stackexchange.com/questions/22133/how-to-detect-if-object-is-moving-in-clockwise-or-counterclockwise-direction
return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x) > 0;
}
Boolean between(float t, float a, float b) {
return (t>=a && t<=b) || (t>=b && t<=a);
}
float smoothstep(float x, float min, float max) {
float t;
t = constrain((x - min) / (max - min), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
PVector toSphere(float x, float y) { //-0.5 ... +0.5
PVector v = new PVector(x, y);
if (v.mag()>=1.0f) v.normalize();
else v.z = sqrt(1.0 - v.mag());
return v;
}
PVector toSphere(PVector v) { //-0.5 ... +0.5
return toSphere(v.x, v.y);
}
float angleLerp(float from, float to, float t) {
float diff = (to - from) % TWO_PI;
return from + (2 * diff % TWO_PI - diff) * t;
}
void rotate(Quaternion q) {
rotate(q.getAngle(), q.getAxis().x, q.getAxis().y, q.getAxis().z);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment