feat(environment): add painterly skies and living meadows
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
"""Build the original Jajce meadow flowers in Blender 5.1+.
|
||||
|
||||
Blender --background --python tools/art/build_meadow_flowers.py
|
||||
Each clump has one opaque vertex-colored surface and no textures. Coordinates
|
||||
are authored Y-up and converted for Blender; GLB export restores Godot Y-up.
|
||||
The .blend source files are editable and excluded from Godot import.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SOURCE = ROOT / "art/meadow"
|
||||
OUTPUT = ROOT / "assets/meadow"
|
||||
SOURCE.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def linear(hex_color):
|
||||
values = [int(hex_color[i:i + 2], 16) / 255 for i in (0, 2, 4)]
|
||||
return tuple(v / 12.92 if v <= 0.04045 else ((v + 0.055) / 1.055) ** 2.4 for v in values)
|
||||
|
||||
|
||||
def coord(point):
|
||||
return (point[0], -point[2], point[1])
|
||||
|
||||
|
||||
class FlowerMesh:
|
||||
def __init__(self):
|
||||
self.vertices = []
|
||||
self.faces = []
|
||||
self.colors = []
|
||||
|
||||
def face(self, points, color):
|
||||
first = len(self.vertices)
|
||||
self.vertices.extend(coord(point) for point in points)
|
||||
self.faces.append(tuple(range(first, first + len(points))))
|
||||
self.colors.extend([(*linear(color), 1.0)] * len(points))
|
||||
|
||||
def stem(self, base, top):
|
||||
bottom = Vector(base)
|
||||
tip = Vector(top)
|
||||
middle = bottom.lerp(tip, 0.55) + Vector((0.015, 0.0, -0.018))
|
||||
for start, end in ((bottom, middle), (middle, tip)):
|
||||
for side in range(4):
|
||||
a = side * math.tau / 4
|
||||
b = (side + 1) * math.tau / 4
|
||||
edge_a = Vector((math.cos(a), 0, math.sin(a))) * 0.009
|
||||
edge_b = Vector((math.cos(b), 0, math.sin(b))) * 0.009
|
||||
self.face((start + edge_a, start + edge_b, end + edge_b * 0.5, end + edge_a * 0.5), "54864b")
|
||||
|
||||
def leaf(self, base, angle, length=0.18, width=0.042):
|
||||
root = Vector(base)
|
||||
direction = Vector((math.cos(angle), 0.6, math.sin(angle))) * length
|
||||
side = Vector((-math.sin(angle), 0, math.cos(angle))) * width
|
||||
middle = root + direction * 0.52
|
||||
tip = root + direction
|
||||
# Raised crease gives a leaf silhouette from both ground and game camera.
|
||||
crease = middle + Vector((0, 0.028, 0))
|
||||
self.face((root, middle + side, crease), "6fa74f")
|
||||
self.face((middle + side, tip, crease), "80af58")
|
||||
self.face((root, crease, middle - side), "4f8548")
|
||||
self.face((middle - side, crease, tip), "679c4e")
|
||||
|
||||
def blossom(self, center, radius, petals, color, highlight, heart="e6b344"):
|
||||
center = Vector(center)
|
||||
for petal in range(petals):
|
||||
angle = petal * math.tau / petals + 0.12
|
||||
forward = Vector((math.cos(angle), 0, math.sin(angle)))
|
||||
side = Vector((-math.sin(angle), 0, math.cos(angle)))
|
||||
root = center + forward * radius * 0.12
|
||||
middle = center + forward * radius * 0.65 + Vector((0, radius * 0.09, 0))
|
||||
tip = center + forward * radius + Vector((0, radius * 0.24, 0))
|
||||
width = radius * (0.30 if petals < 7 else 0.23)
|
||||
self.face((root, middle + side * width, tip + side * width * 0.42), color)
|
||||
self.face((root, tip + side * width * 0.42, tip - side * width * 0.42), highlight)
|
||||
self.face((root, tip - side * width * 0.42, middle - side * width), color)
|
||||
# An opaque domed pollen heart rather than a texture or billboard.
|
||||
for side in range(8):
|
||||
a = side * math.tau / 8
|
||||
b = (side + 1) * math.tau / 8
|
||||
self.face((center + Vector((0, radius * 0.24, 0)),
|
||||
center + Vector((math.cos(a) * radius * 0.24, 0.008, math.sin(a) * radius * 0.24)),
|
||||
center + Vector((math.cos(b) * radius * 0.24, 0.008, math.sin(b) * radius * 0.24))), heart)
|
||||
|
||||
def build(self, name):
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
mesh.from_pydata(self.vertices, [], self.faces)
|
||||
mesh.update()
|
||||
attr = mesh.color_attributes.new(name="Color", type="FLOAT_COLOR", domain="CORNER")
|
||||
for index, loop in enumerate(mesh.loops):
|
||||
attr.data[index].color = self.colors[loop.vertex_index]
|
||||
mesh.materials.append(bpy.data.materials["MeadowPalette"])
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
|
||||
def clear_scene():
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
def create_material():
|
||||
material = bpy.data.materials.new("MeadowPalette")
|
||||
material.use_nodes = True
|
||||
material.use_backface_culling = False
|
||||
nodes = material.node_tree.nodes
|
||||
nodes.clear()
|
||||
color = nodes.new("ShaderNodeVertexColor")
|
||||
color.layer_name = "Color"
|
||||
surface = nodes.new("ShaderNodeBsdfPrincipled")
|
||||
surface.inputs["Roughness"].default_value = 1.0
|
||||
material.node_tree.links.new(color.outputs["Color"], surface.inputs["Base Color"])
|
||||
output = nodes.new("ShaderNodeOutputMaterial")
|
||||
material.node_tree.links.new(surface.outputs["BSDF"], output.inputs["Surface"])
|
||||
|
||||
|
||||
def flower(name, colors, petals, height, radius):
|
||||
clear_scene()
|
||||
builder = FlowerMesh()
|
||||
for index, (x, z, scale) in enumerate(((0, 0, 1.0), (-0.16, 0.1, 0.76), (0.16, -0.1, 0.88))):
|
||||
top = (x + 0.035 * math.cos(index * 2.2), height * scale, z + 0.04)
|
||||
builder.stem((x, 0, z), top)
|
||||
for leaf in range(3):
|
||||
y = height * scale * (0.17 + leaf * 0.16)
|
||||
builder.leaf((x, y, z), index * 2.1 + leaf * 2.45, 0.17 * scale, 0.034)
|
||||
if name == "lavender_spire":
|
||||
for tier in range(4):
|
||||
pos = (top[0], top[1] - tier * 0.086, top[2])
|
||||
builder.blossom(pos, radius * (0.58 + tier * 0.13), 5, *colors, heart="c7b3df")
|
||||
else:
|
||||
builder.blossom(top, radius * scale, petals, *colors)
|
||||
obj = builder.build(name)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=str(OUTPUT / (name + ".glb")), export_format="GLB",
|
||||
use_selection=True, export_yup=True, export_animations=False,
|
||||
export_vertex_color="ACTIVE", export_all_vertex_colors=False,
|
||||
export_materials="EXPORT", export_extras=False, export_cameras=False,
|
||||
)
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
area.spaces.active.shading.type = "MATERIAL"
|
||||
bpy.context.preferences.filepaths.save_version = 0
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(SOURCE / (name + ".blend")))
|
||||
obj.data.calc_loop_triangles()
|
||||
return {
|
||||
"triangles": len(obj.data.loop_triangles), "surfaces": 1,
|
||||
"flower_stems": 3, "bytes": (OUTPUT / (name + ".glb")).stat().st_size,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
clear_scene()
|
||||
create_material()
|
||||
specs = (
|
||||
("cream_daisy", ("f0e7c4", "fff4d8"), 8, 0.47, 0.125),
|
||||
("pink_cosmos", ("d780a7", "f1abc4"), 7, 0.64, 0.165),
|
||||
("yellow_buttercup", ("edc45a", "ffdf83"), 5, 0.36, 0.10),
|
||||
("lavender_spire", ("9585b8", "bfafd6"), 5, 0.79, 0.080),
|
||||
)
|
||||
stats = {spec[0]: flower(*spec) for spec in specs}
|
||||
(OUTPUT / "mesh_budget.json").write_text(json.dumps(stats, indent=2) + "\n")
|
||||
print(json.dumps(stats, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,267 @@
|
||||
"""Build original scalloped tree meshes, editable in Blender and portable to Godot.
|
||||
|
||||
Run: Blender --background --python tools/art/build_painterly_trees.py
|
||||
One crown surface and one joined trunk/branch surface; no textures or alpha cards.
|
||||
All coordinates use Blender Z-up and glTF exports them as Godot Y-up.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SOURCE = ROOT / "art/painterly"
|
||||
OUTPUT = ROOT / "assets/painterly"
|
||||
SOURCE.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
(SOURCE / ".gdignore").write_text("")
|
||||
RNG = random.Random(2741)
|
||||
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
def material(name):
|
||||
mat = bpy.data.materials.new(name)
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
shader = nodes.get("Principled BSDF")
|
||||
shader.inputs["Roughness"].default_value = 0.95
|
||||
shader.inputs["Specular IOR Level"].default_value = 0.0
|
||||
color = nodes.new("ShaderNodeVertexColor")
|
||||
color.layer_name = "Color"
|
||||
mat.node_tree.links.new(color.outputs["Color"], shader.inputs["Base Color"])
|
||||
return mat
|
||||
|
||||
|
||||
def mesh_object(name, vertices, faces, colors, mat):
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
mesh.from_pydata(vertices, [], faces)
|
||||
mesh.update()
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
attr = mesh.color_attributes.new(name="Color", type="FLOAT_COLOR", domain="POINT")
|
||||
for entry, color in zip(attr.data, colors):
|
||||
entry.color = (*color, 1.0)
|
||||
mesh.materials.append(mat)
|
||||
for polygon in mesh.polygons:
|
||||
polygon.use_smooth = True
|
||||
return obj
|
||||
|
||||
|
||||
def source_tint(mat, tint):
|
||||
"""Set an editing preview tint after portable glTF export."""
|
||||
nodes = mat.node_tree.nodes
|
||||
mix = nodes.new("ShaderNodeMixRGB")
|
||||
mix.blend_type = "MULTIPLY"
|
||||
mix.inputs[0].default_value = 1.0
|
||||
mix.inputs[2].default_value = (*tint, 1.0)
|
||||
mat.node_tree.links.new(nodes.get("Color Attribute").outputs["Color"], mix.inputs[1])
|
||||
mat.node_tree.links.new(mix.outputs[0], nodes.get("Principled BSDF").inputs["Base Color"])
|
||||
|
||||
|
||||
# Connected, irregular crown with broad painted lobes and small leafy tips.
|
||||
# Perturbing an envelope keeps the silhouette solid and removes internal overdraw.
|
||||
bumps = []
|
||||
for index in range(23):
|
||||
z = 1 - 2 * (index + 0.5) / 23
|
||||
angle = index * 2.399963 + 0.2
|
||||
direction = Vector((math.sqrt(1 - z * z) * math.cos(angle),
|
||||
math.sqrt(1 - z * z) * math.sin(angle), z))
|
||||
bumps.append((direction, RNG.uniform(0.12, 0.25)))
|
||||
|
||||
|
||||
def envelope(direction):
|
||||
ridges = sum(height * math.exp((direction.dot(axis) - 1) * 30)
|
||||
for axis, height in bumps)
|
||||
fine = math.sin(direction.x * 22 + direction.z * 9) * math.sin(direction.y * 19) * 0.018
|
||||
radius = 0.84 + ridges + fine
|
||||
return Vector((direction.x * radius, direction.y * radius * 0.95,
|
||||
direction.z * radius * 0.86))
|
||||
|
||||
|
||||
def pigment(point):
|
||||
broad = math.sin(point.x * 8 + point.z * 2) * math.sin(point.y * 7 - point.z * 5)
|
||||
light = 0.82 + 0.11 * broad + 0.07 * max(0, point.z)
|
||||
return (light * (1.0 + 0.035 * broad), light, light * (0.95 - 0.025 * broad))
|
||||
|
||||
|
||||
vertices, faces, colors = [], [], []
|
||||
segments, rings = 32, 16
|
||||
vertices.append(envelope(Vector((0, 0, 1))))
|
||||
for ring in range(1, rings):
|
||||
phi = math.pi * ring / rings
|
||||
for segment in range(segments):
|
||||
theta = math.tau * segment / segments
|
||||
direction = Vector((math.sin(phi) * math.cos(theta), math.sin(phi) * math.sin(theta), math.cos(phi)))
|
||||
vertices.append(envelope(direction))
|
||||
vertices.append(envelope(Vector((0, 0, -1))))
|
||||
for segment in range(segments):
|
||||
faces.append((0, 1 + segment, 1 + (segment + 1) % segments))
|
||||
for ring in range(rings - 2):
|
||||
for segment in range(segments):
|
||||
a = 1 + ring * segments + segment
|
||||
b = 1 + ring * segments + (segment + 1) % segments
|
||||
faces.append((a, a + segments, b + segments, b))
|
||||
last = len(vertices) - 1
|
||||
for segment in range(segments):
|
||||
faces.append((last, last - segments + (segment + 1) % segments, last - segments + segment))
|
||||
colors = [pigment(point) for point in vertices]
|
||||
|
||||
# Bent solid leaf tips give a scalloped edge at close range. They are tiny opaque
|
||||
# wedges with both sides, so they do not require cull-disabled or alpha materials.
|
||||
for axis, _height in bumps:
|
||||
for leaf in range(2):
|
||||
tangent = axis.cross(Vector((0, 0, 1)))
|
||||
if tangent.length < 0.01:
|
||||
tangent = Vector((1, 0, 0))
|
||||
tangent.normalize()
|
||||
sideways = axis.cross(tangent).normalized()
|
||||
direction = (axis + tangent * (leaf - 0.5) * 0.13).normalized()
|
||||
base = envelope(direction) * 0.98
|
||||
length = RNG.uniform(0.11, 0.17)
|
||||
width = RNG.uniform(0.04, 0.065)
|
||||
middle = base + direction * length * 0.55
|
||||
start = len(vertices)
|
||||
vertices.extend([base, middle + tangent * width, middle + sideways * width * 0.28,
|
||||
middle - tangent * width, base + direction * length])
|
||||
faces.extend([tuple(start + j for j in indices) for indices in
|
||||
((0, 1, 2), (0, 2, 3), (1, 4, 2), (2, 4, 3), (0, 3, 1), (1, 3, 4))])
|
||||
colors.extend([pigment(point) for point in vertices[-5:]])
|
||||
canopy = mesh_object("Canopy", vertices, faces, colors, material("CanopyPigment"))
|
||||
|
||||
# A crooked trunk, joined forks and roots use one surface. Colors are linear,
|
||||
# with grey warm bark, moss at the base and subtle broad plane variation.
|
||||
vertices, faces, colors = [], [], []
|
||||
|
||||
|
||||
def tube(points, radii, sides=9):
|
||||
start = len(vertices)
|
||||
for index, raw in enumerate(points):
|
||||
point = Vector(raw)
|
||||
tangent = Vector(points[min(index + 1, len(points) - 1)]) - Vector(points[max(0, index - 1)])
|
||||
tangent.normalize()
|
||||
side = tangent.cross(Vector((0, 1, 0))).normalized()
|
||||
other = tangent.cross(side).normalized()
|
||||
for segment in range(sides):
|
||||
angle = math.tau * segment / sides
|
||||
offset = (math.cos(angle) * side + math.sin(angle) * other) * radii[index]
|
||||
vertices.append(point + offset)
|
||||
shade = 0.90 + math.sin(angle * 3 + 0.6) * 0.12
|
||||
moss = max(0, 1 - point.z / 0.55) * (0.5 + 0.5 * math.cos(angle))
|
||||
colors.append(((0.20 - moss * 0.035) * shade, (0.13 + moss * 0.01) * shade,
|
||||
(0.068 - moss * 0.005) * shade))
|
||||
if index:
|
||||
for segment in range(sides):
|
||||
a = start + (index - 1) * sides + segment
|
||||
b = start + (index - 1) * sides + (segment + 1) % sides
|
||||
faces.append((a, b, b + sides, a + sides))
|
||||
faces.append(tuple(reversed(range(start, start + sides))))
|
||||
faces.append(tuple(range(len(vertices) - sides, len(vertices))))
|
||||
|
||||
|
||||
tube([(0, 0, 0), (-0.07, 0.025, 0.22), (0.02, 0.0, 1.0), (-0.10, 0.025, 1.85),
|
||||
(0.03, 0.02, 2.62), (0.22, -0.04, 3.48)], [0.38, 0.27, 0.23, 0.20, 0.14, 0.025])
|
||||
tube([(-0.06, 0, 1.70), (-0.36, 0.04, 2.35), (-0.84, 0.03, 2.83), (-1.05, 0.09, 3.23)],
|
||||
[0.18, 0.13, 0.075, 0.012], 7)
|
||||
tube([(0.00, 0, 2.10), (0.39, 0.12, 2.48), (0.81, 0.18, 2.94), (1.00, 0.25, 3.29)],
|
||||
[0.14, 0.10, 0.055, 0.012], 7)
|
||||
tube([(-0.06, 0.025, 2.34), (-0.22, -0.38, 2.83), (-0.35, -0.62, 3.26)],
|
||||
[0.10, 0.058, 0.012], 7)
|
||||
for index in range(5):
|
||||
angle = index * math.tau / 5 + 0.2
|
||||
tube([(math.cos(angle) * 0.13, math.sin(angle) * 0.13, 0.30),
|
||||
(math.cos(angle) * 0.36, math.sin(angle) * 0.36, 0.08),
|
||||
(math.cos(angle + 0.12) * 0.66, math.sin(angle + 0.12) * 0.66, -0.015)],
|
||||
[0.12, 0.085, 0.012], 6)
|
||||
trunk = mesh_object("Trunk", vertices, faces, colors, material("BarkPigment"))
|
||||
|
||||
# Export object origins at zero; runtime scales/repositions four shared crowns.
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.export_scene.gltf(filepath=str(OUTPUT / "painterly_tree.glb"), export_format="GLB",
|
||||
use_selection=True, export_yup=True, export_animations=False,
|
||||
export_vertex_color="ACTIVE", export_all_vertex_colors=False,
|
||||
export_materials="EXPORT", export_extras=False, export_cameras=False)
|
||||
|
||||
# Display a complete tree in the editable source without baking placement into GLB.
|
||||
source_tint(canopy.data.materials[0], (0.22, 0.42, 0.10))
|
||||
canopy.location.z = 3.8
|
||||
canopy.scale = (1.8, 1.55, 1.4)
|
||||
for location, scale in (((-0.85, 0.05, 3.55), (1.28, 1.22, 1.15)),
|
||||
((0.87, -0.08, 3.67), (1.27, 1.15, 1.1)),
|
||||
((0.05, 0, 4.46), (1.20, 1.08, 1.0))):
|
||||
crown = canopy.copy()
|
||||
crown.data = canopy.data
|
||||
bpy.context.collection.objects.link(crown)
|
||||
crown.location = location
|
||||
crown.scale = scale
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
area.spaces.active.shading.type = "MATERIAL"
|
||||
bpy.context.preferences.filepaths.save_version = 0
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(SOURCE / "painterly_tree.blend"))
|
||||
|
||||
budget = {}
|
||||
for obj in (canopy, trunk):
|
||||
obj.data.calc_loop_triangles()
|
||||
budget[obj.name] = {"triangles": len(obj.data.loop_triangles), "surfaces": 1,
|
||||
"vertices": len(obj.data.vertices)}
|
||||
budget["runtime_tree"] = {"triangles": budget["Canopy"]["triangles"] * 4 + budget["Trunk"]["triangles"],
|
||||
"surfaces": 5, "shared_meshes": 2}
|
||||
budget["glb_bytes"] = (OUTPUT / "painterly_tree.glb").stat().st_size
|
||||
(OUTPUT / "tree_mesh_budget.json").write_text(json.dumps(budget, indent=2) + "\n")
|
||||
print("PAINTERLY TREE BUDGET", json.dumps(budget))
|
||||
|
||||
# Narrow evergreen boughs, scalloped skirts and an uneven leader. One opaque
|
||||
# surface is joined across eight whorls, preserving a restrained draw budget.
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
vertices, faces, colors = [], [], []
|
||||
segments = 24
|
||||
for tier in range(8):
|
||||
height = 0.64 + tier * 0.27
|
||||
radius = 0.62 * (1 - tier / 8.8)
|
||||
start = len(vertices)
|
||||
angle_offset = tier * 0.61
|
||||
lean_x = math.sin(tier * 0.8) * 0.035
|
||||
lean_y = math.cos(tier * 1.1) * 0.025
|
||||
for ring, (radial, lift) in enumerate(((0.78, 0.04), (1.0, 0.08), (0.63, 0.25), (0.12, 0.57))):
|
||||
for segment in range(segments):
|
||||
angle = math.tau * segment / segments + angle_offset
|
||||
tip = (1 + math.cos(angle * 8 + tier)) * 0.5
|
||||
organic = math.sin(angle * 3 + tier) * 0.08 + math.sin(angle * 5 - tier * 2) * 0.035
|
||||
r = radius * radial * (0.84 + tip * 0.16 + organic)
|
||||
z = height + lift - tip * (0.085 if ring < 2 else 0.022)
|
||||
vertices.append((lean_x + math.cos(angle) * r, lean_y + math.sin(angle) * r, z))
|
||||
pigment = 0.72 + tier * 0.015 + ring * 0.05 + tip * 0.045
|
||||
colors.append((pigment * 0.94, pigment, pigment * 0.93))
|
||||
if ring:
|
||||
a = start + (ring - 1) * segments + segment
|
||||
b = start + (ring - 1) * segments + (segment + 1) % segments
|
||||
faces.append((a, b, b + segments, a + segments))
|
||||
faces.append(tuple(reversed(range(start, start + segments))))
|
||||
faces.append(tuple(range(len(vertices) - segments, len(vertices))))
|
||||
conifer = mesh_object("ConiferCanopy", vertices, faces, colors, material("EvergreenPigment"))
|
||||
vertices, faces, colors = [], [], []
|
||||
tube([(0, 0, 0), (0.015, -0.01, 0.48), (-0.02, 0, 1.5), (0, 0, 2.58), (0.01, 0, 2.84)],
|
||||
[0.10, 0.07, 0.045, 0.02, 0.004], 8)
|
||||
conifer_trunk = mesh_object("ConiferTrunk", vertices, faces, colors, material("EvergreenBark"))
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.export_scene.gltf(filepath=str(OUTPUT / "painterly_conifer.glb"), export_format="GLB",
|
||||
use_selection=True, export_yup=True, export_animations=False,
|
||||
export_vertex_color="ACTIVE", export_all_vertex_colors=False,
|
||||
export_materials="EXPORT", export_extras=False, export_cameras=False)
|
||||
source_tint(conifer.data.materials[0], (0.10, 0.28, 0.17))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(SOURCE / "painterly_conifer.blend"))
|
||||
for obj in (conifer, conifer_trunk):
|
||||
obj.data.calc_loop_triangles()
|
||||
budget["conifer"] = {"triangles": sum(len(o.data.loop_triangles) for o in (conifer, conifer_trunk)),
|
||||
"surfaces": 2, "height_m": 3.10,
|
||||
"glb_bytes": (OUTPUT / "painterly_conifer.glb").stat().st_size}
|
||||
(OUTPUT / "tree_mesh_budget.json").write_text(json.dumps(budget, indent=2) + "\n")
|
||||
print("CONIFER BUDGET", json.dumps(budget["conifer"]))
|
||||
Reference in New Issue
Block a user