HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
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.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
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.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
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.
Using packages here is powered by Skypack, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ES6 import
usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<div id="world"></div>
body {
margin:0;
}
#world {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
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);
}
Also see: Tab Triggers