- Dapatkan pautan
- X
- E-mel
- Apl Lain
Hanya satu gambaran. Perpaduan (?)
HTML view ▼
Demo
HTML view ▼
<div class="labu">
<canvas id="canvas"></canvas>
</div>
<style>
.labu {
background: black;
width: 100%;
display: flex;
margin: auto;
padding: 0;
}
canvas {
width: 100%;
object-fit: contain;
}
</style>
<script>
"use strict";
function Vector(x, y) {
this.x = x;
this.y = y;
}
function rand(min, max) {
return Math.random() * (max - min) + min;
}
let worldGravity = { x: 0, y: 0, z: 0 };
class MovementRules {
constructor(_types) {
this.rules = {};
this.types = [];
this.types = _types;
for (let i = 0; i < this.types.length; i++) {
for (let j = 0; j < this.types.length; j++) {
this.rules[this.types[i] + '_' + this.types[j]] = {
vector: new Vector(rand(-1, 1), rand(-1, 1)),
distance: rand(0, 100)
};
}
}
}
getRule(typeA, typeB) {
return this.rules[typeA + '_' + typeB];
}
applyRule(particleA, particleB) {
let rule = this.getRule(particleA.type, particleB.type);
let dx = particleA.position.x - particleB.position.x;
let dy = particleA.position.y - particleB.position.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let force = rule.distance - distance;
let vector = rule.vector;
particleA.velocity.x += 0.0000033 * force * vector.x;
particleA.velocity.y += 0.0000033 * force * vector.y;
}
}
const types = [
'yellow',
'blue',
'red',
'green',
'purple',
'orange',
'brown',
'darkblue',
'darkgreen',
'darkred',
'darkyellow',
'darkpurple',
'darkorange',
'lightblue',
'lightgreen',
];
const rules = new MovementRules(types);
class DeformableCircle {
constructor(context, x, y, radius = 10, numSegments = 48) {
this.context = context;
this.position = { x, y };
this.velocity = { x: 0, y: 0 };
this.acceleration = { x: 0, y: 0 };
this.radius = radius;
this.numSegments = numSegments;
this.deformations = new Array(numSegments).fill(0);
this.type = types[Math.floor(Math.random() * types.length)];
}
displayPathAsCurves(ctx, xyPath) {
ctx.beginPath();
ctx.moveTo(xyPath[0][0], xyPath[0][1]);
for (let i = 1; i < xyPath.length - 1; i++) {
const xc = (xyPath[i][0] + xyPath[i + 1][0]) / 2;
const yc = (xyPath[i][1] + xyPath[i + 1][1]) / 2;
ctx.quadraticCurveTo(xyPath[i][0], xyPath[i][1], xc, yc);
}
const xyl = xyPath.length;
const xyLast = xyl - 1;
const xy2nd = xyl - 2;
ctx.quadraticCurveTo(xyPath[xy2nd][0], xyPath[xy2nd][1], xyPath[xyLast][0], xyPath[xyLast][1]);
}
getAverageDeformations() {
let total = 0;
for (let i = 0; i < this.deformations.length; i++) {
total += this.deformations[i];
}
return total;
}
reform() { this.deformations.forEach((d, i) => this.deformations[i] *= 0.9999); }
deform(impactVector, impactForce) {
const dx = impactVector.x - this.position.x;
const dy = impactVector.y - this.position.y;
const ns = this.numSegments;
const df = this.deformations;
const rd = this.radius;
const d = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const segment = Math.floor((angle / (Math.PI * 2)) * ns);
df[segment] -= impactForce / rd;
df[(segment + ns) % ns] -= impactForce / rd;
for (let i = 1; i < this.numSegments / 8; i++) {
const index = (segment + i) % ns;
const distance = Math.abs(i);
df[index] += impactForce / (distance * rd);
df[(index + ns) % ns] -= impactForce / rd;
}
for (let i = 1; i < ns / 8; i++) {
const index = (segment - i + ns) % ns;
const distance = Math.abs(i);
df[index] -= impactForce / (distance * rd);
df[(index + ns) % ns] += impactForce / rd;
}
for (let i = 0; i < this.deformations.length; i++) {
df[i] *= 0.9999;
if (df[i] > rd / 3) {
df[i] = rd / 3;
}
if (df[i] < -rd / 3) {
df[i] = -rd / 3;
}
}
}
get color() { return this._color; }
set color(value) { this._color = value; ctx.fillStyle = value; ctx.strokeStyle = value; }
draw(ctx) {
ctx.beginPath();
let path = [];
for (let i = 0; i < this.numSegments; i++) {
const angle = (i / this.numSegments) * Math.PI * 2;
const x = Math.cos(angle) * (this.radius + this.deformations[i]);
const y = Math.sin(angle) * (this.radius + this.deformations[i]);
if (i == 0)
path.push([x + this.position.x, y + this.position.y]);
else
path.push([x + this.position.x, y + this.position.y]);
}
ctx.fillStyle = this.type;
this.displayPathAsCurves(ctx, path);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
isCollidingWithCircle(circle) {
const dx = circle.position.x - this.position.x;
const dy = circle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
return { colliding: d < this.radius + circle.radius * 1, distance: d, distanceX: dx, distanceY: dy };
}
getRadiusFrom(otherCircle) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const angle = Math.atan2(dy, dx);
const segment = Math.floor((angle / (Math.PI * 2)) * this.numSegments);
return this.radius + this.deformations[segment];
}
deformCollidingCircles(otherCircle, collisionData) {
const collision = collisionData;
const dX = collision.distanceX;
const dY = collision.distanceY;
const impactForce = this.radius;
const impactVector = {
x: this.position.x - dX,
y: this.position.y - dY
};
const complementaryImpactVector = {
x: otherCircle.position.x + dX,
y: otherCircle.position.y + dY
};
this.deform(complementaryImpactVector, impactForce);
otherCircle.deform(impactVector, impactForce);
}
handleCollision(otherCircle, collisionData) {
this.deformCollidingCircles(otherCircle, collisionData);
this.moveApartFrom(otherCircle);
}
moveApartFrom(otherCircle) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
const distance = d - this.radius - otherCircle.radius;
const moveX = -1 * (dx / d) * distance;
const moveY = -1 * (dy / d) * distance;
const avgDef = this.getAverageDeformations();
const deformation = this.deformations.reduce((a, b) => a + b, 0);
const otherDeformation = otherCircle.deformations.reduce((a, b) => a + b, 0);
const totalDeformation = deformation + otherDeformation;
const totalRadius = this.radius + otherCircle.radius;
const totalDistance = distance - totalDeformation;
const moveDistance = totalDistance - totalRadius;
this.position.x -= moveX;
this.position.y -= moveY;
otherCircle.position.x += moveX;
otherCircle.position.y += moveY;
}
applyForceVector(otherCircle, strength, distance, distanceX, distanceY) {
const force = (this.radius * otherCircle.radius) / (distance * distance);
const forceVector = { x: distanceX, y: distanceY };
const forceX = strength * (force * forceVector.x) / distance;
const forceY = strength * (force * forceVector.y) / distance;
this.velocity.x += forceX / this.radius;
this.velocity.y += forceY / this.radius;
otherCircle.velocity.x -= forceX / otherCircle.radius;
otherCircle.velocity.y -= forceY / otherCircle.radius;
}
applyRule(otherCircle) {
rules.applyRule(this, otherCircle);
}
applyWorldGravity() {
this.velocity.x += worldGravity.x;
this.velocity.y += worldGravity.y + worldGravity.z;
}
bounceOff(otherCircle, strength) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const impactForce = this.radius;
const impactVector = { x: this.position.x - dx, y: this.position.y - dy, };
const complementaryImpactVector = { x: otherCircle.position.x + dx, y: otherCircle.position.y + dy, };
this.deform(complementaryImpactVector, impactForce);
otherCircle.deform(impactVector, impactForce);
this.velocity.x -= strength * Math.cos(angle) * impactForce;
this.velocity.y -= strength * Math.sin(angle) * impactForce;
otherCircle.velocity.x += strength * Math.cos(angle) * impactForce;
otherCircle.velocity.y += strength * Math.sin(angle) * impactForce;
}
stickTo(otherCircle) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const impactForce = this.radius;
const impactVector = { x: this.position.x - dx, y: this.position.y - dy, };
const complementaryImpactVector = { x: otherCircle.position.x + dx, y: otherCircle.position.y + dy, };
this.deform(complementaryImpactVector, impactForce);
otherCircle.deform(impactVector, impactForce);
this.velocity.x = 0;
this.velocity.y = 0;
otherCircle.velocity.x = 0;
otherCircle.velocity.y = 0;
}
constrain() {
if (this.position.x + this.radius < 0)
this.position.x = canvas.width - this.radius;
if (this.position.x + this.radius > canvas.width)
this.position.x = this.radius;
if (this.position.y + this.radius < 0)
this.position.y = canvas.height - this.radius;
if (this.position.y + this.radius > canvas.height)
this.position.y = this.radius;
}
interact(otherCircle) {
const { colliding, distance, distanceX, distanceY, } = this.isCollidingWithCircle(otherCircle);
if (colliding) {
this.deformCollidingCircles(otherCircle, { distanceX, distanceY });
this.handleCollision(otherCircle, {
colliding,
distance,
distanceX,
distanceY,
});
this.stickTo(otherCircle, 0.5);
}
else {
this.reform();
const strength = 0.9100;
this.applyForceVector(otherCircle, strength, distance, distanceX, distanceY);
}
this.applyRule(otherCircle);
this.applyWorldGravity();
this.velocity.x *= 0.99;
this.velocity.y *= 0.99;
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
this.constrain();
}
isCollidingWithPoint(point) {
const dx = point.x - this.position.x;
const dy = point.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
return d < this.radius;
}
}
const circles = [];
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.globalAlpha = 0.7;
for (let i = 0; i < 36; i++) {
circles.push(new DeformableCircle(ctx, Math.random() * canvas.width, Math.random() * canvas.height, 44 + Math.random() * 62, 36));
}
const loop = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
for (let j = i + 1; j < circles.length; j++) {
circles[i].interact(circles[j]);
}
circles[i].reform();
}
requestAnimationFrame(loop);
};
loop();
window.addEventListener('deviceorientation', (event) => {
worldGravity.x = event.gamma;
worldGravity.y = event.beta;
worldGravity.z = event.alpha;
});
const mouseState = {
down: false,
x: 0,
y: 0,
lastX: 0,
lastY: 0,
velocityX: 0,
velocityY: 0,
lastTime: 0,
touchedCircle: null,
};
window.addEventListener('touchstart', (event) => {
mouseState.down = true;
mouseState.x = event.touches[0].clientX;
mouseState.y = event.touches[0].clientY;
mouseState.lastX = event.touches[0].clientX;
mouseState.lastY = event.touches[0].clientY;
mouseState.lastTime = Date.now();
mouseState.touchedCircle = circles.find(circle => circle.isCollidingWithPoint(mouseState.x, mouseState.y));
});
window.addEventListener('touchend', (event) => {
mouseState.down = false;
mouseState.touchedCircle = null;
});
window.addEventListener('touchmove', (event) => {
mouseState.x = event.touches[0].clientX;
mouseState.y = event.touches[0].clientY;
mouseState.velocityX = mouseState.x - mouseState.lastX;
mouseState.velocityY = mouseState.y - mouseState.lastY;
mouseState.lastX = mouseState.x;
mouseState.lastY = mouseState.y;
const now = Date.now();
const timeDiff = now - mouseState.lastTime;
mouseState.lastTime = now;
if (mouseState.touchedCircle) {
mouseState.touchedCircle.position.x = mouseState.x;
mouseState.touchedCircle.position.y = mouseState.y;
mouseState.touchedCircle.velocity.x = mouseState.velocityX / timeDiff;
mouseState.touchedCircle.velocity.y = mouseState.velocityY / timeDiff;
}
});
window.addEventListener('mousedown', (event) => {
mouseState.down = true;
mouseState.x = event.clientX;
mouseState.y = event.clientY;
mouseState.lastX = event.clientX;
mouseState.lastY = event.clientY;
mouseState.lastTime = Date.now();
mouseState.touchedCircle = circles.find(circle => circle.isCollidingWithPoint(mouseState.x, mouseState.y));
});
window.addEventListener('mouseup', (event) => {
mouseState.down = false;
mouseState.touchedCircle = null;
});
window.addEventListener('mousemove', (event) => {
mouseState.x = event.clientX;
mouseState.y = event.clientY;
mouseState.velocityX = mouseState.x - mouseState.lastX;
mouseState.velocityY = mouseState.y - mouseState.lastY;
mouseState.lastX = mouseState.x;
mouseState.lastY = mouseState.y;
const now = Date.now();
const timeDiff = now - mouseState.lastTime;
mouseState.lastTime = now;
if (mouseState.touchedCircle) {
mouseState.touchedCircle.position.x = mouseState.x;
mouseState.touchedCircle.position.y = mouseState.y;
mouseState.touchedCircle.velocity.x = mouseState.velocityX / timeDiff;
mouseState.touchedCircle.velocity.y = mouseState.velocityY / timeDiff;
}
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
<canvas id="canvas"></canvas>
</div>
<style>
.labu {
background: black;
width: 100%;
display: flex;
margin: auto;
padding: 0;
}
canvas {
width: 100%;
object-fit: contain;
}
</style>
<script>
"use strict";
function Vector(x, y) {
this.x = x;
this.y = y;
}
function rand(min, max) {
return Math.random() * (max - min) + min;
}
let worldGravity = { x: 0, y: 0, z: 0 };
class MovementRules {
constructor(_types) {
this.rules = {};
this.types = [];
this.types = _types;
for (let i = 0; i < this.types.length; i++) {
for (let j = 0; j < this.types.length; j++) {
this.rules[this.types[i] + '_' + this.types[j]] = {
vector: new Vector(rand(-1, 1), rand(-1, 1)),
distance: rand(0, 100)
};
}
}
}
getRule(typeA, typeB) {
return this.rules[typeA + '_' + typeB];
}
applyRule(particleA, particleB) {
let rule = this.getRule(particleA.type, particleB.type);
let dx = particleA.position.x - particleB.position.x;
let dy = particleA.position.y - particleB.position.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let force = rule.distance - distance;
let vector = rule.vector;
particleA.velocity.x += 0.0000033 * force * vector.x;
particleA.velocity.y += 0.0000033 * force * vector.y;
}
}
const types = [
'yellow',
'blue',
'red',
'green',
'purple',
'orange',
'brown',
'darkblue',
'darkgreen',
'darkred',
'darkyellow',
'darkpurple',
'darkorange',
'lightblue',
'lightgreen',
];
const rules = new MovementRules(types);
class DeformableCircle {
constructor(context, x, y, radius = 10, numSegments = 48) {
this.context = context;
this.position = { x, y };
this.velocity = { x: 0, y: 0 };
this.acceleration = { x: 0, y: 0 };
this.radius = radius;
this.numSegments = numSegments;
this.deformations = new Array(numSegments).fill(0);
this.type = types[Math.floor(Math.random() * types.length)];
}
displayPathAsCurves(ctx, xyPath) {
ctx.beginPath();
ctx.moveTo(xyPath[0][0], xyPath[0][1]);
for (let i = 1; i < xyPath.length - 1; i++) {
const xc = (xyPath[i][0] + xyPath[i + 1][0]) / 2;
const yc = (xyPath[i][1] + xyPath[i + 1][1]) / 2;
ctx.quadraticCurveTo(xyPath[i][0], xyPath[i][1], xc, yc);
}
const xyl = xyPath.length;
const xyLast = xyl - 1;
const xy2nd = xyl - 2;
ctx.quadraticCurveTo(xyPath[xy2nd][0], xyPath[xy2nd][1], xyPath[xyLast][0], xyPath[xyLast][1]);
}
getAverageDeformations() {
let total = 0;
for (let i = 0; i < this.deformations.length; i++) {
total += this.deformations[i];
}
return total;
}
reform() { this.deformations.forEach((d, i) => this.deformations[i] *= 0.9999); }
deform(impactVector, impactForce) {
const dx = impactVector.x - this.position.x;
const dy = impactVector.y - this.position.y;
const ns = this.numSegments;
const df = this.deformations;
const rd = this.radius;
const d = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const segment = Math.floor((angle / (Math.PI * 2)) * ns);
df[segment] -= impactForce / rd;
df[(segment + ns) % ns] -= impactForce / rd;
for (let i = 1; i < this.numSegments / 8; i++) {
const index = (segment + i) % ns;
const distance = Math.abs(i);
df[index] += impactForce / (distance * rd);
df[(index + ns) % ns] -= impactForce / rd;
}
for (let i = 1; i < ns / 8; i++) {
const index = (segment - i + ns) % ns;
const distance = Math.abs(i);
df[index] -= impactForce / (distance * rd);
df[(index + ns) % ns] += impactForce / rd;
}
for (let i = 0; i < this.deformations.length; i++) {
df[i] *= 0.9999;
if (df[i] > rd / 3) {
df[i] = rd / 3;
}
if (df[i] < -rd / 3) {
df[i] = -rd / 3;
}
}
}
get color() { return this._color; }
set color(value) { this._color = value; ctx.fillStyle = value; ctx.strokeStyle = value; }
draw(ctx) {
ctx.beginPath();
let path = [];
for (let i = 0; i < this.numSegments; i++) {
const angle = (i / this.numSegments) * Math.PI * 2;
const x = Math.cos(angle) * (this.radius + this.deformations[i]);
const y = Math.sin(angle) * (this.radius + this.deformations[i]);
if (i == 0)
path.push([x + this.position.x, y + this.position.y]);
else
path.push([x + this.position.x, y + this.position.y]);
}
ctx.fillStyle = this.type;
this.displayPathAsCurves(ctx, path);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
isCollidingWithCircle(circle) {
const dx = circle.position.x - this.position.x;
const dy = circle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
return { colliding: d < this.radius + circle.radius * 1, distance: d, distanceX: dx, distanceY: dy };
}
getRadiusFrom(otherCircle) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const angle = Math.atan2(dy, dx);
const segment = Math.floor((angle / (Math.PI * 2)) * this.numSegments);
return this.radius + this.deformations[segment];
}
deformCollidingCircles(otherCircle, collisionData) {
const collision = collisionData;
const dX = collision.distanceX;
const dY = collision.distanceY;
const impactForce = this.radius;
const impactVector = {
x: this.position.x - dX,
y: this.position.y - dY
};
const complementaryImpactVector = {
x: otherCircle.position.x + dX,
y: otherCircle.position.y + dY
};
this.deform(complementaryImpactVector, impactForce);
otherCircle.deform(impactVector, impactForce);
}
handleCollision(otherCircle, collisionData) {
this.deformCollidingCircles(otherCircle, collisionData);
this.moveApartFrom(otherCircle);
}
moveApartFrom(otherCircle) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
const distance = d - this.radius - otherCircle.radius;
const moveX = -1 * (dx / d) * distance;
const moveY = -1 * (dy / d) * distance;
const avgDef = this.getAverageDeformations();
const deformation = this.deformations.reduce((a, b) => a + b, 0);
const otherDeformation = otherCircle.deformations.reduce((a, b) => a + b, 0);
const totalDeformation = deformation + otherDeformation;
const totalRadius = this.radius + otherCircle.radius;
const totalDistance = distance - totalDeformation;
const moveDistance = totalDistance - totalRadius;
this.position.x -= moveX;
this.position.y -= moveY;
otherCircle.position.x += moveX;
otherCircle.position.y += moveY;
}
applyForceVector(otherCircle, strength, distance, distanceX, distanceY) {
const force = (this.radius * otherCircle.radius) / (distance * distance);
const forceVector = { x: distanceX, y: distanceY };
const forceX = strength * (force * forceVector.x) / distance;
const forceY = strength * (force * forceVector.y) / distance;
this.velocity.x += forceX / this.radius;
this.velocity.y += forceY / this.radius;
otherCircle.velocity.x -= forceX / otherCircle.radius;
otherCircle.velocity.y -= forceY / otherCircle.radius;
}
applyRule(otherCircle) {
rules.applyRule(this, otherCircle);
}
applyWorldGravity() {
this.velocity.x += worldGravity.x;
this.velocity.y += worldGravity.y + worldGravity.z;
}
bounceOff(otherCircle, strength) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const impactForce = this.radius;
const impactVector = { x: this.position.x - dx, y: this.position.y - dy, };
const complementaryImpactVector = { x: otherCircle.position.x + dx, y: otherCircle.position.y + dy, };
this.deform(complementaryImpactVector, impactForce);
otherCircle.deform(impactVector, impactForce);
this.velocity.x -= strength * Math.cos(angle) * impactForce;
this.velocity.y -= strength * Math.sin(angle) * impactForce;
otherCircle.velocity.x += strength * Math.cos(angle) * impactForce;
otherCircle.velocity.y += strength * Math.sin(angle) * impactForce;
}
stickTo(otherCircle) {
const dx = otherCircle.position.x - this.position.x;
const dy = otherCircle.position.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const impactForce = this.radius;
const impactVector = { x: this.position.x - dx, y: this.position.y - dy, };
const complementaryImpactVector = { x: otherCircle.position.x + dx, y: otherCircle.position.y + dy, };
this.deform(complementaryImpactVector, impactForce);
otherCircle.deform(impactVector, impactForce);
this.velocity.x = 0;
this.velocity.y = 0;
otherCircle.velocity.x = 0;
otherCircle.velocity.y = 0;
}
constrain() {
if (this.position.x + this.radius < 0)
this.position.x = canvas.width - this.radius;
if (this.position.x + this.radius > canvas.width)
this.position.x = this.radius;
if (this.position.y + this.radius < 0)
this.position.y = canvas.height - this.radius;
if (this.position.y + this.radius > canvas.height)
this.position.y = this.radius;
}
interact(otherCircle) {
const { colliding, distance, distanceX, distanceY, } = this.isCollidingWithCircle(otherCircle);
if (colliding) {
this.deformCollidingCircles(otherCircle, { distanceX, distanceY });
this.handleCollision(otherCircle, {
colliding,
distance,
distanceX,
distanceY,
});
this.stickTo(otherCircle, 0.5);
}
else {
this.reform();
const strength = 0.9100;
this.applyForceVector(otherCircle, strength, distance, distanceX, distanceY);
}
this.applyRule(otherCircle);
this.applyWorldGravity();
this.velocity.x *= 0.99;
this.velocity.y *= 0.99;
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
this.constrain();
}
isCollidingWithPoint(point) {
const dx = point.x - this.position.x;
const dy = point.y - this.position.y;
const d = Math.sqrt(dx * dx + dy * dy);
return d < this.radius;
}
}
const circles = [];
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.globalAlpha = 0.7;
for (let i = 0; i < 36; i++) {
circles.push(new DeformableCircle(ctx, Math.random() * canvas.width, Math.random() * canvas.height, 44 + Math.random() * 62, 36));
}
const loop = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
for (let j = i + 1; j < circles.length; j++) {
circles[i].interact(circles[j]);
}
circles[i].reform();
}
requestAnimationFrame(loop);
};
loop();
window.addEventListener('deviceorientation', (event) => {
worldGravity.x = event.gamma;
worldGravity.y = event.beta;
worldGravity.z = event.alpha;
});
const mouseState = {
down: false,
x: 0,
y: 0,
lastX: 0,
lastY: 0,
velocityX: 0,
velocityY: 0,
lastTime: 0,
touchedCircle: null,
};
window.addEventListener('touchstart', (event) => {
mouseState.down = true;
mouseState.x = event.touches[0].clientX;
mouseState.y = event.touches[0].clientY;
mouseState.lastX = event.touches[0].clientX;
mouseState.lastY = event.touches[0].clientY;
mouseState.lastTime = Date.now();
mouseState.touchedCircle = circles.find(circle => circle.isCollidingWithPoint(mouseState.x, mouseState.y));
});
window.addEventListener('touchend', (event) => {
mouseState.down = false;
mouseState.touchedCircle = null;
});
window.addEventListener('touchmove', (event) => {
mouseState.x = event.touches[0].clientX;
mouseState.y = event.touches[0].clientY;
mouseState.velocityX = mouseState.x - mouseState.lastX;
mouseState.velocityY = mouseState.y - mouseState.lastY;
mouseState.lastX = mouseState.x;
mouseState.lastY = mouseState.y;
const now = Date.now();
const timeDiff = now - mouseState.lastTime;
mouseState.lastTime = now;
if (mouseState.touchedCircle) {
mouseState.touchedCircle.position.x = mouseState.x;
mouseState.touchedCircle.position.y = mouseState.y;
mouseState.touchedCircle.velocity.x = mouseState.velocityX / timeDiff;
mouseState.touchedCircle.velocity.y = mouseState.velocityY / timeDiff;
}
});
window.addEventListener('mousedown', (event) => {
mouseState.down = true;
mouseState.x = event.clientX;
mouseState.y = event.clientY;
mouseState.lastX = event.clientX;
mouseState.lastY = event.clientY;
mouseState.lastTime = Date.now();
mouseState.touchedCircle = circles.find(circle => circle.isCollidingWithPoint(mouseState.x, mouseState.y));
});
window.addEventListener('mouseup', (event) => {
mouseState.down = false;
mouseState.touchedCircle = null;
});
window.addEventListener('mousemove', (event) => {
mouseState.x = event.clientX;
mouseState.y = event.clientY;
mouseState.velocityX = mouseState.x - mouseState.lastX;
mouseState.velocityY = mouseState.y - mouseState.lastY;
mouseState.lastX = mouseState.x;
mouseState.lastY = mouseState.y;
const now = Date.now();
const timeDiff = now - mouseState.lastTime;
mouseState.lastTime = now;
if (mouseState.touchedCircle) {
mouseState.touchedCircle.position.x = mouseState.x;
mouseState.touchedCircle.position.y = mouseState.y;
mouseState.touchedCircle.velocity.x = mouseState.velocityX / timeDiff;
mouseState.touchedCircle.velocity.y = mouseState.velocityY / timeDiff;
}
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
Ulasan