motiontrail: Clean up and document MotionTrail class

This commit is contained in:
rdb 2022-12-15 17:43:24 +01:00
parent cd984732b7
commit 154223d075

View File

@ -1,14 +1,16 @@
from panda3d.core import * from panda3d.core import *
from panda3d.direct import * from panda3d.direct import *
from direct.task import Task from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
from direct.showbase.DirectObject import DirectObject from direct.showbase.DirectObject import DirectObject
from direct.directnotify.DirectNotifyGlobal import directNotify from direct.directnotify.DirectNotifyGlobal import directNotify
_want_python_motion_trails = ConfigVariableBool('want-python-motion-trails', False)
def remove_task(): def remove_task():
if (MotionTrail.task_added): if MotionTrail.task_added:
total_motion_trails = len(MotionTrail.motion_trail_list) total_motion_trails = len(MotionTrail.motion_trail_list)
if total_motion_trails > 0: if total_motion_trails > 0:
@ -21,7 +23,7 @@ def remove_task ( ):
print("MotionTrail task removed") print("MotionTrail task removed")
MotionTrail.task_added = False MotionTrail.task_added = False
return
class MotionTrailVertex: class MotionTrailVertex:
def __init__(self, vertex_id, vertex_function, context): def __init__(self, vertex_id, vertex_function, context):
@ -35,12 +37,38 @@ class MotionTrailVertex:
self.end_color = Vec4(0.0, 0.0, 0.0, 1.0) self.end_color = Vec4(0.0, 0.0, 0.0, 1.0)
self.v = 0.0 self.v = 0.0
class MotionTrailFrame: class MotionTrailFrame:
def __init__(self, current_time, transform): def __init__(self, current_time, transform):
self.time = current_time self.time = current_time
self.transform = transform self.transform = transform
class MotionTrail(NodePath, DirectObject): class MotionTrail(NodePath, DirectObject):
"""Generates smooth geometry-based motion trails behind a moving object.
To use this class, first define the shape of the cross-section of the trail
by repeatedly calling `add_vertex()` and `set_vertex_color()`.
When this is done, `update_vertices()` must be called.
To generate the motion trail, either call `register_motion_trail()`
to have Panda update it automatically, or periodically call the method
`update_motion_trail()` with the current time and the new transform.
The duration of the sample history is specified by `time_window`. A larger
time window creates longer motion trails (given constant speed). Samples
that are no longer within the time window are automatically discarded.
The `use_nurbs` option can be used to create smooth interpolated curves
from the samples. This option is useful for animations that lack sampling
to begin with, animations that move very quickly, or low frame rates, or if
`sampling_time` is used to artificially slow down the update frequency.
By default, the optimized C++ implementation (provided by `.CMotionTrail`)
is used to generate the motion trails. If for some reason you want to use
the pure-Python implementation instead, set `want-python-motion-trails` to
true in Config.prc.
"""
notify = directNotify.newCategory("MotionTrail") notify = directNotify.newCategory("MotionTrail")
@ -51,12 +79,13 @@ class MotionTrail(NodePath, DirectObject):
global_enable = True global_enable = True
@classmethod @classmethod
def setGlobalEnable (self, enable): def setGlobalEnable(cls, enable):
MotionTrail.global_enable = enable cls.global_enable = enable
def __init__(self, name, parent_node_path): def __init__(self, name, parent_node_path):
"""Creates the motion trail with the given name and parents it to the
DirectObject.__init__(self) given root node.
"""
NodePath.__init__(self, name) NodePath.__init__(self, name)
# required initialization # required initialization
@ -82,13 +111,22 @@ class MotionTrail(NodePath, DirectObject):
self.previous_matrix = None self.previous_matrix = None
self.calculate_relative_matrix = False self.calculate_relative_matrix = False
self.playing = False; self.playing = False
# default options # default options
self.continuous_motion_trail = True self.continuous_motion_trail = True
self.color_scale = 1.0 self.color_scale = 1.0
## How long the time window is for which the trail is computed. Can be
## increased to obtain a longer trail, decreased for a shorter trail.
self.time_window = 1.0 self.time_window = 1.0
## How often the trail updates, in seconds. The default is 0.0, which
## has the trail updated every frame for the smoothest result. Higher
## values will generate a choppier trail. The `use_nurbs` option can
## compensate partially for this choppiness, however.
self.sampling_time = 0.0 self.sampling_time = 0.0
self.square_t = True self.square_t = True
# self.task_transform = False # self.task_transform = False
@ -96,6 +134,10 @@ class MotionTrail(NodePath, DirectObject):
# node path states # node path states
self.reparentTo(parent_node_path) self.reparentTo(parent_node_path)
## A `.GeomNode` object containing the generated geometry. By default
## parented to the MotionTrail itself, but can be reparented elsewhere
## if necessary.
self.geom_node = GeomNode("motion_trail") self.geom_node = GeomNode("motion_trail")
self.geom_node_path = self.attachNewNode(self.geom_node) self.geom_node_path = self.attachNewNode(self.geom_node)
node_path = self.geom_node_path node_path = self.geom_node_path
@ -113,9 +155,9 @@ class MotionTrail(NodePath, DirectObject):
node_path.setLightOff() node_path.setLightOff()
# disable writes to destination alpha, write out rgb colors only # disable writes to destination alpha, write out rgb colors only
node_path.setAttrib (ColorWriteAttrib.make (ColorWriteAttrib.CRed | ColorWriteAttrib.CGreen | ColorWriteAttrib.CBlue)); node_path.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.CRed | ColorWriteAttrib.CGreen | ColorWriteAttrib.CBlue))
if (MotionTrail.task_added == False): if not MotionTrail.task_added:
#taskMgr.add(self.motion_trail_task, "motion_trail_task", priority = 50) #taskMgr.add(self.motion_trail_task, "motion_trail_task", priority = 50)
taskMgr.add(self.motion_trail_task, MotionTrail.motion_trail_task_name) taskMgr.add(self.motion_trail_task, MotionTrail.motion_trail_task_name)
@ -123,9 +165,10 @@ class MotionTrail(NodePath, DirectObject):
MotionTrail.task_added = True MotionTrail.task_added = True
self.relative_to_render = False self.relative_to_render = False
## Set this to True to use a NURBS curve to generate a smooth trail,
## even if the underlying animation or movement is janky.
self.use_nurbs = False self.use_nurbs = False
self.resolution_distance = 0.5 self.resolution_distance = 0.5
@ -133,19 +176,18 @@ class MotionTrail(NodePath, DirectObject):
self.cmotion_trail.setGeomNode(self.geom_node) self.cmotion_trail.setGeomNode(self.geom_node)
self.modified_vertices = True self.modified_vertices = True
if base.config.GetBool('want-python-motion-trails', 0): if _want_python_motion_trails:
self.use_python_version = True self.use_python_version = True
else: else:
self.use_python_version = False self.use_python_version = False
return
def delete(self): def delete(self):
"""Completely cleans up the motion trail object.
"""
self.reset_motion_trail() self.reset_motion_trail()
self.reset_motion_trail_geometry() self.reset_motion_trail_geometry()
self.cmotion_trail.resetVertexList() self.cmotion_trail.resetVertexList()
self.removeNode() self.removeNode()
return
def print_matrix(self, matrix): def print_matrix(self, matrix):
separator = ' ' separator = ' '
@ -160,37 +202,37 @@ class MotionTrail(NodePath, DirectObject):
total_motion_trails = len(MotionTrail.motion_trail_list) total_motion_trails = len(MotionTrail.motion_trail_list)
index = 0 index = 0
while (index < total_motion_trails): while index < total_motion_trails:
motion_trail = MotionTrail.motion_trail_list [index] motion_trail = MotionTrail.motion_trail_list [index]
if (MotionTrail.global_enable): if MotionTrail.global_enable:
if (motion_trail.use_python_version): if motion_trail.use_python_version:
# Python version # Python version
if (motion_trail.active and motion_trail.check_for_update (current_time)): if motion_trail.active and motion_trail.check_for_update(current_time):
transform = None transform = None
if (motion_trail.root_node_path != None) and (motion_trail.root_node_path != render): if motion_trail.root_node_path is not None and motion_trail.root_node_path != render:
motion_trail.root_node_path.update() motion_trail.root_node_path.update()
if (motion_trail.root_node_path and (motion_trail.relative_to_render == False)): if motion_trail.root_node_path and not motion_trail.relative_to_render:
transform = motion_trail.getMat(motion_trail.root_node_path) transform = motion_trail.getMat(motion_trail.root_node_path)
else: else:
transform = Mat4(motion_trail.getNetTransform().getMat()) transform = Mat4(motion_trail.getNetTransform().getMat())
if (transform != None): if transform is not None:
motion_trail.update_motion_trail(current_time, transform) motion_trail.update_motion_trail(current_time, transform)
else: else:
# C++ version # C++ version
if (motion_trail.active and motion_trail.cmotion_trail.checkForUpdate (current_time)): if motion_trail.active and motion_trail.cmotion_trail.checkForUpdate(current_time):
transform = None transform = None
if (motion_trail.root_node_path != None) and (motion_trail.root_node_path != render): if motion_trail.root_node_path is not None and motion_trail.root_node_path != render:
motion_trail.root_node_path.update() motion_trail.root_node_path.update()
if (motion_trail.root_node_path and (motion_trail.relative_to_render == False)): if motion_trail.root_node_path and not motion_trail.relative_to_render:
transform = motion_trail.getMat(motion_trail.root_node_path) transform = motion_trail.getMat(motion_trail.root_node_path)
else: else:
transform = Mat4(motion_trail.getNetTransform().getMat()) transform = Mat4(motion_trail.getNetTransform().getMat())
if (transform != None): if transform is not None:
motion_trail.transferVertices() motion_trail.transferVertices()
motion_trail.cmotion_trail.updateMotionTrail(current_time, transform) motion_trail.cmotion_trail.updateMotionTrail(current_time, transform)
@ -203,7 +245,17 @@ class MotionTrail(NodePath, DirectObject):
return Task.cont return Task.cont
def add_vertex(self, vertex_id, vertex_function, context): def add_vertex(self, vertex_id, vertex_function, context):
"""This must be called repeatedly to define the polygon that forms the
cross-section of the generated motion trail geometry. The first
argument is a user-defined vertex identifier, the second is a function
that will be called with three parameters that should return the
position of the vertex as a `.Vec4` object, and the third is an
arbitrary context object that is passed as last argument to the
provided function.
After calling this, you must call `update_vertices()` before the
changes will fully take effect.
"""
motion_trail_vertex = MotionTrailVertex(vertex_id, vertex_function, context) motion_trail_vertex = MotionTrailVertex(vertex_id, vertex_function, context)
total_vertices = len(self.vertex_list) total_vertices = len(self.vertex_list)
@ -216,18 +268,26 @@ class MotionTrail(NodePath, DirectObject):
return motion_trail_vertex return motion_trail_vertex
def set_vertex_color(self, vertex_id, start_color, end_color): def set_vertex_color(self, vertex_id, start_color, end_color):
if (vertex_id >= 0 and vertex_id < self.total_vertices): """Sets the start and end color of the vertex with the given index,
which must have been previously added by `add_vertex()`. The motion
trail will contain a smooth gradient between these colors. By default,
the motion trail fades from white to black (which, with the default
additive blending mode, makes it show up as a purely white motion trail
that fades out towards the end).
"""
if vertex_id >= 0 and vertex_id < self.total_vertices:
motion_trail_vertex = self.vertex_list[vertex_id] motion_trail_vertex = self.vertex_list[vertex_id]
motion_trail_vertex.start_color = start_color motion_trail_vertex.start_color = start_color
motion_trail_vertex.end_color = end_color motion_trail_vertex.end_color = end_color
self.modified_vertices = True self.modified_vertices = True
return
def set_texture(self, texture): def set_texture(self, texture):
"""Defines the texture that should be applied to the trail geometry.
This also enables generation of UV coordinates.
"""
self.texture = texture self.texture = texture
if (texture): if texture:
self.geom_node_path.setTexture(texture) self.geom_node_path.setTexture(texture)
# texture.setWrapU(Texture.WMClamp) # texture.setWrapU(Texture.WMClamp)
# texture.setWrapV(Texture.WMClamp) # texture.setWrapV(Texture.WMClamp)
@ -235,16 +295,18 @@ class MotionTrail(NodePath, DirectObject):
self.geom_node_path.clearTexture() self.geom_node_path.clearTexture()
self.modified_vertices = True self.modified_vertices = True
return
def update_vertices(self): def update_vertices(self):
"""This must be called after the list of vertices defining the
cross-section shape of the motion trail has been defined by
`add_vertex()` and `set_vertex_color()`.
"""
total_vertices = len(self.vertex_list) total_vertices = len(self.vertex_list)
self.total_vertices = total_vertices self.total_vertices = total_vertices
if (total_vertices >= 2): if total_vertices >= 2:
vertex_index = 0 vertex_index = 0
while (vertex_index < total_vertices): while vertex_index < total_vertices:
motion_trail_vertex = self.vertex_list[vertex_index] motion_trail_vertex = self.vertex_list[vertex_index]
motion_trail_vertex.vertex = motion_trail_vertex.vertex_function(motion_trail_vertex, motion_trail_vertex.vertex_id, motion_trail_vertex.context) motion_trail_vertex.vertex = motion_trail_vertex.vertex_function(motion_trail_vertex, motion_trail_vertex.vertex_id, motion_trail_vertex.context)
vertex_index += 1 vertex_index += 1
@ -255,7 +317,7 @@ class MotionTrail(NodePath, DirectObject):
float_vertex_index = 0.0 float_vertex_index = 0.0
float_total_vertices = 0.0 float_total_vertices = 0.0
float_total_vertices = total_vertices - 1.0 float_total_vertices = total_vertices - 1.0
while (vertex_index < total_vertices): while vertex_index < total_vertices:
motion_trail_vertex = self.vertex_list[vertex_index] motion_trail_vertex = self.vertex_list[vertex_index]
motion_trail_vertex.v = float_vertex_index / float_total_vertices motion_trail_vertex.v = float_vertex_index / float_total_vertices
vertex_index += 1 vertex_index += 1
@ -264,41 +326,41 @@ class MotionTrail(NodePath, DirectObject):
# print "motion_trail_vertex.v", motion_trail_vertex.v # print "motion_trail_vertex.v", motion_trail_vertex.v
self.modified_vertices = True self.modified_vertices = True
return
def transferVertices(self): def transferVertices(self):
# transfer only on modification # transfer only on modification
if (self.modified_vertices): if self.modified_vertices:
self.cmotion_trail.setParameters (self.sampling_time, self.time_window, self.texture != None, self.calculate_relative_matrix, self.use_nurbs, self.resolution_distance) self.cmotion_trail.setParameters(self.sampling_time, self.time_window, self.texture is not None, self.calculate_relative_matrix, self.use_nurbs, self.resolution_distance)
self.cmotion_trail.resetVertexList() self.cmotion_trail.resetVertexList()
vertex_index = 0 vertex_index = 0
total_vertices = len(self.vertex_list) total_vertices = len(self.vertex_list)
while (vertex_index < total_vertices): while vertex_index < total_vertices:
motion_trail_vertex = self.vertex_list[vertex_index] motion_trail_vertex = self.vertex_list[vertex_index]
self.cmotion_trail.addVertex(motion_trail_vertex.vertex, motion_trail_vertex.start_color, motion_trail_vertex.end_color, motion_trail_vertex.v) self.cmotion_trail.addVertex(motion_trail_vertex.vertex, motion_trail_vertex.start_color, motion_trail_vertex.end_color, motion_trail_vertex.v)
vertex_index += 1 vertex_index += 1
self.modified_vertices = False self.modified_vertices = False
return
def register_motion_trail(self): def register_motion_trail(self):
"""Adds this motion trail to the list of trails that are updated
automatically every frame. Be careful not to call this twice.
"""
MotionTrail.motion_trail_list = MotionTrail.motion_trail_list + [self] MotionTrail.motion_trail_list = MotionTrail.motion_trail_list + [self]
return
def unregister_motion_trail(self): def unregister_motion_trail(self):
if (self in MotionTrail.motion_trail_list): """Removes this motion trail from the list of trails that are updated
automatically every frame. If it is not on that list, does nothing.
"""
if self in MotionTrail.motion_trail_list:
MotionTrail.motion_trail_list.remove(self) MotionTrail.motion_trail_list.remove(self)
return
def begin_geometry(self): def begin_geometry(self):
self.vertex_index = 0
self.vertex_index = 0; if self.texture is not None:
if (self.texture != None):
self.format = GeomVertexFormat.getV3c4t2() self.format = GeomVertexFormat.getV3c4t2()
else: else:
self.format = GeomVertexFormat.getV3c4() self.format = GeomVertexFormat.getV3c4()
@ -307,7 +369,7 @@ class MotionTrail(NodePath, DirectObject):
self.vertex_writer = GeomVertexWriter(self.vertex_data, "vertex") self.vertex_writer = GeomVertexWriter(self.vertex_data, "vertex")
self.color_writer = GeomVertexWriter(self.vertex_data, "color") self.color_writer = GeomVertexWriter(self.vertex_data, "color")
if (self.texture != None): if self.texture is not None:
self.texture_writer = GeomVertexWriter(self.vertex_data, "texcoord") self.texture_writer = GeomVertexWriter(self.vertex_data, "texcoord")
self.triangles = GeomTriangles(Geom.UHStatic) self.triangles = GeomTriangles(Geom.UHStatic)
@ -324,13 +386,13 @@ class MotionTrail(NodePath, DirectObject):
self.color_writer.addData4f(c2) self.color_writer.addData4f(c2)
self.color_writer.addData4f(c3) self.color_writer.addData4f(c3)
if (self.texture != None): if self.texture is not None:
self.texture_writer.addData2f(t0) self.texture_writer.addData2f(t0)
self.texture_writer.addData2f(t1) self.texture_writer.addData2f(t1)
self.texture_writer.addData2f(t2) self.texture_writer.addData2f(t2)
self.texture_writer.addData2f(t3) self.texture_writer.addData2f(t3)
vertex_index = self.vertex_index; vertex_index = self.vertex_index
self.triangles.addVertex(vertex_index + 0) self.triangles.addVertex(vertex_index + 0)
self.triangles.addVertex(vertex_index + 1) self.triangles.addVertex(vertex_index + 1)
@ -352,12 +414,15 @@ class MotionTrail(NodePath, DirectObject):
self.geom_node.addGeom(self.geometry) self.geom_node.addGeom(self.geometry)
def check_for_update(self, current_time): def check_for_update(self, current_time):
"""Returns true if the motion trail is overdue for an update based on
the configured `sampling_time` (by default 0.0 to update continuously),
and is not currently paused.
"""
state = False state = False
if ((current_time - self.last_update_time) >= self.sampling_time): if (current_time - self.last_update_time) >= self.sampling_time:
state = True state = True
if (self.pause): if self.pause:
state = False state = False
update = state and self.enable update = state and self.enable
@ -365,24 +430,26 @@ class MotionTrail(NodePath, DirectObject):
return state return state
def update_motion_trail(self, current_time, transform): def update_motion_trail(self, current_time, transform):
"""If the trail is overdue for an update based on the given time in
if (len (self.frame_list) >= 1): seconds, updates it, extracting the new object position from the given
if (transform == self.frame_list [0].transform): transform matrix.
"""
if len(self.frame_list) >= 1:
if transform == self.frame_list[0].transform:
# ignore duplicate transform updates # ignore duplicate transform updates
return return
if (self.check_for_update (current_time)): if self.check_for_update(current_time):
color_scale = self.color_scale
color_scale = self.color_scale; if self.fade:
if (self.fade):
elapsed_time = current_time - self.fade_start_time elapsed_time = current_time - self.fade_start_time
if (elapsed_time < 0.0): if elapsed_time < 0.0:
print("elapsed_time < 0: %f" % (elapsed_time)) print("elapsed_time < 0: %f" % (elapsed_time))
elapsed_time = 0.0 elapsed_time = 0.0
if (elapsed_time < self.fade_time): if elapsed_time < self.fade_time:
color_scale = (1.0 - (elapsed_time / self.fade_time)) * color_scale color_scale = (1.0 - (elapsed_time / self.fade_time)) * color_scale
else: else:
color_scale = 0.0 color_scale = 0.0
@ -397,13 +464,13 @@ class MotionTrail(NodePath, DirectObject):
last_frame_index = len(self.frame_list) - 1 last_frame_index = len(self.frame_list) - 1
while (index <= last_frame_index): while index <= last_frame_index:
motion_trail_frame = self.frame_list[last_frame_index - index] motion_trail_frame = self.frame_list[last_frame_index - index]
if (motion_trail_frame.time >= minimum_time): if motion_trail_frame.time >= minimum_time:
break break
index += 1 index += 1
if (index > 0): if index > 0:
self.frame_list[last_frame_index - index: last_frame_index + 1] = [] self.frame_list[last_frame_index - index: last_frame_index + 1] = []
# add new frame to beginning of list # add new frame to beginning of list
@ -413,29 +480,26 @@ class MotionTrail(NodePath, DirectObject):
# convert frames and vertices to geometry # convert frames and vertices to geometry
total_frames = len(self.frame_list) total_frames = len(self.frame_list)
""" #print("total_frames", total_frames)
print "total_frames", total_frames #
#index = 0
index = 0; #while index < total_frames:
while (index < total_frames): # motion_trail_frame = self.frame_list[index]
motion_trail_frame = self.frame_list [index] # print("frame time", index, motion_trail_frame.time)
print "frame time", index, motion_trail_frame.time # index += 1
index += 1
"""
if ((total_frames >= 2) and (self.total_vertices >= 2)):
if total_frames >= 2 and self.total_vertices >= 2:
self.begin_geometry() self.begin_geometry()
total_segments = total_frames - 1 total_segments = total_frames - 1
last_motion_trail_frame = self.frame_list[total_segments] last_motion_trail_frame = self.frame_list[total_segments]
minimum_time = last_motion_trail_frame.time minimum_time = last_motion_trail_frame.time
delta_time = current_time - minimum_time delta_time = current_time - minimum_time
if (self.calculate_relative_matrix): if self.calculate_relative_matrix:
inverse_matrix = Mat4(transform) inverse_matrix = Mat4(transform)
inverse_matrix.invertInPlace() inverse_matrix.invertInPlace()
if (self.use_nurbs and (total_frames >= 5)): if self.use_nurbs and total_frames >= 5:
total_distance = 0.0 total_distance = 0.0
vector = Vec3() vector = Vec3()
@ -446,7 +510,7 @@ class MotionTrail(NodePath, DirectObject):
# create a NurbsCurveEvaluator for each vertex(the starting point for the trail) # create a NurbsCurveEvaluator for each vertex(the starting point for the trail)
index = 0 index = 0
while (index < self.total_vertices): while index < self.total_vertices:
nurbs_curve_evaluator = NurbsCurveEvaluator() nurbs_curve_evaluator = NurbsCurveEvaluator()
nurbs_curve_evaluator.reset(total_segments) nurbs_curve_evaluator.reset(total_segments)
nurbs_curve_evaluator_list = nurbs_curve_evaluator_list + [nurbs_curve_evaluator] nurbs_curve_evaluator_list = nurbs_curve_evaluator_list + [nurbs_curve_evaluator]
@ -454,13 +518,13 @@ class MotionTrail(NodePath, DirectObject):
# add vertices to each NurbsCurveEvaluator # add vertices to each NurbsCurveEvaluator
segment_index = 0 segment_index = 0
while (segment_index < total_segments): while segment_index < total_segments:
motion_trail_frame_start = self.frame_list[segment_index] motion_trail_frame_start = self.frame_list[segment_index]
motion_trail_frame_end = self.frame_list[segment_index + 1] motion_trail_frame_end = self.frame_list[segment_index + 1]
vertex_segement_index = 0 vertex_segment_index = 0
if (self.calculate_relative_matrix): if self.calculate_relative_matrix:
start_transform = Mat4() start_transform = Mat4()
end_transform = Mat4() end_transform = Mat4()
@ -476,36 +540,36 @@ class MotionTrail(NodePath, DirectObject):
v0 = start_transform.xform(motion_trail_vertex_start.vertex) v0 = start_transform.xform(motion_trail_vertex_start.vertex)
v2 = end_transform.xform(motion_trail_vertex_start.vertex) v2 = end_transform.xform(motion_trail_vertex_start.vertex)
nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segement_index] nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segment_index]
nurbs_curve_evaluator.setVertex(segment_index, v0) nurbs_curve_evaluator.setVertex(segment_index, v0)
while (vertex_segement_index < total_vertex_segments): while vertex_segment_index < total_vertex_segments:
motion_trail_vertex_start = self.vertex_list [vertex_segement_index] motion_trail_vertex_start = self.vertex_list[vertex_segment_index]
motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1] motion_trail_vertex_end = self.vertex_list[vertex_segment_index + 1]
v1 = start_transform.xform(motion_trail_vertex_end.vertex) v1 = start_transform.xform(motion_trail_vertex_end.vertex)
v3 = end_transform.xform(motion_trail_vertex_end.vertex) v3 = end_transform.xform(motion_trail_vertex_end.vertex)
nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segement_index + 1] nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segment_index + 1]
nurbs_curve_evaluator.setVertex(segment_index, v1) nurbs_curve_evaluator.setVertex(segment_index, v1)
if (vertex_segement_index == (total_vertex_segments - 1)): if vertex_segment_index == (total_vertex_segments - 1):
v = v1 - v3 v = v1 - v3
vector.set(v[0], v[1], v[2]) vector.set(v[0], v[1], v[2])
distance = vector.length() distance = vector.length()
total_distance += distance total_distance += distance
vertex_segement_index += 1 vertex_segment_index += 1
segment_index += 1 segment_index += 1
# evaluate NurbsCurveEvaluator for each vertex # evaluate NurbsCurveEvaluator for each vertex
index = 0 index = 0
nurbs_curve_result_list = [] nurbs_curve_result_list = []
while (index < self.total_vertices): while index < self.total_vertices:
nurbs_curve_evaluator = nurbs_curve_evaluator_list [index] nurbs_curve_evaluator = nurbs_curve_evaluator_list [index]
nurbs_curve_result = nurbs_curve_evaluator.evaluate() nurbs_curve_result = nurbs_curve_evaluator.evaluate()
nurbs_curve_result_list = nurbs_curve_result_list + [nurbs_curve_result] nurbs_curve_result_list = nurbs_curve_result_list + [nurbs_curve_result]
@ -517,8 +581,8 @@ class MotionTrail(NodePath, DirectObject):
# create quads from NurbsCurveResult # create quads from NurbsCurveResult
total_curve_segments = total_distance / self.resolution_distance total_curve_segments = total_distance / self.resolution_distance
if (total_curve_segments < total_segments): if total_curve_segments < total_segments:
total_curve_segments = total_segments; total_curve_segments = total_segments
v0 = Vec3() v0 = Vec3()
v1 = Vec3() v1 = Vec3()
@ -527,26 +591,24 @@ class MotionTrail(NodePath, DirectObject):
def one_minus_x(x): def one_minus_x(x):
x = 1.0 - x x = 1.0 - x
if (x < 0.0): if x < 0.0:
x = 0.0 x = 0.0
return x return x
curve_segment_index = 0.0 curve_segment_index = 0.0
while (curve_segment_index < total_curve_segments): while curve_segment_index < total_curve_segments:
vertex_segement_index = 0 vertex_segment_index = 0
if (True):
st = curve_segment_index / total_curve_segments st = curve_segment_index / total_curve_segments
et = (curve_segment_index + 1.0) / total_curve_segments et = (curve_segment_index + 1.0) / total_curve_segments
else: #st = curve_segment_index / total_segments
st = curve_segment_index / total_segments #et = (curve_segment_index + 1.0) / total_segments
et = (curve_segment_index + 1.0) / total_segments
start_t = st start_t = st
end_t = et end_t = et
if (self.square_t): if self.square_t:
start_t *= start_t start_t *= start_t
end_t *= end_t end_t *= end_t
@ -561,13 +623,13 @@ class MotionTrail(NodePath, DirectObject):
t0 = Vec2(one_minus_x(st), motion_trail_vertex_start.v) t0 = Vec2(one_minus_x(st), motion_trail_vertex_start.v)
t2 = Vec2(one_minus_x(et), motion_trail_vertex_start.v) t2 = Vec2(one_minus_x(et), motion_trail_vertex_start.v)
while (vertex_segement_index < total_vertex_segments): while vertex_segment_index < total_vertex_segments:
motion_trail_vertex_start = self.vertex_list [vertex_segement_index] motion_trail_vertex_start = self.vertex_list[vertex_segment_index]
motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1] motion_trail_vertex_end = self.vertex_list[vertex_segment_index + 1]
start_nurbs_curve_result = nurbs_curve_result_list [vertex_segement_index] start_nurbs_curve_result = nurbs_curve_result_list [vertex_segment_index]
end_nurbs_curve_result = nurbs_curve_result_list [vertex_segement_index + 1] end_nurbs_curve_result = nurbs_curve_result_list [vertex_segment_index + 1]
start_nurbs_start_t = start_nurbs_curve_result.getStartT() start_nurbs_start_t = start_nurbs_curve_result.getStartT()
start_nurbs_end_t = start_nurbs_curve_result.getEndT() start_nurbs_end_t = start_nurbs_curve_result.getEndT()
@ -577,11 +639,11 @@ class MotionTrail(NodePath, DirectObject):
start_delta_t = (start_nurbs_end_t - start_nurbs_start_t) start_delta_t = (start_nurbs_end_t - start_nurbs_start_t)
end_delta_t = (end_nurbs_end_t - end_nurbs_start_t) end_delta_t = (end_nurbs_end_t - end_nurbs_start_t)
start_nurbs_curve_result.evalPoint (start_nurbs_start_t + (start_delta_t * st), v0); start_nurbs_curve_result.evalPoint(start_nurbs_start_t + (start_delta_t * st), v0)
end_nurbs_curve_result.evalPoint (end_nurbs_start_t + (end_delta_t * st), v1); end_nurbs_curve_result.evalPoint(end_nurbs_start_t + (end_delta_t * st), v1)
start_nurbs_curve_result.evalPoint (start_nurbs_start_t + (start_delta_t * et), v2); start_nurbs_curve_result.evalPoint(start_nurbs_start_t + (start_delta_t * et), v2)
end_nurbs_curve_result.evalPoint (end_nurbs_start_t + (end_delta_t * et), v3); end_nurbs_curve_result.evalPoint(end_nurbs_start_t + (end_delta_t * et), v3)
# color # color
vertex_end_color = motion_trail_vertex_end.end_color + (motion_trail_vertex_end.start_color - motion_trail_vertex_end.end_color) vertex_end_color = motion_trail_vertex_end.end_color + (motion_trail_vertex_end.start_color - motion_trail_vertex_end.end_color)
@ -602,15 +664,13 @@ class MotionTrail(NodePath, DirectObject):
t0 = t1 t0 = t1
t2 = t3 t2 = t3
vertex_segement_index += 1 vertex_segment_index += 1
curve_segment_index += 1.0 curve_segment_index += 1.0
else: else:
segment_index = 0 segment_index = 0
while (segment_index < total_segments): while segment_index < total_segments:
motion_trail_frame_start = self.frame_list[segment_index] motion_trail_frame_start = self.frame_list[segment_index]
motion_trail_frame_end = self.frame_list[segment_index + 1] motion_trail_frame_end = self.frame_list[segment_index + 1]
@ -620,14 +680,14 @@ class MotionTrail(NodePath, DirectObject):
st = start_t st = start_t
et = end_t et = end_t
if (self.square_t): if self.square_t:
start_t *= start_t start_t *= start_t
end_t *= end_t end_t *= end_t
vertex_segement_index = 0 vertex_segment_index = 0
total_vertex_segments = self.total_vertices - 1 total_vertex_segments = self.total_vertices - 1
if (self.calculate_relative_matrix): if self.calculate_relative_matrix:
start_transform = Mat4() start_transform = Mat4()
end_transform = Mat4() end_transform = Mat4()
start_transform.multiply(motion_trail_frame_start.transform, inverse_matrix) start_transform.multiply(motion_trail_frame_start.transform, inverse_matrix)
@ -650,10 +710,10 @@ class MotionTrail(NodePath, DirectObject):
t0 = Vec2(st, motion_trail_vertex_start.v) t0 = Vec2(st, motion_trail_vertex_start.v)
t2 = Vec2(et, motion_trail_vertex_start.v) t2 = Vec2(et, motion_trail_vertex_start.v)
while (vertex_segement_index < total_vertex_segments): while vertex_segment_index < total_vertex_segments:
motion_trail_vertex_start = self.vertex_list [vertex_segement_index] motion_trail_vertex_start = self.vertex_list[vertex_segment_index]
motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1] motion_trail_vertex_end = self.vertex_list[vertex_segment_index + 1]
v1 = start_transform.xform(motion_trail_vertex_end.vertex) v1 = start_transform.xform(motion_trail_vertex_end.vertex)
v3 = end_transform.xform(motion_trail_vertex_end.vertex) v3 = end_transform.xform(motion_trail_vertex_end.vertex)
@ -680,87 +740,88 @@ class MotionTrail(NodePath, DirectObject):
t0 = t1 t0 = t1
t2 = t3 t2 = t3
vertex_segement_index += 1 vertex_segment_index += 1
segment_index += 1 segment_index += 1
self.end_geometry() self.end_geometry()
return
def enable_motion_trail(self, enable): def enable_motion_trail(self, enable):
"""Sets whether the motion trail is currently enabled. Every motion
trail starts off as being enabled, passing False to this method prevents
it from being updated.
"""
self.enable = enable self.enable = enable
return
def reset_motion_trail(self): def reset_motion_trail(self):
"""Call this to have the motion trail restart from nothing on the next
update.
"""
self.frame_list = [] self.frame_list = []
self.cmotion_trail.reset ( ); self.cmotion_trail.reset()
return
def reset_motion_trail_geometry(self): def reset_motion_trail_geometry(self):
if (self.geom_node != None): """Destroys the currently generated motion trail geometry immediately.
However, it will be fully regenerated on the next call to update, see
`reset_motion_trail()` to prevent this.
"""
if self.geom_node is not None:
self.geom_node.removeAllGeoms() self.geom_node.removeAllGeoms()
return
def attach_motion_trail(self): def attach_motion_trail(self):
"""Alias of `reset_motion_trail()`.
"""
self.reset_motion_trail() self.reset_motion_trail()
return
def begin_motion_trail(self): def begin_motion_trail(self):
if (self.continuous_motion_trail == False): if not self.continuous_motion_trail:
self.reset_motion_trail() self.reset_motion_trail()
self.active = True; self.active = True
self.playing = True; self.playing = True
return
def end_motion_trail(self): def end_motion_trail(self):
if (self.continuous_motion_trail == False): if not self.continuous_motion_trail:
self.active = False self.active = False
self.reset_motion_trail() self.reset_motion_trail()
self.reset_motion_trail_geometry() self.reset_motion_trail_geometry()
self.playing = False; self.playing = False
return
# the following functions are not currently supported in the C++ version # the following functions are not currently supported in the C++ version
def set_fade(self, time, current_time): def set_fade(self, time, current_time):
if (self.pause == False): if not self.pause:
self.fade_color_scale = 1.0 self.fade_color_scale = 1.0
if (time == 0.0): if time == 0.0:
self.fade = False self.fade = False
else: else:
self.fade_start_time = current_time self.fade_start_time = current_time
self.fade_time = time self.fade_time = time
self.fade = True self.fade = True
return
def pause_motion_trail(self, current_time): def pause_motion_trail(self, current_time):
if (self.pause == False): if not self.pause:
self.pause_time = current_time self.pause_time = current_time
self.pause = True self.pause = True
return
def resume_motion_trail(self, current_time): def resume_motion_trail(self, current_time):
if (self.pause): if self.pause:
delta_time = current_time - self.pause_time delta_time = current_time - self.pause_time
frame_index = 0 frame_index = 0
total_frames = len(self.frame_list) total_frames = len(self.frame_list)
while (frame_index < total_frames): while frame_index < total_frames:
motion_trail_frame = self.frame_list[frame_index] motion_trail_frame = self.frame_list[frame_index]
motion_trail_frame.time += delta_time motion_trail_frame.time += delta_time
frame_index += 1 frame_index += 1
if (self.fade): if self.fade:
self.fade_start_time += delta_time self.fade_start_time += delta_time
self.pause = False self.pause = False
return
def toggle_pause_motion_trail(self, current_time): def toggle_pause_motion_trail(self, current_time):
if (self.pause): if self.pause:
self.resume_motion_trail(current_time) self.resume_motion_trail(current_time)
else: else:
self.pause_motion_trail(current_time) self.pause_motion_trail(current_time)