based on Stereographic by @andrewl
- made image black & white (removed light grey in half the polygons)
- tried to optimize pathing (much less back and forth for faster drawing)
Log in to post a comment.
// created by Andrew Lamoureux (andrewl) - 2019
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
const nlats = 32
const nlongs = 64
const nverts = 1 + nlats*nlongs + 1
const radius = 90
Canvas.setpenopacity(0.5);
const turtle = new Turtle();
/* get, rotate the sphere */
vertices = []
polyidxs = []
gen_sphere()
for(i=0; i<vertices.length; ++i) {
var [x,y,z] = vertices[i]
var [x,y,z] = rotx(x,y,z,.3)
var [x,y,z] = roty(x,y,z,1.2)
vertices[i] = [x,y,z]
}
/* convert all 3d sphere points to 2d plane points
(this is the stereographic projection) */
points2d = []
for(i=0; i<vertices.length; ++i) {
var [x,y,z] = vertices[i]
if(z == 1) {
points2d.push(null)
continue
}
x_ = (.41*radius)* x/(radius-z)
y_ = (.41*radius)* y/(radius-z)
points2d.push([x_,y_])
}
function walk(fan)
{
fills = [1,8]
/* draw one "fan" of blade polygons */
for(blade=0; blade<nlongs; ++blade) {
i = fan*nlongs + blade
poly = []
for(j=0; j<polyidxs[i].length; ++j)
poly.push(points2d[polyidxs[i][j]])
if(inbounds(poly))
polyfill(poly, fills[(fan+blade)%2])
}
return fan<nlats
}
//------------------------------------------------------------------------------
// 3d rotations
//------------------------------------------------------------------------------
function rotx(x,y,z, angle)
{
s = Math.sin(angle)
c = Math.cos(angle)
return [x, c*y-s*z, s*y+c*z]
}
function roty(x,y,z, angle)
{
s = Math.sin(angle)
c = Math.cos(angle)
return [c*x+s*z, y, -s*x+c*z]
}
function rotz(x,y,z, angle)
{
s = Math.sin(angle)
c = Math.cos(angle)
return [c*x-s*y, s*x+c*y, z]
}
//------------------------------------------------------------------------------
// 3d object
//------------------------------------------------------------------------------
function gen_sphere()
{
anglediv = Math.PI/(nlats + 1)
// south pole
vertices.push([0,-radius,0])
// latitudes/longitudes
for(anglestep=1; anglestep<(nlats+1); ++anglestep) {
angle = 3*Math.PI/2 + (anglestep*anglediv)
x = radius*Math.cos(angle)
y = radius*Math.sin(angle)
z = 0
// rotate about y
for(si=0; si<nlongs; ++si) {
spin = si/nlongs*2*Math.PI
var [x2,y2,z2] = roty(x,y,z, spin)
vertices.push([x2,y2,z2])
}
}
// north pole
vertices.push([0,radius,0])
// build all the polygons
for(i=0; i<nlongs; ++i)
polyidxs.push([0,i+1,(i+1)%nlongs + 1])
for(lat=0; lat<nlats-1; ++lat) {
base = 1+lat*nlongs
for(idx=0; idx<nlongs; ++idx) {
polyidxs.push(
[base+idx, base+(idx+1)%nlongs,
base+(idx+1)%nlongs+nlongs, base+idx+nlongs])
}
}
base = nverts-1-nlongs
for(idx=0; idx<nlongs; ++idx)
polyidxs.push([base+idx, base+(idx+1)%nlongs, nverts-1])
}
//------------------------------------------------------------------------------
// etc.
//------------------------------------------------------------------------------
function line(x0,y0,x1,y1)
{
//console.log('line('+x0+','+y0+','+x1+','+y1+')')
turtle.penup()
turtle.goto(x0,y0)
turtle.pendown()
turtle.goto(x1,y1)
}
function dot(x,y)
{
//console.log('dot('+x0+','+y0+','+x1+','+y1+')')
turtle.penup()
turtle.goto(x,y)
turtle.pendown()
turtle.goto(x+1,y)
turtle.goto(x+1,y+1)
turtle.goto(x,y+1)
turtle.goto(x,y)
}
function inbounds(poly)
{
for(i=0; i<poly.length; ++i) {
var [x,y] = [poly[i][0], poly[i][1]]
if(x>=-100 && x<=100 && y>=-100 && y<=100)
return true
}
return false
}
/* scan-line polygon fill
WARNNG: basic implementation, no optimizations here */
function polyfill(points, lrepeat)
{
if(lrepeat==1) return;
lines = []
scan_start = 100
scan_end = -100
for(i=0; i<points.length; i++) { /* collect lines from points */
p = points[i]
q = points[(i+1)%points.length]
r = points[(i+2)%points.length]
if(!(p && q && r))
continue
line(p[0], p[1], q[0], q[1])
if(p[1] == q[1]) continue /* no horizontals */
m = null /* slope */
if(p[0] != q[0])
m = (1.0*q[1]-p[1])/(q[0]-p[0])
eflag = false /* endpoint flag */
if((q[1]-p[1] > 0) != (q[1]-r[1] >= 0))
eflag = true
var [ylo, yhi] = [p[1], q[1]] /* y's span */
if(ylo > yhi)
[ylo,yhi]=[yhi,ylo]
scan_start = Math.min(scan_start, ylo)
scan_end = Math.max(scan_end, yhi)
lines.push({'ylo':ylo,'yhi':yhi,'p':p,'q':q,'m':m,'eflag':eflag})
}
/* round to magic boundary */
tmp = scan_start
scan_start = Math.floor((scan_start * 1000000)/390625) * 0.390625
for(y=scan_start; y<=scan_end; y+=.1953125) { /* scan */
intersects = [] /* collect intersections */
for(l of lines) {
if(y<l['ylo'] || y>l['yhi'])
continue
x = null
if(l['m']==null)
x = l['p'][0]
else
x = (y-l['p'][1])/l['m'] + l['p'][0]
if(! (y==l['q'][1] && l['eflag']) )
intersects.push(x)
}
intersects.sort() /* draw intersections */
oldy=0;
for(i=0; i<intersects.length; i+=2) {
x0 = intersects[i]
x1 = intersects[i+1]
for(j=0; j<lrepeat; ++j) {
if(j==0) oldy=y;
else if(y==oldy) continue;
if((j%2)==0) {
line(x0,y,x1,y)
} else {
line(x1,y,x0,y)
}
}
}
}
}