Shofel2_T124_python/venv/lib/python3.10/site-packages/asciimatics/renderers/kaleidoscope.py

96 lines
4.3 KiB
Python

# -*- coding: utf-8 -*-
"""
This module implements a kaeldioscope effect renderer.
"""
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
from builtins import range
from math import sin, cos, pi, atan2
from asciimatics.renderers.base import DynamicRenderer
class Kaleidoscope(DynamicRenderer):
"""
Renderer to create a 2-mirror kaleidoscope effect.
This is a chained renderer (i.e. it acts upon the output of another Renderer which is
passed to it on construction). The other Renderer is used as the cell that is rotated over
time to create the animation.
You can specify the desired rotational symmetry of the kaleidoscope (which determines the
angle between the mirrors). If you chose values of less than 2, you are effectively removing
one or both mirrors, thus either getting the original cell or a simple mirrored image of the
cell.
Since this renderer rotates the background cell, it needs operate on square pixels, which
means each character in the cell is drawn as 2 next to each other on the screen. In other
words the cell needs to be half the width of the desired output (when measured in text
characters).
"""
def __init__(self, height, width, cell, symmetry):
"""
:param height: Height of the box to contain the kaleidoscope.
:param width: Width of the box to contain the kaleidoscope.
:param cell: A Renderer to use as the backing cell for the kaleidoscope.
:param symmetry: The desired rotational symmetry. Must be a non-negative integer.
"""
super(Kaleidoscope, self).__init__(height, width)
self._symmetry = symmetry
self._rotation = 0
self._cell = cell
def _render_now(self):
# Rotate a point (x, y) through an angle theta.
def _rotate(x, y, theta):
return x * cos(theta) - y * sin(theta), x * sin(theta) + y * cos(theta)
# Reflect a point (x, y) in a line at angle theta
def _reflect(x, y, theta):
return x * cos(2 * theta) + y * sin(2 * theta), x * sin(2 * theta) - y * cos(2 * theta)
# Get the base cell now - so we can pick out characters as needed.
text, colour_map = self._cell.rendered_text
# Integer maths will result in gaps between characters if you rotate from the starting
# point to desired end-point. We therefore look for the reverse mapping from the final
# character and trace-back instead.
for dx in range(self._canvas.width // 2):
for dy in range(self._canvas.height):
# Figure out which segment of the circle we're in, so we know what affine
# transformations to apply.
ox = (dx - self._canvas.width / 4)
oy = dy - self._canvas.height / 2
segment = round(atan2(oy, ox) * self._symmetry / pi)
if segment % 2 == 0:
# Just a rotation required for even segments.
x1, y1 = _rotate(
ox, oy, 0 if self._symmetry == 0 else -segment * pi / self._symmetry)
else:
# Odd segments require a rotation and then a reflection.
x1, y1 = _rotate(ox, oy, (1 - segment) * pi / self._symmetry)
x1, y1 = _reflect(x1, y1, pi / self._symmetry / 2)
# Now rotate once more to simulate the rotation of the background cell too.
x1, y1 = _rotate(x1, y1, self._rotation)
# Re-normalize back to the box coordinates and draw the character that we found
# from the reverse mapping.
x2 = int(x1 + self._cell.max_width / 2)
y2 = int(y1 + self._cell.max_height / 2)
if (0 <= y2 < len(text)) and (0 <= x2 < len(text[y2])):
self._write(text[y2][x2] + text[y2][x2],
dx * 2,
dy,
colour_map[y2][x2][0],
colour_map[y2][x2][1],
colour_map[y2][x2][2])
# Now rotate the background cell for the next frame.
self._rotation += pi / 180
return self._plain_image, self._colour_map