Skip to content

Commit

Permalink
Merge branch 'path_following'
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter-Noble committed Sep 8, 2016
2 parents dd3725d + 745cb84 commit 7c370f0
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 1 deletion.
6 changes: 6 additions & 0 deletions __init__.py
Expand Up @@ -388,6 +388,10 @@ def register():
action_register()
event_register()

global cm_channels
from . import cm_channels
cm_channels.register()

def initialise():
sce = bpy.context.scene

Expand Down Expand Up @@ -419,5 +423,7 @@ def unregister():
cm_utilities.unregister()
cm_prefs.unregister()

cm_channels.unregister()

if __name__ == "__main__":
register()
9 changes: 9 additions & 0 deletions cm_channels/__init__.py
Expand Up @@ -5,5 +5,14 @@
from .cm_crowdChannels import Crowd
from .cm_groundChannels import Ground
from .cm_formationChannels import Formation
from .cm_pathChannels import Path
from . import cm_pathChannels
Path = cm_pathChannels.Path

from .cm_masterChannels import Wrapper

def register():
cm_pathChannels.register()

def unregister():
cm_pathChannels.unregister()
283 changes: 283 additions & 0 deletions cm_channels/cm_pathChannels.py
@@ -0,0 +1,283 @@
import bpy
import mathutils
from mathutils import Vector
Rotation = mathutils.Matrix.Rotation
Euler = mathutils.Euler
import math
import bmesh

from bpy.props import IntProperty, EnumProperty, CollectionProperty
from bpy.props import PointerProperty, BoolProperty, StringProperty
from bpy.props import FloatProperty
from bpy.types import PropertyGroup, UIList, Panel, Operator

from .cm_masterChannels import MasterChannel as Mc

class Path(Mc):
"""Used to access data about paths in the scene"""
def __init__(self, sim):
Mc.__init__(self, sim)

self.pathObjectCache = {}
self.resultsCache = {}

def newframe(self):
self.pathObjectCache = {}

def setuser(self, userid):
Mc.setuser(self, userid)
self.resultsCache = {}

def calcPathData(self, pathObject):
if pathObject in self.pathObjectCache:
return self.pathObjectCache[pathObject]
obj = bpy.context.scene.objects[pathObject]
mesh = obj.data
size = len(mesh.vertices)
kd = mathutils.kdtree.KDTree(size)

for i, v in enumerate(mesh.vertices):
kd.insert(v.co, i)

kd.balance()

bm = bmesh.new()
bm.from_mesh(mesh)

bm.verts.ensure_lookup_table()

pathMatrixInverse = obj.matrix_world.inverted()

x, y, z = obj.rotation_euler
z = mathutils.Matrix.Rotation(obj.rotation_euler[2], 4, 'Z')
y = mathutils.Matrix.Rotation(obj.rotation_euler[1], 4, 'Y')
x = mathutils.Matrix.Rotation(obj.rotation_euler[0], 4, 'X')

rotation = x * y * z

self.pathObjectCache[pathObject] = (kd, bm, pathMatrixInverse, rotation)

return kd, bm, pathMatrixInverse, rotation

def followPath(self, bm, co, index, vel, co_find, radius):
nVel = vel.normalized()
lVel = vel.length
next = index

edges = bm.verts[next].link_edges

bestScore = -2 # scores in range -1 -> 1 (worst to best)
nextVert = None

for e in edges:
otherVert = e.verts[0] if e.verts[0].index != next else e.verts[1]
score = (otherVert.co - bm.verts[next].co).normalized().dot(nVel)
if score > bestScore:
bestScore = score
nextVert = otherVert

if nextVert is None:
raise Exception("Invalid mesh")

ab = nextVert.co - co
ap = co_find - co

fac = ap.dot(ab) / ab.dot(ab)
adjust = fac * ab
lVel += ab.length * fac
start = co + adjust

startDistFac = (co_find - start).length / radius

nextIndex = nextVert.index

while True:
currentVert = bm.verts[index].co
nextVert = bm.verts[nextIndex].co

length = (nextVert - currentVert).length
if lVel < length:
fac = lVel/length
target = currentVert * (1 - fac) + nextVert * fac
rCorrect = start - co_find
offTargetDist = rCorrect.length - radius
if offTargetDist > 0:
rCorrect *= (offTargetDist / rCorrect.length)
return target - start + rCorrect
# TODO For variable radius add here AND BELOW!!!!!!!!!!
return target - start
lVel -= length

edges = bm.verts[next].link_edges
bestScore = -2 # scores in range -1 -> 1 (worst to best)
nextCo = nextVert
nextVert = None

endOfPath = True
for e in edges:
if e.verts[0].index != index and e.verts[1].index != index:
endOfPath = False
otherVert = e.verts[0] if e.verts[0].index != nextIndex else e.verts[1]
score = (otherVert.co - bm.verts[nextIndex].co).normalized().dot(nVel)
if score > bestScore:
bestScore = score
nextVert = otherVert
if endOfPath:
rCorrect = start - co_find
offTargetDist = rCorrect.length - radius
target = nextCo
if offTargetDist > 0:
rCorrect *= (offTargetDist / rCorrect.length)
return target - start + rCorrect
# Also add here for variable length path
return target - start

index = nextIndex
nextIndex = nextVert.index


def calcRelativeTarget(self, pathObject, radius, lookahead):
context = bpy.context

