def swing_twist_decompose(quat_in, axis):
	"""
	Decompose the rotation on to 2 parts.
	1. Twist - rotation around the "direction" vector
	2. Swing - rotation around axis that is perpendicular to "direction" vector
	The rotation can be composed back by
	quat_in = swing * twist
	
	has singularity in case of swing_rotation close to 180 degrees rotation.
	if the input quaternion is of non-unit length, the outputs are non-unit as well
	otherwise, outputs are both unit
	output = (swing, twist)
	"""

	# vector3 quat_rotation_axis( quat_in.x, quat_in.y, quat_in.z ); // rotation axis
	# quat rotation axis
	quat_rotation_axis = quat_in[1:4]
	
	# vector3 p = projection( quat_rotation_axis, axis ); // return projection x on to y  (parallel component)
	p = core.my_projection(quat_rotation_axis, axis)
	
	# twist.set( p.x, p.y, p.z, quat_in.w ); // but i use them as W X Y Z
	twist = [quat_in[0], p[0], p[1], p[2]]
	
	# twist.normalize();
	length = core.my_euclidian_distance(twist)
	twist = [t / length for t in twist]
	
	# swing = quat_in * twist.conjugated();
	twist_conjugate = core.my_quat_conjugate(twist)
	swing = core.hamilton_product(quat_in, twist_conjugate)
	
	return swing, twist
Beispiel #2
0
def rotate3d(origin, angle_quat, point_in):
    # "rotate around a point in 3d space"

    # subtract "origin" to move the whole system to rotating around 0,0,0
    point = [p - o for p, o in zip(point_in, origin)]

    # might need to scale the point down to unit-length???
    # i'll do it just to be safe, it couldn't hurt
    length = core.my_euclidian_distance(point)
    if length != 0:
        point = [p / length for p in point]

        # set up the math as instructed by math.stackexchange
        p_vect = [0] + point
        r_prime_vect = core.my_quat_conjugate(angle_quat)
        # r_prime_vect = [angle_quat[0], -angle_quat[1], -angle_quat[2], -angle_quat[3]]

        # P' = R * P * R'
        # P' = H( H(R,P), R')
        temp = core.hamilton_product(angle_quat, p_vect)
        p_prime_vect = core.hamilton_product(temp, r_prime_vect)
        # note that the first element of P' will always be 0
        point = p_prime_vect[1:4]

        # might need to undo scaling the point down to unit-length???
        point = [p * length for p in point]

    # re-add "origin" to move the system to where it should have been
    point = [p + o for p, o in zip(point, origin)]

    return point
Beispiel #3
0
def morph_winnow(pmx: pmxstruct.Pmx, moreinfo=False):
    total_num_verts = 0
    total_vert_dropped = 0
    total_morphs_affected = 0

    morphs_now_empty = []

    # for each morph:
    for d, morph in enumerate(pmx.morphs):
        # if not a vertex morph, skip it
        if morph.morphtype != 1: continue
        # for each vert in this vertex morph:
        i = 0
        this_vert_dropped = 0  # lines dropped from this morph
        total_num_verts += len(morph.items)
        while i < len(morph.items):
            vert = morph.items[i]
            vert: pmxstruct.PmxMorphItemVertex
            # determine if it is worth keeping or deleting
            # first, calculate euclidian distance
            length = core.my_euclidian_distance(vert.move)
            if length < WINNOW_THRESHOLD:
                morph.items.pop(i)
                this_vert_dropped += 1
            else:
                i += 1
        if len(morph.items) == 0:
            # mark newly-emptied vertex morphs for later removal
            morphs_now_empty.append(d)
        # increment tracking variables
        if this_vert_dropped != 0:
            if moreinfo:
                core.MY_PRINT_FUNC(
                    "morph #{:<3} JP='{}' / EN='{}', removed {} vertices".
                    format(d, morph.name_jp, morph.name_en, this_vert_dropped))
            total_morphs_affected += 1
            total_vert_dropped += this_vert_dropped

    if total_vert_dropped == 0:
        core.MY_PRINT_FUNC("No changes are required")
        return pmx, False

    core.MY_PRINT_FUNC(
        "Dropped {} / {} = {:.1%} vertices from among {} affected morphs".
        format(total_vert_dropped, total_num_verts,
               total_vert_dropped / total_num_verts, total_morphs_affected))

    if morphs_now_empty and DELETE_NEWLY_EMPTIED_MORPHS:
        core.MY_PRINT_FUNC(
            "Deleted %d morphs that had all of their vertices below the threshold"
            % len(morphs_now_empty))
        rangemap = delme_list_to_rangemap(morphs_now_empty)

        pmx = apply_morph_remapping(pmx, morphs_now_empty, rangemap)

    return pmx, True
