xxxxxxxxxx
597
const DEBUG = false;
class Cog {
constructor(x, y, radius, teeth, toothType, rotationSpeed = 0) {
this.x = x;
this.y = y;
this.radius = radius;
this.teeth = teeth;
this.toothType = toothType;
this.rotationSpeed = rotationSpeed;
this.angle = 0;
this.neighbors = [];
this.color = color(random(360), 80, 80); // Main color with random hue
this.toothColor = deriveSimilarColor(this.color)
this.toothHeight = random(15, 25);
this.toothWidth = random(8, 12);
this.toothSkew = random(-5, 5);
this.material = random(['steel', 'wood', 'copper','iron','gold']);
this.friction = random(0.1, 0.5);
this.wearRate = random(0.01, 0.1);
this.noiseLevel = random(10, 50);
this.efficiency = random(70, 100);
this.temperature = random(20, 40);
}
link(otherCog) {
let ratio = this.teeth / otherCog.teeth;
this.neighbors.push({ cog: otherCog, ratio });
otherCog.neighbors.push({ cog: this, ratio: 1 / ratio });
}
update() {
if (this.rotationSpeed !== 0) {
this.angle += this.rotationSpeed;
}
for (let neighbor of this.neighbors) {
if (this.rotationSpeed === 0) {
this.angle = neighbor.cog.angle * -neighbor.ratio;
} else {
neighbor.cog.angle = this.angle * -neighbor.ratio;
}
}
}
linkWithBar(otherCog) {
this.link(otherCog); // Existing linking logic
this.neighbors.find(n => n.cog === otherCog).hasBar = true; // Add a bar connection
}
linkWithRubberBand(otherCog) {
this.link(otherCog); // Existing linking logic
this.neighbors.find(n => n.cog === otherCog).hasRubberBand = true; // Add a rubber band connection
}
drawConnection(otherCog, connectionType) {
push();
strokeWeight(connectionType === 'bar' ? 4 : 2); // Thicker line for bars
stroke(connectionType === 'bar' ? 0 : 255, 0, 0); // Black for bars, red for rubber bands
line(this.x, this.y, otherCog.x, otherCog.y);
pop();
}
show(machineX, machineY) {
push();
translate(this.x - machineX, this.y - machineY);
rotate(this.angle);
stroke(0);
fill(this.color);
ellipse(0, 0, this.radius * 2);
fill(this.toothColor)
for (let i = 0; i < this.teeth; i++) {
push();
rotate((360 / this.teeth) * i);
translate(0, -this.radius);
if (this.toothType === 'rectangular') {
rect(-this.toothWidth / 2, 0, this.toothWidth, this.toothHeight);
} else if (this.toothType === 'triangular') {
triangle(
-this.toothWidth / 2, 0,
this.toothWidth / 2, 0,
this.toothSkew, this.toothHeight
);
}
pop();
}
pop();
}
}
class Conveyor {
constructor(x, y, direction, width, length, speed, color) {
this.x = x;
this.y = y;
this.direction = direction; // Use degrees directly
this.width = width;
this.length = length;
this.speed = speed;
this.color = color;
this.segments = [];
this.numSegments = int(random(10, 17)); // Random number of segments
this.segmentLength = this.length / this.numSegments;
for (let i = 0; i < this.numSegments; i++) {
let segLength = i / this.numSegments > 0.9 ?
this.segmentLength * (1 - (i - this.numSegments * 0.9) / (this.numSegments * 0.1)) :
this.segmentLength;
this.segments.push({x: i * this.segmentLength, length: segLength});
}
}
update() {
// Move the belt
for (let segment of this.segments) {
segment.x -= this.speed;
// Wrap around
if (segment.x < -this.segmentLength) {
segment.x = this.length - this.segmentLength;
}
}
}
display() {
push();
translate(this.x, this.y);
rotate(this.direction);
fill(this.color);
// Draw the conveyor belt segments
for (let segment of this.segments) {
rect(segment.x - this.length / 2, -this.width / 2, segment.length, this.width);
}
pop();
}
}
class Machine {
constructor(numCogs, width, height, x, y) {
this.pos = createVector(x, y);
this.width = width;
this.height = height;
this.cogs = [];
this.maxRotationSpeed = 4;
this.createCogs(numCogs);
this.boxes = [];
this.setupBoxes(width, height, x, y);
this.setupConveyors();
this.forceGraph = d3.forceSimulation(this.cogs)
.force("center", d3.forceCenter(x + width / 2, y + height / 2))
.force("collide", d3.forceCollide().radius(d => d.radius + 5))
.force("box", this.forceBox(x, y, width, height, this.cogs)) // Pass cogs to the force function
.on("tick", () => this.updateCogsPositions());
this.previousPositions = new Map(this.cogs.map(cog => [cog, {x: cog.x, y: cog.y}]));
this.maxIterations = 1000; // Maximum number of iterations for stabilization
this.iterations = 0;
this.stabilizationThreshold = 1; // Threshold for movement
this.forceGraph.on("tick", () => this.checkStabilization());
this.commands= [];
this.getCommand()
}
getCommand(){
// pop the first command from the global command array. then put it in an object that we can show as a circle, coming from one of the conveyor belts.
// grab the mostleft command (command[0])
let command = commands.shift()
// put the command in an object on a random conveyor belt, a bit outside the canvas.
let conveyor = random(this.conveyors)
let x = conveyor.x;
let y = conveyor.y;
let pos = createVector(x, y)
var v;
if (x < 1) {v = createVector(1, 0)}
else if (x > (width-1)) {v = createVector(-1, 0)}
else if (y < 1) {v = createVector(0, 1)}
else if (y > (height-1)) {v = createVector(0, -1)}
// assert v is nonzero
if (v.x == 0 && v.y == 0) {console.log('v is zero')}
let commandObject = {command: command, pos:pos,
v: v, goal: p5.Vector.add(pos, p5.Vector.mult(v, 30))
,moving:true};
this.commands.push(commandObject)
console.log(commandObject)
}
setupConveyors() {
this.conveyors = [];
let sides = ['top', 'bottom', 'left', 'right'];
let counts = { top: 0, bottom: 0, left: 0, right: 0 };
for (let i = 0; i < 5; i++) {
// Ensure no more than 3 conveyors on one side
let side;
do {
side = random(sides);
} while (counts[side] >= 3);
counts[side]++;
this.conveyors.push(this.createConveyor(side));
}
}
createConveyor(side) {
let conveyorWidth = random(60,120); // Width of the conveyor
let conveyorLength = random(40,80); // Length of the conveyor
let conveyorSpeed = random(); // Speed of the conveyor belt
let conveyorColor = color(random(360), 80, 80); // Main color with random hue
let x, y, direction;
switch (side) {
case 'top':
x = random(0, width - conveyorLength);
y = 0;
direction = -90;
break;
case 'bottom':
x = random(0, width - conveyorLength);
y = height;
direction = 90;
break;
case 'left':
x = 0;
y = random(0, height - conveyorLength);
direction = 180
break;
case 'right':
x = width
y = random(0, height - conveyorLength);
direction = 0;
break;
}
return new Conveyor(x, y, direction, conveyorWidth, conveyorLength, conveyorSpeed, conveyorColor);
}
setupBoxes(machineWidth, machineHeight, machineX, machineY) {
let totalBoxes = 256;
let boxArea = (width * height) - (machineWidth * machineHeight); // Total area for boxes
let boxSize = sqrt(boxArea / totalBoxes); // Uniform size for all boxes
// Define areas around the machine
let topArea = { x: 0, y: 0, w: width, h: machineY };
let bottomArea = { x: 0, y: machineY + machineHeight, w: width, h: height - (machineY + machineHeight) };
let leftArea = { x: 0, y: machineY, w: machineX, h: machineHeight };
let rightArea = { x: machineX + machineWidth, y: machineY, w: width - (machineX + machineWidth), h: machineHeight };
// for debug
this.topArea = topArea;
this.bottomArea = bottomArea;
this.leftArea = leftArea;
this.rightArea = rightArea;
// Function to fill an area with boxes, adjusting for margins
let fillAreaWithBoxes = (area, totalAreaBoxes) => {
let numCols = Math.floor(area.w / boxSize);
let numRows = Math.floor(area.h / boxSize);
let totalBoxesArea = numCols * numRows;
let excessAreaWidth = area.w - (numCols * boxSize);
let excessAreaHeight = area.h - (numRows * boxSize);
let marginX = excessAreaWidth / (numCols + 1);
let marginY = excessAreaHeight / (numRows + 1);
for (let i = 0; i < numRows; i++) {
for (let j = 0; j < numCols; j++) {
if (this.boxes.length < totalBoxes) {
let x = area.x + marginX + j * (boxSize + marginX);
let y = area.y + marginY + i * (boxSize + marginY);
this.boxes.push({ x, y, size: boxSize, width: boxSize, height: boxSize, number: this.boxes.length ,
color: color(random(360), 80, 80)
});
}
}
}
};
// Distribute the boxes across the areas
fillAreaWithBoxes(topArea);
fillAreaWithBoxes(leftArea);
fillAreaWithBoxes(rightArea);
fillAreaWithBoxes(bottomArea);
}
ensureAllCogsSpinning() {
for (let cog of this.cogs) {
// Check if the cog is not spinning
if (cog.rotationSpeed === 0) {
let closestCog = this.findClosestSpinningCog(cog);
if (closestCog) {
cog.link(closestCog);
this.setCogSpeed(cog, closestCog);
}
}
}
}
findClosestSpinningCog(cog) {
let closestCog = null;
let minDist = Number.MAX_VALUE;
for (let otherCog of this.cogs) {
if (otherCog !== cog && otherCog.rotationSpeed !== 0) {
let distance = dist(cog.x, cog.y, otherCog.x, otherCog.y);
if (distance < minDist) {
minDist = distance;
closestCog = otherCog;
}
}
}
return closestCog;
}
setCogSpeed(cog, linkedCog) {
// Calculate the speed based on the ratio of teeth
let speedRatio = linkedCog.teeth / cog.teeth;
let calculatedSpeed = -linkedCog.rotationSpeed * speedRatio;
// Apply the maximum speed limit
cog.rotationSpeed = this.limitSpeed(calculatedSpeed);
}
limitSpeed(speed) {
return Math.sign(speed) * Math.min(Math.abs(speed), this.maxRotationSpeed);
}
findClosestCog(cog) {
let closestCog = null;
let minDist = Number.MAX_VALUE;
for (let otherCog of this.cogs) {
if (otherCog !== cog) {
let distance = dist(cog.x, cog.y, otherCog.x, otherCog.y);
if (distance < minDist) {
minDist = distance;
closestCog = otherCog;
}
}
}
return closestCog;
}
checkStabilization() {
let totalMovement = 0;
for (let cog of this.cogs) {
let prevPos = this.previousPositions.get(cog);
totalMovement += dist(cog.x, cog.y, prevPos.x, prevPos.y);
prevPos.x = cog.x;
prevPos.y = cog.y;
}
if (totalMovement < this.stabilizationThreshold || this.iterations++ > this.maxIterations) {
this.forceGraph.stop();
print('stabilized, force-graph stopped.')
this.ensureAllCogsSpinning();
}
}
forceBox(x, y, width, height, nodes) {
return function() {
for (let cog of nodes) {
cog.x = Math.max(x + cog.radius, Math.min(x + width - cog.radius, cog.x));
cog.y = Math.max(y + cog.radius, Math.min(y + height - cog.radius, cog.y));
}
};
}
updateCogsPositions() {
for (let cog of this.cogs) {
cog.x = cog.x;
cog.y = cog.y;
}
}
createCogs(numCogs) {
this.cogs = [];
this.bands = [];
for (let i = 0; i < numCogs; i++) {
let radius = random(30, 80);
let x, y;
if (i === 0) {
// Position the first cog randomly but within bounds
x = random(this.pos.x + radius, this.pos.x + this.width - radius);
y = random(this.pos.y + radius, this.pos.y + this.height - radius);
} else {
// Position subsequent cogs next to an existing cog
let existingCog = random(this.cogs);
let angle = random(360); //since we're on degrees we dont use TWO_PI radians
let distance = existingCog.radius + radius + random(-10, 10); // Allow some overlap
x = existingCog.x + cos(angle) * distance;
y = existingCog.y + sin(angle) * distance;
if (x - radius < this.pos.x || x + radius > this.pos.x + this.width ||
y - radius < this.pos.y || y + radius > this.pos.y + this.height) {
x = constrain(x, this.pos.x , this.pos.x + this.width);
y = constrain(y, this.pos.y , this.pos.y + this.height );
}
}
let teeth = floor(random(10, 20));
let toothType = random(['rectangular', 'triangular']);
let rotationSpeed = (i === 0) ? random(1, 3) : 0;
let newCog = new Cog(x, y, radius, teeth, toothType, rotationSpeed);
this.cogs.push(newCog);
for (let otherCog of this.cogs) {
if (otherCog !== newCog && dist(x, y, otherCog.x, otherCog.y) < radius + otherCog.radius) {
newCog.link(otherCog);
}
}
}
// Ensure all cogs are spinning, retry if not
if (!this.allCogsSpinning()) {
this.createCogs(numCogs);
}
}
move(x, y) {
let dx = x - this.pos.x;
let dy = y - this.pos.y;
this.pos.set(x, y);
for (let cog of this.cogs) {
cog.x += dx;
cog.y += dy;
}
}
allCogsSpinning() {
for (let cog of this.cogs) {
if (cog.neighbors.length === 0) {
return false; // Found a cog that is not spinning
}
}
return true; // All cogs are spinning
}
update() {
for (let cog of this.cogs) {
cog.update();
}
for (const command of this.commands){
command.pos.add(command.v)
// stop the command when its inside the screen fully, the command is 55px wide
if (command.pos.dist(command.goal) < 1){
command.v = createVector(0, 0)
command.moving = false;
}
}
}
show() {
push();
translate(this.pos.x, this.pos.y);
fill('pink')
strokeWeight(2);
rect(0, 0, this.width, this.height); // Draw the boundary box
// Draw connections before the cogs
for (let cog of this.cogs) {
for (let neighbor of cog.neighbors) {
if (neighbor.hasBar) { // Assuming hasBar is a boolean indicating a bar connection
cog.drawConnection(neighbor.cog, 'bar');
}
if (neighbor.hasRubberBand) { // Assuming hasRubberBand is a boolean for rubber band connection
cog.drawConnection(neighbor.cog, 'rubberBand');
}
}
}
// Draw cogs
for (let cog of this.cogs) {
cog.show(this.pos.x, this.pos.y);
}
pop();
this.drawBoxes();
this.drawConveyors();
// draw alpha rectangles over the top,bot etc areas
if (DEBUG){
push()
stroke(5)
strokeWeight(5)
colorMode(RGB)
fill(255, 255, 255, 100)
rect(this.topArea.x, this.topArea.y, this.topArea.w, this.topArea.h)
rect(this.bottomArea.x, this.bottomArea.y, this.bottomArea.w, this.bottomArea.h)
rect(this.leftArea.x, this.leftArea.y, this.leftArea.w, this.leftArea.h)
rect(this.rightArea.x, this.rightArea.y, this.rightArea.w, this.rightArea.h)
pop()
}
this.drawCommands()
}
drawBoxes() {
for (let box of this.boxes) {
push();
translate(box.x, box.y);
fill(box.color);
rect(0, 0, box.width, box.height);
fill(0);
textAlign(CENTER, CENTER);
text(box.number, box.width / 2, box.height / 2);
pop();
}
}
drawConveyors() {
for (let conveyor of this.conveyors) {
conveyor.update(); // Update the conveyor for animation
conveyor.display(); // Show the conveyor with its moving parts
}
}
drawCommands(){
for (const command of this.commands){
push();
translate(command.pos.x, command.pos.y);
fill('red')
circle(0,0, 55)
fill('white')
text(command.command, 0, 0);
pop();
}
}
}
function deriveSimilarColor(c) {
let h = hue(c);
let s = saturation(c);
let b = brightness(c);
// Randomly adjust hue, saturation, and brightness within a small range
h += 5
s += 25
b += -20
return color(h, s, b);
}
var commands;
let machine;
function preload(){
commands = loadStrings('in.txt')
}
function setup() {
createCanvas(800, 600);
colorMode(HSB);
angleMode(DEGREES);
console.log(`there are ${commands[0].split(',').length} commands`)
commands = commands[0].split(',')
machine = new Machine(7, 250, 250, width/2 - 125 , height/2 - 125);
}
function draw() {
background(220);
machine.update();
machine.show();
}