<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2706593001080080323</id><updated>2011-07-07T14:33:02.385-07:00</updated><category term='Python'/><category term='Maya'/><category term='Kd-tree'/><category term='Space Partitioning'/><title type='text'>Stev's Technical Art Stuff</title><subtitle type='html'>Problems and solutions which arise in the life of a 3d technical artist</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://stevkalinowski.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2706593001080080323/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://stevkalinowski.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Stev</name><uri>http://www.blogger.com/profile/08257010298758570476</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>1</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2706593001080080323.post-8786237744892906850</id><published>2010-01-23T20:53:00.000-08:00</published><updated>2010-03-27T21:33:36.580-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Kd-tree'/><category scheme='http://www.blogger.com/atom/ns#' term='Space Partitioning'/><category scheme='http://www.blogger.com/atom/ns#' term='Maya'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Space partitioning with a KD-Tree</title><content type='html'>More than once I've had to compare world space position data with vertex positions on a mesh object. In Maya there's a handy closestPointOnMesh node, but if you're dealing with a set of data instead of an existing object you're kind of stuck. Then recently I was introduced to the concept of space partitioning. After studying several different types I settled on the kd-tree as being my favorite one (octree being a close second) when dealing with 3d data.&lt;br /&gt;&lt;br /&gt;In a nutshell, to construct a kd-tree out of 3d points you simply divide your data into two sets along one dimension(x-axis), then in the next dimension(y-axis) you divide each of those sets of data, and so on cycling through each dimension until the data sets contain only one point. Perhaps the wiki page can do a better job explaining than me.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Kd-tree" target="_blank"&gt;http://en.wikipedia.org/wiki/Kd-tree&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So why does dividing the data up this way make looking up points quicker? Well, instead of having to iterate over all points to determine which is closest, we start at the top of the tree and move to the next appropriate branch until your reach the bottom. You end up having to do a fraction of the number of calculations which make this method very fast.&lt;br /&gt;&lt;br /&gt;Here is an example of constructing and using a kd-tree in Maya using python. Select two mesh objects and execute the following code in the python script editor. After you've executed the script, the "lCmpPoints" python list will contain the closest point on the first mesh for each vertex on the second mesh. You can check the results by running&amp;nbsp;lCmpPoints[100&lt;vert index=""&gt;] for any index in the script editor.&lt;/vert&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.stevenkalinowski.com/scripts/sak_kdtree.zip"&gt;www.stevenkalinowski.com/scripts/sak_kdtree.zip&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="alt2" style="border: 2px inset; margin: 0px; padding: 6px; background: #111111 none repeat scroll 0% 0%; overflow: auto; font-family: arial; width: 590px; height: auto;"&gt;import maya.OpenMaya as OpenMaya&lt;br /&gt;import maya.cmds as cmds&lt;br /&gt;import time&lt;br /&gt;&lt;br /&gt;# Here I am using a kdtree to store mesh points, this makes looking up the closest vertex&lt;br /&gt;# to a given worldspace position very fast&lt;br /&gt;&lt;br /&gt;# Point class defines a vertex, simply index(int) and 3d position(tuple) attributes&lt;br /&gt;class Point(object):&lt;br /&gt;&amp;nbsp;&amp;nbsp; def __init__(self, i, pos):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;self.index = i&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;self.position = pos&lt;br /&gt;&amp;nbsp;&amp;nbsp; def __str__(self):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;return str( 'Vertex:%d at (%4.3f, %4.3f, %4.3f)' %&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (self.index, self.position[0], self.position[1], self.position[2]) )&lt;br /&gt;&amp;nbsp;&amp;nbsp; def __repr__(self):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;return repr(str(self))&lt;br /&gt;&amp;nbsp;&amp;nbsp; # The axisAverage method will average one axis of a list of positions&lt;br /&gt;&amp;nbsp;&amp;nbsp; # pVtx - list of tuples&lt;br /&gt;&amp;nbsp;&amp;nbsp; # pAxis - int index of axis, 0 for X, 1 for Y, and 2 for Z&lt;br /&gt;&amp;nbsp;&amp;nbsp; @staticmethod&lt;br /&gt;&amp;nbsp;&amp;nbsp; def axisAverage(pVtx, pAxis):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lTotal = 0.0&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;for v in pVtx:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lTotal += v.position[pAxis]&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;return lTotal / len(pVtx)&lt;br /&gt;&lt;br /&gt;class KdTree(object):&lt;br /&gt;&amp;nbsp;&amp;nbsp; # 3d space so we have 3 dimensions&lt;br /&gt;&amp;nbsp;&amp;nbsp; kDimensions = 3&lt;br /&gt;&amp;nbsp;&amp;nbsp; # Node objects hold the points and child nodes, these make up the tree&lt;br /&gt;&amp;nbsp;&amp;nbsp; class Node(object):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;def __init__(self, pOrigin, pPts):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Floating point number of location of axis division&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; self.origin = pOrigin&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Python list of tuples&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; self.points = pPts&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Child Node objects&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; self.leftNode = None&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; self.rightNode = None&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; # The KdTree class is instantiated with a list of tuples for its points&lt;br /&gt;&amp;nbsp;&amp;nbsp; def __init__(self, pPoints):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Create the tree by calling the recursive method addPoints&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;self.node = KdTree.addPoints(pPoints)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; # This recursive method will create the entire tree, it will continuously&lt;br /&gt;&amp;nbsp;&amp;nbsp; # split the list of points into child Nodes until there is only one left&lt;br /&gt;&amp;nbsp;&amp;nbsp; @staticmethod&lt;br /&gt;&amp;nbsp;&amp;nbsp; def addPoints(pPoints, depth=0):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;if not pPoints:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# On each recursion we cycle through each axis&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lAxis = depth % KdTree.kDimensions&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Sort the points list according to the axis we are splitting along&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;pPoints.sort(lambda x,y:cmp(x.position[lAxis], y.position[lAxis]))&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Find the mean of all points in the list for this particular axis&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lOriginAxis = Point.axisAverage(pPoints, lAxis)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Split the points into two lists, one for each side of the axis&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lLeftPts = [x for x in pPoints if x.position[lAxis] &amp;lt;= lOriginAxis]&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lRightPts = [x for x in pPoints if x.position[lAxis] &amp;gt; lOriginAxis]&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Create the Node object&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lNode = KdTree.Node(lOriginAxis, pPoints)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Now create the child nodes&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# If either list is zero, we do not need to create child nodes because&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# the other list will only contain one point meaning it cannot be split&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# anymore&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;if len(lLeftPts) &amp;gt; 0 and len(lRightPts) &amp;gt; 0:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lNode.leftNode = KdTree.addPoints(lLeftPts, depth+1)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lNode.rightNode = KdTree.addPoints(lRightPts, depth+1)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;return lNode&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; # The closestPoint method is called to look up the closest point in the tree&lt;br /&gt;&amp;nbsp;&amp;nbsp; # to a given position in world space&lt;br /&gt;&amp;nbsp;&amp;nbsp; def closestPoint(self, pPos, depth=0, node=None):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# The first time this method is called, we start with the parent node&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;if not node:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; node = self.node&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# On each recursion we cycle through each axis&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lAxis = depth % KdTree.kDimensions&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# If there are no children nodes there must be only one point in this&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Node, so we return it&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;if not node.leftNode or not node.rightNode:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return node.points[0]&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Check which side of the split axis we are on, the with the appropriate&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# child Node we call this method again&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;if pPos[lAxis] &amp;lt;= node.origin:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lPt = self.closestPoint(pPos, depth+1, node.leftNode)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;else:&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lPt = self.closestPoint(pPos, depth+1, node.rightNode)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Return the last Point&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;return lPt&lt;br /&gt;&lt;br /&gt;# Now we'll do a test by creating a kdtree with one mesh, then look up the closest&lt;br /&gt;# postion for each vertex in another mesh&lt;br /&gt;# Select two meshes, first the one to make the tree, then the one to compare&lt;br /&gt;lSelList = OpenMaya.MSelectionList();&lt;br /&gt;OpenMaya.MGlobal.getActiveSelectionList(lSelList)&lt;br /&gt;&lt;br /&gt;if lSelList.length() == 2:&lt;br /&gt;&amp;nbsp;&amp;nbsp; lMeshPath = OpenMaya.MDagPath()&lt;br /&gt;&amp;nbsp;&amp;nbsp; lCmpMeshPath = OpenMaya.MDagPath()&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; lSelList.getDagPath(0, lMeshPath)&lt;br /&gt;&amp;nbsp;&amp;nbsp; lSelList.getDagPath(1, lCmpMeshPath)&lt;br /&gt;&amp;nbsp;&amp;nbsp; # Make sure both objects are meshes&lt;br /&gt;&amp;nbsp;&amp;nbsp; if lMeshPath.hasFn(OpenMaya.MFn.kMesh) and lCmpMeshPath.hasFn(OpenMaya.MFn.kMesh):&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lMeshIter = OpenMaya.MItMeshVertex(lMeshPath)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Python list of Point objects&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lPoints = []&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;while not lMeshIter.isDone():&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lPos = lMeshIter.position(OpenMaya.MSpace.kWorld)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Add each vertex to the list as a Point object&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lPoints.append( Point(lMeshIter.index(), (lPos.x, lPos.y, lPos.z)) )&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lMeshIter.next()&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Create the tree&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lKdTree = KdTree(lPoints)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Now we'll time how long it takes to iterate the second mesh looking up each vertex's&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# closest point&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lStartTime = time.time()&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lCmpMeshIter = OpenMaya.MItMeshVertex(lCmpMeshPath)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lCmpPoints = []&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;while not lCmpMeshIter.isDone():&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lCmpPos = lCmpMeshIter.position(OpenMaya.MSpace.kWorld)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Find the closest point and append it to the list&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lCmpPoints.append( lKdTree.closestPoint( (lCmpPos.x, lCmpPos.y, lCmpPos.z) ) )&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; lCmpMeshIter.next()&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;lTimeTaken = time.time() - lStartTime&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;# Report how long it took&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;print lTimeTaken, 'Using KdTree'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2706593001080080323-8786237744892906850?l=stevkalinowski.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stevkalinowski.blogspot.com/feeds/8786237744892906850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2706593001080080323&amp;postID=8786237744892906850' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2706593001080080323/posts/default/8786237744892906850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2706593001080080323/posts/default/8786237744892906850'/><link rel='alternate' type='text/html' href='http://stevkalinowski.blogspot.com/2010/01/space-partitioning-with-kd-tree.html' title='Space partitioning with a KD-Tree'/><author><name>Stev</name><uri>http://www.blogger.com/profile/08257010298758570476</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