Beispiel #4
0
def normalize_normals(pmx: pmxstruct.Pmx) -> Tuple[int, List[int]]:
    """
	Normalize normal vectors for each vertex in the PMX object. Return # of verts that were modified, and also a list
	of all vert indexes that have 0,0,0 normals and need special handling.
	
	:param pmx: PMX list-of-lists object
	:return: # verts modified + list of all vert idxs that have 0,0,0 normals
	"""
    norm_fix = 0

    normbad = []
    for d, vert in enumerate(pmx.verts):
        # normalize the normal
        if vert.norm == [0, 0, 0]:
            # invalid normals will be taken care of below
            normbad.append(d)
        else:
            norm_L = core.my_euclidian_distance(vert.norm)
            if round(norm_L, 6) != 1.0:
                norm_fix += 1
                vert.norm = [n / norm_L for n in vert.norm]
    # printing is handled outside
    return norm_fix, normbad
Beispiel #5
0
def calculate_percentiles(pmx: pmxstruct.Pmx, bone_arm: int, bone_elbow: int,
                          bone_hasweight: int):
    retme_verts = []
    retme_percents = []
    retme_centers = []

    axis_start = pmx.bones[bone_arm].pos
    axis_end = pmx.bones[bone_elbow].pos

    # 1. determine the axis from arm to elbow
    deltax, deltay, deltaz = [e - s for e, s in zip(axis_end, axis_start)]
    startx, starty, startz = axis_start
    axis_length = core.my_euclidian_distance((deltax, deltay, deltaz))

    # 2. determine y-rot and z-rot needed to make elbow have same Y/Z, elbowX > armX... first y-rotate, THEN z-rotate
    # ay = -atan2(dz, dx)
    theta_y = -math.atan2(deltaz, deltax)
    # apply 2d Y-rotation
    inter1x, inter1z = core.rotate2d(origin=(startx, startz),
                                     angle=theta_y,
                                     point=(axis_end[0], axis_end[2]))
    # az = -atan2(dy, dx)........ x position has changed, delta needs recalculated! y delta was untouched tho
    theta_z = -math.atan2(deltay, inter1x - startx)
    # now have found theta_y and theta_z

    # 3. collect all vertices controlled by bone_hasweight
    for d, vert in enumerate(pmx.verts):
        weighttype = vert.weighttype
        w = vert.weight
        weightlen = weight_type_to_len[weighttype]
        if bone_hasweight in w[0:weightlen]:
            # if this vert is controlled by bone_hasweight, then it is relevant! save it!
            # most of the verts aren't going to have 0 weight for a bone, dont worry about checking that
            retme_verts.append(d)

            # 4. calculate how far along the axis each vertex lies
            # percentile: 0.0 means at the startpoint, 1.0 means at the endpoint
            # apply 2d Y-rotation: ignore Y-axis, this will cause Z==Z
            inter1x, inter1z = core.rotate2d(origin=(startx, startz),
                                             angle=theta_y,
                                             point=(vert[0], vert[2]))
            # apply 2d Z-rotation: ignore Z-axis, this will cause Y==Y
            finalx, finaly = core.rotate2d(origin=(startx, starty),
                                           angle=theta_z,
                                           point=(inter1x, vert[1]))
            # calculate the actual percentile
            v_percentile = (finalx - startx) / axis_length
            retme_percents.append(v_percentile)

            # 5. calculate the centerpoint for if this bone is determined to use SDEF
            # c should lie on this start-end axis
            # to get c, after applying yrot zrot to a vertex, keep x unchanged and set y/z to match the startbone pos (point being rotated around)
            # center_before = finalx, starty, startz
            # then apply trig in reverse order: zrot, yrot
            # apply 2d Z-rotation: ignore Z-axis
            inter2x, inter2y = core.rotate2d(origin=(startx, starty),
                                             angle=-theta_z,
                                             point=(finalx, starty))
            # apply 2d Y-rotation: ignore Y-axis
            centerx, centerz = core.rotate2d(origin=(startx, startz),
                                             angle=-theta_y,
                                             point=(inter2x, startz))
            center = centerx, inter2y, centerz
            # result is the point along the axis that is closest to the vertex
            retme_centers.append(center)
        pass  # close the loop
    return retme_verts, retme_percents, retme_centers
