This commit is contained in:
Connor
2026-02-24 17:22:41 +09:00
parent 1b7fd1c7f8
commit bee5aa0e8f
8 changed files with 1113 additions and 123 deletions

View File

@@ -320,7 +320,7 @@ public:
QueryDistanceNode(int32_t id, int32_t maxDist)
: tileID(id), maxDistance(maxDist) {}
Type GetOutputType() const override { return Type::Int; }
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "QueryDistance"; }
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
@@ -333,7 +333,57 @@ public:
best = d;
}
}
return Value::MakeInt(best);
return Value::MakeFloat(1.f - static_cast<float>(best) / (maxDistance + 1));
}
};
/// Returns true when the cell at (worldX, worldY) is AIR in the previous pass,
/// has solid ground within maxDepth tiles below, and is enclosed by solid walls
/// within maxWidth tiles on both the left and right at this cell's Y level.
class LiquidNode : public Node {
public:
int32_t maxWidth { 8 };
int32_t maxDepth { 4 };
LiquidNode() = default;
LiquidNode(int32_t w, int32_t d) : maxWidth(w), maxDepth(d) {}
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "QueryLiquid"; }
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
// Quick discard: cell itself must be AIR
if (ctx.GetPrevTile(ctx.worldX, ctx.worldY) != 0)
return Value::MakeBool(false);
// Ground below (Y increases upward, so below = decreasing Y)
bool hasGround = false;
for (int32_t dy = 1; dy <= maxDepth; ++dy)
if (ctx.GetPrevTile(ctx.worldX, ctx.worldY - dy) != 0) { hasGround = true; break; }
if (!hasGround) return Value::MakeBool(false);
// Left wall: scan left, but stop early if an intermediate AIR cell has
// no ground below it — liquid would drain through that gap.
bool hasLeft = false;
for (int32_t dx = 1; dx <= maxWidth; ++dx) {
if (ctx.GetPrevTile(ctx.worldX - dx, ctx.worldY) != 0) { hasLeft = true; break; }
bool floored = false;
for (int32_t dy = 1; dy <= maxDepth; ++dy)
if (ctx.GetPrevTile(ctx.worldX - dx, ctx.worldY - dy) != 0) { floored = true; break; }
if (!floored) break;
}
if (!hasLeft) return Value::MakeBool(false);
// Right wall: same floor-continuity check.
for (int32_t dx = 1; dx <= maxWidth; ++dx) {
if (ctx.GetPrevTile(ctx.worldX + dx, ctx.worldY) != 0)
return Value::MakeBool(true);
bool floored = false;
for (int32_t dy = 1; dy <= maxDepth; ++dy)
if (ctx.GetPrevTile(ctx.worldX + dx, ctx.worldY - dy) != 0) { floored = true; break; }
if (!floored) return Value::MakeBool(false);
}
return Value::MakeBool(false);
}
};
@@ -704,23 +754,65 @@ public:
}
};
/// Cellular (Worley) noise — output in [-1, 1] (Float)
/// Returns true for the single tile that is closest to the centre of its
/// Voronoi cell. Exactly one tile per cell returns true. (Bool)
///
/// Works by finding the nearest jittered feature point in scaled lattice space,
/// then rounding it back to the nearest integer world coordinate. A tile
/// returns true only when it IS that rounded coordinate.
class CellularNoiseNode : public Node {
public:
float frequency { 0.01f };
explicit CellularNoiseNode(float freq = 0.01f) : frequency(freq) {}
Type GetOutputType() const override { return Type::Float; }
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "CellularNoise"; }
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
FastNoiseLite fn;
fn.SetSeed(static_cast<int>(ctx.seed));
fn.SetNoiseType(FastNoiseLite::NoiseType_Cellular);
fn.SetFrequency(frequency);
return Value::MakeFloat(fn.GetNoise(static_cast<float>(ctx.worldX),
static_cast<float>(ctx.worldY)));
// Map world position into lattice space.
float sx = ctx.worldX * frequency;
float sy = ctx.worldY * frequency;
int32_t cellX = static_cast<int32_t>(std::floor(sx));
int32_t cellY = static_cast<int32_t>(std::floor(sy));
// Search the 3×3 neighbourhood for the nearest feature point.
float bestDist2 = 1e30f;
float bestFX = 0.0f, bestFY = 0.0f;
for (int dy = -1; dy <= 1; ++dy) {
for (int dx = -1; dx <= 1; ++dx) {
int32_t nx = cellX + dx;
int32_t ny = cellY + dy;
// Hash (seed, nx, ny) → a jitter in [0, 1) for each axis.
uint32_t h = static_cast<uint32_t>(ctx.seed)
^ (static_cast<uint32_t>(nx) * 2654435761u)
^ (static_cast<uint32_t>(ny) * 2246822519u);
h ^= h >> 16;
h *= 0x45d9f3bu;
h ^= h >> 16;
float jx = (h & 0xFFFFu) / 65536.0f;
float jy = (h >> 16) / 65536.0f;
float fpX = static_cast<float>(nx) + jx;
float fpY = static_cast<float>(ny) + jy;
float ddx = fpX - sx;
float ddy = fpY - sy;
float d2 = ddx * ddx + ddy * ddy;
if (d2 < bestDist2) {
bestDist2 = d2;
bestFX = fpX;
bestFY = fpY;
}
}
}
// The centre tile is the integer world coordinate nearest to the
// feature point. Return true only when this tile IS that coordinate.
auto centerX = static_cast<int32_t>(std::round(bestFX / frequency));
auto centerY = static_cast<int32_t>(std::round(bestFY / frequency));
return Value::MakeBool(ctx.worldX == centerX && ctx.worldY == centerY);
}
};