diff --git a/korman/exporter/explosions.py b/korman/exporter/explosions.py index 63e9774..f21ca6c 100644 --- a/korman/exporter/explosions.py +++ b/korman/exporter/explosions.py @@ -32,6 +32,11 @@ class ExportError(Exception): super(Exception, self).__init__(args[0]) +class BlendNotSupported(ExportError): + def __init__(self, progression, axis): + super(ExportError, self).__init__("Alpha Blend not supported: {}, {}", progression, axis) + + class BlenderOptionNotSupportedError(ExportError): def __init__(self, opt): super(ExportError, self).__init__("Unsupported Blender Option: '{}'".format(opt)) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 99e2a85..a2e29bf 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -136,6 +136,7 @@ class MaterialConverter: self._pending = {} self._alphatest = {} self._tex_exporters = { + "BLEND": self._export_texture_type_blend, "ENVIRONMENT_MAP": self._export_texture_type_environment_map, "IMAGE": self._export_texture_type_image, "NONE": self._export_texture_type_none, @@ -715,6 +716,112 @@ class MaterialConverter: # We'll allow this, just for sanity's sake... pass + def _export_texture_type_blend(self, bo, layer, slot): + # This has been separated out because other things may need alpha blend textures. + texture = slot.texture + self.export_alpha_blend(texture.progression, texture.use_flip_axis, layer) + + def export_alpha_blend(self, progression, axis, owner, indent=2): + """This exports an alpha blend texture as exposed by bpy.types.BlendTexture. + The following arguments are expected: + - progression: (required) + - axis: (required) + - owner: (required) the Plasma object using this image + - indent: (optional) indentation level for log messages + default: 2 + """ + + # Certain blend types don't use an axis... + progression_axes = {"EASING", "LINEAR", "RADIAL", "QUADRATIC"} + if progression in progression_axes: + filename = "ALPHA_BLEND_{}_{}".format(progression, axis) + else: + filename = "ALPHA_BLEND_{}".format(progression) + axis = "" + + image = bpy.data.images.get(filename) + if image is None: + def _calc_diagonal(x, y, width, height): + distance = math.sqrt(pow(x, 2) + pow(y, 2)) + total = math.sqrt(pow(width, 2) + pow(height, 2)) + return distance / total + + def _calc_radial(x, y, width, height, horizontal=None): + if horizontal is True: + relative = (y - height / 2, x - width / 2) + elif horizontal is False: + relative = (x - width / 2, y - height / 2) + else: + raise RuntimeError() + angle = math.atan2(*relative) + math.pi + # PyPRP had some weird code that looked like an infinite loop for clamping from + # zero through 2pi. atan2 is documented to return in the range of -pi through pi. + two_pi = math.pi * 2 + if angle < 0.0: + angle += two_pi + return max(0.0, angle / two_pi) + + def _calc_lin_sphere(x, y, width, height): + half_width, half_height = width / 2, height / 2 + distance = math.sqrt(pow(x - half_width, 2) + pow(y - half_height, 2)) + value = math.cos(distance / half_width * 0.5 * math.pi) + if value < 0.0 or distance > half_width: + return 0.0 + else: + return min(1.0, value) + + def _calc_quad_sphere(x, y, width, height): + half_width, half_height = width / 2, height / 2 + distance = math.sqrt(pow(x - half_width, 2) + pow(y - half_height, 2)) + value = 0.5 + (0.5 * math.cos(distance / half_width * math.pi)) + if value < 0.0 or distance > half_width: + return 0.0 + else: + return min(1.0, value) + + dimensions = { + ("EASING", "HORIZONTAL"): (64, 4), + ("EASING", "VERTICAL"): (4, 64), + ("LINEAR", "HORIZONTAL"): (64, 4), + ("LINEAR", "VERTICAL"): (4, 64), + ("QUADRATIC", "HORIZONTAL"): (64, 4), + ("QUADRATIC", "VERTICAL"): (4, 64), + } + funcs = { + ("DIAGONAL", ""): _calc_diagonal, + ("EASING", "HORIZONTAL"): lambda x, y, width, height: 0.5 - math.cos(x / width * math.pi) * 0.5, + ("EASING", "VERTICAL"): lambda x, y, width, height: 0.5 - math.cos(y / height * math.pi) * 0.5, + ("LINEAR", "HORIZONTAL"): lambda x, y, width, height: x / width, + ("LINEAR", "VERTICAL"): lambda x, y, width, height: y / height, + ("QUADRATIC", "HORIZONTAL"): lambda x, y, width, height: pow(x / width, 2), + ("QUADRATIC", "VERTICAL"): lambda x, y, width, height: pow(y / height, 2), + ("QUADRATIC_SPHERE", ""): _calc_quad_sphere, + ("RADIAL", "HORIZONTAL"): functools.partial(_calc_radial, horizontal=True), + ("RADIAL", "VERTICAL"): functools.partial(_calc_radial, horizontal=False), + ("SPHERICAL", ""): _calc_lin_sphere, + } + + blend_type = (progression, axis) + width, height = dimensions.get(blend_type, (64, 64)) + pixels = [None] * (width * height * 4) + func = funcs.get(blend_type) + if func is None: + raise BlendNotSupported(progression, axis) + + # This is slower than a custom writer for each blend texture, but that would be uglier + # and less maintainable. Running this function in the Blender console is nearly instant, + # so I think this is the best option, really. + for x in range(width): + for y in range(height): + offset = (y * width * 4) + (x * 4) + value = func(x, y, width, height) + pixels[offset:offset+4] = (value,) * 4 + image = bpy.data.images.new(filename, width=width, height=height, alpha=True) + image.pixels = pixels + + self.export_prepared_image(image=image, owner=owner, allowed_formats={"BMP"}, + alpha_type=TextureAlpha.full, indent=2, ephemeral=True) + def export_prepared_image(self, **kwargs): """This exports an externally prepared image and an optional owning layer. The following arguments are typical: