Files
EVEngine/examples/venom-transform/main.nut
T
Cursor Agentand西风逍遥游 66b069d1f9 Polish venom character framing and refresh captures.
Face the Quaternius Superhero Female toward a fixed front 3/4 camera during
the auto capture pass, note the free-pack bald base mesh, and check in the
updated Lavapipe PNG sequence.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>
2026-09-11 12:06:48 +00:00

285 lines
9.8 KiB
Plaintext

// Venom-style body coverage demo (SakuraRabbit / Gwen-transform inspired).
// Quaternius CC0 female character + height/noise goo shader that spreads
// feet -> head with an emissive frontier and decorative tendril ribbons.
// Space = play/pause | Left/Right = scrub | R = reset | P = screenshot
persist venomShader = null
persist venomCamera = null
persist venomModel = null
persist venomParts = []
persist venomTendrils = []
persist venomDroplets = []
persist venomGround = null
persist venomCoverage = 0.0
persist venomPlaying = true
persist venomTime = 0.0
persist venomYaw = 0.72
persist venomFrame = 0
persist venomCaptureIdx = 0
persist venomCapturePending = false
persist venomCaptureName = ""
persist venomAutoMode = true
persist venomAutoHold = 0.0
persist venomDone = false
venomAutoShots <- [0.0, 0.28, 0.55, 0.82, 1.0]
venomCharacterHeight <- 1.80
// Quaternius free Universal Base is bald (full hair is in the paid pack);
// eyebrows/eyes still ship as separate meshes with the hair/eye textures.
function keyJust(k) {
return key_just_pressed(k);
}
function pushUniforms() {
venomShader.sendFloat("coverage", venomCoverage);
venomShader.sendFloat("edgeWidth", 0.06);
venomShader.sendFloat("noiseScale", 2.2);
venomShader.sendFloat("time", venomTime);
venomShader.sendFloat("gooR", 0.02);
venomShader.sendFloat("gooG", 0.02);
venomShader.sendFloat("gooB", 0.04);
venomShader.sendFloat("edgeR", 0.55);
venomShader.sendFloat("edgeG", 0.12);
venomShader.sendFloat("edgeB", 0.85);
venomShader.sendFloat("edgeGlow", 2.4);
venomShader.sendFloat("gloss", 0.92);
}
function addTendril(seed) {
local t = {
body = eve.Renderable3D()
seed = seed.tofloat()
phase = seed * 0.73
};
t.body.setMesh(gfx.newMeshCylinder(10, 1, true));
t.body.setShader(venomShader);
t.body.setTint(0.03, 0.03, 0.05, 1.0);
t.body.setRoughness(0.2);
t.body.setCastShadow(false);
t.body.setReceiveShadow(true);
venomTendrils.push(t);
}
function addDroplet(seed) {
local d = {
body = eve.Renderable3D()
seed = seed.tofloat()
life = 0.0
};
d.body.setMesh(gfx.newMeshSphere(8, 6));
d.body.setShader(venomShader);
d.body.setTint(0.04, 0.04, 0.06, 1.0);
d.body.setRoughness(0.15);
d.body.setCastShadow(false);
venomDroplets.push(d);
}
function buildCharacter() {
if (model3d == null) model3d = eve.Model3D();
venomModel = model3d.newModelDataFromFile(
"assets/quaternius/Superhero_Female_FullBody.gltf");
if (venomModel == null)
throw "Failed to load Quaternius Superhero_Female_FullBody.gltf";
local meshCount = venomModel.getMeshCount();
print(format("[venom] loaded character meshes=%d\n", meshCount));
for (local i = 0; i < meshCount; ++i) {
local part = model3d.createRenderable(gfx, venomModel, i);
part.setPosition(0.0, 0.0, 0.0);
// Default bind facing is +Z; orbit camera sits on +Z at yaw=0.
part.setYaw(0.0);
part.setCastShadow(true);
part.setReceiveShadow(true);
part.setReceiveLight(true);
local mat = part.getMaterial();
mat.setShader(venomShader);
mat.setShadingModel("custom");
mat.setRoughness(0.55);
// Keep authored albedo/normal if the importer bound them.
local texPath = venomModel.getMaterialTexturePath(
venomModel.getMaterialIndex(i), "base_color", 0);
if (texPath != "") {
local tex = gfx.newTextureFromFile("assets/quaternius/" + texPath);
part.setTexture(tex);
mat.setAlbedoTexture(tex);
}
venomParts.push(part);
}
if (venomParts.len() == 0)
throw "Character had no renderable meshes";
for (local i = 0; i < 10; ++i) addTendril(i);
for (local i = 0; i < 14; ++i) addDroplet(i);
venomGround = eve.Renderable3D();
venomGround.setMesh(gfx.newMeshCube(1.0));
venomGround.setPosition(0.0, -0.04, 0.0);
venomGround.setScale(5.5, 0.08, 5.5);
venomGround.setTint(0.55, 0.16, 0.22, 1.0);
venomGround.setRoughness(0.95);
venomGround.setCastShadow(false);
venomGround.setReceiveShadow(true);
}
function updateTendrils(_dt) {
local frontY = venomCoverage * venomCharacterHeight;
local bloom = 1.0 - fabs(venomCoverage - 0.55) / 0.55;
if (bloom < 0.0) bloom = 0.0;
bloom = bloom * bloom;
for (local i = 0; i < venomTendrils.len(); ++i) {
local t = venomTendrils[i];
local ang = t.phase + venomTime * (0.7 + t.seed * 0.05) + i * 0.628;
local radius = 0.28 + 0.22 * bloom + 0.05 * sin(venomTime * 2.1 + t.seed);
local y = frontY + 0.06 * sin(venomTime * 3.0 + t.seed);
local len = (0.12 + 0.45 * bloom) * (0.7 + 0.3 * sin(venomTime * 1.7 + i));
local thick = bloom > 0.05 ? (0.015 + 0.025 * bloom) : 0.001;
local visibleLen = bloom > 0.05 ? len : 0.001;
local pitch = 0.45 + 0.55 * sin(venomTime * 2.4 + t.seed);
t.body.setPosition(cos(ang) * radius, y, sin(ang) * radius * 0.7);
t.body.setScale(thick, visibleLen, thick);
t.body.setRotation(ang, pitch, 0.0);
}
}
function updateDroplets(dt) {
local frontY = venomCoverage * venomCharacterHeight;
local active = venomCoverage > 0.05 && venomCoverage < 0.95;
for (local i = 0; i < venomDroplets.len(); ++i) {
local d = venomDroplets[i];
d.life += dt * (0.8 + (i % 5) * 0.13);
if (d.life > 1.0) d.life -= 1.0;
local ang = d.seed * 1.7 + venomTime * 0.4;
local spread = 0.20 + d.life * 0.50;
local y = frontY + 0.22 * d.life;
local size = active ? (0.012 + 0.025 * (1.0 - d.life)) : 0.001;
d.body.setPosition(cos(ang) * spread, y, sin(ang) * spread * 0.7);
d.body.setScale(size, size * 1.4, size);
}
}
function orbitCamera() {
// Front 3/4 of the full body so the goo climb reads clearly.
local radius = 3.9;
local eyeY = 1.35;
venomCamera.setEye(sin(venomYaw) * radius, eyeY, cos(venomYaw) * radius);
venomCamera.setTarget(0.0, 0.90, 0.0);
}
function requestShot(name) {
venomCaptureName = name;
venomCapturePending = true;
}
eve_init = function() {
gfx.setBackgroundColor(0.62, 0.18, 0.28, 1.0);
if (venomShader == null) {
venomShader = gfx.newMeshShader(fs.readText("shaders/venom.frag"));
local names = ["coverage", "edgeWidth", "noiseScale", "time",
"gooR", "gooG", "gooB", "edgeR", "edgeG", "edgeB",
"edgeGlow", "gloss"];
foreach (n in names) venomShader.declareFloat(n);
}
venomCamera = eve.Camera3D();
venomCamera.setUp(0.0, 1.0, 0.0);
venomCamera.setFov(38.0);
venomCamera.setAmbient(0.28, 0.24, 0.30);
venomCamera.setActive(true);
gfx.setDirectionalLight(-0.35, 0.90, 0.35, 1.65, 1.40, 1.25);
local rc = gfx.getRenderControl();
rc.disable("ao");
rc.disable("gi");
rc.enable("msaa");
rc.compile();
if (venomParts.len() == 0) buildCharacter();
orbitCamera();
pushUniforms();
print("[venom] Quaternius Superhero Female (CC0)\n");
print("[venom] Space play/pause | Left/Right scrub | R reset | P screenshot\n");
print("[venom] auto demo will capture coverage steps then exit\n");
};
eve_update = function(dt) {
venomFrame += 1;
venomTime += dt;
// Keep a fixed front 3/4 during the auto capture pass so every
// coverage step shows the character face-on; orbit only when interactive.
if (!venomAutoMode)
venomYaw += dt * 0.22;
orbitCamera();
if (keyJust("space")) venomPlaying = !venomPlaying;
if (keyJust("r") || keyJust("R")) {
venomCoverage = 0.0;
venomPlaying = true;
venomAutoMode = false;
}
if (keyJust("left")) {
venomCoverage = max(0.0, venomCoverage - 0.05);
venomPlaying = false;
venomAutoMode = false;
}
if (keyJust("right")) {
venomCoverage = min(1.0, venomCoverage + 0.05);
venomPlaying = false;
venomAutoMode = false;
}
if (keyJust("p") || keyJust("P"))
requestShot(format("captures/venom-manual-%.0f.png", venomCoverage * 100.0));
if (venomAutoMode) {
if (venomCaptureIdx < venomAutoShots.len()) {
local target = venomAutoShots[venomCaptureIdx];
local diff = target - venomCoverage;
if (fabs(diff) > 0.01) {
local step = (diff > 0.0 ? 1.0 : -1.0) * dt * 0.45;
if (fabs(step) > fabs(diff)) step = diff;
venomCoverage += step;
venomAutoHold = 0.0;
} else {
venomCoverage = target;
venomAutoHold += dt;
if (venomAutoHold > 0.40 && !venomCapturePending) {
requestShot(format("captures/venom-%02d-c%.0f.png",
venomCaptureIdx, target * 100.0));
}
}
} else if (!venomDone && !venomCapturePending) {
venomDone = true;
print("[venom] auto demo complete\n");
// Prefer load.nut bootBench early-exit so the window is not
// destroyed mid-frame (win.close() here segfaults under Vulkan).
eve.bootBench = true;
return;
}
} else if (venomPlaying) {
venomCoverage += dt * 0.20;
if (venomCoverage >= 1.0) {
venomCoverage = 1.0;
venomPlaying = false;
}
}
updateTendrils(dt);
updateDroplets(dt);
pushUniforms();
};
eve_render = function() {
if (venomCapturePending && venomFrame > 8 &&
gfx.saveFramePng(venomCaptureName)) {
print("[venom] saved " + venomCaptureName + "\n");
venomCapturePending = false;
if (venomAutoMode) {
venomCaptureIdx += 1;
venomAutoHold = 0.0;
}
}
gfx.clear();
gfx.render3D();
};