paperDollPortrait.py (26074B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\paperDoll\paperDollPortrait.py 2 from __future__ import print_function 3 import math 4 import datetime 5 import os 6 import geo2 7 import trinity 8 import blue 9 import yaml 10 import telemetry 11 import legacy_r_drive 12 import eve.common.script.paperDoll.paperDollDefinitions as pdDef 13 import eve.common.script.paperDoll.paperDollDataManagement as pdDm 14 import eve.client.script.paperDoll.commonClientFunctions as pdCcf 15 import eve.common.script.paperDoll.paperDollCommonFunctions as pdCf 16 from eve.client.script.paperDoll.SkinLightmapRenderer import SkinLightmapRenderer 17 from itertools import chain 18 19 def ApplySuffix(basePath, suffix): 20 path = '{0}{1}'.format(basePath, suffix) 21 return path 22 23 24 _FACE_SETUP = {} 25 _FACE_TWEAK = None 26 27 def _GetFaceSetup(gender): 28 if gender in _FACE_SETUP: 29 return _FACE_SETUP[gender] 30 rootFolder = 'R:/' 31 if not legacy_r_drive.loadFromContent: 32 rootFolder = 'res:/' 33 faceSetupPath = rootFolder + 'Graphics/Character/Global/FaceSetup/BasicFace.face' 34 if gender != pdDef.GENDER.FEMALE: 35 faceSetupPath = rootFolder + 'Graphics/Character/Global/FaceSetup/BasicFaceMale.face' 36 if blue.paths.exists(faceSetupPath): 37 data = pdDm.LoadYamlFileNicely(faceSetupPath) 38 else: 39 data = None 40 _FACE_SETUP[gender] = data 41 42 43 def _GetFaceTweakData(): 44 global _FACE_TWEAK 45 if _FACE_TWEAK: 46 return _FACE_TWEAK 47 rootFolder = 'R:/' 48 if not legacy_r_drive.loadFromContent: 49 rootFolder = 'res:/' 50 faceTweaksPath = rootFolder + 'Graphics/Character/Global/FaceSetup/FaceTweakSettings.yaml' 51 _FACE_TWEAK = pdDm.LoadYamlFileNicely(faceTweaksPath) 52 return _FACE_TWEAK 53 54 55 class PortraitTools(object): 56 __metaclass__ = telemetry.ZONE_PER_METHOD 57 58 @staticmethod 59 def BindHeroShader(effect, isOptimized, suffix): 60 if not isOptimized: 61 effect.effectFilePath = 'res:/Graphics/Effect/Managed/Interior/Avatar/skinnedavatarbrdf_detailed' + suffix 62 else: 63 effect.effectFilePath = 'res:/Graphics/Effect/Managed/Interior/Avatar/avatarbrdfcombined_detailed' + suffix 64 while not effect.effectResource.isPrepared: 65 pdCf.Yield() 66 67 if not effect.effectResource.isGood: 68 raise ValueError('Paperdoll::Effect resource failed to load.') 69 effect.PopulateParameters() 70 for res in effect.resources: 71 if res.name == 'NormalDetailMap': 72 res.resourcePath = 'res:/Texture/Global/NormalNoise_N.dds' 73 break 74 75 for par in effect.parameters: 76 if par.name == 'NormalDetailStrength': 77 par.value = 0.25 78 if par.name == 'NormalDetailTiling': 79 par.value = 50.0 80 81 def SetupWrinkleMapControls(self, avatar, effects, pdDoll): 82 if avatar is None: 83 return 84 set = trinity.TriCurveSet() 85 set.name = 'Wrinkles' 86 for s in avatar.curveSets: 87 if s.name == 'Wrinkles': 88 for bind in s.bindings: 89 if hasattr(bind, 'copyValueCallable'): 90 bind.copyValueCallable = None 91 92 avatar.curveSets.remove(s) 93 break 94 95 avatar.curveSets.append(set) 96 weakAvatar = blue.BluePythonWeakRef(avatar) 97 pdDoll.LoadBindPose() 98 99 def CreateBoneDriver(set, bone1, bone2, zone, min, max, oldMin, oldMax, curveDictionary, name, tweakData): 100 if bone1 in pdDoll.bindPose and bone1 in pdDoll.boneOffsets and bone2 in pdDoll.bindPose and bone2 in pdDoll.boneOffsets: 101 bindPosePos1 = pdDoll.bindPose[bone1]['translation'] 102 offset1 = pdDoll.boneOffsets[bone1]['translation'] 103 bindPosePos2 = pdDoll.bindPose[bone2]['translation'] 104 offset2 = pdDoll.boneOffsets[bone2]['translation'] 105 realPos1 = (bindPosePos1[0] / 100.0 + offset1[0], bindPosePos1[1] / 100.0 + offset1[1], bindPosePos1[2] / 100.0 + offset1[2]) 106 realPos2 = (bindPosePos2[0] / 100.0 + offset2[0], bindPosePos2[1] / 100.0 + offset2[1], bindPosePos2[2] / 100.0 + offset2[2]) 107 bindPoseDist = geo2.Vec3Distance(bindPosePos1, bindPosePos2) / 100.0 108 dist = geo2.Vec3Distance(realPos1, realPos2) 109 multiplier = dist / bindPoseDist 110 oldMin = oldMin * multiplier 111 oldMax = oldMax * multiplier 112 113 def GetOrMakeCurve(bone, set, curveDictionary, name): 114 curve = curveDictionary.get((weakAvatar, bone), None) 115 if curve is not None: 116 return curve 117 curve = trinity.Tr2BoneMatrixCurve() 118 curve.bone = bone 119 curve.name = name 120 curveDictionary[weakAvatar, bone] = curve 121 set.curves.append(curve) 122 return curve 123 124 curve1 = GetOrMakeCurve(bone1, set, curveDictionary, 'bone1') 125 curve2 = GetOrMakeCurve(bone2, set, curveDictionary, 'bone2') 126 bind = trinity.TriValueBinding() 127 set.bindings.append(bind) 128 129 def distanceBinding(curve1, curve2, min, max, parameters, index, gamma): 130 avatar = weakAvatar.object 131 if avatar is None: 132 return 133 curve1.skinnedObject = avatar 134 curve2.skinnedObject = avatar 135 if curve1.startValue == curve1.currentValue or curve2.startValue == curve2.currentValue: 136 curve1.skinnedObject = None 137 curve2.skinnedObject = None 138 return 139 p1 = curve1.currentValue[3] 140 p2 = curve2.currentValue[3] 141 dist = geo2.Vec3Distance(p1, p2) 142 d = 1.0 - (dist - min) * 1 / (max - min) 143 if d < 0: 144 d = 0 145 elif d > 1: 146 d = 1 147 if gamma != 1.0: 148 d = math.pow(d, gamma) 149 if False: 150 trinity.GetDebugRenderer().DrawLine((p1[0], p1[1], p1[2]), 4294967295L, (p2[0], p2[1], p2[2]), 4294967295L) 151 trinity.GetDebugRenderer().Print3D((p1[0], p1[1], p1[2]), 4294967295L, str(dist)) 152 trinity.GetDebugRenderer().Print3D((p2[0], p2[1], p2[2]), 4294967295L, str(d)) 153 for p_ in parameters: 154 p = p_.object 155 if p is None: 156 continue 157 if index == 1: 158 p.v1 = d 159 if index == 2: 160 p.v2 = d 161 if index == 3: 162 p.v3 = d 163 if index == 4: 164 p.v4 = d 165 166 curve1.skinnedObject = None 167 curve2.skinnedObject = None 168 169 zone = zone - 1 170 paramIndex = zone / 4 + 1 171 index = zone % 4 + 1 172 param = 'WrinkleNormalStrength' + str(paramIndex) 173 parameters = [] 174 for fx in effects: 175 for p in fx.parameters: 176 if p.name == param: 177 parameters.append(blue.BluePythonWeakRef(p)) 178 179 gammaCurves = tweakData.get('gammaCurves', {}) 180 gamma = gammaCurves.get(name, gammaCurves.get('default', 1.0)) 181 bind.copyValueCallable = lambda source, dest: distanceBinding(curve1, curve2, oldMin, oldMax, parameters, index, gamma) 182 183 def CreateAxisDriver(set, jointName, zone, axis, maxValue, name, tweakData, pdDoll): 184 avatar = weakAvatar.object 185 if avatar is None or not hasattr(avatar, 'animationUpdater') or avatar.animationUpdater is None or not hasattr(avatar.animationUpdater, 'network') or avatar.animationUpdater.network is None: 186 return 187 bind = trinity.TriValueBinding() 188 set.bindings.append(bind) 189 if not hasattr(avatar.animationUpdater, 'network'): 190 return 191 jointIndex = avatar.animationUpdater.network.GetBoneIndex(jointName) 192 if jointIndex == -1: 193 print("Error: Can't find bone for axis control: " + jointName) 194 return 195 if not type(jointIndex) == type(1): 196 print('Non-int returned for bone in axis: ' + str(jointIndex) + ', name:' + jointName) 197 return 198 bindPosePos = pdDoll.bindPose[jointName] 199 if jointName in pdDoll.boneOffsets: 200 offset = pdDoll.boneOffsets[jointName] 201 else: 202 offset = {'translation': [0, 0, 0]} 203 startPos = [0, 204 0, 205 0, 206 0, 207 0, 208 0, 209 0, 210 0, 211 0, 212 (bindPosePos['translation'][0] + offset['translation'][0]) / 100.0, 213 (bindPosePos['translation'][1] + offset['translation'][1]) / 100.0, 214 (bindPosePos['translation'][2] + offset['translation'][2]) / 100.0] 215 216 def AxisBinding(startPos, jointIndex, axis, maxValue, parameters, index, gamma): 217 avatar = weakAvatar.object 218 if avatar is None: 219 return 220 if not hasattr(avatar.animationUpdater, 'network') or avatar.animationUpdater.network is None: 221 return 222 newPos = avatar.animationUpdater.network.GetRawTrackData(jointIndex) 223 diff = (newPos[9] - startPos[9], newPos[10] - startPos[10], newPos[11] - startPos[11]) 224 value = 0 225 if axis == 'x': 226 value = diff[0] 227 elif axis == 'y': 228 value = diff[1] 229 elif axis == 'z': 230 value = diff[2] 231 finalValue = 0 232 max = maxValue 233 if max < 0: 234 max = -max 235 value = -value 236 if value > max: 237 finalValue = 1.0 238 elif value < 0: 239 finalValue = 0.0 240 else: 241 finalValue = value / max 242 if gamma != 1.0: 243 finalValue = math.pow(finalValue, gamma) 244 for p_ in parameters: 245 p = p_.object 246 if p is None: 247 continue 248 if index == 1: 249 p.v1 = finalValue 250 if index == 2: 251 p.v2 = finalValue 252 if index == 3: 253 p.v3 = finalValue 254 if index == 4: 255 p.v4 = finalValue 256 257 zone = zone - 1 258 paramIndex = zone / 4 + 1 259 index = zone % 4 + 1 260 param = 'WrinkleNormalStrength' + str(paramIndex) 261 parameters = [] 262 for fx in effects: 263 for p in fx.parameters: 264 if p.name == param: 265 parameters.append(blue.BluePythonWeakRef(p)) 266 267 gammaCurves = tweakData.get('gammaCurves', {}) 268 gamma = gammaCurves.get(name, gammaCurves.get('default', 1.0)) 269 bind.name = 'AxisBind' 270 bind.copyValueCallable = lambda source, dest: AxisBinding(startPos, jointIndex, axis, maxValue, parameters, index, gamma) 271 272 data = _GetFaceSetup(pdDoll.gender) 273 if not data: 274 return 275 tweakData = _GetFaceTweakData() 276 curveDictionary = {} 277 for d in data: 278 entry = data[d] 279 if len(entry) == 7: 280 CreateBoneDriver(set, entry[0], entry[1], entry[2], entry[3] / 100.0, entry[4] / 100.0, entry[5] / 100.0, entry[6] / 100.0, curveDictionary, d, tweakData) 281 elif len(entry) == 4: 282 CreateAxisDriver(set, entry[0], entry[1], entry[2], entry[3] / 100.0, d, tweakData, pdDoll) 283 284 wrinkleMultiplier = tweakData.get('wrinkleMultiplier', 1.0) 285 correctionMultiplier = tweakData.get('correctionMultiplier', 1.0) 286 for fx in effects: 287 for p in fx.parameters: 288 if p.name == 'WrinkleParams': 289 p.value = (wrinkleMultiplier, 290 correctionMultiplier, 291 0.0, 292 0.0) 293 294 set.Play() 295 return set 296 297 @staticmethod 298 def BindLinearAvatarBRDF(effect, suffix): 299 path = effect.effectFilePath.lower() 300 pathSansSuffix = path[:path.rfind('.')] 301 if pathSansSuffix.endswith('skinnedavatarbrdf') or pathSansSuffix.endswith('skinnedavatarbrdfdouble') or pathSansSuffix.endswith('clothavatar'): 302 effect.effectFilePath = ApplySuffix(pathSansSuffix + 'linear', suffix) 303 elif pathSansSuffix.endswith('skinnedavatarbrdflinear') or pathSansSuffix.endswith('skinnedavatarbrdfdoublelinear') or pathSansSuffix.endswith('clothavatarlinear'): 304 effect.effectFilePath = ApplySuffix(pathSansSuffix, suffix) 305 306 @staticmethod 307 def TweakDiffuseSampler(effect): 308 for s in effect.samplerOverrides: 309 if s[0] == 'DiffuseMapSampler': 310 return 311 312 sampler = list(effect.samplerOverrides.GetDefaultValue()) 313 sampler[0] = 'DiffuseMapSampler' 314 sampler[1] = 3 315 sampler[2] = 3 316 effect.samplerOverrides.append(tuple(sampler)) 317 318 @staticmethod 319 def BindSkinShader(effect, wrinkleFx, scattering, buildDataManager, gender, use_png, fxSuffix): 320 name = effect.name.lower() 321 path = effect.effectFilePath.lower() 322 if name.startswith('c_skin'): 323 wasDouble = 'double' in path 324 fxPath = pdDef.SKINNED_AVATAR_SINGLEPASS_DOUBLE_PATH if wasDouble else pdDef.SKINNED_AVATAR_SINGLEPASS_SINGLE_PATH 325 effect.effectFilePath = ApplySuffix(fxPath[:-3], fxSuffix) 326 while not effect.effectResource.isPrepared: 327 pdCf.Yield() 328 329 if not effect.effectResource.isGood: 330 raise ValueError('Paperdoll::Effect resource failed to load') 331 effect.PopulateParameters() 332 pdCcf.SetOrAddMap(effect, 'BeckmannLookup', 'res:/Texture/Global/beckmannSpecular.dds') 333 PortraitTools.TweakDiffuseSampler(effect) 334 suffix = 'tga' 335 if use_png and not legacy_r_drive.loadFromContent: 336 suffix = 'png' 337 headMods = buildDataManager.GetModifiersByCategory(pdDef.DOLL_PARTS.HEAD) 338 if pdDef.GENDER_ROOT: 339 if gender == pdDef.GENDER.FEMALE: 340 headFolderGeneric = 'res:/Graphics/Character/Female/Paperdoll/head/Head_Generic' 341 else: 342 headFolderGeneric = 'res:/Graphics/Character/Male/Paperdoll/head/Head_Generic' 343 elif gender == pdDef.GENDER.FEMALE: 344 headFolderGeneric = 'res:/Graphics/Character/Modular/Female/head/Head_Generic' 345 else: 346 headFolderGeneric = 'res:/Graphics/Character/Modular/Male/head/Head_Generic' 347 headFolder = headFolderGeneric 348 for hm in headMods: 349 headFolder = os.path.dirname(hm.redfile) 350 351 if scattering: 352 if headFolder != '' and blue.paths.exists(headFolder + '/SkinMap.' + suffix): 353 pdCcf.SetOrAddMap(effect, 'SkinMap', headFolder + '/SkinMap.' + suffix) 354 else: 355 pdCcf.SetOrAddMap(effect, 'SkinMap', headFolderGeneric + '/SkinMap.' + suffix) 356 if wrinkleFx is not None: 357 pdCcf.SetOrAddMap(effect, 'WrinkleZoneMap', pdDef.FEMALE_WRINKLE_FACEZONE_PREFIX + suffix) 358 if headFolder != '' and blue.paths.exists(headFolder + '/WrinkleNormal.' + suffix): 359 pdCcf.SetOrAddMap(effect, 'WrinkleNormalMap', headFolder + '/WrinkleNormal.' + suffix) 360 else: 361 pdCcf.SetOrAddMap(effect, 'WrinkleNormalMap', headFolderGeneric + '/WrinkleNormal.' + suffix) 362 if headFolder != '' and blue.paths.exists(headFolder + '/WrinkleNormalCorrection.' + suffix): 363 pdCcf.SetOrAddMap(effect, 'WrinkleNormalCorrectionMap', headFolder + '/WrinkleNormalCorrection.' + suffix) 364 else: 365 pdCcf.SetOrAddMap(effect, 'WrinkleNormalCorrectionMap', headFolderGeneric + '/WrinkleNormalCorrection.' + suffix) 366 if headFolder != '' and blue.paths.exists(headFolder + '/WrinkleDiffuse.' + suffix): 367 pdCcf.SetOrAddMap(effect, 'WrinkleDiffuseMap', headFolder + '/WrinkleDiffuse.' + suffix) 368 else: 369 pdCcf.SetOrAddMap(effect, 'WrinkleDiffuseMap', headFolderGeneric + '/WrinkleDiffuse.' + suffix) 370 wrinkleFx.append(effect) 371 elif name.startswith('c_standard'): 372 373 def AddClothingCube(effect): 374 for res in effect.resources: 375 if res.name == 'ClothingReflectionCube': 376 return 377 378 res = trinity.TriTextureParameter() 379 res.name = 'ClothingReflectionCube' 380 res.resourcePath = 'res:/Texture/Global/GenericReflection_cube.dds' 381 effect.resources.append(res) 382 383 AddClothingCube(effect) 384 elif name.startswith('c_eyewetness'): 385 effect.effectFilePath = ApplySuffix(pdDef.SKINNED_AVATAR_EYEWETNESS_SHADER[:-3], fxSuffix) 386 elif name.startswith('c_tongue'): 387 effect.effectFilePath = ApplySuffix(pdDef.SKINNED_AVATAR_TONGUE_SHADER[:-3], fxSuffix) 388 elif name.startswith('c_teeth'): 389 effect.effectFilePath = ApplySuffix(pdDef.SKINNED_AVATAR_TEETH_SHADER[:-3], fxSuffix) 390 elif name.startswith('c_eyes'): 391 effect.effectFilePath = ApplySuffix(pdDef.SKINNED_AVATAR_EYE_SHADER[:-3], fxSuffix) 392 393 def AddEyeCube(effect): 394 for res in effect.resources: 395 if res.name == 'EyeReflectionCube': 396 return 397 398 res = trinity.TriTextureParameter() 399 res.name = 'EyeReflectionCube' 400 res.resourcePath = pdDef.EYE_SHADER_REFLECTION_CUBE_PATH 401 effect.resources.append(res) 402 403 AddEyeCube(effect) 404 405 def BindCustomShaders(effect, useFastShaders, doll): 406 path = effect.effectFilePath.lower() 407 name = effect.name.lower() 408 if 'glassshader' in path: 409 for res in effect.resources: 410 if res.name == 'GlassReflectionCube': 411 return 412 413 res = trinity.TriTextureParameter() 414 res.name = 'GlassReflectionCube' 415 res.resourcePath = pdDef.GLASS_SHADER_REFLECTION_CUBE_PATH 416 effect.resources.append(res) 417 elif name.startswith('c_s2'): 418 for res in effect.resources: 419 if res.name == 'ClothingReflectionCube': 420 return 421 422 res = trinity.TriTextureParameter() 423 res.name = 'ClothingReflectionCube' 424 res.resourcePath = 'res:/Texture/Global/GenericReflection_cube.dds' 425 effect.resources.append(res) 426 for name in ['CellReflectionMap', 'CellReflection2ndMap']: 427 res = trinity.TriTextureParameter() 428 res.name = name 429 effect.resources.append(res) 430 431 fastFx = '_fast.fx' 432 if useFastShaders and fastFx not in path and path in pdDef.SHADERS_THAT_CAN_SWITCH_TO_FAST_SHADER_MODE: 433 effect.effectFilePath = ApplySuffix(effect.effectFilePath[:-3], fastFx) 434 elif doll.useDXT5N: 435 effect.effectFilePath = ApplySuffix(effect.effectFilePath[:-3], '_dxt5n.fx') 436 437 @staticmethod 438 def BindHeroHairShader(effect, suffix): 439 path = effect.effectFilePath.lower() 440 if path.find('hair') == -1: 441 return False 442 if path.find('clothavatarhair') != -1: 443 effect.effectFilePath = ApplySuffix('res:/Graphics/Effect/Managed/Interior/Avatar/clothavatarhair_detailed', suffix) 444 else: 445 effect.effectFilePath = ApplySuffix('res:/Graphics/Effect/Managed/Interior/Avatar/skinnedavatarhair_detailed', suffix) 446 while not effect.effectResource.isPrepared: 447 pdCf.Yield() 448 449 if not effect.effectResource.isGood: 450 raise ValueError('Paperdoll::Effect resource failed to load') 451 name = effect.name.lower() 452 effect.PopulateParameters() 453 pdCcf.SetOrAddMap(effect, 'HairNoise', 'res:/Texture/Global/noise1d.dds') 454 pdCcf.SetOrAddMap(effect, 'TangentMap', 'res:/Texture/Global/50gray.dds' if not name.startswith('c_hair_head') else 'res:/Texture/Global/HeadTangents.dds') 455 PortraitTools.TweakDiffuseSampler(effect) 456 return True 457 458 @staticmethod 459 def BindHeroClothShader(effect, useDXT5n): 460 path = effect.effectFilePath.lower() 461 if 'clothavatar' not in path or 'clothavatarhair' in path: 462 return 463 suffix = '_dxt5n.fx' if useDXT5n else '.fx' 464 effect.effectFilePath = ApplySuffix('res:/Graphics/Effect/Managed/Interior/Avatar/ClothAvatarLinear', suffix) 465 while not effect.effectResource.isPrepared: 466 pdCf.Yield() 467 468 if not effect.effectResource.isGood: 469 raise ValueError('Paperdoll::Effect resource failed to load.') 470 effect.PopulateParameters() 471 PortraitTools.TweakDiffuseSampler(effect) 472 473 @staticmethod 474 def SetupStubble(meshes, name, stubblePath, adding = True): 475 ret = [] 476 stubbleName = '{0}Stubble'.format(name) 477 stubbleMeshes = (mesh for mesh in meshes if mesh.name.startswith(pdDef.DOLL_PARTS.HEAD)) 478 for mesh in stubbleMeshes: 479 if adding: 480 if any(map(lambda x: 'stubble' in x.name.lower(), mesh.decalAreas)): 481 ret.append(mesh) 482 continue 483 c_skin_opaque_areas = (area for area in mesh.opaqueAreas if area.effect.name.lower().startswith('c_skin_')) 484 c_skin_decal_areas = (area for area in mesh.decalAreas if area.effect.name.lower().startswith('c_skin_')) 485 c_skin_areas = chain(c_skin_opaque_areas, c_skin_decal_areas) 486 for area in c_skin_areas: 487 stubbleArea = trinity.Tr2MeshArea() 488 stubbleArea.name = stubbleName 489 stubbleArea.index = area.index 490 stubbleArea.count = area.count 491 blue.paths.GetFileContentsWithYield(stubblePath) 492 stubbleArea.effect = trinity.Load(stubblePath) 493 for resource in stubbleArea.effect.resources: 494 if resource.name == 'LengthTexture': 495 starttime = datetime.datetime.now() 496 timeout = starttime + datetime.timedelta(seconds=10) 497 while not (resource.resource.isPrepared and resource.resource.isGood) and datetime.datetime.now() < timeout: 498 pdCf.Yield() 499 500 if not resource.resource.IsGood(): 501 raise ValueError('Paperdoll::Stubble texture failed to load.') 502 503 mesh.decalAreas.append(stubbleArea) 504 ret.append(mesh) 505 for instance in SkinLightmapRenderer.instances: 506 if instance(): 507 instance().BindLightmapShader(mesh) 508 509 else: 510 targetAreasToRemove = [ area for area in mesh.decalAreas if area.name == stubbleName ] 511 for ta in targetAreasToRemove: 512 mesh.decalAreas.remove(ta) 513 514 return ret 515 516 @staticmethod 517 def HandleRemovedStubble(removedModifiers, buildDataManager): 518 stubbleMods = (mod for mod in removedModifiers if mod.stubblePath) 519 for mod in stubbleMods: 520 stubbleMeshes = buildDataManager.GetMeshes(pdDef.DOLL_PARTS.HEAD) 521 PortraitTools.SetupStubble(stubbleMeshes, mod.name, mod.stubblePath, False) 522 523 @staticmethod 524 def GetStubbleHash(addedAndChangedModifiers): 525 for mod in iter(addedAndChangedModifiers): 526 if mod.stubblePath: 527 return 'active' 528 529 return '' 530 531 @staticmethod 532 def HandleUpdatedStubble(addedAndChangedModifiers, buildDataManager): 533 stubbleMods = (mod for mod in addedAndChangedModifiers if mod.stubblePath) 534 stubbleMeshes = buildDataManager.GetMeshes(pdDef.DOLL_PARTS.HEAD) 535 for mod in stubbleMods: 536 if mod.weight > 0.001: 537 meshes = PortraitTools.SetupStubble(stubbleMeshes[:], mod.name, mod.stubblePath, True) 538 mod.meshes = meshes 539 for mesh in iter(mod.meshes): 540 for area in mesh.decalAreas: 541 fx = area.effect 542 wt = mod.weight 543 length = 0.003 + wt * 0.0027 544 alpha = 0.25 + wt * 2.25 545 for p in fx.parameters: 546 if p.name == 'MaterialDiffuseColor': 547 col = mod.colorizeData[0] 548 p.value = (col[0], 549 col[1], 550 col[2], 551 1.0) 552 if p.name == 'FurLength': 553 p.value = length 554 if p.name == 'AlphaMultiplier': 555 p.value = alpha 556 557 @staticmethod 558 def RebindDXT5ShadersForSM2(meshes): 559 if meshes is None: 560 return 561 for mesh in meshes: 562 effects = pdCcf.GetEffectsFromMesh(mesh) 563 for effect in effects: 564 path = effect.effectFilePath.lower() 565 if '_dxt5n.fx' not in path: 566 continue 567 path = path[:-9] + '.fx' 568 if path not in pdDef.SHADERS_THAT_CAN_SWITCH_TO_FAST_SHADER_MODE: 569 continue 570 path = path[:-3] + '_fast_dxt5n.fx' 571 effect.effectFilePath = path