How do people render so many voxels at once? - python

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.

Related

maximum heap priority queue python

For school i have to make an assignment -->
"The city of Amsterdam wants to store the maximum values of the past few years for research
purposes. It is important that the current maximum measured value can be accessed very quickly.
One idea to fulfill this requirement is to use a priority queue. Your job is to implement a priority
queue with a maximum heap and return again a tuple of the current maximal measurement and
its corresponding date when the maximum occurred. Output: date,covid level"
The program takes as input:
(string)’yyyy-mm-dd’, (int)sensor id, (int)covid level.
The expected output is: yyyy-mm-dd,covid level.
Input: 2022−09−08, 23, 371; 2022−09−08, 2, 3171; 2022−09−08, 12, 43; 2021−03−21, 4, 129
Output: 2022 −09 −08, 3171
I have provided my code below. When creating a max heap, the max element should be the first element (root). A Max-Heap is a complete binary tree in which the value in each internal node is greater than or equal to the values in the children of that node, though when inserting the tuples the nodes do not get sorted. My output is very strange, i do not understand where it comes from. When putting in the above input, this is my output:
1.1.1977, 9223372036854775807
could somebody help me? what piece of code am i missing, i have gone over it so many times.
import sys
class MaxHeap:
def __init__(self, maxsize):
self.maxsize = maxsize
self.size = 0
self.Heap = [0] * (self.maxsize + 1)
self.Heap[0] = ('1.1.1977', sys.maxsize)
self.FRONT = 1
# Function to return the position of
# parent for the node currently
# at pos
def parent(self, pos):
return pos // 2
# Function to return the position of
# the left child for the node currently
# at pos
def leftChild(self, pos):
return 2 * pos
# Function to return the position of
# the right child for the node currently
# at pos
def rightChild(self, pos):
return (2 * pos) + 1
# Function that returns true if the passed
# node is a leaf node
def isLeaf(self, pos):
if pos >= (self.size // 2) and pos <= self.size:
return True
return False
# Function to swap two nodes of the heap
def swap(self, fpos, spos):
self.Heap[fpos], self.Heap[spos] = (self.Heap[spos],
self.Heap[fpos])
# Function to heapify the node at pos
def maxHeapify(self, pos):
if not self.isLeaf(pos):
if (self.Heap[pos] < self.Heap[self.leftChild(pos)] or
self.Heap[pos] < self.Heap[self.rightChild(pos)]):
if (self.Heap[self.leftChild(pos)] >
self.Heap[self.rightChild(pos)]):
self.swap(pos, self.leftChild(pos))
self.maxHeapify(self.leftChild(pos))
else:
self.swap(pos, self.rightChild(pos))
self.maxHeapify(self.rightChild(pos))
# Function to insert a node into the heap
def insert(self, element):
if self.size >= self.maxsize:
return
self.size += 1
self.Heap[self.size] = element
current = self.size
while (self.Heap[current] >
self.Heap[self.parent(current)]):
self.swap(current, self.parent(current))
current = self.parent(current)
# Function to print the contents of the heap
def Print(self):
for i in range(1, (self.size // 2) + 1):
print(i)
print("PARENT : " + str(self.Heap[i]) +
"LEFT CHILD : " + str(self.Heap[2 * i]) +
"RIGHT CHILD : " + str(self.Heap[2 * i + 1]))
# Function to remove and return the maximum
# element from the heap
def extractMax(self):
extraction = self.Heap[self.FRONT]
self.Heap[self.FRONT] = self.Heap[self.size]
self.size -= 1
self.maxHeapify(self.FRONT)
return extraction
# Driver Code
if __name__ == "__main__":
input = input()
input = input.split(";")
dates = []
values = []
for d in input:
date = d.split(',', 2)
dates.append(date[0])
values.append(date[2])
values = [int(x) for x in values]
tuples = list(zip(dates, values))
heap = MaxHeap(len(tuples) + 1)
# print(tuples)
for t in tuples:
heap.insert(t)
print(t)
print(heap.extractMax())

Implementing Breadth first search

Your friend bought you a present for the New Year, it's a puzzle! The puzzle consists of a number of
wooden rectangular pieces of varying lengths and widths and a board. The goal is to position the
wooden pieces on the board in a way such that all of the pieces will fit.
I have this program and I need help fixing my breadth first search algorithm.
Right now it is very slow and using a lot of memory. I think it is because I deep copy multiple times. The solve function is the main function and will do the heavy work.
I added a text file that has the first line as the dimensions of the puzzle and the rest of the lines are pieceID, pieceWidth and pieceLength respectively.
This is the Input File. Thank you so much.
10,10
1,10,1
2,1,10
3,1,5
4,3,5
5,20,2
6,1,5
7,1,5
8,2,5
import argparse, copy
import queue
import copy
import numpy as np
class PuzzleBoard():
def __init__(self, board_length, board_width ):
self.l = board_length
self.w = board_width
self.state = [[0 for _ in range(board_width)] for _ in range(board_length)]
self.used_piece = []
# Input: point - tuple cotaining (row_index, col_index) of point in self.state
# Returns true if point is out of bounds; otherwise, returns false
def __out_of_bounds(self, point):
# TODO: Implement this function
if(point < 0 or point > (len(self.state)) or (point > (self.state[0]))):
return True
return False
# Finds the next available open space in the PuzzleBoard (looking from the top-left in row-major order)
def __next(self):
for i in range(len(self.state)) :
for j in range(len(self.state[0])):
if (self.state[i][j] == 0):
return (i, j)
return False
# Input: piece - PuzzlePiece object
# Check if piece fits in the next available space (determined by __next method above)
def fits(self, piece):
position = self.__next()
if not position:
return False
#TODO: Check if any part of the piece is out of bounds
#if piece will be out bounds when place rotate to see if that helps
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
piece.rotate()
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
return False
#TODO: Check if piece can be placed without intersecting another placed piece
return True
# Input: piece - PuzzlePiece object
# Insert piece into the next available position on the board and update state
def place(self, piece):
# TODO: Bug in this function. Pieces not being placed correctly.
position = self.__next()
if self.fits(piece):
for i in range(position[0], position[0] + piece.w ):
for j in range(position[1], position[1] + piece.l):
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
return
if(self.state[i][j]== 0):
#self.used_piece.append(piece)
self.state[i][j] = piece.id
else:
continue
return position
def check(self, piece):
position = self.__next()
if(position[0] + piece.w > self.w or position[1] + piece.l > self.l):
return False
return True
# Returns whether the board has been filledwith pieces
def completed(self):
return True if not self.__next() else False
def copy(self):
copied = PuzzleBoard(self.l, self.w)
copied.state = copy.deepcopy(self.state)
return copied
class PuzzlePiece():
def __init__(self, pid, length, width):
self.id = pid
self.l = length
self.w = width
itfits = False
def rotate(self):
#TODO: Bug in this function. Pieces are not rotating correctly
temp = self.l
self.l = self.w
self.w = temp
def orientation(self):
return "H" if self.w >= self.l else "V"
def __str__(self):
return f"ID: {self.id}, LENGTH: {self.l}, WIDTH: {self.w}, ROTATED: {self.rotated}"
def parse_input(filepath) :
#TODO: Bug in this function. Error raised when called
parsed = {'board' : {}, 'pieces' : {}}
with open(filepath, 'r') as f:
file_contents = f.read().strip().split("\n")
board_length, board_width = file_contents[0].strip().split(",")
parsed['board']['length'] = int(board_length)
parsed['board']['width'] = int(board_width)
for i in range(1, len(file_contents)):
#FIX: the issue was fix
pid, l, w = file_contents[i].strip().split(",")
pid, l, w = int(pid), int(l), int(w)
parsed['pieces'][pid] = {}
parsed['pieces'][pid]['length'] = l
parsed['pieces'][pid]['width'] = w
return parsed
def helper(board, piece):
unused = []
#for piece in pieces:
if board.fits(piece):
position = board.place(piece)
board.used_piece.append((piece, position))
return board
def solve(board, remaining, used_pieces=[]):
# TODO: Implement a solution for a variable amount of pieces and puzzle board size.
# HINT: Recursion might help.7
poss = queue.Queue()
poss.put(board)
currboard = PuzzleBoard(len(board.state), len(board.state[0]))
while not currboard.completed():
currboard = poss.get()
#print(currboard.state)
for piece in remaining:
fakeboard = copy.deepcopy(currboard)
if(not (piece.id in np.array(fakeboard.state))):
#if( fakeboard.check(piece)):
poss.put(helper(fakeboard, piece))
print("Suff done")
return currboard
'''if(len(remaining) != 0):
board, used_pieces, unused_pieces = helper(board, remaining, used_pieces)
if board.completed():
return board, used_pieces
for i in board.state:
print(i)
print("\n \n")
return solve(board, unused_pieces, used_pieces)
return board'''
def main():
#TODO: Bug in this function. Positions are not correct after solution is found.
parser = argparse.ArgumentParser()
parser.add_argument('input')
args = parser.parse_args()
parsed = parse_input(args.input)
board = PuzzleBoard(parsed['board']['length'], parsed['board']['width'])
pieces = []
for k, v in parsed['pieces'].items():
pieces.append(PuzzlePiece(k, v['length'], v['width']))
solved = solve(board, pieces)
if not solved:
print("No solution found for given input.")
else:
print("Solution found.")
board = solved
for u, position in solved.used_piece:
print(f"Piece ID: {u.id}, Position:{position}, Orientation: {u.orientation()}")
if __name__ == "__main__":
main()

Issue with friction and collision for a simple 2D physics engine

I need to code a 2d physics engine (for the moment without rotation) with a model of a wheel (here : one non-rotating Disc with small discs attached to it with springs in a circle to simulate the tyre).
It worked quite well until now (given that I choose a short enough time step), but now I have to add friction (it can be full friction : no relative speed between the tyre and the floor).
So when I'm computing the collisions, I want to know the speed BEFORE the acceleration due to forces. So instead of (Forces)>(Collisions)>(Change speed from acceleration)>(Update position),
I used (Forces)>(Change speed from acceleration)>(Collisions)>(Update position).
But then, no matter the time step, I have strange results, especially when colliding.
I could maybe have friction with the first order of steps, but it will be more complicated I guess.
In the code here, I tried to focus on the main things (but it's not THAT minimal either), so I removed friction for example, since the problem seems to be in the order of my steps.
In the tkinter window, there are several time steps available if you want to test (for example the first one completely fails).
Thanks in advance
PS : I know the springs are very strong (k = 1e7), that sould be a wheel.
import numpy as np
import math as m
import random as rd
import tkinter as tk
import time
def CC2(coords,size=500,zoom=160,offset=[100,100]):#Change x,y coordinates into canvas coordinates
x = int(coords[0]*zoom+offset[0])
y = int((size-coords[1]*zoom)-offset[1])
return x,y
def CC4(coords):#Change from (x1,y1),(x2,y2)
return CC2(coords[0]),CC2(coords[1])
def normalize(vec):#Normalize the vector
return (1/norm(vec))*vec
def norm(vec):#Norm of the vector
return m.sqrt(sum(vec**2))
def sqnorm(vec):#Square norm
return sum(vec**2)
class Scene:
def __init__(self,objectlist,canvas):
self.can = canvas
self.objects = objectlist
self.time = 0#Scene timer
g = 9.81
self.gravity = np.array([0,-g])
def makeStep(self,dt=0.01,display = True):
#Acceleration from gravity
for obj in self.objects:
if obj.invmass != 0:
obj.accel = self.gravity.copy()
#Get accelerations from other forces (here : spring joints)
for obj in self.objects:
if obj.invmass != 0:
#From special joints i.e. spring joints
for joint in obj.joints:#Joint → Force
j = joint.objId
o1 = self.objects[j]
force = joint.getForce(o1,obj)
o1.accel += o1.invmass*force
obj.accel -= obj.invmass*force
"""
Works quite well when the following loop is AFTER the collisions
But in order to add (full) friction properly I wanted to know the speed AFTER applying the forces hence the acceleration
(I can maybe do otherwise but it's more complicated and might not work either...)
"""
#Change speeds from acceleration
for obj in self.objects:
obj.accelerate(dt)
#Apply collisions and change speeds
self.findCollisions(dt)
#Move objects
for obj in self.objects:
obj.move(dt)
if display:
self.display()
self.time += dt
def play(self,dt=0.0001,total_time=5,get_energies=False):#Play the simulation (dt is the time step)
realtime = time.time()
starting_time=realtime
last_display = realtime
while self.time-starting_time <= total_time:
#Just for display
display = False
if time.time()-last_display >= 0.1:
display = True
last_display = time.time()
#Next step
self.makeStep(dt,display)
def findCollisions(self,dt):#Find all collisions, get normal vectors from getCollision and call resolveCollision
n = len(self.objects)
for i in range(n):
o2 = self.objects[i]
joints = o2.joints
for j in range(i):# j<i
o1 = self.objects[j]#Objects 1 & 2
if o1.classCollide(o2):#Classes compatible for collision
if o1.bboxIntersect(o2):
normal = self.getCollision(o1,o2)
self.resolveCollision(o1,o2,normal)#Resolve collision
def resolveCollision(self,o1,o2,normal):#Change speed and position to resolve collision
if normal.any():#normal is not 0,0 (collision)
depth = norm(normal)
normal = 1/depth*normal
relative_speed = o2.speed - o1.speed
normal_speed = relative_speed # normal#Norm of projection of relative speed
total_invmass = o1.invmass + o2.invmass#Sum of inverse masses
if normal_speed > 0:#Real collision:
e=1
coef = (1+e)*normal_speed
o1.speed += coef*(o1.invmass/total_invmass)*normal
o2.speed += -coef*(o2.invmass/total_invmass)*normal
if 0.001<depth:#Positional correction
correction = 0.2*depth/total_invmass*normal
o1.center += o1.invmass*correction
o2.center -= o2.invmass*correction
def getCollision(self,o1,o2,display=False):#Intersection between objects with intersecting bbox: returns normal vector with norm = penetration depth (directed towards o1)
if o1.type == "box" and o2.type == "box":
delta = o2.center-o1.center
dim_sum = o1.dimensions+o2.dimensions#Sum of half-widths and heights
dsides = [delta[0]+dim_sum[0],-delta[0]+dim_sum[0],delta[1]+dim_sum[1],-delta[1]+dim_sum[1]]#Left, right, bottom, top, bottom, left, right of o1
imin = np.argmin(dsides)
if imin == 0:#Left
normal = np.array([dsides[0],0])#Orientation : right = positive
elif imin == 1:#Right
normal = np.array([-dsides[1],0])
elif imin == 2:#Bottom
normal = np.array([0,dsides[2]])
else:#Top
normal = np.array([0,-dsides[3]])
return normal
if o1.type == "disc":
return o1.getCollisionVector(o2)
if o2.type == "disc":
return -o2.getCollisionVector(o1)
def display(self):#Just display the scene
self.can.delete('all')
for obj in self.objects:
color = "yellow"
if obj.type == "box":
if obj.invmass==0:#Unmoveable
color = "black"
can.create_rectangle(CC4(obj.bbox()),fill=color)
if obj.type == "disc":
can.create_oval(CC4(obj.bbox()),fill="springgreen")
for joint in obj.joints:
can.create_line(CC2(obj.center),CC2(self.objects[joint.objId].center+joint.offset),dash=(3,2))
fen.update()
## Objects
class Object2D:#Abstract class for circles and boxes
def bboxIntersect(self,object2):#Intersection of bounding boxes
bbox1 = self.bbox()
bbox2 = object2.bbox()
if (bbox1[1][0]<bbox2[0][0] or bbox1[0][0]>bbox2[1][0]):#No intersecting on x axis
return False
if (bbox1[1][1]<bbox2[0][1] or bbox1[0][1]>bbox2[1][1]):#No intersecting on y axis
return False
return True
def move(self,dt):
if self.invmass == 0:
return None
self.center += dt*self.speed
def accelerate(self,dt):
if self.invmass == 0:
return None
self.speed += self.accel*dt
def classCollide(self,obj):
if (self.cls == "nc1" or obj.cls == "nc1"):#No collision at all
return False
if (self.cls == "nc2" and obj.cls == "nc2"):#No collision inside this class
return False
return True
class Box(Object2D):
def __init__(self,mass,center,width,height,initspeed=[0.0,0.0],joints=[],cls=""):
self.invmass = 1/mass
self.center = np.array(center,dtype=float)#x,y
self.hheight = height/2#Half height
self.hwidth = width/2
self.dimensions=np.array([self.hwidth,self.hheight])
self.speed = np.array(initspeed,dtype=float)#Initial speed (x,y)
self.accel = np.zeros(2)#x,y acceleration
self.type = "box"
self.joints = joints
self.cls=cls
def bbox(self):
return (self.center[0]-self.hwidth,self.center[1]-self.hheight),(self.center[0]+self.hwidth,self.center[1]+self.hheight)
class Disc(Object2D):
def __init__(self,mass,center,radius,initspeed=[0.0,0.0],joints = [],cls=""):
self.invmass = 1/mass
self.center = np.array(center,dtype=float)#x,y
self.radius = radius
self.speed = np.array(initspeed,dtype=float)#Initial speed (x,y)
self.accel = np.zeros(2)#x,y acceleration
self.type = "disc"
self.joints = joints
self.cls=cls
def bbox(self):
return (self.center[0]-self.radius,self.center[1]-self.radius),(self.center[0]+self.radius,self.center[1]+self.radius)
def getCollisionVector(self,obj):
if obj.type == "box":#VS BOX
box = obj
bbox = box.bbox()
delta = self.center-box.center
if (bbox[0][0] <= self.center[0] <= bbox[1][0]):#Vertical collision
return np.sign(delta[1])*np.array([0,self.radius+box.hheight-abs(delta[1])])
if (bbox[0][1] <= self.center[1] <= bbox[1][1]):#Horizontal collision
return np.sign(delta[0])*np.array([self.radius+box.hwidth-abs(delta[0]),0])
#else find closest corner
if delta[1] > 0:#Top
if delta[0] > 0:#Right
delta_corner = self.center - (box.center+box.dimensions)
else:#Left
delta_corner = self.center - (box.center+np.array([-box.hwidth,box.hheight]))
else:#Bottom
if delta[0] > 0:#Right
delta_corner = self.center - (box.center+np.array([box.hwidth,-box.hheight]))
else:#Left
delta_corner = self.center - (box.center-box.dimensions)
distance = norm(delta_corner)
if distance > self.radius:#No collision
return np.zeros(2)
return (self.radius-distance)/distance*delta_corner
elif obj.type == "disc":#VS DISC
delta = self.center - obj.center
norm_delta = norm(delta)
depth = self.radius + obj.radius - norm_delta
if depth > 0:#Collision
return depth*normalize(delta)
return np.zeros(2)
class Floor(Box):
def __init__(self,y,xmin=-500,xmax=500):
self.invmass = 0#Infinite mass
self.y = y
self.hwidth = (xmax-xmin)/2
self.hheight = 50
self.dimensions=np.array([self.hwidth,self.hheight])
self.center = np.array([(xmin+xmax)/2,y-50])
self.type = "box"
self.accel = np.zeros(2)
self.speed = np.zeros(2)
self.joints = []
self.cls=""
## Forces & joints
class SpringJoint:
def __init__(self,objId,k,l0,damper=10,offset=[0,0]):
self.objId = objId
self.l0 = l0
self.k = k
self.offset = np.array(offset)
self.damper = damper
def getForce(self,o1,o2):
delta = o2.center - (o1.center+self.offset)
normal = normalize(delta)
diff = delta - self.l0*normal
delta_speed = o2.speed - o1.speed
return self.k*diff + self.damper*delta_speed#normal*normal
## Objects definitions
#Test wheel with spring : generates a "wheel" model
def getWheel(Radius,IntRadius,IntMass,ExtMass,kr,ks,x=0,y=0.5,n=14,initspeed=[0,0]):
arc = 2*m.pi*Radius/n
r = 0.35*arc
l0s = 2*(Radius-r)*m.sin(m.pi/n)
R = IntRadius - r
l0r = Radius - r
core = Disc(IntMass,[x,y],R,initspeed=initspeed)
tyre= []
for k in range(n):
a = k/n*2*m.pi
tyre.append(Disc(ExtMass/n,[x+l0r*m.cos(a),y+l0r*m.sin(a)],r,joints=[SpringJoint(0,kr,l0r),SpringJoint(k%n,ks,l0s)],cls="nc2"))
#Discs from the outside don't interact with each other except with the spring joints
tyre[-1].joints.append(SpringJoint(1,ks,l0s))
del tyre[0].joints[1]
return [core] + tyre
#Objects in the scene
#☺Simple wheel with n=5
objects = getWheel(0.5,0.25,500,1,1e7,1e7,y=0.5,initspeed=[5,0],n=5) + [Floor(0)]
## Scene
fen = tk.Tk()
can = tk.Canvas(fen,width = 1000,height=500)
can.pack()
scene = Scene(objects,can)
scene.display()
tk.Button(fen,text="Go quick (10**-3 s)",command = lambda : scene.play(0.001,3,get_energies)).pack()
tk.Button(fen,text="Go medium (10**-4)",command = lambda : scene.play(0.0001,3,get_energies)).pack()
tk.Button(fen,text="Go slowly (3*10**-5)",command = lambda : scene.play(0.00003,1,get_energies)).pack()
tk.Button(fen,text="Go very slowly (10**-5)",command = lambda : scene.play(0.00001,1,get_energies)).pack()
tk.Button(fen,text="Do 0.01s",command = lambda : scene.play(0.0001,0.01,get_energies)).pack()
tk.Button(fen,text="Do 1 step",command = lambda : scene.play(0.01,0.01,get_energies)).pack()
fen.mainloop()
Edit: misunderstood the question.
Would it help to have the move step before the collision step? Movement should happen right after acceleration.
Try to calculate the acceleration before the collision in order to get frictional forces without ever applying it to the objects.
Eventually I kept the original order and found another way to implement friction, so now it works quite well

Faster than if-elif statement

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))

Crop a collection of lines using another collection of lines

First of all, here is a quick graphical description of what I intend to do. I will use Python, but feel free to use pseudo code in your answers.
I have 2 collections of 2D segments, stored as the following: [ [start_point, end_point], [...] ].
For the first step, I have to detect each segment of the blue collection which is colliding with the black collection. For this I use LeMothe's line/line intersection algorithm.
Then comes my first problem: Having a segment AB which intersects with my line in C, I don't know how to determine using code if I have to trim my segment as AC or as CB , ie: I don't know which part of my segment I need to keep and which one I need to remove.
Then for the second step, I have really no ideas how to achieve this.
Any help would be greatly appreciated, so thank you in advance!
The second step is trivial once you figure what to keep and what not, you just need to keep track of the segments you clipped and see where they were originally joined (e.g. assume that the segments are in order and form a connected line).
On the other hand, given that your black line is in fact a line and not a polygon, in your first step, choosing what is "outside" and what is "inside" seems completely arbitrary; is it possible to close that into a polygon? Otherwise, you may need to artificially create two polygons (one for each side of the line) and then do clipping inside those polygons. You could use something like the Cyrus and Beck line clipping algorithm (see this tutorial for an overview: https://www.tutorialspoint.com/computer_graphics/viewing_and_clipping.htm)
Feel free to use any of the code below as a starting point (you have an intersect function and some useful classes). Implements Sutherland and Hodgman.
class Point2(object):
"""Structure for a 2D point"""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __copy__(self):
return self.__class__(self.x, self.y)
copy = __copy__
def __repr__(self):
return 'Point2(%d, %d)' % (self.x, self.y)
def __getitem__(self, key):
return (self.x, self.y)[key]
def __setitem__(self, key, value):
l = [self.x, self.y]
l[key] = value
self.x, self.y = l
def __eq__(self, other):
if isinstance(other, Point2):
return self.x == other.x and \
self.y == other.y
else:
assert hasattr(other, '__len__') and len(other) == 2
return self.x == other[0] and \
self.y == other[1]
def __ne__(self, other):
return not self.__eq__(other)
def __nonzero__(self):
return self.x != 0 or self.y != 0
def __len__(self):
return 2
class Line2(object):
"""Structure for a 2D line"""
def __init__(self,pt1,pt2):
self.pt1,self.pt2=pt1,pt2
def __repr__(self):
return 'Line2(%s, %s)' % (self.pt1, self.pt2)
class Polygon2(object):
def __init__(self,points):
self.points = points
def __repr__(self):
return '[\n %s\n]' % '\n '.join([str(i) for i in self.points])
def lines(self):
lines = []
e = self.points[-1].copy()
for p in self.points:
lines.append(Line2(e,p))
e = p.copy()
return lines
#return [Line2(a,b) for a,b in zip(self.points,self.points[1:]+[self.points[0]])]
def __copy__(self):
return self.__class__(list(self.points))
copy = __copy__
class Renderer(object):
"""Rendering algorithm implementations"""
def __init__(self,world,img,color=1):
self.world,self.img,self.color=world,img,color
def transform(self,s,r,m,n):
"""Homogeneous transformation operations"""
for i in self.world.points():
j = Matrix3.new_translate(m, n)*Matrix3.new_rotate(r)*Matrix3.new_scale(s)*i
i.x,i.y = j.x,j.y
def clip(self,a,b,c,d):
"""Clipping for the world window defined by a,b,c,d"""
self.clip_lines(a, b, c, d)
self.clip_polygons(a, b, c, d)
def shift(self,a,b,c,d):
"""Shift the world window"""
for i in self.world.points():
i.x -= a
i.y -= b
def clip_lines(self,a,b,c,d):
"""Clipping for lines (i.e. open polygons)"""
clipped = []
for i in self.world.lines:
clipped += [self.clip_lines_cohen_sutherland(i.pt1, i.pt2, a, b, c, d)]
self.world.lines = [i for i in clipped if i]
def clip_polygons(self,a,b,c,d):
"""Clipping for polygons"""
polygons = []
for polygon in self.world.polygons:
new_polygon = self.clip_polygon_sutherland_hodgman(polygon, a, b, c, d)
polygons.append(new_polygon)
self.world.polygons = polygons
def clip_polygon_sutherland_hodgman(self,polygon,xmin,ymin,xmax,ymax):
edges = [Line2(Point2(xmax,ymax),Point2(xmin,ymax)), #top
Line2(Point2(xmin,ymax),Point2(xmin,ymin)), #left
Line2(Point2(xmin,ymin),Point2(xmax,ymin)), #bottom
Line2(Point2(xmax,ymin),Point2(xmax,ymax)), #right
]
def is_inside(pt,line):
# uses the determinant of the vectors (AB,AQ), Q(X,Y) is the query
# left is inside
det = (line.pt2.x-line.pt1.x)*(pt.y-line.pt1.y) - (line.pt2.y-line.pt1.y)*(pt.x-line.pt1.x)
return det>=0
def intersect(pt0,pt1,line):
x1,x2,x3,x4 = pt0.x,pt1.x,line.pt1.x,line.pt2.x
y1,y2,y3,y4 = pt0.y,pt1.y,line.pt1.y,line.pt2.y
x = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))
y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))
return Point2(int(x),int(y))
polygon_new = polygon.copy()
for edge in edges:
polygon_copy = polygon_new.copy()
polygon_new = Polygon2([])
s = polygon_copy.points[-1]
for p in polygon_copy.points:
if is_inside(s,edge) and is_inside(p,edge):
polygon_new.points.append(p)
elif is_inside(s,edge) and not is_inside(p,edge):
polygon_new.points.append(intersect(s,p,edge))
elif not is_inside(s,edge) and not is_inside(p,edge):
pass
else:
polygon_new.points.append(intersect(s,p,edge))
polygon_new.points.append(p)
s = p
return polygon_new
def clip_lines_cohen_sutherland(self,pt0,pt1,xmin,ymin,xmax,ymax):
"""Cohen-Sutherland clipping algorithm for line pt0 to pt1 and clip rectangle with diagonal from (xmin,ymin) to (xmax,ymax)."""
TOP = 1
BOTTOM = 2
RIGHT = 4
LEFT = 8
def ComputeOutCode(pt):
code = 0
if pt.y > ymax: code += TOP
elif pt.y < ymin: code += BOTTOM
if pt.x > xmax: code += RIGHT
elif pt.x < xmin: code += LEFT
return code
accept = False
outcode0, outcode1 = ComputeOutCode(pt0), ComputeOutCode(pt1)
while True:
if outcode0==outcode1==0:
accept=True
break
elif outcode0&outcode1:
accept=False
break
else:
#Failed both tests, so calculate the line segment to clip from an outside point to an intersection with clip edge.
outcodeOut = outcode0 if not outcode0 == 0 else outcode1
if TOP & outcodeOut:
x = pt0.x + (pt1.x - pt0.x) * (ymax - pt0.y) / (pt1.y - pt0.y)
y = ymax
elif BOTTOM & outcodeOut:
x = pt0.x + (pt1.x - pt0.x) * (ymin - pt0.y) / (pt1.y - pt0.y)
y = ymin
elif RIGHT & outcodeOut:
y = pt0.y + (pt1.y - pt0.y) * (xmax - pt0.x) / (pt1.x - pt0.x);
x = xmax;
elif LEFT & outcodeOut:
y = pt0.y + (pt1.y - pt0.y) * (xmin - pt0.x) / (pt1.x - pt0.x);
x = xmin;
if outcodeOut == outcode0:
pt0 = Point2(x,y)
outcode0 = ComputeOutCode(pt0)
else:
pt1 = Point2(x,y)
outcode1 = ComputeOutCode(pt1);
if accept:
return Line2(pt0,pt1)
else:
return False
I think what you'll need to do is find a line from the center of the blue object to the line segment in question. If that new line from the center to the segment AB or BC hits a black line on its way to the blue line segment, then that segment is outside and is trimmed. You would want to check this at a point between A and B or between B and C, so that you don't hit the intersection point.
As for the python aspect, I would recommend defining a line object class with some midpoint attributes and a shape class that's made up of lines with a center attribute, (Actually come to think of it, then a line would count as a shape so you could make line a child class of the shape class and preserve code), that way you can make methods that compare two lines as part of each object.
line_a = Line((4,2),(6,9))
line_b = Line((8,1),(2,10))
line_a.intersects(line.b) #Could return Boolean, or the point of intersection
In my mind that just feels like a really comfortable way to go about this problem since it lets you keep track of what everything's doing.

Categories