Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                 <div id="world"></div>
              
            
!

CSS

              
                body {
  margin:0;
}

#world {
  position: absolute;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
              
            
!

JS

              
                let container, 
    stats,
    camera, scene, renderer,
    cameraZoomX = 125,
    controls, sphere,
    clock = new THREE.Clock(),
    gui,
    snowflakeArmGeometry, snowflakeParentMaterial,
    manager = new THREE.LoadingManager(),
    snowflakeLineMat = new THREE.LineBasicMaterial({color: 0xff0000, visible: false}),
    snowflakeIntersectionLineMat = new THREE.LineBasicMaterial({color: 0xff00ff, visible: false}),
    snowflakeObj = new THREE.Object3D(),
    snowflakeObjCopy = new THREE.Object3D(),
    snowflake = [],
    snowflakeCopy = [],
    snowflakeShard = [],
    snowflakeConstraints = {},
    segmentCount = 6,
    sublineAngle = Math.floor(Math.random() * (65 - 15) + 15),
    radius = 25,
    cylgeometry = new THREE.Geometry(),
    pivotParent = new THREE.Object3D(),
    modelUrls = [
        {'name': 'subline', 'url': 'https://winter-tidings.s3.amazonaws.com/codepen/snowflakeGenerator/snowflake-sub-arm-texture.json'},
        {'name': 'mainarm', 'url': 'https://winter-tidings.s3.amazonaws.com/codepen/snowflakeGenerator/snowflake-arm-texture.json'},
        {'name': 'middle',  'url': 'https://winter-tidings.s3.amazonaws.com/codepen/snowflakeGenerator/snowflake-middle-texture.json'}
    ],
    modelGeometry = [];

// initialise on window load
window.addEventListener('load', createLoader, false);

manager.onStart = function( item, loaded, total ) {
    console.log( 'Loading started' );
};

manager.onLoad = function() {
    console.log( 'Loading complete' );

    // calculate main arms
    calculateSnowflakeMainArms();

    getModelDimensions();
    init();
};

manager.onProgress = function( item, loaded, total ) {
    console.log( item, loaded, total );
};

manager.onError = function( url ) {
    console.log( 'Error loading' );
};


function getModelDimensions() {

    let box = new THREE.Box3().setFromPoints( modelGeometry.subline.geometry.vertices );
    let modelLength = box.max.y - box.min.y;
    let modelWidth  = box.max.z - box.min.z;

    snowflakeConstraints.modelLength = Number(modelLength);
    snowflakeConstraints.modelWidth  = Number(modelWidth);
}


function returnModelDimensions(geometry) {
    let box = new THREE.Box3().setFromPoints( geometry.vertices );
    let modelLength = box.max.y - box.min.y;
    let modelWidth  = box.max.z - box.min.z;
    let modelDepth  = box.max.x - box.min.x;

    return {"length":modelLength, "width":modelWidth, "depth":modelDepth};
}


/**
 * load models
 */
function createLoader() {

    // preload
    let loader = new THREE.JSONLoader(manager);

    // get next model
    let nextModel = modelUrls.shift();

    if(!nextModel) {
        // no more models to load
        return;
    }

    loader.load(
        nextModel.url,

        function(geometry, materials){
            modelGeometry[nextModel.name] = [];

            modelGeometry[nextModel.name]['geometry'] = geometry;
            modelGeometry[nextModel.name]['material'] = materials;

            // load next model
            createLoader();
        }
    );
}

/**
 * calculate points from center of circle to edge
 * given number of segments
 */
