meshValidation.py (8533B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\evegraphics\validation\validators\meshValidation.py 2 import os 3 import blue 4 import trinity 5 from trinity import effects 6 from evegraphics.validation.commonUtilities import Validate 7 from evegraphics.validation.validationFunctions import ValidateResPath 8 from evegraphics.validation import resources 9 from evegraphics.validation.validators.effectValidation import effectCache 10 MAX_BONES_PER_MESH = 58 11 12 def _GetGranny(context, resPath): 13 return resources.GetResource(context, resPath, resources.ResourceType.GRANNY) 14 15 16 @Validate('Tr2Mesh') 17 def ValidResPath(context, mesh): 18 ValidateResPath(context, mesh, 'geometryResPath', extensions=('.gr2',)) 19 20 21 @Validate('Tr2InstancedMesh') 22 def ValidInstanceResPath(context, mesh): 23 if mesh.instanceGeometryResPath: 24 ValidateResPath(context, mesh, 'instanceGeometryResPath', extensions=('.gr2',)) 25 elif not mesh.instanceGeometryResource: 26 context.Error(mesh, 'instanced mesh has no instance geometry resource') 27 28 29 @Validate('Tr2Mesh') 30 def ValidMeshIndex(context, mesh): 31 granny = _GetGranny(context, mesh.geometryResPath) 32 if not granny: 33 return 34 if mesh.meshIndex < 0 or mesh.meshIndex >= len(granny.meshes): 35 context.Error(mesh, 'mesh index is out of range (should be between 0 and %s)' % (len(granny.meshes) - 1)) 36 37 38 def _DeleteObject(context, default): 39 obj = context.GetTop() 40 root = context.GetStack()[0] 41 42 def inner(): 43 for route in blue.FindRoute(root, obj): 44 last = route[-1] 45 if last[1] == 0: 46 setattr(last[0], last[2], None) 47 else: 48 last[0].remove(obj) 49 50 return True 51 52 return ('Delete %s' % (getattr(obj, 'name', '') or type(obj).__name__), inner, default) 53 54 55 @Validate('Tr2Mesh') 56 def HasAreas(context, mesh): 57 if len(_GetAllAreas(mesh)) == 0: 58 actions = [] 59 if len(context.GetStack()) > 1: 60 actions.append(_DeleteObject(context, True)) 61 context.Error(mesh, 'has no areas', actions=actions) 62 63 64 @Validate('Tr2MeshArea') 65 def AreaContainsEffect(context, area): 66 if not area.effect: 67 context.Error(area, 'mesh area without an effect') 68 69 70 def _GetMeshAreaCount(granny, meshIndex): 71 try: 72 return len(granny.meshes[meshIndex].trigroups) 73 except IndexError: 74 return 0 75 76 77 @Validate('Tr2Mesh(mesh)/.../Tr2MeshArea(area)') 78 def ValidMeshAreaIndex(context, mesh, area): 79 granny = _GetGranny(context, mesh.geometryResPath) 80 if not granny: 81 return 82 count = _GetMeshAreaCount(granny, mesh.meshIndex) 83 if area.index < 0 or area.index + area.count > count: 84 context.Error(area, 'mesh area index or count is out of range (should be between 0 and %s)' % (count - 1)) 85 86 87 GRANNY_VERTEX_ELEMENT_TO_USAGE = {'Position': 0, 88 'DiffuseColor': 1, 89 'Normal': 2, 90 'Tangent': 3, 91 'Binormal': 4, 92 'TextureCoordinates': 5, 93 'BoneIndices': 6, 94 'BoneWeights': 7} 95 USAGE_TO_GRANNY_VERTEX_ELEMENT = {v:k for k, v in GRANNY_VERTEX_ELEMENT_TO_USAGE.items()} 96 97 def _GrannyVertexElementToTrinityUsage(name, offset): 98 for k, v in GRANNY_VERTEX_ELEMENT_TO_USAGE.items(): 99 if name.startswith(k): 100 if name == k: 101 return (v, offset) 102 else: 103 return (v, int(name[len(k):]) + offset) 104 105 106 def _TrinityUsageToGrannyVertexElement(usage, usageIndex): 107 name = USAGE_TO_GRANNY_VERTEX_ELEMENT.get(usage, 'Unknown(%s)' % usage) 108 if usageIndex: 109 name += str(usageIndex) 110 return name 111 112 113 def _GetAllAreas(mesh): 114 areas = [] 115 if mesh.opaqueAreas: 116 areas.extend(mesh.opaqueAreas) 117 if mesh.decalAreas: 118 areas.extend(mesh.decalAreas) 119 if mesh.depthAreas: 120 areas.extend(mesh.depthAreas) 121 if mesh.transparentAreas: 122 areas.extend(mesh.transparentAreas) 123 if mesh.additiveAreas: 124 areas.extend(mesh.additiveAreas) 125 if mesh.pickableAreas: 126 areas.extend(mesh.pickableAreas) 127 if mesh.mirrorAreas: 128 areas.extend(mesh.mirrorAreas) 129 if mesh.decalNormalAreas: 130 areas.extend(mesh.decalNormalAreas) 131 if mesh.depthNormalAreas: 132 areas.extend(mesh.depthNormalAreas) 133 if mesh.opaquePrepassAreas: 134 areas.extend(mesh.opaquePrepassAreas) 135 if mesh.decalPrepassAreas: 136 areas.extend(mesh.decalPrepassAreas) 137 if mesh.geometryEraserAreas: 138 areas.extend(mesh.geometryEraserAreas) 139 if mesh.distortionAreas: 140 areas.extend(mesh.distortionAreas) 141 return areas 142 143 144 def _GetAllShaderInputs(mesh): 145 areas = _GetAllAreas(mesh) 146 inputs = set() 147 for each in areas: 148 if each.effect: 149 inputs.update(effects.GetVertexShaderInputs(each.effect, effectCache)) 150 151 return inputs 152 153 154 def _SubtractVertexInputs(context, mesh, path, inputs, grannyName, offset): 155 granny = _GetGranny(context, path) 156 if not granny: 157 return False 158 try: 159 grannyMesh = granny.meshes[mesh.meshIndex] 160 except IndexError: 161 return False 162 163 decl = grannyMesh.GetVertexDeclaration() 164 for each in decl.keys(): 165 i = _GrannyVertexElementToTrinityUsage(each, offset) 166 if i is None: 167 context.Error(mesh, '%s granny file contains invalid vertex element %s' % (grannyName, each)) 168 elif i in inputs: 169 inputs.remove(i) 170 171 return True 172 173 174 def _ValidVertexShaderInputs(context, mesh, path, inputs, grannyName): 175 if not _SubtractVertexInputs(context, mesh, path, inputs, grannyName, 0): 176 return 177 err = [] 178 for usage, usageIndex in inputs: 179 if (usage, usageIndex) == (5, 1): 180 continue 181 codeUsages = [] 182 for obj in context.GetStack(): 183 if getattr(obj, 'GetVertexElementAddedThroughCode', None) and callable(obj.GetVertexElementAddedThroughCode): 184 codeUsages = obj.GetVertexElementAddedThroughCode() 185 186 if (usage, usageIndex) in codeUsages: 187 continue 188 err.append(_TrinityUsageToGrannyVertexElement(usage, usageIndex)) 189 190 if err: 191 context.Error(mesh, '%s: %s granny file is missing vertex elements required by the shader: %s' % (grannyName, path, ', '.join(err))) 192 193 194 def GetGrannyBoneCount(context, path): 195 granny = _GetGranny(context, path) 196 if not granny: 197 return 0 198 return max(granny.GetMeshesJointCount()) 199 200 201 def GetBoneCountFromLoadedGranny(grannyObject): 202 return max(grannyObject.GetMeshesJointCount()) 203 204 205 def GetMeshBoneCount(context, mesh): 206 return GetGrannyBoneCount(context, mesh.geometryResPath) 207 208 209 @Validate('=Tr2Mesh') 210 def ValidMeshVertexShaderInputs(context, mesh): 211 inputs = _GetAllShaderInputs(mesh) 212 _ValidVertexShaderInputs(context, mesh, mesh.geometryResPath, inputs, '') 213 214 215 @Validate('Tr2InstancedMesh') 216 def ValidInstancedMeshVertexShaderInputs(context, mesh): 217 inputs = _GetAllShaderInputs(mesh) 218 if not _SubtractVertexInputs(context, mesh, mesh.geometryResPath, inputs, '', 0): 219 return 220 ignoreInstanced = not _SubtractVertexInputs(context, mesh, mesh.instanceGeometryResPath, inputs, '', 8) 221 err = [] 222 for usage, usageIndex in inputs: 223 if ignoreInstanced and usageIndex >= 8: 224 continue 225 err.append(_TrinityUsageToGrannyVertexElement(usage, usageIndex)) 226 227 if err: 228 context.Error(mesh, 'granny file "%s" is missing vertex elements required by the shader: %s' % (mesh.geometryResPath, ', '.join(err))) 229 230 231 @Validate('Tr2Mesh') 232 def MaxBonesLimit(context, mesh): 233 count = GetGrannyBoneCount(context, mesh.geometryResPath) 234 if count > MAX_BONES_PER_MESH: 235 context.Error(mesh, 'granny file contains %s bones; maximum allowed count is %s' % (count, MAX_BONES_PER_MESH)) 236 237 238 def _IsSkinned(context, resPath): 239 granny = _GetGranny(context, resPath) 240 return IsGrannySkinned(granny) 241 242 243 def IsGrannySkinned(grannyObject): 244 if not grannyObject: 245 return None 246 for each in grannyObject.meshes: 247 if 'BoneIndices' in each.GetVertexDeclaration(): 248 return True 249 250 return False 251 252 253 def IsMeshSkinned(context, mesh): 254 return _IsSkinned(context, mesh.geometryResPath) 255 256 257 @Validate('Tr2Mesh(mesh)/.../Tr2Effect(effect)') 258 def SkinnedUnskinnedMismatch(context, mesh, effect): 259 if not effect.effectFilePath: 260 return 261 isSkinned = IsMeshSkinned(context, mesh) 262 if isSkinned is None: 263 return 264 isEffectSkinned = 'skinned_' in effect.effectFilePath.lower() 265 if not isSkinned and isEffectSkinned: 266 context.Error(effect, 'skinned effect is applied to a non-skinned mesh')