Fractal Cloud Viewer 2
Fractal Cloud Viewer 2
This lets you view an interesting 3D model made from a few levels of recurring patterns.
Drag mouse or touch up or down to move vertically.
An older version implemented with canvas 2D context is available here.
html, body {
padding: 0;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100vh;
background-color: #000;
/* Prevent swiping and dragging problems. */
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
#dialog-backdrop {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 100%;
opacity: 0.5;
background-color: #000;
}
#dialog {
position: fixed;
top: 50%;
left: 50%;
margin-top: -175px;
margin-left: -125px;
padding: 8px;
font-family: arial;
width: 350px;
height: 250px;
display: flex;
flex-direction: column;
color: #fff;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 5px;
border: 1px solid #888;
box-shadow: 0 0 25px #000;
}
#dialog a {
color: #fff;
}
#dialog h1 {
font-size: 22px;
margin-top: 0;
}
#dialog div.body {
flex-grow: 1;
}
#dialog footer {
flex-grow: 0;
text-align: center;
}
#dialog button {
font-weight: bold;
cursor: pointer; /* encourage people to click it. */
}
var pointsData = [];
function getPointCoordinatesData() {
var newVal = [];
pointsData.forEach(function(point) {
for (var i = 0; i < point.coords.length;i++) {
newVal.push(point.coords[i]);
}
});
return newVal;
}
function getPointColoursData() {
var newVal = [];
pointsData.forEach(function(point) {
newVal.push(point.r);
newVal.push(point.g);
newVal.push(point.b);
});
return newVal;
}
pointsData = [];
function addPointToModel(pointInfo) {
var scale = pointInfo.radius/255;
pointInfo.r *= scale;
pointInfo.g *= scale;
pointInfo.b *= scale;
pointsData.push(new Point([pointInfo.cx, pointInfo.cy, pointInfo.cz], pointInfo.r, pointInfo.g, pointInfo.b));
}
function addExtremelyTiny(cx, cy, cz) {
addPointToModel({
'cx': cx,
'cy': cy,
'cz': cz,
'radius': 0.1,
"r": 0xff,
"g": 0xff,
"b": 0xff
});
}
function addSuperTiny(cx, cy, cz) {
addPointToModel({
'cx': cx,
'cy': cy,
'cz': cz,
'radius': 0.6,
"r": 0,
"g": 0,
"b": 0x88
});
var tinyRadius = 1.2;
for (var i = 0; i < 3; i++) {
var angle = i * Math.PI * 2 / 3;
var x = cx + tinyRadius * Math.cos(angle);
var z = cz + tinyRadius * Math.sin(angle);
addExtremelyTiny(x, cy + tinyRadius, z);
addExtremelyTiny(x, cy - tinyRadius, z);
}
}
function addTiny(cx, cy, cz) {
addPointToModel({
'cx': cx,
'cy': cy,
'cz': cz,
'radius': 1,
'r': 0x44,
'g': 0xbb,
'b': 0,
});
var smallRadius = 3;
for (var i = 0; i < 4; i++) {
var angle = i * Math.PI / 2;
var x = cx + smallRadius * Math.cos(angle);
var z = cz + smallRadius * Math.sin(angle);
addSuperTiny(x, cy + smallRadius, z);
addSuperTiny(x, cy - smallRadius, z);
}
}
function addSmall(cx, cy, cz) {
addPointToModel({
'cx': cx,
'cy': cy,
'cz': cz,
'radius': 3,
'r': 0xff,
'g': 0x55,
'b': 0xff
});
var smallRadius = 10;
for (var i = 0; i < 8; i++) {
var angle = i * Math.PI / 4;
var x = cx + smallRadius * Math.cos(angle);
var z = cz + smallRadius * Math.sin(angle);
addTiny(x, cy + smallRadius, z);
addTiny(x, cy - smallRadius, z);
}
}
function addGlowing(cx, cy, cz) {
addPointToModel({
'cx': cx,
'cy': cy,
'cz': cz,
'radius': 6,
'r': 0xff,
'g': 0xdd,
'b': 0xff
});
var smallRadius = 30;
for (var i = 0; i < 8; i++) {
var angle = i * Math.PI / 4;
var x = cx + smallRadius * Math.cos(angle);
var z = cz + smallRadius * Math.sin(angle);
addSmall(x, cy + smallRadius, z);
addSmall(x, cy - smallRadius, z);
}
}
function initializeModel() {
addPointToModel({
"cx": 0,
"cy": 0,
"cz": 0,
"radius": 20,
"r": 0xff,
"g": 0xff,
"b": 0xee
});
var radius = 100;
for (var i = 0; i < 8; i++) {
var angle = i * Math.PI / 4;
var x = radius * Math.cos(angle);
var z = radius * Math.sin(angle);
addGlowing(x, 100, z);
addGlowing(x, -100, z);
}
}
initializeModel();
document.addEventListener('DOMContentLoaded', function() {
var body = document.querySelector('body');
var dialog = document.getElementById('dialog');
var ok = document.getElementById('dialog-ok');
var dialogBackdrop = document.getElementById('dialog-backdrop');
function hideDialog() {
dialog.remove();
dialogBackdrop.remove();
}
ok.addEventListener('click', hideDialog);
dialogBackdrop.addEventListener('mousedown', hideDialog);
dialogBackdrop.addEventListener('touchstart', hideDialog);
});
function loadShader(gl, program, src, type) {
let sid = gl.createShader(type);
gl.shaderSource(sid, src);
gl.compileShader(sid);
var compiled = gl.getShaderParameter(sid, gl.COMPILE_STATUS);
if (!compiled) {
console.log('Shader compiled successfully: ' + compiled);
var compilationLog = gl.getShaderInfoLog(sid);
console.log('Shader compiler log: ' + compilationLog);
}
gl.attachShader(program, sid);
}class Points {
constructor(gl, program) {
this.gl = gl;
this.program = program;
this.coloursData = getPointColoursData();
this.coordinates = getPointCoordinatesData();
this.vertexShaderCode = `
precision mediump float;
attribute vec3 position;
attribute vec3 colour;
uniform vec3 scale;
uniform mat3 transform;
uniform float distance;
uniform float yTranslation;
varying vec3 pointColour;
void main() {
vec3 transformedPosition = position;
transformedPosition = transformedPosition * transform;
transformedPosition.x = transformedPosition.x * scale.x;
transformedPosition.y = (transformedPosition.y + yTranslation) * scale.y;
transformedPosition.z += 180.0 + distance;
gl_Position = vec4(transformedPosition.xy / transformedPosition.z, 0.0, 1.0);
float brightnessScale = max(colour.r, max(colour.g, colour.b));
gl_PointSize = 5000.0 * scale.z * scale.z * brightnessScale / transformedPosition.z;
if (gl_PointSize < 1.0) {
gl_PointSize = 0.0;
}
pointColour = colour / brightnessScale;
}`;
this.fragmentShaderCode = `
precision mediump float;
varying vec3 pointColour;
void main() {
float distance = length(2.0 * gl_PointCoord - 1.0);
if (distance > 1.0) {
discard; // do not draw this pixel.
}
else {
distance = (1.0 - distance);
gl_FragColor = vec4(pointColour * distance * distance, 1);
}
}`;
}
_initVec3Attribute(key, attributeName, initData) {
this[key] = this.gl.getAttribLocation(this.program, attributeName);
let array = new Float32Array(initData);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.createBuffer());
this.gl.bufferData(this.gl.ARRAY_BUFFER, array, this.gl.STATIC_DRAW);
this.gl.vertexAttribPointer(this[key], 3 /*components per vertex */, this.gl.FLOAT, false, 0, 0);
this.gl.enableVertexAttribArray(this[key]);
}
initCoords() {
this.uniforms = {
'scale': this.gl.getUniformLocation(this.program, 'scale'),
'transform': this.gl.getUniformLocation(this.program, 'transform'),
'distance': this.gl.getUniformLocation(this.program, 'distance'),
'yTranslation': this.gl.getUniformLocation(this.program, 'yTranslation')
};
this._initVec3Attribute('coords', 'position', this.coordinates);
this._initVec3Attribute('colours', 'colour', this.coloursData);
this.setAngle(0);
}
setScale(newScale, aspectRatio) {
newScale = [newScale, newScale * aspectRatio, newScale];
this.gl.uniform3fv(this.uniforms.scale, newScale);
}
setAngle(newRotation) {
var sinTheta = Math.sin(newRotation);
var cosTheta = Math.cos(newRotation);
var matrix = [
cosTheta, 0, sinTheta,
0, 1, 0,
-sinTheta, 0, cosTheta
];
this.gl.uniformMatrix3fv(this.uniforms.transform, false, matrix);
}
setYTranslation(newYTranslation) {
this.gl.uniform1f(this.uniforms.yTranslation, newYTranslation);
}
setDistance(newDistance) {
this.gl.uniform1f(this.uniforms.distance, newDistance);
}
loadShaders() {
loadShader(this.gl, this.program, this.vertexShaderCode, this.gl.VERTEX_SHADER);
loadShader(this.gl, this.program, this.fragmentShaderCode, this.gl.FRAGMENT_SHADER);
}
}document.addEventListener('DOMContentLoaded', function() {
var canvas = document.querySelector('canvas');
var gl = canvas.getContext('webgl2', {premultipliedAlpha: false});
gl.getExtension('EXT_frag_depth');
var program = gl.createProgram();
var point = new Points(gl, program);
point.loadShaders();
gl.linkProgram(program);
gl.useProgram(program);
point.initCoords();
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_COLOR, gl.DST_COLOR);
gl.blendFunc (gl.ONE, gl.ONE);
function draw() {
var w = canvas.getAttribute('width');
var h = canvas.getAttribute('height');
gl.viewport(0, 0, w, h);
gl.drawArrays(gl.POINTS, 0, point.coordinates.length / 3);
}
function resized() {
var w = window.innerWidth;
var h = window.innerHeight;
point.setScale((w + h) * 0.0003, w / h);
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);
requestAnimationFrame(draw);
}
function updateAnimation() {
var t = new Date().getTime();
point.setAngle((t * 0.0001) % (2 * Math.PI));
point.setDistance(80 * Math.sin(t * 0.0005));
draw();
requestAnimationFrame(updateAnimation);
}
resized();
window.addEventListener('resize', resized);
gl.clearDepth(0.5);
setUpDragUI(point);
updateAnimation();
});function setUpDragUI(points) {
var y = 61;
var previousMousePosition;
function getPositionFromEvent(event) {
if (event.touches !== undefined && event.touches[0] !== undefined) {
var touch = event.touches[0];
return {'x': touch.pageX, 'y': touch.pageY};
}
else {
return {
'x': event.clientX,
'y': event.clientY
};
}
}
function mouseDown(event) {
previousMousePosition = getPositionFromEvent(event);
}
function mouseUp() {
previousMousePosition = undefined;
}
function mouseMove(event) {
if (previousMousePosition !== undefined) {
var newP = getPositionFromEvent(event);
y += (newP.y - previousMousePosition.y) * 0.1;
previousMousePosition = newP;
points.setYTranslation(y);
}
}
window.addEventListener('mousedown', mouseDown);
window.addEventListener('touchstart', mouseDown);
window.addEventListener('mouseup', mouseUp);
window.addEventListener('touchend', mouseUp);
window.addEventListener('mousemove', mouseMove);
window.addEventListener('touchmove', mouseMove);
points.setYTranslation(y);
}
Comments
Post a Comment