function calculateSnowflakeMainArms() {

    // get geometry for snowflake template
    for (let i = 0; i < segmentCount; i++) {

        // segement size
        let theta = (i / segmentCount) * Math.PI * 2;

        // line drawing
        cylgeometry.vertices.push(
            new THREE.Vector3(
                0,
                Math.cos(theta) * radius,
                Math.sin(theta) * radius
            )
        );

        // start point is always central to screen
        let start = new THREE.Vector3(0,0,0);

        // end is a segment point from edge of circle
        let end =
            new THREE.Vector3(
                0,
                Math.cos(theta) * radius,
                Math.sin(theta) * radius
            );

        //define end point for copy
        let rotatedEnd =
            new THREE.Vector3(
                0,
                Math.cos(theta) * radius,
                Math.sin(theta) * radius
            );

        // rotate vector around axis to find new co-ordinate
        let axis = new THREE.Vector3( 1, 0, 0 );
        let angle = Math.PI / segmentCount;

        //apply rotation
        rotatedEnd.applyAxisAngle( axis, angle );

        // push lines to obj
        snowflake.push({"start": start, "end":end});
        snowflakeCopy.push({"start": start, "end":rotatedEnd});
    }

    snowflakeMainArmsGeometry();
    calculateSnowflakeConstraints();
}


/**
 * work out snowflake main arms geometry
 */
function snowflakeMainArmsGeometry() {

    // draw lines for snowflake
    for(let i = 0; i < snowflake.length; i++) {

        //temp vars
        let start, end;
        let geo = new THREE.Geometry();
        let line;

        // define start and end co-ordinates
        start = snowflake[i].start;
        end   = snowflake[i].end;

        // angle of line in degrees
        let angleDeg = Math.atan2(end.y - start.y, end.z - start.z) * 180 / Math.PI;
        angleDeg     = Math.round(angleDeg);

        // push to snowflake
        snowflake[i].angle = angleDeg;

        // push to geometry
        geo.vertices.push( start );
        geo.vertices.push( end );

        //new line
        line = new THREE.Line(geo, snowflakeLineMat);
        line2 = new THREE.Line(geo, snowflakeIntersectionLineMat); // visual only

        // add line to obj
        snowflakeObj.add(line);
        snowflakeObjCopy.add(line2); // visual only
    }
}


/**
 * set snowflake constraints
 */
function calculateSnowflakeConstraints() {

    // constraints obj
    snowflakeConstraints.snowflakeSegments = segmentCount;
    snowflakeConstraints.lineLength        = radius;
    snowflakeConstraints.sublineAngle      = sublineAngle;
    snowflakeConstraints.angle             = 360/segmentCount;
    snowflakeConstraints.offset            = 2.5; // this is width of center piece

    if(sublineAngle < snowflakeConstraints.angle) {
        snowflakeConstraints.sublineAngle = snowflakeConstraints.angle;
    }
}


/**
 * Re draw scene when changing dat.gui parameters
 */
function reDrawScene() {

    cylgeometry = new THREE.Geometry();
    snowflake = [];
    pivotParent = new THREE.Object3D();
    snowflakeObj = new THREE.Object3D();
    snowflakeObjCopy = new THREE.Object3D();

    for( let i = scene.children.length - 1; i >= 0; i--) {

        obj = scene.children[i];
        scene.remove(obj);
    }

    createLights();
    createSnowflakeScene();

    addSnowflakeMiddle();
    calculateSnowflakeMainArms();
    drawSnowflakeTemplate();

    // add pivot parent to scene
    scene.add(pivotParent);

    // draw sublines
    calculateSublines();
}


function randomiseSnowflake() {
    segmentCount = Math.floor(Math.random() * (10 - 6) + 6);
    sublineAngle = Math.floor(Math.random() * (65 - 15) + 15);

    reDrawScene();
}


function toggleLineVisibility() {
    snowflakeLineMat.visible = !snowflakeLineMat.visible;
    snowflakeIntersectionLineMat.visible = !snowflakeIntersectionLineMat.visible;
}


/**
 * init
 * @return {[type]} [description]
 */
