Faster than if-elif statement - python

I am receiving Points in large number from a sensor in real-time. However, I just need 4 categories of points, i.e., top_left, top_right, bottom_left, and bottom_right. I have an if-elif statement in Python 2 as follows:
from random import random, randint
# points below are received from sensor. however,
# here in this post I am creating it randomly.
points = [Point(randint(0, i), random(), random(), random()) for i in range(100)]
# 4 categories
top_left, top_right, bottom_left, bottom_right = None, None, None, None
for p in points:
if p.id == 5:
top_left = p
elif p.id == 7:
top_right = p
elif p.id == 13:
bottom_left = p
elif p.id == 15:
bottom_right = p
print top_left.id, top_left.x, top_left.y, top_left.z # check variable
Each Point has an id and x, y, z parameters. This is an inbuilt class. I am just showing a sample class here.
class Point():
def __init__(self, id, x, y, z):
self.id = id
self.x = x
self.y = y
self.z = z
Is there any efficient way considering runtime of achieving the same.
Answer:
I am adding the results which I got from the answers. It seems that the answer by Elis Byberi is fastest among all. Below is my test code:
class Point():
def __init__(self, id, x, y, z):
self.id = id
self.x = x
self.y = y
self.z = z
from random import random, randint
n = 1000
points = [Point(randint(0, i), random(), random(), random()) for i in range(n)]
def method1():
top_left, top_right, bottom_left, bottom_right = None, None, None, None
for p in points:
if p.id == 5:
top_left = p
elif p.id == 7:
top_right = p
elif p.id == 13:
bottom_left = p
elif p.id == 15:
bottom_right = p
#print top_left.id, top_left.x, top_left.y, top_left.z
def method2():
categories = {
5: None, # top_left
7: None, # top_right
13: None, # bottom_left
15: None # bottom_right
}
for p in points:
categories[p.id] = p
top_left = categories[5]
#print top_left.id, top_left.x, top_left.y, top_left.z
def method3():
name_to_id = {'top_left': 5, 'top_right': 7, 'bottom_left': 13, 'bottom_right': 15}
ids = [value for value in name_to_id.values()]
bbox = {id: None for id in ids}
for point in points:
try:
bbox[point.id] = Point(point.id, point.x, point.y, point.z)
except KeyError: # Not an id of interest.
pass
top_left = bbox[name_to_id['top_left']]
#print top_left.id, top_left.x, top_left.y, top_left.z
from timeit import Timer
print 'method 1:', Timer(lambda: method1()).timeit(number=n)
print 'method 2:', Timer(lambda: method2()).timeit(number=n)
print 'method 3:', Timer(lambda: method3()).timeit(number=n)
See Below the returned output:
ravi#home:~/Desktop$ python test.py
method 1: 0.174991846085
method 2: 0.0743980407715
method 3: 0.582262039185

You can use a dict to save objects. Dict is very efficient in key lookup.
Using dict is twice as fast as using if else block.
This is the most efficient way in python:
from random import random, randint
class Point():
def __init__(self, id, x, y, z):
self.id = id
self.x = x
self.y = y
self.z = z
# points below are received from sensor. however,
# here in this post I am creating it randomly.
points = [Point(randint(0, i), random(), random(), random()) for i in
range(100)]
# 4 categories
categories = {
5: None, # top_left
7: None, # top_right
13: None, # bottom_left
15: None # bottom_right
}
for p in points:
categories[p.id] = p
>>> print categories[5].id, categories[5].x, categories[5].y, categories[5].z # check variable
5 0.516239541892 0.935096344266 0.0859987803457

Instead of using list-comprehension:
points = [Point(randint(0, i), random(), random(), random()) for i in range(100)]
use a loop and assign the points during creation:
points = []
for i in range(100):
p = Point(randint(0, i), random(), random(), random())
points.append(p)
if p.id == 5:
top_left = p
elif p.id == 7:
top_right = p
elif p.id == 13:
bottom_left = p
elif p.id == 15:
bottom_right = p
This way you get all done in one iteration instead of two.

