boreal.zip_v0/collectables/toybox.js

213 lines
5.5 KiB
JavaScript
Raw Permalink Normal View History

2024-07-17 01:50:54 +00:00
// toybox.js by Dante Scanline 2022
// UNIVERSAL PUBLIC DOMAIN LICENSE - This code and everything else in the universe is in the public domain
/// ------------
// CONFIGURATION VARIABLES, please change these!
const BUBBLE_IN = true; //makes the toys come in one at a time
const BUBBLE_DELAY = 250; // if bubbling in, how much time in miliseconds between each new toy. bigger number is slowe
const PUSH_SPEED = 3; // base amount that toys push each other, bigger number means quicker stronger pushing
const RANDOM_START_OFFSET = 200; // random distance from center in pixels where toys are spawned inside off
/// ------------
let toyContainer;
let items = []; // array of dom elements
// FisherYates knuth shuffle
function shuffle(array) {
let currentIndex = array.length,
randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
// reenable items sequentially for bubbling
function reEnable(index) {
if (index > items.length - 1) {
return;
}
items[index].domElement.style.display = "initial";
setTimeout(() => {
reEnable(index + 1);
}, BUBBLE_DELAY);
}
// the object, with width height from DOM and then a position we set :)
class Toy {
constructor(domElement) {
this.domElement = domElement;
this.domElement.style.position = "absolute";
let rect = toyContainer.getBoundingClientRect();
this.x = rect.width / 2 + 100 - Math.random() * 200;
this.y = rect.height / 2 + 100 - Math.random() * 200;
this.visualX = this.x;
this.visualY = this.y;
this.updateSize();
this.updatePosition();
}
updatePosition() {
this.visualX += (this.x - this.visualX) * 0.1;
this.visualY += (this.y - this.visualY) * 0.1;
this.domElement.style.left = `${this.x}px`;
this.domElement.style.top = `${this.y}px`;
}
updateSize() {
this.width = this.domElement.clientWidth;
if (this.width == 0 || this.width == undefined) {
console.error("no size for element yet");
}
this.height = this.domElement.clientHeight;
if (this.width < 65 || this.height < 65) {
this.domElement.style["z-index"] = "20";
}
this.domElement.style.marginLeft = `${-this.width / 2}px`;
this.domElement.style.marginTop = `${-this.height / 2}px`;
// this.domElement.style.transform = `translate(${-this.width / 2}px, ${-this.height / 2}px)`
}
pointOverlaps(xx, yy) {
return (
xx > this.x - this.width / 2 &&
xx < this.x + this.width / 2 &&
yy > this.y - this.height / 2 &&
yy < this.y + this.height / 2
);
}
toyOverlaps(otherToy) {
const corners = [
[otherToy.x - otherToy.width / 2, otherToy.y - otherToy.height / 2],
[otherToy.x + otherToy.width / 2, otherToy.y - otherToy.height / 2],
[
otherToy.x - otherToy.height / 2,
otherToy.y + otherToy.height / 2,
],
[otherToy.x + otherToy.width / 2, otherToy.y + otherToy.height / 2],
];
// check if the other toys corners are inside our rect
for (let i = 0; i < corners.length; i++) {
let pair = corners[i];
if (this.pointOverlaps(pair[0], pair[1])) {
return true;
}
}
// edge case, where we are fit entirely inside the other rect, so check one of our points on them
return otherToy.pointOverlaps(this.x, this.y);
}
getPushed(otherToy) {
let distance = PUSH_SPEED;
let centerX = this.x;
let centerY = this.y;
let otherCenterX = otherToy.x;
let otherCenterY = otherToy.y;
let angle = Math.atan2(centerY - otherCenterY, centerX - otherCenterX);
let finalStrength = distance;
// if the toy pushing us is roughly bigger than us, get pushed more to help offset some biasing
if (
otherToy.width + otherToy.height >
(this.width + this.height) * 1.3
) {
finalStrength *= 3;
}
this.x += Math.cos(angle) * finalStrength;
this.y += Math.sin(angle) * finalStrength;
this.updatePosition();
}
}
document.addEventListener("DOMContentLoaded", init);
function init() {
toyContainer = document.querySelector("#toybox");
let nodes = Array.from(toyContainer.childNodes);
nodes = nodes.filter((node) => node.nodeType == 1);
nodes = shuffle(nodes); // help prevent biases
for (let i = 0; i < nodes.length; i++) {
items.push(new Toy(nodes[i]));
if (BUBBLE_IN) {
if (i > 3) {
items[i].domElement.style.display = "none";
}
}
}
if (BUBBLE_IN) {
reEnable(3);
}
requestAnimationFrame(main);
}
function main() {
items.map((item) => item.updateSize());
for (let i = 0; i < items.length; i++) {
const itemA = items[i];
for (let k = 0; k < items.length; k++) {
if (i == k) continue;
const itemB = items[k];
if (itemA.toyOverlaps(itemB)) {
itemB.getPushed(itemA);
}
}
}
let rect = toyContainer.getBoundingClientRect();
for (let i = 0; i < items.length; i++) {
const itemA = items[i];
if (itemA.x - itemA.width / 2 < 0) {
itemA.x += 10;
itemA.updatePosition();
}
if (itemA.y - itemA.height / 2 < 0) {
itemA.y += 10;
itemA.updatePosition();
}
if (itemA.x + itemA.width / 2 > rect.width) {
itemA.x -= 10;
itemA.updatePosition();
}
if (itemA.y + itemA.height / 2 > rect.height) {
itemA.y -= 5;
itemA.updatePosition();
}
}
requestAnimationFrame(main);
}