studio/src/admin-preview/gdArches/archFactories.js
МИН 31adbf151b Initial public release: Студия Рублокса v1.0
Open-source веб-студия для создания игр Рублокса, двойная лицензия
AGPL-3.0 + Коммерческая.

Главное:
- Vite 5 + React 18 + Babylon 7.54.3 + Monaco Editor + Colyseus 0.16
- Самодостаточный движок ~28к строк (66 файлов): BlockManager,
  TerrainVoxelBuilder, ModelManager, DecoManager, PlayerController,
  ScriptSandboxWorker, MultiplayerSync, 30+ GD-гейммодов
- Главный редактор KubikonEditor (~37к строк) + панели, ScriptEditor (Monaco)
- Витрина игр (KubikonFeed, KubikonStudio, KubikonDocs, KubikonLearn)
- Geometry Dash sub-app (GdMenu, GdShop, GdRules, GdCoverArt)
- 10 admin-preview каталогов для дизайнеров (скины, музыка, порталы и т.д.)
- Конфигурируемый бэкенд через VITE_API_BASE — работает со staging
  (dev-api.rublox.pro) без настройки
- Standalone-режим (VITE_STANDALONE=true) — открыть пустой редактор без бэка
- Полная документация (на русском): README, ARCHITECTURE, CONTRIBUTING,
  SECURITY, CHANGELOG
- ESLint + Prettier + EditorConfig
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Перед публикацией:
- Все импорты из minecraftia заменены на локальные
- Все хардкоды URL (minecraftia-school.ru) и внутренних IP убраны → env
- Admin-эндпоинты Kubikon3DService вырезаны (остаются в приватном репо)
- AdminKubikonModeration не публикуется (модерация — в team.rublox.pro)
- 93 МБ ассетов public/kubikon-assets вынесены в .gitignore
  (раздаются через release artifact)
2026-05-27 23:41:10 +03:00

536 lines
27 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.