Here's a way that should be faster because it uses a single if to determine whether a Point is one of the ones representing the extremes based on their id attribute, plus the if uses the very fast dictionary in operation for membership testing. Essentially what is done it the bbox dictionary is preloaded with keys that correspond to the four ids sought, which make checking for any of them a single relatively efficient operation.
Note that if there are points with duplicate ids in the Point list, the last one seen will be the one selected. Also note that if no point with a matching id is found, some of the final variables will have a value of None instead of a Point instance.
from random import randint, random
from pprint import pprint
from operator import attrgetter
class Point():
def __init__(self, id, x, y, z):
self.id = id
self.x = x
self.y = y
self.z = z
points = [Point(randint(0, 20), random(), random(), random()) for i in range(100)]
name_to_id = {'top_left': 5, 'top_right': 7, 'bottom_left': 13, 'bottom_right': 15}
bbox = {id: None for id in name_to_id.values()} # Preload with ids of interest.
for point in points:
if point.id in bbox: # id of interest?
bbox[point.id] = point
# Assign bbox's values to variables with meaningful names.
top_left = bbox[name_to_id['top_left']]
top_right = bbox[name_to_id['top_right']]
bottom_left = bbox[name_to_id['bottom_left']]
bottom_right = bbox[name_to_id['bottom_right']]
for point in [top_left, top_right, bottom_left, bottom_right]:
print('Point({}, {}, {}, {})'.format(point.id, point.x, point.y, point.z))

Related

How can I use an element of a python array that is an instance of an object in a function by using the index into the array?

I have a task wherein I am to determine if a Point(x,y) is closer than some amount to any of the Points that are stored in a Python array. Here is the test code:
from point import *
collection = []
p1 = Point(3,4)
collection.append(p1)
print(collection)
p2 = Point(3,0)
collection.append(p2)
print(collection)
p3 = Point(3,1)
radius = 1
print( collection[1] ) # This works, BTW
p = collection[1]
print( p ) # These two work also!
for i in collection:
p = collection[i] # THIS FAILS
if distance(p3,p) < 2*radius:
print("Point "+collection[i]+" is too close to "+p3)
The file point.py contains:
import math
class Point:
'''Creates a point on a coordinate plane with values x and y.'''
COUNT = 0
def __init__(self, x, y):
'''Defines x and y variables'''
self.X = x
self.Y = y
def move(self, dx, dy):
'''Determines where x and y move'''
self.X = self.X + dx
self.Y = self.Y + dy
def __str__(self):
return "Point(%s,%s)"%(self.X, self.Y)
def __str__(self):
return "(%s,%s)"%(self.X,self.Y)
def testPoint(x=0,y=0):
'''Returns a point and distance'''
p1 = Point(3, 4)
print (p1)
p2 = Point(3,0)
print (p2)
return math.hypot(p1, p2)
def distance(self, other):
dx = self.X - other.X
dy = self.Y - other.Y
return math.sqrt(dx**2 + dy**2)
#p1 = Point(3,4)
#p2 = Point(3,0)
#print ("p1 = %s"%p1)
#print ("distance = %s"%(distance(p1, p2)))
Now, I have a couple of questions here to help me understand.
In the test case, why doesn't the print of the array use the str function to
print the Point out as '(x,y)'?
In ' if distance(p3,collection[i]) ', why isn't collection[i] recognized as a Point which the distance function is expecting?
In the 'p = collection[i]' statement, why does python complain that the list indices must be integers or slices, not Point?
It appears that the collection array is not recognized as an array of Point instances. I'm confused as in other OO languages like Objective-C or Java, these are simple things to do.
Take a look at this question. __repr__() is used when rendering things in lists.
(and 3.) I'm not sure if I follow your questions, but the problem you have in your code is that Python hands you the object itself, not the index. So:
for i in collection:
p = collection[i] # THIS FAILS
if distance(p3,p) < 2*radius:
print("Point "+collection[i]+" is too close to "+p3)
should be:
for p in collection:
if distance(p3,p) < 2*radius:
print(f"Point {p} is too close to {p3}")

finding distance between two points in python, passing inputs via two different objects

