boreal.zip_v0/collectables/toybox.js

213 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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);
}