Beispiel #6
0
def repair_invalid_normals(pmx: pmxstruct.Pmx, normbad: List[int]) -> int:
    """
	Repair all 0,0,0 normals in the model by averaging the normal vector for each face that vertex is a member of.
	It is theoretically possible for a vertex to be a member in two faces with exactly opposite normals, and therefore
	the average would be zero; in this case one of the faces is arbitrarily chosen and its normal is used. Therefore,
	after this function all invalid normals are guaranteed to be fixed.
	Returns the number of times this fallback method was used.
	
	:param pmx: PMX list-of-lists object
	:param normbad: list of vertex indices so I don't need to walk all vertices again
	:return: # times fallback method was used
	"""
    normbad_err = 0
    # create a list in parallel with the faces list for holding the perpendicular normal to each face
    facenorm_list = [list() for i in pmx.faces]
    # create a list in paralle with normbad for holding the set of faces connected to each bad-norm vert
    normbad_linked_faces = [list() for i in normbad]

    # goal: build the sets of faces that are associated with each bad vertex

    # first, flatten the list of face-vertices, probably faster to search that way
    flatlist = [item for sublist in pmx.faces for item in sublist]

    # second, for each face-vertex, check if it is a bad vertex
    # (this takes 70% of time)
    for d, facevert in enumerate(flatlist):
        core.print_progress_oneline(.7 * d / len(flatlist))
        # bad vertices are unique and in sorted order, can use binary search to further optimize
        whereinlist = core.binary_search_wherein(facevert, normbad)
        if whereinlist != -1:
            # if it is a bad vertex, int div by 3 to get face ID
            (normbad_linked_faces[whereinlist]).append(d // 3)

    # for each bad vert:
    # (this takes 30% of time)
    for d, (badvert_idx,
            badvert_faces) in enumerate(zip(normbad, normbad_linked_faces)):
        newnorm = [0, 0, 0]  # default value in case something goes wrong
        core.print_progress_oneline(.7 + (.3 * d / len(normbad)))
        # iterate over the faces it is connected to
        for face_id in badvert_faces:
            # for each face, does the perpendicular normal already exist in the parallel list? if not, calculate and save it for reuse
            facenorm = facenorm_list[face_id]
            if not facenorm:
                # need to calculate it! use cross product or whatever
                # q,r,s order of vertices is important!
                q = pmx.verts[pmx.faces[face_id][0]].pos
                r = pmx.verts[pmx.faces[face_id][1]].pos
                s = pmx.verts[pmx.faces[face_id][2]].pos
                # qr, qs order of vertices is critically important!
                qr = [r[i] - q[i] for i in range(3)]
                qs = [s[i] - q[i] for i in range(3)]
                facenorm = core.my_cross_product(qr, qs)
                # then normalize the fresh normal
                norm_L = core.my_euclidian_distance(facenorm)
                try:
                    facenorm = [n / norm_L for n in facenorm]
                except ZeroDivisionError:
                    # this should never happen in normal cases
                    # however it can happen when the verts are at the same position and therefore their face has zero surface area
                    facenorm = [0, 1, 0]
                # then save the result so I don't have to do this again
                facenorm_list[face_id] = facenorm
            # once I have the perpendicular normal for this face, then accumulate it (will divide later to get avg)
            for i in range(3):
                newnorm[i] += facenorm[i]
        # error case check, theoretically possible for this to happen if there are no connected faces or their normals exactly cancel out
        if newnorm == [0, 0, 0]:
            if len(badvert_faces) == 0:
                # if there are no connected faces, set the normal to 0,1,0 (same handling as PMXE)
                pmx.verts[badvert_idx].norm = [0, 1, 0]
            else:
                # if there are faces that just so happened to perfectly cancel, choose the first face and use its normal
                pmx.verts[badvert_idx].norm = facenorm_list[badvert_faces[0]]
            normbad_err += 1
            continue
        # when done accumulating, divide by # to make an average
        # zerodiv err not possible: if there are no connected faces then it will hit [0,0,0] branch above
        newnorm = [n / len(badvert_faces) for n in newnorm]
        # then normalize this, again
        norm_L = core.my_euclidian_distance(newnorm)
        newnorm = [n / norm_L for n in newnorm]
        # finally, apply this new normal
        pmx.verts[badvert_idx].norm = newnorm
    return normbad_err
def main(moreinfo=True):
	# the goal: extract rotation around the "arm" bone local X? axis and transfer it to rotation around the "armtwist" bone local axis
	
	# prompt PMX name
	core.MY_PRINT_FUNC("Please enter name of PMX input file:")
	input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
	pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)
	core.MY_PRINT_FUNC("")
	# get bones
	realbones = pmx.bones
	
	twistbone_axes = []
	# then, grab the "twist" bones & save their fixed-rotate axes, if they have them
	# fallback plan: find the arm-to-elbow and elbow-to-wrist unit vectors and use those
	for i in range(len(jp_twistbones)):
		r = core.my_list_search(realbones, lambda x: x.name_jp == jp_twistbones[i], getitem=True)
		if r is None:
			core.MY_PRINT_FUNC("ERROR1: twist bone '{}'({}) cannot be found model, unable to continue. Ensure they use the correct semistandard names, or edit the script to change the JP names it is looking for.".format(jp_twistbones[i], eng_twistbones[i]))
			raise RuntimeError()
		if r.has_fixedaxis:
			# this bone DOES have fixed-axis enabled! use the unit vector in r[18]
			twistbone_axes.append(r.fixedaxis)
		else:
			# i can infer local axis by angle from arm-to-elbow or elbow-to-wrist
			start = core.my_list_search(realbones, lambda x: x.name_jp == jp_sourcebones[i], getitem=True)
			if start is None:
				core.MY_PRINT_FUNC("ERROR2: semistandard bone '%s' is missing from the model, unable to infer axis of rotation" % jp_sourcebones[i])
				raise RuntimeError()
			end = core.my_list_search(realbones, lambda x: x.name_jp == jp_pointat_bones[i], getitem=True)
			if end is None:
				core.MY_PRINT_FUNC("ERROR3: semistandard bone '%s' is missing from the model, unable to infer axis of rotation" % jp_pointat_bones[i])
				raise RuntimeError()
			start_pos = start.pos
			end_pos = end.pos
			# now have both startpoint and endpoint! find the delta!
			delta = [b - a for a,b in zip(start_pos, end_pos)]
			# normalize to length of 1
			length = core.my_euclidian_distance(delta)
			unit = [t / length for t in delta]
			twistbone_axes.append(unit)
	
	# done extracting axes limits from bone CSV, in list "twistbone_axes"
	core.MY_PRINT_FUNC("...done extracting axis limits from PMX...")
	
	
	###################################################################################
	# prompt VMD file name
	core.MY_PRINT_FUNC("Please enter name of VMD dance input file:")
	input_filename_vmd = core.MY_FILEPROMPT_FUNC(".vmd")
	
	# next, read/use/prune the dance vmd
	nicelist_in = vmdlib.read_vmd(input_filename_vmd, moreinfo=moreinfo)
	
	# sort boneframes into individual lists: one for each [Larm + Lelbow + Rarm + Relbow] and remove them from the master boneframelist
	# frames for all other bones stay in the master boneframelist
	all_sourcebone_frames = []
	for sourcebone in jp_sourcebones:
		# partition & writeback
		temp, nicelist_in.boneframes = core.my_list_partition(nicelist_in.boneframes, lambda x: x.name == sourcebone)
		# all frames for "sourcebone" get their own sublist here
		all_sourcebone_frames.append(temp)
	
	# verify that there is actually arm/elbow frames to process
	sourcenumframes = sum([len(x) for x in all_sourcebone_frames])
	if sourcenumframes == 0:
		core.MY_PRINT_FUNC("No arm/elbow bone frames are found in the VMD, nothing for me to do!")
		core.MY_PRINT_FUNC("Aborting: no files were changed")
		return None
	else:
		core.MY_PRINT_FUNC("...source contains " + str(sourcenumframes) + " arm/elbow bone frames to decompose...")
	
	if USE_OVERKEY_BANDAID:
		# to fix the path that the arms take during interpolation we need to overkey the frames
		# i.e. create intermediate frames that they should have been passing through already, to FORCE it to take the right path
		# i'm replacing the interpolation curves with actual frames
		for sublist in all_sourcebone_frames:
			newframelist = []
			sublist.sort(key=lambda x: x.f) # ensure they are sorted by frame number
			# for each frame
			for i in range(1, len(sublist)):
				this = sublist[i]
				prev = sublist[i-1]
				# use interpolation curve i to interpolate from i-1 to i
				# first: do i need to do anything or are they already close on the timeline?
				thisframenum = this.f
				prevframenum = prev.f
				if (thisframenum - prevframenum) <= OVERKEY_FRAME_SPACING:
					continue
				# if they are far enough apart that i need to do something,
				thisframequat = core.euler_to_quaternion(this.rot)
				prevframequat = core.euler_to_quaternion(prev.rot)
				# 3, 7, 11, 15 = r_ax, r_ay, r_bx, r_by
				bez = core.MyBezier((this.interp[3], this.interp[7]), (this.interp[11], this.interp[15]), resolution=50)
				# create new frames at these frame numbers, spacing is OVERKEY_FRAME_SPACING
				for interp_framenum in range(prevframenum + OVERKEY_FRAME_SPACING, thisframenum, OVERKEY_FRAME_SPACING):
					# calculate the x time percentage from prev frame to this frame
					x = (interp_framenum - prevframenum) / (thisframenum - prevframenum)
					# apply the interpolation curve to translate X to Y
					y = bez.approximate(x)
					# interpolate from prev to this by amount Y
					interp_quat = core.my_slerp(prevframequat, thisframequat, y)
					# begin building the new frame
					newframe = vmdstruct.VmdBoneFrame(
						name=this.name,  # same name
						f=interp_framenum,  # overwrite frame num
						pos=list(this.pos),  # same pos (but make a copy)
						rot=list(core.quaternion_to_euler(interp_quat)),  # overwrite euler angles
						phys_off=this.phys_off,  # same phys_off
						interp=list(core.bone_interpolation_default_linear)  # overwrite interpolation
					)
					newframelist.append(newframe)
				# overwrite thisframe interp curve with default too
				this.interp = list(core.bone_interpolation_default_linear) # overwrite custom interpolation
			# concat the new frames onto the existing frames for this sublist
			sublist += newframelist
			
	# re-count the number of frames for printing purposes
	totalnumframes = sum([len(x) for x in all_sourcebone_frames])
	overkeyframes = totalnumframes - sourcenumframes
	if overkeyframes != 0:
		core.MY_PRINT_FUNC("...overkeying added " + str(overkeyframes) + " arm/elbow bone frames...")
	core.MY_PRINT_FUNC("...beginning decomposition of " + str(totalnumframes) + " arm/elbow bone frames...")
	
	# now i am completely done reading the VMD file and parsing its data! everything has been distilled down to:
	# all_sourcebone_frames = [Larm, Lelbow, Rarm, Relbow] plus nicelist_in[1]
	
	###################################################################################
	# begin the actual calculations
	
	# output array
	new_twistbone_frames = []
	# progress tracker
	curr_progress = 0
	
	# for each sourcebone & corresponding twistbone,
	for (twistbone, axis_orig, sourcebone_frames) in zip(jp_twistbones, twistbone_axes, all_sourcebone_frames):
		# for each frame of the sourcebone,
		for frame in sourcebone_frames:
			# XYZrot = 567 euler
			quat_in = core.euler_to_quaternion(frame.rot)
			axis = list(axis_orig)	# make a copy to be safe
			
			# "swing twist decomposition"
			# swing = "local" x rotation and nothing else
			# swing = sourcebone, twist = twistbone
			(swing, twist) = swing_twist_decompose(quat_in, axis)
			
			# modify "frame" in-place
			# only modify the XYZrot to use new values
			new_sourcebone_euler = core.quaternion_to_euler(swing)
			frame.rot = list(new_sourcebone_euler)
			
			# create & store new twistbone frame
			# name=twistbone, framenum=copy, XYZpos=copy, XYZrot=new, phys=copy, interp16=copy
			new_twistbone_euler = core.quaternion_to_euler(twist)
			newframe = vmdstruct.VmdBoneFrame(
				name=twistbone,
				f=frame.f,
				pos=list(frame.pos),
				rot=list(new_twistbone_euler),
				phys_off=frame.phys_off,
				interp=list(frame.interp)
			)
			new_twistbone_frames.append(newframe)
			# print progress updates
			curr_progress += 1
			core.print_progress_oneline(curr_progress / totalnumframes)
	
	
	######################################################################
	# done with calculations!
	core.MY_PRINT_FUNC("...done with decomposition, now reassembling output...")
	# attach the list of newly created boneframes, modify the original input
	for sublist in all_sourcebone_frames:
		nicelist_in.boneframes += sublist
	nicelist_in.boneframes += new_twistbone_frames
	
	core.MY_PRINT_FUNC("")
	# write out the VMD
	output_filename_vmd = "%s_twistbones_for_%s.vmd" % \
						   (input_filename_vmd[0:-4], core.get_clean_basename(input_filename_pmx))
	output_filename_vmd = core.get_unused_file_name(output_filename_vmd)
	vmdlib.write_vmd(output_filename_vmd, nicelist_in, moreinfo=moreinfo)
	
	core.MY_PRINT_FUNC("Done!")
	return None