I have to write a code to find the different between two point by passing value via two objects as below.
But I am getting TypeError: init() missing 3 required positional arguments: 'x', 'y', and 'z'
class Point:
def __init__(self, x, y,z):
self.x = x
self.y = y
self.z = z
def __str__(self):
return '(point: {},{},{})'.format(self.x, self.y, self.z)
def distance(self, other):
return sqrt( (self.x-other.x)**2 + (self.y-other.y)**2 + (self.z -other.z)**2 )
p = Point()
p1 = Point(12, 3, 4)
p2 = Point(4, 5, 6)
p3 = Point(-2, -1, 4)
print(p.distance(p1,p3))
The problem comes from this line:
p = Point()
When you defined you class, you specified it has to be passed 3 parameters for it to be initialised (def __init__(self, x, y,z)).
If you still want to be able to create this Point object without having to pass those 3 parameters, you can make them optional like this :
def __init__(self, x=0, y=0, z=0):
self.x = x
self.y = y
self.z = z
This way, if you were to not specify these parameters (as you did), it will create a point with coordinates {0, 0, 0} by default.
You are not passing the required 3 arguments for p = Point()
fixed your errors
from math import sqrt
class Point:
def __init__(self, x, y,z):
self.x = x
self.y = y
self.z = z
def __str__(self):
return '(point: {},{},{})'.format(self.x, self.y, self.z)
def distance(self, other):
return sqrt( (self.x-other.x)**2 + (self.y-other.y)**2 + (self.z -other.z)**2 )
# p = Point() # not required
p1 = Point(12, 3, 4)
p2 = Point(4, 5, 6)
p3 = Point(-2, -1, 4)
print(p1.distance(p3)) # use this to find distance between p1 and any other point
# or use this
print(Point.distance(p1,p3))
class Point:
def __init__(self, x, y,z):
self.x = x
self.y = y
self.z = z
def __str__(self):
return '(point: {},{},{})'.format(self.x, self.y, self.z)
def distance(self, other):
return math.sqrt( (self.x-other.x)**2 + (self.y-other.y)**2 + (self.z -other.z)**2 )
p1 = Point(12, 3, 4)
p2 = Point(4, 5, 6)
p3 = Point(-2, -1, 4)
print(Point.distance(p1,p3))
It works like this.You should not define a P point seperate than the other three points. Every point is a seperate instance. But when you try to use the function just call the class.

Animate scatter plot

