Skip to content

Instantly share code, notes, and snippets.

@SpineyPete
Last active April 11, 2022 18:40
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 SpineyPete/f0ba818865f6e9d4cffa5caae6eac695 to your computer and use it in GitHub Desktop.
Save SpineyPete/f0ba818865f6e9d4cffa5caae6eac695 to your computer and use it in GitHub Desktop.
Concentric Hemisphere Point Generator (Processing 3 Sketch)
// Visualization of Shirley and Chui's Concentric Mapping
// projected onto a hemisphere -- e.g. for irradiance tracing.
// The points are drawn to the window and C code is output to the console.
// This import is needed since Processing 4.
// In Processing complains about it not finding it:
// In the menubar go to to sketch -> import library -> add library,
// enter javafx in the searchbox and download it.
import processing.javafx.*;
// ===========================================================
// The amount of points is the square of this number
int POINTS_PER_AXIS = 16;
// Whether to generate a cosine or uniform covering
// of the hemisphere.
boolean COSINE_DISTRIBUTION = true;
// Whether to skip the ring at the base of the hemisphere.
// (where cos(theta) == 0)
boolean SKIP_BASE_RING = true;
// ===========================================================
void circle(float x, float y, float r) {
ellipse(x, y, r*2, r*2);
}
void xcross(float x, float y, float r) {
line(x-r, y-r, x+r, y+r);
line(x+r, y-r, x-r, y+r);
}
void setup() {
size(600, 600, FX2D);
background(255);
noLoop();
}
PVector MapToConcentricHemisphere(float s, float t, boolean isCosine) {
// Adapted from "A Low Distortion Map Between Disk and Square"
// By Peter Shirley and Kenneth Chiu
assert(s >= 0.0 && s <= 1.0);
assert(t >= 0.0 && t <= 1.0);
// Avoid float and sqrt() problems
if (s == 0.0) { s = 0.0001; }
else if (s == 1.0) { s = 0.9999; }
if (t == 0.0) { t = 0.0001; }
else if (t == 1.0) { t = 0.9999; }
// First check which quadrant the input coordinate
// lies in, map it to it's concentric equivalent in
// polar coordinates, with phi r and theta phi.
float phi, theta;
float a = 2*s - 1;
float b = 2*t - 1;
if (a > -b) {
if (a > b) {
// Quadrant 1, also abs(a) > abs(b).
phi = a;
theta = (PI / 4) * (b / a);
} else {
// Quadrant 2, also abs(b) > abs(a).
phi = b;
theta = (PI / 4) * (2 - (a / b));
}
} else {
if (a < b) {
// Quadrant 3, also abs(a) >= abs(b) and a != 0.
phi = -a;
theta = (PI/4) * (4 + (b/a));
} else {
// Quadrant 4, also abs(b) >= abs(a),
// but a == 0 and b == 0 could occur.
phi = -b;
theta = b == 0 ? 0 : (PI/4) * (6 - (a/b));
}
}
// Convert to cartesian coordinates.
float u, v;
u = phi * cos(theta);
v = phi * sin(theta);
// Project from disk to hemisphere.
float x, y, z, r;
r = sqrt(u*u + v*v);
if (isCosine) {
x = phi * cos(theta);
y = phi * sin(theta);
z = sqrt(1.0 - r*r);
} else { // uniform
x = u * sqrt(2.0 - r*r);
y = v * sqrt(2.0 - r*r);
z = 1.0 - r*r;
}
return new PVector(x, y, z);
}
ArrayList<PVector> ConcentricHemisphere(int points_per_axis, boolean isCosine, boolean skip) {
assert(points_per_axis > 1);
ArrayList<PVector> result = new ArrayList();
int begin, end;
if (skip) {
points_per_axis += 1;
begin = 1;
end = points_per_axis;
} else {
points_per_axis -= 1;
begin = 0;
end = points_per_axis + 1;
}
for (int y = begin; y < end; y++) {
for (int x = begin; x < end; x++) {
float _x = float(x)/points_per_axis;
float _y = float(y)/points_per_axis;
result.add(MapToConcentricHemisphere(_x, _y, isCosine));
}
}
return result;
}
void draw() {
background(125, 130, 120);
strokeWeight(2);
float radius = 280;
ArrayList<PVector> hemisphere = ConcentricHemisphere(
POINTS_PER_AXIS, COSINE_DISTRIBUTION, SKIP_BASE_RING);
noFill();
stroke(255);
circle(width/2, height/2, radius);
for (int i = 0; i < hemisphere.size(); i++) {
float x = hemisphere.get(i).x;
float y = hemisphere.get(i).y;
float z = hemisphere.get(i).z;
// Generate some copypasta C...
if (i == 0) {
print(
"#define DOME_" +
(COSINE_DISTRIBUTION ? "COSINE_" : "UNIFORM_") +
(POINTS_PER_AXIS*POINTS_PER_AXIS) +
(SKIP_BASE_RING ? "X " : " ") +
"{\\\n");
}
print("\t" + x + ", " + y + ", " + z);
if (i == hemisphere.size()-1) {
print(" };");
} else {
print(",\\\n");
}
float px = width/2 + radius*x;
float py = height/2 + radius*y;
fill(
floor( (x + 1.0)/2.0*255.0 + 0.5 ),
floor( (y + 1.0)/2.0*255.0 + 0.5 ),
floor( (z*0.5 + 0.5)*255 + 0.5 ));
// stroke(0);
noStroke();
circle(px, py, 15);
fill(0, 0, 0);
text(i, px-7, py+6);
fill(255, 255, 255);
text(i, px-8, py+5);
}
}
@SpineyPete
Copy link
Author

points

@jdolan
Copy link

jdolan commented Apr 11, 2022

@SpineyPete I'd love to try using this tool to bake lighting direction into the alpha channel of lightmaps, but I'm not sure how to run this. I see we have a 64 point version of this saved in quemap, but I'd love to use the 255 point version for maximum precision. Would you be able to share the C points array for DOME_COSINE_255X?

@SpineyPete
Copy link
Author

@jdolan
It's a Processing script, which is like an IDE for creative coding.
https://processing.org/download

Just download it, and paste the code into it when you run.
I had to edit it for the 4.0 version: you might need to fetch javafx with the builtin package manager, I added a comment above the import.

C code gets dumped to the console.

@jdolan-chwy
Copy link

Thanks dude! Works mint! Hope you are well 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment