1903 lines
57 KiB
JavaScript
1903 lines
57 KiB
JavaScript
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,
|
||
];
|