I'm building a Python tool for visualizing data structures in 3D. The code below is the full program, it's even set up to run a default test model with some random data; you just need numpy and matplotlib. Basically, you declare a Node, connect it to other Nodes, and it makes pretty 3D networks. I'd like to be able to call switchNode() and have it flip the color of a node between black and white. With the way it works right now, every time a Node is instantiated, the plot is added to with another data point. I'm not familiar enough with matplotlib's animation tools to know the best way of doing this (my attempt at following the example from another post is commented out on line 83, and hoped someone could offer me some tips. Thanks!!
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# user-defined variables
debug = False
axisOn = False
labelsOn = True
maxNodes = 20
edgeColor = 'b'
dottedLine = ':'
darkGrey = '#191919'
lightGrey = '#a3a3a3'
plotBackgroundColor = lightGrey
fontColor = darkGrey
gridColor = 'k'
numFrames = 200
# global variables
fig = None
hTable = []
ax = None
numNodes = 0
# initialize plot
def initPlot():
global ax, fontColor, fig
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d', axisbg=plotBackgroundColor)
if axisOn == False:
ax.set_axis_off()
else:
ax.set_axis_on()
fontColor = darkGrey
# gives n random float values between vmin and vmax
def randrange(n, vmin, vmax):
return (vmax - vmin) * np.random.rand(n) + vmin
# builds an empty node with a given value, helper method for makeNode
def makeNodeS(value):
global hTable, numNodes
n = Node(value)
hTable.append(n)
numNodes = len(hTable)
return n
# builds a node with given parameters
def makeNode(value, location, color, marker):
n = makeNodeS(value)
n.setLoc(location)
n.setStyle(color, marker)
if debug:
print("Building node {} at {} with color = {}, marker = {}, and associations = {}.".format(value, location, color, marker, n.assocs))
return n
# aggregate nodes in hTable and plot them in 3D
def plotNodes():
global hTable
if debug:
print("Plotting Graph...")
for elem in hTable:
if debug:
print(" Plotting node {}...".format(elem.value))
global fig, numFrames
scat = ax.scatter(elem.location[0], elem.location[1], elem.location[2], c=elem.color, marker=elem.marker)
for c in elem.assocs:
if (getNode(c).value != elem.value):
if elem.count in getNode(c).assocs: # if the two nodes are associated to each other, draw solid line
ax.plot([elem.location[0], getNode(c).location[0]], [elem.location[1], getNode(c).location[1]], [elem.location[2], getNode(c).location[2]], edgeColor)
if debug:
print(" Plotting double edge between {} and {}...".format(elem.value, getNode(c).value))
else:
ax.plot([elem.location[0], getNode(c).location[0]], [elem.location[1], getNode(c).location[1]], [elem.location[2], getNode(c).location[2]], edgeColor + dottedLine)
if debug:
print(" Plotting single edge from {} to {}...".format(elem.value, getNode(c).value))
#ani = anim.FuncAnimation(fig, update_plot, frames=xrange(numFrames), fargs=(['b', 'w'], scat))
# build single connection from node A to node B
def sConnect(nodeA, nodeB):
nodeA.addAssoc(nodeB)
if debug:
print(" Drawing single connection from node {} to node {}...".format(nodeA.value, nodeB.value))
# build double connection from node A to node B, and from node B to node A
def dConnect(nodeA, nodeB):
if debug:
print("\nDouble node connection steps:")
sConnect(nodeA, nodeB)
sConnect(nodeB, nodeA)
# update scatter with new color data
def update_plot(i, data, scat):
scat.set_array(data[i])
return scat
# returns the node with given count
def getNode(count):
global hTable
n = hTable[count-1]
return n
# set up axis info
def defineAxis():
ax.set_xlabel('X Label')
ax.xaxis.label.set_color(lightGrey)
ax.tick_params(axis='x', colors=lightGrey)
ax.set_ylabel('Y Label')
ax.yaxis.label.set_color(lightGrey)
ax.tick_params(axis='y', colors=lightGrey)
ax.set_zlabel('Z Label')
ax.zaxis.label.set_color(lightGrey)
ax.tick_params(axis='z', colors=lightGrey)
# randomly populate nodes and connect them
def test():
for i in range (0, maxNodes):
rand = np.random.rand(2)
if (0 <= rand[0] <= 0.25):
q = makeNode(i, np.random.rand(3), 'r', '^')
elif (0.25 < rand[0] <= 0.5):
q = makeNode(i, np.random.rand(3), 'b', 'o')
elif (0.5 < rand[0] <= 0.75):
q = makeNode(i, np.random.rand(3), 'g', 'v')
elif (0.75 < rand[0]):
q = makeNode(i, np.random.rand(3), 'w', 'o')
if (0 < i < maxNodes-1):
if (rand[1] <= 0.2):
dConnect(q, getNode(q.count-1))
elif (rand[1] < 0.5):
sConnect(q, getNode(q.count-1))
# randomly populate binary nodes and connect them
def test2():
for i in range (0, maxNodes):
rand = np.random.rand(2)
if (0 <= rand[0] <= 0.80):
q = makeNode(i, np.random.rand(3), 'k', 'o')
else:
q = makeNode(i, np.random.rand(3), 'w', 'o')
if (i > 0):
if (rand[1] <= 0.2):
dConnect(q, getNode(q.count-1))
elif (rand[1] > 0.2):
sConnect(q, getNode(q.count-1))
# switches a binary node between black and white
def switchNode(count):
q = getNode(count)
if (q.color == 'b'):
q.color = 'w'
else:
q.color = 'b'
# main program
def main():
## MAIN PROGRAM
initPlot()
test2()
plotNodes()
defineAxis()
plt.show()
# class structure for Node class
class Node(str):
value = None
location = None
assocs = None
count = 0
color = None
marker = None
# initiate node
def __init__(self, val):
self.value = val
global numNodes
numNodes += 1
self.count = numNodes
self.assocs = []
self.color = 'b'
self.marker = '^'
# set node location and setup 3D text label
def setLoc(self, coords):
self.location = coords
global labelsOn
if labelsOn:
ax.text(self.location[0], self.location[1], self.location[2], self.value, color=fontColor)
# define node style
def setStyle(self, color, marker):
self.color = color
self.marker = marker
# define new association
def addAssoc(self, newAssociation):
self.assocs.append(newAssociation.count)
if debug:
print(" Informing node association: Node {} -> Node {}".format(self.value, newAssociation.value))
main()
Scatter returns a collection and you can change the colors of the points in a collection with set_facecolor. Here's an example you can adapt for your code:
plt.figure()
n = 3
# Plot 3 white points.
c = [(1,1,1), (1,1,1), (1,1,1)]
p = plt.scatter(np.random.rand(n), np.random.rand(n), c = c, s = 100)
# Change the color of the second point to black.
c[1] = (0,0,0)
p.set_facecolor(c)
plt.show()

