Spiral Triangles

This is an attempt to reproduce an effect from the demo "Intrinsic Gravity" by Still.
The triangles' positions lead your eye towards certain lines while their rotations lead to others.

Log in to post a comment.

// created by Andrew Lamoureux (andrewl) - 2019
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

Canvas.setpenopacity(1);
const turtle = new Turtle();

function walk(i)
{
    nspokes = 22
    ndots = 41
    spacing = 5
    spin_speed = 1.6
    tri_rot = .78
    ysizer = 7
    
    fbradius = 150
    
    for(spoke=0; spoke<nspokes; ++spoke) {
        angle = (spoke/nspokes)*2*Math.PI

        for(ri=3; ri<ndots; ++ri) {
            r = ri * (fbradius/ndots)
            
            /* increase angle as r increases
                (spiral effect) */
            angle_ = angle + spin_speed*r/fbradius*Math.PI

            var [x,y] = [r*Math.cos(angle_), r*Math.sin(angle_)]
            
            trisize = 1 + (r/150)*5 + ysizer*((y+100)/200)**2
            triangle(x, y, trisize, tri_rot*angle)
        }
    }

    triangle(0, 0, 10, 0)

    return false;
}
/* scan-line polygon fill
    WARNNG: basic implementation, no optimizations here */
function polyfill(points)
{
	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[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})
	}

	for(y=scan_start; y<=scan_end; y+=.15) {    /* 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 */
		for(i=0; i<intersects.length; i+=2) {
		    x0 = intersects[i]
		    x1 = intersects[i+1]
	        line(x0,y,x1,y)
		}
	}
}

function rotate(p, angle)
{
	[x,y] = [p[0], p[1]]
	//console.log('rotate('+x+','+y+')')
	return [x*Math.cos(angle) - y*Math.sin(angle),
	        y*Math.cos(angle) + x*Math.sin(angle)]
}

function shift(point, x, y)
{
	return [point[0]+x, point[1]+y]
}

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 triangle(x, y, side, angle)
{
    //console.log('triangle('+x+','+y+')')
    dx = side/2.0
    dya = side * (1/Math.sqrt(3))
    dyb = side * (1/(2*Math.sqrt(3)))
    
    var p0 = [0, -dya]
    var p1 = [side/2.0, dyb]
    var p2 = [-side/2.0, dyb]

    var [p0,p1,p2] = [rotate(p0,angle), rotate(p1,angle), rotate(p2,angle)]
    var [p0,p1,p2] = [shift(p0,x,y), shift(p1,x,y), shift(p2,x,y)]
    polyfill([p0,p1,p2])
}