Let's say I wanted to write a simulation script...



  • Alright, so December is easy at work and there are no fires to put out, so I'm bored and decided to learn a bit more about the Poser Python API. I know zero about Python API, but I'm still an IBM programmer, so I can read a manual. What it would take to do something? Say, a 3D metaball thingie?

    Any help, hints and tips are of course appreciated.

    Now, here's what I read from the Poser Python manual: there's a nice little function AddGeneralMesh(), where I can add a list of polygons (I think that means an array of 2 numbers as <setIndex, 3>, where 3 means that setIndex, setIndex+1, setIndex+2 make a triangle), a list of sets (I think that means an array of vertexIndexes), and a list of vertices (that's an array of 3 numbers <x,y,z>). I also see I can make that geometry into a prop by calling CreatePropFromGeom().

    Nice enough. Now, I need an animated mesh, so how to do it. This sample gives the answer, I think: sceneWorldSpace.py; it has this little code:

    def sceneWorldSpaceCallback(iScene):
    curActor = iScene.CurrentActor()
    geom = curActor.Geometry()
    firstVert = geom.WorldVertex(0)
    verts = geom.WorldVertices()
    for vert in verts:
    vert.SetX(vert.X()+NextRand())
    ...etc...

    That's checked to work ok. Very nice; so once we have a mesh we can use that callback thingie to animate its geometry through every frame.

    Now, how to get a geometry for a single frame. I'm thinking of 3 scripts.

    1st script "Create Metaball" searches for a prop named IsoField. If not found it adds a cube prop (with CreatePropFromGeom()) with name IsoField, which defines the limits of the simulation. Then use CreateValueParameter() to create parameters Resolution, Threshold and Number Vertices. These control the isosurface. As this is a regular prop the user can move it around as he/she wishes. Set its render style to be Wireframe.

    Then it adds a sphere-like prop with name MetaSphereN (N=1, 2, 3, etc...), with parameters X, Y, Z, Strength, and sets IsoField as their parent by using SetParent(). Strength > 0 makes a positive metaball, Strength < 0 makes a negative metaball. Again the user can move them around as he/she wishes.

    Now a 2nd script "Simulate Metaballs"; when it runs it sets to Frame 1, searches for IsoField and creates a lattice of cells (just the original cube sub-divided by Resolution). Then it searches for all children named like MetaSphere*. Then it calculates the field strength at the 8 vertices of each cell using the MetaSphere* X,Y,Z and strength. Compare the field strength at the 8 vertices to the Threshold value. Then it applies the marching cube algorithm thingie to calculate a the triangles in the iso-surface for each cell. Each cell yields from 0 to 5 triangles. So if Resolution = 8, that means 8^3 = 512 cells, and up to 512^5 = 2,560 triangles or 7,680 vertices. This is worst case, typically it will be much less as the isosurface will tend to be smooth. Calculate the number of triangles needed for that frame. Now we have the geometry for one frame. Then repeat for Frame 2, Frame 3, etc...; as the spheres can move and change strength, and the threshold and number cells can change too, each frame may provide a different list of triangles. Then make the spheres invisible I didn't check for that, but I'm sure there's some function for it).

    Then keep in memory the whole collection of frame #, number of triangles and list of triangles for that frame. Then get the maximum number of triangles for all frames. Now create a generic mesh MetaSurface with that number of triangles using the CreatePropFromGeom() thingie, and make the IsoField its parent with SetParent(). The actual vertex positions do not matter, as they will all be flipped during rendering. Then copy the render root node of IsoField into MetaSurface (so that the user will have control over its color and whatever else can be done without an uv map) and make IsoField invisible (I didn't check for those either, but I'm sure these functions exist).

    Create a callback function just like the sceneWorldSpace.py sample; that function will get the frame number, find the MetaSurface prop, then set the X, Y, Z coordinates of the vertices in each triangle as those saved in memory for that frame. Now, not all triangles will be set in that frame (as the actual number of triangles change per frame, and the actual prop is set to the maximum), so the remaining ones can be set to whatever number will get them out of visible space.

    The 3rd script "Stop Simulation" will just search for IsoField, set MetaSurface to be visible, clear the callback, delete these lists in memory, delete the IsoSurface node, and make MetaBallN objects visible. As the whole thing just shows as cube and sphere props, they should be saved and loaded normally in the PZ3.

    Now, with Resolution = 8 and 300 frames, that makes up to 768,000 vertices to keep in memory, and that's because the whole thing is baked; but I think that can be decreased by reusing them when there are no changes in the calculation parameters, and unless one creates too many metaballs these guys should be very smooth and therefore use way less triangles. But only experience will prove that.

    Also, one may be worried that all these triangles are disconnected and don't make a manifold mesh. That's on purpose, so that just one mesh can be reused across many frames whatever topology it gets. But then one may think that they may show tiny gaps between the triangles and ruin the effect of a solid surface; that's a chance, but what I know of DirectX/OpenCL tells me that whatever mesh is given, at the end the driver just sends a list of triangles to the hardware, and the dumb thing just renders them one by one. So I suspect that will be ok, but then only experience will prove that.

    So, that's my idea of how to implement that simulation through Python API; it has the advantage that everything can be animated as they are just regular parameters for the objects, the thing is fully baked so playback should be fast, has zero effect unless the script is running, integrates with both Render and Make Movie, and it can have whatever shading the user wants (as long as it doesn't depend on uv maps, as there is none).

    I think a similar approach can be used for liquids, but I have no idea about liquid algorithms.

    Any thoughts?



  • @fbs7 said in Let's say I wanted to write a simulation script...:

    The 3rd script "Stop Simulation" will just search for IsoField, set MetaSurface to be visible, clear the callback, delete these lists in memory, delete the IsoSurface node, and make MetaBallN objects visible. As the whole thing just shows as cube and sphere props, they should be saved and loaded normally in the PZ3.

    Hmm.. this was meant to be "search for IsoField, set IsoField to be visible, clear the callback, delete these lists in memory, delete the MetaSurface prop, and make all MetaBallN objects visible. As the whole thing just shows as cube and sphere props, they should be saved and loaded normally in the PZ3.



  • Hint:
    Deep down in the guts of the Renderosity forums there is a specific PoserPython forum with loads of discussions and example solutions.
    At first the content looks very limited but be sure to set the date filter wide enough.
    The folks there come not very frequently so it may take a day or two before there is a response.



  • Thank you.



  • Alright. I forgot to say I'm a complete noob in Python (I managed to ignore Python up so far, haha), but it's no biggie, just another manual to read. First script was pretty simple, it just does the steps described above. So this is the first ever script from a Python noob & Poser API noob (took some 1.5 hours to code and debug, mostly to sort out the vertex order and unicode comparisons):

    import poser
    from numpy import oldnumeric
    
    #
    # This script creates a cube to limit an isosurface, and a handler for a metaball
    # fbs 12/7/2017
    #
    
    # Check if "IsoField" already exists, if not create one
    scene = poser.Scene()
    try:
    	isoField = scene.Actor("IsoField")
    except:
    	# Vertices of a cube
    	c = 0.3         # Cube size
    	verts = oldnumeric.array( [[0,0,0],[0,0,c],[0,c,0],[0,c,c],[c,0,0],[c,0,c],[c,c,0],[c,c,c]] )
    
    	# Sets = vertex indexes
    	sets = oldnumeric.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    
    	# Polys = faces, index to sets
    	polys = oldnumeric.array( [[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]] )
    
    	# Create the geometry
    	meshGeom = poser.NewGeometry()
    	meshGeom.AddGeneralMesh( polys, sets, verts )
    
    	# Create the prop
    	isoField = scene.CreatePropFromGeom(meshGeom,"IsoField")
    
    	# Set draw style to wireframe
    	isoField.SetDisplayStyle( poser.kDisplayCodeWIREFRAME )
    	parmResolution = isoField.CreateValueParameter('Resolution')
    	parmResolution.SetValue(1)
    	parmResolution.SetMinValue(1)
    	parmResolution.SetMaxValue(20)
    	parmResolution.SetSensitivity(1)
    	parmResolution.SetForceLimits(1)
    	isoField.CreateValueParameter('Threshold')
    	parmResolution.SetInitValue(0.1)
    	scene.DrawAll()
    
    # Find the smallest N that "MetaBallN" does not already exist as children of the iso field
    childList = isoField.Children()
    found = 0
    for n in xrange(1,10):
    	metaBallName = u'MetaBall' + str(n)	# Poser needs Unicode
    	found = 0
    	for child in childList:
    		if child.Name() == metaBallName:
    			found = 1
    			break
    	if found == 0:
    		break
    if found == 1:
    	print 'Couldnt find a valid name for the metaball!'
    
    # Now let's create a metaball handler; it will be a little cube; code is the same as above
    c = 0.01
    verts = oldnumeric.array( [[0,0,0],[0,0,c],[0,c,0],[0,c,c],[c,0,0],[c,0,c],[c,c,0],[c,c,c]] )
    sets = oldnumeric.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    polys = oldnumeric.array( [[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]] )
    
    # Create the geometry
    meshGeom = poser.NewGeometry()
    meshGeom.AddGeneralMesh( polys, sets, verts )
    
    # Create the prop and make it a child of isoField
    metaBall = scene.CreatePropFromGeom(meshGeom,metaBallName )
    parmStrength = metaBall.CreateValueParameter('Strength')
    parmStrength.SetValue(0.01)
    metaBall.SetParent(isoField)
    
    # All done; now we have a the cube for the iso field, and a smaller cube to center metaballs in that field
    


  • @fbs7
    Congrats! You are bound to get far with this!
    I find myself too much thinking in Fortran77 LOL!



  • Haha.

    Some cosmetic fixes in the Create Metaball.py:

    import poser
    from numpy import oldnumeric
    
    #
    # This script creates a cube to limit an isosurface, and a handler for a metaball
    # fbs 12/7/2017
    #
    
    # Check if 'IsoField' already exists, if not create one
    scene = poser.Scene()
    try:
    	isoField = scene.Actor('IsoField')
    except:
    	# Vertices of a cube
    	c = 0.3         # Cube size
    	verts = oldnumeric.array( [[0,0,0],[0,0,c],[0,c,0],[0,c,c],[c,0,0],[c,0,c],[c,c,0],[c,c,c]] )
    
    	# Sets = vertex indexes
    	sets = oldnumeric.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    
    	# Polys = faces, index to sets
    	polys = oldnumeric.array( [[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]] )
    
    	# Create the geometry
    	meshGeom = poser.NewGeometry()
    	meshGeom.AddGeneralMesh( polys, sets, verts )
    
    	# Create the prop
    	isoField = scene.CreatePropFromGeom(meshGeom,'IsoField')
    
    	# Set draw style to wireframe
    	isoField.SetDisplayStyle( poser.kDisplayCodeWIREFRAME )
    	parmResolution = isoField.CreateValueParameter('Resolution')
    	parmResolution.SetValue(1)
    	parmResolution.SetMinValue(1)
    	parmResolution.SetMaxValue(20)
    	parmResolution.SetSensitivity(1)
    	parmResolution.SetForceLimits(1)
    	isoField.CreateValueParameter('Threshold')
    	parmResolution.SetValue(0.1)
    	scene.DrawAll()
    
    # Find the smallest N that 'MetaBallN' does not already exist as children of the iso field
    childList = isoField.Children()
    found = 0
    for n in xrange(1,101):
    	metaBallName = u'MetaBall' + str(n)	# Poser needs Unicode
    	found = 0
    	for child in childList:
    		if child.Name() == metaBallName:
    			found = 1
    			break
    	if found == 0:
    		break
    
    # Now let's create a metaball handler; it will be a little cube; code is the same as above
    c = 0.01
    verts = oldnumeric.array( [[0,0,0],[0,0,c],[0,c,0],[0,c,c],[c,0,0],[c,0,c],[c,c,0],[c,c,c]] )
    sets = oldnumeric.array( [0,1,3,2,  0,4,5,1,  0,2,6,4,  7,5,4,6,  7,3,1,5,  7,6,2,3] )
    polys = oldnumeric.array( [[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]] )
    
    # Create the geometry
    meshGeom = poser.NewGeometry()
    meshGeom.AddGeneralMesh( polys, sets, verts )
    
    # Create the prop and make it a child of isoField
    metaBall = scene.CreatePropFromGeom(meshGeom,metaBallName )
    parmStrength = metaBall.CreateValueParameter('Strength')
    parmStrength.SetValue(0.01)
    metaBall.SetParent(isoField)
    
    # All done; now we have a the cube for the iso field, and a smaller cube to center metaballs in that field
    

    then the Run Metaballs.py, which will bake the iso surface on all frames (right now it's only doing that at the current frame); baking time is between 0.5s and 1.0s with resolution = 30 (which means a grid of 30x30x30). Run Metaballs.py took some 6-8 hours to code and debug, mostly because I had to reference everything about Python (I'm such a noob, haha):

    import poser
    import numpy
    #from numpy import oldnumeric
    
    #
    # This script creates a dynamic isosurface
    # fbs 12/7/2017
    #
    
    # Find IsoField, resolution and threshold
    keepGoing = 1
    scene = poser.Scene()
    try:
    	isoField = scene.Actor('IsoField')
    	resolution = int(isoField.Parameter('Resolution').Value())
    	res1 = resolution+1
    	invres1 = 1.0/res1
    	threshold = isoField.Parameter('Threshold').Value()
    	if resolution < 1:
    		print 'Resolution must be 1 or higher'
    		keepGoing = 0
    except:
    	print 'Run Create Metaballs.py first!'
    	keepGoing = 0
    
    # Build a lattice spanning the whole isoField; vertex 0 = [0,0,0], and vertex 7 the sizing of the cube
    if keepGoing:
    	try:
    		geom = isoField.Geometry()
    		vertex7 = geom.Vertex(7)
    		sX = vertex7.X() * invres1
    		sY = vertex7.Y() * invres1
    		sZ = vertex7.Z() * invres1
    		lattice = numpy.zeros((res1+1,res1+1,res1+1))
    	except:
    		print 'Invalid geometry (isoField)...'
    		keepGoing = 0
    
    # Build a list of the metaballs in the isofield; get X,Y,Z and Strength
    if keepGoing:
    	try:
    		childList = isoField.Children()
    		metaList = ([])
    		for child in childList:
    			strength = child.Parameter('Strength').Value()
    			childLocation = child.LocalDisplacement() 	# Notice is this internal units, not display units
    			metaList.append( [childLocation[0], childLocation[1], childLocation[2], strength] )
    	except:
    		print 'Invalid setup (metaballs)...'
    
    # Now we calculate the field value at the whole lattice; the field function is sum( str/r ), where r is the distance from
    # the lattice point to the metaball center; but that's expensive, so there are approximations; the simplest one is
    # sum of { (str-r^2)^3 ) for r<str, or 0 for r^2>str }; we'll force 0 at the ends of the lattice to force the iso surface to be closed
    
    # what a big fat inefficient loop; this is the kind of thing we use vector instructions...
    
    if keepGoing:
    	try:
    		for metaball in metaList:
    			Xo = metaball[0]
    			Yo = metaball[1]
    			Zo = metaball[2]
    			strength = metaball[3]/1000.0
    			for nX in xrange(1,res1):
    				dX = sX*nX-Xo
    				dX2 = dX*dX
    				#if dX2 < strength:
    				for nY in xrange(1,res1):
    					dY = sY*nY-Yo
    					dY2 = dY*dY
    					#if dY2 < strength:
    					for nZ in xrange(1,res1):
    						dZ = sZ*nZ-Zo
    						dZ2 = dZ*dZ
    						#if dZ2 < strength:
    						r2 = dX2+dY2+dZ2
    						#if r2<strength:
    						f = strength/r2
    						lattice[nX,nY,nZ] += f
    	except:
    		print 'Error calculating field values'
    		keepGoing = 0
    
    # Now we get the data tables for the marching cubes thingie; the algorithm is widely spread, not sure who was the first
    # author; this is from Ben Kenwright at www.xbdev.net:
    
    edgeTable=(
    	[0x0  , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
    	0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
    	0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
    	0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
    	0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
    	0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
    	0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
    	0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
    	0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
    	0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
    	0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
    	0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
    	0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
    	0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
    	0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
    	0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
    	0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
    	0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
    	0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
    	0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
    	0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
    	0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
    	0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
    	0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
    	0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
    	0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
    	0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
    	0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
    	0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
    	0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
    	0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
    	0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0])
    
    vertTable= (
    	[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1],
    	[3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1],
    	[3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1],
    	[3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1],
    	[9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1],
    	[9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1],
    	[2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1],
    	[8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1],
    	[9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1],
    	[4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1],
    	[3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1],
    	[1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1],
    	[4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1],
    	[4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1],
    	[5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1],
    	[2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1],
    	[9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1],
    	[0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1],
    	[2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1],
    	[10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1],
    	[5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1],
    	[5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1],
    	[9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1],
    	[0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1],
    	[1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1],
    	[10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1],
    	[8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1],
    	[2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1],
    	[7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1],
    	[2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1],
    	[11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1],
    	[5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1],
    	[11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1],
    	[11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1],
    	[1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1],
    	[9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1],
    	[5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1],
    	[2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1],
    	[5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1],
    	[6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1],
    	[3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1],
    	[6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1],
    	[5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1],
    	[1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1],
    	[10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1],
    	[6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1],
    	[8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1],
    	[7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1],
    	[3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1],
    	[5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1],
    	[0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1],
    	[9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1],
    	[8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1],
    	[5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1],
    	[0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1],
    	[6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1],
    	[10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1],
    	[10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1],
    	[8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1],
    	[1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1],
    	[0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1],
    	[10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1],
    	[3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1],
    	[6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1],
    	[9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1],
    	[8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1],
    	[3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1],
    	[6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1],
    	[0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1],
    	[10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1],
    	[10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1],
    	[2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1],
    	[7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1],
    	[7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1],
    	[2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1],
    	[1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1],
    	[11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1],
    	[8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1],
    	[0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1],
    	[7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1],
    	[10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1],
    	[2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1],
    	[6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1],
    	[7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1],
    	[2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1],
    	[1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1],
    	[10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1],
    	[10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1],
    	[0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1],
    	[7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1],
    	[6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1],
    	[8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1],
    	[6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1],
    	[4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1],
    	[10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1],
    	[8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1],
    	[0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1],
    	[1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1],
    	[8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1],
    	[10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1],
    	[4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1],
    	[10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1],
    	[5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1],
    	[11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1],
    	[9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1],
    	[6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1],
    	[7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1],
    	[3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1],
    	[7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1],
    	[3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1],
    	[6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1],
    	[9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1],
    	[1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1],
    	[4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1],
    	[7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1],
    	[6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1],
    	[3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1],
    	[0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1],
    	[6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1],
    	[0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1],
    	[11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1],
    	[6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1],
    	[5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1],
    	[9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1],
    	[1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1],
    	[1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1],
    	[10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1],
    	[0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1],
    	[5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1],
    	[10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1],
    	[11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1],
    	[9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1],
    	[7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1],
    	[2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1],
    	[8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1],
    	[9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1],
    	[9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1],
    	[1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1],
    	[9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1],
    	[9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1],
    	[5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1],
    	[0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1],
    	[10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1],
    	[2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1],
    	[0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1],
    	[0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1],
    	[9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1],
    	[5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1],
    	[3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1],
    	[5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1],
    	[8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1],
    	[9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1],
    	[1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1],
    	[3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1],
    	[4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1],
    	[9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1],
    	[11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1],
    	[11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1],
    	[2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1],
    	[9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1],
    	[3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1],
    	[1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1],
    	[4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1],
    	[3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1],
    	[0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1],
    	[9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1],
    	[1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
    
    # Now we iterate again through the lattice grid
    if keepGoing:
    	try:
    		listOfIsoSurfaces = ()			# This will have all is surfaes for all frames
    		for nFrame in xrange(1,2):			# TODO: change this to a loop on frames
    			trianglist = []			# This has the list of triangles for this frame
    			vertlist = [0,0,0,0,0,0,0,0,0,0,0,0]	# This will have the list of up to 12 vertexes where the surface can cross a grid cell
    			for nX in xrange(0,res1):
    				dX = sX*nX
    				dX1 = dX + sX
    				nX1 = nX + 1
    				for nY in xrange(0,res1):
    					dY = sY*nY
    					dY1 = dY + sY
    					nY1 = nY + 1
    					for nZ in xrange(0,res1):
    						dZ = sZ*nZ
    						dZ1 = dZ + sZ
    						nZ1 = nZ + 1
    
    						# Get the coordinates at the 8 corners of the lattice cell
    						pV0 = [dX, dY, dZ]
    						pV1 = [dX1,dY, dZ]
    						pV2 = [dX1,dY, dZ1]
    						pV3 = [dX, dY, dZ1]
    						pV4 = [dX, dY1,dZ]
    						pV5 = [dX1,dY1,dZ]
    						pV6 = [dX1,dY1,dZ1]
    						pV7 = [dX, dY1,dZ1]	
    						gV0 = lattice[nX, nY, nZ]
    						gV1 = lattice[nX1,nY, nZ]
    						gV2 = lattice[nX1,nY, nZ1]
    						gV3 = lattice[nX, nY, nZ1]
    						gV4 = lattice[nX, nY1,nZ]
    						gV5 = lattice[nX1,nY1,nZ]
    						gV6 = lattice[nX1,nY1,nZ1]
    						gV7 = lattice[nX, nY1,nZ1]	
    
    						# Determine which vertexes are inside the isosurface
    						cubeindex = 0
    						if gV0 < threshold:	cubeindex += 1
    						if gV1 < threshold:	cubeindex += 2
    						if gV2 < threshold:	cubeindex += 4
    						if gV3 < threshold:	cubeindex += 8
    						if gV4 < threshold:	cubeindex += 16
    						if gV5 < threshold:	cubeindex += 32
    						if gV6 < threshold:	cubeindex += 64
    						if gV7 < threshold:	cubeindex += 128
    
    						# Determine if the lattice cell is not entirely in or out the isosurface
    						if edgeTable[cubeindex] > 0:
    
    							# Now interpolates the vertices where the isosurface crosses the lattice cell
    							if ( edgeTable[cubeindex] & 1 ):	vertlist[ 0] = VertexInterlop( 0, 1, threshold, pV0, pV1, gV0, gV1 )
    							if ( edgeTable[cubeindex] & 2 ):	vertlist[ 1] = VertexInterlop( 1, 2, threshold, pV1, pV2, gV1, gV2 )
    							if ( edgeTable[cubeindex] & 4 ):	vertlist[ 2] = VertexInterlop( 2, 3, threshold, pV2, pV3, gV2, gV3 )
    							if ( edgeTable[cubeindex] & 8 ):	vertlist[ 3] = VertexInterlop( 3, 0, threshold, pV3, pV0, gV3, gV0 )
    							if ( edgeTable[cubeindex] & 16 ):	vertlist[ 4] = VertexInterlop( 4, 5, threshold, pV4, pV5, gV4, gV5 )
    							if ( edgeTable[cubeindex] & 32 ):	vertlist[ 5] = VertexInterlop( 5, 6, threshold, pV5, pV6, gV5, gV6 )
    							if ( edgeTable[cubeindex] & 64 ):	vertlist[ 6] = VertexInterlop( 6, 7, threshold, pV6, pV7, gV6, gV7 )
    							if ( edgeTable[cubeindex] & 128 ):	vertlist[ 7] = VertexInterlop( 7, 4, threshold, pV7, pV4, gV7, gV4 )
    							if ( edgeTable[cubeindex] & 256 ):	vertlist[ 8] = VertexInterlop( 0, 4, threshold, pV0, pV4, gV0, gV4 )
    							if ( edgeTable[cubeindex] & 512 ):	vertlist[ 9] = VertexInterlop( 1, 5, threshold, pV1, pV5, gV1, gV5 )
    							if ( edgeTable[cubeindex] & 1024 ):	vertlist[10] = VertexInterlop( 2, 6, threshold, pV2, pV6, gV2, gV6 )
    							if ( edgeTable[cubeindex] & 2048 ):	vertlist[11] = VertexInterlop( 3, 7, threshold, pV3, pV7, gV3, gV7 )
    						
    							# Now create the triangles (up to 5)
    							i = 0
    							while vertTable[cubeindex][i] != -1:
    								vert1 = vertlist[ vertTable[cubeindex][ i     ] ]
    								vert2 = vertlist[ vertTable[cubeindex][ i+2 ] ]
    								vert3 = vertlist[ vertTable[cubeindex][ i+1 ] ]
    								i += 3
    								trianglist.append((vert1,vert2,vert3))
    	except:
    		print 'Simulation failed...'
    		keepGoing = 0
    
    # Display the isoSurface at the current frame, just for fun
    if keepGoing:
    #	try:
    		isoGeom = poser.NewGeometry()
    		if len(trianglist) > 0:
    			for triangle in trianglist:
    				isoGeom.AddTriangle(triangle[0],triangle[1],triangle[2])
    			# Create the prop
    			isoSurface = scene.CreatePropFromGeom(isoGeom,'IsoSurface')
    		
    
    def VertexInterlop( n1, n2, threshold, pv1, pv2, gv1, gv2 ):
    	if abs(threshold-gv1) < 0.00001:	return pv1
    	if abs(threshold-gv2) < 0.00001:	return pv2
    	if abs(gv1-gv2)          < 0.00002:	return pv1
    	mu = (threshold-gv1)/(gv2-gv1)
    	mu = 0.5
    	x = pv1[0] + mu*(pv2[0]-pv1[0])
    	y = pv1[1] + mu*(pv2[1]-pv1[1])
    	z = pv1[2] + mu*(pv2[2]-pv1[2])
    	return (x,y,z)
    


  • This is the result of running Create Metaball 3 times: it creates the cube where the calculation is made, and 3 handlers; I used simple boxes for the handlers, as I didn't have patience to put spheres there:

    0_1512695893037_Metaball1.png

    then I set threshold = 0.1, resolution = 30 on the isofield cube, and then strength 0.1, 0.1 and 0.3 in the 3 handlers. Once I run Run Metaballs.py, it makes this:

    0_1512696012709_Metaball2.png

    then I move the right-most handler a bit to the left, delete the old isosurface (that's not automated yet), re-run Run Metaballs.py, and I get this:

    0_1512696269706_Metaball3.png

    This is the generated mesh:

    0_1512696412468_Metaball4.png

    This is at resolution=50 (3 to 5s to calculate per frame):

    0_1512696797386_Metaball5.png

    So, the normals seem to be alright and the mesh topology looks ok, but I'm not happy with the field function; I'm using f(r) = strength/r^2, where r is the distance from the point to the metaball; that seems to be falling off too fast; I'll look for a polynomial expansion of some sort, it should improve it, I guess.

    Also will probably need to compile the field calculations in C as a DLL and link python to that DLL to perform the field calculation; I think I'm doing to need a much better resolution than I thought at first (or find a way to smooth those vertices).



  • Oh, I'm such an anta; I had left a forced value of 0.5 in the code (that disables the built-in smoothing); with smoothing properly enabled, this is the result with resolution = 30 (about 1 sec per frame to calculate):

    0_1512697617057_metaball6.png

    So resolution = 30 or so should work fine, I think, and the thing is still fully in Python.



  • By the way, the thing can be converted to a traditional manifold mesh by going to Group / New Group / Add All / Weld Vertices; it will look exactly the same and then can be exported to OBJ or whatever.

    Once welded, it can be manipulated with morph editor and magnets, as it will then be a normal mesh.



  • This is a test to verify that it can get a texture from material room (as long as it's not uv-mapped, there are no uv coordinates); it's the same mesh with 2 random nodes added:

    0_1512699418674_metaball8.png

    Now, Poser is not smoothing the preview, because the triangles are disconnected; but rendering quality can be improved even in preview by running welding the vertices (Group / New Group / Add All / Weld), that will allow the preview window to work better and I guess rendering too; that's the same mesh with the same material, but now welded:

    0_1512699542776_metaball7.png



  • This is looking really good. I will be following this with some interest.



  • Thank you; I'm wondering if I can take a shot at doing the welding thingie for animation; that will destroy the idea of using deltas during animation playback as the example on top... but it will allow the smoothing on preview and rendering.

    The idea would be to create the geometry as above in Run Metaball.py for every frame, then CreatePropFromGeometry(), then create a group, add all, weld, then get back the geometry with Actor.Geometry() and store that in a list.

    Then during playback switch the geometry on the callback with Actor.SetGeometry(). Maybe that will be even faster than processing all vertices for deltas, but I have no idea what the effect will be on playback.

    So, instead of using a fixed geometry and baking the morphs for recalculation on playback, it would back the entire welded geometry and flip the geometry on playback. Hmm.. what to do... gotta test, I guess...



  • Hmm... I'm also thinking this thing can be very much optimized; the biggest loop is the field calculation - it has 4 nested loops with an expensive math calculation at the bottom; we calculate a lot of empty space.

    I'm think that instead of looping over the metaballs, nX, nY, nZ, I can:

    (a) loop over all metaballs
    (a.1) push the grid nearest nX, nY, nZ to that metaball into a stack
    (b) while stack not empty
    (b.1) pop nX, nY, nZ from stack
    (b.2) loop over all metaballs and calculate field strength at that nX, nY, nZ, and set it
    (b.3) mark that nX, nY, nZ as calculated
    (b.3) if field strength > threshold, then
    (b.3.1) push all nX+/-1, nY+/-1, nZ+/-1 that are not yet calculated into the stack

    That way we only calculate the field strength at lattice places where the field is > threshold.



  • Alright, did the first optimization, which is to replace the big fat inefficient 4 nested loops with a volume crawler. The crawler starts from the metaballs and keeps crawling around them until it calculates all field potentials higher than the threshold. That's less efficient when the metaballs fill the simulation area completely, but more efficient when they don't, which I think is the usual case.

    import poser
    import numpy
    #from numpy import oldnumeric
    
    #
    # This script creates a dynamic isosurface
    # fbs 12/7/2017
    #
    
    # Find IsoField, resolution and threshold
    keepGoing = 1
    scene = poser.Scene()
    try:
    	isoField = scene.Actor('IsoField')
    	resolution = int(isoField.Parameter('Resolution').Value())
    	res1 = resolution+1
    	invres1 = 1.0/res1
    	threshold = isoField.Parameter('Threshold').Value()
    	if resolution < 1:
    		print 'Resolution must be 1 or higher'
    		keepGoing = 0
    except:
    	print 'Run Create Metaballs.py first!'
    	keepGoing = 0
    
    # Build a lattice spanning the whole isoField; vertex 0 = [0,0,0], and vertex 7 the sizing of the cube
    if keepGoing:
    	try:
    		geom = isoField.Geometry()
    		vertex7 = geom.Vertex(7)
    		sX = vertex7.X() * invres1
    		sY = vertex7.Y() * invres1
    		sZ = vertex7.Z() * invres1
    		# lattice = numpy.zeros((res1+1,res1+1,res1+1))
    	except:
    		print 'Invalid geometry (isoField)...'
    		keepGoing = 0
    
    # Build a list of the metaballs in the isofield; get X,Y,Z and Strength
    if keepGoing:
    	try:
    		childList = isoField.Children()
    		metaList = ([])
    		stack = ([])
    		for child in childList:
    			strength = child.Parameter('Strength').Value()
    			childLocation = child.LocalDisplacement() 	# Notice is this internal units, not display units
    			metaList.append( [childLocation[0], childLocation[1], childLocation[2], strength] )
    			
    			# calculate the nX, nY, nZ in the grid, limited to [1,resolution] (as 0 and res1 have field = 0 by definition)
    			nX = min(resolution,max(1,int(round(childLocation[0]/sX))))
    			nY = min(resolution,max(1,int(round(childLocation[1]/sY))))
    			nZ = min(resolution,max(1,int(round(childLocation[2]/sZ))))
    			stack.append([nX,nY,nZ])
    	except:
    		print 'Invalid setup (metaballs)...'
    
    # Now we calculate the field value at the whole lattice; the field function is sum( str/r ), where r is the distance from
    # the lattice point to the metaball center; but that's expensive, so there are approximations; the simplest one is
    # sum of { (str-r^2)^3 ) for r<str, or 0 for r^2>str }; we'll force 0 at the ends of the lattice to force the iso surface to be closed
    
    # what a big fat inefficient loop; this is the kind of thing we use vector instructions...
    
    if keepGoing:
    	try:	
    		# we're now building the field in a lattice: -0.01 is not calculated (will default to 0.0 if continues
    		# not calculated), anything different is calculated
    
    		lattice = (-0.01)*numpy.ones((res1+1,res1+1,res1+1),dtype=numpy.float16)
    		
    		# the stack has a list of coordinates not yet calculated
    		while len(stack) > 0:
    			vXYZ = stack.pop()
    			nX = vXYZ[0]
    			nY = vXYZ[1]
    			nZ = vXYZ[2]
    			minus1 = numpy.float16(-0.01)
    			
    			# if not calculated yet:
    			if lattice[nX,nY,nZ] == minus1:
    				f = 0.0
    				for metaball in metaList:
    					Xo = metaball[0]
    					Yo = metaball[1]
    					Zo = metaball[2]
    					strength = metaball[3]/1000.0
    					dX = sX*nX-Xo
    					dY = sY*nY-Yo
    					dZ = sZ*nZ-Zo
    					r2 = dX*dX+dY*dY+dZ*dZ
    					f += strength/r2
    				lattice[nX,nY,nZ] = f
    
    				# if above threshold, calculate neighbors too
    				if f >= threshold:
    					nXm1 = nX-1
    					nXp1 = nX+1
    					nYm1 = nY-1
    					nYp1 = nY+1
    					nZm1 = nZ-1
    					nZp1 = nZ+1
    					okXm1 = nXm1 > 0
    					okXp1 = nXp1 < res1
    					okYm1 = nYm1 > 0
    					okYp1 = nYp1 < res1
    					okZm1 = nZm1 > 0
    					okZp1 = nZp1 < res1
    
    					if okXm1 and okYm1 and okZm1:	stack.append([nXm1,nYm1,nZm1])
    					if okXm1 and okYm1          :	stack.append([nXm1,nYm1,nZ  ])
    					if okXm1 and okYm1 and okZp1:	stack.append([nXm1,nYm1,nZp1])
    					if okXm1           and okZm1:	stack.append([nXm1,nY  ,nZm1])
    					if okXm1                    :	stack.append([nXm1,nY  ,nZ  ])
    					if okXm1           and okZp1:	stack.append([nXm1,nY  ,nZp1])
    					if okXm1 and okYp1 and okZm1:	stack.append([nXm1,nYp1,nZm1])
    					if okXm1 and okYp1          :	stack.append([nXm1,nYp1,nZ  ])
    					if okXm1 and okYp1 and okZp1:	stack.append([nXm1,nYp1,nZp1])
    					if           okYm1 and okZm1:	stack.append([nX  ,nYm1,nZm1])
    					if           okYm1          :	stack.append([nX  ,nYm1,nZ  ])
    					if           okYm1 and okZp1:	stack.append([nX  ,nYm1,nZp1])
    					if                     okZm1:	stack.append([nX  ,nY  ,nZm1])
    					##                          :	stack.append([nX  ,nY  ,nZ  ])
    					if                     okZp1:	stack.append([nX  ,nY  ,nZp1])
    					if           okYp1 and okZm1:	stack.append([nX  ,nYp1,nZm1])
    					if           okYp1          :	stack.append([nX  ,nYp1,nZ  ])
    					if           okYp1 and okZp1:	stack.append([nX  ,nYp1,nZp1])
    					if okXp1 and okYm1 and okZm1:	stack.append([nXp1,nYm1,nZm1])
    					if okXp1 and okYm1          :	stack.append([nXp1,nYm1,nZ  ])
    					if okXp1 and okYm1 and okZp1:	stack.append([nXp1,nYm1,nZp1])
    					if okXp1           and okZm1:	stack.append([nXp1,nY  ,nZm1])
    					if okXp1                    :	stack.append([nXp1,nY  ,nZ  ])
    					if okXp1           and okZp1:	stack.append([nXp1,nY  ,nZp1])
    					if okXp1 and okYp1 and okZm1:	stack.append([nXp1,nYp1,nZm1])
    					if okXp1 and okYp1          :	stack.append([nXp1,nYp1,nZ  ])
    					if okXp1 and okYp1 and okZp1:	stack.append([nXp1,nYp1,nZp1])
    d			
    	except:
    		print 'Error calculating field values'
    		keepGoing = 0
    		raise
    
    # Now we get the data tables for the marching cubes thingie; the algorithm is widely spread, not sure who was the first
    # author; this is from Ben Kenwright at www.xbdev.net:
    
    edgeTable=(
    	[0x0  , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
    	0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
    	0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
    	0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
    	0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
    	0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
    	0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
    	0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
    	0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
    	0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
    	0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
    	0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
    	0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
    	0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
    	0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
    	0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
    	0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
    	0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
    	0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
    	0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
    	0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
    	0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
    	0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
    	0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
    	0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
    	0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
    	0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
    	0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
    	0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
    	0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
    	0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
    	0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0])
    
    vertTable= (
    	[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1],
    	[3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1],
    	[3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1],
    	[3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1],
    	[9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1],
    	[9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1],
    	[2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1],
    	[8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1],
    	[9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1],
    	[4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1],
    	[3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1],
    	[1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1],
    	[4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1],
    	[4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1],
    	[5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1],
    	[2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1],
    	[9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1],
    	[0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1],
    	[2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1],
    	[10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1],
    	[5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1],
    	[5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1],
    	[9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1],
    	[0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1],
    	[1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1],
    	[10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1],
    	[8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1],
    	[2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1],
    	[7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1],
    	[2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1],
    	[11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1],
    	[5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1],
    	[11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1],
    	[11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1],
    	[1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1],
    	[9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1],
    	[5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1],
    	[2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1],
    	[5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1],
    	[6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1],
    	[3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1],
    	[6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1],
    	[5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1],
    	[1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1],
    	[10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1],
    	[6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1],
    	[8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1],
    	[7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1],
    	[3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1],
    	[5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1],
    	[0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1],
    	[9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1],
    	[8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1],
    	[5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1],
    	[0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1],
    	[6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1],
    	[10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1],
    	[10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1],
    	[8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1],
    	[1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1],
    	[0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1],
    	[10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1],
    	[3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1],
    	[6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1],
    	[9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1],
    	[8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1],
    	[3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1],
    	[6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1],
    	[0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1],
    	[10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1],
    	[10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1],
    	[2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1],
    	[7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1],
    	[7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1],
    	[2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1],
    	[1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1],
    	[11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1],
    	[8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1],
    	[0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1],
    	[7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1],
    	[10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1],
    	[2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1],
    	[6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1],
    	[7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1],
    	[2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1],
    	[1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1],
    	[10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1],
    	[10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1],
    	[0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1],
    	[7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1],
    	[6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1],
    	[8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1],
    	[6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1],
    	[4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1],
    	[10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1],
    	[8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1],
    	[0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1],
    	[1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1],
    	[8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1],
    	[10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1],
    	[4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1],
    	[10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1],
    	[5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1],
    	[11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1],
    	[9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1],
    	[6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1],
    	[7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1],
    	[3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1],
    	[7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1],
    	[9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1],
    	[3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1],
    	[6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1],
    	[9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1],
    	[1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1],
    	[4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1],
    	[7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1],
    	[6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1],
    	[3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1],
    	[0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1],
    	[6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1],
    	[0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1],
    	[11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1],
    	[6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1],
    	[5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1],
    	[9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1],
    	[1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1],
    	[1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1],
    	[10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1],
    	[0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1],
    	[5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1],
    	[10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1],
    	[11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1],
    	[9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1],
    	[7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1],
    	[2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1],
    	[8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1],
    	[9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1],
    	[9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1],
    	[1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1],
    	[9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1],
    	[9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1],
    	[5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1],
    	[0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1],
    	[10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1],
    	[2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1],
    	[0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1],
    	[0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1],
    	[9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1],
    	[5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1],
    	[3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1],
    	[5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1],
    	[8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1],
    	[9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1],
    	[0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1],
    	[1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1],
    	[3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1],
    	[4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1],
    	[9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1],
    	[11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1],
    	[11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1],
    	[2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1],
    	[9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1],
    	[3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1],
    	[1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1],
    	[4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1],
    	[4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1],
    	[0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1],
    	[3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1],
    	[3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1],
    	[0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1],
    	[9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1],
    	[1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
    	[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
    
    # Now we iterate again through the lattice grid
    if keepGoing:
    	try:
    		listOfIsoSurfaces = ()			# This will have all is surfaes for all frames
    		for nFrame in xrange(1,2):		# TODO: change this to a loop on frames
    			trianglist = []			# This has the list of triangles for this frame
    			vertlist = [0,0,0,0,0,0,0,0,0,0,0,0]	# This will have the list of up to 12 vertexes where the surface can cross a grid cell
    			for nX in xrange(0,res1):
    				dX = sX*nX
    				dX1 = dX + sX
    				nX1 = nX + 1
    				for nY in xrange(0,res1):
    					dY = sY*nY
    					dY1 = dY + sY
    					nY1 = nY + 1
    					for nZ in xrange(0,res1):
    						dZ = sZ*nZ
    						dZ1 = dZ + sZ
    						nZ1 = nZ + 1
    
    						# Get the coordinates at the 8 corners of the lattice cell
    						pV0 = [dX, dY, dZ]
    						pV1 = [dX1,dY, dZ]
    						pV2 = [dX1,dY, dZ1]
    						pV3 = [dX, dY, dZ1]
    						pV4 = [dX, dY1,dZ]
    						pV5 = [dX1,dY1,dZ]
    						pV6 = [dX1,dY1,dZ1]
    						pV7 = [dX, dY1,dZ1]	
    						gV0 = lattice[nX, nY, nZ]
    						gV1 = lattice[nX1,nY, nZ]
    						gV2 = lattice[nX1,nY, nZ1]
    						gV3 = lattice[nX, nY, nZ1]
    						gV4 = lattice[nX, nY1,nZ]
    						gV5 = lattice[nX1,nY1,nZ]
    						gV6 = lattice[nX1,nY1,nZ1]
    						gV7 = lattice[nX, nY1,nZ1]	
    
    						# Determine which vertexes are inside the isosurface
    						cubeindex = 0
    						if gV0 < threshold:	cubeindex += 1
    						if gV1 < threshold:	cubeindex += 2
    						if gV2 < threshold:	cubeindex += 4
    						if gV3 < threshold:	cubeindex += 8
    						if gV4 < threshold:	cubeindex += 16
    						if gV5 < threshold:	cubeindex += 32
    						if gV6 < threshold:	cubeindex += 64
    						if gV7 < threshold:	cubeindex += 128
    
    						# Determine if the lattice cell is not entirely in or out the isosurface
    						if edgeTable[cubeindex] > 0:
    
    							# Now interpolates the vertices where the isosurface crosses the lattice cell
    							if ( edgeTable[cubeindex] & 1 ):	vertlist[ 0] = VertexInterpol( 0, 1, threshold, pV0, pV1, gV0, gV1 )
    							if ( edgeTable[cubeindex] & 2 ):	vertlist[ 1] = VertexInterpol( 1, 2, threshold, pV1, pV2, gV1, gV2 )
    							if ( edgeTable[cubeindex] & 4 ):	vertlist[ 2] = VertexInterpol( 2, 3, threshold, pV2, pV3, gV2, gV3 )
    							if ( edgeTable[cubeindex] & 8 ):	vertlist[ 3] = VertexInterpol( 3, 0, threshold, pV3, pV0, gV3, gV0 )
    							if ( edgeTable[cubeindex] & 16 ):	vertlist[ 4] = VertexInterpol( 4, 5, threshold, pV4, pV5, gV4, gV5 )
    							if ( edgeTable[cubeindex] & 32 ):	vertlist[ 5] = VertexInterpol( 5, 6, threshold, pV5, pV6, gV5, gV6 )
    							if ( edgeTable[cubeindex] & 64 ):	vertlist[ 6] = VertexInterpol( 6, 7, threshold, pV6, pV7, gV6, gV7 )
    							if ( edgeTable[cubeindex] & 128 ):	vertlist[ 7] = VertexInterpol( 7, 4, threshold, pV7, pV4, gV7, gV4 )
    							if ( edgeTable[cubeindex] & 256 ):	vertlist[ 8] = VertexInterpol( 0, 4, threshold, pV0, pV4, gV0, gV4 )
    							if ( edgeTable[cubeindex] & 512 ):	vertlist[ 9] = VertexInterpol( 1, 5, threshold, pV1, pV5, gV1, gV5 )
    							if ( edgeTable[cubeindex] & 1024 ):	vertlist[10] = VertexInterpol( 2, 6, threshold, pV2, pV6, gV2, gV6 )
    							if ( edgeTable[cubeindex] & 2048 ):	vertlist[11] = VertexInterpol( 3, 7, threshold, pV3, pV7, gV3, gV7 )
    						
    							# Now create the triangles (up to 5)
    							i = 0
    							while vertTable[cubeindex][i] != -1:
    								vert1 = vertlist[ vertTable[cubeindex][ i     ] ]
    								vert2 = vertlist[ vertTable[cubeindex][ i+2 ] ]
    								vert3 = vertlist[ vertTable[cubeindex][ i+1 ] ]
    								i += 3
    								trianglist.append((vert1,vert2,vert3))
    	except:
    		print 'Simulation failed...'
    		keepGoing = 0
    
    # Display the isoSurface at the current frame, just for fun
    if keepGoing:
    #	try:
    		isoGeom = poser.NewGeometry()
    		if len(trianglist) > 0:
    			for triangle in trianglist:
    				isoGeom.AddTriangle(triangle[0],triangle[1],triangle[2])
    			# Create the prop
    			isoSurface = scene.CreatePropFromGeom(isoGeom,'IsoSurface')
    		
    
    def VertexInterpol( n1, n2, threshold, pv1, pv2, gv1, gv2 ):
    	if abs(threshold-gv1) < 0.00001:	return pv1
    	if abs(threshold-gv2) < 0.00001:	return pv2
    	if abs(gv1-gv2)       < 0.00002:	return pv1
    	mu = (threshold-gv1)/(gv2-gv1)
    	x = pv1[0] + mu*(pv2[0]-pv1[0])
    	y = pv1[1] + mu*(pv2[1]-pv1[1])
    	z = pv1[2] + mu*(pv2[2]-pv1[2])
    	return (x,y,z)
    

    I'm thinking I can do a similar idea with the triangle calculation. The triangle calculation is still going on 3 nested loops across all grid cells (at resolution = 50 that's 50x50x50 loops = 125,000 passes), but the end mesh has someting like 6,000 triangles, which means about 2,000 grid cells actually generating triangles.

    So I'm thinking that once a lattice grid point is calculated, then if it has value > threshold then I push the coordinates of the 8 cells that share that grid point into a list for triangle calculation, provided that I didn't push them already. Then I just have to scan that list to calculate triangles.

    Hopefully then I just got to scan over 2,000 cells in the list, instead of running 125,000 for loops...



  • Oh, also changed the datatype for the field matrix to be 16 bit floats. So at resolution = 50 that's 250 KB memory for that, which is pretty much nothing. At resolution = 100 that would be 2 MB, so that should allow for larger calculation fields, I guess.



  • Alright, 2nd optimization is in too; now the triangle generation uses a similar volume crawler as the field calculation; the timings in seconds per frame are:

    Resolution = 5 Lattice size = 7
    Field calculation time = 0.0019998550415
    Mesh generation time = 0.00500011444092

    Resolution = 20 Lattice size = 22
    Field calculation time = 0.0780000686646
    Mesh generation time = 0.0650000572205

    Resolution = 40 Lattice size = 42
    Field calculation time = 0.591000080109
    Mesh generation time = 0.282999992371

    Resolution = 60 Lattice size = 62
    Field calculation time = 1.86899995804
    Mesh generation time = 0.705000162125

    I'm calling that good enough for that part; now, to welding, so that we can get some proper shading on these guys.



  • Moving this discussion to thread with a better name.


Log in to reply
 

Looks like your connection to Graphics Forum was lost, please wait while we try to reconnect.