peng3d/model.py
def tickEntity(self,data):
        """
        Callback that should be called regularly to update the animation.
        
        It is recommended to call this method about 60 times a second for smooth animations. Irregular calling of this method will be automatically adjusted.
        
        This method sets all the bones in the given actor to the next state of the animation.
        
        Note that :py:meth:`startAnimation()` must have been called before calling this method.
        """
        adata = data["_anidata"]
        
        if adata.get("anitype",self.name)!=self.name:
            return # incorrectly called
        
        dt = time.time()-adata.get("last_tick",time.time())
        adata["last_tick"]=time.time()
        
        if adata.get("phase","transition")=="transition":
            # If transitioning to this animation
            if adata.get("jumptype",self.default_jt)=="jump":
                # Jumping to the animation, e.g. set all bones to the start frame
                for bone,dat in self.start_frame.get("bones",{}).items():
                    if "rot" in dat:
                        self.bones[bone].setRot(data,dat["rot"])
                    if "length" in dat:
                        self.bones[bone].setLength(data,dat["length"])
                adata["phase"]="animation"
                if self.atype=="keyframes":
                    adata["from_frame"]=self.start_frame.get("frame",0)
            elif adata.get("jumptype",self.default_jt)=="animate":
                raise NotImplementedError("Animation update phase transition type animate not yet implemented")
                
                # Not yet working
                if "transition_frame" not in adata:
                    adata["transition_frame"] = 0
                tlen = self.anidata.get("transition_length",self.anidata.get("length",60)/3)
                tspeed = self.anidata.get("transition_speed",self.anidata.get("keyframespersecond",60))
                
                framediff=int(dt/(1./tspeed)) # Number of frames that have passed since the last update
                overhang = dt-(framediff*(1./tspeed)) # time that has passed but is not enough for a full frame
                adata["last_tick"]-=overhang # causes the next frame to include the overhang from this frame
                if framediff == 0:
                    return # optimization that saves time 
                
                frame1 = adata["transition_frame"]
                adata["transition_frame"]+=framediff
                if adata["transition_frame"]>tlen:
                    adata["phase"]="animation"
                    if self.atype=="keyframes":
                        adata["from_frame"]=self.start_frame.get("frame",0)
                    return
                frame2 = adata["transition_frame"]
                
                if "frombones" not in adata:
                    frombones = {}
                    for bname,bone in self.bones.items():
                        frombones[bname]={"rot":bone.getRot(data),"length":bone.getLength(data)}
                    adata["frombones"] = frombones
                if "tobones" not in adata:
                    tobones = {}
                    for bname,bone in self.start_frame["bones"].items():
                        tobones[bname]=bone
                    adata["tobones"] = tobones
                
                frombones = adata["frombones"]
                tobones = adata["tobones"]
                
                from_frame = 0
                to_frame = tlen
                
                for bname,bone in self.bones.items():
                    # rot
                    if bname not in frombones or bname not in tobones:
                        continue
                    
                    from_rot = frombones[bname]["rot"]
                    to_rot = tobones[bname]["rot"]
                    
                    from_x,from_y=from_rot
                    to_x,to_y=to_rot
                    
                    delta_per_frame_x = (to_x-from_x)/(to_frame-from_frame)
                    delta_per_frame_y = (to_y-from_y)/(to_frame-from_frame)
                    
                    delta_x = delta_per_frame_x*(frame2-frame1)
                    delta_y = delta_per_frame_y*(frame2-frame1)
                    
                    rot = bone.getRot(data)
                    new_rot = [rot[0]+delta_x,rot[1]+delta_y]
                    
                    bone.setRot(data,new_rot)
                    
                    # length
                    
                    # Not yet implemented
                
        elif adata["phase"]=="animation":
            # If animating
            if self.atype == "static":
                pass # Should not need any updates as the transition will set the bones
            elif self.atype == "keyframes":
                framediff=int(dt/(1./self.kps)) # Number of frames that have passed since the last update
                overhang = dt-(framediff*(1./self.kps)) # time that has passed but is not enough for a full frame
                adata["last_tick"]-=overhang # causes the next frame to include the overhang from this frame
                if framediff == 0:
                    return # optimization that saves time 
                
                frame1 = adata["keyframe"]
                adata["keyframe"]+=framediff
                if adata["keyframe"]>self.anilength:
                    repeat = True
                    adata["keyframe"]%=self.anilength
                else:
                    repeat = False
                frame2 = adata["keyframe"]
                
                if repeat:
                    frame1 = frame2
                    frame2+=1
                
                for bname,bone in self.bones.items():
                    # Rot
                    if bname in self.rframes_per_bone:
                        
                        from_frame = None#adata["from_frame"]
                        to_frame = None
                        for framenum,rot in self.rframes_per_bone[bname].items():
                            if (from_frame is None or framenum>from_frame) and framenum<=frame1:
                                # from_frame is the largest frame number that is before the starting point
                                from_frame = framenum
                            if (to_frame is None or framenum<to_frame) and framenum>=frame2:
                                # to_frame is the smallest frame number that is after the end point
                                to_frame = framenum
                        if from_frame is None or to_frame is None:
                            raise ValueError("Invalid frames for bone %s in animation %s"%(bname,self.name))
                        if from_frame==to_frame or repeat:
                            if self.anidata.get("repeat","jump")=="jump":
                                for b,dat in self.start_frame.get("bones",{}).items():
                                    if "rot" in dat:
                                        self.bones[b].setRot(data,dat["rot"])
                                    if "length" in dat:
                                        self.bones[b].setLength(data,dat["length"])
                            elif self.anidata.get("repeat","jump")=="animate":
                                from_frame = adata["to_frame"]
                        adata["from_frame"]=from_frame
                        adata["to_frame"]=to_frame
                        
                        from_rot = self.rframes_per_bone[bname][from_frame]
                        to_rot = self.rframes_per_bone[bname][to_frame]
                        
                        from_x,from_y=from_rot
                        to_x,to_y=to_rot
                        
                        if self.anidata.get("interpolation","linear") == "linear":
                            delta_per_frame_x = (to_x-from_x)/(to_frame-from_frame)
                            delta_per_frame_y = (to_y-from_y)/(to_frame-from_frame)
                            
                            delta_x = delta_per_frame_x*(frame2-frame1)
                            delta_y = delta_per_frame_y*(frame2-frame1)
                            
                            rot = bone.getRot(data)
                            new_rot = [rot[0]+delta_x,rot[1]+delta_y]
                        elif self.anidata["interpolation"] == "jump":
                            new_rot = to_x,to_y
                        else:
                            raise ValueError("Invalid interpolation method '%s' for animation %s"%(self.anidata["interpolation"],self.name))
                        
                        bone.setRot(data,new_rot)
                        
                    # Length
                    if bname in self.lframes_per_bone:
                        # Not yet implemented
                        pass
                
            else:
                # Should not be possible, but still
                raise ValueError("Invalid animation type %s for animation %s during tick"%(self.atype,name))
