You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

971 lines
42 KiB

# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.props import *
import functools
from PyHSPlasma import *
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable
from ...exporter.etlight import _NUM_RENDER_LAYERS
from ...exporter import utils
from ...exporter.explosions import ExportError
from .gui import languages, PlasmaJournalTranslation, TranslationMixin
from ... import idprops
class PlasmaBlendOntoObject(bpy.types.PropertyGroup):
blend_onto = PointerProperty(name="Blend Onto",
description="Object to render first",
options=set(),
type=bpy.types.Object,
poll=idprops.poll_drawable_objects)
enabled = BoolProperty(name="Enabled",
default=True,
options=set())
class PlasmaBlendMod(PlasmaModifierProperties):
pl_id = "blend"
bl_category = "Render"
bl_label = "Blending"
bl_description = "Advanced Blending Options"
render_level = EnumProperty(name="Render Pass",
description="Suggested render pass for this object.",
items=[("AUTO", "(Auto)", "Let Korman decide when to render this object."),
("OPAQUE", "Before Avatar", "Prefer for the object to draw before the avatar."),
("FRAMEBUF", "Frame Buffer", "Prefer for the object to draw after the avatar but before other blended objects."),
("BLEND", "Blended", "Prefer for the object to draw after most other geometry in the blended pass.")],
options=set())
sort_faces = EnumProperty(name="Sort Faces",
description="",
items=[("AUTO", "(Auto)", "Let Korman decide if faces should be sorted."),
("ALWAYS", "Always", "Force the object's faces to be sorted."),
("NEVER", "Never", "Force the object's faces to never be sorted.")],
options=set())
dependencies = CollectionProperty(type=PlasmaBlendOntoObject)
active_dependency_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
# What'er you lookin at?
pass
@property
def draw_opaque(self):
return self.render_level == "OPAQUE"
@property
def draw_framebuf(self):
return self.render_level == "FRAMEBUF"
@property
def draw_no_defer(self):
return self.render_level != "BLEND"
@property
def face_sort(self):
return self.sort_faces == "ALWAYS"
@property
def no_face_sort(self):
return self.sort_faces == "NEVER"
@property
def has_dependencies(self):
return bool(self.dependencies)
@property
def has_circular_dependency(self):
return self._check_circular_dependency()
def _check_circular_dependency(self, objects=None):
if objects is None:
objects = set()
elif self.id_data.name in objects:
return True
objects.add(self.id_data.name)
for i in self.iter_dependencies():
# New deep copy of the set for each dependency, so an object can be reused as a
# dependant's dependant.
this_branch = set(objects)
sub_mod = i.plasma_modifiers.blend
if sub_mod.enabled and sub_mod._check_circular_dependency(this_branch):
return True
return False
def iter_dependencies(self):
for i in (j.blend_onto for j in self.dependencies if j.blend_onto is not None and j.enabled):
yield i
def sanity_check(self):
if self.has_circular_dependency:
raise ExportError("'{}': Circular Render Dependency detected!".format(self.id_data.name))
class PlasmaDecalManagerRef(bpy.types.PropertyGroup):
enabled = BoolProperty(name="Enabled",
default=True,
options=set())
name = StringProperty(name="Decal Name",
options=set())
class PlasmaDecalMod:
def _iter_decals(self, func):
for decal_ref in self.managers:
if decal_ref.enabled:
func(decal_ref.name)
@classmethod
def register(cls):
cls.managers = CollectionProperty(type=PlasmaDecalManagerRef)
cls.active_manager_index = IntProperty(options={"HIDDEN"})
class PlasmaDecalPrintMod(PlasmaDecalMod, PlasmaModifierProperties):
pl_id = "decal_print"
bl_category = "Render"
bl_label = "Print Decal"
bl_description = "Prints a decal onto an object"
decal_type = EnumProperty(name="Decal Type",
description="Type of decal to print onto another object",
items=[("DYNAMIC", "Dynamic", "This object prints a decal onto dynamic decal surfaces"),
("STATIC", "Static", "This object is a decal itself")],
options=set())
# Dynamic Decals
length = FloatProperty(name="Length",
min=0.1, soft_max=30.0, precision=2,
default=0.45,
options=set())
width = FloatProperty(name="Width",
min=0.1, soft_max=30.0, precision=2,
default=0.9,
options=set())
height = FloatProperty(name="Height",
min=0.1, soft_max=30.0, precision=2,
default=1.0,
options=set())
@property
def copy_material(self):
return self.decal_type == "STATIC"
def get_key(self, exporter, so):
if self.decal_type == "DYNAMIC":
pClass = plActivePrintShape if any((i.enabled for i in self.managers)) else plPrintShape
return exporter.mgr.find_create_key(pClass, so=so)
def export(self, exporter, bo, so):
if self.decal_type == "STATIC":
exporter.decal.export_static_decal(bo)
elif self.decal_type == "DYNAMIC":
print_shape = self.get_key(exporter, so).object
print_shape.length = self.length
print_shape.width = self.width
print_shape.height = self.height
def post_export(self, exporter, bo, so):
if self.decal_type == "DYNAMIC":
print_shape = self.get_key(exporter, so).object
f = functools.partial(exporter.decal.export_active_print_shape, print_shape)
self._iter_decals(f)
class PlasmaDecalReceiveMod(PlasmaDecalMod, PlasmaModifierProperties):
pl_id = "decal_receive"
bl_category = "Render"
bl_label = "Receive Decal"
bl_description = "Allows this object to receive dynamic decals"
managers = CollectionProperty(type=PlasmaDecalManagerRef)
active_manager_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
f = functools.partial(exporter.decal.generate_dynamic_decal, bo)
self._iter_decals(f)
def post_export(self, exporter, bo, so):
f = functools.partial(exporter.decal.add_dynamic_decal_receiver, so)
self._iter_decals(f)
class PlasmaFadeMod(PlasmaModifierProperties):
pl_id = "fademod"
bl_category = "Render"
bl_label = "Opacity Fader"
bl_description = "Fades an object based on distance or line-of-sight"
fader_type = EnumProperty(name="Fader Type",
description="Type of opacity fade",
items=[("DistOpacity", "Distance", "Fade based on distance to object"),
("FadeOpacity", "Line-of-Sight", "Fade based on line-of-sight to object"),
("SimpleDist", "Simple Distance", "Fade for use as Great Zero Markers")],
default="SimpleDist")
fade_in_time = FloatProperty(name="Fade In Time",
description="Number of seconds before the object is fully visible",
min=0.0, max=5.0, default=0.5, subtype="TIME", unit="TIME")
fade_out_time = FloatProperty(name="Fade Out Time",
description="Number of seconds before the object is fully invisible",
min=0.0, max=5.0, default=0.5, subtype="TIME", unit="TIME")
bounds_center = BoolProperty(name="Use Mesh Midpoint",
description="Use mesh's midpoint to calculate LOS instead of object origin",
default=False)
near_trans = FloatProperty(name="Near Transparent",
description="Nearest distance at which the object is fully transparent",
min=0.0, default=0.0, subtype="DISTANCE", unit="LENGTH")
near_opaq = FloatProperty(name="Near Opaque",
description="Nearest distance at which the object is fully opaque",
min=0.0, default=0.0, subtype="DISTANCE", unit="LENGTH")
far_opaq = FloatProperty(name="Far Opaque",
description="Farthest distance at which the object is fully opaque",
min=0.0, default=15.0, subtype="DISTANCE", unit="LENGTH")
far_trans = FloatProperty(name="Far Transparent",
description="Farthest distance at which the object is fully transparent",
min=0.0, default=20.0, subtype="DISTANCE", unit="LENGTH")
def export(self, exporter, bo, so):
if self.fader_type == "DistOpacity":
mod = exporter.mgr.find_create_object(plDistOpacityMod, so=so, name=self.key_name)
mod.nearTrans = self.near_trans
mod.nearOpaq = self.near_opaq
mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans
elif self.fader_type == "FadeOpacity":
mod = exporter.mgr.find_create_object(plFadeOpacityMod, so=so, name=self.key_name)
mod.fadeUp = self.fade_in_time
mod.fadeDown = self.fade_out_time
mod.boundsCenter = self.bounds_center
elif self.fader_type == "SimpleDist":
mod = exporter.mgr.find_create_object(plDistOpacityMod, so=so, name=self.key_name)
mod.nearTrans = 0.0
mod.nearOpaq = 0.0
mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans
@property
def requires_actor(self):
return self.fader_type == "FadeOpacity"
class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
pl_id = "followmod"
bl_category = "Render"
bl_label = "Follow"
bl_description = "Follow the movement of the camera, player, or another object"
follow_mode = EnumProperty(name="Mode",
description="Leader's movement to follow",
items=[
("kPositionX", "X Axis", "Follow the leader's X movements"),
("kPositionY", "Y Axis", "Follow the leader's Y movements"),
("kPositionZ", "Z Axis", "Follow the leader's Z movements"),
("kRotate", "Rotation", "Follow the leader's rotation movements"),
],
default={"kPositionX", "kPositionY", "kPositionZ"},
options={"ENUM_FLAG"})
leader_type = EnumProperty(name="Leader Type",
description="Leader to follow",
items=[
("kFollowCamera", "Camera", "Follow the camera"),
("kFollowListener", "Listener", "Follow listeners"),
("kFollowPlayer", "Player", "Follow the local player"),
("kFollowObject", "Object", "Follow an object"),
])
leader = PointerProperty(name="Leader Object",
description="Object to follow",
type=bpy.types.Object)
def export(self, exporter, bo, so):
fm = exporter.mgr.find_create_object(plFollowMod, so=so, name=self.key_name)
fm.mode = 0
for flag in (getattr(plFollowMod, mode) for mode in self.follow_mode):
fm.mode |= flag
fm.leaderType = getattr(plFollowMod, self.leader_type)
if self.leader_type == "kFollowObject":
# If this object is following another object, make sure that the
# leader has been selected and is a valid SO.
if self.leader:
fm.leader = exporter.mgr.find_create_key(plSceneObject, bl=self.leader)
else:
raise ExportError("'{}': Follow's leader object must be selected".format(self.key_name))
@classmethod
def _idprop_mapping(cls):
return {"leader": "leader_object"}
@property
def requires_actor(self):
return True
class PlasmaGrassWave(bpy.types.PropertyGroup):
distance = FloatVectorProperty(name="Distance",
size=3,
default=(0.2, 0.2, 0.1),
subtype="XYZ",
unit="LENGTH",
options=set())
direction = FloatVectorProperty(name="Direction",
size=2,
default=(0.2, 0.05),
soft_min=0.0, soft_max=1.0,
unit="LENGTH",
subtype="XYZ",
options=set())
speed = FloatProperty(name="Speed",
default=0.1,
unit="VELOCITY",
options=set())
class PlasmaGrassShaderMod(PlasmaModifierProperties):
pl_id = "grass_shader"
bl_category = "Render"
bl_label = "Grass Shader"
bl_description = "Applies waving grass effect at run-time"
wave1 = PointerProperty(type=PlasmaGrassWave)
wave2 = PointerProperty(type=PlasmaGrassWave)
wave3 = PointerProperty(type=PlasmaGrassWave)
wave4 = PointerProperty(type=PlasmaGrassWave)
# UI Accessor
wave_selector = EnumProperty(items=[("wave1", "Wave 1", ""),
("wave2", "Wave 2", ""),
("wave3", "Wave 3", ""),
("wave4", "Wave 4", "")],
name="Waves",
options=set())
@property
def copy_material(self):
return True
def export(self, exporter, bo, so):
if exporter.mgr.getVer() <= pvPots:
exporter.report.warn("Not supported on this version of Plasma", indent=3)
return
else:
exporter.report.port("This will only function on MOUL and EOA", indent=3)
materials = exporter.mesh.material.get_materials(bo)
if not materials:
exporter.report.warn("No materials are associated with this object, no grass shader exported!",
indent=3)
return
elif len(materials) > 1:
exporter.report.warn("Ah, a multiple material grass shader, eh. You like living dangerously...",
indent=3)
for material in materials:
mod = exporter.mgr.find_create_object(plGrassShaderMod, so=so, name=material.name)
mod.material = material
for mod_wave, settings in zip(mod.waves, (self.wave1, self.wave2, self.wave3, self.wave4)):
mod_wave.dist = hsVector3(*settings.distance)
mod_wave.dirX, mod_wave.dirY = settings.direction
mod_wave.speed = settings.speed
class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaModifierUpgradable):
pl_id = "lightmap"
bl_category = "Render"
bl_label = "Bake Lighting"
bl_description = "Auto-Bake Static Lighting"
deprecated_properties = {"render_layers"}
quality = EnumProperty(name="Quality",
description="Resolution of lightmap",
items=[
("128", "128px", "128x128 pixels"),
("256", "256px", "256x256 pixels"),
("512", "512px", "512x512 pixels"),
("1024", "1024px", "1024x1024 pixels"),
("2048", "2048px", "2048x2048 pixels"),
])
bake_type = EnumProperty(name="Bake To",
description="Destination for baked lighting data",
items=[
("lightmap", "Lightmap Texture", "Bakes lighting to a lightmap texture"),
("vcol", "Vertex Colors", "Bakes lighting to vertex colors"),
],
options=set())
render_layers = BoolVectorProperty(name="Layers",
description="DEPRECATED: Render layers to use for baking",
options={"HIDDEN"},
subtype="LAYER",
size=_NUM_RENDER_LAYERS,
default=((True,) * _NUM_RENDER_LAYERS))
bake_pass_name = StringProperty(name="Bake Pass",
description="Pass in which to bake lighting",
options=set())
lights = PointerProperty(name="Light Group",
description="Group that defines the collection of lights to bake",
type=bpy.types.Group)
uv_map = StringProperty(name="UV Texture",
description="UV Texture used as the basis for the lightmap")
image = PointerProperty(name="Baked Image",
description="Use this image instead of re-baking the lighting each export",
type=bpy.types.Image)
@property
def bake_lightmap(self):
if not self.enabled:
return False
age = bpy.context.scene.world.plasma_age
if age.export_active:
if age.lighting_method == "force_lightmap":
return True
elif self.bake_type == "lightmap" and age.lighting_method == "bake":
return True
else:
return False
else:
return self.bake_type == "lightmap"
@property
def copy_material(self):
return self.bake_lightmap
def export(self, exporter, bo, so):
# If we're exporting vertex colors, who gives a rat's behind?
if not self.bake_lightmap:
return
# If no lightmap image is found, then either lightmap generation failed (error raised by oven)
# or baking is turned off. Either way, bail out.
lightmap_im = self.image if self.image is not None else exporter.oven.get_lightmap(bo)
if lightmap_im is None:
return
mat_mgr = exporter.mesh.material
materials = mat_mgr.get_materials(bo)
# Find the stupid UVTex
uvtex_name = exporter.oven.lightmap_uvtex_name
uvw_src = next((i for i, uvtex in enumerate(bo.data.uv_textures) if uvtex.name == uvtex_name), None)
if uvw_src is None:
raise ExportError("'{}': Lightmap UV Texture '{}' seems to be missing. Did you delete it?", bo.name, uvtex_name)
for matKey in materials:
layer = exporter.mgr.add_object(plLayer, name="{}_LIGHTMAPGEN".format(matKey.name), so=so)
layer.UVWSrc = uvw_src
# Colors science'd from PRPs
layer.ambient = hsColorRGBA(1.0, 1.0, 1.0)
layer.preshade = hsColorRGBA(0.5, 0.5, 0.5)
layer.runtime = hsColorRGBA(0.5, 0.5, 0.5)
# GMatState
gstate = layer.state
gstate.blendFlags |= hsGMatState.kBlendMult
gstate.clampFlags |= (hsGMatState.kClampTextureU | hsGMatState.kClampTextureV)
gstate.ZFlags |= hsGMatState.kZNoZWrite
gstate.miscFlags |= hsGMatState.kMiscLightMap
mat = matKey.object
mat.compFlags |= hsGMaterial.kCompIsLightMapped
mat.addPiggyBack(layer.key)
# Mmm... cheating
mat_mgr.export_prepared_image(owner=layer, image=lightmap_im,
allowed_formats={"PNG", "JPG"},
extension="hsm",
ephemeral=True,
indent=2)
@classmethod
def _idprop_mapping(cls):
return {"lights": "light_group"}
def _idprop_sources(self):
return {"light_group": bpy.data.groups}
@property
def key_name(self):
return "{}_LIGHTMAPGEN".format(self.id_data.name)
@property
def latest_version(self):
return 2
@property
def resolution(self):
return int(self.quality)
def upgrade(self):
# In version 1, bake passes were assigned on a per modifier basis by setting
# the view layers on the modifier. Version 2 moves them into a global list
# that can be selected by name in the modifier
if self.current_version < 2:
bake_passes = bpy.context.scene.plasma_scene.bake_passes
render_layers = tuple(self.render_layers)
# Try to find a render pass matching, if possible...
bake_pass = next((i for i in bake_passes if tuple(i.render_layers) == render_layers), None)
if bake_pass is None:
bake_pass = bake_passes.add()
bake_pass.display_name = "Pass {}".format(len(bake_passes))
bake_pass.render_layers = render_layers
self.bake_pass_name = bake_pass.display_name
self.property_unset("render_layers")
self.current_version = 2
class PlasmaLightingMod(PlasmaModifierProperties):
pl_id = "lighting"
bl_category = "Render"
bl_label = "Lighting Info"
bl_description = "Fine tune Plasma lighting settings"
force_rt_lights = BoolProperty(name="Force RT Lighting",
description="Unleashes satan by forcing the engine to dynamically light this object",
default=False,
options=set())
force_preshade = BoolProperty(name="Force Vertex Shading",
description="Ensures vertex lights are baked, even if illogical",
default=False,
options=set())
@property
def allow_preshade(self):
mods = self.id_data.plasma_modifiers
if mods.water_basic.enabled:
return False
return True
def export(self, exporter, bo, so):
# Exposes no new keyed objects, mostly a hint to the ET light code
pass
@property
def preshade(self):
bo = self.id_data
if self.allow_preshade:
if self.force_preshade:
return True
# RT lights means no preshading unless requested
if self.rt_lights:
return False
if not bo.plasma_object.has_transform_animation:
return True
return False
@property
def rt_lights(self):
"""Are RT lights forcibly enabled or do we otherwise want them?"""
return (self.enabled and self.force_rt_lights) or self.want_rt_lights
@property
def unleashed(self):
"""Has Satan been unleashed? Meaning, RT lights and preshading."""
return self.enabled and self.rt_lights and self.preshade
@property
def want_rt_lights(self):
"""Gets whether or not this object ought to be lit dynamically"""
mods = self.id_data.plasma_modifiers
if mods.lightmap.enabled and mods.lightmap.bake_type == "lightmap":
return False
if mods.water_basic.enabled:
return True
if self.id_data.plasma_object.has_transform_animation:
return True
if mods.collision.enabled and mods.collision.dynamic:
return True
return False
_LOCALIZED_TEXT_PFM = (
{ 'id': 1, 'type': "ptAttribDynamicMap", 'name': "dynTextMap", },
{ 'id': 2, 'type': "ptAttribString", 'name': "locPath" },
{ 'id': 3, 'type': "ptAttribString", 'name': "fontFace" },
{ 'id': 4, 'type': "ptAttribInt", 'name': "fontSize" },
{ 'id': 5, 'type': "ptAttribFloat", 'name': "fontColorR" },
{ 'id': 6, 'type': "ptAttribFloat", 'name': "fontColorG" },
{ 'id': 7, 'type': "ptAttribFloat", 'name': "fontColorB" },
{ 'id': 8, 'type': "ptAttribFloat", 'name': "fontColorA" },
{ 'id': 9, 'type': "ptAttribInt", 'name': "marginTop" },
{ 'id': 10, 'type': "ptAttribInt", 'name': "marginLeft" },
{ 'id': 11, 'type': "ptAttribInt", 'name': "marginBottom" },
{ 'id': 12, 'type': "ptAttribInt", 'name': "marginRight" },
{ 'id': 13, 'type': "ptAttribInt", 'name': "lineSpacing" },
# Yes, it's really a ptAttribDropDownList, but those are only for use in
# artist generated node trees.
{ 'id': 14, 'type': "ptAttribString", 'name': "justify" },
{ 'id': 15, 'type': "ptAttribFloat", 'name': "clearColorR" },
{ 'id': 16, 'type': "ptAttribFloat", 'name': "clearColorG" },
{ 'id': 17, 'type': "ptAttribFloat", 'name': "clearColorB" },
{ 'id': 18, 'type': "ptAttribFloat", 'name': "clearColorA" },
)
class PlasmaLocalizedTextModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin):
pl_id = "dynatext"
bl_category = "Render"
bl_label = "Localized Text"
bl_description = ""
bl_icon = "TEXT"
translations = CollectionProperty(name="Translations",
type=PlasmaJournalTranslation,
options=set())
active_translation_index = IntProperty(options={"HIDDEN"})
active_translation = EnumProperty(name="Language",
description="Language of this translation",
items=languages,
get=TranslationMixin._get_translation,
set=TranslationMixin._set_translation,
options=set())
def _poll_dyna_text(self, value: bpy.types.Texture) -> bool:
if value.type != "IMAGE":
return False
if value.image is not None:
return False
tex_materials = frozenset(value.users_material)
obj_materials = frozenset(filter(None, (i.material for i in self.id_data.material_slots)))
return bool(tex_materials & obj_materials)
texture = PointerProperty(name="Texture",
description="The texture to write the localized text on",
type=bpy.types.Texture,
poll=_poll_dyna_text)
font_face = StringProperty(name="Font Face",
default="Arial",
options=set())
font_size = IntProperty(name="Font Size",
default=12,
min=0, soft_max=72,
options=set())
font_color = FloatVectorProperty(name="Font Color",
default=(0.0, 0.0, 0.0, 1.0),
min=0.0, max=1.0,
subtype="COLOR", size=4,
options=set())
# Using individual properties for better UI documentation
margin_top = IntProperty(name="Margin Top",
min=-4096, soft_min=0, max=4096,
options=set())
margin_left = IntProperty(name="Margin Left",
min=-4096, soft_min=0, max=4096,
options=set())
margin_bottom = IntProperty(name="Margin Bottom",
min=-4096, soft_min=0, max=4096,
options=set())
margin_right = IntProperty(name="Margin Right",
min=-4096, soft_min=0, max=4096,
options=set())
justify = EnumProperty(name="Justification",
items=[("left", "Left", ""),
("center", "Center", ""),
("right", "Right", "")],
default="left",
options=set())
line_spacing = IntProperty(name="Line Spacing",
default=0,
soft_min=0, soft_max=10,
options=set())
def pre_export(self, exporter, bo):
yield self.convert_logic(bo, age_name=exporter.age_name, version=exporter.mgr.getVer())
def logicwiz(self, bo, tree, *, age_name, version):
# Rough justice. If the dynamic text map texture doesn't request alpha, then we'll want
# to explicitly clear it to the material's diffuse color. This will allow artists to trivially
# add text surfaces directly to objects, opposed to where Cyan tends to use a separate
# transparent object over the background object.
if not self.texture.use_alpha:
material_filter = lambda slot: slot and slot.material and self.texture in (i.texture for i in slot.material.texture_slots if i)
for slot in filter(material_filter, bo.material_slots):
self._create_nodes(bo, tree, age_name=age_name, version=version,
material=slot.material, clear_color=slot.material.diffuse_color)
else:
self._create_nodes(bo, tree, age_name=age_name, version=version)
def _create_nodes(self, bo, tree, *, age_name, version, material=None, clear_color=None):
pfm_node = self._create_python_file_node(tree, "xDynTextLoc.py", _LOCALIZED_TEXT_PFM)
loc_path = self.key_name if version <= pvPots else "{}.{}.{}".format(age_name, self.localization_set, self.key_name)
self._create_python_attribute(pfm_node, "dynTextMap", "ptAttribDynamicMap",
target_object=bo, material=material, texture=self.texture)
self._create_python_attribute(pfm_node, "locPath", value=loc_path)
self._create_python_attribute(pfm_node, "fontFace", value=self.font_face)
self._create_python_attribute(pfm_node, "fontSize", value=self.font_size)
self._create_python_attribute(pfm_node, "fontColorR", value=self.font_color[0])
self._create_python_attribute(pfm_node, "fontColorG", value=self.font_color[1])
self._create_python_attribute(pfm_node, "fontColorB", value=self.font_color[2])
self._create_python_attribute(pfm_node, "fontColorA", value=self.font_color[3])
self._create_python_attribute(pfm_node, "marginTop", value=self.margin_top)
self._create_python_attribute(pfm_node, "marginLeft", value=self.margin_left)
self._create_python_attribute(pfm_node, "marginBottom", value=self.margin_bottom)
self._create_python_attribute(pfm_node, "marginRight", value=self.margin_right)
self._create_python_attribute(pfm_node, "justify", value=self.justify)
if clear_color is not None:
self._create_python_attribute(pfm_node, "clearColorR", value=clear_color[0])
self._create_python_attribute(pfm_node, "clearColorG", value=clear_color[1])
self._create_python_attribute(pfm_node, "clearColorB", value=clear_color[2])
self._create_python_attribute(pfm_node, "clearColorA", value=1.0)
@property
def localization_set(self):
return "DynaTexts"
def sanity_check(self):
if self.texture is None:
raise ExportError("'{}': Localized Text modifier requires a texture", self.id_data.name)
class PlasmaShadowCasterMod(PlasmaModifierProperties):
pl_id = "rtshadow"
bl_category = "Render"
bl_label = "Cast RT Shadow"
bl_description = "Cast runtime shadows"
blur = IntProperty(name="Blur",
description="Blur factor for the shadow map",
min=0, max=100, default=0,
subtype="PERCENTAGE", options=set())
boost = IntProperty(name="Boost",
description="Multiplies the shadow's power",
min=0, max=5000, default=100,
subtype="PERCENTAGE", options=set())
falloff = IntProperty(name="Falloff",
description="Multiplier for each lamp's falloff value",
min=10, max=1000, default=100,
subtype="PERCENTAGE", options=set())
limit_resolution = BoolProperty(name="Limit Resolution",
description="Increase performance by halving the resolution of the shadow map",
default=False,
options=set())
self_shadow = BoolProperty(name="Self Shadow",
description="Object can cast shadows on itself",
default=False,
options=set())
def export(self, exporter, bo, so):
caster = exporter.mgr.find_create_object(plShadowCaster, so=so, name=self.key_name)
caster.attenScale = self.falloff / 100.0
caster.blurScale = self.blur / 100.0
caster.boost = self.boost / 100.0
if self.limit_resolution:
caster.castFlags |= plShadowCaster.kLimitRes
if self.self_shadow:
caster.castFlags |= plShadowCaster.kSelfShadow
class PlasmaViewFaceMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
pl_id = "viewfacemod"
bl_category = "Render"
bl_label = "Swivel"
bl_description = "Swivel object to face the camera, player, or another object"
preset_options = EnumProperty(name="Type",
description="Type of Facing",
items=[
("Billboard", "Billboard", "Face the camera (Y Axis only)"),
("Sprite", "Sprite", "Face the camera (All Axis)"),
("Custom", "Custom", "Custom Swivel"),
])
follow_mode = EnumProperty(name="Target Type",
description="Target of the swivel",
items=[
("kFaceCam", "Camera", "Face the camera"),
("kFaceList", "Listener", "Face listeners"),
("kFacePlay", "Player", "Face the local player"),
("kFaceObj", "Object", "Face an object"),
])
target = PointerProperty(name="Target Object",
description="Object to face",
type=bpy.types.Object)
pivot_on_y = BoolProperty(name="Pivot on local Y",
description="Swivel only around the local Y axis",
default=False)
offset = BoolProperty(name="Offset", description="Use offset vector", default=False)
offset_local = BoolProperty(name="Local", description="Use local coordinates", default=False)
offset_coord = FloatVectorProperty(name="", subtype="XYZ")
def export(self, exporter, bo, so):
vfm = exporter.mgr.find_create_object(plViewFaceModifier, so=so, name=self.key_name)
# Set a default scaling (libHSPlasma will set this to 0 otherwise).
vfm.scale = hsVector3(1,1,1)
l2p = utils.matrix44(bo.matrix_local)
vfm.localToParent = l2p
vfm.parentToLocal = l2p.inverse()
# Cyan has these as separate components, but they're really just preset
# options for common swivels. We've consolidated them both here, along
# with the fully-customizable swivel as a third option.
if self.preset_options == "Billboard":
vfm.setFlag(plViewFaceModifier.kFaceCam, True)
vfm.setFlag(plViewFaceModifier.kPivotY, True)
elif self.preset_options == "Sprite":
vfm.setFlag(plViewFaceModifier.kFaceCam, True)
vfm.setFlag(plViewFaceModifier.kPivotFace, True)
elif self.preset_options == "Custom":
# For the discerning artist, full control over their swivel options!
vfm.setFlag(getattr(plViewFaceModifier, self.follow_mode), True)
if self.follow_mode == "kFaceObj":
# If this swivel is following an object, make sure that the
# target has been selected and is a valid SO.
if self.target:
vfm.faceObj = exporter.mgr.find_create_key(plSceneObject, bl=self.target)
else:
raise ExportError("'{}': Swivel's target object must be selected".format(self.key_name))
if self.pivot_on_y:
vfm.setFlag(plViewFaceModifier.kPivotY, True)
else:
vfm.setFlag(plViewFaceModifier.kPivotFace, True)
if self.offset:
vfm.offset = hsVector3(*self.offset_coord)
if self.offset_local:
vfm.setFlag(plViewFaceModifier.kOffsetLocal, True)
@classmethod
def _idprop_mapping(cls):
return {"target": "target_object"}
@property
def requires_actor(self):
return True
class PlasmaVisControl(idprops.IDPropObjectMixin, PlasmaModifierProperties):
pl_id = "visregion"
bl_category = "Render"
bl_label = "Visibility Control"
bl_description = "Controls object visibility using VisRegions"
mode = EnumProperty(name="Mode",
description="Purpose of the VisRegion",
items=[("normal", "Normal", "Objects are only visible when the camera is inside this region"),
("exclude", "Exclude", "Objects are only visible when the camera is outside this region"),
("fx", "Special FX", "This is a list of objects used for special effects only")])
soft_region = PointerProperty(name="Region",
description="Object defining the SoftVolume for this VisRegion",
type=bpy.types.Object,
poll=idprops.poll_softvolume_objects)
replace_normal = BoolProperty(name="Hide Drawables",
description="Hides drawables attached to this region",
default=True)
def export(self, exporter, bo, so):
rgn = exporter.mgr.find_create_object(plVisRegion, bl=bo, so=so)
rgn.setProperty(plVisRegion.kReplaceNormal, self.replace_normal)
if self.mode == "fx":
rgn.setProperty(plVisRegion.kDisable, True)
else:
this_sv = bo.plasma_modifiers.softvolume
if this_sv.enabled:
exporter.report.msg("[VisRegion] I'm a SoftVolume myself :)", indent=1)
rgn.region = this_sv.get_key(exporter, so)
else:
if not self.soft_region:
raise ExportError("'{}': Visibility Control must have a Soft Volume selected".format(self.key_name))
sv_bo = self.soft_region
sv = sv_bo.plasma_modifiers.softvolume
exporter.report.msg("[VisRegion] SoftVolume '{}'", sv_bo.name, indent=1)
if not sv.enabled:
raise ExportError("'{}': '{}' is not a SoftVolume".format(self.key_name, sv_bo.name))
rgn.region = sv.get_key(exporter)
rgn.setProperty(plVisRegion.kIsNot, self.mode == "exclude")
@classmethod
def _idprop_mapping(cls):
return {"soft_region": "softvolume"}
class VisRegion(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
enabled = BoolProperty(default=True)
control_region = PointerProperty(name="Control",
description="Object defining a Plasma Visibility Control",
type=bpy.types.Object,
poll=idprops.poll_visregion_objects)
@classmethod
def _idprop_mapping(cls):
return {"control_region": "region_name"}
class PlasmaVisibilitySet(PlasmaModifierProperties):
pl_id = "visibility"
bl_category = "Render"
bl_label = "Visibility Set"
bl_description = "Defines areas where this object is visible"
regions = CollectionProperty(name="Visibility Regions",
type=VisRegion)
active_region_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
if not self.regions:
# TODO: Log message about how this modifier is totally worthless
return
# Currently, this modifier is valid for meshes and lamps
if bo.type == "MESH":
diface = exporter.mgr.find_create_object(plDrawInterface, bl=bo, so=so)
addRegion = diface.addRegion
elif bo.type == "LAMP":
light = exporter.light.get_light_key(bo, bo.data, so)
addRegion = light.object.addVisRegion
for region in self.regions:
if not region.enabled:
continue
if not region.control_region:
raise ExportError("{}: Not all Visibility Controls are set up properly in Visibility Set".format(bo.name))
addRegion(exporter.mgr.find_create_key(plVisRegion, bl=region.control_region))