Files
vending-machine-game/src/game/models/factory.js
T

1903 lines
57 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.
import * as THREE from 'three';
import { createVendingMachine } from './vending.js';
import { createCarModel } from '../systems/traffic.js';
import { createPickup, createTruck } from './carModels.js';
import { createPerson, createCrew, nameTag } from './staff.js';
import { FACTORY } from '../../constants/locations.js';
import { textTexture } from './textures.js';
/**
* Runs the stripes operation for this subsystem.
* @param {*} c - c.
* @param {*} w - w.
* @param {*} h - h.
* @param {*} base - base.
* @param {*} line - line.
* @param {*} every - every.
* @returns {*} Operation result, when applicable.
*/
function stripes(c, w, h, base, line, every) {
c.fillStyle = base;
c.fillRect(0, 0, w, h);
c.fillStyle = line;
for (let x = 0; x < w; x += every) c.fillRect(x, 0, every * 0.35, h);
}
// Persian sign: white text on the company's dark teal.
/**
* Runs the sign operation for this subsystem.
* @param {*} text - text.
* @param {*} sub - sub.
* @returns {*} Operation result, when applicable.
*/
const sign = (text, sub = '') =>
textTexture(512, 160, (c, w, h) => {
c.fillStyle = '#0d2f2c';
c.fillRect(0, 0, w, h);
c.fillStyle = '#3ee0c9';
c.fillRect(0, h - 12, w, 12);
c.textAlign = 'center';
c.direction = 'rtl';
c.fillStyle = '#ffffff';
c.font = `bold ${sub ? 58 : 66}px Vazirmatn, Tahoma, sans-serif`;
c.fillText(text, w / 2, sub ? 72 : 100);
if (sub) {
c.fillStyle = '#9fe9da';
c.font = '36px Vazirmatn, Tahoma, sans-serif';
c.fillText(sub, w / 2, 128);
}
});
// Monitor contents: a few lines of code (or rows) on a dark editor background.
/**
* Runs the screen operation for this subsystem.
* @param {*} lines - lines.
* @param {*} background - background.
* @returns {*} Operation result, when applicable.
*/
const screen = (lines, background = '#14181f') =>
textTexture(512, 300, (c, w, h) => {
c.fillStyle = background;
c.fillRect(0, 0, w, h);
c.fillStyle = '#232a35';
c.fillRect(0, 0, w, 26);
c.font = '21px monospace';
lines.forEach(([text, color], i) => {
c.fillStyle = color;
c.fillText(text, 16, 58 + i * 29);
});
});
const CODE = {
react: screen([
['export function Shelf({items}) {', '#c792ea'],
[' const [slot, setSlot] = useState();', '#82aaff'],
[' return (', '#eeffff'],
[' <Grid cols={4}>', '#f07178'],
[' {items.map(i => <Item {...i} />)}', '#c3e88d'],
[' </Grid>', '#f07178'],
[' );', '#eeffff'],
['}', '#c792ea'],
]),
go: screen([
['func (s *Server) Pay(w http.Res', '#82aaff'],
[' tx, err := s.bank.Charge(ctx, req)', '#eeffff'],
[' if err != nil {', '#c792ea'],
[' http.Error(w, err.Error(), 402)', '#f07178'],
[' return', '#c792ea'],
[' }', '#c792ea'],
[' s.events <- Sale{tx.ID, req.Slot}', '#c3e88d'],
['}', '#82aaff'],
]),
cpp: screen([
['void Motor::dispense(int slot) {', '#82aaff'],
[' auto& coil = coils_[slot];', '#eeffff'],
[' coil.rotate(360_deg);', '#c3e88d'],
[' while (!sensor_.dropped())', '#c792ea'],
[' std::this_thread::yield();', '#eeffff'],
[' fridge_.hold(4.0_C);', '#f78c6c'],
[' log("slot", slot, "ok");', '#c3e88d'],
['}', '#82aaff'],
]),
terminal: screen(
[
['$ go test ./...', '#c3e88d'],
['ok payments 0.42s', '#eeffff'],
['$ cmake --build build', '#c3e88d'],
['[100%] Built target firmware', '#eeffff'],
['$ npm run build', '#c3e88d'],
['✓ built in 2.1s', '#eeffff'],
['$ git push origin main', '#c3e88d'],
['', '#fff'],
],
'#0c0f12',
),
sheet: screen(
[
['فاکتور دستگاه مبلغ', '#1d3b2f'],
['۱۰۴۲ بوستان ملت ۴۸۰', '#333'],
['۱۰۴۳ دانشگاه فردوسی ۳۲۰', '#333'],
['۱۰۴۴ مترو آزادی ۲۹۰', '#333'],
['۱۰۴۵ مشهد مال ۶۱۰', '#333'],
['۱۰۴۶ مدرسه راحیل ۱۸۰', '#333'],
['جمع ۱۸۸۰', '#0b8f88'],
],
'#f4f7f2',
),
dashboard: screen(
[
['فروش امروز: ۱۲۸ دستگاه', '#3ee0c9'],
['تولید هفته: ۴۶ دستگاه', '#f2c94c'],
['ارسال شده: ۳۸', '#eeffff'],
['', '#fff'],
['▇▇▇▇▇▇▇▇▇▇▇ خم', '#82aaff'],
['▇▇▇▇▇▇▇▇▇ برش', '#82aaff'],
['▇▇▇▇▇▇▇ رنگ', '#82aaff'],
],
'#101820',
),
};
/**
* The Robot Market factory compound: production hall with five work areas and forty workers, an
* office building (engineering, accounting and the manager's room), a loading truck, the manager's
* arrival in his pickup, fenced yard and gate. Layout uses a local frame: u runs along the driveway
* towards the gate, v across it. Returns world-space collision footprints, key positions and the
* people the family can talk to.
*/
export function createFactory(scene) {
const build = {
scene,
};
for (const [name, method] of Object.entries(FACTORY_METHODS))
build[name] = (...args) => method(build, ...args);
for (const initialize of FACTORY_STEPS) initialize(build);
return build.result;
}
// Desk facing `facing` (the side the person sits on) with one or two monitors.
/**
* Runs the factory desk operation for this subsystem.
* @param {*} build - build.
* @param {*} u - u.
* @param {*} v - v.
* @param {*} sitSide - sit Side.
* @param {*} screens - screens.
* @param {*} width - width.
* @returns {*} Operation result, when applicable.
*/
function factoryDesk(build, u, v, sitSide, screens, width = 1.8) {
build.box(u - width / 2, u + width / 2, v - 0.45, v + 0.45, 0.72, 0.77, build.wood);
for (const du of [-1, 1])
build.box(
u + du * (width / 2 - 0.05) - 0.03,
u + du * (width / 2 - 0.05) + 0.03,
v - 0.4,
v + 0.4,
0,
0.72,
build.black,
false,
);
screens.forEach((texture, i) => {
const du = screens.length > 1 ? (i - 0.5) * 0.78 : 0;
const mats = [build.black, build.black, build.black, build.black, build.black, build.black];
mats[sitSide > 0 ? 4 : 5] = new THREE.MeshBasicMaterial({
map: texture,
});
const monitor = new THREE.Mesh(new THREE.BoxGeometry(0.72, 0.44, 0.04), mats);
monitor.position.set(u + du, 1.12, v - sitSide * 0.2);
monitor.rotation.y = (i - 0.5) * -0.25 * sitSide * (screens.length > 1 ? 1 : 0);
build.group.add(monitor);
build.box(
u + du - 0.03,
u + du + 0.03,
v - sitSide * 0.22 - 0.03,
v - sitSide * 0.22 + 0.03,
0.77,
0.9,
build.black,
false,
);
});
build.box(
u - 0.25,
u + 0.25,
v + sitSide * 0.12,
v + sitSide * 0.28,
0.77,
0.79,
build.mat('#30353b'),
false,
);
}
// Office chair behind a seated person; the back is on the far side from the desk.
/**
* Runs the factory chair operation for this subsystem.
* @param {*} build - build.
* @param {*} u - u.
* @param {*} v - v.
* @param {*} sitSide - sit Side.
* @returns {*} Operation result, when applicable.
*/
function factoryChair(build, u, v, sitSide) {
build.box(u - 0.26, u + 0.26, v - 0.26, v + 0.26, 0.4, 0.47, build.fabric, false);
build.box(
u - 0.25,
u + 0.25,
v + sitSide * 0.26,
v + sitSide * 0.32,
0.47,
1.1,
build.fabric,
false,
);
build.cylinder(0.035, 0.4, build.black, u, 0.2, v);
build.box(u - 0.3, u + 0.3, v - 0.03, v + 0.03, 0.02, 0.06, build.black, false);
build.box(u - 0.03, u + 0.03, v - 0.3, v + 0.3, 0.02, 0.06, build.black, false);
build.solid(u - 0.35, u + 0.35, v - 0.35, v + 0.35);
}
// A seated person at a desk; `sitSide` is +1 when they sit on the +v side facing −v.
/**
* Runs the factory seat operation for this subsystem.
* @param {*} build - build.
* @param {*} person - person.
* @param {*} u - u.
* @param {*} v - v.
* @param {*} sitSide - sit Side.
* @param {*} tag - tag.
* @returns {*} Operation result, when applicable.
*/
function factorySeat(build, person, u, v, sitSide, tag) {
person.root.position.set(u, 0, v);
person.root.rotation.y = sitSide > 0 ? Math.PI : 0;
build.group.add(person.root);
if (tag) {
tag.position.set(0, 1.78, 0);
person.root.add(tag);
}
build.chair(u, v, sitSide);
return person;
}
// Engineering: five developers, a whiteboard, and the office's vending machine by the door.
/**
* Runs the factory update arrival operation for this subsystem.
* @param {*} build - build.
* @param {*} dt - dt.
* @param {*} time - time.
* @returns {*} Operation result, when applicable.
*/
function factoryUpdateArrival(build, dt, time) {
if (build.arrival.stage === 'drive') {
build.arrival.s += dt * 7;
const p = build.along(build.DRIVE_IN, build.arrival.s);
build.placeVehicle(build.pickup, build.arrival, p, dt);
build.pickup.wheels.forEach((w) => (w.rotation.z -= (dt * 7) / 0.4));
build.manager.root.visible = false;
if (p.done)
Object.assign(build.arrival, {
stage: 'walk',
s: 0,
});
} else if (build.arrival.stage === 'walk') {
build.arrival.s += dt * 1.3;
const p = build.along(build.WALK_IN, build.arrival.s);
build.manager.root.visible = true;
build.manager.root.position.set(p.u, 0, p.v);
build.manager.root.rotation.y = Math.atan2(p.du, p.dv);
build.manager.pose('walk', time);
if (p.done) build.arrival.stage = 'seated';
} else if (!build.arrival.sat) {
build.arrival.sat = true;
build.manager.root.position.set(45, 0, -40.7);
build.manager.root.rotation.y = 0;
build.manager.pose('sit');
}
}
/**
* Runs the factory reset arrival operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryResetArrival(build) {
Object.assign(build.arrival, {
stage: 'drive',
s: 0,
sat: false,
});
build.placeVehicle(build.pickup, build.arrival, build.along(build.DRIVE_IN, 0), 0);
build.manager.root.visible = false;
}
/**
* Runs the factory update truck operation for this subsystem.
* @param {*} build - build.
* @param {*} dt - dt.
* @returns {*} Operation result, when applicable.
*/
function factoryUpdateTruck(build, dt) {
const d = build.delivery;
d.t += dt;
build.moving.visible = d.stage === 'load';
if (d.stage === 'load') {
// Each box: two seconds along the conveyor, one second lifted onto its slot.
const [sx, sz] = build.SLOTS[build.loaded.length],
bed = build.truck.bed.position;
const slot = {
u: build.DOCK[0] + bed.x + sx,
y: bed.y + 0.95,
v: build.DOCK[1] + sz,
};
if (d.t < 2) build.moving.position.set(-6 + ((1.6 + 6) * d.t) / 2, 1.05 + 0.95, 31);
else {
const k = Math.min(1, d.t - 2);
build.moving.position.set(
1.6 + (slot.u - 1.6) * k,
2.0 + (slot.y - 2.0) * k + Math.sin(k * Math.PI) * 0.5,
31 + (slot.v - 31) * k,
);
}
if (d.t >= 3) {
const box = build.packaged();
box.position.set(sx, 0.95, sz);
build.truck.bed.add(box);
build.loaded.push(box);
d.t = 0;
if (build.loaded.length === build.SLOTS.length) d.stage = 'full';
}
} else if (d.stage === 'full' && d.t > 2)
Object.assign(d, {
stage: 'depart',
s: 0,
});
else if (d.stage === 'depart' || d.stage === 'return' || d.stage === 'back') {
const speed = d.stage === 'back' ? 2.5 : 6;
d.s += dt * speed;
const path = {
depart: build.DEPART,
return: build.RETURN,
back: build.BACK_IN,
}[d.stage];
const p = build.along(path, d.s);
build.placeVehicle(build.truck, d, p, dt, d.stage === 'back');
build.truck.wheels.forEach(
(w) => (w.rotation.z -= ((dt * speed) / 0.5) * (d.stage === 'back' ? -1 : 1)),
);
if (p.done) {
if (d.stage === 'depart') {
Object.assign(d, {
stage: 'away',
t: 0,
});
build.truck.group.visible = false;
build.loaded.splice(0).forEach((box) => build.truck.bed.remove(box));
} else if (d.stage === 'return')
Object.assign(d, {
stage: 'back',
s: 0,
});
else {
build.truckAtDock();
Object.assign(d, {
stage: 'load',
t: 0,
});
}
}
} else if (d.stage === 'away' && d.t > 18) {
build.truck.group.visible = true;
Object.assign(d, {
stage: 'return',
s: 0,
heading: Math.PI,
});
}
}
/**
* Runs the factory build geometry operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry(build) {
({ center: build.center, axis: build.axis } = FACTORY);
build.normal = {
x: -build.axis.z,
z: build.axis.x,
};
build.rot = Math.atan2(-build.axis.z, build.axis.x);
build.toWorld = (u, v) => ({
x: build.center.x + build.axis.x * u + build.normal.x * v,
z: build.center.z + build.axis.z * u + build.normal.z * v,
});
// A heading in the local frame (u, v direction) as a world-space car heading.
build.worldHeading = (du, dv) => {
const x = build.axis.x * du + build.normal.x * dv,
z = build.axis.z * du + build.normal.z * dv;
return Math.atan2(-z, x);
};
build.group = new THREE.Group();
build.group.position.set(build.center.x, 0, build.center.z);
build.group.rotation.y = build.rot;
build.scene.add(build.group);
build.footprints = [];
}
/**
* Runs the factory build solid operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildSolid(build) {
build.solid = (u0, u1, v0, v1) =>
build.footprints.push(
[
[u0, v0],
[u1, v0],
[u1, v1],
[u0, v1],
].flatMap(([u, v]) => Object.values(build.toWorld(u, v))),
);
// Axis-aligned box in the local frame; solid ones also block movement.
build.box = (a0, a1, b0, b1, y0, y1, material, isSolid = true, parent = build.group) => {
const u0 = Math.min(a0, a1),
u1 = Math.max(a0, a1),
v0 = Math.min(b0, b1),
v1 = Math.max(b0, b1);
const mesh = new THREE.Mesh(new THREE.BoxGeometry(u1 - u0, y1 - y0, v1 - v0), material);
mesh.position.set((u0 + u1) / 2, (y0 + y1) / 2, (v0 + v1) / 2);
mesh.castShadow = mesh.receiveShadow = true;
parent.add(mesh);
if (isSolid) build.solid(u0, u1, v0, v1);
return mesh;
};
build.cylinder = (r, h, material, u, y, v, parent = build.group) => {
const mesh = new THREE.Mesh(new THREE.CylinderGeometry(r, r, h, 16), material);
mesh.position.set(u, y, v);
mesh.castShadow = true;
parent.add(mesh);
return mesh;
};
}
/**
* Runs the factory build panel operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildPanel(build) {
// A sign plane; `facing` is the local direction its front faces: 1 = +v, -1 = −v.
build.panel = (texture, w, h, u, y, v, facing = 1) => {
const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(w, h),
new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
}),
);
mesh.position.set(u, y, v);
if (facing < 0) mesh.rotation.y = Math.PI;
build.group.add(mesh);
return mesh;
};
build.mat = (color, extra = {}) =>
new THREE.MeshStandardMaterial({
color,
roughness: 0.7,
...extra,
});
build.floorMat = (color, extra = {}) =>
build.mat(color, {
polygonOffset: true,
polygonOffsetFactor: -16,
polygonOffsetUnits: -32,
...extra,
});
}
/**
* Runs the factory build flat operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildFlat(build) {
build.flat = (u0, u1, v0, v1, y, material) => {
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(u1 - u0, v1 - v0), material);
mesh.rotation.x = -Math.PI / 2;
mesh.position.set((u0 + u1) / 2, y, (v0 + v1) / 2);
mesh.receiveShadow = true;
build.group.add(mesh);
return mesh;
};
build.steel = build.mat('#59646b', {
metalness: 0.5,
});
build.teal = build.mat('#0b8f88', {
metalness: 0.3,
roughness: 0.4,
});
build.yellow = build.mat('#f2c94c', {
roughness: 0.5,
});
build.darkSteel = build.mat('#3d4549', {
metalness: 0.5,
});
build.lamp = new THREE.MeshBasicMaterial({
color: '#fffbe8',
});
}
/**
* Runs the factory build animated operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildAnimated(build) {
build.animated = [];
// ---------- Yard, fence, gate ----------
build.flat(
-110,
80,
-50,
50,
0.08,
build.floorMat('#8f8c86', {
roughness: 0.95,
polygonOffsetFactor: -14,
polygonOffsetUnits: -28,
}),
);
build.fence = build.mat('#d9d3c4');
build.box(-110, 80, -50.3, -49.7, 0, 2.4, build.fence);
build.box(-110, 80, 49.7, 50.3, 0, 2.4, build.fence);
build.box(-110.3, -109.7, -50, 50, 0, 2.4, build.fence);
build.box(79.7, 80.3, -50, -9, 0, 2.4, build.fence);
build.box(79.7, 80.3, 9, 50, 0, 2.4, build.fence);
for (const v of [-9.5, 9.5]) build.box(79, 81, v - 1, v + 1, 0, 4.2, build.teal);
// Painted parking bays in front of the office.
build.paint = build.floorMat('#f1efe6', {
roughness: 0.9,
polygonOffsetFactor: -18,
polygonOffsetUnits: -36,
});
for (let u = 38; u <= 70; u += 4) build.flat(u - 0.06, u + 0.06, -21.5, -16.5, 0.09, build.paint);
}
/**
* Runs the factory build name sign operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildNameSign(build) {
build.nameSign = textTexture(1024, 320, (c, w, h) => {
c.fillStyle = '#0d2f2c';
c.fillRect(0, 0, w, h);
c.fillStyle = '#3ee0c9';
c.fillRect(0, h - 18, w, 18);
c.textAlign = 'center';
c.direction = 'rtl';
c.fillStyle = '#ffffff';
c.font = 'bold 66px Vazirmatn, Tahoma, sans-serif';
c.fillText(FACTORY.name, w / 2, 120);
c.fillStyle = '#9fe9da';
c.font = '44px Vazirmatn, Tahoma, sans-serif';
c.fillText(FACTORY.tagline, w / 2, 210);
c.font = 'bold 34px sans-serif';
c.fillStyle = '#f2c94c';
c.fillText('ROBOT MARKET · VENDING MACHINES', w / 2, 270);
});
// Two faces, so the name also reads correctly from inside the yard.
build.frontSign = new THREE.MeshBasicMaterial({
map: build.nameSign,
});
}
/**
* Runs the factory build geometry2 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry2(build) {
for (const side of [1, -1]) {
const gateSign = new THREE.Mesh(new THREE.PlaneGeometry(14, 4.4), build.frontSign);
gateSign.position.set(82 + side * 0.02, 6.6, -20);
gateSign.rotation.y = (side * Math.PI) / 2;
build.group.add(gateSign);
}
for (const v of [-25.5, -14.5])
build.box(81.6, 82.4, v - 0.3, v + 0.3, 0, 8.8, build.steel, false);
// ---------- Production hall ----------
// Corrugated walls, a wide door onto the yard (v = V0) and a loading dock door at the east end.
build.corrugated = textTexture(256, 64, (c, w, h) => stripes(c, w, h, '#c9d3d6', '#b2bec2', 16));
build.corrugated.wrapS = build.corrugated.wrapT = THREE.RepeatWrapping;
build.corrugated.repeat.set(18, 2);
build.wall = build.mat('#ffffff', {
map: build.corrugated,
metalness: 0.35,
roughness: 0.55,
});
build.H = 10;
build.U0 = -104;
build.U1 = 0;
build.V0 = 10;
}
/**
* Runs the factory build v1 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildV1(build) {
build.V1 = 42;
build.D0 = -36;
build.D1 = -20;
build.DOCK0 = 27;
build.DOCK1 = 35;
build.box(build.U0, build.U1, build.V1, build.V1 + 0.4, 0, build.H, build.wall);
build.box(build.U0 - 0.4, build.U0, build.V0, build.V1 + 0.4, 0, build.H, build.wall);
build.box(build.U1, build.U1 + 0.4, build.V0 - 0.4, build.DOCK0, 0, build.H, build.wall);
build.box(build.U1, build.U1 + 0.4, build.DOCK1, build.V1 + 0.4, 0, build.H, build.wall);
build.box(build.U1, build.U1 + 0.4, build.DOCK0, build.DOCK1, 5, build.H, build.wall, false);
build.box(build.U0 - 0.4, build.D0, build.V0 - 0.4, build.V0, 0, build.H, build.wall);
build.box(build.D1, build.U1 + 0.4, build.V0 - 0.4, build.V0, 0, build.H, build.wall);
build.box(build.D0, build.D1, build.V0 - 0.4, build.V0, 7, build.H, build.wall, false);
build.box(
build.U0 - 0.4,
build.U1 + 0.4,
build.V0 - 0.5,
build.V0 - 0.3,
build.H - 1.6,
build.H - 0.6,
build.teal,
false,
);
build.roof = build.box(
build.U0 - 1,
build.U1 + 1,
build.V0 - 1,
build.V1 + 1,
build.H,
build.H + 0.4,
build.mat('#9aa6aa', {
metalness: 0.4,
}),
false,
);
// The roof stays out of the shadow map, so daylight still reaches the hall floor.
build.roof.castShadow = false;
build.flat(
build.U0,
build.U1,
build.V0,
build.V1,
0.1,
build.floorMat('#b9c4bd', {
roughness: 0.35,
}),
);
// Safety walkway along the door side.
}
/**
* Runs the factory build geometry3 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry3(build) {
build.flat(
build.U0 + 1,
build.U1 - 1,
18.6,
19,
0.11,
build.floorMat('#f2c94c', {
polygonOffsetFactor: -18,
polygonOffsetUnits: -36,
}),
);
for (let u = build.U0 + 8; u < build.U1; u += 12)
for (const v of [18, 32])
build.box(
u - 3,
u + 3,
v - 1,
v + 1,
build.H - 0.3,
build.H - 0.1,
build.lamp,
false,
).castShadow = false;
build.panel(build.nameSign, 16, 5, (build.D0 + build.D1) / 2, build.H + 3, build.V0 - 0.6, -1);
for (const u of [(build.D0 + build.D1) / 2 - 7.5, (build.D0 + build.D1) / 2 + 7.5])
build.box(
u - 0.2,
u + 0.2,
build.V0 - 0.7,
build.V0 - 0.5,
build.H,
build.H + 5.5,
build.steel,
false,
);
// Work-area signs hang above the walkway, readable from the door.
build.zone = (text, sub, u) => {
build.panel(sign(text, sub), 5.4, 1.7, u, 6.6, 21, -1);
for (const du of [-2.2, 2.2])
build.box(u + du - 0.03, u + du + 0.03, 20.97, 21.03, 7.45, build.H, build.steel, false);
};
build.zone('خم‌کاری', 'ورق‌کاری بدنهٔ دستگاه', -93);
build.zone('برش لیزر', 'برش ورق و قطعات', -71);
build.zone('رنگ‌کاری', 'کابین رنگ پودری', -52);
build.zone('یخچال‌سازی', 'کمپرسور و عایق‌کاری', -31);
build.zone('مونتاژ', 'خط مونتاژ نهایی', -13);
build.zone('بسته‌بندی', 'ارسال به شهر', -3);
// Bending: two press brakes with a moving ram, sheet stacks behind.
}
/**
* Runs the factory build sheet operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildSheet(build) {
build.sheet = build.mat('#d4dadd', {
metalness: 0.85,
roughness: 0.25,
});
for (const u of [-98, -88]) {
build.box(
u - 1.8,
u + 1.8,
29.4,
30.6,
0,
1.0,
build.mat('#4a6b80', {
metalness: 0.4,
}),
);
for (const du of [-1.8, 1.5])
build.box(
u + du,
u + du + 0.3,
29.2,
30.8,
0,
2.9,
build.mat('#4a6b80', {
metalness: 0.4,
}),
false,
);
const ram = build.box(u - 1.5, u + 1.5, 29.6, 30.4, 1.9, 2.5, build.yellow, false);
build.animated.push((t) => (ram.position.y = 2.2 + Math.max(0, Math.sin(t * 1.4 + u)) * -0.6));
build.box(u - 1.4, u + 1.4, 30.6, 31.4, 1.0, 1.03, build.sheet, false);
}
for (const u of [-101, -95, -90, -85]) {
build.box(u - 1, u + 1, 38.4, 39.6, 0, 0.15, build.mat('#8a6a45'));
for (let i = 0; i < 6; i++)
build.box(
u - 0.95,
u + 0.95,
38.45,
39.55,
0.15 + i * 0.05,
0.19 + i * 0.05,
build.sheet,
false,
);
}
// Laser cutting: a bed with a moving gantry and glowing cutting head, plus a shear.
build.box(-78, -66, 26, 32, 0, 0.9, build.darkSteel);
}
/**
* Runs the factory build slats operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildSlats(build) {
build.slats = textTexture(256, 128, (c, w, h) => stripes(c, w, h, '#2b3135', '#6d767c', 12));
build.flat(
-77.8,
-66.2,
26.2,
31.8,
0.91,
new THREE.MeshStandardMaterial({
map: build.slats,
metalness: 0.6,
roughness: 0.4,
}),
);
build.gantry = new THREE.Group();
build.group.add(build.gantry);
build.box(-0.3, 0.3, 25.4, 32.6, 1.6, 1.9, build.yellow, false, build.gantry);
for (const v of [25.5, 32.5])
build.box(-0.25, 0.25, v - 0.2, v + 0.2, 0.9, 1.9, build.yellow, false, build.gantry);
build.head = build.box(-0.2, 0.2, -0.2, 0.2, 1.1, 1.6, build.darkSteel, false, build.gantry);
build.spark = new THREE.Mesh(
new THREE.SphereGeometry(0.09, 8, 6),
new THREE.MeshBasicMaterial({
color: '#ff5a3c',
}),
);
build.gantry.add(build.spark);
build.animated.push((t) => {
build.gantry.position.x = -72 + Math.sin(t * 0.45) * 5;
build.head.position.z = build.spark.position.z = 29 + Math.sin(t * 1.3) * 2.5;
build.spark.position.y = 0.95;
build.spark.scale.setScalar(0.7 + Math.random() * 0.6);
});
build.box(-65, -64, 33.2, 33.8, 0, 1.4, build.mat('#2e3a40'));
}
/**
* Runs the factory build geometry4 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry4(build) {
build.box(
-73.5,
-70.5,
37.9,
39.1,
0,
1.6,
build.mat('#4a6b80', {
metalness: 0.4,
}),
);
// Powder painting booth: see-through panels, an overhead rail carrying panels through it.
build.glass = build.mat('#9fd3e6', {
transparent: true,
opacity: 0.28,
depthWrite: false,
roughness: 0.1,
});
build.box(-58, -46, 36.8, 37, 0, 4.2, build.glass);
build.box(-58, -57.8, 25, 37, 0, 4.2, build.glass);
build.box(-46.2, -46, 25, 37, 0, 4.2, build.glass);
build.box(-58, -46, 25, 37, 4.2, 4.3, build.glass, false);
build.flat(
-57.8,
-46.2,
25.2,
36.8,
0.12,
build.floorMat('#5d7f86', {
polygonOffsetFactor: -18,
polygonOffsetUnits: -36,
}),
);
build.box(-61, -43, 30.9, 31.1, 3.7, 3.8, build.steel, false);
build.hung = [];
}
/**
* Runs the factory build geometry5 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry5(build) {
for (let i = 0; i < 5; i++) {
const piece = build.box(
-0.6,
0.6,
30.97,
31.03,
1.4,
3.2,
i % 2 ? build.teal : build.mat('#eef4f2'),
false,
);
build.hung.push(piece);
}
build.animated.push((t) =>
build.hung.forEach((piece, i) => (piece.position.x = -60 + ((t * 0.5 + i * 3.4) % 17))),
);
// Refrigeration: work tables with insulated cabinets, compressors and copper coils.
build.copper = build.mat('#c27a45', {
metalness: 0.9,
roughness: 0.3,
});
build.insulated = build.mat('#f2f4f3', {
roughness: 0.5,
});
}
/**
* Runs the factory build geometry6 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry6(build) {
for (const u of [-37, -31, -25]) {
build.box(u - 2, u + 2, 29.2, 30.8, 0, 0.9, build.steel);
build.box(u - 1.6, u - 0.2, 29.4, 30.6, 0.9, 2.2, build.insulated, false);
build.cylinder(
0.28,
0.55,
build.mat('#16191c', {
metalness: 0.5,
}),
u + 0.7,
1.18,
30,
);
const coil = new THREE.Mesh(new THREE.TorusGeometry(0.35, 0.04, 6, 20), build.copper);
coil.position.set(u + 1.4, 1.3, 30);
coil.rotation.y = Math.PI / 2;
build.group.add(coil);
}
for (let i = 0; i < 4; i++)
build.box(-35 + i * 2.1, -33.2 + i * 2.1, 38.6, 40.4, 0, 1.9, build.insulated);
// Assembly: conveyor carrying machine frames between robot arms, finished machines at the end.
build.box(-20, -6, 32.4, 34.6, 0, 0.9, build.darkSteel);
build.rollers = build.mat('#9aa4a8', {
metalness: 0.8,
roughness: 0.3,
});
for (let u = -19.4; u < -6; u += 1.2) {
const roller = build.cylinder(0.12, 2.2, build.rollers, u, 0.95, 33.5);
roller.rotation.x = Math.PI / 2;
}
}
/**
* Runs the factory build frames operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildFrames(build) {
build.frames = [];
build.frameMaterial = build.mat('#0b8f88', {
wireframe: true,
});
for (let i = 0; i < 4; i++) {
const frame = new THREE.Mesh(
new THREE.BoxGeometry(1.1, 2.4, 1.8),
i % 2 ? build.frameMaterial : build.teal,
);
frame.position.set(-19 + i * 3.5, 2.2, 33.5);
frame.castShadow = true;
build.group.add(frame);
build.frames.push(frame);
}
build.arms = [];
}
/**
* Runs the factory build geometry7 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry7(build) {
for (const u of [-16, -10]) {
build.box(u - 0.6, u + 0.6, 36, 37.2, 0, 1.2, build.yellow);
const arm = new THREE.Group();
arm.position.set(u, 1.2, 36.6);
build.group.add(arm);
const upper = new THREE.Mesh(new THREE.BoxGeometry(0.35, 2.2, 0.35), build.yellow);
upper.position.y = 1.1;
upper.castShadow = true;
const fore = new THREE.Mesh(new THREE.BoxGeometry(0.28, 0.28, 2.4), build.mat('#e0b23a'));
fore.position.set(0, 2.1, -1.1);
fore.castShadow = true;
arm.add(upper, fore);
build.arms.push(arm);
}
build.animated.push((t) => {
build.arms.forEach((arm, i) => (arm.rotation.y = Math.sin(t * 0.8 + i * 2) * 0.9));
build.frames.forEach((frame, i) => (frame.position.x = -19.5 + ((t * 0.6 + i * 3.5) % 14)));
});
}
/**
* Runs the factory build geometry8 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry8(build) {
for (let i = 0; i < 4; i++) {
const finished = createVendingMachine({
name: 'آمادهٔ نصب',
products: ['water', 'juice', 'biscuit', 'chocolate'],
});
finished.beacon.visible = finished.ring.visible = false;
const u = -19.2 + i * 4.2;
finished.group.position.set(u, 0, 40.6);
finished.group.rotation.y = Math.PI;
build.group.add(finished.group);
build.solid(u - 1, u + 1, 40, 41.2);
}
// Packing: wrapped machines and a roller conveyor out through the dock door to the truck.
}
/**
* Runs the factory build carton operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildCarton(build) {
build.carton = textTexture(256, 512, (c, w, h) => {
c.fillStyle = '#c49a64';
c.fillRect(0, 0, w, h);
c.fillStyle = 'rgba(255,255,255,.18)';
for (let y = 0; y < h; y += 40) c.fillRect(0, y, w, 14);
c.fillStyle = '#0b8f88';
c.fillRect(0, h * 0.42, w, 34);
c.fillStyle = '#1d2a2a';
c.textAlign = 'center';
c.font = 'bold 34px sans-serif';
c.fillText('ROBOT', w / 2, h * 0.3);
c.fillText('MARKET', w / 2, h * 0.3 + 40);
c.font = '60px sans-serif';
c.fillText('⇧', w / 2, h * 0.75);
});
build.cartonMat = build.mat('#ffffff', {
map: build.carton,
roughness: 0.85,
});
build.cartonGeometry = new THREE.BoxGeometry(0.95, 1.9, 1.05);
}
/**
* Runs the factory build packaged operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildPackaged(build) {
build.packaged = () => {
const mesh = new THREE.Mesh(build.cartonGeometry, build.cartonMat);
mesh.castShadow = mesh.receiveShadow = true;
return mesh;
};
for (const v of [37, 38.4, 39.8]) {
const p = build.packaged();
p.position.set(-3.2, 0.95, v);
build.group.add(p);
build.solid(-3.7, -2.7, v - 0.55, v + 0.55);
}
build.box(-6, 1.6, 30.4, 31.6, 0, 1.0, build.darkSteel);
for (let u = -5.6; u < 1.6; u += 0.8)
build.cylinder(0.08, 1.1, build.rollers, u, 1.04, 31).rotation.x = Math.PI / 2;
// ---------- Production crew: 40 workers ----------
build.overall = '#2f4e7a';
build.vest = '#e8742c';
build.paintSuit = '#eef1f2';
}
/**
* Runs the factory build w operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildW(build) {
build.W = (x, z, facing, kind = 'work', extra = {}) => ({
x,
z,
facing,
kind,
suit: build.overall,
...extra,
});
build.walk = (x, z, tx, tz, extra = {}) =>
build.W(x, z, 0, 'walk', {
to: {
x: tx,
z: tz,
},
...extra,
});
build.carry = (x, z, tx, tz, extra = {}) =>
build.W(x, z, 0, 'carry', {
to: {
x: tx,
z: tz,
},
...extra,
});
}
/**
* Runs the factory build crew operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildCrew(build) {
build.crew = [
// Bending: two at each press, four carrying sheets.
build.W(-98.8, 32.3, Math.PI),
build.W(-97.2, 32.3, Math.PI),
build.W(-88.8, 32.3, Math.PI),
build.W(-87.2, 32.3, Math.PI, 'work', {
suit: build.vest,
}),
build.carry(-101, 37.3, -99.5, 33.6),
build.carry(-95, 37.3, -96.2, 33.6, {
suit: build.vest,
}),
build.carry(-90, 37.3, -90.5, 33.6),
build.carry(-85, 37.3, -86.2, 33.6),
// Laser cutting: operators at the bed and control panel, two at the shear, three moving parts.
build.W(-64.5, 34.6, Math.PI),
build.W(-68, 33.4, Math.PI),
build.W(-75, 33.4, Math.PI, 'work', {
suit: build.vest,
}),
build.W(-73, 37.1, 0),
build.W(-71, 37.1, 0),
build.walk(-79, 35.3, -62, 35.3),
build.carry(-68, 40.4, -62, 40.4),
build.walk(-80, 37, -76, 40.4, {
suit: build.vest,
}),
// Painting: four spraying inside the booth in white suits, three outside.
build.W(-55, 29.4, 0, 'work', {
suit: build.paintSuit,
hat: '#ffffff',
}),
build.W(-51.5, 29.4, 0, 'work', {
suit: build.paintSuit,
hat: '#ffffff',
}),
build.W(-53, 32.6, Math.PI, 'work', {
suit: build.paintSuit,
hat: '#ffffff',
}),
build.W(-49, 32.6, Math.PI, 'work', {
suit: build.paintSuit,
hat: '#ffffff',
}),
build.walk(-60, 23, -44, 23),
build.carry(-59, 39.4, -45, 39.4),
build.walk(-44.5, 26, -44.5, 37, {
suit: build.vest,
}),
// Refrigeration: two at each table, three moving cabinets and coils.
build.W(-38, 28.4, 0),
build.W(-36, 28.4, 0),
build.W(-32, 28.4, 0, 'work', {
suit: build.vest,
}),
build.W(-30, 28.4, 0),
build.W(-26, 28.4, 0),
build.W(-24, 28.4, 0),
build.walk(-39, 34.4, -22, 34.4),
build.carry(-24, 37, -38, 37),
build.walk(-31, 32.4, -31, 36),
// Assembly and packing.
build.W(-18, 31, 0),
build.W(-14.4, 31, 0),
build.W(-11, 31, 0, 'work', {
suit: build.vest,
}),
build.W(-7.6, 31, 0),
build.W(-17.1, 38.4, 0),
build.W(-12.9, 38.4, 0),
build.carry(-20, 24, -4, 24),
build.carry(-3, 22.5, -3, 29),
];
}
/**
* Runs the factory build workers operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildWorkers(build) {
build.workers = createCrew(build.group, build.crew);
for (const w of build.crew)
if (w.kind === 'work') build.solid(w.x - 0.3, w.x + 0.3, w.z - 0.3, w.z + 0.3);
// ---------- Office building: engineering, accounting, management ----------
// One storey, rooms side by side with their own doors onto the yard (front at v = -24).
build.windows = textTexture(128, 128, (c, w, h) => {
c.fillStyle = '#e9e4d8';
c.fillRect(0, 0, w, h);
c.fillStyle = '#4c6672';
c.fillRect(14, 26, 100, 64);
c.fillStyle = 'rgba(255,255,255,.25)';
c.fillRect(18, 30, 28, 56);
});
build.windows.wrapS = build.windows.wrapT = THREE.RepeatWrapping;
build.facade = build.mat('#ffffff', {
map: build.windows,
});
build.plaster = build.mat('#ece8df', {
roughness: 0.9,
});
build.OH = 3.8;
build.O0 = 6;
}
/**
* Runs the factory build o1 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildO1(build) {
build.O1 = 50;
build.F = -24;
build.B = -46;
build.doors = [
[14, 17],
[31, 34],
[43.5, 46.5],
];
build.from = build.O0;
for (const [d0, d1] of build.doors) {
build.box(build.from, d0, build.F - 0.3, build.F, 0, build.OH, build.plaster);
build.box(d0, d1, build.F - 0.3, build.F, 2.7, build.OH, build.plaster, false);
build.from = d1;
}
build.box(build.from, build.O1, build.F - 0.3, build.F, 0, build.OH, build.plaster);
build.box(build.O0, build.O1, build.B - 0.3, build.B, 0, build.OH, build.plaster);
build.box(build.O0 - 0.3, build.O0, build.B - 0.3, build.F, 0, build.OH, build.plaster);
build.box(build.O1, build.O1 + 0.3, build.B - 0.3, build.F, 0, build.OH, build.plaster);
build.box(25.85, 26.15, build.B, build.F - 0.3, 0, build.OH, build.plaster);
build.box(39.85, 40.15, build.B, build.F - 0.3, 0, build.OH, build.plaster);
build.officeRoof = build.box(
build.O0 - 0.5,
build.O1 + 0.5,
build.B - 0.8,
build.F + 0.5,
build.OH,
build.OH + 0.25,
build.mat('#bdb5a5'),
false,
);
build.officeRoof.castShadow = false;
build.box(
build.O0 - 0.5,
build.O1 + 0.5,
build.F + 0.4,
build.F + 0.5,
build.OH - 0.5,
build.OH,
build.teal,
false,
);
// Windows on the front wall between the doors, and on the end walls.
}
/**
* Runs the factory build geometry9 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry9(build) {
for (const [u0, u1] of [
[7, 13.2],
[18, 30.2],
[35, 42.6],
[47.4, 49.4],
]) {
const pane = new THREE.Mesh(new THREE.PlaneGeometry(u1 - u0, 1.4), build.facade);
pane.position.set((u0 + u1) / 2, 1.9, build.F + 0.02);
build.facade.map.repeat.set(1, 1);
build.group.add(pane);
}
build.panel(build.nameSign, 22, 3.4, 28, build.OH + 2, build.F + 0.1);
for (const u of [18, 38])
build.box(
u - 0.15,
u + 0.15,
build.F - 0.1,
build.F + 0.05,
build.OH,
build.OH + 3.7,
build.steel,
false,
);
build.flat(build.O0, 26, build.B, build.F, 0.1, build.floorMat('#a9b4bb'));
build.flat(26, 40, build.B, build.F, 0.1, build.floorMat('#c8bba4'));
build.flat(
40,
build.O1,
build.B,
build.F,
0.1,
build.floorMat('#7b5a44', {
roughness: 0.5,
}),
);
for (const [u0, u1] of [
[8, 24],
[28, 38],
[42, 48],
])
build.box(u0, u1, -36, -34, build.OH - 0.08, build.OH - 0.02, build.lamp, false).castShadow =
false;
build.panel(sign('دفتر فنی مهندسی', 'Engineering'), 3.6, 1.1, 15.5, 3.25, build.F + 0.05);
build.panel(sign('واحد حسابداری', 'Accounting'), 3.6, 1.1, 32.5, 3.25, build.F + 0.05);
build.panel(sign('مدیریت', 'آقای محمدی'), 3.6, 1.1, 45, 3.25, build.F + 0.05);
build.wood = build.mat('#c8a477', {
roughness: 0.6,
});
build.black = build.mat('#1b1d20', {
roughness: 0.5,
});
}
/**
* Runs the factory build fabric operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildFabric(build) {
build.fabric = build.mat('#2c3238');
build.people = [];
build.talks = [];
}
/**
* Runs the factory build developers operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildDevelopers(build) {
build.developers = [
{
name: 'حامد',
role: 'React',
u: 11,
v: -31.6,
look: {
outfit: 'hoodie',
top: '#151517',
bottom: '#2d3d57',
},
code: CODE.react,
speaker: 'hamed',
lines: [
'سلام! من حامدم. رابط کاربری صفحهٔ لمسی دستگاه‌ها رو با React می‌سازم.',
'هر چی روی صفحهٔ دستگاه می‌بینین، از همین مانیتور اومده!',
],
},
{
name: 'رایان',
role: 'Go',
u: 16,
v: -31.6,
look: {
outfit: 'hoodie',
top: '#2f6fd6',
bottom: '#33363b',
skin: '#e9bf9c',
},
code: CODE.go,
speaker: 'ryan',
lines: ['سلام! من رایانم. سرور پرداخت و گزارش فروش دستگاه‌ها رو با Go می‌نویسم.'],
},
{
name: 'علیرضا',
role: 'C++',
u: 21,
v: -31.6,
look: {
outfit: 'hoodie',
top: '#1a1a1c',
bottom: '#3b4a5e',
hair: '#1a1410',
},
code: CODE.cpp,
speaker: 'alireza',
lines: [
'سلام! علیرضام. فریمور بُرد کنترل دستگاه با C++ ـه؛ الان دارم موتورهای تحویل کالا رو تنظیم می‌کنم.',
],
},
{
name: 'رضا',
role: 'C++',
u: 13.5,
v: -38.6,
look: {
outfit: 'suit',
top: '#2b2f3a',
bottom: '#2b2f3a',
tie: '#7a1f2b',
shoes: '#111',
},
code: CODE.cpp,
speaker: 'reza',
lines: [
'سلام، خوش اومدین. من رضام؛ کد C++ سنسورها و دمای یخچال دستگاه با منه.',
'دستگاه وندینگ همین دفتر کنار دره؛ از اون خرید کنین!',
],
},
{
name: 'محمد',
role: '',
u: 18.5,
v: -38.6,
look: {
outfit: 'hoodie',
top: '#6f767d',
bottom: '#2a2f36',
skin: '#d9a985',
},
code: CODE.terminal,
speaker: 'mohammad',
lines: ['سلام! من محمدم. به دفتر فنی مهندسی روبات مارکت خوش اومدین!'],
},
];
}
/**
* Runs the factory build geometry10 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry10(build) {
for (const dev of build.developers) {
build.desk(dev.u, dev.v - 1.0, 1, [dev.code, CODE.terminal]);
const person = build.seat(createPerson(dev.look), dev.u, dev.v, 1, nameTag(dev.name, dev.role));
build.people.push({
person,
pose: 'type',
});
build.talks.push({
speaker: dev.speaker,
label: dev.name,
lines: dev.lines,
radius: 2.6,
pos: () => build.toWorld(dev.u, dev.v),
});
}
}
/**
* Runs the factory build board operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildBoard(build) {
build.board = textTexture(512, 256, (c, w, h) => {
c.fillStyle = '#fbfbf8';
c.fillRect(0, 0, w, h);
c.strokeStyle = '#9aa4a8';
c.lineWidth = 8;
c.strokeRect(0, 0, w, h);
c.textAlign = 'center';
c.direction = 'rtl';
c.fillStyle = '#1d4f91';
c.font = 'bold 40px Vazirmatn, Tahoma, sans-serif';
c.fillText('اسپرینت ۱۴: دستگاه نسل جدید', w / 2, 56);
c.font = '28px Vazirmatn, Tahoma, sans-serif';
c.fillStyle = '#333';
['صفحهٔ لمسی جدید ← React', 'سرویس پرداخت ← Go', 'فریمور موتور و سنسور ← C++'].forEach((t, i) =>
c.fillText(t, w / 2, 110 + i * 44),
);
});
build.panel(build.board, 4, 2, 16, 1.9, build.B + 0.05);
build.officeMachine = {
u: 6.8,
v: -28.6,
};
}
/**
* Runs the factory build accountants operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildAccountants(build) {
// Accounting: four colleagues at desks, filing cabinets along the wall.
build.accountants = [
{
u: 29.5,
v: -31.6,
look: {
top: '#2d3550',
scarf: '#c25b7a',
},
},
{
u: 36.5,
v: -31.6,
look: {
top: '#1f1f22',
scarf: '#3e6e8e',
skin: '#edc2a0',
},
},
{
u: 29.5,
v: -38.6,
look: {
top: '#8a6e55',
scarf: '#e3d3b9',
},
},
{
u: 36.5,
v: -38.6,
look: {
top: '#4a5a48',
scarf: '#7a3b52',
skin: '#d9a985',
},
},
];
}
/**
* Runs the factory build geometry11 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry11(build) {
for (const a of build.accountants) {
build.desk(a.u, a.v - 1.0, 1, [CODE.sheet]);
build.people.push({
person: build.seat(
createPerson({
outfit: 'manteau',
bottom: '#24262b',
shoes: '#111',
...a.look,
}),
a.u,
a.v,
1,
),
pose: 'type',
});
}
for (let i = 0; i < 4; i++)
build.box(
26.2,
26.9,
-44.5 + i * 1,
-43.6 + i * 1,
0,
1.4,
build.mat('#9aa3a8', {
metalness: 0.5,
}),
);
build.talks.push({
speaker: 'accountant',
label: 'واحد حسابداری',
radius: 4,
pos: () => build.toWorld(33, -29),
lines: ['سلام! اینجا فروش همهٔ دستگاه‌ها ثبت می‌شه. هر خریدی بکنین، فاکتورش این‌جا میاد!'],
});
// Management: the manager's desk facing the door, guest chairs, bookshelf and flag.
}
/**
* Runs the factory build geometry12 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry12(build) {
build.desk(45, -39.7, -1, [CODE.dashboard], 2.4);
build.chair(45, -40.7, -1);
for (const u of [43.8, 46.2]) {
build.box(u - 0.28, u + 0.28, -37.1, -36.5, 0.42, 0.5, build.mat('#5a3a2a'), false);
build.box(u - 0.28, u + 0.28, -36.5, -36.4, 0.5, 1.0, build.mat('#5a3a2a'), false);
build.solid(u - 0.3, u + 0.3, -37.1, -36.4);
}
build.box(41, 49, -45.9, -45.4, 0, 2.6, build.mat('#6b4a33'));
for (let i = 0; i < 4; i++)
for (let j = 0; j < 9; j++)
build.box(
41.3 + j * 0.85,
41.3 + j * 0.85 + 0.5,
-45.4,
-45.2,
0.3 + i * 0.6,
0.78 + i * 0.6,
build.mat(['#7e1f1d', '#2f4a6b', '#d6c9a5', '#3e6b48'][(i + j) % 4]),
false,
);
build.box(49, 49.5, -27, -26.5, 0, 0.5, build.mat('#7a5a3c'), true);
build.leaves = new THREE.Mesh(new THREE.SphereGeometry(0.45, 10, 8), build.mat('#3e7a3e'));
build.leaves.position.set(49.25, 1.0, -26.75);
build.group.add(build.leaves);
build.nameplate = build.panel(sign('محمدی', 'مدیر شرکت'), 0.6, 0.19, 45, 0.87, -39.2, 1);
build.nameplate.rotation.x = -0.3;
// ---------- Staff car park ----------
}
/**
* Runs the factory build geometry13 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry13(build) {
for (let i = 0; i < 4; i++) {
const parked = createCarModel(['#2f4a6b', '#e9e9e4', '#7e1f1d', '#9aa3a8'][i]);
const u = 56 + i * 4;
parked.group.position.set(u, 0, -19);
parked.group.rotation.y = Math.PI / 2;
build.group.add(parked.group);
build.solid(u - 1.1, u + 1.1, -21.3, -16.7);
}
// ---------- The manager arrives in his pickup ----------
// Mr Mohammadi drives in through the gate when the game starts, parks by the office, walks to
// his room and sits at his desk.
build.pickup = createPickup('#f1f1ee');
build.group.add(build.pickup.group);
}
/**
* Runs the factory build manager operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildManager(build) {
build.manager = createPerson({
outfit: 'suit',
top: '#3a3f47',
bottom: '#3a3f47',
hair: '#8d8a86',
glasses: true,
shoes: '#111',
});
build.managerTag = nameTag('آقای محمدی', 'مدیر شرکت');
build.managerTag.position.y = 2.15;
build.manager.root.add(build.managerTag);
build.group.add(build.manager.root);
build.DRIVE_IN = [
[98, -3],
[80, -3],
[62, -5],
[51, -9],
[46, -16],
];
build.WALK_IN = [
[47.4, -16.9],
[45, -21],
[45, -25.6],
[48.5, -29],
[48.5, -40.7],
[45, -40.7],
];
{
// The parked pickup blocks its bay.
const [ax, az] = build.DRIVE_IN.at(-2),
[bx, bz] = build.DRIVE_IN.at(-1),
len = Math.hypot(bx - ax, bz - az);
const du = (bx - ax) / len,
dv = (bz - az) / len;
build.footprints.push(
[
[2.7, 1],
[2.7, -1],
[-2.7, -1],
[-2.7, 1],
].flatMap(([a, b]) =>
Object.values(build.toWorld(bx + du * a - dv * b, bz + dv * a + du * b)),
),
);
}
}
/**
* Runs the factory build arrival operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildArrival(build) {
build.arrival = {
stage: 'drive',
s: 0,
heading: Math.PI,
sat: false,
};
build.talks.push({
speaker: 'mohammadi',
label: 'آقای محمدی',
radius: 3.4,
pos: () => build.toWorld(build.manager.root.position.x, build.manager.root.position.z),
ready: () => build.arrival.stage === 'seated',
lines: [
'سلام، خوش اومدین! من محمدی‌ام، مدیر روبات مارکت.',
'امروز چهل نفر توی سالن تولید کار می‌کنن: خم‌کاری، برش، رنگ، یخچال‌سازی و مونتاژ.',
'یه سر به دفتر فنی بزنین و از دستگاهش خرید کنین؛ بعد برین سراغ دستگاه‌های شهر!',
],
});
// Position and direction a distance `s` along a polyline of [u, v] points.
}
/**
* Runs the factory build along operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildAlong(build) {
build.along = (path, s) => {
for (let i = 1; i < path.length; i++) {
const [ax, az] = path[i - 1],
[bx, bz] = path[i],
len = Math.hypot(bx - ax, bz - az);
if (s <= len || i === path.length - 1) {
const t = Math.min(1, s / len);
return {
u: ax + (bx - ax) * t,
v: az + (bz - az) * t,
du: (bx - ax) / len,
dv: (bz - az) / len,
done: s >= len && i === path.length - 1,
};
}
s -= len;
}
};
build.turn = (from, to, k) => from + Math.atan2(Math.sin(to - from), Math.cos(to - from)) * k;
}
/**
* Runs the factory build place vehicle operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildPlaceVehicle(build) {
build.placeVehicle = (vehicle, state, p, dt, reverse = false) => {
const want = reverse ? Math.atan2(p.dv, -p.du) : Math.atan2(-p.dv, p.du);
state.heading = dt ? build.turn(state.heading, want, Math.min(1, dt * 3)) : want;
vehicle.group.position.set(p.u, 0, p.v);
vehicle.group.rotation.y = state.heading;
};
build.resetArrival();
// ---------- Delivery truck ----------
// Packed machines ride the conveyor onto the truck at the dock; when it is full it drives out
// of the gate to the city, and comes back empty a while later, reversing up to the dock.
build.truck = createTruck();
build.group.add(build.truck.group);
build.DOCK = [5.5, 31];
build.DEPART = [build.DOCK, [14, 31], [30, 22], [50, 8], [66, 1], [80, 0], [100, 0]];
build.RETURN = [
[100, 0],
[80, 0],
[60, 6],
[40, 14],
[27, 18],
];
build.BACK_IN = [[27, 18], [16, 28.5], build.DOCK];
build.SLOTS = [2.1, 0.95, -0.2, -1.35].flatMap((x) => [
[x, -0.6],
[x, 0.6],
]);
build.loaded = [];
build.moving = build.packaged();
build.group.add(build.moving);
}
/**
* Runs the factory build delivery operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildDelivery(build) {
build.delivery = {
stage: 'load',
t: 0,
s: 0,
heading: 0,
};
build.truckAtDock = () => {
build.truck.group.visible = true;
build.truck.group.position.set(build.DOCK[0], 0, build.DOCK[1]);
build.truck.group.rotation.y = build.delivery.heading = 0;
};
build.truckAtDock();
build.talks.push({
speaker: 'child',
radius: 9,
pos: () => build.toWorld(4, 31),
once: true,
lines: ['بابا ببین! کامیون داره دستگاه‌های بسته‌بندی‌شده رو بار می‌زنه که ببره شهر!'],
});
// The family comments on each work area the first time they walk into it.
}
/**
* Runs the factory build geometry14 operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildGeometry14(build) {
for (const [u, text, speaker] of [
[-93, 'اینجا خم‌کاریه؛ ورق‌های فلزی بدنهٔ دستگاه رو با پرس خم می‌کنن.', 'father'],
[-71, 'دستگاه برش لیزر رو ببین! ورق رو مثل کاغذ می‌بُره.', 'child'],
[-52, 'این کابین رنگه؛ بدنه‌ها اینجا رنگ فیروزه‌ای روبات مارکت می‌گیرن.', 'father'],
[-31, 'یخچال‌سازی! برای همینه که نوشیدنی‌های دستگاه همیشه خنکن.', 'child'],
[
-13,
'خط مونتاژ: روبات‌ها و کارگرها قطعه‌ها رو کنار هم می‌ذارن و دستگاه کامل می‌شه.',
'father',
],
])
build.talks.push({
speaker,
radius: 8,
pos: () => build.toWorld(u, 25),
once: true,
lines: [text],
});
// ---------- Key positions ----------
// The family starts in the yard between the hall and the office, the car parked right beside them.
build.demo = build.toWorld(build.officeMachine.u, build.officeMachine.v);
build.facing = {
x: build.axis.x,
z: build.axis.z,
};
build.car = build.toWorld(1.5, -7);
build.start = build.toWorld(-1, -3.6);
build.door = build.toWorld(15.5, -24);
}
/**
* Runs the factory build result operation for this subsystem.
* @param {*} build - build.
* @returns {*} Operation result, when applicable.
*/
function factoryBuildResult(build) {
build.result = {
group: build.group,
footprints: build.footprints,
demo: {
x: build.demo.x,
z: build.demo.z,
rot: Math.atan2(build.facing.x, build.facing.z),
},
car: {
x: build.car.x,
z: build.car.z,
heading: build.worldHeading(1, 0),
},
start: {
x: build.start.x,
z: build.start.z,
heading: Math.atan2(-(build.door.z - build.start.z), build.door.x - build.start.x),
},
label: {
name: FACTORY.name,
kind: 'factory',
...build.toWorld(-20, 0),
},
talks: build.talks,
reset: build.resetArrival,
// `dt` is 0 while the game is paused; distant viewers skip the people and machines.
update(time, dt, viewer) {
const near =
!viewer || Math.hypot(viewer.x - build.center.x, viewer.z - build.center.z) < 260;
build.updateArrival(dt, time);
build.updateTruck(dt);
build.workers.group.visible = near;
if (!near) return;
for (const step of build.animated) step(time);
build.workers.update(time);
build.people.forEach(({ person, pose }, i) => person.pose(pose, time, i * 1.7));
},
};
}
const FACTORY_METHODS = {
desk: factoryDesk,
chair: factoryChair,
seat: factorySeat,
updateArrival: factoryUpdateArrival,
resetArrival: factoryResetArrival,
updateTruck: factoryUpdateTruck,
};
const FACTORY_STEPS = [
factoryBuildGeometry,
factoryBuildSolid,
factoryBuildPanel,
factoryBuildFlat,
factoryBuildAnimated,
factoryBuildNameSign,
factoryBuildGeometry2,
factoryBuildV1,
factoryBuildGeometry3,
factoryBuildSheet,
factoryBuildSlats,
factoryBuildGeometry4,
factoryBuildGeometry5,
factoryBuildGeometry6,
factoryBuildFrames,
factoryBuildGeometry7,
factoryBuildGeometry8,
factoryBuildCarton,
factoryBuildPackaged,
factoryBuildW,
factoryBuildCrew,
factoryBuildWorkers,
factoryBuildO1,
factoryBuildGeometry9,
factoryBuildFabric,
factoryBuildDevelopers,
factoryBuildGeometry10,
factoryBuildBoard,
factoryBuildAccountants,
factoryBuildGeometry11,
factoryBuildGeometry12,
factoryBuildGeometry13,
factoryBuildManager,
factoryBuildArrival,
factoryBuildAlong,
factoryBuildPlaceVehicle,
factoryBuildDelivery,
factoryBuildGeometry14,
factoryBuildResult,
];