diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 6ddcdaf..2cf6208 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -109,6 +109,7 @@ class _Texture: class MaterialConverter: def __init__(self, exporter): self._obj2mat = {} + self._bumpMats = {} self._exporter = weakref.ref(exporter) self._pending = {} self._alphatest = {} @@ -143,7 +144,17 @@ class MaterialConverter: # Loop over layers for idx, slot in slots: - if slot.use_stencil: + if slot.use_map_normal: + if bo in self._bumpMats: + raise ExportError("Material '{}' has more than one bumpmap layer".format(bm.name)) + + bump_layers = self.export_bumpmap_slot(bo, bm, hsgmat, slot, idx) + hsgmat.addLayer(bump_layers[0].key) # Du + hsgmat.addLayer(bump_layers[1].key) # Dw + hsgmat.addLayer(bump_layers[2].key) # Dv + hsgmat.addLayer(bump_layers[3].key) # Normal map + self._bumpMats[bo] = (bump_layers[3].UVWSrc, bump_layers[3].transform) + elif slot.use_stencil: stencils.append((idx, slot)) else: tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx) @@ -194,6 +205,74 @@ class MaterialConverter: # Wasn't that easy? return hsgmat.key + + def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx): + name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name) + print(" Exporting Plasma Bumpmap Layer '{}'".format(name)) + + # The normal map layer + nm_layer = self._mgr.add_object(plLayer, name=name, bl=bo) + + # UVW Channel + if slot.texture_coords == "UV": + for i, uvchan in enumerate(bo.data.uv_layers): + if uvchan.name == slot.uv_layer: + nm_layer.UVWSrc = i + print(" Using UV Map #{} '{}'".format(i, name)) + break + else: + print(" No UVMap specified... Blindly using the first one, maybe it exists :|") + + # Transform + xform = hsMatrix44() + xform.setTranslate(hsVector3(*slot.offset)) + xform.setScale(hsVector3(*slot.scale)) + nm_layer.transform = xform + + state = nm_layer.state + state.blendFlags = hsGMatState.kBlendDot3 + state.miscFlags = hsGMatState.kMiscBumpLayer + + nm_layer.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0) + nm_layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0) + nm_layer.runtime = hsColorRGBA(1.0, 0.0, 0.0, 1.0) # Solid Red + nm_layer.specular = hsColorRGBA(0.0, 0.0, 0.0, 1.0) + + texture = slot.texture + + # Export the specific texture type + self._tex_exporters[texture.type](bo, nm_layer, slot) + + + # Okay, now we need to make 3 layers for the Du, Dw, and Dv + du_layer = self._mgr.add_object(plLayer, name="{}_DU_BumpLut".format(name), bl=bo) + dw_layer = self._mgr.add_object(plLayer, name="{}_DW_BumpLut".format(name), bl=bo) + dv_layer = self._mgr.add_object(plLayer, name="{}_DV_BumpLut".format(name), bl=bo) + + for layer in [du_layer, dw_layer, dv_layer]: + layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0) + layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0) + layer.runtime = hsColorRGBA(0.0, 0.0, 0.0, 1.0) + layer.specular = hsColorRGBA(0.0, 0.0, 0.0, 1.0) + + layer.state.ZFlags = hsGMatState.kZNoZWrite + layer.state.clampFlags = hsGMatState.kClampTexture + layer.state.miscFlags = hsGMatState.kMiscBindNext + layer.state.blendFlags = hsGMatState.kBlendAdd + + du_layer.state.blendFlags = hsGMatState.kBlendMADD + du_layer.state.miscFlags |= hsGMatState.kMiscBumpDu | hsGMatState.kMiscRestartPassHere + dw_layer.state.miscFlags |= hsGMatState.kMiscBumpDw + dv_layer.state.miscFlags |= hsGMatState.kMiscBumpDv + + du_uv = len(bo.data.uv_layers) + du_layer.UVWSrc = du_uv + dw_layer.UVWSrc = du_uv | plLayerInterface.kUVWNormal + dv_layer.UVWSrc = du_uv + 1 + + return (du_layer, dw_layer, dv_layer, nm_layer) + + def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True): if name is None: name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name) @@ -476,7 +555,7 @@ class MaterialConverter: # First, let's apply any relevant flags state = layer.state - if not slot.use_stencil: + if not slot.use_stencil and not slot.use_map_normal: # mutually exclusive blend flags if texture.use_alpha and has_alpha: if slot.blend_type == "ADD": @@ -605,6 +684,9 @@ class MaterialConverter: def get_materials(self, bo): return self._obj2mat.get(bo, []) + def has_bump_layer(self, bo): + return self._bumpMats.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""" assert tex_name or tex_slot diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 204806d..18ff54d 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -15,6 +15,7 @@ import bpy from PyHSPlasma import * +from math import fabs import weakref from . import explosions @@ -174,6 +175,7 @@ class MeshConverter: def _export_geometry(self, bo, mesh, materials, geospans): geodata = [_GeoData(len(mesh.vertices)) for i in materials] + has_bumpmap = self.material.has_bump_layer(bo) # Locate relevant vertex color layers now... color, alpha = None, None @@ -191,6 +193,8 @@ class MeshConverter: data = geodata[tessface.material_index] face_verts = [] use_smooth = tessface.use_smooth + dPosDu = hsVector3(0.0, 0.0, 0.0) + dPosDv = hsVector3(0.0, 0.0, 0.0) # Unpack the UV coordinates from each UV Texture layer # NOTE: Blender has no third (W) coordinate @@ -214,6 +218,24 @@ 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: + gradPass = [] + gradUVWs = [] + + if len(tessface.vertices) != 3: + gradPass.append([tessface.vertices[0], tessface.vertices[1], tessface.vertices[2]]) + gradPass.append([tessface.vertices[0], tessface.vertices[2], tessface.vertices[3]]) + gradUVWs.append([tuple([uvw[0] for uvw in tessface_uvws]), tuple([uvw[1] for uvw in tessface_uvws]), tuple([uvw[2] for uvw in tessface_uvws])]) + gradUVWs.append([tuple([uvw[0] for uvw in tessface_uvws]), tuple([uvw[2] for uvw in tessface_uvws]), tuple([uvw[3] for uvw in tessface_uvws])]) + else: + gradPass.append(tessface.vertices) + gradUVWs.append([tuple([uvw[0] for uvw in tessface_uvws]), tuple([uvw[1] for uvw in tessface_uvws]), 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) + dPosDv = -dPosDv + # Convert to per-material indices for j, vertex in enumerate(tessface.vertices): uvws = tuple([uvw[j] for uvw in tessface_uvws]) @@ -240,10 +262,21 @@ class MeshConverter: 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) 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 + # Convert to triangles, if need be... if len(face_verts) == 3: data.triangles += face_verts @@ -265,10 +298,74 @@ class MeshConverter: if numVerts > _WARN_VERTS_PER_SPAN: 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 + for v,vtx in enumerate(data.vertices): + uvMap = vtx.uvs + uvMap[geospan.format - 2].normalize() + uvMap[geospan.format - 1].normalize() + vtx.uvs = uvMap + # If we're still here, let's add our data to the GeometrySpan geospan.indices = data.triangles geospan.vertices = data.vertices + + def _get_bump_gradient(self, xform, uvws, mesh, vIds, uvIdx, iUV): + v0 = hsVector3(*mesh.vertices[vIds[0]].co) + v1 = hsVector3(*mesh.vertices[vIds[1]].co) + v2 = hsVector3(*mesh.vertices[vIds[2]].co) + + uv0 = [(uv[0], uv[1], 0.0) for uv in uvws[0]][uvIdx] + uv1 = [(uv[0], uv[1], 0.0) for uv in uvws[1]][uvIdx] + uv2 = [(uv[0], uv[1], 0.0) for uv in uvws[2]][uvIdx] + + notUV = int(not iUV) + kRealSmall = 1.e-6 + + delta = uv0[notUV] - uv1[notUV] + if fabs(delta) < kRealSmall: + if uv0[iUV] - uv1[iUV] < 0: + return v1 - v0 + else: + return v0 - v1 + + delta = uv2[notUV] - uv1[notUV] + if fabs(delta) < kRealSmall: + if uv2[iUV] - uv1[iUV] < 0: + return v1 - v2 + else: + return v2 - v1 + + delta = uv2[notUV] - uv0[notUV] + if fabs(delta) < kRealSmall: + if uv2[iUV] - uv0[iUV] < 0: + return v0 - v2 + else: + return v2 - v0 + + + # On to the real fun... + delta = uv0[notUV] - uv1[notUV] + delta = 1.0 / delta + v0Mv1 = v0 - v1 + v0Mv1 *= delta + v0uv = (uv0[iUV] - uv1[iUV]) * delta + + delta = uv2[notUV] - uv1[notUV] + delta = 1.0 / delta + v2Mv1 = v2 - v1 + v2Mv1 *= delta + v2uv = (uv2[iUV] - uv1[iUV]) * delta + + if v0uv > v2uv: + return v0Mv1 - v2Mv1 + else: + return v2Mv1 - v0Mv1 + + + def export_object(self, bo): # If this object has modifiers, then it's a unique mesh, and we don't need to try caching it # Otherwise, let's *try* to share meshes as best we can...