function init() {

    gui = new dat.GUI();
    gui.close();

    let snowflakeFolder = gui.addFolder('Options');

    let guiSnowflakeSegments = snowflakeFolder.add(snowflakeConstraints, 'snowflakeSegments', 5, 12).step(1).name('# of Arms');
    let guiSnowflakeSublineAngle = snowflakeFolder.add(snowflakeConstraints, 'sublineAngle', 1, 90).step(1).name('Shard angle');

    let guiSnowflakeRandomise = snowflakeFolder.add(this, 'randomiseSnowflake').name('Generate new');

    let guiToggleSnowflakeLines = snowflakeFolder.add(this, 'toggleLineVisibility').name('Toggle lines');


    guiSnowflakeSegments.onFinishChange(function(val) {
        segmentCount = val;
        reDrawScene();
    });

    guiSnowflakeSublineAngle.onFinishChange(function(val) {
        sublineAngle = val;
        reDrawScene();
    });

    // create scene
    createScene();
    createSnowflakeScene();

    // orbital camera
    createControls();

    // lighting
    createLights();

    drawSnowflakeTemplate();

    // add pivot parent to scene
    pivotParent.name = "Snowflake Pivot Point";
    scene.add(pivotParent);

    // add snowflake middle
    addSnowflakeMiddle();

    // draw sublines
    calculateSublines();


    // add stats
    stats = new Stats();
    container.appendChild( stats.dom );

    window.addEventListener( 'resize', onWindowResize, false );

    animate();
}


/**
 * Add skybox
 * Initialise textures for snowflake models
 */
function createSnowflakeScene() {

    snowflakeParentMaterial = new THREE.MeshPhongMaterial(
        {
            color: new THREE.Color('#D1CADC'),
            specular: new THREE.Color('#5D230A'),
            emissive: new THREE.Color('#467D7E'),
            shininess: 50.00,
            opacity: 1.0,
            transparent: true,
            visible: true,
        }
    );

    // texture loader
    let textureLoader = new THREE.TextureLoader();
    textureLoader.crossOrigin = '';

    // load textures
    let mapTexture = textureLoader.load('https://winter-tidings.s3.amazonaws.com/assets/models/snowflake/texture/diffuse_COLOR.png');
    let aoTexture = textureLoader.load('https://winter-tidings.s3.amazonaws.com/assets/models/snowflake/texture/diffuse_OCC.png');
    let specTexture = textureLoader.load('https://winter-tidings.s3.amazonaws.com/assets/models/snowflake/texture/diffuse_SPEC.png');
  
    //set args
    mapTexture.wrapS = THREE.RepeatWrapping;
    mapTexture.wrapT = THREE.RepeatWrapping;
    mapTexture.repeat.set( 2, 2 );

    aoTexture.wrapS = THREE.RepeatWrapping;
    aoTexture.wrapT = THREE.RepeatWrapping;
    aoTexture.repeat.set( 2, 2 );

    specTexture.wrapS = THREE.RepeatWrapping;
    specTexture.wrapT = THREE.RepeatWrapping;
    specTexture.repeat.set( 2, 2 );

    snowflakeParentMaterial.map = mapTexture;
    snowflakeParentMaterial.aoMap = aoTexture;
    snowflakeParentMaterial.specularMap = specTexture;
}

/**
 * Add snowflake middle section
 */
function addSnowflakeMiddle(newPivotParent=false) {

    // set geometry
    let snowflakeMiddleGeometry = modelGeometry.middle.geometry;

    // create new mesh
    let snowflakeMiddle = new THREE.Mesh( snowflakeMiddleGeometry, snowflakeParentMaterial );

    // get dimensions
    let snowflakeMiddleDims = returnModelDimensions(snowflakeMiddleGeometry);
    let middleRatio = snowflakeConstraints.offset/snowflakeMiddleDims.width;

    // multiply to correct size in scene
    middleRatio = 4.1;

    // scale models
    snowflakeMiddle.scale.x *= middleRatio;
    snowflakeMiddle.scale.y *= middleRatio;
    snowflakeMiddle.scale.z *= middleRatio;

    snowflakeMiddle.name = "Snowflake Centre Piece"

    // add to snowflake
    pivotParent.add(snowflakeMiddle);
}


/**
 * Create scene
 * Add camera
 */
function createScene() {

    // scene
    scene = new THREE.Scene();

    // set camera
    camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 0.5, 20000 );
    camera.position.set( cameraZoomX, 0, 0 );

    // Create the renderer
    renderer = new THREE.WebGLRenderer({
        antialias: true
    });

    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.setClearColor(0x102132);

    container = document.getElementById('world');
    container.appendChild(renderer.domElement);
}


// controls
function createControls() {
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.target = new THREE.Vector3(0,0,0);
}


// create lights
function createLights() {
    var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.75 );
    scene.add( directionalLight );

    directionalLight.position.set(10,0,0);
}


