diff --git a/korman/exporter/explosions.py b/korman/exporter/explosions.py index 715680b..331c173 100644 --- a/korman/exporter/explosions.py +++ b/korman/exporter/explosions.py @@ -29,9 +29,9 @@ class ExportAssertionError(ExportError): class TooManyUVChannelsError(ExportError): - def __init__(self, obj, mat): - msg = "There are too many UV Textures on the material '{}' associated with object '{}'.".format( - mat.name, obj.name) + def __init__(self, obj, mat, numUVTexs, maxUVTexCount=8): + msg = "There are too many UV Textures on the material '{}' associated with object '{}'. You can have at most {} (there are {})".format( + mat.name, obj.name, maxUVTexCount, numUVTexs) super(ExportError, self).__init__(msg) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 5ac85d3..3a792af 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -109,7 +109,7 @@ class _Texture: class MaterialConverter: def __init__(self, exporter): self._obj2mat = {} - self._bumpMats = {} + self._bump_mats = {} self._exporter = weakref.ref(exporter) self._pending = {} self._alphatest = {} @@ -147,7 +147,7 @@ class MaterialConverter: for idx, slot in slots: # Prepend any BumpMapping magic layers if slot.use_map_normal: - if bo in self._bumpMats: + if bo in self._bump_mats: raise ExportError("Material '{}' has more than one bumpmap layer".format(bm.name)) du, dw, dv = self.export_bumpmap_slot(bo, bm, hsgmat, slot, idx) hsgmat.addLayer(du.key) # Du @@ -163,7 +163,7 @@ class MaterialConverter: restart_pass_next = False hsgmat.addLayer(tex_layer.key) if slot.use_map_normal: - self._bumpMats[bo] = (tex_layer.UVWSrc, tex_layer.transform) + self._bump_mats[bo] = (tex_layer.UVWSrc, tex_layer.transform) # After a bumpmap layer(s), the next layer *must* be in a # new pass, otherwise it gets added in non-intuitive ways restart_pass_next = True @@ -682,7 +682,7 @@ class MaterialConverter: return self._obj2mat.get(bo, []) def get_bump_layer(self, bo): - return self._bumpMats.get(bo, None) + return self._bump_mats.get(bo, None) def get_texture_animation_key(self, bo, bm, tex_name=None, tex_slot=None): """Finds or creates the appropriate key for sending messages to an animated Texture""" diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 15cfa52..98fb63c 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -119,6 +119,24 @@ class MeshConverter: self._dspans = {} self._mesh_geospans = {} + def _calc_num_uvchans(self, bo, mesh): + max_user_texs = plGeometrySpan.kUVCountMask + num_user_texs = len(mesh.tessface_uv_textures) + total_texs = num_user_texs + + # Bump Mapping requires 2 magic channels + if self.material.get_bump_layer(bo) is not None: + total_texs += 2 + max_user_texs -= 2 + + # Lightmapping requires its own LIGHTMAPGEN channel + # NOTE: the LIGHTMAPGEN texture has already been created, so it is in num_user_texs + if bo.plasma_modifiers.lightmap.enabled: + num_user_texs -= 1 + max_user_texs -= 1 + + return (num_user_texs, total_texs, max_user_texs) + def _create_geospan(self, bo, mesh, bm, hsgmatKey): """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial""" geospan = plGeometrySpan() @@ -126,10 +144,10 @@ class MeshConverter: # GeometrySpan format # For now, we really only care about the number of UVW Channels - numUVWchans = len(mesh.tessface_uv_textures) - if numUVWchans > plGeometrySpan.kUVCountMask: - raise explosions.TooManyUVChannelsError(bo, bm) - geospan.format = numUVWchans + user_uvws, total_uvws, max_user_uvws = self._calc_num_uvchans(bo, mesh) + if total_uvws > plGeometrySpan.kUVCountMask: + raise explosions.TooManyUVChannelsError(bo, bm, user_uvws, max_user_uvws) + geospan.format = total_uvws # Begin total guesswork WRT flags mods = bo.plasma_modifiers @@ -175,7 +193,7 @@ class MeshConverter: def _export_geometry(self, bo, mesh, materials, geospans): geodata = [_GeoData(len(mesh.vertices)) for i in materials] - has_bumpmap = self.material.get_bump_layer(bo) + bumpmap = self.material.get_bump_layer(bo) # Locate relevant vertex color layers now... color, alpha = None, None @@ -218,7 +236,7 @@ class MeshConverter: ((src.color3[0] + src.color3[1] + src.color3[2]) / 3), ((src.color4[0] + src.color4[1] + src.color4[2]) / 3)) - if has_bumpmap is not None: + if bumpmap is not None: gradPass = [] gradUVWs = [] @@ -238,8 +256,8 @@ class MeshConverter: tuple((uvw[2] for uvw in tessface_uvws)))) for p, vids in enumerate(gradPass): - dPosDu += self._get_bump_gradient(has_bumpmap[1], gradUVWs[p], mesh, vids, has_bumpmap[0], 0) - dPosDv += self._get_bump_gradient(has_bumpmap[1], gradUVWs[p], mesh, vids, has_bumpmap[0], 1) + dPosDu += self._get_bump_gradient(bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 0) + dPosDv += self._get_bump_gradient(bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 1) dPosDv = -dPosDv # Convert to per-material indices @@ -267,21 +285,31 @@ class MeshConverter: geoVertex.normal = hsVector3(*tessface.normal) geoVertex.color = hsColor32(*vertex_color) - geoVertex.uvs = [hsVector3(uv[0], 1.0 - uv[1], 0.0) for uv in uvws] - - data.blender2gs[vertex][coluv] = len(data.vertices) + uvs = [hsVector3(uv[0], 1.0 - uv[1], 0.0) for uv in uvws] + if bumpmap is not None: + uvs.append(dPosDu) + uvs.append(dPosDv) + geoVertex.uvs = uvs + + idx = len(data.vertices) + data.blender2gs[vertex][coluv] = idx data.vertices.append(geoVertex) - - face_verts.append(data.blender2gs[vertex][coluv]) - - if has_bumpmap is not None: - idx = len(uvws) - geoVert = data.vertices[data.blender2gs[vertex][coluv]] - # We can't edit in place :\ - uvMaps = geoVert.uvs - uvMaps[idx] += dPosDu - uvMaps[idx + 1] += dPosDv - geoVert.uvs = uvMaps + face_verts.append(idx) + else: + # If we have a bump mapping layer, then we need to add the bump gradients for + # this face to the vertex's magic channels + if bumpmap is not None: + num_user_uvs = len(uvws) + geoVertex = data.vertices[data.blender2gs[vertex][coluv]] + + # Unfortunately, PyHSPlasma returns a copy of everything. Previously, editing + # in place would result in silent failures; however, as of python_refactor, + # PyHSPlasma now returns tuples to indicate this. + geoUVs = list(geoVertex.uvs) + geoUVs[num_user_uvs] += dPosDu + geoUVs[num_user_uvs+1] += dPosDv + geoVertex.uvs = geoUVs + face_verts.append(data.blender2gs[vertex][coluv]) # Convert to triangles, if need be... if len(face_verts) == 3: @@ -294,6 +322,7 @@ class MeshConverter: for i, data in enumerate(geodata): geospan = geospans[i][0] numVerts = len(data.vertices) + numUVs = geospan.format & plGeometrySpan.kUVCountMask # There is a soft limit of 0x8000 vertices per span in Plasma, but the limit is # theoretically 0xFFFF because this field is a 16-bit integer. However, bad things @@ -305,12 +334,11 @@ class MeshConverter: raise explosions.TooManyVerticesError(bo.data.name, geospan.material.name, numVerts) # If we're bump mapping, we need to normalize our magic UVW channels - if has_bumpmap is not None: - geospan.format += 2 # We dded 2 special bumpmapping UV layers + if bumpmap is not None: for vtx in data.vertices: uvMap = vtx.uvs - uvMap[geospan.format - 2].normalize() - uvMap[geospan.format - 1].normalize() + uvMap[numUVs - 2].normalize() + uvMap[numUVs - 1].normalize() vtx.uvs = uvMap # If we're still here, let's add our data to the GeometrySpan @@ -328,18 +356,18 @@ class MeshConverter: uv2 = (uvws[2][uvIdx][0], uvws[2][uvIdx][1], 0.0) notUV = int(not iUV) - kRealSmall = 1.e-6 + _REAL_SMALL = 0.000001 delta = uv0[notUV] - uv1[notUV] - if fabs(delta) < kRealSmall: + if fabs(delta) < _REAL_SMALL: return v1 - v0 if uv0[iUV] - uv1[iUV] < 0 else v0 - v1 delta = uv2[notUV] - uv1[notUV] - if fabs(delta) < kRealSmall: + if fabs(delta) < _REAL_SMALL: return v1 - v2 if uv2[iUV] - uv1[iUV] < 0 else v2 - v1 delta = uv2[notUV] - uv0[notUV] - if fabs(delta) < kRealSmall: + if fabs(delta) < _REAL_SMALL: return v0 - v2 if uv2[iUV] - uv0[iUV] < 0 else v2 - v0 # On to the real fun...