Geodesic polyhedron 🌐

Sphere built from triangles: en.wikipedia.org/wiki/geodesic_polyhedron

Log in to post a comment.

const baseShape = 0;     // min=0 max=3 step=1 (Icosahedral,Octahedral,Hexahedral,Tetrahedral)// 
const frequency = 1;     // min=1 max=6 step=1
const splitMethod = 0;   // min=0 max=1 step=1 (Edge,Triangle median)// 
const vertexDots = 1;    // min=0 max=1 step=1 (No,Yes)
const edgeLines = 1;     // min=0 max=1 step=1 (No,Yes)
const xAxisRotation = 0; //min=0 max=360 step=1
const yAxisRotation = 0; //min=0 max=360 step=1
const zAxisRotation = 0; //min=0 max=360 step=1

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(.7);

// Global code will be evaluated once.
const turtle = new Turtle();
//turtle.jump(0,-80);
//turtle.circle(80);

const r = 80;

let ts = new TriSphere(r);
for(let i = 1; i < frequency; i++) {
    ts.generateNext();
}

function walk() {
    for(let i = 0; i < ts.points.length; i++) {
        dot3(ts.points[i][0] * r, ts.points[i][1] * r, ts.points[i][2]);
    } 
    
    if(edgeLines == 0) return false;

    let segPointer = ts.segments.length - 1;
    for(let i = 0; i < ts.segments[segPointer].length; i++) {
        turtle.jump(projectPoint3(
            ts.points[ts.segments[segPointer][i][0]][0] * r,
            ts.points[ts.segments[segPointer][i][0]][1] * r,
            ts.points[ts.segments[segPointer][i][0]][2]
        ));
        turtle.goto(projectPoint3(
            ts.points[ts.segments[segPointer][i][1]][0] * r,
            ts.points[ts.segments[segPointer][i][1]][1] * r,
            ts.points[ts.segments[segPointer][i][1]][2]
        ));
    }
    return false;
}

function dot3(x, y, z, r = 1) {
    let pr = projectPoint3(x, y, z);
    dot(pr[0], pr[1], 1.5 + z);
}

function projectPoint3(x, y, z) {
    return [x * (1 + (z * .15)), y * (1 + (z * .15))];
}

function dot(x, y, r = 1) {
    if(vertexDots == 0) return;
    let h = turtle.h();
    turtle.seth(0);
    for(let rr = 0; rr < r; rr+=.1) {
        turtle.jump(x, y-(r/2));
        turtle.circle(rr);
    }
    turtle.jump(x,y);
    turtle.seth(h);
}