if pathObject in self.pathObjectCache:
kd, bm, pathMatrixInverse, rotation = self.pathObjectCache[pathObject]
else:
kd, bm, pathMatrixInverse, rotation = self.calcPathData(pathObject)

obj = context.scene.objects[pathObject]
vel = self.sim.agents[self.userid].globalVelocity * lookahead
vel = vel * rotation
co_find = pathMatrixInverse * context.scene.objects[self.userid].location
co, index, dist = kd.find(co_find)
offset = self.followPath(bm, co, index, vel, co_find, radius)

offset = offset * pathMatrixInverse

eul = Euler([-x for x in context.scene.objects[self.userid].rotation_euler], 'ZYX')
offset.rotate(eul)

return offset

def rz(self, pathName):
if pathName in self.resultsCache:
target = self.resultsCache[pathName]
else:
lookahead = 25 # Hard coded for simplicity
pathEntry = bpy.context.scene.cm_paths.coll.get(pathName)
pathObject = pathEntry.objectName
radius = pathEntry.radius
target = self.calcRelativeTarget(pathObject, radius, lookahead)
self.resultsCache[pathObject] = target
return math.atan2(target[0], target[1])/math.pi

def rx(self, pathName):
if pathName in self.resultsCache:
target = self.resultsCache[pathName]
else:
lookahead = 25 # Hard coded for simplicity
pathEntry = bpy.context.scene.cm_paths.coll.get(pathName)
pathObject = pathEntry.objectName
radius = pathEntry.radius
target = self.calcRelativeTarget(pathObject, radius, lookahead)
self.resultsCache[pathObject] = target
return math.atan2(target[2], target[1])/math.pi



class path_entry(PropertyGroup):
"""For storing a single path"""
# name - aliase given to the path
objectName = StringProperty()
radius = FloatProperty(min=0)


class paths_collection(PropertyGroup):
coll = CollectionProperty(type=path_entry)
index = IntProperty()


class SCENE_OT_cm_path_populate(Operator):
bl_idname = "scene.cm_paths_populate"
bl_label = "Add a path"

def execute(self, context):
item = context.scene.cm_paths.coll.add()
return {'FINISHED'}


class SCENE_OT_cm_path_remove(Operator):
bl_idname = "scene.cm_paths_remove"
bl_label = "Remove a path"

@classmethod
def poll(cls, context):
s = context.scene
return len(s.cm_paths.coll) > s.cm_paths.index >= 0

def execute(self, context):
s = context.scene
s.cm_paths.coll.remove(s.cm_paths.index)
if s.cm_paths.index > 0:
s.cm_paths.index -= 1
return {'FINISHED'}


class SCENE_UL_cm_path(UIList):
"""for drawing each row"""
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname):
layout.prop(item, "name", text="")
layout.prop_search(item, "objectName", bpy.data, "objects", text="")
layout.prop(item, "radius")


class SCENE_PT_path(Panel):
"""Creates CrowdMaster Panel in the node editor"""
bl_label = "Paths"
bl_idname = "SCENE_PT_path"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'TOOLS'
bl_category = "CrowdMaster"
bl_options = {'DEFAULT_CLOSED'}

@classmethod
def poll(self, context):
try:
return bpy.context.space_data.tree_type == 'CrowdMasterTreeType', context.space_data.tree_type == 'CrowdMasterGenTreeType'
except (AttributeError, KeyError, TypeError):
return False

def draw(self, context):
layout = self.layout
sce = context.scene

row = layout.row()

row.template_list("SCENE_UL_cm_path", "", sce.cm_paths,
"coll", sce.cm_paths, "index")

col = row.column()
sub = col.column(True)
blid_ap = SCENE_OT_cm_path_populate.bl_idname
sub.operator(blid_ap, text="", icon="ZOOMIN")
blid_ar = SCENE_OT_cm_path_remove.bl_idname
sub.operator(blid_ar, text="", icon="ZOOMOUT")


def register():
bpy.utils.register_class(path_entry)
bpy.utils.register_class(paths_collection)
bpy.utils.register_class(SCENE_OT_cm_path_populate)
bpy.utils.register_class(SCENE_OT_cm_path_remove)
bpy.utils.register_class(SCENE_UL_cm_path)
bpy.utils.register_class(SCENE_PT_path)
bpy.types.Scene.cm_paths = PointerProperty(type=paths_collection)

def unregister():
bpy.utils.unregister_class(path_entry)
bpy.utils.unregister_class(paths_collection)
bpy.utils.unregister_class(SCENE_OT_cm_path_populate)
bpy.utils.unregister_class(SCENE_OT_cm_path_remove)
bpy.utils.unregister_class(SCENE_UL_cm_path)
bpy.utils.unregister_class(SCENE_PT_path)
del bpy.types.Scene.cm_paths
4 changes: 3 additions & 1 deletion cm_simulate.py
Expand Up @@ -24,13 +24,15 @@ def __init__(self):
Crowd = chan.Crowd(self)
Ground = chan.Ground(self)
Formation = chan.Formation(self)
Path = chan.Path(self)
self.lvars = {"Noise": wr(Noise),
"Sound": wr(Sound),
"State": wr(State),
"World": wr(World),
"Crowd": wr(Crowd),
"Ground": wr(Ground),
"Formation": wr(Formation)}
"Formation": wr(Formation),
"Path": wr(Path)}
if debugMode:
self.totalTime = 0
self.totalFrames = 0
Expand Down

0 comments on commit 7c370f0

Please sign in to comment.