from panda3d.core import *
from panda3d.direct import *
from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
from direct.showbase.DirectObject import DirectObject
from direct.directnotify.DirectNotifyGlobal import directNotify
import warnings
_want_python_motion_trails = ConfigVariableBool('want-python-motion-trails', False)
[docs]def remove_task():
if MotionTrail.task_added:
total_motion_trails = len(MotionTrail.motion_trail_list)
if total_motion_trails > 0:
if __debug__:
warnings.warn("%d motion trails still exist when motion trail task is removed" % (total_motion_trails), RuntimeWarning, stacklevel=2)
MotionTrail.motion_trail_list = []
taskMgr.remove(MotionTrail.motion_trail_task_name)
print("MotionTrail task removed")
MotionTrail.task_added = False
[docs]class MotionTrailVertex:
[docs] def __init__(self, vertex_id, vertex_function, context):
self.vertex_id = vertex_id
self.vertex_function = vertex_function
self.context = context
self.vertex = Vec4(0.0, 0.0, 0.0, 1.0)
# default
self.start_color = Vec4(1.0, 1.0, 1.0, 1.0)
self.end_color = Vec4(0.0, 0.0, 0.0, 1.0)
self.v = 0.0
[docs]class MotionTrailFrame:
[docs] def __init__(self, current_time, transform):
self.time = current_time
self.transform = transform
[docs]class MotionTrail(NodePath, DirectObject):
notify = directNotify.newCategory("MotionTrail")
task_added = False
motion_trail_list = []
motion_trail_task_name = "motion_trail_task"
global_enable = True
[docs] @classmethod
def setGlobalEnable(cls, enable):
cls.global_enable = enable
[docs] def __init__(self, name, parent_node_path):
NodePath.__init__(self, name)
# required initialization
self.active = True
self.enable = True
self.pause = False
self.pause_time = 0.0
self.fade = False
self.fade_end = False
self.fade_start_time = 0.0
self.fade_color_scale = 1.0
self.total_vertices = 0
self.last_update_time = 0.0
self.texture = None
self.vertex_list = []
self.frame_list = []
self.parent_node_path = parent_node_path
self.previous_matrix = None
self.calculate_relative_matrix = False
self.playing = False
# default options
self.continuous_motion_trail = True
self.color_scale = 1.0
self.time_window = 1.0
self.sampling_time = 0.0
self.square_t = True
# self.task_transform = False
self.root_node_path = None
# node path states
self.reparentTo(parent_node_path)
self.geom_node = GeomNode("motion_trail")
self.geom_node_path = self.attachNewNode(self.geom_node)
node_path = self.geom_node_path
### set render states
node_path.setTwoSided(True)
# set additive blend effects
node_path.setTransparency(True)
node_path.setDepthWrite(False)
node_path.node().setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd))
# do not light
node_path.setLightOff()
# disable writes to destination alpha, write out rgb colors only
node_path.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.CRed | ColorWriteAttrib.CGreen | ColorWriteAttrib.CBlue))
if not MotionTrail.task_added:
#taskMgr.add(self.motion_trail_task, "motion_trail_task", priority = 50)
taskMgr.add(self.motion_trail_task, MotionTrail.motion_trail_task_name)
self.acceptOnce("clientLogout", remove_task)
MotionTrail.task_added = True
self.relative_to_render = False
self.use_nurbs = False
self.resolution_distance = 0.5
self.cmotion_trail = CMotionTrail()
self.cmotion_trail.setGeomNode(self.geom_node)
self.modified_vertices = True
if _want_python_motion_trails:
self.use_python_version = True
else:
self.use_python_version = False
return
[docs] def delete(self):
self.reset_motion_trail()
self.reset_motion_trail_geometry()
self.cmotion_trail.resetVertexList()
self.removeNode()
return
[docs] def print_matrix(self, matrix):
separator = ' '
print(matrix.getCell(0, 0), separator, matrix.getCell(0, 1), separator, matrix.getCell(0, 2), separator, matrix.getCell(0, 3))
print(matrix.getCell(1, 0), separator, matrix.getCell(1, 1), separator, matrix.getCell(1, 2), separator, matrix.getCell(1, 3))
print(matrix.getCell(2, 0), separator, matrix.getCell(2, 1), separator, matrix.getCell(2, 2), separator, matrix.getCell(2, 3))
print(matrix.getCell(3, 0), separator, matrix.getCell(3, 1), separator, matrix.getCell(3, 2), separator, matrix.getCell(3, 3))
[docs] def motion_trail_task(self, task):
current_time = task.time
total_motion_trails = len(MotionTrail.motion_trail_list)
index = 0
while index < total_motion_trails:
motion_trail = MotionTrail.motion_trail_list [index]
if MotionTrail.global_enable:
if motion_trail.use_python_version:
# Python version
if motion_trail.active and motion_trail.check_for_update(current_time):
transform = None
if motion_trail.root_node_path is not None and motion_trail.root_node_path != render:
motion_trail.root_node_path.update()
if motion_trail.root_node_path and not motion_trail.relative_to_render:
transform = motion_trail.getMat(motion_trail.root_node_path)
else:
transform = Mat4(motion_trail.getNetTransform().getMat())
if transform is not None:
motion_trail.update_motion_trail(current_time, transform)
else:
# C++ version
if motion_trail.active and motion_trail.cmotion_trail.checkForUpdate(current_time):
transform = None
if motion_trail.root_node_path is not None and motion_trail.root_node_path != render:
motion_trail.root_node_path.update()
if motion_trail.root_node_path and not motion_trail.relative_to_render:
transform = motion_trail.getMat(motion_trail.root_node_path)
else:
transform = Mat4(motion_trail.getNetTransform().getMat())
if transform is not None:
motion_trail.transferVertices()
motion_trail.cmotion_trail.updateMotionTrail(current_time, transform)
else:
motion_trail.reset_motion_trail()
motion_trail.reset_motion_trail_geometry()
index += 1
return Task.cont
[docs] def add_vertex(self, vertex_id, vertex_function, context):
motion_trail_vertex = MotionTrailVertex(vertex_id, vertex_function, context)
total_vertices = len(self.vertex_list)
self.vertex_list [total_vertices : total_vertices] = [motion_trail_vertex]
self.total_vertices = len(self.vertex_list)
self.modified_vertices = True
return motion_trail_vertex
[docs] def set_vertex_color(self, vertex_id, start_color, end_color):
if vertex_id >= 0 and vertex_id < self.total_vertices:
motion_trail_vertex = self.vertex_list [vertex_id]
motion_trail_vertex.start_color = start_color
motion_trail_vertex.end_color = end_color
self.modified_vertices = True
return
[docs] def set_texture(self, texture):
self.texture = texture
if texture:
self.geom_node_path.setTexture(texture)
# texture.setWrapU(Texture.WMClamp)
# texture.setWrapV(Texture.WMClamp)
else:
self.geom_node_path.clearTexture()
self.modified_vertices = True
return
[docs] def update_vertices(self):
total_vertices = len(self.vertex_list)
self.total_vertices = total_vertices
if total_vertices >= 2:
vertex_index = 0
while vertex_index < total_vertices:
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)
vertex_index += 1
# calculate v coordinate
# this is based on the number of vertices only and not on the relative positions of the vertices
vertex_index = 0
float_vertex_index = 0.0
float_total_vertices = 0.0
float_total_vertices = total_vertices - 1.0
while vertex_index < total_vertices:
motion_trail_vertex = self.vertex_list [vertex_index]
motion_trail_vertex.v = float_vertex_index / float_total_vertices
vertex_index += 1
float_vertex_index += 1.0
# print "motion_trail_vertex.v", motion_trail_vertex.v
self.modified_vertices = True
return
[docs] def transferVertices(self):
# transfer only on modification
if self.modified_vertices:
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()
vertex_index = 0
total_vertices = len(self.vertex_list)
while vertex_index < total_vertices:
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)
vertex_index += 1
self.modified_vertices = False
return
[docs] def register_motion_trail(self):
MotionTrail.motion_trail_list = MotionTrail.motion_trail_list + [self]
return
[docs] def unregister_motion_trail(self):
if self in MotionTrail.motion_trail_list:
MotionTrail.motion_trail_list.remove(self)
return
[docs] def begin_geometry(self):
self.vertex_index = 0
if self.texture is not None:
self.format = GeomVertexFormat.getV3c4t2()
else:
self.format = GeomVertexFormat.getV3c4()
self.vertex_data = GeomVertexData("vertices", self.format, Geom.UHStatic)
self.vertex_writer = GeomVertexWriter(self.vertex_data, "vertex")
self.color_writer = GeomVertexWriter(self.vertex_data, "color")
if self.texture is not None:
self.texture_writer = GeomVertexWriter(self.vertex_data, "texcoord")
self.triangles = GeomTriangles(Geom.UHStatic)
[docs] def add_geometry_quad(self, v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3):
self.vertex_writer.addData3f(v0 [0], v0 [1], v0 [2])
self.vertex_writer.addData3f(v1 [0], v1 [1], v1 [2])
self.vertex_writer.addData3f(v2 [0], v2 [1], v2 [2])
self.vertex_writer.addData3f(v3 [0], v3 [1], v3 [2])
self.color_writer.addData4f(c0)
self.color_writer.addData4f(c1)
self.color_writer.addData4f(c2)
self.color_writer.addData4f(c3)
if self.texture is not None:
self.texture_writer.addData2f(t0)
self.texture_writer.addData2f(t1)
self.texture_writer.addData2f(t2)
self.texture_writer.addData2f(t3)
vertex_index = self.vertex_index
self.triangles.addVertex(vertex_index + 0)
self.triangles.addVertex(vertex_index + 1)
self.triangles.addVertex(vertex_index + 2)
self.triangles.closePrimitive()
self.triangles.addVertex(vertex_index + 1)
self.triangles.addVertex(vertex_index + 3)
self.triangles.addVertex(vertex_index + 2)
self.triangles.closePrimitive()
self.vertex_index += 4
[docs] def end_geometry(self):
self.geometry = Geom(self.vertex_data)
self.geometry.addPrimitive(self.triangles)
self.geom_node.removeAllGeoms()
self.geom_node.addGeom(self.geometry)
[docs] def check_for_update(self, current_time):
state = False
if (current_time - self.last_update_time) >= self.sampling_time:
state = True
if self.pause:
state = False
update = state and self.enable
return state
[docs] def update_motion_trail(self, current_time, transform):
if len(self.frame_list) >= 1:
if transform == self.frame_list [0].transform:
# ignore duplicate transform updates
return
if self.check_for_update(current_time):
color_scale = self.color_scale
if self.fade:
elapsed_time = current_time - self.fade_start_time
if elapsed_time < 0.0:
print("elapsed_time < 0: %f" %(elapsed_time))
elapsed_time = 0.0
if elapsed_time < self.fade_time:
color_scale = (1.0 - (elapsed_time / self.fade_time)) * color_scale
else:
color_scale = 0.0
self.fade_end = True
self.last_update_time = current_time
# remove expired frames
minimum_time = current_time - self.time_window
index = 0
last_frame_index = len(self.frame_list) - 1
while index <= last_frame_index:
motion_trail_frame = self.frame_list [last_frame_index - index]
if motion_trail_frame.time >= minimum_time:
break
index += 1
if index > 0:
self.frame_list [last_frame_index - index: last_frame_index + 1] = []
# add new frame to beginning of list
motion_trail_frame = MotionTrailFrame(current_time, transform)
self.frame_list = [motion_trail_frame] + self.frame_list
# convert frames and vertices to geometry
total_frames = len(self.frame_list)
#print("total_frames", total_frames)
#
#index = 0
#while index < total_frames:
# motion_trail_frame = self.frame_list [index]
# print("frame time", index, motion_trail_frame.time)
# index += 1
if (total_frames >= 2) and(self.total_vertices >= 2):
self.begin_geometry()
total_segments = total_frames - 1
last_motion_trail_frame = self.frame_list [total_segments]
minimum_time = last_motion_trail_frame.time
delta_time = current_time - minimum_time
if self.calculate_relative_matrix:
inverse_matrix = Mat4(transform)
inverse_matrix.invertInPlace()
if self.use_nurbs and(total_frames >= 5):
total_distance = 0.0
vector = Vec3()
nurbs_curve_evaluator_list = []
total_vertex_segments = self.total_vertices - 1
# create a NurbsCurveEvaluator for each vertex(the starting point for the trail)
index = 0
while index < self.total_vertices:
nurbs_curve_evaluator = NurbsCurveEvaluator()
nurbs_curve_evaluator.reset(total_segments)
nurbs_curve_evaluator_list = nurbs_curve_evaluator_list + [nurbs_curve_evaluator]
index += 1
# add vertices to each NurbsCurveEvaluator
segment_index = 0
while segment_index < total_segments:
motion_trail_frame_start = self.frame_list [segment_index]
motion_trail_frame_end = self.frame_list [segment_index + 1]
vertex_segement_index = 0
if self.calculate_relative_matrix:
start_transform = Mat4()
end_transform = Mat4()
start_transform.multiply(motion_trail_frame_start.transform, inverse_matrix)
end_transform.multiply(motion_trail_frame_end.transform, inverse_matrix)
else:
start_transform = motion_trail_frame_start.transform
end_transform = motion_trail_frame_end.transform
motion_trail_vertex_start = self.vertex_list [0]
v0 = start_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.setVertex(segment_index, v0)
while vertex_segement_index < total_vertex_segments:
motion_trail_vertex_start = self.vertex_list [vertex_segement_index]
motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1]
v1 = start_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.setVertex(segment_index, v1)
if vertex_segement_index == (total_vertex_segments - 1):
v = v1 - v3
vector.set(v[0], v[1], v[2])
distance = vector.length()
total_distance += distance
vertex_segement_index += 1
segment_index += 1
# evaluate NurbsCurveEvaluator for each vertex
index = 0
nurbs_curve_result_list = []
while index < self.total_vertices:
nurbs_curve_evaluator = nurbs_curve_evaluator_list [index]
nurbs_curve_result = nurbs_curve_evaluator.evaluate()
nurbs_curve_result_list = nurbs_curve_result_list + [nurbs_curve_result]
nurbs_start_t = nurbs_curve_result.getStartT()
nurbs_end_t = nurbs_curve_result.getEndT()
index += 1
# create quads from NurbsCurveResult
total_curve_segments = total_distance / self.resolution_distance
if total_curve_segments < total_segments:
total_curve_segments = total_segments
v0 = Vec3()
v1 = Vec3()
v2 = Vec3()
v3 = Vec3()
def one_minus_x(x):
x = 1.0 - x
if x < 0.0:
x = 0.0
return x
curve_segment_index = 0.0
while curve_segment_index < total_curve_segments:
vertex_segement_index = 0
st = curve_segment_index / total_curve_segments
et = (curve_segment_index + 1.0) / total_curve_segments
#st = curve_segment_index / total_segments
#et = (curve_segment_index + 1.0) / total_segments
start_t = st
end_t = et
if self.square_t:
start_t *= start_t
end_t *= end_t
motion_trail_vertex_start = self.vertex_list [0]
vertex_start_color = motion_trail_vertex_start.end_color + (motion_trail_vertex_start.start_color - motion_trail_vertex_start.end_color)
color_start_t = color_scale * start_t
color_end_t = color_scale * end_t
c0 = vertex_start_color * one_minus_x(color_start_t)
c2 = vertex_start_color * one_minus_x(color_end_t)
t0 = Vec2(one_minus_x(st), motion_trail_vertex_start.v)
t2 = Vec2(one_minus_x(et), motion_trail_vertex_start.v)
while vertex_segement_index < total_vertex_segments:
motion_trail_vertex_start = self.vertex_list [vertex_segement_index]
motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1]
start_nurbs_curve_result = nurbs_curve_result_list [vertex_segement_index]
end_nurbs_curve_result = nurbs_curve_result_list [vertex_segement_index + 1]
start_nurbs_start_t = start_nurbs_curve_result.getStartT()
start_nurbs_end_t = start_nurbs_curve_result.getEndT()
end_nurbs_start_t = end_nurbs_curve_result.getStartT()
end_nurbs_end_t = end_nurbs_curve_result.getEndT()
start_delta_t = (start_nurbs_end_t - start_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)
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)
end_nurbs_curve_result.evalPoint(end_nurbs_start_t + (end_delta_t * et), v3)
# color
vertex_end_color = motion_trail_vertex_end.end_color + (motion_trail_vertex_end.start_color - motion_trail_vertex_end.end_color)
c1 = vertex_end_color * one_minus_x(color_start_t)
c3 = vertex_end_color * one_minus_x(color_end_t)
# uv
t1 = Vec2(one_minus_x(st), motion_trail_vertex_end.v)
t3 = Vec2(one_minus_x(et), motion_trail_vertex_end.v)
self.add_geometry_quad(v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3)
# reuse calculations
c0 = c1
c2 = c3
t0 = t1
t2 = t3
vertex_segement_index += 1
curve_segment_index += 1.0
else:
segment_index = 0
while segment_index < total_segments:
motion_trail_frame_start = self.frame_list [segment_index]
motion_trail_frame_end = self.frame_list [segment_index + 1]
start_t = (motion_trail_frame_start.time - minimum_time) / delta_time
end_t = (motion_trail_frame_end.time - minimum_time) / delta_time
st = start_t
et = end_t
if self.square_t:
start_t *= start_t
end_t *= end_t
vertex_segement_index = 0
total_vertex_segments = self.total_vertices - 1
if self.calculate_relative_matrix:
start_transform = Mat4()
end_transform = Mat4()
start_transform.multiply(motion_trail_frame_start.transform, inverse_matrix)
end_transform.multiply(motion_trail_frame_end.transform, inverse_matrix)
else:
start_transform = motion_trail_frame_start.transform
end_transform = motion_trail_frame_end.transform
motion_trail_vertex_start = self.vertex_list [0]
v0 = start_transform.xform(motion_trail_vertex_start.vertex)
v2 = end_transform.xform(motion_trail_vertex_start.vertex)
vertex_start_color = motion_trail_vertex_start.end_color + (motion_trail_vertex_start.start_color - motion_trail_vertex_start.end_color)
color_start_t = color_scale * start_t
color_end_t = color_scale * end_t
c0 = vertex_start_color * color_start_t
c2 = vertex_start_color * color_end_t
t0 = Vec2(st, motion_trail_vertex_start.v)
t2 = Vec2(et, motion_trail_vertex_start.v)
while vertex_segement_index < total_vertex_segments:
motion_trail_vertex_start = self.vertex_list [vertex_segement_index]
motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1]
v1 = start_transform.xform(motion_trail_vertex_end.vertex)
v3 = end_transform.xform(motion_trail_vertex_end.vertex)
# color
vertex_end_color = motion_trail_vertex_end.end_color + (motion_trail_vertex_end.start_color - motion_trail_vertex_end.end_color)
c1 = vertex_end_color * color_start_t
c3 = vertex_end_color * color_end_t
# uv
t1 = Vec2(st, motion_trail_vertex_end.v)
t3 = Vec2(et, motion_trail_vertex_end.v)
self.add_geometry_quad(v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3)
# reuse calculations
v0 = v1
v2 = v3
c0 = c1
c2 = c3
t0 = t1
t2 = t3
vertex_segement_index += 1
segment_index += 1
self.end_geometry()
[docs] def enable_motion_trail(self, enable):
self.enable = enable
[docs] def reset_motion_trail(self):
self.frame_list = []
self.cmotion_trail.reset()
[docs] def reset_motion_trail_geometry(self):
if self.geom_node is not None:
self.geom_node.removeAllGeoms()
[docs] def attach_motion_trail(self):
self.reset_motion_trail()
[docs] def begin_motion_trail(self):
if not self.continuous_motion_trail:
self.reset_motion_trail()
self.active = True
self.playing = True
[docs] def end_motion_trail(self):
if not self.continuous_motion_trail:
self.active = False
self.reset_motion_trail()
self.reset_motion_trail_geometry()
self.playing = False
# the following functions are not currently supported in the C++ version
[docs] def set_fade(self, time, current_time):
if not self.pause:
self.fade_color_scale = 1.0
if time == 0.0:
self.fade = False
else:
self.fade_start_time = current_time
self.fade_time = time
self.fade = True
[docs] def pause_motion_trail(self, current_time):
if not self.pause:
self.pause_time = current_time
self.pause = True
[docs] def resume_motion_trail(self, current_time):
if self.pause:
delta_time = current_time - self.pause_time
frame_index = 0
total_frames = len(self.frame_list)
while frame_index < total_frames:
motion_trail_frame = self.frame_list [frame_index]
motion_trail_frame.time += delta_time
frame_index += 1
if self.fade:
self.fade_start_time += delta_time
self.pause = False
[docs] def toggle_pause_motion_trail(self, current_time):
if self.pause:
self.resume_motion_trail(current_time)
else:
self.pause_motion_trail(current_time)