def tickEntity(self,data):
        """
        Callback that should be called regularly to update the animation.
        
        It is recommended to call this method about 60 times a second for smooth animations. Irregular calling of this method will be automatically adjusted.
        
        This method sets all the bones in the given actor to the next state of the animation.
        
        Note that :py:meth:`startAnimation()` must have been called before calling this method.
        """
        adata = data["_anidata"]
        
        if adata.get("anitype",self.name)!=self.name:
            return # incorrectly called
        
        dt = time.time()-adata.get("last_tick",time.time())
        adata["last_tick"]=time.time()
        
        if adata.get("phase","transition")=="transition":
            # If transitioning to this animation
            if adata.get("jumptype",self.default_jt)=="jump":
                # Jumping to the animation, e.g. set all bones to the start frame
                for bone,dat in self.start_frame.get("bones",{}).items():
                    if "rot" in dat:
                        self.bones[bone].setRot(data,dat["rot"])
                    if "length" in dat:
                        self.bones[bone].setLength(data,dat["length"])
                adata["phase"]="animation"
                if self.atype=="keyframes":
                    adata["from_frame"]=self.start_frame.get("frame",0)
            elif adata.get("jumptype",self.default_jt)=="animate":
                raise NotImplementedError("Animation update phase transition type animate not yet implemented")
                
                # Not yet working
                if "transition_frame" not in adata:
                    adata["transition_frame"] = 0
                tlen = self.anidata.get("transition_length",self.anidata.get("length",60)/3)
                tspeed = self.anidata.get("transition_speed",self.anidata.get("keyframespersecond",60))
                
                framediff=int(dt/(1./tspeed)) # Number of frames that have passed since the last update
                overhang = dt-(framediff*(1./tspeed)) # time that has passed but is not enough for a full frame
                adata["last_tick"]-=overhang # causes the next frame to include the overhang from this frame
                if framediff == 0:
                    return # optimization that saves time 
                
                frame1 = adata["transition_frame"]
                adata["transition_frame"]+=framediff
                if adata["transition_frame"]>tlen:
                    adata["phase"]="animation"
                    if self.atype=="keyframes":
                        adata["from_frame"]=self.start_frame.get("frame",0)
                    return
                frame2 = adata["transition_frame"]
                
                if "frombones" not in adata:
                    frombones = {}
                    for bname,bone in self.bones.items():
                        frombones[bname]={"rot":bone.getRot(data),"length":bone.getLength(data)}
                    adata["frombones"] = frombones
                if "tobones" not in adata:
                    tobones = {}
                    for bname,bone in self.start_frame["bones"].items():
                        tobones[bname]=bone
                    adata["tobones"] = tobones
                
                frombones = adata["frombones"]
                tobones = adata["tobones"]
                
                from_frame = 0
                to_frame = tlen
                
                for bname,bone in self.bones.items():
                    # rot
                    if bname not in frombones or bname not in tobones:
                        continue
                    
                    from_rot = frombones[bname]["rot"]
                    to_rot = tobones[bname]["rot"]
                    
                    from_x,from_y=from_rot
                    to_x,to_y=to_rot
                    
                    delta_per_frame_x = (to_x-from_x)/(to_frame-from_frame)
                    delta_per_frame_y = (to_y-from_y)/(to_frame-from_frame)
                    
                    delta_x = delta_per_frame_x*(frame2-frame1)
                    delta_y = delta_per_frame_y*(frame2-frame1)
                    
                    rot = bone.getRot(data)
                    new_rot = [rot[0]+delta_x,rot[1]+delta_y]
                    
                    bone.setRot(data,new_rot)
                    
                    # length
                    
                    # Not yet implemented
                
        elif adata["phase"]=="animation":
            # If animating
            if self.atype == "static":
                pass # Should not need any updates as the transition will set the bones
            elif self.atype == "keyframes":
                framediff=int(dt/(1./self.kps)) # Number of frames that have passed since the last update
                overhang = dt-(framediff*(1./self.kps)) # time that has passed but is not enough for a full frame
                adata["last_tick"]-=overhang # causes the next frame to include the overhang from this frame
                if framediff == 0:
                    return # optimization that saves time 
                
                frame1 = adata["keyframe"]
                adata["keyframe"]+=framediff
                if adata["keyframe"]>self.anilength:
                    repeat = True
                    adata["keyframe"]%=self.anilength
                else:
                    repeat = False
                frame2 = adata["keyframe"]
                
                if repeat:
                    frame1 = frame2
                    frame2+=1
                
                for bname,bone in self.bones.items():
                    # Rot
                    if bname in self.rframes_per_bone:
                        
                        from_frame = None#adata["from_frame"]
                        to_frame = None
                        for framenum,rot in self.rframes_per_bone[bname].items():
                            if (from_frame is None or framenum>from_frame) and framenum<=frame1:
                                # from_frame is the largest frame number that is before the starting point
                                from_frame = framenum
                            if (to_frame is None or framenum<to_frame) and framenum>=frame2:
                                # to_frame is the smallest frame number that is after the end point
                                to_frame = framenum
                        if from_frame is None or to_frame is None:
                            raise ValueError("Invalid frames for bone %s in animation %s"%(bname,self.name))
                        if from_frame==to_frame or repeat:
                            if self.anidata.get("repeat","jump")=="jump":
                                for b,dat in self.start_frame.get("bones",{}).items():
                                    if "rot" in dat:
                                        self.bones[b].setRot(data,dat["rot"])
                                    if "length" in dat:
                                        self.bones[b].setLength(data,dat["length"])
                            elif self.anidata.get("repeat","jump")=="animate":
                                from_frame = adata["to_frame"]
                        adata["from_frame"]=from_frame
                        adata["to_frame"]=to_frame
                        
                        from_rot = self.rframes_per_bone[bname][from_frame]
                        to_rot = self.rframes_per_bone[bname][to_frame]
                        
                        from_x,from_y=from_rot
                        to_x,to_y=to_rot
                        
                        if self.anidata.get("interpolation","linear") == "linear":
                            delta_per_frame_x = (to_x-from_x)/(to_frame-from_frame)
                            delta_per_frame_y = (to_y-from_y)/(to_frame-from_frame)
                            
                            delta_x = delta_per_frame_x*(frame2-frame1)
                            delta_y = delta_per_frame_y*(frame2-frame1)
                            
                            rot = bone.getRot(data)
                            new_rot = [rot[0]+delta_x,rot[1]+delta_y]
                        elif self.anidata["interpolation"] == "jump":
                            new_rot = to_x,to_y
                        else:
                            raise ValueError("Invalid interpolation method '%s' for animation %s"%(self.anidata["interpolation"],self.name))
                        
                        bone.setRot(data,new_rot)
                        
                    # Length
                    if bname in self.lframes_per_bone:
                        # Not yet implemented
                        pass
                
            else:
                # Should not be possible, but still
                raise ValueError("Invalid animation type %s for animation %s during tick"%(self.atype,name))
Callback that should be called regularly to update the animation.
        
        It is recommended to call this method about 60 times a second for smooth animations. Irregular calling of this method will be automatically adjusted.
        
        This method sets all the bones in the given actor to the next state of the animation.
        
        Note that :py:meth:`startAnimation()` must have been called before calling this method.