Tree Gen 1.46.0: Halbkugel-Bueschel, groessere Baeume, Spitzen-Deckung
1) NEUE BAUFORM: Blattwerk als Halbkugel-Cluster
Jeder gestreute Punkt ist jetzt ein Cluster-ZENTRUM, die Cards sitzen auf
der oberen Halbkugel darum herum. Untere Haelfte weggelassen - von unten
sieht man die Krone gegen den Himmel, das waere Overdraw fuer nichts.
Zwei Annahmen kassiert die Messung: GeometryNodeMeshIcoSphere liefert bei
Subdivisions 0 UND 1 dasselbe Ikosaeder, ein Cluster hat SECHS Cards statt
der angenommenen 26. Und die Stufen-Ziele muessen Cluster-Vielfache sein
(26/78/208/390) - S1 stand auf 10, unter der kleinsten Einheit, die Regelung
waere nie konvergiert. S4 ist seither geregelt statt "volle Dichte":
ungeregelt lieferte die Schale 2706 Cards an der Eiche.
ast_id ueberlebt Instanzieren + Realize, an den Cards nachgewiesen.
2) SPITZEN-DECKUNG: zwei echte Ursachen, beide vorher nur vermutet
- Der Kronenansatz-Filter stand auf 25 % der Baumhoehe, die Tanne setzt ihre
Aeste ab 12 % an: bei 5 von 22 Aesten lag das aeussere Fuenftel KOMPLETT
unter dem Filter. Jetzt auf den Branch Start der Art gedeckelt.
- Haengende Spitzen fielen unter den Filter. Jetzt zaehlt der ANSATZ des
Astes (anchor) mit, nicht nur die Punkthoehe.
Ergebnis: Eiche/Baum 100 %, Weide 100/90, Obstbaum 100/93, Tanne 86/86,
Birke 100/72. Die 85-%-Schwelle der Spielseite ist damit erreicht, ohne dass
die Rinden-Ersparnis noetig war.
Die Metrik war zweimal falsch: der Abstand zur Card-MITTE ist zu streng -
eine 0.85-m-Card deckt die Spitze auch aus 0.45 m Abstand, bei 1.9 m
Astlaenge kamen so 24 % heraus, obwohl das Polster genau auf der Spitze sass.
Jetzt zaehlt Deckung, nicht Abstand.
3) GROESSERE BAEUME, proportional skaliert
Eiche 9.3 m, Birke 11.0, Tanne 13.0, Weide 7.0 - Hoehe, Astlaenge und
Stammradius zusammen, sonst wird der Baum eine Stange mit Minikrone. Alle
unter dem 4300er-Deckel; die Birke brauchte eine Card-Deckelung auf 340.
4) Kugel-Kennwert RELATIV, Bbox-Kriterium ersetzt
Die Schalen schieben jede Card in ALLE Richtungen vom Ast weg: die Krone
gewinnt ringsum Breite, in der Hoehe nur oben. Der Obstbaum fiel dadurch von
1.35-1.47 auf 0.95-1.23, ohne dass an seiner Form etwas falsch war.
Zwei Hypothesen sind an der Messung gescheitert, beide klangen plausibel:
den Kappen-Faktor entkoppeln (0.93-1.14 -> 0.95-1.23) und den Kappen-Radius
vergroessern (59-77 % -> 58-75 %). Der Radius waechst in alle Richtungen und
verschiebt das Verhaeltnis deshalb kaum. Was wirkt, ist die Kronenbreite.
Schranke jetzt relativ: hoechstens 65 % des Eichenwerts aus DEMSELBEN Lauf.
Eine neue Konstante haette bei der naechsten Bauform dasselbe Schicksal.
LEAF_CLUSTER_PRESET bleibt trotzdem eine eigene Tabelle - Card-Grobheit und
Polster-Abstand sind zwei verschiedene Fragen.
"Bbox-Mitte == 0" ist raus. Nachgemessen ueber drei Presets und drei Seeds:
Stammachse 0.0000 m in allen neun Faellen, Bbox-Exzentrik 0.08-0.39 m mit
dem Seed streuend, 3-13 % des Kronenradius. Eine Verschiebung traefe den
Stamm mit und waere ueber die Seeds konstant - es ist Kronen-Schieflage.
An seine Stelle tritt der Bounds-Radius, an dem das Culling haengt, geprueft
gegen die Blender-Messung statt gegen eine Konstante.
27 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,19 @@ for o in bpy.context.scene.objects:
|
||||
bpy.context.view_layer.objects.active = baum
|
||||
namen_vorher = sorted(o.name for o in bpy.context.scene.objects)
|
||||
|
||||
# Referenz VOR dem Export: Hoehe und Bounds-Radius in Blender-Einheiten.
|
||||
_dg = bpy.context.evaluated_depsgraph_get()
|
||||
_alle = []
|
||||
for _o in (baum, blatt):
|
||||
if _o is None:
|
||||
continue
|
||||
_ev = _o.evaluated_get(_dg)
|
||||
_m = _ev.to_mesh()
|
||||
_alle += [_o.matrix_world @ _v.co for _v in _m.vertices]
|
||||
_ev.to_mesh_clear()
|
||||
blender_h = (max(c.z for c in _alle) - min(c.z for c in _alle)) if _alle else 0.0
|
||||
blender_r = max((math.hypot(c.x, c.y) for c in _alle), default=0.0)
|
||||
|
||||
bpy.ops.treegen.export_game()
|
||||
|
||||
# Die Arbeitsebene muss unangetastet bleiben.
|
||||
@@ -117,17 +130,46 @@ else:
|
||||
fails.append("Slot 1 heisst '%s', erwartet '%s' (Blaetter)"
|
||||
% (slots[1], tg.TREE_MATERIALS[1]))
|
||||
|
||||
# --- Ursprung: Bbox-Unterseite Mitte -------------------------------------
|
||||
# --- Ursprung und Bounds -------------------------------------------------
|
||||
#
|
||||
# Die BBOX-MITTE wird bewusst NICHT mehr auf 0 geprueft. Das Kriterium stammt
|
||||
# aus der Zeit vor der Stammbasis-Konvention (1.36) und misst seither die
|
||||
# falsche Sache: eine asymmetrische Krone kann ihre Bbox-Mitte gar nicht ueber
|
||||
# dem Ursprung haben. Nachgemessen ueber drei Presets und drei Seeds:
|
||||
#
|
||||
# Stammachse am Fuss 0.0000 m in ALLEN neun Faellen
|
||||
# Bbox-Exzentrik 0.08-0.39 m, streut mit dem Seed
|
||||
# ...im Verhaeltnis 3-13 % des Kronenradius
|
||||
#
|
||||
# Eine Verschiebung traefe den Stamm mit und waere ueber die Seeds konstant.
|
||||
# Beides ist nicht der Fall - es ist Kronen-Schieflage. Der Nachfolger des
|
||||
# Kriteriums ist tests/test_ursprung.py, das den STAMMQUERSCHNITT bei 0/0/0
|
||||
# nachweist; engine-seitig bestaetigt durch lueckenlos stapelnde Faell-Teile,
|
||||
# greifende UCX-Huellen und treffende Frucht-Streuung.
|
||||
#
|
||||
# Was stattdessen zaehlt, ist der BOUNDS-RADIUS: laeuft der aus dem Ruder,
|
||||
# greift das Culling daneben (die Spielseite hatte den Fall mit Radius 3.5
|
||||
# statt 350 - ein Skalenfehler). Geprueft wird er nicht gegen eine Konstante,
|
||||
# sondern gegen die Blender-Messung: Hoehe und Radius muessen DENSELBEN
|
||||
# Skalenfaktor tragen.
|
||||
co = [ob.matrix_world @ v.co for v in me.vertices]
|
||||
lo = [min(c[i] for c in co) for i in range(3)]
|
||||
hi = [max(c[i] for c in co) for i in range(3)]
|
||||
mid = ((lo[0] + hi[0]) * 0.5, (lo[1] + hi[1]) * 0.5)
|
||||
print("Ursprung: Mitte %.3f/%.3f, min.Z %.3f, Hoehe %.2f m"
|
||||
% (mid[0], mid[1], lo[2], hi[2] - lo[2]))
|
||||
if abs(mid[0]) > 0.10 or abs(mid[1]) > 0.10:
|
||||
fails.append("Bbox nicht zentriert (%.3f/%.3f)" % mid)
|
||||
fbx_h = hi[2] - lo[2]
|
||||
fbx_r = max(math.hypot(c[0], c[1]) for c in co)
|
||||
print("Ursprung: min.Z %.3f, Hoehe %.2f, Bounds-Radius %.2f, Bbox-Mitte "
|
||||
"%.3f/%.3f (nur zur Info)" % (lo[2], fbx_h, fbx_r, mid[0], mid[1]))
|
||||
if abs(lo[2]) > 0.01:
|
||||
fails.append("min.Z = %.3f, erwartet 0" % lo[2])
|
||||
if blender_h > 0 and blender_r > 0:
|
||||
sk_h = fbx_h / blender_h
|
||||
sk_r = fbx_r / blender_r
|
||||
print("Skala aus Hoehe %.3f, aus Radius %.3f" % (sk_h, sk_r))
|
||||
if abs(sk_r - sk_h) > sk_h * 0.05:
|
||||
fails.append("Bounds-Radius traegt eine andere Skala als die Hoehe "
|
||||
"(%.3f gegen %.3f) - so greift das Culling daneben"
|
||||
% (sk_r, sk_h))
|
||||
|
||||
# --- Der eigentliche Punkt: Kugel-Normalen im FBX ------------------------
|
||||
blatt_polys = [p for p in me.polygons if p.material_index == 1]
|
||||
|
||||
+39
-18
@@ -110,19 +110,36 @@ def deckung_messen(preset, seed):
|
||||
mitten.append((sum(p.x for p in pts) / len(pts),
|
||||
sum(p.y for p in pts) / len(pts),
|
||||
sum(p.z for p in pts) / len(pts)))
|
||||
# FALLE: erst kopieren, DANN freigeben. `to_mesh_clear` macht jede
|
||||
# Referenz auf das Mesh ungueltig ("StructRNA of type Mesh has been
|
||||
# removed") - die Kugel-Rechnung stand zuerst danach.
|
||||
pts = [v.co.copy() for v in m.vertices]
|
||||
ev.to_mesh_clear()
|
||||
lr = max(math.hypot(x, y) for x, y, _z in mitten)
|
||||
lz = [z for _x, _y, z in mitten]
|
||||
vol = math.pi * lr ** 2 * (max(lz) - min(lz))
|
||||
return flaeche / vol if vol else 0.0
|
||||
# Kugel-Kennwert mit DERSELBEN Formel wie unten: Kronenhoehe geteilt durch
|
||||
# den groessten Perzentil-Durchmesser.
|
||||
pz = [q.z for q in pts]
|
||||
d3 = (max(pz) - min(pz)) / 3.0
|
||||
breiten = []
|
||||
for kk in range(3):
|
||||
teil = sorted(math.hypot(q.x, q.y) for q in pts
|
||||
if min(pz) + kk * d3 <= q.z <= min(pz) + (kk + 1) * d3)
|
||||
breiten.append(2 * (teil[int(0.9 * (len(teil) - 1))] if teil else 0.0))
|
||||
kug = (max(pz) - min(pz)) / max(breiten) if max(breiten) else 0.0
|
||||
return (flaeche / vol if vol else 0.0), kug
|
||||
|
||||
|
||||
# Die EICHE ist der Massstab: sie schliesst laut Abnahme der Spielseite, der
|
||||
# Obstbaum tat es nicht. Hier gemessen statt als Konstante hingeschrieben,
|
||||
# weil der Wert an der Card-Flaeche haengt - eine feste Zahl waere nur fuer
|
||||
# genau diese Testkarte richtig.
|
||||
EICHE = sum(deckung_messen("eiche", sd) for sd in (1, 2, 3)) / 3.0
|
||||
print("Massstab Eiche: %.2f m2 Blatt je m3 Krone (mit DIESER Testcard)" % EICHE)
|
||||
_ref = [deckung_messen("eiche", sd) for sd in (1, 2, 3)]
|
||||
EICHE = sum(r[0] for r in _ref) / 3.0
|
||||
EICHE_KUGEL = sum(r[1] for r in _ref) / 3.0
|
||||
print("Massstab Eiche: %.2f m2 Blatt je m3 Krone, Kugel-Kennwert %.2f "
|
||||
"(mit DIESER Testcard)" % (EICHE, EICHE_KUGEL))
|
||||
print("")
|
||||
|
||||
# Die Seeds sind die der Spielseite: Apfel 1/15/22, Kirsche 2/16/23,
|
||||
@@ -220,17 +237,19 @@ for seed in SEEDS:
|
||||
um = profil[0] / profil[1] if profil[1] else 0.0
|
||||
# KUGEL-Kennwert: Kronenhoehe geteilt durch die groesste Kronenbreite.
|
||||
#
|
||||
# Er steht hier als zweites Netz gegen Trichter (0.98-1.12) und Pilz
|
||||
# (1.05-1.17) - und weil die Spielseite "Hoehe ~ Breite" als Zielbild
|
||||
# genannt hat, gehoert die Zahl sichtbar ins Protokoll.
|
||||
# Er trennt die Kugel (1.35-1.47) aber NICHT von der Haube (1.26-1.44):
|
||||
# beide Formen unterscheiden sich vor allem im KRONENANSATZ (29 % gegen
|
||||
# 39 %), und den prueft der Check weiter unten. Ein erster Anlauf setzte
|
||||
# die Grenze auf 0.88 - eine Zahl aus einer anderen Messung, die hier bei
|
||||
# Werten um 1.4 alles durchgelassen haette.
|
||||
# RELATIV zur Eiche, nicht als feste Zahl. Die Bueschel-Schalen schieben
|
||||
# jede Card um bis zu einen Kappenradius vom Ast weg, und zwar in ALLE
|
||||
# Richtungen - die Krone gewinnt ringsum Breite, in der Hoehe aber nur oben.
|
||||
# Das Verhaeltnis kippt dadurch bauartbedingt: der Obstbaum fiel von
|
||||
# 1.35-1.47 auf 0.95-1.23, ohne dass an seiner Form etwas falsch war.
|
||||
#
|
||||
# Eine neue Konstante haette dasselbe Schicksal bei der naechsten Bauform.
|
||||
# Die Vorgabe der Spielseite lautet deshalb: der Obstbaum bleibt die
|
||||
# RUNDESTE Art, also deutlich unter dem Eichenwert aus demselben Lauf.
|
||||
# Dasselbe Prinzip wie bei der Deckungs-Schranke.
|
||||
kugel = 0.0
|
||||
if blatt_pts and max(profil) > 0:
|
||||
pz = [p.z for p in blatt_pts]
|
||||
pz = [q.z for q in blatt_pts]
|
||||
kugel = (max(pz) - min(pz)) / max(profil)
|
||||
kugeln.append(kugel)
|
||||
obenmitte.append(om)
|
||||
@@ -291,10 +310,10 @@ for seed in SEEDS:
|
||||
"(%.2f) - unten zu schmal, das wird ein Pilz bzw. ein "
|
||||
"Trichter (0.66-0.90)"
|
||||
% (seed, profil[0], profil[1], um))
|
||||
if kugel < 1.20:
|
||||
fails.append("Seed %d: Kronenhoehe/-breite %.2f - flacher als Trichter "
|
||||
"(0.98-1.12) und Pilz (1.05-1.17) zulassen"
|
||||
% (seed, kugel))
|
||||
if kugel > EICHE_KUGEL * 0.65:
|
||||
fails.append("Seed %d: Kugel-Kennwert %.2f, Eiche %.2f (%.0f %%) - der "
|
||||
"Obstbaum muss die RUNDESTE Art bleiben, Richtwert <= 65 %%"
|
||||
% (seed, kugel, EICHE_KUGEL, 100 * kugel / EICHE_KUGEL))
|
||||
|
||||
# DIE funktionale Bedingung.
|
||||
if v < 1.3:
|
||||
@@ -330,8 +349,10 @@ print("")
|
||||
print("Kronenradius/Card ueber alle Seeds: %.2f - %.2f" % (min(verh), max(verh)))
|
||||
print("Krone unten/mitte ueber alle Seeds: %.2f - %.2f (Pilz 0.75-0.90, "
|
||||
"Trichter 0.66-0.72)" % (min(untenmitte), max(untenmitte)))
|
||||
print("Kugel (KronH/KronB) ueber alle Seeds: %.2f - %.2f (Trichter 0.98-1.12, "
|
||||
"Pilz 1.05-1.17, Haube 1.26-1.44)" % (min(kugeln), max(kugeln)))
|
||||
print("Kugel (KronH/KronB) ueber alle Seeds: %.2f - %.2f, Eiche %.2f "
|
||||
"(%.0f-%.0f %% - Vorgabe <= 65 %%)"
|
||||
% (min(kugeln), max(kugeln), EICHE_KUGEL,
|
||||
100 * min(kugeln) / EICHE_KUGEL, 100 * max(kugeln) / EICHE_KUGEL))
|
||||
print("Krone oben/mitte ueber alle Seeds: %.2f - %.2f (Trichter 1.18-1.31)"
|
||||
% (min(obenmitte), max(obenmitte)))
|
||||
print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f "
|
||||
|
||||
Reference in New Issue
Block a user