summaryrefslogtreecommitdiff
path: root/math_funcs.py
diff options
context:
space:
mode:
Diffstat (limited to 'math_funcs.py')
-rw-r--r--math_funcs.py503
1 files changed, 232 insertions, 271 deletions
diff --git a/math_funcs.py b/math_funcs.py
index 8f92bef..ec7f7de 100644
--- a/math_funcs.py
+++ b/math_funcs.py
@@ -1,319 +1,280 @@
-import math
-from mathutils import Matrix
+import math, numpy
+import mathutils
+import warnings
+warnings.simplefilter("ignore", numpy.RankWarning)
# file containning math related functions
# function to return a 0 filled matrix 3x3
def get_zero_mat3x3():
-
- return Matrix(([0, 0, 0], [0, 0, 0], [0, 0, 0]))
+ return mathutils.Matrix(([0, 0, 0], [0, 0, 0], [0, 0, 0]))
# function to return a 0 filled matrix 4x4
def get_zero_mat4x4():
-
- return Matrix(([0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]))
+ return mathutils.Matrix(([0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]))
# function to return an identity matrix 3x3
def get_id_mat3x3():
-
- return Matrix(([1, 0, 0], [0, 1, 0], [0, 0, 1]))
+ return mathutils.Matrix(([1, 0, 0], [0, 1, 0], [0, 0, 1]))
# function to return an identity matrix 4x4
def get_id_mat4x4():
-
- return Matrix(([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]))
+ return mathutils.Matrix(([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]))
# calc_scale_matrix function
# function to build the scale matrix
-def calc_scale_matrix(x_scale, y_scale, z_scale):
+def calc_scale_matrix(sx, sy, sz):
- scale_matrix = Matrix(( [x_scale, 0, 0, 0],
- [0, y_scale, 0, 0],
- [0, 0, z_scale, 0],
- [0, 0, 0, 1]
- ))
-
- return scale_matrix
+ mat = mathutils.Matrix(([sx, 0, 0, 0],
+ [0, sy, 0, 0],
+ [0, 0, sz, 0],
+ [0, 0, 0, 1]))
+ return mat
# calc_rotation_matrix function
# function to calculate the rotation matrix
# for a Extrinsic Euler XYZ system (radians)
-def calc_rotation_matrix(x_angle, y_angle, z_angle):
-
- x_rot_mat = Matrix(([1, 0, 0, 0],
- [0, math.cos(x_angle), -math.sin(x_angle), 0],
- [0, math.sin(x_angle), math.cos(x_angle), 0],
- [0, 0, 0, 1]))
-
- y_rot_mat = Matrix(([math.cos(y_angle), 0, math.sin(y_angle), 0],
- [0, 1, 0, 0],
+def calc_rotation_matrix(rx, ry, rz):
- [-math.sin(y_angle), 0, math.cos(y_angle), 0],
- [0, 0, 0, 1]))
-
- z_rot_mat = Matrix(([math.cos(z_angle), -math.sin(z_angle), 0, 0],
- [math.sin(z_angle), math.cos(z_angle), 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]))
-
- return z_rot_mat * y_rot_mat * x_rot_mat
+ x_rot = mathutils.Matrix(([1, 0, 0, 0],
+ [0, math.cos(rx), -math.sin(rx), 0],
+ [0, math.sin(rx), math.cos(rx), 0],
+ [0, 0, 0, 1]))
+ y_rot = mathutils.Matrix(([math.cos(ry), 0, math.sin(ry), 0],
+ [0, 1, 0, 0],
+ [-math.sin(ry), 0, math.cos(ry), 0],
+ [0, 0, 0, 1]))
+ z_rot = mathutils.Matrix(([math.cos(rz), -math.sin(rz), 0, 0],
+ [math.sin(rz), math.cos(rz), 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]))
+ return z_rot * y_rot * x_rot
# calc_translation_matrix function
# function to build the translation matrix
-def calc_translation_matrix(x_translation, y_translation, z_translation):
+def calc_translation_matrix(tx, ty, tz):
- translation_matrix = Matrix(( [1, 0, 0, x_translation],
- [0, 1, 0, y_translation],
- [0, 0, 1, z_translation],
- [0, 0, 0, 1]
- ))
-
- return translation_matrix
-
-# interpolate function
-# used to find a value in an interval with the specified mode.
-# So that it is clear that the values are points that have 2
-# coordinates I will treat the input as they are (x,y) points
-# the function will either return m_x or m_y depending on
-# which of the middle point values is provided to the function
-# (set None to the variable going to be returned by the
-# function). Only linear interpolation is supported for now.
-#
-# l_x (float) --> left point X axis component
-# l_y (float) --> left point Y axis component
-# r_x (float) --> right point X axis component
-# r_y (float) --> right point Y axis component
-# m_x (float) --> middle point X axis component
-# m_y (float) --> middle point Y axis component
-# interp_type (string) --> "linear" for linear interpolation
-def interpolate(l_x, l_y, r_x, r_y, m_x, m_y, interp_type):
-
- # variable to be returned
- result = 0
-
- # if right point does not exist (special case)
- # return l_y as the interpolation result
- if (r_x == None or r_y == None):
- result = l_y
- return result
-
- # m_x is the one to be returned
- if (m_x == None):
-
- # linear interpolation
- if (interp_type == "linear"):
- m_x = (((r_x - l_x) / (r_y - l_y)) * (m_y - r_y)) + r_x
- result = m_x
-
- # m_y is the one to be returned
- if (m_y == None):
-
- # linear interpolation
- if (interp_type == "linear"):
- m_y = (((r_y - l_y) / (r_x - l_x)) * (m_x - r_x)) + r_y
- result = m_y
-
- return result
+ mat = mathutils.Matrix(([1, 0, 0, tx],
+ [0, 1, 0, ty],
+ [0, 0, 1, tz],
+ [0, 0, 0, 1]))
+ return mat
-# find_left_right function
-# find the values and positions of the elements at the left and the
-# right of the element in position pos on the anim_array
-# elements will be used in the interpolate() function later
-#
-# anim_array (array of floats) --> anim property frame array its
-# length must the animation length
-# pos (int) --> position of the animation property value to be
-# later interpolated in the anim_array array
-def find_left_right(anim_array, pos):
+# calculate transformation matrix for blender
+def calc_transf_mat(scale, rotation, translation):
+ mat = calc_translation_matrix(translation[0], translation[1], translation[2])
+ mat *= calc_rotation_matrix(rotation[0], rotation[1], rotation[2])
+ mat *= calc_scale_matrix(scale[0], scale[1], scale[2])
+ return mat
- # create left/right variables
- l_val = 0
- l_val_pos = 0
- r_val = 0
- r_val_pos = 0
+# calculate a value of a cubic hermite interpolation
+# it is assumed t0 and tf are 0 and 1
+# t is the parametrization variable
+# https://humming-owl.neocities.org/smg-stuff/pages/tutorials/model3#cubic-hermite-spline
+def cubic_hermite_spline_time_unitary(p0, m0, m1, p1, t):
+ # interpolation not defined here bruh
+ if (t < 0 or t > 1):
+ return None
+ t_pow3 = math.pow(t, 3)
+ t_pow2 = math.pow(t, 2)
+ t_pow1 = t
+ interp_result = p0 * ((+2 * t_pow3) - (3 * t_pow2) + 1)
+ interp_result += m0 * ((+1 * t_pow3) - (2 * t_pow2) + t_pow1)
+ interp_result += m1 * ((+1 * t_pow3) - (1 * t_pow2) + 0)
+ interp_result += p1 * ((-2 * t_pow3) + (3 * t_pow2) + 0)
+ return interp_result
- # find near left value (has to exist)
- # read array from right to left
- for i in range(len(anim_array), -1, -1):
- #
- # skip elements at the right of
- # pos in anim_array
- if (i >= pos):
- continue
-
- # left value is found
- if (anim_array[i] != None):
- l_val = anim_array[i]
- l_val_pos = i
- break
- #
+# same as the above function but t0 and tf can be any time value (tf > t0)
+def cubic_hermite_spline_time_general(p0, m0, m1, p1, t0, tf, t):
+ # interpolation not defined here bruh
+ if (t < t0 or t > tf):
+ return None
+ interp_result = cubic_hermite_spline_time_unitary(p0, m0 * (tf - t0), m1 * (tf - t0), p1, (t - t0) / (tf - t0))
+ return interp_result
- ##############
- # special case
- # if pos is the last element position on
- # the array r_val and r_val_pos do not exist
- if (pos == (len(anim_array) - 1)):
- return [l_val_pos, l_val, None, None]
+# structure to be returned by the function below
+class best_chs_fits:
- # find near right value (might not exist)
- # read array from left to right
- for i in range(len(anim_array)):
- # skip elements at the left of
- # pos in anim_array
- if (i <= pos):
- continue
-
- # right value is found
- if (anim_array[i] != None):
- r_val = anim_array[i]
- r_val_pos = i
- break
-
- # if no value is found at the end of
- # the anim_array r_val and r_val_pos do not exist
- # (value does not change between the left value
- # found and the end of the animation)
- if (i == (len(anim_array) - 1)):
- return [l_val_pos, l_val, None, None]
+ def __init__(self):
+ self.kf_count = 0
+ self.time = []
+ self.value = []
+ self.in_slope = []
+ self.out_slope = []
- # if all values are found, return them
- return [l_val_pos, l_val, r_val_pos, r_val]
+ def __str__(self):
+ rtn = "keyframe count: %s\n" % (self.kf_count)
+ rtn += "times: %s\n" % (self.time)
+ rtn += "values: %s\n" % (self.value)
+ rtn += "in slopes: %s\n" % (self.in_slope)
+ rtn += "out slopes: %s" % (self.out_slope)
+ return rtn
-# convert_angle_to_180 function
-# function used by the convert_anim_rot_to_180 function
-# to convert a single angle in its representation on
-# the -180/+180 degree range (angles passed to the
-# function that are already in this range will be
-# returned without conversion)
-#
-# angle (float) --> angle to convert to the -180/+180
-# degree range (angle is expected to
-# be in degrees)
-def convert_angle_to_180(angle):
-#
- # check if the angle really needs to be processed
- # i.e. is already inside the -180/+180 degree range
- if (angle >= -180 and angle <= 180):
- return angle
-
- # convert it otherwise
+# function to calculate the best cubic hermite spline interpolator fits
+# for a given a set of points. The set of points is expected to be at
+# each frame of the animation (start_frame indicates the start frame, integer)
+# the function will return the above structure
+# it will be the generic cubic hermite spline form
+def find_best_cubic_hermite_spline_fit(start_frame, values, angle_limit):
- # check if it is positive or negative
- # and set the opposite direction of the angle
- # if the angle is > 0 then its mesurement is clockwise (opposite is counter-clockwise)
- # if the angle is < 0 then its mesurement is counter-clockwise (opposite is clockwise)
- if (angle > 0):
- opposite_spin_dir = -1
- else: # it is negative
- opposite_spin_dir = 1
+ # check
+ if ((type(start_frame) != int)
+ or (type(values) != list)
+ or (len(values) == 0)):
+ return None
- # decrease the angle by 360 degrees until it
- # is in the -180/+180 degree interval
- while (abs(angle) > 180):
- angle = angle + (opposite_spin_dir * 360)
+ # create the object to return
+ rtn = best_chs_fits()
- return angle
-
-# convert_anim_rot_to_180 function
-# used to re-calculate a rotation animation on an axis
-# so that angles used lay in between -180/180 degrees
-# done to avoid rotation animation data loss when extracting
-# said angles from a transformation matrix
-# this function calls the convert_angle_to_180() function
-# at the end of the function csv_keyframe_numbers is updated
-# with the new frames to be injected into the animation
-#
-# example:
-#
-# Original keyframes:
-# Frame 0 Frame 21 (2 keyframes)
-# 0º 360º
-#
-# Processed keyframes:
-# Frame 0 Frame 10 Frame 11 Frame 21 (4 keyframes)
-# 0º 171.4º -171.5º 0º
-#
-# rot_array (array of floats) --> bone rotation animation data
-# for a single axis
-# csv_keyframe_numbers (array of ints) --> original keyframes
-# of the animation
-#
-# Note: function will have problems interpreting keyframes with
-# high rotation diferences if the number of frames in
-# between said keyframes in lower than 2 times the spins
-# done in between those keyframe angles (thinking a fix)
-def convert_rot_anim_to_180(rot_array, csv_keyframe_numbers):
+ # the cubic hermite spline is just a sub-family of 3rd degree bezier curves
+ # that makes this curve to be "able" to represent 3rd degree polynomials
+ # these polinomials can have a maximum of 2 concavity changes
+ # to avoid a greater data loss I will just make the best fit
+ # when single concavity change is reached
- # temp rot array to store calculated values and keyframe position
- rot_array_cp = [[], []]
+ # special cases
+ if (len(values) == 1): # single keyframe values like in BCK
+ rtn.kf_count = 1
+ rtn.time = [None]
+ rtn.value = values
+ rtn.in_slope = [None]
+ rtn.out_slope = [None]
+ return rtn
+ elif (len(values) == 2): # 2 keyframe values (force linear interpolation)
+ rtn.kf_count = 2
+ rtn.time = [start_frame, start_frame + 1]
+ rtn.value = values
+ rtn.in_slope = [None, (rtn.value[1] - rtn.value[0]) / (rtn.time[1] - rtn.time[0])]
+ rtn.out_slope = [(rtn.value[1] - rtn.value[0]) / (rtn.time[1] - rtn.time[0]), None]
+ return rtn
- # find the frames in which rot_array has values defined
- rot_array_kf = []
- for i in range(len(rot_array)):
- if (rot_array[i] != None):
- rot_array_kf.append(i)
+ # get the lowest and highest value of the set of values given
+ # will be used to scale the points to be able to do derivative difference checking
+ # also get the average
+ # all of them positive
+ lowest_value = 0
+ highest_value = 0
+ avg_value = 0
+ for i in range(len(values)):
+ if (abs(values[i]) < lowest_value):
+ lowest_value = values[i]
+ if (abs(values[i]) > highest_value):
+ highest_value = values[i]
+ avg_value += abs(values[i])
+ scale_factor = (1 / highest_value) * (len(values) - start_frame)
+ avg_value /= len(values)
- # loop through each consecutive pair of keyframes of the rot_array_kf array
- for i in range(len(rot_array_kf) - 1):
- #
- # get left/right keyframe values and positions
- l_kf_pos = rot_array_kf[i]
- l_kf_val = rot_array[l_kf_pos]
- r_kf_pos = rot_array_kf[i + 1]
- r_kf_val = rot_array[r_kf_pos]
-
- # append l_kf_val to rot_array_cp (converted)
- rot_array_cp[0].append(l_kf_pos)
- rot_array_cp[1].append(convert_angle_to_180(l_kf_val))
+ # lets do this bruv
+ start_index = 0
+ old_value = None
+ cur_value = None
+ new_value = None
+ left_first_derivative = None
+ right_first_derivative = None
+ old_concavity = None
+ new_concavity = None
+ generate_keyframe = None
+ small_time_dif = 0.000001
+ # generate the keyframes
+ i = 0
+ while (i < len(values)):
+ # first keyframe
+ if (i == 0):
+ rtn.kf_count += 1
+ rtn.time.append(start_frame)
+ rtn.value.append(values[0])
+ rtn.in_slope.append(None)
+ rtn.out_slope.append(None)
- # get the rotation direction
- if (r_kf_val > l_kf_val): # clockwise
- rot_direction = 1
- else: # counter-clockwise
- rot_direction = -1
+ # last keyframe / concavity never changes
+ elif (i == len(values) - 1):
+ # add the last keyframe of the animation
+ poly_deg = 3
+ if (i - start_index == 1):
+ poly_deg = 1
+ elif(i - start_index == 2):
+ poly_deg = 2
+ rtn.kf_count += 1
+ rtn.time.append(start_frame + i)
+ rtn.value.append(values[-1])
+ poly = numpy.polyfit(list(range(start_index, i + 1)),
+ values[start_index : i + 1], poly_deg)
+ p0 = numpy.polyval(poly, start_index)
+ pa = numpy.polyval(poly, start_index + small_time_dif)
+ m0 = (pa - p0) / small_time_dif
+ pb = numpy.polyval(poly, i - small_time_dif)
+ p1 = numpy.polyval(poly, i)
+ m1 = (p1 - pb) / small_time_dif
+ rtn.in_slope.append(m1)
+ rtn.out_slope[-1] = m0
+ rtn.out_slope.append(None)
- # advance -180/+180 (depending on the rotation direction)
- # and generate the middle keyframe values
- # angle is fixed to the l_kf_val's closest 360 degree multiple
- angle_val = l_kf_val - convert_angle_to_180(l_kf_val)
- while (abs(r_kf_val - angle_val) > 180):
- #
- angle_val = angle_val + (rot_direction * 180)
+ # the rest
+ else:
- # find the frame (float) in which this value exists
- angle_pos = interpolate(l_kf_pos, l_kf_val, r_kf_pos, r_kf_val, None, angle_val, "linear")
+ # get the values
+ old_value = values[i - 1]
+ cur_value = values[i]
+ new_value = values[i + 1]
+
+ # determine the first derivative on both sides
+ left_first_derivative = cur_value - old_value
+ right_first_derivative = new_value - cur_value
+ new_concavity = right_first_derivative - left_first_derivative
- # find the 2 frames (integer) that are lower
- # and upper limits of this angle_pos
- lower_frame = int(angle_pos)
- upper_frame = int(angle_pos + 1)
+ # check if this is the first time getting the concavity
+ if (old_concavity == None):
+ old_concavity = new_concavity
- # interpolate to find the values on lower_frame and upper_frame
- lower_frame_value = convert_angle_to_180(interpolate(l_kf_pos, l_kf_val, r_kf_pos, r_kf_val, lower_frame, None, "linear"))
- upper_frame_value = convert_angle_to_180(interpolate(l_kf_pos, l_kf_val, r_kf_pos, r_kf_val, upper_frame, None, "linear"))
+ # check concavity changes
+ if (numpy.sign(old_concavity) != numpy.sign(new_concavity)):
+ generate_keyframe = True
- # append results to rot_array_cp
+ # scale the values up and check if the slope change is too violent with vector angles
+ # the scaling is done to be able to visually see the angle changes, it is the same thing
+ # we do with curves when zoomin in or out (either horizontally or vertically)
+ left_first_derivative_scaled = left_first_derivative * scale_factor
+ right_first_derivative_scaled = right_first_derivative * scale_factor
+ left_vec = mathutils.Vector((1, left_first_derivative_scaled))
+ right_vec = mathutils.Vector((1, right_first_derivative_scaled))
+ angle = abs(math.degrees(left_vec.angle(right_vec)))
+ if (angle > angle_limit):
+ generate_keyframe = True
- # keyframes
- rot_array_cp[0].append(lower_frame)
- rot_array_cp[0].append(upper_frame)
- # values
- rot_array_cp[1].append(lower_frame_value)
- rot_array_cp[1].append(upper_frame_value)
-
- # add the new keyframe values to rot_array
- # on their respective frame position
- for i in range(len(rot_array_cp[0])):
- rot_array[rot_array_cp[0][i]] = rot_array_cp[1][i]
+ # store the old concavity for the next loop
+ old_concavity = new_concavity
+
+ # check if a new keyframe should be added
+ if (generate_keyframe == True):
+ # find the best 3rd degree polynomial "fit" with the set of points studied
+ poly_deg = 3
+ if (i - start_index == 1):
+ poly_deg = 1
+ elif(i - start_index == 2):
+ poly_deg = 2
+ poly = numpy.polyfit(list(range(start_index, i + 1)),
+ values[start_index : i + 1], poly_deg)
+ print(poly)
+ p0 = numpy.polyval(poly, start_index)
+ pa = numpy.polyval(poly, start_index + small_time_dif)
+ m0 = (pa - p0) / small_time_dif
+ pb = numpy.polyval(poly, i - small_time_dif)
+ p1 = numpy.polyval(poly, i)
+ m1 = (p1 - pb) / small_time_dif
+ # write the data into the structure
+ rtn.kf_count += 1
+ rtn.time.append(start_frame + i)
+ rtn.value.append(p1)
+ rtn.in_slope.append(m1)
+ rtn.out_slope[-1] = m0
+ rtn.out_slope.append(None)
+
+ # reset these variables for the next iteration
+ start_index = i
+ generate_keyframe = False
+
+ # increment i
+ i += 1
- # update the keyframes on csv_keyframe_numbers to store
- # the calculated keyframes numbers from rot_array_cp[0]
- # append those at the end of csv_keyframe_numbers
- for i in range(len(rot_array_cp[0])):
- value_found = False
- for j in range(len(csv_keyframe_numbers)):
- if (rot_array_cp[0][i] == csv_keyframe_numbers[j]):
- value_found = True
- break
- if (value_found == False):
- csv_keyframe_numbers.append(rot_array_cp[0][i])
+ # done!
+ return rtn