chore: update .gitignore, enhance imgui.ini layout, and refine castle and character motion examples

This commit is contained in:
西风逍遥游 committed 2026-09-11 22:50:22 +08:00
1 parent ac42525522
commit 58e01f6d53
6 files changed
+102 -16

No files matched your search

+1
View File
@@ -22,6 +22,7 @@ test/assets/classic/.downloaded
# Skinned character: CesiumMan glTF is committed under test/assets/skinned/cesium_man/
# (see ATTRIBUTION.md). Re-run scripts/download_skinned_character.sh to refresh.
.vscode/
.local-tools/
.superpowers/
artifacts-local-test/
imgui.ini
+10
View File
@@ -13,3 +13,13 @@ Pos=12,8
Size=803,322
Collapsed=0
[Window][###map-input/Window]
Pos=24,88
Size=672,504
Collapsed=0
[Window][###hud/建造面板]
Pos=724,20
Size=532,756
Collapsed=0
+9 -7
View File
@@ -65,19 +65,21 @@ function rebuildCastle() {
if (castleCamera == null) {
castleCamera = eve.Camera3D();
castleCamera.setEye(54.0, 34.0, -58.0);
castleCamera.setTarget(0.0, 5.0, 0.0);
castleCamera.setUp(0.0, 1.0, 0.0);
castleCamera.setFov(43.0);
castleCamera.setAmbient(0.23, 0.25, 0.29);
castleCamera.setActive(true);
gfx.setDirectionalLight(-0.55, -1.0, -0.35, 1.45, 1.30, 1.08);
}
// Default far plane is 100 m; a rotating keep with outer rings sits past that.
castleCamera.setEye(88.0, 52.0, -96.0);
castleCamera.setTarget(0.0, 7.0, 0.0);
castleCamera.setUp(0.0, 1.0, 0.0);
castleCamera.setFov(38.0);
castleCamera.setClipPlanes(0.5, 2000.0);
castleCamera.setAmbient(0.23, 0.25, 0.29);
gfx.setDirectionalLight(-0.55, -1.0, -0.35, 1.45, 1.30, 1.08);
if (castleParts.len() == 0) rebuildCastle();
gfx.setBackgroundColor(0.10, 0.14, 0.20, 1.0);
function eve_update(dt) {
castleYaw += dt * 3.5;
castleYaw += dt * 0.22;
foreach (part in castleParts) part.setYaw(castleYaw);
if (castlePressed("r") || castlePressed("R")) { castleSeed += 1; rebuildCastle(); }
if (castlePressed("1")) { rings = max(1, rings - 1); rebuildCastle(); }
+42 -7
View File
@@ -32,6 +32,34 @@ function humanoidProfile(target) {
return p;
}
// createRenderable enables the glTF extension PBR path whenever Assimp reports
// an alphaMode. That shader is meant for IBL/capture, not this UI viewport:
// albedo is decoded twice, linear ORM/normal maps are treated as sRGB, and the
// characters render as a flat red unlit mass. Rebuild a conventional Material
// so renderScene3DToCanvas uses mesh3d lighting like the original showcase.
function labPreviewMaterial(part, albedo) {
local surface = gfx.newMaterial();
surface.setShadingModel("pbr");
surface.setMetallic(0.0);
surface.setRoughness(0.78);
surface.setReceiveLight(true);
surface.setReceiveShadow(true);
surface.setCastShadow(true);
surface.setTint(part.getTintR(), part.getTintG(), part.getTintB(), 1.0);
if (albedo != null) {
surface.setAlbedoTexture(albedo);
surface.setTint(1.0, 1.0, 1.0, 1.0);
part.setTexture(albedo);
part.setTint(1.0, 1.0, 1.0, 1.0);
}
part.setMaterial(surface);
part.setMetallic(0.0);
part.setRoughness(0.78);
part.setCastShadow(true);
part.setReceiveShadow(true);
part.setReceiveLight(true);
}
function addLabActor(model, name, x, source) {
local sk = source ? lab.skeleton : anim.newSkeletonFromModel(model);
local a = { model=model, name=name, skeleton=sk, clips=[], parts=[], skins=[],
@@ -50,14 +78,15 @@ function addLabActor(model, name, x, source) {
if (!model.hasBones(i)) continue;
local part = model3d.createRenderable(gfx, model, i);
part.setPosition(x, 0.0, 0.0);
part.setCastShadow(true); part.setReceiveShadow(true); part.setReceiveLight(true);
part.setRoughness(0.78);
local albedo = null;
local imported = part.getMaterial();
if (imported != null) albedo = imported.getAlbedoTexture();
local texturePath = model.getMaterialTexturePath(model.getMaterialIndex(i), "base_color", 0);
if (texturePath != "") {
local texture = gfx.newTextureFromFile("assets/quaternius/" + texturePath);
a.textures.push(texture); part.setTexture(texture);
part.getMaterial().setAlbedoTexture(texture);
albedo = gfx.newTextureFromFile("assets/quaternius/" + texturePath);
a.textures.push(albedo);
}
labPreviewMaterial(part, albedo);
a.parts.push(part);
a.skins.push(anim.newSkinFromModel(model, i, sk));
}
@@ -98,11 +127,17 @@ eve_init = function() {
"TARGET / Male", 2.5, false);
lab.ground = eve.Renderable3D(); lab.ground.setMesh(gfx.newMeshCube(1.0));
lab.ground.setPosition(0.0,-0.10,0.0); lab.ground.setScale(10.0,0.16,5.0);
lab.ground.setTint(0.13,0.17,0.21,1.0); lab.ground.setReceiveShadow(true);
lab.ground.setTint(0.13,0.17,0.21,1.0); lab.ground.setRoughness(0.92);
lab.ground.setReceiveShadow(true); lab.ground.setReceiveLight(true); lab.ground.setCastShadow(false);
lab.camera = eve.Camera3D(); labCamera(); lab.camera.setUp(0.0,1.0,0.0);
lab.camera.setFov(26.0); lab.camera.setAmbient(0.38,0.40,0.46); lab.camera.setActive(true);
gfx.setDirectionalLight(-0.4, 1.0, 0.7, 1.60, 1.49, 1.34);
local key = eve.Light3D(); key.setType("dir"); key.setDirection(-0.4,1.0,0.7);
key.setColor(1.0,0.93,0.84,1.6); lab.lights.push(key);
key.setColor(1.0,0.93,0.84,1.6); key.setCastShadow(true); key.setShadowStrength(0.85);
lab.lights.push(key);
local fill = eve.Light3D(); fill.setType("point");
fill.setPosition(2.2, 2.6, 3.4); fill.setRadius(12.0);
fill.setColor(0.55, 0.68, 0.92, 1.15); lab.lights.push(fill);
gfx.setBackgroundColor(0.045,0.065,0.09,1.0);
ui.setScale(1.0); ui.setTheme("dark"); ui.beginBuild(); ui.beginWindow("Character Motion Lab", "root");
ui.text("CHARACTER MOTION LAB / Quaternius CC0", "title");
+11 -2
View File
@@ -43,9 +43,18 @@ struct Builder {
float x0=cx+std::cos(a0)*radius, z0=cz+std::sin(a0)*radius;
float x1=cx+std::cos(a1)*radius, z1=cz+std::sin(a1)*radius;
float am=(a0+a1)*0.5f;
quad(x0,y0,z0, x1,y0,z1, x1,y1,z1, x0,y1,z0,
// CCW from the outside, matching Graphics::newMeshCylinder / Vulkan
// front faces. The previous order wound the wall and cap inward, so
// back-face culling showed the interior of every tower.
quad(x0,y0,z0, x0,y1,z0, x1,y1,z1, x1,y0,z1,
std::cos(am),0,std::sin(am));
quad(cx,y1,cz, x0,y1,z0, x1,y1,z1, cx,y1,cz, 0,1,0);
const uint32_t cap = uint32_t(mesh.getVertexCount());
mesh.addVertex(cx, y1, cz, 0, 1, 0, 0.5f, 0.5f);
mesh.addVertex(x1, y1, z1, 0, 1, 0, 0.5f + 0.5f * std::cos(a1),
0.5f + 0.5f * std::sin(a1));
mesh.addVertex(x0, y1, z0, 0, 1, 0, 0.5f + 0.5f * std::cos(a0),
0.5f + 0.5f * std::sin(a0));
mesh.addTriangle(cap, cap + 1, cap + 2);
}
}
};
+29
View File
@@ -369,6 +369,28 @@ bool meshNormalsFiniteUnit(const MeshBuild &m, float tol = 0.15f) {
return true;
}
bool meshWindingAgreesWithNormals(const MeshBuild &m, float minFraction = 0.9f) {
int ok = 0, counted = 0;
for (int t = 0; t + 2 < m.getIndexCount(); t += 3) {
const int i0 = m.getIndex(t), i1 = m.getIndex(t + 1), i2 = m.getIndex(t + 2);
const float ax = m.getPositionX(i1) - m.getPositionX(i0);
const float ay = m.getPositionY(i1) - m.getPositionY(i0);
const float az = m.getPositionZ(i1) - m.getPositionZ(i0);
const float bx = m.getPositionX(i2) - m.getPositionX(i0);
const float by = m.getPositionY(i2) - m.getPositionY(i0);
const float bz = m.getPositionZ(i2) - m.getPositionZ(i0);
const float gx = ay * bz - az * by, gy = az * bx - ax * bz, gz = ax * by - ay * bx;
const float gLen = std::sqrt(gx * gx + gy * gy + gz * gz);
if (gLen < 1e-8f) continue;
const float nx = (m.getNormalX(i0) + m.getNormalX(i1) + m.getNormalX(i2)) / 3.f;
const float ny = (m.getNormalY(i0) + m.getNormalY(i1) + m.getNormalY(i2)) / 3.f;
const float nz = (m.getNormalZ(i0) + m.getNormalZ(i1) + m.getNormalZ(i2)) / 3.f;
++counted;
if (gx * nx + gy * ny + gz * nz > 0.f) ++ok;
}
return counted > 0 && float(ok) / float(counted) >= minFraction;
}
bool meshPositionsFinite(const MeshBuild &m) {
for (int i = 0; i < m.getVertexCount(); ++i) {
if (!std::isfinite(m.getPositionX(i)) || !std::isfinite(m.getPositionY(i)) ||
@@ -3536,6 +3558,13 @@ TEST_CASE("procgen.mesh.castle.multilevelDeterministicAndComplete") {
CHECK(stairs->getVertexCount() > 100);
CHECK_EQ(stairs->getMeta("group", ""), "stairs");
CHECK(meshIndicesInRange(*stairs));
int towerGroup = -1;
for (int i = 0; i < a.getGroupCount(); ++i)
if (a.getGroupName(i) == "towers") towerGroup = i;
REQUIRE(towerGroup >= 0);
auto towers = a.copyGroup(towerGroup);
REQUIRE(towers.get() != nullptr);
CHECK(meshWindingAgreesWithNormals(*towers));
}
TEST_CASE("procgen.mesh.castle.parametersControlTopologyAndBounds") {