Beispiel #8
0
def get_corner_sharpness_factor(
        quatA: Tuple[float, float, float, float], quatB: Tuple[float, float,
                                                               float, float],
        quatC: Tuple[float, float, float, float]) -> float:
    """
	Calculate a [0.0-1.0] factor indicating how "sharp" the corner is at B.
	By "corner" I mean the directional change when A->B stops and B->C begins.
	If they are going the same angular "direction", then return 1.0. If they
	are going perfectly opposite directions, return 0.0. Otherwise return something
	in between.
	The option ROTATION_CORNER_SHARPNESS_FACTOR_MODE controls what the transfer
	curve looks like from angle to factor.
	
	:param quatA: quaterinon WXYZ for frame A
	:param quatB: quaterinon WXYZ for frame B
	:param quatC: quaterinon WXYZ for frame C
	:return: float [0.0-1.0]
	"""
    # to compensate for the angle difference, both will be slowed by some amount
    # IDENTICAL IMPACT

    # first, find the deltas between the quaternions
    deltaquat_AB = core.hamilton_product(core.my_quat_conjugate(quatA), quatB)
    deltaquat_BC = core.hamilton_product(core.my_quat_conjugate(quatB), quatC)
    # to get sensible results below, ignore the "W" component and only use the XYZ components, treat as 3d vector
    deltavect_AB = deltaquat_AB[1:4]
    deltavect_BC = deltaquat_BC[1:4]
    # second, find the angle between these two deltas
    # use the plain old "find the angle between two vectors" formula
    t = core.my_euclidian_distance(deltavect_AB) * core.my_euclidian_distance(
        deltavect_BC)
    if t == 0:
        # this happens when one vector has a length of 0
        ang_d = 0
    else:
        # technically the clamp shouldn't be necessary but floating point inaccuracy caused it to do math.acos(1.000000002) which crashed lol
        shut_up = core.my_dot(deltavect_AB, deltavect_BC) / t
        shut_up = core.clamp(shut_up, -1.0, 1.0)
        ang_d = math.acos(shut_up)
    # print(math.degrees(ang_d))
    # if ang = 0, perfectly colinear, factor = 1
    # if ang = 180, perfeclty opposite, factor = 0
    factor = 1 - (math.degrees(ang_d) / 180)
    # print(factor)
    # ANGLE_SHARPNESS_FACTORS.append(factor)
    if ROTATION_CORNER_SHARPNESS_FACTOR_MODE == 1:
        # disabled
        out_factor = 1
    elif ROTATION_CORNER_SHARPNESS_FACTOR_MODE == 2:
        # linear
        out_factor = factor
    elif ROTATION_CORNER_SHARPNESS_FACTOR_MODE == 3:
        # square root
        out_factor = math.sqrt(factor)
    elif ROTATION_CORNER_SHARPNESS_FACTOR_MODE == 4:
        # piecewise floored, (0,.5) to (.5,1)
        out_factor = 0.5 + factor
        out_factor = core.clamp(out_factor, 0.0, 1.0)
    else:
        out_factor = 1
    out_factor = core.clamp(out_factor, 0.0, 1.0)
    return out_factor
