#!/usr/bin/env python
import os
import sys
from methods import print_error
libname = "proc_nodes"
localEnv = Environment(tools=["default"], PLATFORM="")
customs = ["custom.py"]
customs = [os.path.abspath(path) for path in customs]
opts = Variables(customs, ARGUMENTS)
opts.Update(localEnv)
Help(opts.GenerateHelpText(localEnv))
env = localEnv.Clone()
if not (os.path.isdir("godot-cpp") and os.listdir("godot-cpp")):
print_error("""godot-cpp is not available within this folder.
Either clone it as a submodule or symlink it:
git submodule update --init --recursive
# or
ln -s /path/to/godot-cpp godot-cpp""")
sys.exit(1)
env = SConscript("godot-cpp/SConstruct", {"env": env, "customs": customs})
if os.environ.get("CC"):
env["CC"] = os.environ["CC"]
if os.environ.get("CXX"):
env["CXX"] = os.environ["CXX"]
env.Append(CPPPATH=["src/", "src/shared/"])
# ── libdc: THE ISOSURFACE EXTRACTOR, shared with dc-terrain ──────────────────
#
# Dual Contouring -- octree, QEF, contour, simplify -- lives in
# dc-terrain/libdc, is pure C++ with no Godot dependency, and was already the
# one implementation. proc-nodes needs it so Volume to Mesh can do what its name
# says: turn a density field into a constructed mesh. Writing a second extractor
# here is the mistake this repo has spent months undoing on other algorithms.
#
# This is the MIRROR of dc-terrain's own CPPPATH entry for
# ../proc-nodes/src/shared -- the two repos already require each other to be
# checked out side by side, and this makes it symmetric rather than new.
LIBDC_INC = "../dc-terrain/libdc/include"
LIBDC_SRC = "../dc-terrain/libdc/src"
if not os.path.isdir(LIBDC_INC):
# LOUD. A missing sibling used to be an empty Glob, which builds a library
# with no extractor in it and fails at the call site -- or worse, links.
print("ERROR: dc-terrain must be checked out beside proc-nodes.")
print(" Expected: %s" % os.path.abspath(LIBDC_INC))
print(" libdc holds the Dual Contouring extractor that Volume to Mesh")
print(" uses; there is deliberately no second copy in this repo.")
Exit(1)
env.Append(CPPPATH=[LIBDC_INC])
# A VARIANT DIR, and it is not optional. dc-terrain compiles these same sources
# with its own flags and leaves the objects BESIDE THE SOURCE (libdc/src/*.os).
# Globbing them directly from here would have two builds writing the same object
# paths with different flags, and whichever ran last would win silently.
VariantDir("build/libdc", LIBDC_SRC, duplicate=0)
# generators/ too: DCGeneratorMeshSDF is the mesh -> signed-distance evaluator
# (pseudonormal method, BVH) that Mesh to SDF Grid needs. Missing it is a link
# error rather than a silent gap, but only once something calls it.
VariantDir("build/libdc_gen", LIBDC_SRC + "/generators", duplicate=0)
# ⚠ EVERYTHING EXCEPT dc_seam. libdc is self-contained apart from one file:
# dc_seam.h includes dc_octree_clipmap.h, which lives in dc-terrain/src and
# DOES use godot_cpp -- so that one header breaks libdc's "pure C++" property
# and would drag the engine in. Seams stitch chunk boundaries in the clipmap;
# meshing a single authored volume has no boundaries to stitch, so nothing here
# needs it.
#
# Recorded rather than fixed: the honest repair is dc_seam.h not reaching into
# src/, and moving dc_octree_clipmap.h INTO libdc would make libdc
# godot-dependent, which is worse than the layering violation.
#
# dc_simplify is out for a different reason: it includes <meshoptimizer.h> from
# dc-terrain/thirdparty, and NOTHING on the meshing path calls it -- neither
# dc_mesher.cpp nor dc_contour.cpp mentions simplify. Taking it would mean
# vendoring a third-party library to compile a file we never call.
LIBDC_SKIP = ("dc_seam.cpp", "dc_simplify.cpp")
LIBDC_SOURCES = ([f for f in Glob("build/libdc/*.cpp")
if not str(f).endswith(LIBDC_SKIP)]
+ Glob("build/libdc_gen/*.cpp"))
# THE erosion filter lives once, in src/shared/erosion_filter.inc, and is
# compiled two ways: as C++ (via erosion_cpp_shim.h, here and in dc-terrain)
# and as GLSL (embedded in the generated header below, because a GDExtension
# cannot read a source file at run time). Generating rather than checking in
# the header is the point — a checked-in copy is a copy, and three copies of
# this algorithm is exactly what shipped a 60x divergence between the meshed
# surface and the collidable one.
glsl_inc_gen = [
env.Command(
"src/gpu/erosion_filter_glsl.gen.h",
"src/shared/erosion_filter.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET EROSION_FILTER_GLSL",
),
# The hash layer, second .inc since 2026-09-05. Voronoi's cell offsets,
# Perlin's lattice gradients and the whole of White Noise are this text,
# so a CPU copy and a GPU copy agreeing by review is the one thing that
# cannot be allowed here.
env.Command(
"src/gpu/blender_hash_glsl.gen.h",
"src/shared/blender_hash.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_HASH_GLSL",
),
# Perlin, third .inc. Depends on the hash and does not include it — GLSL
# has no #include, so the emitter concatenates hash-then-perlin and the
# C++ shim includes them in that order.
env.Command(
"src/gpu/blender_perlin_glsl.gen.h",
"src/shared/blender_perlin.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_PERLIN_GLSL",
),
# Voronoi, fourth .inc. Also depends on the hash and does not include it.
env.Command(
"src/gpu/blender_voronoi_glsl.gen.h",
"src/shared/blender_voronoi.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_VORONOI_GLSL",
),
# The colour blend modes, fifth .inc. Depends on nothing else.
env.Command(
"src/gpu/blender_ramp_blend_glsl.gen.h",
"src/shared/blender_ramp_blend.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_RAMP_BLEND_GLSL",
),
# The land/sea transfer curve, seventh .inc. Depends on nothing else.
# Emitted into the noise library beside the erosion filter because the
# WRAPPER calls it (on the mountain layer, before erosion), and the
# wrapper only exists where that library does.
env.Command(
"src/gpu/hypso_curve_glsl.gen.h",
"src/shared/hypso_curve.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET HYPSO_CURVE_GLSL",
),
# 3D simplex + fBm. The seabed below the coastal buffer is VOLUMETRIC --
# a heightfield cannot have a sea cave in it -- and that needs a noise in
# three dimensions, which none of the other .inc files provide.
env.Command(
"src/gpu/proc_simplex_glsl.gen.h",
"src/shared/proc_simplex.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET PROC_SIMPLEX_GLSL",
),
# Ashima simplex, sixth .inc. NOT Blender's -- it is proc-nodes' own
# Simplex Noise node, which blender-bridge mirrors as a node group.
env.Command(
"src/gpu/proc_simplex_glsl.gen.h",
"src/shared/proc_simplex.inc",
"python3 tools/gen_glsl_inc.py $SOURCE $TARGET PROC_SIMPLEX_GLSL",
),
]
sources = (
LIBDC_SOURCES
+ Glob("src/*.cpp")
+ Glob("src/core/*.cpp")
+ Glob("src/graph/*.cpp")
+ Glob("src/nodes/*.cpp")
+ Glob("src/scene/*.cpp")
+ Glob("src/gpu/*.cpp")
)
if env["target"] in ["editor", "template_debug"]:
sources += Glob("src/editor/*.cpp")
# Every TU may pull in the generated header transitively; make the dependency
# explicit rather than relying on scan order for a file that may not exist yet
# on a clean checkout.
env.Depends(sources, glsl_inc_gen)
if env["target"] in ["editor", "template_debug"]:
try:
doc_data = env.GodotCPPDocData(
"src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml")
)
sources.append(doc_data)
except AttributeError:
print("Not including class reference as we're targeting a pre-4.3 baseline.")
suffix = env["suffix"].replace(".dev", "").replace(".universal", "")
lib_filename = "{}{}{}{}".format(
env.subst("$SHLIBPREFIX"), libname, suffix, env.subst("$SHLIBSUFFIX")
)
library = env.SharedLibrary(
"bin/{}/{}".format(env["platform"], lib_filename),
source=sources,
)
Default(library)