Last active
March 24, 2024 11:45
-
-
Save ES-Alexander/84a0f12d097d409bf70fed07f252ae91 to your computer and use it in GitHub Desktop.
Projective Perspective
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from fullcontrol.visualize.tube_mesh import FlowTubeMesh, go, np | |
# Display parameters | |
COLOURS = '#666' # 'black' / '#C0FFEE' / None | |
SHOW = True # if False, saves animation frames instead | |
# Geometry parameters | |
np.random.seed(sum(b'gcode')) | |
SEGMENTS_PER_CURVE = 31 | |
BASE_THICKNESS = 0.6 # 0.2 | |
RANDOM_THICKNESS_VARIATION = None # 0.4 | |
CHARACTER_WIDTH = 2.0 | |
CHARACTER_SPACING = 1.0 | |
# Animation parameters | |
TOTAL_DURATION_S = 2. # Desired animation length | |
DESIRED_FPS = 30 # Desired animation framerate | |
SLOWDOWN_FACTOR = 0. # Text slowdowns (at the expense of faster transitions) -> (-1, inf) | |
# Internal parameters - DO NOT CHANGE | |
_BASE_CHAR_WIDTH = 2.0 | |
_BASE_CHAR_SPACING = 1.0 | |
n_chars = 7 | |
n_spaces = n_chars - 1 | |
_HORIZONTAL_STRETCH = ( | |
(n_chars*CHARACTER_WIDTH + n_spaces*CHARACTER_SPACING) | |
/ (n_chars*_BASE_CHAR_WIDTH + n_spaces*_BASE_CHAR_SPACING) | |
) | |
def adjust(letter, i): | |
letter[:,0] = ( | |
(letter[:,0] - i*(_BASE_CHAR_WIDTH + _BASE_CHAR_SPACING)) | |
* CHARACTER_WIDTH / _BASE_CHAR_WIDTH | |
+ i*(CHARACTER_WIDTH + CHARACTER_SPACING) | |
) | |
F_left__C_top = np.float64([ | |
[0,0,0], | |
[0,4,0], | |
*([(1+np.sqrt(2))/2+(1+np.sqrt(2))/2*np.cos(theta), 4, 2*np.sin(theta)] | |
for theta in np.linspace(np.pi, np.pi/4, SEGMENTS_PER_CURVE)) | |
]) | |
F_middle__C_bottom = np.float64([ | |
*([(1+np.sqrt(2))/2+(1+np.sqrt(2))/2*np.cos(theta), 2, 2*np.sin(theta)] | |
for theta in np.linspace(np.pi, 7*np.pi/4, SEGMENTS_PER_CURVE)) | |
]) | |
gap__O1 = np.float64([ | |
*([4+np.cos(theta), 0, 2*np.sin(theta)] | |
for theta in np.linspace(0, 2*np.pi, SEGMENTS_PER_CURVE)) | |
]) | |
U__N = np.float64([ | |
[6,4,-2], | |
[6,1,2], | |
*([7+np.cos(theta), 1+np.sin(theta), -2*np.cos(theta)] | |
for theta in np.linspace(np.pi, 2*np.pi, SEGMENTS_PER_CURVE)), | |
[8,4,2] | |
]) | |
gap__T_vert = np.float64([ | |
[10,0,-2], | |
[10,0,2] | |
]) | |
gap__T_top = np.float64([ | |
[9,0,2], | |
[11,0,2] | |
]) | |
L1__R = np.float64([ | |
[12,4,-2], | |
[12,0,2], | |
[12.7,0,2], # Decent corner without sharp transition to curve | |
*([13+np.cos(theta), 0, 1+np.sin(theta)] | |
for theta in np.linspace(np.pi/2, -np.pi/2, SEGMENTS_PER_CURVE)), | |
[12,0,0], | |
[14,0,-2] | |
]) | |
gap__O2 = np.float64([ | |
*([16+np.cos(theta), 0, 2*np.sin(theta)] | |
for theta in np.linspace(0, 2*np.pi, SEGMENTS_PER_CURVE)) | |
]) | |
L2__L = np.float64([ | |
[18,4,2], | |
[18,0,-2], | |
[20,0,-2] | |
]) | |
meshes = [] | |
for index, character in enumerate(( | |
(F_left__C_top, F_middle__C_bottom,), | |
(gap__O1,), | |
(U__N,), | |
(gap__T_vert, gap__T_top,), | |
(L1__R,), | |
(gap__O2,), | |
(L2__L,), | |
)): | |
for line in character: | |
adjust(line, index) | |
if RANDOM_THICKNESS_VARIATION: | |
widths = np.random.random(len(line)) * RANDOM_THICKNESS_VARIATION + BASE_THICKNESS | |
if len(widths) > 2: | |
widths[-1] = widths[0] # match start and end for smooth looped meshes | |
else: | |
widths = BASE_THICKNESS | |
meshes.append( | |
FlowTubeMesh( | |
line, deviation_threshold_degrees=80, widths=widths, sides=8, | |
rounding_strength=1, flat_sides=False, capped=False | |
).to_Mesh3d(colors=COLOURS) | |
) | |
fig = go.Figure(meshes) | |
fig.update_scenes( | |
aspectmode='data', camera_projection_type='orthographic', dragmode='orbit', | |
xaxis_visible=False, yaxis_visible=False, zaxis_visible=False, | |
) | |
if SHOW: | |
fig.show() | |
exit() | |
# Determine animation parameters | |
centiseconds_per_frame = round(100 / DESIRED_FPS) | |
print(f'{centiseconds_per_frame = }') | |
# Enforce an even number of steps, as close as possible to the specified FPS & duration | |
output_fps = 100 / centiseconds_per_frame | |
animation_steps = round(TOTAL_DURATION_S * output_fps / 2) * 2 | |
output_duration = animation_steps / output_fps | |
print(f'{output_fps = }\n{output_duration = :.2f}s') | |
# Easing to slow only at F_U_L_L and CONTROL | |
x = np.linspace(0, np.pi, animation_steps//2, endpoint=False) | |
slow_fast_slow = 1 - np.sqrt((1 + SLOWDOWN_FACTOR) / (1 + SLOWDOWN_FACTOR * np.cos(x)**2)) * np.cos(x) | |
# 50% for 1/4 turn from F_U_L_L -> CONTROL, then 50% for 3/4 turn from CONTROL -> F_U_L_L | |
thetas = np.hstack([np.pi/4 * slow_fast_slow, (2*np.pi-np.pi/2)/2 * slow_fast_slow + np.pi/2]) | |
#import cv2 | |
#t = np.empty((400,1120,4), dtype=np.uint8) | |
for index, theta in enumerate(thetas): | |
print(f'\b\r{index}/{len(thetas)}', end='', flush=True) | |
fig.update_scenes( | |
camera_up_x=0, camera_up_z=np.sin(theta), camera_up_y=np.cos(theta), | |
camera_eye_x=0.001*np.sin(theta), camera_eye_z=np.cos(theta), camera_eye_y=-np.sin(theta), | |
) | |
filename = f'images/black/{10*np.rad2deg(theta):04.0f}.png' | |
fig.write_image(filename, width=round(1500 * _HORIZONTAL_STRETCH), height=1000, scale=1) | |
#image = cv2.imread(filename) | |
#cropped = image[300:700, 190:1310] | |
#cv2.imwrite(filename, cropped) | |
#t[:,:,:3] = cropped | |
#t[:,:,3] = 255 | |
#t[np.all(t[:,:,:3] == 255, axis=2)] = 0 | |
#cv2.imwrite(filename[:-4] + '_transparent.png', t) |
- Added some parameters for colour, thickness, variation, and horizontal stretch control
COLOURS = None
--> the default "new colour per tube" behaviour
- Reliant on a FullControl version with this PR merged
- Changed horizontal stretch control into independent character width and spacing controls
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added some animation parameters, and a seed for the randomness so it's possible to reproduce results (e.g. to generate a different animation of the same text).