/**
 * show template circle & snowflake main arms
 */
function drawSnowflakeTemplate() {

    // add to scene
    snowflakeObj.position.x = 5;
    scene.add(snowflakeObj);

    // visual only
    // add second snowflake for intersect points
    snowflakeObjCopy.rotation.x = THREE.Math.degToRad(snowflakeConstraints.angle/2);
    snowflakeObjCopy.position.x = 5;
    scene.add(snowflakeObjCopy);

    // visual - circle template
    var newline = new THREE.Line(cylgeometry,snowflakeLineMat);
    newline.position.x = 5;
    scene.add(newline);
}

/**
 * Add main arms model to snowflake
 */
function addMainArm(j) {

    let segmentAngle = snowflakeConstraints.angle;

    // add main arm to scene
    let snowflakeMainArmGeometry = modelGeometry.mainarm.geometry;
    let snowflakeMainArmMaterial = modelGeometry.mainarm.material;

    let snowflakeMainArm = new THREE.Mesh( snowflakeMainArmGeometry, snowflakeParentMaterial );
    let snowflakeMainArmDims = returnModelDimensions(snowflakeMainArmGeometry);
    let mainarmRatio = snowflakeConstraints.lineLength/snowflakeMainArmDims.length;
    snowflakeMainArm.rotation.x = THREE.Math.degToRad( j*segmentAngle );

    // scale models
    snowflakeMainArm.scale.x *= mainarmRatio;
    snowflakeMainArm.scale.y *= mainarmRatio;
    snowflakeMainArm.scale.z *= mainarmRatio;

    // set name
    snowflakeMainArm.name = "Snowflake Main Arm " + j;

    // add to snowflake
    pivotParent.add(snowflakeMainArm);
}

/**
 * work out sub line distribution
 */
function calculateSublines() {

    // loop through main arms
    for(let j = 0; j < snowflake.length; j++) {

        // set vars
        let lineStart     = snowflake[j].start;
        let lineEnd       = snowflake[j].end;
        let lineLength    = snowflakeConstraints.lineLength;
        let lineSection   = snowflakeConstraints.lineSection;
        let lineFrequency = snowflakeConstraints.lineFrequency;
        let count         = 0;
        let ratio         = 0;

        // add main arm to scene
        addMainArm(j);

        // loop through whilst offset does not exceed line
        // generates position of sublines
        while(lineLength >= snowflakeConstraints.offset) {

            //get point on line at offset
            let pointOnLine = getPointInBetweenByLen( lineStart, lineEnd, snowflakeConstraints.offset);

            // store angles as vars
            let sublineAngle = snowflakeConstraints.sublineAngle;

            let nextLineStart = snowflakeCopy[0].start;
            let nextLineEnd = snowflakeCopy[0].end;

            let pointA = new THREE.Vector3( 0,0,0 );

            // work out where intersect point is
            let angle1 = snowflakeConstraints.angle/2;
            let angle2 = 180-sublineAngle;
            let angle3 = 180 - angle2 - angle1;
            let side1  = pointA.distanceTo(pointOnLine);

            // work out length of side of triangle to get intersect point
            let calculateLength = Math.sin(angle2/180*Math.PI) * (side1/Math.sin(angle3/180*Math.PI)); // calculate length of side of triangle

            // get point on secondary line for intersect
            let intersectPoint = getPointInBetweenByLen( nextLineStart, nextLineEnd, calculateLength);

            //get distance between points
            distanceBetweenPoints = pointOnLine.distanceTo(intersectPoint);

            // work out max length for model if in first iteration
            if( count === 0 ) {

                //work out ratio of length / modelLength to scale all axis
                ratio = distanceBetweenPoints/snowflakeConstraints.modelLength;
                ratio = ratio.toFixed(2);

            } else {

                //set ratio
                ratio = snowflakeConstraints.ratio;
            }

            // generate random ratio for next sub line
            nextRatio = Math.random() * (15 - 1) + 1;
            nextRatio = nextRatio.toFixed(2);

            // if current ratio make subline too long, reduce size and reset data in array
            if(ratio*snowflakeConstraints.modelLength > distanceBetweenPoints/snowflakeConstraints.modelLength) {

                ratio = Math.random() * (distanceBetweenPoints/snowflakeConstraints.modelLength - 1) + 1;
                snowflakeShard[count-1].ratio = Number(ratio);
            }

            // set data for next iteration
            snowflakeConstraints.ratio = nextRatio;

            //claculte distance to space subline
            let currentIntersectDistance = Math.hypot((snowflakeConstraints.modelWidth/2)*ratio, (snowflakeConstraints.modelWidth/2)*ratio);
            let nextIntersectDistance    = Math.hypot((snowflakeConstraints.modelWidth/2)*nextRatio, (snowflakeConstraints.modelWidth/2)*nextRatio);

            // if acute angle, add more spacing
            if( snowflakeConstraints.sublineAngle < 40 ) {
                currentIntersectDistance = currentIntersectDistance*2;
            }

            // offset equal to currentIntersect and nextIntersect
            snowflakeConstraints.offset += currentIntersectDistance + (nextIntersectDistance/2) + 0.15;

            // push data
            snowflakeShard[count] = {
                "ratio": Number(ratio),
                "currentIntersectDistance": currentIntersectDistance,
                "nextIntersectDistance": nextIntersectDistance,
                "offset": snowflakeConstraints.offset,
            };

            count++; // increase count to access constraints obj
        }

        // loop through all sublines and add to scene
        for(let k = 0; k < snowflakeShard.length; k++) {

            drawSublines(j, k, lineStart, lineEnd);
        }
    }
}



