feat: 接入 Poly Haven island_tree_01 真实扫描树模型
tree_style 新增 'polyhaven' 选项,用 wm.append 从 vendored .blend 加载 LOD1 网格(4 种 branch+leaf 组合),通过实例化放置场景中的树。 每棵树约 625 tris / 2 meshes (branch+leaf),比旧 procedural tree (~400 tris) 多了约 60%,但换来真实树形和 PBR 贴图,树叶有 alpha 透明度,在 Cesium 远距离比纯几何球体更可读。 纹理从 ~/Downloads 手动下载(Poly Haven CDN 的 API URL 不可用), 存放在 assets/models/polyhaven/island_tree_01/textures/(不入 git)。 默认 tree_style 仍为 'natural',parity 保持不变。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ from osmassets.osm import Projector, parse_height, parse_osm # noqa: E402
|
||||
from osmassets import water as _water # noqa: E402
|
||||
from osmassets import grass as _grass # noqa: E402
|
||||
from osmassets import scrub as _scrub # noqa: E402
|
||||
from osmassets import tree as _tree # noqa: E402
|
||||
|
||||
|
||||
# Building assembly stays in this file because it needs make_prism, add_roof,
|
||||
@@ -91,6 +92,38 @@ def _assemble_building(ring, way_id, tag, args, buildings_c, building_mats):
|
||||
return 1, int(industrial), ring
|
||||
|
||||
|
||||
def _add_polyhaven_trees(positions, collection):
|
||||
"""Scatter island_tree_01 instances at `positions`.
|
||||
|
||||
positions is a list of (x, y, height) tuples as gathered by the two
|
||||
tree-collecting loops (point nodes + tree_row samples).
|
||||
"""
|
||||
variants = _tree.load_tree_variants()
|
||||
if not variants:
|
||||
# Fall back to procedural so a missing model is not a hard build error.
|
||||
print("Poly Haven tree model unavailable; switching to procedural trees")
|
||||
add_tree_batch(positions, collection,
|
||||
material_from_spec(catalog.MATERIALS["tree_trunk"]),
|
||||
material_from_spec(catalog.MATERIALS["tree_crown"]))
|
||||
return
|
||||
|
||||
for index, (x, y, height) in enumerate(positions):
|
||||
branch_mesh, leaf_mesh = variants[index % len(variants)]
|
||||
target = max(4.0, height)
|
||||
factor = target / 3.2
|
||||
|
||||
b_obj = bpy.data.objects.new(f"Tree_PH_{index:03d}_Branch", branch_mesh)
|
||||
b_obj.location = (x, y, 0.0)
|
||||
b_obj.scale = (factor, factor, factor)
|
||||
b_obj.rotation_euler = (0, 0, (index * 1.61803398875) % 1.0 * math.tau)
|
||||
collection.objects.link(b_obj)
|
||||
|
||||
l_obj = bpy.data.objects.new(f"Tree_PH_{index:03d}_Leaf", leaf_mesh)
|
||||
l_obj.location = (x, y, 0.0)
|
||||
l_obj.scale = (factor, factor, factor)
|
||||
l_obj.rotation_euler = (0, 0, (index * 1.61803398875) % 1.0 * math.tau)
|
||||
collection.objects.link(l_obj)
|
||||
|
||||
|
||||
MODEL_ROOT = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), "..", "assets", "models", "polyhaven"
|
||||
@@ -144,8 +177,8 @@ def cli_args():
|
||||
values["office_overrides"] = set()
|
||||
else:
|
||||
values["office_overrides"] = set()
|
||||
if values.get("tree_style") not in {"natural", "procedural"}:
|
||||
raise RuntimeError("--tree-style must be 'natural' or 'procedural'")
|
||||
if values.get("tree_style") not in {"natural", "procedural", "polyhaven"}:
|
||||
raise RuntimeError("--tree-style must be 'natural', 'procedural', or 'polyhaven'")
|
||||
return values
|
||||
|
||||
|
||||
@@ -714,13 +747,17 @@ def build(args):
|
||||
trees.extend(row_samples)
|
||||
row_tree_count += len(row_samples)
|
||||
if trees:
|
||||
tree_trunk = material_from_spec(catalog.MATERIALS["tree_trunk"])
|
||||
if args.get("tree_style") == "natural":
|
||||
tree_style = args.get("tree_style")
|
||||
if tree_style == "polyhaven":
|
||||
_add_polyhaven_trees(trees, props_c)
|
||||
elif tree_style == "natural":
|
||||
tree_trunk = material_from_spec(catalog.MATERIALS["tree_trunk"])
|
||||
add_natural_tree_instances(
|
||||
trees, props_c, tree_trunk,
|
||||
material_from_spec(catalog.MATERIALS["tree_crown_dark"]),
|
||||
material_from_spec(catalog.MATERIALS["tree_crown_light"]))
|
||||
else:
|
||||
tree_trunk = material_from_spec(catalog.MATERIALS["tree_trunk"])
|
||||
add_tree_batch(trees, props_c, tree_trunk,
|
||||
material_from_spec(catalog.MATERIALS["tree_crown"]))
|
||||
|
||||
@@ -794,7 +831,14 @@ def build(args):
|
||||
|
||||
os.makedirs(os.path.dirname(args["output"]), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(args["render"]), exist_ok=True)
|
||||
bpy.ops.file.pack_all()
|
||||
try:
|
||||
bpy.ops.file.pack_all()
|
||||
except RuntimeError:
|
||||
# Poly Haven textures are resolved relative to the model directory
|
||||
# and the blend source expects them under textures/ next to .blend.
|
||||
# When they are absent the scene still saves — the tree meshes just
|
||||
# render with missing-texture magenta.
|
||||
print("Some external resources could not be packed; saving anyway")
|
||||
bpy.ops.wm.save_as_mainfile(filepath=args["output"])
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print("SCENE_DONE", json.dumps({"output": args["output"],
|
||||
|
||||
Reference in New Issue
Block a user