function TriSphere() {
    class TriSphere {
        constructor() {
            this.generate();
        }
        generate() {
            switch(baseShape) {
                case 0:
                    this.generateIcosahedral();
                break;
                case 1:
                    this.generateOctahedral();
                break;
                case 2:
                    this.generateHexahedral();
                break;
                case 3:
                    this.generateTetrahedral();
                break;
            }

            function rot3(ax, ay, az) { return [Math.cos(ax)*Math.cos(ay), Math.cos(ax)*Math.sin(ay)*Math.sin(az)-Math.sin(ax)*Math.cos(az), Math.cos(ax)*Math.sin(ay)*Math.cos(az)+Math.sin(ax)*Math.sin(az),Math.sin(ax)*Math.cos(ay), Math.sin(ax)*Math.sin(ay)*Math.sin(az)+Math.cos(ax)*Math.cos(az), Math.sin(ax)*Math.sin(ay)*Math.cos(az)-Math.cos(ax)*Math.sin(az),-Math.sin(ay), Math.cos(ay)*Math.sin(az), Math.cos(ay)*Math.cos(az)]; }
            function multiply3(a3x3, a) { return [(a[0]*a3x3[0])+(a[1]*a3x3[1])+(a[2]*a3x3[2]), (a[0]*a3x3[3])+(a[1]*a3x3[4])+(a[2]*a3x3[5]), (a[0]*a3x3[6])+(a[1]*a3x3[7])+(a[2]*a3x3[8])]; }
            let rot = rot3(Math.PI * (zAxisRotation / 180), Math.PI * (yAxisRotation / 180), Math.PI * (xAxisRotation / 180));

            for(let i = 0; i < this.points.length; i++) {
                this.points[i] = multiply3(rot, this.points[i]);
            }
        }
        generateTetrahedral() {
            //top
            this.points = [
                [0,-1,0] //0
            ];
            
            //middle
            this.points.push([0                                             , Math.cos(Math.PI / 3), -Math.sin(Math.PI / 3)]); //1
            this.points.push([-Math.sin(Math.PI / 3) * Math.sin(Math.PI / 3), Math.cos(Math.PI / 3), Math.cos(Math.PI / 3) * Math.sin(Math.PI / 3)]); //1
            this.points.push([Math.sin(Math.PI / 3) * Math.sin(Math.PI / 3) , Math.cos(Math.PI / 3), Math.cos(Math.PI / 3) * Math.sin(Math.PI / 3)]); //1

            this.segments = [[
                [0, 1], [0, 2], [0, 3], //0 to 2

                [1, 2], [2, 3], [3, 1] //3 to 5
            ]];
            
            this.triangles = [[
                [0, 1, 3], [1, 2, 4], [2, 0, 5],
                
                [3, 4, 5]
            ]];
        }
        generateHexahedral() {
            //top
            this.points = [
                [0,-1,0] //0
            ];
            
            //middle
            this.points.push([0, 0, -1]); //1
            this.points.push([Math.sin(Math.PI / 3), 0, Math.cos(Math.PI / 3)]); //2
            this.points.push([-Math.sin(Math.PI / 3), 0, Math.cos(Math.PI / 3)]); //3

            //bottom
            this.points.push(
                [0,1,0] //4
            );
            
            this.segments = [[
                [0, 1], [0, 2], [0, 3], //0 to 2

                [1, 2], [2, 3], [3, 1], //3 to 5

                [1, 4], [2, 4], [3, 4] // 6 to 8
            ]];
            
            this.triangles = [[
                [0, 1, 3], [1, 2, 4], [2, 0, 5],
                
                [6, 7, 3], [7, 8, 4], [8, 6, 5]
            ]];
        }
        generateOctahedral() {
            //top
            this.points = [
                [0,-1,0] //0
            ];
            
            //middle
            this.points.push([0, 0, 1]); //1
            this.points.push([1, 0, 0]); //2
            this.points.push([0, 0, -1]); //3
            this.points.push([-1, 0, 0]); //4
            
            //bottom
            this.points.push(
                [0,1,0] //5
            );
            
            this.segments = [[
                [0, 1], [0, 2], [0, 3], [0, 4], //0 to 3

                [1, 2], [2, 3], [3, 4], [4, 1], //4 to 7

                [1, 5], [2, 5], [3, 5], [4, 5] // 8 to 11
            ]];
            
            this.triangles = [[
                [0, 1, 4], [1, 2, 5], [2, 3, 6], [3, 0, 7],
                
                [4, 8, 9], [5, 9, 10], [6, 10, 11], [7, 11, 8]
            ]];
        }
        generateIcosahedral() {
            let yTropics = Math.cos(Math.PI  / 3);
            let tropicsScale = Math.sin(Math.PI / 3);
            
            //top
            this.points = [
                [0,-1,0] //0
            ];
            
            let startRotation = Math.PI / 4;
            let shift = 0;
            //top tropic 1, 2, 3, 4, 5
            for(let i = 0; i < 5; i++) {
                this.points.push([
                    Math.cos(startRotation + shift + (((2 * Math.PI) / 5) * i)) * tropicsScale,
                    -yTropics,
                    Math.sin(startRotation + shift + (((2 * Math.PI) / 5) * i)) * tropicsScale
                ]);
            }
            
            shift = Math.PI / 5;
            //bottom tropic 6, 7, 8, 9, 10
            for(let i = 0; i < 5; i++) {
                this.points.push([
                    Math.cos(startRotation + shift + (((2 * Math.PI) / 5) * i)) * tropicsScale,
                    yTropics,
                    Math.sin(startRotation + shift + (((2 * Math.PI) / 5) * i)) * tropicsScale
                ]);
            }
                
            //bottom
            this.points.push(
                [0,1,0] //11
            );

            this.segments = [[
                [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], //0 to 4
                [1, 2], [2, 3], [3, 4], [4, 5], [5, 1], //5 to 9
                
                [1, 6], [6, 2], [2, 7], [7, 3], [3, 8], //10 to 14
                [8, 4], [4, 9], [9, 5], [5, 10], [10, 1], // 15 to 19
                
                [6, 7], [7, 8], [8, 9], [9, 10], [10, 6], // 20 to 24
                [6, 11], [7, 11], [8, 11], [9, 11], [10, 11] // 25 to 29
            ]];
            
            this.triangles = [[
                [0, 1, 5], [1, 2, 6], [2, 3, 7], [3, 4, 8], [4, 0, 9],
                
                [10, 11, 5], [11, 12, 20], [12, 13, 6], [13, 14, 21], [14, 15, 7],
                [15, 16, 22], [16, 17, 8], [17, 18, 23], [18, 19, 9], [19, 10, 24],
                
                [25, 26, 20], [26, 27, 21], [27, 28, 22], [28, 29, 23], [29, 25, 24]
            ]];
        }
        generateNext() {
            switch(splitMethod) {
                case 0:
                    return this.generateNextByEdge();
                break;
                case 1:
                    return this.generateNextByMedian();
                break;
            }
            throw new Error('Undefined splitMethod');
        }
        generateNextByMedian() {
            let currentGen = this.segments.length - 1;

            let newSegments = JSON.parse(JSON.stringify(this.segments[currentGen]));
            let newTriangles = [];
            for(let i = 0; i < this.triangles[currentGen].length; i++) {
                let dSegments = this.triangles[currentGen][i];
                let oldPoints = getUniquePointsFromTriangle(dSegments, this.segments[currentGen])
                
                let pointInBetween = [
                    (this.points[oldPoints[0]][0] + this.points[oldPoints[1]][0] + this.points[oldPoints[2]][0]) / 3,
                    (this.points[oldPoints[0]][1] + this.points[oldPoints[1]][1] + this.points[oldPoints[2]][1]) / 3,
                    (this.points[oldPoints[0]][2] + this.points[oldPoints[1]][2] + this.points[oldPoints[2]][2]) / 3
                ];

                //pointInBetweenLength for normalization                
                let pibLength = Math.sqrt( Math.pow(pointInBetween[0], 2) + Math.pow(pointInBetween[1], 2) + Math.pow(pointInBetween[2], 2) )

                let pointSequence = this.points.length;
                this.points.push([pointInBetween[0] / pibLength, pointInBetween[1] / pibLength, pointInBetween[2] / pibLength]);
                
                let newSegmentIndicesByOldPoint = [];
                for(let j = 0; j < oldPoints.length; j++) {
                    newSegmentIndicesByOldPoint[oldPoints[j]] = newSegments.push([oldPoints[j], pointSequence]) - 1;
                }
                
                for(let j = 0; j < dSegments.length; j++) {
                    newTriangles.push([
                        dSegments[j],
                        newSegmentIndicesByOldPoint[newSegments[dSegments[j]][0]],
                        newSegmentIndicesByOldPoint[newSegments[dSegments[j]][1]]
                    ]);
                }
            }
            this.segments.push(newSegments);
            this.triangles.push(newTriangles);
        }
        generateNextByEdge() {
            let currentGen = this.segments.length - 1;

            let newSegments = [];
            let segmentTranslations = [];
            for(let i = 0; i < this.segments[currentGen].length; i++) {
                let startPoint = this.segments[currentGen][i][0];
                let endPoint = this.segments[currentGen][i][1];
                let pointInBetween = [
                    (this.points[startPoint][0] + this.points[endPoint][0]) / 2,
                    (this.points[startPoint][1] + this.points[endPoint][1]) / 2,
                    (this.points[startPoint][2] + this.points[endPoint][2]) / 2
                ];
                //pointInBetweenLength for normalization                
                let pibLength = Math.sqrt( Math.pow(pointInBetween[0], 2) + Math.pow(pointInBetween[1], 2) + Math.pow(pointInBetween[2], 2) )

                let pointSequence = this.points.length;
                this.points.push([pointInBetween[0] / pibLength, pointInBetween[1] / pibLength, pointInBetween[2] / pibLength]);
                
                let segmentSequence = newSegments.length;
                newSegments.push([startPoint, pointSequence]);
                newSegments.push([pointSequence, endPoint]);
                segmentTranslations[i] = [segmentSequence, segmentSequence + 1];
            }
            
            let newTriangles = [];
            for(let i = 0; i < this.triangles[currentGen].length; i++) {
                let dSegments = this.triangles[currentGen][i];
                let dPoints = [
                    newSegments[segmentTranslations[dSegments[0]][0]][1],
                    newSegments[segmentTranslations[dSegments[1]][0]][1],
                    newSegments[segmentTranslations[dSegments[2]][0]][1]
                ]
                
                let dNewSegmentIndex = newSegments.length;
                newSegments.push([dPoints[0], dPoints[1]]);
                newSegments.push([dPoints[1], dPoints[2]]);
                newSegments.push([dPoints[2], dPoints[0]]);
                
                let newTrianglesIndex = newTriangles.length;
                newTriangles.push([dNewSegmentIndex]);
                newTriangles.push([dNewSegmentIndex + 1]);
                newTriangles.push([dNewSegmentIndex + 2]);
                newTriangles.push([dNewSegmentIndex, dNewSegmentIndex + 1, dNewSegmentIndex + 2]);

                let oldPoints = getUniquePointsFromTriangle(dSegments, this.segments[currentGen])
                for(let j = 0; j < 3; j++) {
                    let jsegs = findJoiningSegments(newSegments[dNewSegmentIndex + j], newSegments, oldPoints);
                    newTriangles[newTrianglesIndex + j].push(jsegs[0]);
                    newTriangles[newTrianglesIndex + j].push(jsegs[1]);
                }

            }

            this.segments.push(newSegments);
            this.triangles.push(newTriangles);
        }
    }
    return new TriSphere();
}

function getUniquePointsFromTriangle(triangle, segments) {
    let points = [];
    for(let i = 0; i < triangle.length; i++) {
        for(let j = 0; j < segments[triangle[i]].length; j++) {
            if(!points.includes(segments[triangle[i]][j])) {
                points.push(segments[triangle[i]][j]);
            }
        }
    }
    return points;
}

function findJoiningSegments(segment, allSegments, toOneOfPoints) {
    for(let i = 0; i < toOneOfPoints.length; i++) {
        for(let j = 0; j < allSegments.length; j++) {
            if(allSegments[j].includes(segment[0]) && allSegments[j].includes(toOneOfPoints[i])) {
                for(let k = 0; k < allSegments.length; k++) {
                    if(allSegments[k].includes(segment[1]) && allSegments[k].includes(toOneOfPoints[i])) {
                        return [j, k];
                    }
                }
            }
        }
    }
    throw new Error('not found');
}

function findSegmentByPoints(points, allSegments) {
    for(let i = 0; i < allSegments.length; i++) {
        if(allSegments[i].includes(points[0]) && allSegments[i].includes(points[1])) {
            return i;
        }
    }
    return false;
}