3636import re
3737import sys
3838import unittest
39+ import random
3940import imath
4041import time
4142import threading
4546
4647class MeshAlgoDistributePointsTest ( unittest .TestCase ) :
4748
48- def pointTest ( self , mesh , points , density , error = 0.05 ) :
49+ def checkForDuplicates ( self , positions ) :
50+ duplicatePositions = []
51+ uniquePositions = set ()
52+ for p in positions :
53+ prevNumUnique = len ( uniquePositions )
54+ uniquePositions .add ( tuple ( p ) )
55+ if not len ( uniquePositions ) > prevNumUnique :
56+ duplicatePositions .append ( p )
57+
58+ self .assertEqual ( duplicatePositions , [] )
59+
60+ def checkVectorListsAlmostEqualUnordered ( self , aIn , bIn , tolerance ) :
61+ l = len ( aIn )
62+ self .assertEqual ( len ( bIn ), l )
63+ a = IECore .V3fVectorData ( sorted ( aIn , key = lambda v : v [0 ] ) )
64+ b = IECore .V3fVectorData ( sorted ( bIn , key = lambda v : v [0 ] ) )
65+ aMatched = IECore .BoolVectorData ( l )
66+
67+ aStartIndex = 0
68+
69+ for i in b :
70+ while aStartIndex < l and a [aStartIndex ][0 ] < i [0 ] - tolerance :
71+ aStartIndex += 1
72+
73+ matched = False
74+ aIndex = aStartIndex
75+ while aIndex < l and a [aIndex ][0 ] < i [0 ] + tolerance :
76+ if not aMatched [aIndex ]:
77+ if i .equalWithAbsError ( a [aIndex ], tolerance ):
78+ aMatched [aIndex ] = True
79+ matched = True
80+ continue
81+ aIndex += 1
82+
83+ if not matched :
84+ raise AssertionError ( "No match for point " , i )
85+
86+ self .assertTrue ( all ( aMatched ) )
87+
88+ def pointTest ( self , mesh , points , density , error = 0.05 , checkDuplicates = True ) :
4989
5090 self .assertTrue ( "P" in points )
5191 self .assertEqual ( points .numPoints , points ['P' ].data .size () )
@@ -58,6 +98,9 @@ def pointTest( self, mesh, points, density, error=0.05 ) :
5898 pointsPerFace = [ 0 ] * mesh .verticesPerFace .size ()
5999 positions = points ["P" ].data
60100
101+ if checkDuplicates :
102+ self .checkForDuplicates ( positions )
103+
61104 ## test that the points are on the mesh
62105 for p in positions :
63106 self .assertAlmostEqual ( meshEvaluator .signedDistance ( p , result ), 0.0 , 6 )
@@ -82,6 +125,30 @@ def testSimple( self ) :
82125 p = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 100 )
83126 self .pointTest ( m , p , 100 )
84127
128+ def testActualDistribution ( self ) :
129+ # Most of these tests target the properties of the generated points, rather than the specific points
130+ # chosen. This is good for the generality of the tests, and will come in handy if we ever switch
131+ # the implementation. But it's probably worth having one test that actually asserts the exact
132+ # distribution we are currently using.
133+
134+ # This also checks the handling of points that lie on edges - we accept <0,0> as lying within the
135+ # rect from 0 -> 1, but <1,0>, <0,1> and <1,1> are considered outside ( this ensures that if adjacent
136+ # polygons are added, the point will be included exactly once )
137+
138+ m = IECoreScene .MeshPrimitive .createPlane ( imath .Box2f ( imath .V2f ( 0 ), imath .V2f ( 1 ) ), imath .V2i ( 1 ) )
139+ points = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 5 )["P" ].data
140+ refPoints = [
141+ imath .V3f ( 0 ),
142+ imath .V3f ( 0.612342954 , 0.365361691 , 0 ),
143+ imath .V3f ( 0.309612036 , 0.182079479 , 0 ),
144+ imath .V3f ( 0.0924436674 , 0.497548342 , 0 ),
145+ imath .V3f ( 0.512147605 , 0.871189654 , 0 )
146+ ]
147+ self .assertEqual ( len ( points ), len ( refPoints ) )
148+ for a , b in zip ( points , refPoints ):
149+ with self .subTest ( a = a , b = b ):
150+ self .assertTrue ( a .equalWithRelError ( b , 0.0000001 ) )
151+
85152 def testRaisesExceptionIfInvalidUVs ( self ) :
86153
87154 m = IECore .Reader .create ( os .path .join ( "test" , "IECore" , "data" , "cobFiles" , "pCubeShape1.cob" ) ).read ()
@@ -129,7 +196,10 @@ def testDensityMaskPrimVar( self ) :
129196
130197 m ['density' ] = IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .Constant , IECore .FloatData ( 2 ) )
131198 p = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 1000 )
132- self .pointTest ( m , p , 1000 , error = 0.1 )
199+ # We happen to hit an edge here which has two different UV space locations, so we don't avoid creating
200+ # duplicates - that's probably OK, we just want to make sure that we don't produce the same UV point
201+ # multiple times
202+ self .pointTest ( m , p , 1000 , error = 0.1 , checkDuplicates = False )
133203
134204 m ['density' ] = IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .Constant , IECore .FloatData ( - 0.001 ) )
135205 p = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 1000 )
@@ -167,8 +237,8 @@ def testRefPosition( self ):
167237 m = IECoreScene .MeshPrimitive .createPlane ( imath .Box2f ( imath .V2f ( - 1 ), imath .V2f ( 1 ) ), imath .V2i ( 1 ) )
168238 m ["altP" ] = IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .Vertex , IECore .V3fVectorData ( [ i * 2 + imath .V3f ( 1 , 7 , 10 ) for i in m ["P" ].data ] ) )
169239
170- p1 = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 40 )
171- p2 = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 10 , refPosition = "altP" )
240+ p1 = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 400 )
241+ p2 = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 100 , refPosition = "altP" )
172242
173243 # Using a reference position with a scale affects the density ( which is compensated by changing the
174244 # density argument, but otherwise does not affect anything )
@@ -179,7 +249,7 @@ def testRefPosition( self ):
179249 # across a much larger area, but the point counts are kept constant
180250 m ["P" ] = IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .Vertex , IECore .V3fVectorData ( [ i * 100 for i in m ["P" ].data ] ) )
181251
182- p3 = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 10 , refPosition = "altP" )
252+ p3 = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = 100 , refPosition = "altP" )
183253 self .assertLess ( p3 .bound ().min ()[0 ], - 100 )
184254 self .assertGreater ( p3 .bound ().max ()[0 ], 100 )
185255 self .assertEqual ( p3 .numPoints , p2 .numPoints )
@@ -227,6 +297,50 @@ def testDensityRange( self ) :
227297 m = IECore .Reader .create ( os .path .join ( "test" , "IECore" , "data" , "cobFiles" , "pCubeShape1.cob" ) ).read ()
228298 self .assertRaises ( RuntimeError , IECoreScene .MeshAlgo .distributePoints , m , - 1.0 )
229299
300+ def testEdgeOverlaps ( self ):
301+ # As long as we don't change the edges or stretch the UVs, we should be able to add more subdivisions,
302+ # or more vertices around within the mesh, and still get identical points out ( down to floating point
303+ # error ). This test is run with enough points and polygons to have a good chance of triggering
304+ # special case interactions of points lying on or near edges and vertices. We want to make sure that
305+ # every point gets output exactly once, not being doubled up when it's on a shared edge between two
306+ # polygons, or being missed.
307+
308+ dens = 10000
309+ m = IECoreScene .MeshPrimitive .createPlane ( imath .Box2f ( imath .V2f ( 0 ), imath .V2f ( 1 ) ), imath .V2i ( 1 ) )
310+ refPoints = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = dens )["P" ].data
311+
312+ div = 256
313+ m = IECoreScene .MeshPrimitive .createPlane ( imath .Box2f ( imath .V2f ( 0 ), imath .V2f ( 1 ) ), imath .V2i ( div ) )
314+ pointsFromHighDensity = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = dens )["P" ].data
315+
316+ m = IECoreScene .MeshPrimitive .createPlane ( imath .Box2f ( imath .V2f ( 0 ), imath .V2f ( 1 ) ), imath .V2i ( div ) )
317+ jitterUVs = m ["uv" ].data .copy ()
318+ jitterPs = m ["P" ].data .copy ()
319+
320+ jitter = 0.25 / div
321+ random .seed ( 42 )
322+ for y in range ( 1 , div ):
323+ for x in range ( 1 , div ):
324+ i = y * ( div + 1 ) + x
325+ jitterUVs [ i ] = jitterUVs [ i ] + imath .V2f (
326+ random .uniform ( - jitter , jitter ), random .uniform ( - jitter , jitter )
327+ )
328+ jitterPs [ i ] = imath .V3f ( jitterUVs [ i ][0 ], jitterUVs [ i ][1 ], 0 )
329+ m ["uv" ] = IECoreScene .PrimitiveVariable (
330+ IECoreScene .PrimitiveVariable .Interpolation .FaceVarying , jitterUVs , m ["uv" ].indices
331+ )
332+ m ["P" ] = IECoreScene .PrimitiveVariable (
333+ IECoreScene .PrimitiveVariable .Interpolation .Vertex , jitterPs
334+ )
335+ pointsFromJittered = IECoreScene .MeshAlgo .distributePoints ( mesh = m , density = dens )["P" ].data
336+
337+ self .checkForDuplicates ( refPoints )
338+ self .checkForDuplicates ( pointsFromHighDensity )
339+ self .checkForDuplicates ( pointsFromJittered )
340+
341+ self .checkVectorListsAlmostEqualUnordered ( pointsFromHighDensity , refPoints , 0.0000002 )
342+ self .checkVectorListsAlmostEqualUnordered ( pointsFromJittered , refPoints , 0.0000002 )
343+
230344 def testVertexUVs ( self ) :
231345
232346 m = IECoreScene .MeshPrimitive .createPlane ( imath .Box2f ( imath .V2f ( - 1 ), imath .V2f ( 1 ) ), imath .V2i ( 4 ) )
@@ -259,7 +373,7 @@ def testPrimitiveVariables( self ):
259373 self .assertEqual ( p .keys (), ['N' , 'P' , 'cA' , 'cB' , 'fvA' , 'fvB' , 'uA' , 'uB' , 'uv' , 'vA' , 'vB' , 'vC' ] )
260374 self .assertEqual ( p ['cA' ], IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .Constant , IECore .FloatData ( 42 ) ) )
261375 self .assertEqual ( p ['cB' ], IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .Constant , IECore .M44fData ( imath .M44f ( 7 ) ) ) )
262- self .assertEqual ( p .numPoints , 15 )
376+ self .assertEqual ( p .numPoints , 10 )
263377 for i in range ( p .numPoints ):
264378 v = { k : p [k ].data [i ] for k in p .keys () if k [0 ] != 'c' }
265379 self .assertAlmostEqual ( v ['uv' ][0 ], v ['P' ][0 ] + 0.5 , places = 6 )
@@ -288,7 +402,7 @@ def testPrimitiveVariables( self ):
288402 self .assertEqual ( p .keys (), ['N' , 'P' , 'cA' , 'cB' , 'fvA' , 'fvB' , 'uA' , 'uB' , 'uv' , 'vA' , 'vB' , 'vC' ] )
289403
290404 # Check that the overrides applied correctly
291- self .assertEqual ( p .numPoints , 410 )
405+ self .assertEqual ( p .numPoints , 406 )
292406
293407 # Test variable types that can't be interpolated
294408 m ['invalid1' ] = IECoreScene .PrimitiveVariable ( IECoreScene .PrimitiveVariable .Interpolation .FaceVarying , IECore .StringVectorData ( [ "foo" ] * 16 ) )
0 commit comments