In phase 5
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user