initSimulator = () => {
return (function() {
const maxRadius = worldDims.width / 2,
level = -brickDims.height / 2;
let requestId,
restartTimeout,
waving = false,
disposing = false,
matrix = [], tweens = [], timelines = [];
function all() { return matrix.flatMap(r => r); }
function getBricksOnCircle(target, radius) {
return all().filter(b => isOnCircle(b, target.location.x, target.location.y, radius));
}
function isOnCircle(brick, h, k, r) {
const m = r > 1 ? r - 1 : r;
const v = Math.pow(brick.location.x - h, 2) + Math.pow(brick.location.y - k, 2);
return v >= m * m && v <= r * r;
}
function killTweens() {
tweens.forEach(t => t.kill());
tweens = [];
}
function killTimelines() {
timelines.forEach(t => t.kill());
timelines = [];
}
function levelBricks(y, onComplete) {
all().forEach(b => tweens.push(gsap.to(b.position, {duration: 1, y: y})));
Promise.all(tweens).then(() => {
killTweens();
if (onComplete) onComplete();
});
}
function drawBricks() {
let x = 0, y = 0;
for(let i = -worldDims.width; i < worldDims.width; i += brickDims.width) {
const row = [];
matrix.push(row);
for(let j = -worldDims.height; j < worldDims.height; j += brickDims.depth) {
const c = addCuboid(
brickDims.width, brickDims.height, brickDims.depth,
i, 0, j
);
c.location = new THREE.Vector2(x, y++);
row.push(c);
}
x++;
y = 0;
}
}
function run() {
disposing = false;
drawBricks();
animate();
vibrate();
function animate() {
requestId = requestAnimationFrame(animate);
render();
}
}
function restart() {
Promise.all(timelines).then(() => {
// Check if any timeline is still active
if (!timelines.some(tl => tl.isActive())) {
restartTimeout = setTimeout(() => {
// Check if there is any timeline activated between setTimeout and timeout
if (!timelines.some(tl => tl.isActive())) levelBricks(brickDims.height, vibrate);
}, 1000);
}
});
}
function vibrate() {
waving = false;
killTweens();
all().forEach(b => {
const y = brickDims.height * Math.random();
tweens.push(
gsap.to(
b.position,
{duration: 0.5, y: y, onUpdate: () => onUpdate(b)}
));
});
Promise.all(tweens).then(() => vibrate());
function onUpdate(b) {
const mIndex = Math.floor(b.position.y);
if (b.mIndex != mIndex) {
b.mIndex = mIndex;
b.material = pool.floorMaterials[mIndex];
}
}
}
function touch(target) {
clearTimeout(restartTimeout);
killTweens();
if (waving)
drop(target);
else {
//levelBricks(level, () => drop(target));
levelBricks(level);
drop(target);
}
}
function drop(target) {
const p = target.position;
const cuboid = addCuboid(5, 5, 5, p.x, 100, p.z);
gsap.to(
cuboid.position,
{
duration: 1,
y: level,
onComplete: () => {
scene.remove(cuboid);
cuboid.remove(cuboid.children[0]);
wave(target, 1);
}
});
}
function wave(center, radius) {
if (disposing) return;
if (radius > maxRadius) {
restart();
return;
}
waving = true;
let bricks = getBricksOnCircle(center, radius);
if (radius === 1) bricks = [center].concat(bricks);
const tl = gsap.timeline();
timelines.push(tl);
const targets = bricks.map(b => b.position);
for(let p = brickDims.height; p > 0; p--) {
if (!disposing) {
tl.to(targets, {duration: 0.25, y: p * Math.random() / 2, onUpdate: onUpdate})
.to(targets, {duration: 0.25, y: -p * Math.random() / 2, onUpdate: onUpdate})
.to(targets, {duration: 0.25, y: level, onComplete: onComplete});
if (p === brickDims.height) setTimeout(() => wave(center, ++radius), 25);
}
}
function onUpdate() {
const y = bricks[0].position.y;
const mIndex = (y < 0 ? Math.ceil(y) : Math.floor(y)) * 2 + brickDims.height - 1;
bricks.forEach(b => {
if (b.mIndex !== mIndex) {
b.mIndex = mIndex;
b.material = pool.floorMaterials[mIndex];
}
});
}
function onComplete() {
bricks.forEach(b => {
const mIndex = Math.floor(brickDims.height * Math.random());
b.material = pool.floorMaterials[mIndex];
});
}
}
return {
run: () => run(),
touch: target => touch(target),
dispose: () => {
disposing = true;
cancelAnimationFrame(requestId);
killTweens();
killTimelines();
matrix = [];
}
}
})();
}