diff options
author | Owl <isaclien9752@gmail.com> | 2025-08-21 20:07:13 -0400 |
---|---|---|
committer | Owl <isaclien9752@gmail.com> | 2025-08-21 20:07:13 -0400 |
commit | ef4d0e08b2d312bcf3034aa3ae48436f5d8b56a5 (patch) | |
tree | 88c06847d16e29acdd743066b0781fc17ebde3eb /math_funcs.py | |
parent | 5fb5906f45a20652b8cbadc5657df9ca506f11db (diff) | |
download | blenxy-ef4d0e08b2d312bcf3034aa3ae48436f5d8b56a5.tar.gz blenxy-ef4d0e08b2d312bcf3034aa3ae48436f5d8b56a5.zip |
all the stuff
Diffstat (limited to 'math_funcs.py')
-rw-r--r-- | math_funcs.py | 503 |
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 |