decalValidation.py (15373B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\evegraphics\validation\validators\decalValidation.py 2 import os 3 import blue 4 import trinity 5 from platformtools.compatibility.exposure.artpipeline import decalutils 6 from evegraphics.validation.commonUtilities import Validate, IsContent 7 from evegraphics.validation.validationFunctions import ListAttributesAreDistinct 8 from evegraphics.validation import resources 9 from evegraphics.validation.validators import sofHullValidation 10 from evegraphics.validation.validators import limits 11 from trinity.effects import GetMergedParameters 12 MAX_DECAL_PRIMITIVE_COUNT = 1000 13 _VALID_FXPATH_LOC = 'res:/graphics/effect/managed/space/decals' 14 FACTION_PATH = 'res:/dx9/model/spaceobjectfactory/factions' 15 16 def GetFactionVisibilityGroups(context): 17 factionVisibilityGroups = context.cache.get('factionVisibilityGroups', None) 18 if factionVisibilityGroups is None: 19 factions = [] 20 factionVisibilityGroups = {} 21 for f in blue.paths.listdir(FACTION_PATH): 22 if os.path.splitext(f)[1].lower() == '.red': 23 factions.append(resources.GetResource(context, FACTION_PATH + '/' + f, resources.ResourceType.OBJECT)) 24 25 for faction in factions: 26 if hasattr(faction, 'visibilityGroupSet'): 27 factionVisibilityGroups[faction] = [ g.str for g in getattr(faction.visibilityGroupSet, 'visibilityGroups', []) ] 28 else: 29 factionVisibilityGroups[faction] = [] 30 31 context.cache['factionVisibilityGroups'] = factionVisibilityGroups 32 return factionVisibilityGroups 33 34 35 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSet(decalSet)') 36 def ValidateDecalSet(context, owner, decalSet): 37 if decalSet.name == '': 38 context.Error(decalSet, 'DecalSet has no name') 39 if decalSet.visibilityGroup is None or decalSet.visibilityGroup == '': 40 context.Error(decalSet, 'DecalSet %s has no visibilityGroup set' % str(decalSet.name)) 41 elif decalSet.visibilityGroup != decalSet.visibilityGroup.lower(): 42 context.Error(decalSet, 'DecalSet %s has a visibilityGroup with uppercase letters %s ' % (str(decalSet.name), str(decalSet.visibilityGroup))) 43 killmarkCount = limits.GetValue(owner.category, limits.KILLMARK_DECAL) 44 shouldHaveKillmarks = killmarkCount is not None and killmarkCount > 0 45 if decalSet.visibilityGroup == 'primary': 46 killcounters = [ item.name for item in decalSet.items if item.usage == trinity.DecalUsage.KillCounter ] 47 if shouldHaveKillmarks and len(killcounters) == 0: 48 context.Error(decalSet, 'DecalSet %s has no decals with usage KillCounter' % str(decalSet.name)) 49 elif not shouldHaveKillmarks and len(killcounters) > 0: 50 context.Error(decalSet, "DecalSet %s has decals with usage KillCounter, but the owner category '%s' shouldn't have any" % (str(decalSet.name), str(owner.category))) 51 52 53 @Validate('EveSOFDataHull(owner)/.../List[EveSOFDataHullDecalSet](decalSets)') 54 def ValidateDecalLimit(context, owner, decalSets): 55 factionVisibilityGroups = GetFactionVisibilityGroups(context) 56 nonLogoDecalCountByVisibilityGroup = {decalSet.visibilityGroup:[ item for item in decalSet.items if item.usage != trinity.DecalUsage.Logo ] for decalSet in decalSets} 57 logoDecalCountByVisibilityGroup = {decalSet.visibilityGroup:[ item for item in decalSet.items if item.usage == trinity.DecalUsage.Logo ] for decalSet in decalSets} 58 decalLimit = limits.GetValue(owner.category, limits.MAX_DECAL_COUNT) 59 decalLimit = 0 if decalLimit is None else decalLimit 60 factionDecalCount = {} 61 for faction, visibilityGroups in factionVisibilityGroups.iteritems(): 62 decalCount = 0 63 for factionVisibilityGroup in visibilityGroups: 64 decalCount += len(nonLogoDecalCountByVisibilityGroup.get(factionVisibilityGroup, [])) 65 if faction.logoSet is not None: 66 logoDecals = logoDecalCountByVisibilityGroup.get(factionVisibilityGroup, []) 67 for logoDecal in logoDecals: 68 factionLogos = [faction.logoSet.Primary, 69 faction.logoSet.Secondary, 70 faction.logoSet.Tertiary, 71 faction.logoSet.Marking_01, 72 faction.logoSet.Marking_02] 73 if factionLogos[logoDecal.logoType] is not None: 74 decalCount += 1 75 76 factionDecalCount[faction.name] = decalCount 77 78 offendingFactions = {factionName:decalCount for factionName, decalCount in factionDecalCount.iteritems() if decalCount > decalLimit} 79 if len(offendingFactions): 80 message = 'Hull exceeds decal limit of %s with a highest count of %s.\nThis limit is exceeded when hull is combined with the following factions;\n\n\t- %s' % (decalLimit, max(offendingFactions.values()), ',\n\t- '.join(offendingFactions.keys())) 81 context.Error(owner, message) 82 83 84 @Validate('EveSOFDataHullDecalSet') 85 def ValidateNoRepetitionIsWithinDecalSet(context, decalSet): 86 context.Expect(decalSet.items, None, ListAttributesAreDistinct('name')) 87 context.Expect(decalSet.items, None, ListAttributesAreDistinct(None, listAttributesToIgnore=['name'])) 88 89 90 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSet(decalSet)') 91 def ValidateAllDecalsUseTheSameParentTextures(context, owner, decalSet): 92 allAreas = sofHullValidation._GetAllHullAreas(owner) 93 usedMaps = {'NormalMap': [], 94 'DirtMap': []} 95 for area in allAreas: 96 for tex in area.textures: 97 if tex.name in usedMaps.iterkeys() and tex.resFilePath.lower() not in usedMaps[tex.name]: 98 usedMaps[tex.name].append(tex.resFilePath.lower()) 99 100 for decal in decalSet.items: 101 for tex in decal.textures: 102 if tex.name in usedMaps.iterkeys(): 103 if tex.resFilePath.lower() not in usedMaps[tex.name]: 104 context.Error(decal, 'Decal ' + str(decal.name) + ' uses wrong ' + str(tex.name) + ': ' + str(tex.resFilePath.lower())) 105 106 107 @Validate('EveSOFDataHullDecalSetItem') 108 def ValidateDecalSetItemTextures(context, decal): 109 textures = [] 110 if decal.usage == trinity.DecalUsage.KillCounter: 111 textures = ['DecalTransparencyMap'] 112 if decal.usage in (trinity.DecalUsage.Glow, trinity.DecalUsage.GlowCylindrical): 113 textures = ['DecalTransparencyMap', 'DecalGlowMap'] 114 elif decal.usage == trinity.DecalUsage.Hole: 115 textures = ['DecalInsideCubeMap', 'DecalHoleMap', 'DecalTransparencyMap'] 116 elif decal.usage in (trinity.DecalUsage.Standard, trinity.DecalUsage.Cylindrical): 117 textures = ['DecalTransparencyMap', 118 'DecalNormalMap', 119 'DecalFresnelMap', 120 'DecalAlbedoMap', 121 'DecalRoughnessMap'] 122 _ValidateDecalSetItem(context, decal, textures) 123 124 125 def _ValidateDecalSetItem(context, decal, textureNames): 126 texturesByName = {tex.name:tex.resFilePath for tex in decal.textures} 127 missingTextures = set(textureNames).difference(set(texturesByName.keys())) 128 if len(missingTextures) > 0: 129 context.Error(decal, 'DecalSetItem %s has a missing textures %s' % (str(decal.name), missingTextures)) 130 unsetTextures = [ textureName for textureName, texturePath in texturesByName.iteritems() if texturePath is None or texturePath == '' ] 131 if len(unsetTextures) > 0: 132 context.Error(decal, 'DecalSetItem %s has unset textures %s' % (str(decal.name), unsetTextures)) 133 if decal.usage == trinity.DecalUsage.Logo and len(decal.textures) > 0: 134 context.Error(decal, 'DecalSetItem %s is used as a logo but has textures %s. The textures should come from the factional logoset' % (str(decal.name), texturesByName.keys())) 135 else: 136 unusedTextures = set(texturesByName.keys()).difference(set(textureNames)) 137 if len(unusedTextures) > 0: 138 context.Error(decal, 'DecalSetItem %s has a unused textures %s' % (str(decal.name), unusedTextures)) 139 140 141 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSetItem(decal)', IsContent(IsContent.CONTENT)) 142 def DecalHasGeometry(context, owner, decal): 143 grannyPath = owner.geometryResFilePath 144 if not grannyPath: 145 return 146 ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath)) 147 if not ibs or not ibs[0]: 148 context.Error(decal, 'decal is not touching any geometry') 149 for ib in ibs: 150 if len(ibs) / 3 > MAX_DECAL_PRIMITIVE_COUNT: 151 context.Error(decal, 'too many triangles for the decal (maximum is %s)' % MAX_DECAL_PRIMITIVE_COUNT) 152 153 154 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSetItem(decal)', IsContent(IsContent.BRANCH)) 155 def SofDecalHasGeometry(context, owner, decal): 156 grannyPath = owner.geometryResFilePath 157 if not grannyPath: 158 return 159 existing = [ x.GetIndices() for x in decal.indexBuffers ] 160 ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath)) 161 if existing != ibs: 162 context.Error(decal, 'decal persisted index buffer is out of date') 163 164 165 @Validate('EveSpaceObjectDecal') 166 def ValidateEveSpaceObjectDecal(context, decal): 167 if [ each for each in decal.GetDecalPrimitiveCounts() if each >= MAX_DECAL_PRIMITIVE_COUNT ]: 168 context.Error(decal, 'decal index buffer is too large') 169 170 171 @Validate('List[EveSpaceObjectDecal]') 172 def ValidateListOfEveSpaceObjectDecal(context, decals): 173 context.Expect(decals, None, ListAttributesAreDistinct(None)) 174 175 176 @Validate('EveSpaceObjectDecal', IsContent(IsContent.BRANCH)) 177 def PublishedDecalHasIB(context, decal): 178 if not decal.hasStaticIndexBuffers: 179 context.Error(decal, 'decal needs to have index buffers') 180 181 182 @Validate('EveSpaceObjectDecal', IsContent(IsContent.CONTENT)) 183 def ContentDecalHasNoIB(context, decal): 184 if decal.hasStaticIndexBuffers: 185 context.Error(decal, 'decal should not have index buffers') 186 187 188 @Validate('EveSpaceObject2(owner)/.../EveSpaceObjectDecal(decal)', IsContent(IsContent.CONTENT)) 189 def DecalHasGeometry(context, owner, decal): 190 mesh = owner.mesh 191 if isinstance(mesh, trinity.Tr2Mesh): 192 grannyPath = mesh.geometryResPath 193 else: 194 return 195 if not grannyPath: 196 return 197 ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath)) 198 if not ibs or not ibs[0]: 199 context.Error(decal, 'decal is not touching any geometry') 200 if len(ibs[0]) / 3 > MAX_DECAL_PRIMITIVE_COUNT: 201 context.Error(decal, 'too many triangles for the decal (maximum is %s)' % MAX_DECAL_PRIMITIVE_COUNT) 202 203 204 @Validate('EveSpaceObject2(owner)/.../EveSpaceObjectDecal(decal)', IsContent(IsContent.BRANCH)) 205 def DecalHasGeometry(context, owner, decal): 206 mesh = owner.mesh 207 if isinstance(mesh, trinity.Tr2Mesh): 208 grannyPath = mesh.geometryResPath 209 else: 210 return 211 if not grannyPath: 212 return 213 existing = decal.GetStaticIndexBuffers() 214 ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath)) 215 if existing != ibs: 216 context.Error(decal, 'decal persisted index buffer is out of date') 217 218 219 @Validate('EveSpaceObjectDecal') 220 def DecalNeedsAnEffect(context, decal): 221 if not decal.decalEffect: 222 context.Error(decal, 'decal is missing an effect') 223 224 225 @Validate('EveSpaceObjectDecal') 226 def ValidDecalEffectPath(context, decal): 227 if decal.decalEffect and decal.decalEffect.effectFilePath: 228 if not decal.decalEffect.effectFilePath.lower().startswith(_VALID_FXPATH_LOC): 229 context.Error(decal, 'decal effect must be in %s' % _VALID_FXPATH_LOC) 230 231 232 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSetItem(decal)') 233 def SofDecalMeshIndexIsValid(context, owner, decal): 234 areas = set([ x.index for x in owner.opaqueAreas ]) 235 if decal.meshIndex >= 0 and decal.meshIndex not in areas: 236 context.Error(decal, 'Mesh index does not reference any opaque area') 237 238 239 @Validate('EveSOFDataHullDecalSetItem') 240 def SofDecalWithParentTexturesShouldHaveMeshIndex(context, decalSetItem): 241 if decalSetItem.meshIndex >= 0: 242 return 243 generic = resources.GetResource(context, 'res:/dx9/model/spaceobjectfactory/generic.red', resources.ResourceType.OBJECT) 244 if not generic: 245 return 246 decalShader = _GetDecalSetItemShader(decalSetItem) 247 for each in generic.decalShaders: 248 if each.shader == decalShader: 249 if each.parentTextures: 250 context.Error(decalSetItem, 'Decal with parent textures must have meshIndex specified') 251 252 253 def _GetDecalSetItemShader(decalSetItem): 254 shaderToPick = {trinity.DecalUsage.KillCounter: 'decalcounterv5.fx', 255 trinity.DecalUsage.Standard: 'decalv5.fx', 256 trinity.DecalUsage.Cylindrical: 'decalcylindricv5.fx', 257 trinity.DecalUsage.Glow: 'decalglowv5.fx', 258 trinity.DecalUsage.GlowCylindrical: 'decalglowcylindricv5.fx', 259 trinity.DecalUsage.Logo: 'decalv5.fx', 260 trinity.DecalUsage.Hole: 'decalholev5.fx'} 261 shader = shaderToPick[decalSetItem.usage] 262 return shader 263 264 265 def _GetParentTextures(context, decalSetItem): 266 generic = resources.GetResource(context, 'res:/dx9/model/spaceobjectfactory/generic.red', resources.ResourceType.OBJECT) 267 if not generic: 268 return [] 269 decalShader = _GetDecalSetItemShader(decalSetItem) 270 for each in generic.decalShaders: 271 if each.shader == decalShader: 272 return [ x.str for x in each.parentTextures ] 273 274 return [] 275 276 277 @Validate('EveSOFDataHullDecalSetItem(decal)/.../EveSOFDataTexture(texture)') 278 def SofDecalDoesNotContainParentTextures(context, decal, texture): 279 if decal.meshIndex < 0: 280 return 281 textures = _GetParentTextures(context, decal) 282 if texture.name in textures: 283 context.Error(texture, 'Decal texture is inherited from the hull and needs to be deleted') 284 285 286 def _GetSofDecalSetItemShaderPath(context, decaSetItem): 287 generic = resources.GetResource(context, 'res:/dx9/model/spaceobjectfactory/generic.red', resources.ResourceType.OBJECT) 288 if not generic: 289 return '' 290 decalShader = _GetDecalSetItemShader(decaSetItem) 291 head, tail = os.path.split(decalShader) 292 return '%s/%s/%s%s' % (generic.decalShaderLocation, 293 head, 294 generic.shaderPrefix, 295 tail) 296 297 298 @Validate('EveSOFDataHullDecalSetItem') 299 def ValidateSofDecalTextures(context, decalSetItem): 300 shaderPath = _GetSofDecalSetItemShaderPath(context, decalSetItem) 301 if not shaderPath or not blue.paths.exists(shaderPath): 302 return 303 params, res, _ = GetMergedParameters(shaderPath) 304 decalShader = _GetDecalSetItemShader(decalSetItem) 305 for each in decalSetItem.textures: 306 if each.name not in res: 307 context.Error(each, 'Texture %s is not used by shader %s' % (each.name, decalShader)) 308 309 310 @Validate('EveSOFDataHullDecalSetItem') 311 def ValidateSofDecalParameters(context, decalSetItem): 312 shaderPath = _GetSofDecalSetItemShaderPath(context, decalSetItem) 313 if not shaderPath or not blue.paths.exists(shaderPath): 314 return 315 params, res, _ = GetMergedParameters(shaderPath) 316 decalShader = _GetDecalSetItemShader(decalSetItem) 317 for each in decalSetItem.parameters: 318 if each.name not in params: 319 context.Error(each, 'Parameter %s is not used by shader %s' % (each.name, decalShader))