Rectangle Enclosure (Python3)

I am trying to create a solution to this problem, with my code focusing on looping(for and while):
Let parameter Q be a list of "rectangles", specified by width, height,
and (x,y)-location of the lower left corner. Enclosure(Q) returns the
smallest rectangle that contains each rectangle in Q . The way a
rectangle is represented is by tuple, (x,y,width,height).
Here is my code so far:
def Enclosure(Q):
c = []
d = []
for (x,y,width,height) in sorted(Q):
c.append(x)
d.append(y)
print(c)
print(min(c))
e=tuple([min(c)]+[min(d)])
print(e)
Ignore the print statements, those are only there for debugging purposes. My code to this point creates a tuple of the (x,y) coordinates of the lower left corner of the enclosing rectangle that I am trying to have the program form. But after this point, I have absolutely no idea on how to find the (height,width) of the enclosing rectangle. How can I do that?
Also, here is an example of the program should do when it runs:
R1 = (10,20,5,100) #R1,R2,R3 are each rectangles
R2 = (15,5,30,150) #layout of the tuple:(x,y,width,height)
R3 = (-4,30,20,17)
Enclosure([R1,R2,R3])
(-4, 5, 49, 150) #rectangle that encloses R1,R2,R3
Try this:
def Enclosure(Q):
c = []
d = []
x2 = []
y2 = []
for (x,y,width,height) in sorted(Q):
c.append(x)
d.append(y)
x2.append(width + x)
y2.append(height + y)
e = tuple([min(c)]+[min(d)] + [max(x2) - min(c)] + [max(y2) - min(d)])
print(e)
You could replace e = tuple... to:
e=(min(c), min(d), max(x2) - min(c), max(y2) - min(d)) # This makes a tuple directly
to avoid making first a list, and converting it to tuple as you did.
Make sure you can explain why you used inheritance or your teacher will know you are cheating:
class Geometry(object):
def __init__(self, x, y):
self.x = x
self.y = y
class Point(Geometry):
def __repr__(self):
return "<Point at {s.x},{s.y}>".format(s=self)
class Rectangle(Geometry):
def __init__(self, x, y, width, height):
super(Rectangle, self).__init__(x, y)
self.width = width
self.height = height
#property
def oposite_corner(self):
return Point(self.x + self.width, self.y + self.height)
def __repr__(self):
return "<Rectange of {s.width}x{s.height} at {s.x},{s.y}>".format(s=self)
def enclosing_rectangle(rect_list):
x1, y1, x2, y2 = (
min([r.x for r in rect_list]),
min([r.y for r in rect_list]),
max([r.oposite_corner.x for r in rect_list]),
max([r.oposite_corner.y for r in rect_list]),
)
return Rectangle(x1, y1, x2 - x1, y2 - y1)
Please test and fix any bug by yourself (hint: super changed):
>>> l = [Rectangle(1, 2, 3, 4), Rectangle(-1, -1, 1, 1), Rectangle(3, 4, 1, 1)]
>>> enclosing_rectangle(l)
<Rectangle of 5x7 at -1,-1>
[update]
What?!? A delete vote? Care to explain what is so offensive with this answer?

How do people render so many voxels at once?

