Unappetizing donuts.
A Pen by Toshiya Marukubo on CodePen.
Unappetizing donuts.
A Pen by Toshiya Marukubo on CodePen.
/* | |
* File Name / template.js | |
* Created Date / Oct 01, 2020 | |
* Aurhor / Toshiya Marukubo | |
* Twitter / https://twitter.com/toshiyamarukubo | |
*/ | |
/* | |
Common Tool. | |
*/ | |
class Tool { | |
// random number. | |
static randomNumber(min, max) { | |
return Math.floor(Math.random() * (max - min + 1) + min); | |
} | |
// random color rgb. | |
static randomColorRGB() { | |
return ( | |
"rgb(" + | |
this.randomNumber(0, 255) + | |
", " + | |
this.randomNumber(0, 255) + | |
", " + | |
this.randomNumber(0, 255) + | |
")" | |
); | |
} | |
// random color hsl. | |
static randomColorHSL(hue, saturation, lightness) { | |
return ( | |
"hsl(" + | |
hue + | |
", " + | |
saturation + | |
"%, " + | |
lightness + | |
"%)" | |
); | |
} | |
// gradient color. | |
static gradientColor(ctx, cr, cg, cb, ca, x, y, r) { | |
const col = cr + "," + cg + "," + cb; | |
const g = ctx.createRadialGradient(x, y, 0, x, y, r); | |
g.addColorStop(0, "rgba(" + col + ", " + (ca * 1) + ")"); | |
g.addColorStop(0.5, "rgba(" + col + ", " + (ca * 0.5) + ")"); | |
g.addColorStop(1, "rgba(" + col + ", " + (ca * 0) + ")"); | |
return g; | |
} | |
// framerate | |
static calcFPS() { | |
const now = (+new Date()); | |
const fps = 1000 / (now - lastTime); | |
lastTime = now; | |
return fps.toFixed(); | |
} | |
} | |
/* | |
When want to use angle. | |
*/ | |
class Angle { | |
constructor(angle) { | |
this.a = angle; | |
this.rad = this.a * Math.PI / 180; | |
} | |
incDec(num) { | |
this.a += num; | |
this.rad = this.a * Math.PI / 180; | |
return this.rad; | |
} | |
} | |
/* | |
When want to use vector. | |
*/ | |
class Vector2d { | |
constructor(x, y) { | |
this.vx = x; | |
this.vy = y; | |
} | |
scale(scale) { | |
this.vx *= scale; | |
this.vy *= scale; | |
} | |
add(vec2) { | |
this.vx += vec2.vx; | |
this.vy += vec2.vy | |
} | |
sub(vec2) { | |
this.vx -= vec2.vx; | |
this.vy -= vec2.vy; | |
} | |
negate() { | |
this.vx = -this.vx; | |
this.vy = -this.vy; | |
} | |
length() { | |
return Math.sqrt(this.vx * this.vx + this.vy * this.vy); | |
} | |
lengthSquared() { | |
return this.vx * this.vx + this.vy * this.vy; | |
} | |
normalize() { | |
let len = Math.sqrt(this.vx * this.vx + this.vy * this.vy); | |
if (len) { | |
this.vx /= len; | |
this.vy /= len; | |
} | |
return len; | |
} | |
rotate(angle) { | |
let vx = this.vx; | |
let vy = this.vy; | |
let cosVal = Math.cos(angle); | |
let sinVal = Math.sin(angle); | |
this.vx = vx * cosVal - vy * sinVal; | |
this.vy = vx * sinVal + vy * cosVal; | |
} | |
toString() { | |
return '(' + this.vx.toFixed(3) + ',' + this.vy.toFixed(3) + ')'; | |
} | |
} | |
/* | |
When want to use time. | |
*/ | |
class Stopwatch { | |
constructor(time) { | |
this.startTime = 0; | |
this.running = false; | |
this.elapsed = undefined; | |
} | |
start() { | |
this.startTime = +new Date(); | |
this.elapsedTime = null; | |
this.running = true; | |
} | |
stop() { | |
this.elapsed = (+new Date()) - this.startTime; | |
this.running = false; | |
} | |
getElapsedTime() { | |
if (this.running) { | |
return (+new Date()) - this.startTime; | |
} else { | |
return this.elapsed; | |
} | |
} | |
isRunning() { | |
return this.running; | |
} | |
reset() { | |
this.elapsed = 0; | |
} | |
} | |
/* | |
When want to use collision detection. | |
*/ | |
class Collision { | |
constructor(targetArr) { | |
this.arr = targetArr; | |
} | |
collideAll() { | |
let vec = new Vector2d(0, 0); | |
let dist; | |
let obj1; | |
let obj2; | |
let c; | |
let i; | |
for (c = 0; c < this.arr.length; c++) { | |
obj1 = this.arr[c]; | |
for (i = c + 1; i < this.arr.length; i++) { | |
obj2 = this.arr[i]; | |
vec.vx = obj2.x - obj1.x; | |
vec.vy = obj2.y - obj1.y; | |
dist = vec.length(); | |
if (dist < obj1.r + obj2.r) { | |
vec.normalize(); | |
vec.scale(obj1.r + obj2.r - dist); | |
vec.negate(); | |
obj1.x += vec.vx; | |
obj1.y += vec.vy; | |
this.bounce(obj1, obj2); | |
} | |
} | |
} | |
} | |
bounce(obj1, obj2) { | |
let colnAngle = Math.atan2(obj1.y - obj2.y, obj1.x - obj2.x); | |
let length1 = obj1.v.length(); | |
let length2 = obj2.v.length(); | |
let dirAngle1 = Math.atan2(obj1.v.vy, obj1.v.vx); | |
let dirAngle2 = Math.atan2(obj2.v.vy, obj2.v.vx); | |
let newVX1 = length1 * Math.cos(dirAngle1 - colnAngle); | |
let newVX2 = length2 * Math.cos(dirAngle2 - colnAngle); | |
obj1.v.vy = length1 * Math.sin(dirAngle1 - colnAngle); | |
obj2.v.vy = length2 * Math.sin(dirAngle2 - colnAngle); | |
obj1.v.vx = ((obj1.r - obj2.r) * newVX1 + (2 * obj2.r) * newVX2) / (obj1.r + obj2.r); | |
obj2.v.vx = ((obj2.r - obj1.r) * newVX2 + (2 * obj1.r) * newVX1) / (obj1.r + obj2.r); | |
obj1.v.rotate(colnAngle); | |
obj2.v.rotate(colnAngle); | |
} | |
} | |
/* | |
When want to use controller. | |
*/ | |
class Controller { | |
constructor(id) { | |
this.id = document.getElementById(id); | |
} | |
getVal() { | |
return this.id.value; | |
} | |
} | |
let canvas; | |
let lastTime = 0; // to use framerate. | |
const simplex = new SimplexNoise(); | |
class Canvas { | |
constructor(bool) { | |
// create canvas. | |
this.canvas = document.createElement("canvas"); | |
// if on screen. | |
if (bool === true) { | |
this.canvas.style.position = 'fixed'; | |
this.canvas.style.display = 'block'; | |
this.canvas.style.top = '0'; | |
this.canvas.style.left = '0'; | |
document.getElementsByTagName("body")[0].appendChild(this.canvas); | |
} | |
this.ctx = this.canvas.getContext("2d"); | |
this.width = this.canvas.width = window.innerWidth; | |
this.height = this.canvas.height = window.innerHeight; | |
// mouse infomation. | |
this.mouseX = null; | |
this.mouseY = null; | |
this.mouseZ = null; | |
// shape | |
this.shapeNum = 360; | |
this.shapes = []; | |
} | |
// init, render, resize | |
init() { | |
for (let i = 0; i < this.shapeNum; i++) { | |
const s = new Shape(this.ctx, this.width / 2, this.height / 2, i); | |
this.shapes.push(s); | |
} | |
} | |
render() { | |
this.ctx.clearRect(0, 0, this.width, this.height); | |
for (let i = 0; i < this.shapes.length; i++) { | |
this.shapes[i].render(); | |
} | |
this.drawFPS(); | |
} | |
drawFPS() { | |
const ctx = this.ctx; | |
ctx.save(); | |
ctx.fillStyle = 'white'; | |
ctx.font = '16px sans-selif'; | |
ctx.textAlign = 'right'; | |
ctx.textBaseline = 'bottom'; | |
ctx.fillText(Tool.calcFPS() + ' FPS', this.width, this.height); | |
ctx.restore(); | |
} | |
resize() { | |
this.shapes = []; | |
this.width = this.canvas.width = window.innerWidth; | |
this.height = this.canvas.height = window.innerHeight; | |
this.init(); | |
} | |
} | |
/* | |
Shape class. | |
*/ | |
class Shape { | |
constructor(ctx, x, y, i) { | |
this.ctx = ctx; | |
this.init(x, y, i); | |
} | |
init(x, y, i) { | |
this.x = x; | |
this.y = y; | |
this.i = i; | |
this.r = 100; | |
this.cr = 100; | |
this.a = new Angle(this.i * 1); | |
this.ac = new Angle(180); | |
} | |
draw() { | |
const ctx = this.ctx; | |
const x = Math.cos(this.a.rad) * this.r + this.x; | |
const y = Math.sin(this.a.rad) * this.r + this.y; | |
const noise = simplex.noise3D(x / 100, y / 100, this.ac.rad); | |
ctx.save(); | |
ctx.translate(this.x, this.y); | |
ctx.rotate(Math.sin(this.ac.rad)); | |
ctx.scale(Math.cos(this.ac.rad / 5), Math.sin(this.ac.rad / 3)); | |
ctx.translate(-this.x, -this.y); | |
ctx.globalCompositeOperation = 'lighter' | |
ctx.lineWidth = 0.4; | |
ctx.strokeStyle = 'hsl(' + this.i + ', 80%, 60%)'; | |
ctx.beginPath(); | |
ctx.arc(x + Math.cos(this.ac.rad) * 10 * noise, y + Math.sin(this.ac.rad) * 10 * noise, 0.3 * Math.sin(this.ac.rad) * this.cr + this.cr, 0, Math.PI * 2, false); | |
ctx.stroke(); | |
ctx.restore(); | |
} | |
updateParams() { | |
//this.a.incDec(1); | |
this.ac.incDec(1); | |
} | |
render() { | |
this.draw(); | |
this.updateParams(); | |
} | |
} | |
(function () { | |
"use strict"; | |
window.addEventListener("load", function () { | |
canvas = new Canvas(true); | |
canvas.init(); | |
function render() { | |
window.requestAnimationFrame(function () { | |
canvas.render(); | |
render(); | |
}); | |
} | |
render(); | |
// event | |
window.addEventListener("resize", function() { | |
canvas.resize(); | |
}, false); | |
}); | |
})(); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script> |
* { | |
margin: 0; | |
padding: 0; | |
} | |
html, | |
body { | |
overflow: hidden; | |
} | |
body { | |
position: relative; | |
background-color: #000; | |
} |