function drawSublines(j, k, lineStart, lineEnd) {

    // get point on line
    let pointOnLine = getPointInBetweenByLen( lineStart, lineEnd, snowflakeShard[k].offset);

    // set angles
    let sublineAngle = snowflakeConstraints.sublineAngle;
    let segmentAngle = snowflakeConstraints.angle;

    // set geometry
    snowflakeArmGeometry = modelGeometry.subline.geometry;

    // create mesh for each arm
    let snowflakeArm = new THREE.Mesh( snowflakeArmGeometry, snowflakeParentMaterial );
    let snowflakeArm2 = new THREE.Mesh( snowflakeArmGeometry, snowflakeParentMaterial );

    // rotate mesh & pivot point according to angle of main arm line
    snowflakeArm.rotation.x  = THREE.Math.degToRad( j*segmentAngle + sublineAngle );
    snowflakeArm2.rotation.x = THREE.Math.degToRad( j*segmentAngle -sublineAngle );

    // scale to correct ratio
    ratio = snowflakeShard[k].ratio;

    // scale models
    snowflakeArm.scale.x *= ratio;
    snowflakeArm.scale.y *= ratio;
    snowflakeArm.scale.z *= ratio;

    // position model
    snowflakeArm.position.x = pointOnLine.x;
    snowflakeArm.position.y = pointOnLine.y;
    snowflakeArm.position.z = pointOnLine.z;

    // scale
    snowflakeArm2.scale.x *= ratio;
    snowflakeArm2.scale.y *= ratio;
    snowflakeArm2.scale.z *= ratio;

    // position
    snowflakeArm2.position.x = pointOnLine.x;
    snowflakeArm2.position.y = pointOnLine.y;
    snowflakeArm2.position.z = pointOnLine.z;

    // set name
    snowflakeArm.name  = "Snowflake Shard (left) " + k;
    snowflakeArm2.name = "Snowflake Shard (right) " + k;

    // add to scene
    pivotParent.add( snowflakeArm );
    pivotParent.add( snowflakeArm2 );
}



function getPointInBetweenByLen(pointA, pointB, length) {

    let dir = pointB.clone().sub(pointA).normalize().multiplyScalar(length);
    return pointA.clone().add(dir);
}


function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}



function animate() {
    let delta = clock.getDelta()
    let elapsed = clock.getElapsedTime()

    update(delta, elapsed);

    render(delta);
    stats.update();

    requestAnimationFrame(animate);
}


function update(dt) {
    camera.updateProjectionMatrix();
    controls.update(dt);
}



function render() {
    renderer.render(scene, camera);
}
              
            
!
999px

Console