/**
* archFactories — реестр стартовых арок GD по эпохам.
*
* 10 эпох × 5 вариантов = 50 арок. Каждая фабрика строит арку:
* - две вертикальные «колонны» (cylinder/box) высотой ~4м.
* - горизонтальная «балка» сверху.
* - надпись «СТАРТ» (DynamicTexture на plane) посередине балки.
* - подсветка/glow в стиле эпохи.
*
* Возвращает (scene, id) => { root: TransformNode, dispose() }.
* Габариты: ширина ~3.5м, высота ~4м, центр в (0, 0, 0).
*/
import {
MeshBuilder, StandardMaterial, Color3, Vector3, TransformNode,
DynamicTexture,
} from '@babylonjs/core';
// =========================================================================
// УТИЛИТЫ
// =========================================================================
function makeMat(scene, name, opts = {}) {
const m = new StandardMaterial(name, scene);
m.diffuseColor = new Color3(...(opts.diffuse || [1, 1, 1]));
m.emissiveColor = new Color3(...(opts.emissive || [0, 0, 0]));
m.specularColor = new Color3(...(opts.specular || [0.2, 0.2, 0.2]));
if (opts.specPower != null) m.specularPower = opts.specPower;
if (opts.alpha != null) m.alpha = opts.alpha;
if (opts.disableLighting) m.disableLighting = true;
return m;
}
function makeColumn(scene, name, opts = {}) {
const {
height = 3.5, diameter = 0.5,
shape = 'cylinder', // 'cylinder' | 'box'
diffuse = [0.4, 0.3, 0.2], emissive = [0.05, 0.04, 0.03],
specular, specPower,
} = opts;
let mesh;
if (shape === 'box') {
mesh = MeshBuilder.CreateBox(name, { width: diameter, height, depth: diameter }, scene);
} else {
mesh = MeshBuilder.CreateCylinder(name, {
diameter, height, tessellation: 12,
}, scene);
}
mesh.position.y = height / 2;
mesh.material = makeMat(scene, `${name}_mat`, { diffuse, emissive, specular, specPower });
return mesh;
}
function makeBeam(scene, name, opts = {}) {
const {
width = 3.5, height = 0.5, depth = 0.5,
diffuse = [0.4, 0.3, 0.2], emissive = [0.05, 0.04, 0.03],
specular, specPower,
} = opts;
const beam = MeshBuilder.CreateBox(name, { width, height, depth }, scene);
beam.material = makeMat(scene, `${name}_mat`, { diffuse, emissive, specular, specPower });
return beam;
}
/** Табличка с надписью «СТАРТ» (DynamicTexture на plane). */
function makeSignText(scene, name, text, opts = {}) {
const {
width = 2.4, height = 0.7,
bgColor = '#0a1428', textColor = '#22ff66',
fontSize = 96, fontFamily = 'sans-serif',
emissive = [0.2, 1.0, 0.4],
} = opts;
const TW = 512, TH = 128;
const dt = new DynamicTexture(`${name}_tex`, { width: TW, height: TH }, scene, true);
const ctx = dt.getContext();
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, TW, TH);
ctx.fillStyle = textColor;
ctx.font = `bold ${fontSize}px ${fontFamily}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, TW / 2, TH / 2);
dt.hasAlpha = false;
dt.update();
const plane = MeshBuilder.CreatePlane(name, { width, height }, scene);
const mat = new StandardMaterial(`${name}_mat`, scene);
mat.diffuseTexture = dt;
mat.emissiveTexture = dt;
mat.emissiveColor = new Color3(...emissive);
mat.disableLighting = true;
mat.backFaceCulling = false;
plane.material = mat;
return plane;
}
/** Сферическая «лампочка»-glow (для гирлянд/неона). */
function makeBulb(scene, name, x, y, z, color, scale = 1) {
const bulb = MeshBuilder.CreateSphere(name, { diameter: 0.18, segments: 8 }, scene);
bulb.position.set(x, y, z);
bulb.scaling.set(scale, scale, scale);
const mat = makeMat(scene, `${name}_mat`, { diffuse: color, emissive: color, disableLighting: true });
bulb.material = mat;
return bulb;
}
// =========================================================================
// ФАБРИКИ АРОК ПО ЭПОХАМ (по 5 на эпоху)
// =========================================================================
// ----------------------------------------- Эпоха I — Лес -----
function archForestWood(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.6, GAP = 3.0;
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.55,
diffuse: [0.40, 0.25, 0.13], emissive: [0.10, 0.07, 0.04] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.55,
diffuse: [0.40, 0.25, 0.13], emissive: [0.10, 0.07, 0.04] });
colL.position.x = -GAP / 2;
colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, {
width: GAP + 0.7, height: 0.45, depth: 0.45,
diffuse: [0.45, 0.28, 0.15], emissive: [0.10, 0.07, 0.04],
});
beam.position.y = COL_H + 0.225;
beam.parent = root;
// листва — зелёная сферка над балкой
for (let i = 0; i < 5; i++) {
const leaf = MeshBuilder.CreateSphere(`${id}_leaf_${i}`, { diameter: 0.7 }, scene);
leaf.position.set(-1.4 + i * 0.7, COL_H + 0.7, 0);
const mat = makeMat(scene, `${id}_leaf_mat_${i}`, {
diffuse: [0.2, 0.55, 0.25], emissive: [0.06, 0.18, 0.08],
});
leaf.material = mat;
leaf.parent = root;
}
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', {
width: 2.4, height: 0.7, textColor: '#22ff66',
});
sign.position.set(0, COL_H - 0.3, 0.26);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archForestStone(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.4, GAP = 3.0;
const stoneDif = [0.55, 0.55, 0.50];
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.7, shape: 'box',
diffuse: stoneDif, emissive: [0.10, 0.10, 0.09] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.7, shape: 'box',
diffuse: stoneDif, emissive: [0.10, 0.10, 0.09] });
colL.position.x = -GAP / 2;
colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, {
width: GAP + 0.9, height: 0.55, depth: 0.55,
diffuse: stoneDif, emissive: [0.10, 0.10, 0.09],
});
beam.position.y = COL_H + 0.275;
beam.parent = root;
// мох сверху
for (let i = 0; i < 7; i++) {
const moss = MeshBuilder.CreateSphere(`${id}_moss_${i}`, { diameter: 0.35 }, scene);
moss.position.set(-1.7 + i * 0.55, COL_H + 0.55, 0);
moss.material = makeMat(scene, `${id}_moss_mat_${i}`,
{ diffuse: [0.25, 0.5, 0.20], emissive: [0.07, 0.15, 0.05] });
moss.parent = root;
}
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#88dd55' });
sign.position.set(0, COL_H - 0.3, 0.31);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archForestVine(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.6, GAP = 3.0;
// тонкие колонны-ветки
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.35,
diffuse: [0.32, 0.22, 0.12], emissive: [0.08, 0.05, 0.03] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.35,
diffuse: [0.32, 0.22, 0.12], emissive: [0.08, 0.05, 0.03] });
colL.position.x = -GAP / 2;
colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
// тонкая балка
const beam = makeBeam(scene, `${id}_beam`, {
width: GAP + 0.4, height: 0.25, depth: 0.25,
diffuse: [0.32, 0.22, 0.12], emissive: [0.08, 0.05, 0.03],
});
beam.position.y = COL_H + 0.125;
beam.parent = root;
// лианы — много зелёных мелких сфер
for (let i = 0; i < 12; i++) {
const x = -1.8 + i * 0.32;
const dropY = 0.5 + Math.abs(Math.sin(i * 1.3)) * 0.8;
for (let j = 0; j < 4; j++) {
const leaf = MeshBuilder.CreateSphere(`${id}_v_${i}_${j}`, { diameter: 0.25 }, scene);
leaf.position.set(x, COL_H + 0.2 - j * 0.25, 0);
leaf.material = makeMat(scene, `${id}_vm_${i}_${j}`,
{ diffuse: [0.15 + (j % 2) * 0.1, 0.55 - j * 0.05, 0.15], emissive: [0.05, 0.18, 0.05] });
leaf.parent = root;
}
}
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#aaff66' });
sign.position.set(0, COL_H - 0.5, 0.16);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archForestLanterns(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.6, GAP = 3.0;
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.50,
diffuse: [0.30, 0.18, 0.08], emissive: [0.06, 0.04, 0.02] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.50,
diffuse: [0.30, 0.18, 0.08], emissive: [0.06, 0.04, 0.02] });
colL.position.x = -GAP / 2;
colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, {
width: GAP + 0.6, height: 0.35, depth: 0.35,
diffuse: [0.30, 0.18, 0.08], emissive: [0.06, 0.04, 0.02],
});
beam.position.y = COL_H + 0.175;
beam.parent = root;
// тёплые лампочки-гирлянда
const colors = [[1.0, 0.85, 0.30], [1.0, 0.55, 0.20], [1.0, 0.95, 0.65]];
for (let i = 0; i < 9; i++) {
const bulb = makeBulb(scene, `${id}_b_${i}`,
-1.6 + i * 0.4,
COL_H + 0.18 - Math.abs(Math.sin(i * 1.7)) * 0.3,
0,
colors[i % colors.length],
0.9
);
bulb.parent = root;
}
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', {
textColor: '#ffd76b', bgColor: '#1f1408', emissive: [0.7, 0.55, 0.15],
});
sign.position.set(0, COL_H - 0.4, 0.21);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archForestRustic(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.5, GAP = 2.8;
// кривые колонны (две box со слегка разным масштабом)
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.6, shape: 'box',
diffuse: [0.42, 0.28, 0.16], emissive: [0.10, 0.07, 0.04] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.6, shape: 'box',
diffuse: [0.42, 0.28, 0.16], emissive: [0.10, 0.07, 0.04] });
colL.position.x = -GAP / 2;
colL.rotation.z = -0.05;
colR.position.x = GAP / 2;
colR.rotation.z = 0.05;
colL.parent = root; colR.parent = root;
// балка — две, на разной высоте (как ферма)
const beam1 = makeBeam(scene, `${id}_beam1`, {
width: GAP + 0.5, height: 0.35, depth: 0.35,
diffuse: [0.45, 0.30, 0.18], emissive: [0.10, 0.07, 0.04],
});
beam1.position.y = COL_H + 0.175;
beam1.parent = root;
const beam2 = makeBeam(scene, `${id}_beam2`, {
width: GAP, height: 0.25, depth: 0.25,
diffuse: [0.45, 0.30, 0.18], emissive: [0.10, 0.07, 0.04],
});
beam2.position.y = COL_H - 0.5;
beam2.parent = root;
// X-перекрестье из досок
const dr1 = makeBeam(scene, `${id}_dr1`, {
width: 2.6, height: 0.18, depth: 0.18,
diffuse: [0.40, 0.25, 0.13], emissive: [0.08, 0.05, 0.03],
});
dr1.position.y = COL_H * 0.5;
dr1.rotation.z = Math.PI / 5;
dr1.parent = root;
const dr2 = dr1.clone(`${id}_dr2`);
dr2.rotation.z = -Math.PI / 5;
dr2.parent = root;
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#ffe44a' });
sign.position.set(0, COL_H + 0.5, 0.26);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
// ----------------------------------------- Эпоха II — Горы -----
function archMountainStone(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.4, GAP = 3.0;
const colDif = [0.50, 0.50, 0.55];
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.75, shape: 'box', diffuse: colDif, emissive: [0.10, 0.10, 0.11] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.75, shape: 'box', diffuse: colDif, emissive: [0.10, 0.10, 0.11] });
colL.position.x = -GAP / 2; colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, { width: GAP + 1.0, height: 0.55, depth: 0.55, diffuse: colDif, emissive: [0.10, 0.10, 0.11] });
beam.position.y = COL_H + 0.275; beam.parent = root;
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#aaccff' });
sign.position.set(0, COL_H - 0.3, 0.31);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archMountainCrystal(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.6, GAP = 3.0;
const xtaldif = [0.5, 0.7, 0.95], xtalem = [0.20, 0.35, 0.55];
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.55, diffuse: xtaldif, emissive: xtalem, specPower: 96 });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.55, diffuse: xtaldif, emissive: xtalem, specPower: 96 });
colL.position.x = -GAP / 2; colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, { width: GAP + 0.6, height: 0.35, depth: 0.35, diffuse: xtaldif, emissive: xtalem });
beam.position.y = COL_H + 0.175; beam.parent = root;
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#aaeeff' });
sign.position.set(0, COL_H - 0.4, 0.21);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archMountainIce(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.5, GAP = 3.0;
const icedif = [0.80, 0.92, 1.0], iceem = [0.25, 0.35, 0.50];
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.55, diffuse: icedif, emissive: iceem, specPower: 128 });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.55, diffuse: icedif, emissive: iceem, specPower: 128 });
colL.position.x = -GAP / 2; colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, { width: GAP + 0.6, height: 0.35, depth: 0.35, diffuse: icedif, emissive: iceem });
beam.position.y = COL_H + 0.175; beam.parent = root;
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#ddffff' });
sign.position.set(0, COL_H - 0.4, 0.21);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archMountainPeak(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.8, GAP = 3.0;
const colDif = [0.40, 0.45, 0.50];
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.6, shape: 'box', diffuse: colDif, emissive: [0.05, 0.06, 0.07] });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.6, shape: 'box', diffuse: colDif, emissive: [0.05, 0.06, 0.07] });
colL.position.x = -GAP / 2; colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
// вместо балки — пирамидальная крыша из 2-х наклонённых box
const sideA = makeBeam(scene, `${id}_a`, { width: GAP * 0.7, height: 0.4, depth: 0.4, diffuse: colDif, emissive: [0.05, 0.06, 0.07] });
sideA.position.set(-GAP * 0.27, COL_H + GAP * 0.27, 0);
sideA.rotation.z = -Math.PI / 4;
sideA.parent = root;
const sideB = sideA.clone(`${id}_b`);
sideB.position.set(GAP * 0.27, COL_H + GAP * 0.27, 0);
sideB.rotation.z = Math.PI / 4;
sideB.parent = root;
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#88aacc' });
sign.position.set(0, COL_H - 0.6, 0.31);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
function archMountainBronze(scene, id) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = 3.6, GAP = 3.0;
const brz = [0.55, 0.38, 0.18];
const colL = makeColumn(scene, `${id}_colL`, { height: COL_H, diameter: 0.5, diffuse: brz, emissive: [0.15, 0.08, 0.03], specPower: 96 });
const colR = makeColumn(scene, `${id}_colR`, { height: COL_H, diameter: 0.5, diffuse: brz, emissive: [0.15, 0.08, 0.03], specPower: 96 });
colL.position.x = -GAP / 2; colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, { width: GAP + 0.8, height: 0.45, depth: 0.45, diffuse: brz, emissive: [0.15, 0.08, 0.03] });
beam.position.y = COL_H + 0.225; beam.parent = root;
const sign = makeSignText(scene, `${id}_sign`, 'СТАРТ', { textColor: '#ffd76b' });
sign.position.set(0, COL_H - 0.3, 0.26);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
// ----------------------------------------- Эпохи III-X — упрощённые шаблоны -----
function archGeneric(scene, id, palette) {
const root = new TransformNode(`arch_${id}`, scene);
const COL_H = palette.colH || 3.5, GAP = palette.gap || 3.0;
const colL = makeColumn(scene, `${id}_colL`, {
height: COL_H, diameter: palette.colDia || 0.55,
shape: palette.shape || 'cylinder',
diffuse: palette.colDif, emissive: palette.colEm,
specPower: palette.specPower,
});
const colR = makeColumn(scene, `${id}_colR`, {
height: COL_H, diameter: palette.colDia || 0.55,
shape: palette.shape || 'cylinder',
diffuse: palette.colDif, emissive: palette.colEm,
specPower: palette.specPower,
});
colL.position.x = -GAP / 2; colR.position.x = GAP / 2;
colL.parent = root; colR.parent = root;
const beam = makeBeam(scene, `${id}_beam`, {
width: GAP + 0.6, height: palette.beamH || 0.35, depth: 0.35,
diffuse: palette.beamDif || palette.colDif, emissive: palette.beamEm || palette.colEm,
});
beam.position.y = COL_H + (palette.beamH || 0.35) / 2;
beam.parent = root;
// бонус — например лампочки или сферки
if (palette.bulbs) {
for (let i = 0; i < palette.bulbs.count; i++) {
const b = makeBulb(scene, `${id}_bulb_${i}`,
-GAP / 2 + (i + 0.5) * (GAP / palette.bulbs.count),
COL_H + 0.5,
0,
palette.bulbs.color
);
b.parent = root;
}
}
const sign = makeSignText(scene, `${id}_sign`, palette.signText || 'СТАРТ', {
textColor: palette.signColor || '#ffffff',
bgColor: palette.signBg || '#0a1428',
emissive: palette.signEm || [0.5, 0.5, 0.5],
});
sign.position.set(0, COL_H - 0.4, 0.21);
sign.parent = root;
return { root, dispose: () => { for (const ch of root.getChildMeshes()) ch.dispose(); root.dispose(); } };
}
// =========================================================================
// КАТАЛОГ
// =========================================================================
const GENERIC_EPOCHS = {
3: [
{ name: 'Город хром', colDif:[0.65,0.65,0.7], colEm:[0.15,0.15,0.18], signColor:'#22ccff', specPower:128 },
{ name: 'Город неон', colDif:[0.10,0.10,0.20], colEm:[0.10,0.30,0.60], signColor:'#22ddff', signEm:[0.1,0.6,1.0] },
{ name: 'Город бетон', colDif:[0.55,0.55,0.55], colEm:[0.10,0.10,0.10], signColor:'#cccccc' },
{ name: 'Граффити', colDif:[0.20,0.20,0.25], colEm:[0.05,0.05,0.07], signColor:'#ff44aa', signBg:'#2a0a1a', signEm:[0.8,0.2,0.5] },
{ name: 'Город жёлтый', colDif:[0.85,0.65,0.10], colEm:[0.30,0.20,0.05], signColor:'#000', signBg:'#ffe44a', signEm:[1,0.85,0.1] },
],
4: [
{ name: 'Ночной фиолет', colDif:[0.35,0.10,0.50], colEm:[0.35,0.10,0.60], signColor:'#ff88ff', signEm:[0.7,0.3,0.9] },
{ name: 'Ночной циан', colDif:[0.10,0.40,0.50], colEm:[0.10,0.50,0.65], signColor:'#88eeff', signEm:[0.3,0.8,1.0] },
{ name: 'Ночной розовый', colDif:[0.80,0.20,0.45], colEm:[0.55,0.15,0.30], signColor:'#ff88aa', signEm:[1.0,0.4,0.7] },
{ name: 'Ночной жёлтый', colDif:[0.85,0.65,0.10], colEm:[0.55,0.40,0.05], signColor:'#ffee88', signEm:[1,0.85,0.2] },
{ name: 'Голограмма', colDif:[0.30,0.50,1.0], colEm:[0.40,0.70,1.0], signColor:'#ffffff', signEm:[0.6,0.8,1.0] },
],
5: [
{ name: 'Пустыня песчаник', colDif:[0.75,0.62,0.38], colEm:[0.18,0.13,0.07], signColor:'#5a3a18' },
{ name: 'Пустыня терракота', colDif:[0.65,0.30,0.15], colEm:[0.20,0.07,0.03], signColor:'#ffd76b' },
{ name: 'Пустыня кость', colDif:[0.85,0.80,0.65], colEm:[0.25,0.20,0.12], signColor:'#3a2a1a' },
{ name: 'Пустыня кактус', colDif:[0.30,0.50,0.25], colEm:[0.07,0.15,0.07], signColor:'#ffffff' },
{ name: 'Пустыня закат', colDif:[0.85,0.40,0.20], colEm:[0.45,0.15,0.05], signColor:'#ffe44a', signEm:[1,0.5,0.1] },
],
6: [
{ name: 'Океан коралл', colDif:[0.90,0.40,0.50], colEm:[0.30,0.10,0.15], signColor:'#ffffff' },
{ name: 'Океан синий', colDif:[0.10,0.40,0.65], colEm:[0.05,0.20,0.35], signColor:'#aaeeff' },
{ name: 'Океан жемчуг', colDif:[0.85,0.85,0.95], colEm:[0.20,0.20,0.30], signColor:'#3a5a7a', specPower:128 },
{ name: 'Океан водоросль', colDif:[0.20,0.50,0.30], colEm:[0.07,0.20,0.10], signColor:'#aaffaa' },
{ name: 'Океан бирюза', colDif:[0.10,0.65,0.65], colEm:[0.10,0.40,0.40], signColor:'#ffffff' },
],
7: [
{ name: 'Пещеры камень', colDif:[0.35,0.30,0.25], colEm:[0.07,0.05,0.04], signColor:'#aa8855' },
{ name: 'Пещеры кварц', colDif:[0.55,0.45,0.75], colEm:[0.20,0.15,0.40], signColor:'#ddccff' },
{ name: 'Пещеры гриб', colDif:[0.55,0.20,0.20], colEm:[0.25,0.08,0.08], signColor:'#ffffff' },
{ name: 'Пещеры мох', colDif:[0.20,0.50,0.30], colEm:[0.20,0.55,0.30], signColor:'#aaff88', signEm:[0.3,0.8,0.4] },
{ name: 'Пещеры алмаз', colDif:[0.75,0.85,0.95], colEm:[0.35,0.40,0.45], signColor:'#ffffff', specPower:128 },
],
8: [
{ name: 'Вулкан лава', colDif:[0.30,0.10,0.05], colEm:[0.50,0.15,0.02], signColor:'#ffaa00', signEm:[1,0.5,0.05] },
{ name: 'Вулкан обсидиан', colDif:[0.10,0.05,0.10], colEm:[0.05,0.02,0.05], signColor:'#aa44aa', specPower:128 },
{ name: 'Вулкан магма', colDif:[0.55,0.25,0.05], colEm:[0.85,0.40,0.08], signColor:'#ffff55', signEm:[1,1,0.3] },
{ name: 'Вулкан пепел', colDif:[0.25,0.22,0.22], colEm:[0.05,0.04,0.04], signColor:'#ff5555' },
{ name: 'Вулкан огонь', colDif:[0.45,0.15,0.05], colEm:[0.85,0.30,0.05], signColor:'#ffee00', signEm:[1,0.9,0.1] },
],
9: [
{ name: 'Космос звезда', colDif:[0.45,0.40,0.10], colEm:[0.65,0.55,0.15], signColor:'#ffffaa' },
{ name: 'Космос плазма', colDif:[0.30,0.55,0.95], colEm:[0.40,0.80,1.00], signColor:'#aaffff' },
{ name: 'Космос лазер', colDif:[0.80,0.10,0.50], colEm:[0.95,0.20,0.80], signColor:'#ffaaff', signEm:[1,0.4,1] },
{ name: 'Космос туманность', colDif:[0.55,0.25,0.85], colEm:[0.50,0.20,0.85], signColor:'#ffccff' },
{ name: 'Космос луна', colDif:[0.65,0.65,0.65], colEm:[0.35,0.35,0.35], signColor:'#bbbbbb' },
],
10: [
{ name: 'Кибер зелёный', colDif:[0.10,0.80,0.30], colEm:[0.20,1.00,0.40], signColor:'#aaffaa', signEm:[0.2,1.0,0.4] },
{ name: 'Кибер фиолет', colDif:[0.55,0.15,0.85], colEm:[0.70,0.25,1.00], signColor:'#ffaaff' },
{ name: 'Кибер красный', colDif:[0.85,0.10,0.30], colEm:[0.90,0.20,0.40], signColor:'#ffaaaa' },
{ name: 'Кибер голограмма', colDif:[0.30,0.50,1.0], colEm:[0.50,0.85,1.0], signColor:'#ffffff' },
{ name: 'Кибер жёлтый', colDif:[0.85,0.85,0.10], colEm:[0.85,0.85,0.15], signColor:'#000', signBg:'#ffff44', signEm:[1,1,0.3] },
],
};
export const ARCH_CATALOG = [];
const epoch1 = [
{ id: 'a1_v1', title: 'Деревянная', make: archForestWood },
{ id: 'a1_v2', title: 'Каменная', make: archForestStone },
{ id: 'a1_v3', title: 'С лианами', make: archForestVine },
{ id: 'a1_v4', title: 'С фонариками', make: archForestLanterns },
{ id: 'a1_v5', title: 'Рустик', make: archForestRustic },
];
for (const a of epoch1) ARCH_CATALOG.push({ ...a, epoch: 1 });
const epoch2 = [
{ id: 'a2_v1', title: 'Каменная', make: archMountainStone },
{ id: 'a2_v2', title: 'Кристалл', make: archMountainCrystal },
{ id: 'a2_v3', title: 'Ледяная', make: archMountainIce },
{ id: 'a2_v4', title: 'Пиковая', make: archMountainPeak },
{ id: 'a2_v5', title: 'Бронзовая', make: archMountainBronze },
];
for (const a of epoch2) ARCH_CATALOG.push({ ...a, epoch: 2 });
for (let epoch = 3; epoch <= 10; epoch++) {
const palettes = GENERIC_EPOCHS[epoch] || [];
for (let i = 0; i < palettes.length; i++) {
const palette = palettes[i];
ARCH_CATALOG.push({
id: `a${epoch}_v${i + 1}`,
epoch,
title: palette.name,
make: (scene, id) => archGeneric(scene, id, palette),
});
}
}
export const EPOCH_INFO = [
{ n: 1, name: 'Лес', emoji: '🌲', color: '#3a7a4a' },
{ n: 2, name: 'Горы', emoji: '🏔', color: '#aaccff' },
{ n: 3, name: 'Город днём', emoji: '🏙', color: '#7a8aaa' },
{ n: 4, name: 'Город ночью', emoji: '🌃', color: '#1a1a3a' },
{ n: 5, name: 'Пустыня', emoji: '🏜', color: '#c8a575' },
{ n: 6, name: 'Океан', emoji: '🌊', color: '#3a8aaa' },
{ n: 7, name: 'Пещеры', emoji: '🕳', color: '#3a3a3a' },
{ n: 8, name: 'Вулкан', emoji: '🌋', color: '#8a2a2a' },
{ n: 9, name: 'Космос', emoji: '🚀', color: '#3a1a5a' },
{ n: 10, name: 'Кибер', emoji: '🤖', color: '#ff00ff' },
];
export function getArchesByEpoch(epoch) {
return ARCH_CATALOG.filter(a => a.epoch === epoch);
}