mirror of
https://github.com/OpenVGLab/OmniLottie.git
synced 2026-09-17 07:36:27 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,544 @@
|
||||
import random
|
||||
import math
|
||||
from ..nvector import NVector
|
||||
from ..objects.shapes import Path
|
||||
from .. import objects
|
||||
from ..objects import easing
|
||||
from ..objects import properties
|
||||
|
||||
|
||||
def shake(position_prop, x_radius, y_radius, start_time, end_time, n_frames, interp=easing.Linear()):
|
||||
if not isinstance(position_prop, list):
|
||||
position_prop = [position_prop]
|
||||
|
||||
n_frames = int(round(n_frames))
|
||||
frame_time = (end_time - start_time) / n_frames
|
||||
startpoints = list(map(
|
||||
lambda pp: pp.get_value(start_time),
|
||||
position_prop
|
||||
))
|
||||
|
||||
for i in range(n_frames):
|
||||
x = (random.random() * 2 - 1) * x_radius
|
||||
y = (random.random() * 2 - 1) * y_radius
|
||||
for pp, start in zip(position_prop, startpoints):
|
||||
px = start[0] + x
|
||||
py = start[1] + y
|
||||
pp.add_keyframe(start_time + i * frame_time, NVector(px, py), interp)
|
||||
|
||||
for pp, start in zip(position_prop, startpoints):
|
||||
pp.add_keyframe(end_time, start, interp)
|
||||
|
||||
|
||||
def rot_shake(rotation_prop, angles, start_time, end_time, n_frames):
|
||||
frame_time = (end_time - start_time) / n_frames
|
||||
start = rotation_prop.get_value(start_time)
|
||||
|
||||
for i in range(0, n_frames):
|
||||
a = angles[i % len(angles)] * math.sin(i/n_frames * math.pi)
|
||||
rotation_prop.add_keyframe(start_time + i * frame_time, start + a)
|
||||
rotation_prop.add_keyframe(end_time, start)
|
||||
|
||||
|
||||
def spring_pull(position_prop, point, start_time, end_time, falloff=15, oscillations=7):
|
||||
start = position_prop.get_value(start_time)
|
||||
d = start-point
|
||||
|
||||
delta = (end_time - start_time) / oscillations
|
||||
|
||||
for i in range(oscillations):
|
||||
time_x = i / oscillations
|
||||
factor = math.cos(time_x * math.pi * oscillations) * (1-time_x**(1/falloff))
|
||||
p = point + d * factor
|
||||
position_prop.add_keyframe(start_time + delta * i, p)
|
||||
|
||||
position_prop.add_keyframe(end_time, point)
|
||||
|
||||
|
||||
def follow_path(position_prop, bezier, start_time, end_time, n_keyframes,
|
||||
reverse=False, offset=NVector(0, 0), start_t=0, rotation_prop=None, rotation_offset=0):
|
||||
delta = (end_time - start_time) / (n_keyframes-1)
|
||||
fact = start_t
|
||||
factd = 1 / (n_keyframes-1)
|
||||
|
||||
if rotation_prop:
|
||||
start_rot = rotation_prop.get_value(start_time) if rotation_offset is None else rotation_offset
|
||||
|
||||
for i in range(n_keyframes):
|
||||
time = start_time + i * delta
|
||||
|
||||
if fact > 1 + factd/2:
|
||||
fact -= 1
|
||||
if time != start_time:
|
||||
easing.Jump()(position_prop.keyframes[-1])
|
||||
if rotation_prop:
|
||||
easing.Jump()(rotation_prop.keyframes[-1])
|
||||
|
||||
f = 1 - fact if reverse else fact
|
||||
position_prop.add_keyframe(time, bezier.point_at(f)+offset)
|
||||
|
||||
if rotation_prop:
|
||||
rotation_prop.add_keyframe(time, bezier.tangent_angle_at(f) / math.pi * 180 + start_rot)
|
||||
|
||||
fact += factd
|
||||
|
||||
|
||||
def generate_path_appear(bezier, appear_start, appear_end, n_keyframes, reverse=False):
|
||||
obj = Path()
|
||||
beziers = []
|
||||
maxp = 0
|
||||
|
||||
time_delta = (appear_end - appear_start) / n_keyframes
|
||||
for i in range(n_keyframes+1):
|
||||
time = appear_start + i * time_delta
|
||||
t2 = (time - appear_start) / (appear_end - appear_start)
|
||||
|
||||
if reverse:
|
||||
t2 = 1 - t2
|
||||
segment = bezier.segment(t2, 1)
|
||||
segment.reverse()
|
||||
else:
|
||||
segment = bezier.segment(0, t2)
|
||||
|
||||
beziers.append(segment)
|
||||
if len(segment.vertices) > maxp:
|
||||
maxp = len(segment.vertices)
|
||||
|
||||
obj.shape.add_keyframe(time, segment)
|
||||
|
||||
for segment in beziers:
|
||||
deltap = maxp - len(segment.vertices)
|
||||
if deltap > 0:
|
||||
segment.vertices += [segment.vertices[-1]] * deltap
|
||||
segment.in_tangents += [NVector(0, 0)] * deltap
|
||||
segment.out_tangents += [NVector(0, 0)] * deltap
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def generate_path_disappear(bezier, disappear_start, disappear_end, n_keyframes, reverse=False):
|
||||
obj = Path()
|
||||
beziers = []
|
||||
maxp = 0
|
||||
|
||||
time_delta = (disappear_end - disappear_start) / n_keyframes
|
||||
for i in range(n_keyframes+1):
|
||||
time = disappear_start + i * time_delta
|
||||
t1 = (time - disappear_start) / (disappear_end - disappear_start)
|
||||
if reverse:
|
||||
t1 = 1 - t1
|
||||
segment = bezier.segment(0, t1)
|
||||
else:
|
||||
segment = bezier.segment(1, t1)
|
||||
segment.reverse()
|
||||
|
||||
beziers.append(segment)
|
||||
if len(segment.vertices) > maxp:
|
||||
maxp = len(segment.vertices)
|
||||
|
||||
obj.shape.add_keyframe(time, segment)
|
||||
|
||||
for segment in beziers:
|
||||
deltap = maxp - len(segment.vertices)
|
||||
if deltap > 0:
|
||||
segment.vertices += [segment.vertices[-1]] * deltap
|
||||
segment.in_tangents += [NVector(0, 0)] * deltap
|
||||
segment.out_tangents += [NVector(0, 0)] * deltap
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def generate_path_segment(bezier, appear_start, appear_end, disappear_start, disappear_end, n_keyframes, reverse=False):
|
||||
obj = Path()
|
||||
beziers = []
|
||||
maxp = 0
|
||||
|
||||
# HACK: For some reson reversed works better
|
||||
if not reverse:
|
||||
bezier.reverse()
|
||||
|
||||
time_delta = (appear_end - appear_start) / n_keyframes
|
||||
for i in range(n_keyframes+1):
|
||||
time = appear_start + i * time_delta
|
||||
t1 = (time - disappear_start) / (disappear_end - disappear_start)
|
||||
t2 = (time - appear_start) / (appear_end - appear_start)
|
||||
|
||||
t1 = max(0, min(1, t1))
|
||||
t2 = max(0, min(1, t2))
|
||||
|
||||
#if reverse:
|
||||
if True:
|
||||
t1 = 1 - t1
|
||||
t2 = 1 - t2
|
||||
segment = bezier.segment(t2, t1)
|
||||
segment.reverse()
|
||||
#else:
|
||||
#segment = bezier.segment(t1, t2)
|
||||
#segment.reverse()
|
||||
|
||||
beziers.append(segment)
|
||||
if len(segment.vertices) > maxp:
|
||||
maxp = len(segment.vertices)
|
||||
|
||||
obj.shape.add_keyframe(time, segment)
|
||||
|
||||
for segment in beziers:
|
||||
deltap = maxp - len(segment.vertices)
|
||||
if deltap > 0:
|
||||
segment.split_self_chunks(deltap+1)
|
||||
|
||||
# HACK: Restore
|
||||
if not reverse:
|
||||
bezier.reverse()
|
||||
return obj
|
||||
|
||||
|
||||
class PointDisplacer:
|
||||
def __init__(self, time_start, time_end, n_frames):
|
||||
"""!
|
||||
@param time_start When the animation shall start
|
||||
@param time_end When the animation shall end
|
||||
@param n_frames Number of frames in the animation
|
||||
"""
|
||||
## When the animation shall start
|
||||
self.time_start = time_start
|
||||
## When the animation shall end
|
||||
self.time_end = time_end
|
||||
## Number of frames in the animation
|
||||
self.n_frames = n_frames
|
||||
## Length of a frame
|
||||
self.time_delta = (time_end - time_start) / n_frames
|
||||
|
||||
def animate_point(self, prop):
|
||||
startpos = prop.get_value(self.time_start)
|
||||
for f in range(self.n_frames+1):
|
||||
p = self._on_displace(startpos, f)
|
||||
prop.add_keyframe(self.frame_time(f), startpos+p)
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
raise NotImplementedError()
|
||||
|
||||
def animate_bezier(self, prop):
|
||||
initial = prop.get_value(self.time_start)
|
||||
|
||||
for f in range(self.n_frames+1):
|
||||
bezier = objects.Bezier()
|
||||
bezier.closed = initial.closed
|
||||
|
||||
for pi in range(len(initial.vertices)):
|
||||
startpos = initial.vertices[pi]
|
||||
dp = self._on_displace(startpos, f)
|
||||
t1sp = initial.in_tangents[pi] + startpos
|
||||
t1fin = initial.in_tangents[pi] + self._on_displace(t1sp, f) - dp
|
||||
t2sp = initial.out_tangents[pi] + startpos
|
||||
t2fin = initial.out_tangents[pi] + self._on_displace(t2sp, f) - dp
|
||||
|
||||
bezier.add_point(dp + startpos, t1fin, t2fin)
|
||||
|
||||
prop.add_keyframe(self.frame_time(f), bezier)
|
||||
|
||||
def frame_time(self, f):
|
||||
return f * self.time_delta + self.time_start
|
||||
|
||||
def _init_lerp(self, val_from, val_to, easing):
|
||||
self._kf = properties.OffsetKeyframe(0, NVector(val_from), NVector(val_to), easing)
|
||||
|
||||
def _lerp_get(self, offset):
|
||||
return self._kf.interpolated_value(offset / self.n_frames)[0]
|
||||
|
||||
|
||||
class SineDisplacer(PointDisplacer):
|
||||
def __init__(
|
||||
self,
|
||||
wavelength,
|
||||
amplitude,
|
||||
time_start,
|
||||
time_end,
|
||||
n_frames,
|
||||
speed=1,
|
||||
axis=90,
|
||||
):
|
||||
"""!
|
||||
Displaces points as if they were following a sine wave
|
||||
|
||||
@param wavelength Distance between consecutive peaks
|
||||
@param amplitude Distance from a peak to the original position
|
||||
@param time_start When the animation shall start
|
||||
@param time_end When the animation shall end
|
||||
@param n_frames Number of keyframes to add
|
||||
@param speed Number of peaks a point will go through in the given time
|
||||
If negative, it will go the other way
|
||||
@param axis Wave peak direction
|
||||
"""
|
||||
super().__init__(time_start, time_end, n_frames)
|
||||
|
||||
self.wavelength = wavelength
|
||||
self.amplitude = amplitude
|
||||
self.speed_f = math.pi * 2 * speed
|
||||
self.axis = axis / 180 * math.pi
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
off = -math.sin(startpos[0]/self.wavelength*math.pi*2-f*self.speed_f/self.n_frames) * self.amplitude
|
||||
return NVector(off * math.cos(self.axis), off * math.sin(self.axis))
|
||||
|
||||
|
||||
class MultiSineDisplacer(PointDisplacer):
|
||||
def __init__(
|
||||
self,
|
||||
waves,
|
||||
time_start,
|
||||
time_end,
|
||||
n_frames,
|
||||
speed=1,
|
||||
axis=90,
|
||||
amplitude_scale=1,
|
||||
):
|
||||
"""!
|
||||
Displaces points as if they were following a sine wave
|
||||
|
||||
@param waves List of tuples (wavelength, amplitude)
|
||||
@param time_start When the animation shall start
|
||||
@param time_end When the animation shall end
|
||||
@param n_frames Number of keyframes to add
|
||||
@param speed Number of peaks a point will go through in the given time
|
||||
If negative, it will go the other way
|
||||
@param axis Wave peak direction
|
||||
@param amplitude_scale Multiplies the resulting amplitude by this factor
|
||||
"""
|
||||
super().__init__(time_start, time_end, n_frames)
|
||||
|
||||
self.waves = waves
|
||||
self.speed_f = math.pi * 2 * speed
|
||||
self.axis = axis / 180 * math.pi
|
||||
self.amplitude_scale = amplitude_scale
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
off = 0
|
||||
for wavelength, amplitude in self.waves:
|
||||
off -= math.sin(startpos[0]/wavelength*math.pi*2-f*self.speed_f/self.n_frames) * amplitude
|
||||
|
||||
off *= self.amplitude_scale
|
||||
return NVector(off * math.cos(self.axis), off * math.sin(self.axis))
|
||||
|
||||
|
||||
class DepthRotationAxis:
|
||||
def __init__(self, x, y, keep):
|
||||
self.x = x / x.length
|
||||
self.y = y / y.length
|
||||
self.keep = keep / keep.length # should be the cross product
|
||||
|
||||
def rot_center(self, center, point):
|
||||
return (
|
||||
self.x * self.x.dot(center) +
|
||||
self.y * self.y.dot(center) +
|
||||
self.keep * self.keep.dot(point)
|
||||
)
|
||||
|
||||
def extract_component(self, vector, axis):
|
||||
return sum(vector.element_scaled(axis).components)
|
||||
|
||||
@classmethod
|
||||
def from_points(cls, keep_point, center=NVector(0, 0, 0)):
|
||||
keep = keep_point - center
|
||||
keep /= keep.length
|
||||
# Hughes-Moller to find x and y
|
||||
if abs(keep.x) > abs(keep.z):
|
||||
y = NVector(-keep.y, keep.x, 0)
|
||||
else:
|
||||
y = NVector(0, -keep.z, keep.y)
|
||||
y /= y.length
|
||||
x = y.cross(keep)
|
||||
return cls(x, y, keep)
|
||||
|
||||
|
||||
class DepthRotation:
|
||||
axis_x = DepthRotationAxis(NVector(0, 0, 1), NVector(0, 1, 0), NVector(1, 0, 0))
|
||||
axis_y = DepthRotationAxis(NVector(1, 0, 0), NVector(0, 0, 1), NVector(0, 1, 0))
|
||||
axis_z = DepthRotationAxis(NVector(1, 0, 0), NVector(0, 1, 0), NVector(0, 0, 1))
|
||||
|
||||
def __init__(self, center):
|
||||
self.center = center
|
||||
|
||||
def rotate3d_y(self, point, angle):
|
||||
return self.rotate3d(point, angle, self.axis_y)
|
||||
# Hard-coded version:
|
||||
#c = NVector(self.center.x, point.y, self.center.z)
|
||||
#rad = angle * math.pi / 180
|
||||
#delta = point - c
|
||||
#pol_l = delta.length
|
||||
#pol_a = math.atan2(delta.z, delta.x)
|
||||
#dest_a = pol_a + rad
|
||||
#return NVector(
|
||||
# c.x + pol_l * math.cos(dest_a),
|
||||
# point.y,
|
||||
# c.z + pol_l * math.sin(dest_a)
|
||||
#)
|
||||
|
||||
def rotate3d_x(self, point, angle):
|
||||
return self.rotate3d(point, angle, self.axis_x)
|
||||
# Hard-coded version:
|
||||
#c = NVector(point.x, self.center.y, self.center.z)
|
||||
#rad = angle * math.pi / 180
|
||||
#delta = point - c
|
||||
#pol_l = delta.length
|
||||
#pol_a = math.atan2(delta.y, delta.z)
|
||||
#dest_a = pol_a + rad
|
||||
#return NVector(
|
||||
# point.x,
|
||||
# c.y + pol_l * math.sin(dest_a),
|
||||
# c.z + pol_l * math.cos(dest_a),
|
||||
#)
|
||||
|
||||
def rotate3d_z(self, point, angle):
|
||||
return self.rotate3d(point, angle, self.axis_z)
|
||||
|
||||
def rotate3d(self, point, angle, axis):
|
||||
c = axis.rot_center(self.center, point)
|
||||
rad = angle * math.pi / 180
|
||||
delta = point - c
|
||||
pol_l = delta.length
|
||||
pol_a = math.atan2(
|
||||
axis.extract_component(delta, axis.y),
|
||||
axis.extract_component(delta, axis.x)
|
||||
)
|
||||
dest_a = pol_a + rad
|
||||
return c + axis.x * pol_l * math.cos(dest_a) + axis.y * pol_l * math.sin(dest_a)
|
||||
|
||||
|
||||
class DepthRotationDisplacer(PointDisplacer):
|
||||
axis_x = DepthRotation.axis_x
|
||||
axis_y = DepthRotation.axis_y
|
||||
axis_z = DepthRotation.axis_z
|
||||
|
||||
def __init__(self, center, time_start, time_end, n_frames, axis,
|
||||
depth=0, angle=360, anglestart=0, ease=easing.Linear()):
|
||||
super().__init__(time_start, time_end, n_frames)
|
||||
self.rotation = DepthRotation(center)
|
||||
if isinstance(axis, NVector):
|
||||
axis = DepthRotationAxis.from_points(axis)
|
||||
self.axis = axis
|
||||
self.depth = depth
|
||||
self._angle = angle
|
||||
self.anglestart = anglestart
|
||||
self.ease = ease
|
||||
self._init_lerp(0, angle, ease)
|
||||
|
||||
@property
|
||||
def angle(self):
|
||||
return self._angle
|
||||
|
||||
@angle.setter
|
||||
def angle(self, value):
|
||||
self._angle = value
|
||||
self._init_lerp(0, value, self.ease)
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
angle = self.anglestart + self._lerp_get(f)
|
||||
if len(startpos) < 3:
|
||||
startpos = NVector(*(startpos.components + [self.depth]))
|
||||
return self.rotation.rotate3d(startpos, angle, self.axis) - startpos
|
||||
|
||||
|
||||
class EnvelopeDeformation(PointDisplacer):
|
||||
def __init__(self, topleft, bottomright):
|
||||
self.topleft = topleft
|
||||
self.size = bottomright - topleft
|
||||
self.keyframes = []
|
||||
|
||||
@property
|
||||
def time_start(self):
|
||||
return self.keyframes[0][0]
|
||||
|
||||
def add_reset_keyframe(self, time):
|
||||
self.add_keyframe(
|
||||
time,
|
||||
self.topleft.clone(),
|
||||
NVector(self.topleft.x + self.size.x, self.topleft.y),
|
||||
NVector(self.topleft.x + self.size.x, self.topleft.y + self.size.y),
|
||||
NVector(self.topleft.x, self.topleft.y + self.size.y),
|
||||
)
|
||||
|
||||
def add_keyframe(self, time, tl, tr, br, bl):
|
||||
self.keyframes.append([
|
||||
time,
|
||||
tl.clone(),
|
||||
tr.clone(),
|
||||
br.clone(),
|
||||
bl.clone()
|
||||
])
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
_, tl, tr, br, bl = self.keyframes[f]
|
||||
relp = startpos - self.topleft
|
||||
relp.x /= self.size.x
|
||||
relp.y /= self.size.y
|
||||
|
||||
x1 = tl.lerp(tr, relp.x)
|
||||
x2 = bl.lerp(br, relp.x)
|
||||
|
||||
#return x1.lerp(x2, relp.y)
|
||||
return x1.lerp(x2, relp.y) - startpos
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return len(self.keyframes)-1
|
||||
|
||||
def frame_time(self, f):
|
||||
return self.keyframes[f][0]
|
||||
|
||||
|
||||
class DisplacerDampener(PointDisplacer):
|
||||
"""!
|
||||
Given a displacer and a function that returns a factor for a point,
|
||||
multiplies the effect of the displacer by the factor
|
||||
"""
|
||||
def __init__(self, displacer, dampener):
|
||||
self.displacer = displacer
|
||||
self.dampener = dampener
|
||||
|
||||
@property
|
||||
def time_start(self):
|
||||
return self.displacer.time_start
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
disp = self.displacer._on_displace(startpos, f)
|
||||
damp = self.dampener(startpos)
|
||||
return disp * damp
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
return self.displacer.n_frames
|
||||
|
||||
def frame_time(self, f):
|
||||
return self.displacer.frame_time(f)
|
||||
|
||||
|
||||
class FollowDisplacer(PointDisplacer):
|
||||
def __init__(
|
||||
self,
|
||||
origin,
|
||||
range,
|
||||
offset_func,
|
||||
time_start, time_end, n_frames,
|
||||
falloff_exp=1,
|
||||
):
|
||||
"""!
|
||||
@brief Uses a custom offset function, and applies a falloff to the displacement
|
||||
|
||||
@param origin Origin point for the falloff
|
||||
@param range Radius after which the points will not move
|
||||
@param offset_func Function returning an offset given a ratio of the time
|
||||
@param time_start When the animation shall start
|
||||
@param time_end When the animation shall end
|
||||
@param n_frames Number of frames in the animation
|
||||
@param falloff_exp Exponent for the falloff
|
||||
"""
|
||||
super().__init__(time_start, time_end, n_frames)
|
||||
self.origin = origin
|
||||
self.range = range
|
||||
self.offset_func = offset_func
|
||||
self.falloff_exp = falloff_exp
|
||||
|
||||
def _on_displace(self, startpos, f):
|
||||
influence = 1 - min(1, (startpos - self.origin).length / self.range) ** self.falloff_exp
|
||||
return self.offset_func(f / self.n_frames) * influence
|
||||
Reference in New Issue
Block a user