In phase 5

This commit is contained in:
2026-10-16 15:24:34 +03:00
parent e58c061709
commit 136013440e
24 changed files with 6001 additions and 1761 deletions
+238
View File
@@ -275,6 +275,149 @@ for m in used_mats:
print("gen_vehicle: unpacked texture '%s' -> %s" %
(img.name, tex_name))
# --- merged single material + ID map (CAR_MODELING.md) ---
# Every mesh part is re-materialized onto a single material named
# <base> so blender2ogre emits ONE submesh per part, and the exporter's
# fixed-function .material is replaced with the VehicleIdMap shader
# materials after the export. The ID map texture shares the color
# atlas UV space (all parts MUST use that one UV space with no texel
# shared between materials; fix the blend if not) and marks each
# fragment: red = paint (atlas color), green = bloom (lights),
# blue = glass.
IDMAP_SIZE = 512
def idmap_color(mat):
ln = mat.name.lower() if mat else ""
if "glass" in ln:
return (0, 0, 255)
if "bloom" in ln:
return (0, 255, 0)
return (255, 0, 0)
def bake_idmap(mesh_datas, path):
"""Paint per-face material IDs into a texture in UV space.
The car UVs are degenerate: every polygon collapses onto a single
color-atlas texel (the atlas is a palette, not a surface unwrap),
so the ID map only needs that texel plus a small margin painted.
A texel shared by two different materials is unresolvable and is
warned about (fix the blend: give one of them its own texel)."""
size = IDMAP_SIZE
buf = bytearray(size * size * 3)
owner = {}
warned = set()
def paint(u, v, col, matname):
u = u - math.floor(u)
v = v - math.floor(v)
cx, cy = int(u * (size - 1)), int(v * (size - 1))
for dy in range(-2, 3):
for dx in range(-2, 3):
if dx * dx + dy * dy > 5:
continue
px, py = cx + dx, cy + dy
if not (0 <= px < size and 0 <= py < size):
continue
idx = py * size + px
prev = owner.get(idx)
if prev is not None and prev != matname:
pair = (prev, matname)
if pair not in warned:
warned.add(pair)
print("gen_vehicle: WARNING - UV texel (%d,%d) "
"shared by materials '%s' and '%s'; the "
"ID map cannot tell them apart (fix the "
"blend)" % (px, py, prev, matname))
continue
owner[idx] = matname
buf[idx * 3] = col[0]
buf[idx * 3 + 1] = col[1]
buf[idx * 3 + 2] = col[2]
for me in mesh_datas:
uv_layer = me.uv_layers.active
if not uv_layer:
continue
uv = uv_layer.data
for poly in me.polygons:
mat = (me.materials[poly.material_index]
if poly.material_index < len(me.materials) else None)
puv = uv[poly.loop_indices[0]].uv
paint(puv.x, puv.y, idmap_color(mat),
mat.name if mat else "<none>")
img = bpy.data.images.new(
os.path.splitext(os.path.basename(path))[0],
width=size, height=size, alpha=False)
px = [0.0] * (size * size * 4)
for i in range(size * size):
px[i * 4] = buf[i * 3] / 255.0
px[i * 4 + 1] = buf[i * 3 + 1] / 255.0
px[i * 4 + 2] = buf[i * 3 + 2] / 255.0
px[i * 4 + 3] = 1.0
img.pixels = px
img.filepath_raw = path
img.file_format = 'PNG'
img.save()
bpy.data.images.remove(img)
print("gen_vehicle: wrote ID map %s (%d texels)" %
(path, len(owner)))
def move_glass_faces_last():
"""Re-order each mesh so glass faces export last: with the merged
transparent material (depth_write on) the cabin stays visible
through the windows only if glass triangles draw after the
interior."""
for obj in bpy.data.objects:
if obj.type != 'MESH':
continue
me = obj.data
glass_slots = [i for i, m in enumerate(me.materials)
if m and "glass" in m.name.lower()]
if not glass_slots:
continue
order = [i for i in range(len(me.materials))
if i not in glass_slots] + glass_slots
mats = [me.materials[i] for i in order]
remap = dict((old, new) for new, old in enumerate(order))
# materials.clear() zeroes every polygon's material_index, so
# save them first
saved = [p.material_index for p in me.polygons]
me.materials.clear()
for m in mats:
me.materials.append(m)
for poly, old_idx in zip(me.polygons, saved):
poly.material_index = remap[old_idx]
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.sort_elements(type='MATERIAL', elements={'FACE'})
bpy.ops.object.mode_set(mode='OBJECT')
obj.select_set(False)
print("gen_vehicle: sorted glass faces last on '%s'" % me.name)
all_mesh_datas = []
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.data not in all_mesh_datas:
all_mesh_datas.append(obj.data)
move_glass_faces_last()
bake_idmap(all_mesh_datas, os.path.join(outdir, base + "-idmap.png"))
merged_mat = bpy.data.materials.new(base)
merged_mat.use_nodes = True
for me in all_mesh_datas:
me.materials.clear()
me.materials.append(merged_mat)
print("gen_vehicle: consolidated %d mesh data blocks onto material "
"'%s'" % (len(all_mesh_datas), base))
# --- JSON fragment (Ogre coordinates) ---
smn, smx = mesh_bbox(root.data)
collider = {
@@ -511,6 +654,101 @@ bpy.ops.ogre.export(filepath=scene_file,
EX_LOD_PERCENT=40
)
# --- replace the exporter's fixed-function .material with the merged
# VehicleIdMap shader materials (CAR_MODELING.md): "<base>" is the
# transparent near variant, "<base>-opaque" the far variant; the
# runtime swaps them by camera distance (SedanGlassLodListener).
# Both reference the VehicleIdMapVP/FP programs staged in the same
# resource dir (vehicle_idmap.program, CMake-staged from
# assets/blender/vehicles). ---
IDMAP_MATERIAL_TEMPLATE = """// written by gen_vehicle.py — merged ID-map vehicle material
// (CAR_MODELING.md). Not a blender2ogre product; hand edits are
// overwritten by every export.
material @BASE@
{
receive_shadows on
technique
{
pass
{
cull_hardware clockwise
depth_write on
scene_blend alpha_blend
vertex_program_ref VehicleIdMapVP
{
}
fragment_program_ref VehicleIdMapFP
{
param_named glassAlpha float 0.25
}
texture_unit
{
texture @BASE@-atlas.png
tex_address_mode wrap
filtering linear linear none
}
texture_unit
{
texture @BASE@-idmap.png
tex_address_mode clamp
filtering point point none
}
}
}
}
material @BASE@-opaque
{
receive_shadows on
technique
{
pass
{
cull_hardware clockwise
vertex_program_ref VehicleIdMapVP
{
}
fragment_program_ref VehicleIdMapFP
{
param_named glassAlpha float 1.0
}
texture_unit
{
texture @BASE@-atlas.png
tex_address_mode wrap
filtering linear linear none
}
texture_unit
{
texture @BASE@-idmap.png
tex_address_mode clamp
filtering point point none
}
}
}
}
"""
mat_path = os.path.join(outdir, base + ".material")
with open(mat_path, "w") as f:
f.write(IDMAP_MATERIAL_TEMPLATE.replace("@BASE@", base))
print("gen_vehicle: wrote merged material %s" % mat_path)
# Drop the stale split-material files from pre-ID-map exports so the
# resource dir does not accumulate them.
for suffix in ("-color", "-color-bloom", "-glass"):
stale = os.path.join(outdir, base + suffix + ".material")
if os.path.exists(stale):
os.remove(stale)
bpy.ops.wm.read_homefile(use_empty=True)
import time
time.sleep(2)