I have actually 2 questions.
1) I see these renders of thousands of voxels, yet using an octree I can still get only about 500 voxels on the screen.
import warnings,sys
sys.setrecursionlimit(50000)
class storedItem():
def __init__(self,coord,data):
self.coord = coord
self.data = data
self.node = None
def remove(self):
self.node.children.remove(self)
self.node = None
class Node():
def __init__(self,parent,lower,upper):
self.parent = parent
self.children = []
self.lowerbound = lower
self.upperbound = upper
self.setVolume()
def setVolume(self):
dx = self.upperbound[0] - self.lowerbound[0]
dy = self.upperbound[1] - self.lowerbound[1]
dz = self.upperbound[2] - self.lowerbound[2]
self.volume = dx*dy*dz
def inbound(self,coord):
if self.lowerbound[0] <= coord[0] and self.lowerbound[1] <= coord[1] and self.lowerbound[2] <= coord[2]:
if self.upperbound[0] >= coord[0] and self.upperbound[1] >= coord[1] and self.upperbound[2] >= coord[2]:
return True
return False
def returnValueOrChildnode(self,coord):
if not self.inbound(coord):
return self.parent
for child in self.children:
if child.__class__ == Node:
if child.inbound(coord):
return child
elif child.__class__ == storedItem:
if child.coord == coord:
return child
return None
def deleteOrReturnChildNode(self,coord):
if not self.inbound(coord):
return self.parent
for child in self.children:
if child.__class__ == Node:
if child.inbound(coord):
return child
elif child.__class__ == storedItem:
if child.coord == coord:
self.children.remove(child)
del(child)
return True
return None
def insertStoredItem(self,item):
if len(self.children) < 8:
self.children.append(item)
item.node = self
return True
if len(self.children) == 8:
for child in self.children:
if child.__class__ == Node:
if child.inbound(item.coord):
return child.insertStoredItem(item)
elif item.coord == child.coord:
warnings.warn('Already an item at this location, replacing it')
self.children.remove(child)
self.children.append(item)
self.breakupIntoChildren()
self.insertStoredItem(item)
def breakupIntoChildren(self):
#if self.volume == 8:
# raise Exception("Node full. Cannot add to this node")
nodes = []
delta = (self.upperbound[0] - self.lowerbound[0] +1)/2
x1,x2,x3 = (self.lowerbound[0],self.lowerbound[0]+delta -1,self.upperbound[0])
y1,y2,y3 = (self.lowerbound[1],self.lowerbound[1]+delta -1,self.upperbound[1])
z1,z2,z3 = (self.lowerbound[2],self.lowerbound[2]+delta -1,self.upperbound[2])
nodes.append(Node(self,(x1,y1,z1),(x2,y2,z2)))
nodes.append(Node(self,(x2 + 1,y1,z1),(x3,y2,z2)))
nodes.append(Node(self,(x1,y1,z2 +1),(x2,y2,z3)))
nodes.append(Node(self,(x2 + 1,y1,z2 + 1),(x3,y2,z3)))
nodes.append(Node(self,(x1,y2 + 1,z1),(x2,y3,z2)))
nodes.append(Node(self,(x2 + 1,y2 + 1,z1),(x3,y3,z2)))
nodes.append(Node(self,(x1,y2 + 1,z2 + 1),(x2,y3,z3)))
nodes.append(Node(self,(x2 + 1,y2 + 1,z2 + 1),(x3,y3,z3)))
while self.children:
child = self.children[0]
for node in nodes:
if node.inbound(child.coord):
node.insertStoredItem(child)
self.children.remove(child)
self.children = nodes
class Octree():
def __init__(self,size,maxsearch=1000):
if size % 2:
raise Exception("Size must be multiple of 2")
self.root = Node(None, (0,0,0),(size,size,size))
self.size = size
self.maxsearch=maxsearch
def search(self,coord):
searching = True
node = self.root
count = 0
while searching:
result = node.returnValueOrChildnode(coord)
if result is None:
searching = False
elif result.__class__ == storedItem:
result = result.data
searching = False
elif result.__class__ == Node:
node = result
count += 1
if count > self.maxsearch: #just incase something goes wrong
searching=False
result = None
raise Exception("Max Search depth limit reached")
return result
def insert(self,coord,data):
if not self.root.inbound(coord):
print coord, self.size, self.root.upperbound, self.root.lowerbound
raise Exception("Coordinate outside scope of octree")
item = storedItem(coord,data)
self.root.insertStoredItem(item)
def remove(self,coord):
searching = True
node = self.root
count = 0
while searching:
result = node.deleteOrReturnChildNode(coord)
if result is True:
searching = False
return True
elif result is None:
searching = False
elif result.__class__ == Node:
node = result
count += 1
if count > self.maxsearch: #just incase something goes wrong
searching=False
result = None
raise Exception("Max Search depth limit reached")
return result
def trace(frame, event, arg):
print "%s, %s:%d" % (event, frame.f_code.co_filename, frame.f_lineno)
return trace
Thats the octree code I've been using. I got it off a website, which was pretty neat. It works great for removing the cubes on the inside. Though it makes it just a hollow box, which is pretty weird. Though it does drastically improve the FPS. For rendering cubes I use this little class.
class Cube(object):
def __init__(self, position, color,tree):
self.position = position
self.x = position[0]
self.y = position[1]
self.z = position[2]
self.color = color
self.tree = tree
num_faces = 6
vertices = [ (0.0, 0.0, 1.0),
(1.0, 0.0, 1.0),
(1.0, 1.0, 1.0),
(0.0, 1.0, 1.0),
(0.0, 0.0, 0.0),
(1.0, 0.0, 0.0),
(1.0, 1.0, 0.0),
(0.0, 1.0, 0.0) ]
normals = [ (0.0, 0.0, +1.0), # front
(0.0, 0.0, -1.0), # back
(+1.0, 0.0, 0.0), # right
(-1.0, 0.0, 0.0), # left
(0.0, +1.0, 0.0), # top
(0.0, -1.0, 0.0) ] # bottom
vertex_indices = [ (0, 1, 2, 3), # front
(4, 5, 6, 7), # back
(1, 5, 6, 2), # right
(0, 4, 7, 3), # left
(3, 2, 6, 7), # top
(0, 1, 5, 4) ] # bottom
def render(self):
glColor( self.color )
# Adjust all the vertices so that the cube is at self.position
vertices = [tuple(Vector3(v) + self.position) for v in self.vertices]
# Draw all 6 faces of the cube
glBegin(GL_QUADS)
for face_no in xrange(self.num_faces):
glNormal3dv( self.normals[face_no] )
v1, v2, v3, v4 = self.vertex_indices[face_no]
glVertex( vertices[v1] )
glVertex( vertices[v2] )
glVertex( vertices[v3] )
glVertex( vertices[v4] )
glEnd()
def getneighbors(self):
x = self.x
y = self.y
z = self.z
return ((x,y,z+1),(x+1,y,z),(x,y+1,z),(x-1,y,z),(x,y-1,z),(x,y,z-1))
def checkneighbors(self):
if not self.tree:
return True
positions = self.getneighbors()
for pos in positions:
result = self.tree.search(pos)
if not result:
return True
return False
I can get about 30FPS with this code. I think there is about 62,210 squares on the screen. I get usually around 30-40 FPS (which isn't bad.)
The first thing I would say is: Don't use objects to represent cubes. It sounds like the right thing to do, but I don't think iterating through millions of objects and executing methods on each of those is going to perform too greatly on python.
I would recommend using 3d Numpy arrays to store this (voxel[x][y][z] = color). That's would be an improvement in memory consumption. You can more information here
After you do that and rewrite your algorithms to use an array instead of many objects, the next thing you need to realize is that you don't need to render every voxel you have. You only need to render those that are visible. That means pushing much less polygons to the GPU.
Think about it... if you have a cube of 10x10x10 voxels, right now you are writing 6000 quads. If you grow the cube to be of 100x100x100 voxels you will be writing 6000000 quads. This gets quickly out of control.
You only need to push those voxels that are going to be visible to the user. In the 10x10x10 voxels case you only need to push the "outer" ones. That's 486 voxels (9x9x6), which means 2916 quads if you write every face of each of the voxels. And for the 100x100x100 case you only need 58806 voxels (99x99x6), that's 352836 quads (that's 6% the number of quads you wrote on the first case). The rest of the voxels are hidden behind the outer ones.
Optimizations don't end here, really. But I think these small optimizations will allow you to render many more voxels than you are currently rendering. There are many more advanced optimizations, you could read here if you want to learn more about it.

Categories