# Conflicts: # cmake/module_manifest.cmake # docs/usr/modules/procgen.md # src/engine/CMakeLists.txt # src/engine/cmdline/Run.cpp # src/engine/common/ScriptModule.h # src/modules/animation/Animation.cpp # src/modules/physics/World.cpp # src/modules/physics/World3D.cpp # src/modules/procgen/Procgen.cpp # src/modules/procgen/Procgen.h # src/modules/scene/SceneCapabilities.cpp # src/modules/schema/Schema.cpp # src/modules/schema/Schema.h
201 lines
6.6 KiB
Plaintext
201 lines
6.6 KiB
Plaintext
// Linear, tileable procedural structures demo (Phase C: mesh recipes).
|
|
// Builds six procgen mesh recipes — wooden fence, stone wall, bridge, Great Wall,
|
|
// hedge, cheval de frise — each repeated from a single tileable unit segment.
|
|
// Textures are CC0 images bundled under assets/ (see ATTRIBUTION.md).
|
|
// Run: make run/win32-debug GAME=examples/linear-structures
|
|
|
|
persist structures = []
|
|
persist active = 0
|
|
persist segments = 6
|
|
persist segLength = 2.0
|
|
persist autoSpin = true
|
|
persist yaw = 0.0
|
|
persist camYaw = 0.0
|
|
persist uiReady = false
|
|
persist rWasDown = false
|
|
persist tabWas = false
|
|
persist plusWas = false
|
|
persist minusWas = false
|
|
|
|
local STRUCT_RECIPES = [
|
|
{ id = "mesh.fence", file = "assets/wood.jpg", tex = "tex.soil", tint = { r=1.0, g=0.92, b=0.78 } },
|
|
{ id = "mesh.stonewall", file = "assets/stonewall.jpg", tex = "tex.stone", tint = { r=0.88, g=0.86, b=0.80 } },
|
|
{ id = "mesh.bridge", file = "assets/brick.jpg", tex = "tex.stone", tint = { r=0.95, g=0.88, b=0.74 } },
|
|
{ id = "mesh.greatwall", file = "assets/brick.jpg", tex = "tex.marble", tint = { r=0.92, g=0.85, b=0.70 } },
|
|
{ id = "mesh.hedge", file = "assets/hedge.jpg", tex = "tex.soil", tint = { r=0.62, g=0.86, b=0.45 } },
|
|
{ id = "mesh.chevaldefrise", file = "assets/wood.jpg", tex = "tex.soil", tint = { r=0.94, g=0.78, b=0.55 } },
|
|
];
|
|
|
|
function loadStructureTexture(spec, p) {
|
|
local path = spec.file;
|
|
if (path != null && path.len() > 0) {
|
|
try {
|
|
return gfx.newTextureFromFileRepeated(path, true, true);
|
|
} catch (err) {
|
|
// Fall back to a procedural texture if the CC0 file is missing.
|
|
}
|
|
}
|
|
local paramsResult = procgen.newParams();
|
|
if (!paramsResult.ok) return null;
|
|
local tp = paramsResult.value;
|
|
tp.setSeed(7);
|
|
tp.setSize(128, 128);
|
|
tp.setFloat("scale", 4.0);
|
|
tp.setInt("octaves", 4);
|
|
tp.setInt("colors", 6);
|
|
tp.setInt("pixelSize", 2);
|
|
tp.setInt("seamless", 1);
|
|
local textureResult = procgen.generateTexture(spec.tex, tp, gfx);
|
|
if (!textureResult.ok) return null;
|
|
return textureResult.value;
|
|
}
|
|
|
|
function buildOne(spec, x, y, z) {
|
|
local paramsResult = procgen.newParams();
|
|
if (!paramsResult.ok) {
|
|
ui.select("lab");
|
|
ui.setText("status", "Parameter creation failed: " + paramsResult.status.summary);
|
|
return null;
|
|
}
|
|
local p = paramsResult.value;
|
|
p.setInt("segments", segments);
|
|
p.setFloat("segLength", segLength);
|
|
p.setFloat("uvRepeat", 2.0);
|
|
|
|
local meshResult = procgen.generateMesh(spec.id, p, gfx);
|
|
if (!meshResult.ok) {
|
|
ui.select("lab");
|
|
ui.setText("status", "FAIL " + spec.id + ": " + meshResult.status.summary);
|
|
return null;
|
|
}
|
|
local mesh = meshResult.value;
|
|
local tex = loadStructureTexture(spec, p);
|
|
|
|
local ent = eve.Renderable3D();
|
|
ent.setMesh(mesh);
|
|
ent.setTexture(tex);
|
|
ent.setTint(spec.tint.r, spec.tint.g, spec.tint.b, 1.0);
|
|
ent.setRoughness(0.85);
|
|
ent.setMetallic(0.03);
|
|
ent.setCastShadow(true);
|
|
ent.setReceiveShadow(true);
|
|
ent.setPosition(x, y, z);
|
|
return ent;
|
|
}
|
|
|
|
function rebuild() {
|
|
foreach (s in structures) if (s != null) s.setVisible(false);
|
|
|
|
local total = 0.0;
|
|
local widths = [];
|
|
foreach (spec in STRUCT_RECIPES) {
|
|
// Rough footprint: length = segments * segLength. Depth used for spacing in Z.
|
|
widths.push(segments * segLength);
|
|
total += segments * segLength;
|
|
}
|
|
local gap = 3.0;
|
|
total += gap * (STRUCT_RECIPES.len() - 1);
|
|
local startX = -total * 0.5;
|
|
|
|
structures = [];
|
|
local cx = startX;
|
|
for (local i = 0; i < STRUCT_RECIPES.len(); i++) {
|
|
local spec = STRUCT_RECIPES[i];
|
|
local w = widths[i];
|
|
// Center the structure at the midpoint of its slot.
|
|
local px = cx + w * 0.5;
|
|
structures.push(buildOne(spec, px, 0.0, 0.0));
|
|
cx += w + gap;
|
|
}
|
|
|
|
if (uiReady) {
|
|
ui.select("lab");
|
|
ui.setText("status", STRUCT_RECIPES[active].id + " / segments=" + segments);
|
|
}
|
|
}
|
|
|
|
function setupCamera() {
|
|
camera = eve.Camera3D();
|
|
camera.setEye(0.0, 6.5, 16.0);
|
|
camera.setTarget(0.0, 1.2, 0.0);
|
|
camera.setFov(50.0);
|
|
camera.setAmbient(0.30, 0.30, 0.28);
|
|
gfx.setDirectionalLight(-0.5, 0.9, 0.4, 1.9, 1.6, 1.3);
|
|
}
|
|
|
|
function buildPanel() {
|
|
ui.setTheme("dark");
|
|
ui.setNavKeyboard(true);
|
|
ui.beginBuild();
|
|
ui.beginWindow("LINEAR STRUCTURES", "root");
|
|
ui.text("TILEABLE PROCEDURAL PROPS", "eyebrow");
|
|
ui.text(STRUCT_RECIPES[active].id, "sample");
|
|
ui.slider("Segments", segments.tofloat(), 1.0, 24.0, "segments");
|
|
ui.slider("Unit length", segLength, 0.5, 6.0, "segLength");
|
|
ui.button("Next structure", "next");
|
|
ui.button("Rebuild", "rebuild");
|
|
ui.text("Cycle structures with Tab • +/- adjust segments", "hint");
|
|
ui.text("Ready.", "status");
|
|
ui.end();
|
|
ui.mountBuildAs("lab");
|
|
ui.select("lab");
|
|
ui.setHostOverlay(true);
|
|
ui.setHostPos(900.0, 30.0, 340.0, 420.0);
|
|
uiReady = true;
|
|
}
|
|
|
|
function activeId() { return STRUCT_RECIPES[active].id; }
|
|
|
|
eve_init = function() {
|
|
gfx.setBackgroundColor(0.05, 0.06, 0.07, 1.0);
|
|
setupCamera();
|
|
if (!uiReady) buildPanel();
|
|
rebuild();
|
|
};
|
|
|
|
eve_update = function(dt) {
|
|
if (autoSpin) {
|
|
camYaw += dt * 0.5;
|
|
local ex = math.polarX(16.0, camYaw);
|
|
local ez = math.polarY(16.0, camYaw);
|
|
camera.setEye(ex, 6.5, ez);
|
|
camera.setTarget(0.0, 1.2, 0.0);
|
|
}
|
|
|
|
local changed = ui.consumeChange();
|
|
while (changed != "") {
|
|
ui.select("lab");
|
|
if (changed == "lab/segments") { segments = ui.getValue("segments").tointeger(); rebuild(); }
|
|
else if (changed == "lab/segLength") { segLength = ui.getValue("segLength"); rebuild(); }
|
|
changed = ui.consumeChange();
|
|
}
|
|
|
|
local clicked = ui.consumeClick();
|
|
while (clicked != "") {
|
|
if (clicked == "lab/next") { active = (active + 1) % STRUCT_RECIPES.len(); rebuild(); }
|
|
else if (clicked == "lab/rebuild") { rebuild(); }
|
|
clicked = ui.consumeClick();
|
|
}
|
|
|
|
local tabDown = keyboard.isDown("tab");
|
|
local tabPressed = tabDown && !tabWas;
|
|
tabWas = tabDown;
|
|
if (tabPressed) { active = (active + 1) % STRUCT_RECIPES.len(); rebuild(); }
|
|
|
|
local plus = keyboard.isDown("plus") || keyboard.isDown("equals");
|
|
local wasPlus = plusWas;
|
|
plusWas = plus;
|
|
if (plus && !wasPlus) { segments = segments + 1; rebuild(); }
|
|
|
|
local minus = keyboard.isDown("minus");
|
|
local wasMinus = minusWas;
|
|
minusWas = minus;
|
|
if (minus && !wasMinus) { if (segments > 1) { segments = segments - 1; rebuild(); } }
|
|
};
|
|
|
|
eve_render = function() {
|
|
gfx.clear();
|
|
gfx.render3D();
|
|
ui.beginFrameAndRender();
|
|
};
|