Beispiel #9
0
def main(moreinfo=True):
    # prompt PMX name
    core.MY_PRINT_FUNC("Please enter name of PMX input file:")
    input_filename_pmx = core.MY_FILEPROMPT_FUNC(".pmx")
    pmx = pmxlib.read_pmx(input_filename_pmx, moreinfo=moreinfo)

    # to shift the model by a set amount:
    # first, ask user for X Y Z

    # create the prompt popup
    scale_str = core.MY_GENERAL_INPUT_FUNC(
        lambda x: (model_shift.is_3float(x) is not None), [
            "Enter the X,Y,Z amount to scale this model by:",
            "Three decimal values separated by commas.",
            "Empty input will quit the script."
        ])

    # if empty, quit
    if scale_str == "":
        core.MY_PRINT_FUNC("quitting")
        return None
    # use the same func to convert the input string
    scale = model_shift.is_3float(scale_str)

    uniform_scale = (scale[0] == scale[1] == scale[2])
    if not uniform_scale:
        core.MY_PRINT_FUNC(
            "Warning: when scaling by non-uniform amounts, rigidbody sizes will not be modified"
        )

    ####################
    # what does it mean to scale the entire model?
    # scale vertex position, sdef params
    # ? scale vertex normal vectors, then normalize? need to convince myself of this interaction
    # scale bone position, tail offset
    # scale fixedaxis and localaxis vectors, then normalize
    # scale vert morph, bone morph
    # scale rigid pos, size
    # scale joint pos, movelimits

    for v in pmx.verts:
        # vertex position
        for i in range(3):
            v.pos[i] *= scale[i]
        # vertex normal
        for i in range(3):
            if scale[i] != 0:
                v.norm[i] /= scale[i]
            else:
                v.norm[i] = 100000
        # then re-normalize the normal vector
        L = core.my_euclidian_distance(v.norm)
        if L != 0:
            v.norm = [n / L for n in v.norm]
        # c, r0, r1 params of every SDEF vertex
        if v.weighttype == 3:
            for param in v.weight_sdef:
                for i in range(3):
                    param[i] *= scale[i]

    for b in pmx.bones:
        # bone position
        for i in range(3):
            b.pos[i] *= scale[i]
        # bone tail if using offset mode
        if not b.tail_usebonelink:
            for i in range(3):
                b.tail[i] *= scale[i]
        # scale fixedaxis and localaxis vectors, then normalize
        if b.has_fixedaxis:
            for i in range(3):
                b.fixedaxis[i] *= scale[i]
            # then re-normalize
            L = core.my_euclidian_distance(b.fixedaxis)
            if L != 0:
                b.fixedaxis = [n / L for n in b.fixedaxis]
        # scale fixedaxis and localaxis vectors, then normalize
        if b.has_localaxis:
            for i in range(3):
                b.localaxis_x[i] *= scale[i]
            for i in range(3):
                b.localaxis_z[i] *= scale[i]
            # then re-normalize
            L = core.my_euclidian_distance(b.localaxis_x)
            if L != 0:
                b.localaxis_x = [n / L for n in b.localaxis_x]
            L = core.my_euclidian_distance(b.localaxis_z)
            if L != 0:
                b.localaxis_z = [n / L for n in b.localaxis_z]

    for m in pmx.morphs:
        # vertex morph and bone morph (only translate, not rotate)
        if m.morphtype in (1, 2):
            morph_scale.morph_scale(m, scale, bone_mode=1)

    for rb in pmx.rigidbodies:
        # rigid body position
        for i in range(3):
            rb.pos[i] *= scale[i]
        # rigid body size
        # NOTE: rigid body size is a special conundrum
        # spheres have only one dimension, capsules have two, and only boxes have 3
        # what's the "right" way to scale a sphere by 1,5,1? there isn't a right way!
        # boxes and capsules can be rotated and stuff so their axes dont line up with world axes, too
        # is it at least possible to rotate bodies so they are still aligned with their bones?
        # eh, why even bother with any of that. 95% of the time full-model scale will be uniform scaling.
        # only scale the rigidbody size if doing uniform scaling: that is guaranteed to be safe!
        if uniform_scale:
            for i in range(3):
                rb.size[i] *= scale[i]

    for j in pmx.joints:
        # joint position
        for i in range(3):
            j.pos[i] *= scale[i]
        # joint min slip
        for i in range(3):
            j.movemin[i] *= scale[i]
        # joint max slip
        for i in range(3):
            j.movemax[i] *= scale[i]

    # that's it? that's it!

    # write out
    output_filename_pmx = input_filename_pmx[0:-4] + "_scale.pmx"
    output_filename_pmx = core.get_unused_file_name(output_filename_pmx)
    pmxlib.write_pmx(output_filename_pmx, pmx, moreinfo=moreinfo)
    core.MY_PRINT_FUNC("Done!")
    return None
 def normalize(foo):
     LLL = core.my_euclidian_distance(foo)
     return [t / LLL for t in foo]