Stop GL_TRIANGLE_STRIP from connecting last and first vertex? - python

I have been trying to build a Terrain Visualizer from OpenGL and using a height map from simplex noise. I have the generator all sorted and it produces both colored and non-colored images, I only want to visualized the colored ones. However there is this weird bump thing, that I believe is a result of GL_TRIANGLE_STRIP.
Picture of the artifact, oddities outlined in red:
My best guess is GL_TRIANGLE_STRIP attaching the first and last vertex, but I do not know.
Here is my code:
import pyglet
from pyglet.gl import *
from pyglet.window import key
import math
from PIL import Image
class Model:
def get_points_in_list(self, fp):
img = Image.open(fp)
self.points = [[0 for x in range(img.width)] for y in range(img.height)]
for y in range(img.height):
for x in range(img.width):
self.points[y][x] = ((x, img.getpixel((x, y))[3], y), img.getpixel((x, y)))
def add_points_to_batch(self):
ysiz = len(self.points)
xsiz = len(self.points[0])
# color1[0], color1[1], color1[2], color2[0], color2[1], color2[2]
for yy in range(ysiz-1):
for xx in range(xsiz):
pos = self.points[yy][xx][0]
x, y, z = pos[0], pos[1], pos[2]
pos1 = self.points[yy+1][xx][0]
X, Y, Z = pos1[0], pos1[1], pos1[2]
color1 = (self.points[yy][xx][1][0], self.points[yy][xx][1][1], self.points[yy][xx][1][2])
color2 = (self.points[yy+1][xx][1][0], self.points[yy+1][xx][1][1], self.points[yy+1][xx][1][2])
self.batch.add(2, GL_TRIANGLE_STRIP, None,
('v3f', (x, y, z, X, Y, Z)),
('c3B', (color1[0], color1[1], color1[2], color2[0], color2[1], color2[2]))
)
def __init__(self):
self.batch = pyglet.graphics.Batch()
self.points = None
self.get_points_in_list('Output_colored.png')
self.add_points_to_batch()
def draw(self):
self.batch.draw()
class Player:
def __init__(self, pos=(0, 0, 0), rot=(0, 0)):
self.pos = list(pos)
self.rot = list(rot)
def mouse_motion(self, dx, dy):
dx /= 8
dy /= 8
self.rot[0] += dy
self.rot[1] -= dx
if self.rot[0]>90:
self.rot[0] = 90
elif self.rot[0] < -90:
self.rot[0] = -90
def update(self,dt,keys):
sens = 1
s = dt*100
rotY = -self.rot[1]/180*math.pi
dx, dz = s*math.sin(rotY), math.cos(rotY)
if keys[key.W]:
self.pos[0] += dx*sens
self.pos[2] -= dz*sens
if keys[key.S]:
self.pos[0] -= dx*sens
self.pos[2] += dz*sens
if keys[key.A]:
self.pos[0] -= dz*sens
self.pos[2] -= dx*sens
if keys[key.D]:
self.pos[0] += dz*sens
self.pos[2] += dx*sens
if keys[key.SPACE]:
self.pos[1] += s
if keys[key.LSHIFT]:
self.pos[1] -= s
class Window(pyglet.window.Window):
def push(self,pos,rot):
glPushMatrix()
rot = self.player.rot
pos = self.player.pos
glRotatef(-rot[0],1,0,0)
glRotatef(-rot[1],0,1,0)
glTranslatef(-pos[0], -pos[1], -pos[2])
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluPerspective(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width/self.height, 0.05, 1000)
self.Model()
def setLock(self, state):
self.lock = state
self.set_exclusive_mouse(state)
lock = False
mouse_lock = property(lambda self:self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(300,200)
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
self.model = Model()
self.player = Player((0.5,1.5,1.5),(-30,0))
def on_mouse_motion(self,x,y,dx,dy):
if self.mouse_lock: self.player.mouse_motion(dx,dy)
def on_key_press(self, KEY, _MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.E:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.push(self.player.pos,self.player.rot)
self.model.draw()
glPopMatrix()
if __name__ == '__main__':
window = Window(width=400, height=300, caption='Terrain Viewer', resizable=True)
glClearColor(0, 0, 0, 1)
glEnable(GL_DEPTH_TEST)
pyglet.app.run()

Related

Pyglet texture doesn't cover the square

Im programming a simple pyglet example in pyglet 1.3.0 but i have a problem. I have also tested other pyglet versions but the problem still there.
No error is displayed but the texture only appears in a part of the square. I'm using python 3.7.
Here is the code:
from pyglet.gl import *
from pyglet.window import key
import resources
import math
class Model:
def get_tex(self, file):
tex = pyglet.image.load(file).texture
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
return pyglet.graphics.TextureGroup(tex)
def __init__(self):
self.top = self.get_tex("grass_tex.jpg")
self.bottom = self.get_tex("grass_tex.jpg")
self.side = self.get_tex("terrain_tex.jpg")
self.batch = pyglet.graphics.Batch()
tex_coords = ("t2f", (0,0, 1,0, 1,1, 0,1, ))
#color = ("c3f", (1, 1, 1)*4)
x, y, z = 0, 0, -1
X, Y, Z = x+1, y+1, z+1
self.batch.add(4, GL_QUADS, self.top, ("v3f", (x,y,z, X,y,z, X,Y,z, x,Y,z, )), tex_coords)
def draw(self):
self.batch.draw()
class Player:
def __init__(self):
self.pos = [0, 0, 0]
self.rot = [0, 0]
def update(self, dt, keys):
pass
class Window(pyglet.window.Window):
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluOrtho2D(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width/self.height, 0.05, 1000)
self.Model()
def setLock(self, state):
self.lock = state
self.set_exclusive_mouse(state)
lock = False
mouse_lock = property(lambda self:self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model = Model()
self.player = Player()
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
def on_key_press(self, KEY, MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.SPACE:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.model.draw()
if __name__ == "__main__":
window = Window(width=400, height=300, caption="gamelearn2", resizable=False)
glClearColor(0.5, 0.7, 1, 1)
pyglet.app.run()
And this is the error:
When you use a perspective projection gluPerspective, then the (0, 0) is in the center of the viewport.
You have to draw the quad with the texture from (-1, -1) to (1, 1) and at a depth of 1 (z = -1).
x, y, z = -1, -1, -1
X, Y = 1, 1
self.batch.add(4, GL_QUADS, self.top, ("v3f", (x,y,z, X,y,z, X,Y,z, x,Y,z, )), tex_coords)
Furthermore use a field of view angle of 90°:
class Window(pyglet.window.Window):
# [...]
def set3d(self):
self.Projection()
gluPerspective(90, self.width/self.height, 0.05, 1000)
self.Model()

Pygame gfxdraw program [duplicate]

This question already has answers here:
Create trails of particles for the bullets
(1 answer)
Pygame change particle color
(1 answer)
Closed 5 months ago.
I am a beginner with pygame. I want to include a few "Emitter" classes to "Particle" class to show points. It doesn't work and I don't know how to fix it. I think the problem is with gfxdraw. In the "Emitter" class, it initializes the system window. This can also be a problem. I don't know how to transfer this to the "Particle" class. Would it help at all? I used many combinations for repair. Nothing works. Please help. Does anyone have any idea?
import random
import sys
import pygame
from pygame.locals import *
import pygame.gfxdraw
class ParticleSystem:
def __init__(self, id, pos, a,b, width, height, radius, color):
self.id = id
self.width = width
self.height = height
self.radius = radius
self.color = color
self.g = 9.81
self.pos = [random.randint(a,b), 0]
self.enabled = True
self.df = 0
def update(self, time, collision):
if self.enabled:
v = 1 / float(time)
if not self.collision_detect(collision):
self.pos[1] += self.g*v
else:
self.enabled = False
if self.df != 0:
F = (self.g*v)/9
if self.df < 0:
F = -F
self.pos[0] += F
self.df -= F
def collision_detect(self, collision):
x = int(self.pos[0])
y = self.pos[1]
r = self.radius
points = collision[x-r:x+r]
for p in points:
if y + r >= p:
for i in range(x-r, x+r):
if i >= 0 and i < self.width:
collision[i] = y
return True
if self.pos[1] >= self.height:
return True
else:
return False
def draw(self, surface):
pygame.gfxdraw.filled_circle(surface, int(self.pos[0]), int(self.pos[1]), self.radius, self.color)
pygame.gfxdraw.aacircle(surface, int(self.pos[0]), int(self.pos[1]), self.radius, self.color)
class Emitter:
def __init__(self, a, b, height, width):
self.a = a
self.b = b
self.timer = 60
self.width = width
self.height = height
self.color = (255, 255, 255)
self.background_color = (0, 0, 0)
self.pos = [0,0]
self.particles = []
self.counter = 0
self.freq = 5
self.size = 4
self.collision = [height] * width
self.df_c = 1
self.df_f = 100
self.begin()
def begin(self):
self.screen = pygame.display.set_mode((self.width, self.height))
self.clock = pygame.time.Clock()
while 1:
time = self.clock.get_time()
self.update(time)
self.clock.tick(self.timer)
self.render(time)
def update(self, time):
if self.counter > self.freq:
self.counter = 0
particle = ParticleSystem(len(self.particles), self.pos, self.a, self.b, self.width, self.height, self.size, self.color)
self.particles.append(particle)
else:
self.counter += 1
df_c = random.randint(0, 100)
df_f = 0
if df_c <= self.df_c:
df_f = random.randint(-self.df_f, self.df_f)
for part in self.particles:
if part.enabled:
if df_f != 0:
part.df = df_f
part.update(time, self.collision)
def render(self, time):
surface = pygame.Surface(self.screen.get_size())
surface.convert()
surface.fill(self.background_color)
for part in self.particles:
part.draw(surface)
self.screen.blit(surface, (0, 0))
pygame.display.flip()
class Particle:
def __init__(self, width, height, caption):
self.width = width
self.height = height
self.caption = caption
self.initialization()
def initialization(self):
pygame.init()
pygame.display.set_caption(self.caption)
#Emitter(0, self.width, self.height, self.width)
Emitter(0, 125, self.height, self.width)
Emitter(200, 500, self.height, self.width) #how to show the second Emitter
self.input(pygame.event.get())
def input(self, events):
for event in events:
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
sys.exit(0)
if __name__ == "__main__":
particle = Particle(800, 600, "Particle System - Christmas Time")

Python: How do I generate minecraft style terrain with pyglet?

I'm currently trying to make a Minecraft clone using pyglet and python for fun, and I found a nice tutorial which I used to create a player class with movement, and a 3D block that generates in the scene.
Now I want to create some sort of a terrain, I read about terrain generating and I stumbled upon a function called "noise function" which seems to fit nicely with one I'm trying to do. sadly I don't really know how to implement it. :(
At first I tried to generate a flat terrain by creating a function in the Model class which contains the code that creates a cube, and then I create a loop that generates all the numbers between 1 and 20 for example, and use them as values to the cube function. but it didn't work so i had to remove it :(
I do think that it might be too soon to implement a terrain with the noise function seeing the stage I'm currently in. so creating an endless flat terrain that do work will be good as well :D
If you want to check the program, You'll need to press 'E' when you run the code to enable a mouse lock, which will let you to move the mouse and the player in the scene.
Here is the code I have:
from pyglet.gl import *
from pyglet.window import key
import math
import random
from random import *
class Model:
def get_tex(self, file):
tex = pyglet.image.load(file).texture
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
return pyglet.graphics.TextureGroup(tex)
def __init__(self):
self.top = self.get_tex('grass_top.png')
self.side = self.get_tex('grass_side.png')
self.bottom = self.get_tex('dirt.png')
self.batch = pyglet.graphics.Batch()
tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1,))
x, y, z = randint(0, 5), randint(0, 5), -1
X, Y, Z = x+1, y+1, z+1
self.batch.add(4, GL_QUADS, self.side, ('v3f', (x, y, z, x, y, Z, x, Y, Z, x, Y, z,)), tex_coords)
self.batch.add(4, GL_QUADS, self.side, ('v3f', (X, y, Z, X, y, z, X, Y, z, X, Y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, self.bottom, ('v3f', (x, y, z, X, y, z, X, y, Z, x, y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, self.top, ('v3f', (x, Y, Z, X, Y, Z, X, Y, z, x, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, self.side, ('v3f', (X, y, z, x, y, z, x, Y, z, X, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, self.side, ('v3f', (x, y, Z, X, y, Z, X, Y, Z, x, Y, Z, )), tex_coords)
def draw(self):
self.batch.draw()
class Player:
def __init__(self, pos=(0, 0, 0), rot=(0, 0)):
self.pos = list(pos)
self.rot = list(rot)
def mouse_motion(self, dx, dy):
dx /= 8
dy /= 8
self.rot[0] += dy
self.rot[1] -= dx
if self.rot[0] > 90:
self.rot[0] = 90
elif self.rot[0] < -90:
self.rot[0] = -90
def update(self, dt, keys):
s = dt*10
rotation_y = -self.rot[1]/180*math.pi
dx, dz = s*math.sin(rotation_y), s*math.cos(rotation_y)
if keys[key.W]:
self.pos[0] += dx
self.pos[2] -= dz
if keys[key.S]:
self.pos[0] -= dx
self.pos[2] += dz
if keys[key.A]:
self.pos[0] -= dz
self.pos[2] -= dx
if keys[key.D]:
self.pos[0] += dz
self.pos[2] += dx
if keys[key.SPACE]:
self.pos[1] += s
if keys[key.LSHIFT]:
self.pos[1] -= s
class Window(pyglet.window.Window):
def push(self, pos, rot):
glPushMatrix()
glRotatef(-rot[0], 1, 0, 0)
glRotatef(-rot[1], 0, 1, 0)
glTranslatef(-pos[0], -pos[1], -pos[2],)
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluOrtho2D(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width / self.height, 0.05, 1000)
self.Model()
def setLock(self, state): self.lock = state; self.set_exclusive_mouse(state)
lock = False; mouse_lock = property(lambda self: self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(300, 200)
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
self.model = Model()
self.player = Player((0.5, 1.5, 1.5), (-30, 0))
def on_mouse_motion(self, x, y, dx, dy):
if self.mouse_lock:
self.player.mouse_motion(dx, dy)
def on_key_press(self, KEY, MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.E:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.push(self.player.pos, self.player.rot)
self.model.draw()
glPopMatrix()
if __name__ == '__main__':
window = Window(width=854, height=480, caption='Minecraft', resizable=True)
glClearColor(0.5, 0.7, 1, 1)
glEnable(GL_DEPTH_TEST)
# glEnable(GL_CULL_FACE)
pyglet.app.run()
My project use's some images to create the dirt texture. so here is a link to a website which has the images and the main program which you can see above you:
http://www.mediafire.com/file/7iolhmh1hqj9516/Basic+Pyglet+Cube.rar
I already tried to generate more then one block using a list and recalling a function
You could create a function to show a block at the given position and with the texture texture:
def show_block(self, position, texture):
top = texture[0]
side = texture[1]
bottom = texture[2]
x, y, z = position
X, Y, Z = x+1,y+1, z+1
tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1,))
self.batch.add(4, GL_QUADS, side, ('v3f', (x, y, z, x, y, Z, x, Y, Z, x, Y, z,)), tex_coords)
self.batch.add(4, GL_QUADS, side, ('v3f', (X, y, Z, X, y, z, X, Y, z, X, Y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, bottom, ('v3f', (x, y, z, X, y, z, X, y, Z, x, y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, top, ('v3f', (x, Y, Z, X, Y, Z, X, Y, z, x, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, side, ('v3f', (X, y, z, x, y, z, x, Y, z, X, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, side, ('v3f', (x, y, Z, X, y, Z, X, Y, Z, x, Y, Z, )), tex_coords)
Then you could use perlin to generate terrain:
class Perlin:
def __call__(self,x,y): return (self.noise(x*self.f,y*self.f)+1)/2
def __init__(self,seed=None):
self.f = 15/512; self.m = 65535; p = list(range(self.m))
if seed: random.seed(seed)
random.shuffle(p); self.p = p+p
def fade(self,t): return t*t*t*(t*(t*6-15)+10)
def lerp(self,t,a,b): return a+t*(b-a)
def grad(self,hash,x,y,z):
h = hash&15; u = y if h&8 else x
v = (x if h==12 or h==14 else z) if h&12 else y
return (u if h&1 else -u)+(v if h&2 else -v)
def noise(self,x,y,z=0):
p,fade,lerp,grad = self.p,self.fade,self.lerp,self.grad
xf,yf,zf = math.floor(x),math.floor(y),math.floor(z)
X,Y,Z = xf%self.m,yf%self.m,zf%self.m
x-=xf; y-=yf; z-=zf
u,v,w = fade(x),fade(y),fade(z)
A = p[X ]+Y; AA = p[A]+Z; AB = p[A+1]+Z
B = p[X+1]+Y; BA = p[B]+Z; BB = p[B+1]+Z
return lerp(w,lerp(v,lerp(u,grad(p[AA],x,y,z),grad(p[BA],x-1,y,z)),lerp(u,grad(p[AB],x,y-1,z),grad(p[BB],x-1,y-1,z))),
lerp(v,lerp(u,grad(p[AA+1],x,y,z-1),grad(p[BA+1],x-1,y,z-1)),lerp(u,grad(p[AB+1],x,y-1,z-1),grad(p[BB+1],x-1,y-1,z-1))))
After all these changes, your code should look somewhat like this:
from pyglet.gl import *
from pyglet.window import key,mouse
from collections import deque
import sys, os, time, math, random
class Perlin:
def __call__(self,x,y): return int(sum(self.noise(x*s,y*s)*h for s,h in self.perlins)*self.avg)
def __init__(self):
self.m = 65536; p = list(range(self.m)); random.shuffle(p); self.p = p+p
p = self.perlins = tuple((1/i,i) for i in (16,20,22,31,32,64,512) for j in range(2))
self.avg = 8*len(p)/sum(f+i for f,i in p)
def fade(self,t): return t*t*t*(t*(t*6-15)+10)
def lerp(self,t,a,b): return a+t*(b-a)
def grad(self,hash,x,y,z):
h = hash&15; u = y if h&8 else x
v = (x if h==12 or h==14 else z) if h&12 else y
return (u if h&1 else -u)+(v if h&2 else -v)
def noise(self,x,y,z=0):
p,fade,lerp,grad = self.p,self.fade,self.lerp,self.grad
xf,yf,zf = math.floor(x),math.floor(y),math.floor(z)
X,Y,Z = xf%self.m,yf%self.m,zf%self.m
x-=xf; y-=yf; z-=zf
u,v,w = fade(x),fade(y),fade(z)
A = p[X ]+Y; AA = p[A]+Z; AB = p[A+1]+Z
B = p[X+1]+Y; BA = p[B]+Z; BB = p[B+1]+Z
return lerp(w,lerp(v,lerp(u,grad(p[AA],x,y,z),grad(p[BA],x-1,y,z)),lerp(u,grad(p[AB],x,y-1,z),grad(p[BB],x-1,y-1,z))),
lerp(v,lerp(u,grad(p[AA+1],x,y,z-1),grad(p[BA+1],x-1,y,z-1)),lerp(u,grad(p[AB+1],x,y-1,z-1),grad(p[BB+1],x-1,y-1,z-1))))
class Model:
alpha_textures = 'leaves_oak','tall_grass'
def load_textures(self):
t = self.texture = {}; self.texture_dir = {}; dirs = ['textures']
while dirs:
dir = dirs.pop(0); textures = os.listdir(dir)
for file in textures:
if os.path.isdir(dir+'/'+file): dirs+=[dir+'/'+file]
else:
n = file.split('.')[0]; self.texture_dir[n] = dir; image = pyglet.image.load(dir+'/'+file)
transparent = n in self.alpha_textures
texture = image.texture if transparent else image.get_mipmapped_texture()
self.texture[n] = pyglet.graphics.TextureGroup(texture)
if not transparent: glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST)
self.block = {}; self.ids = []; done = []
items = sorted(self.texture_dir.items(),key=lambda i:i[0])
for name,dir in items:
n = name.split(' ')[0]
if n in done: continue
done+=[n]
if dir.startswith('textures/blocks'):
self.ids+=[n]
if dir=='textures/blocks': self.block[n] = t[n],t[n],t[n],t[n],t[n],t[n]
elif dir=='textures/blocks/tbs': self.block[n] = t[n+' s'],t[n+' s'],t[n+' b'],t[n+' t'],t[n+' s'],t[n+' s']
elif dir=='textures/blocks/ts': self.block[n] = t[n+' s'],t[n+' s'],t[n+' t'],t[n+' t'],t[n+' s'],t[n+' s']
self.ids+=['water']
flow,still = t['water_flow'],t['water_still']
self.block['water'] = flow,flow,still,still,flow,flow
def draw(self):
glEnable(GL_ALPHA_TEST); self.opaque.draw(); glDisable(GL_ALPHA_TEST)
glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); self.transparent.draw()
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); self.transparent.draw()
def update(self,dt):
self.cubes.water.update(dt)
def __init__(self):
self.opaque = pyglet.graphics.Batch()
self.transparent = pyglet.graphics.Batch()
self.load_textures()
self.cubes = CubeHandler(self)
perlin = Perlin()
for x in range(64):
for z in range(64):
y = perlin(x,z)
self.cubes.add((x,-y,-z),'grass')
for i in range(1,3): self.cubes.add((x,-i-y,-z),'dirt')
for cube in self.cubes.cubes.values(): self.cubes.update_cube(cube)
class Water:
def __init__(self,transparent):
self.transparent = transparent
self.time = {'still':TimeLoop(32),'flow':TimeLoop(32)}
self.coords = {'still':[],'flow':[]}; self.still_faces = {}; self.flow_faces = {}
for i in range(32-1,-1,-1):
y0 = i/16; y1 = (i+1)/16; self.coords['still'] += [[0,y0, 1,y0, 1,y1, 0,y1]]
y0 = i/32; y1 = (i+1)/32; self.coords['flow'] += [[0,y0, 1,y0, 1,y1, 0,y1]]
a,b = self.time['still'],self.time['flow']; self.t = b,b,a,a,b,b
a,b = self.coords['still'],self.coords['flow']; self.c = b,b,a,a,b,b
def update(self,dt):
if self.time['still'].update(dt*0.5):
for face,i in self.still_faces.items(): face.tex_coords = self.c[i][self.t[i].int]
if self.time['flow'].update(dt):
for face,i in self.flow_faces.items(): face.tex_coords = self.c[i][self.t[i].int]
def show(self,v,t,i):
face = self.transparent.add(4,GL_QUADS,t,('v3f',v),('t2f',self.c[i][0]))
faces = self.still_faces if i==2 or i==3 else self.flow_faces
faces[face] = i; return face
class CubeHandler:
def __init__(self,model):
self.model = model
self.opaque,self.transparent = model.opaque,model.transparent
self.block,self.alpha_textures = model.block,model.alpha_textures
self.water = Water(self.transparent)
self.cubes = {}
def hit_test(self,p,vec,dist=256):
if normalize(p) in self.cubes: return None,None
m = 8; x,y,z = p; dx,dy,dz = vec
dx/=m; dy/=m; dz/=m; prev = None
for i in range(dist*m):
key = normalize((x,y,z))
if key in self.cubes: return key,prev
prev = key
x,y,z = x+dx,y+dy,z+dz
return None,None
def show(self,v,t,i): return self.opaque.add(4,GL_QUADS,t,('v3f',v),('t2f',(0,0, 1,0, 1,1, 0,1)))
def update_cube(self,cube):
if not any(cube.shown.values()): return
show = self.water.show if cube.name=='water' else self.show
v = cube_vertices(cube.p)
f = 'left','right','bottom','top','back','front'
for i in (0,1,2,3,4,5):
if cube.shown[f[i]] and not cube.faces[f[i]]: cube.faces[f[i]] = show(v[i],cube.t[i],i)
def set_adj(self,cube,adj,state):
x,y,z = cube.p; X,Y,Z = adj; d = X-x,Y-y,Z-z; f = 'left','right','bottom','top','back','front'
for i in (0,1,2):
if d[i]:
j = i+i; a,b = [f[j+1],f[j]][::d[i]]; cube.shown[a] = state
if not state and cube.faces[a]: cube.faces[a].delete(); face = cube.faces[a]; cube.faces[a] = None; self.remove_water(face)
def add(self,p,t,now=False):
if p in self.cubes: return
cube = self.cubes[p] = Cube(t,p,self.block[t],'alpha' if t in self.alpha_textures else 'blend' if t=='water' else 'solid')
for adj in adjacent(*cube.p):
if adj not in self.cubes: self.set_adj(cube,adj,True)
else:
a,b = cube.type,self.cubes[adj].type
if a==b and (a=='solid' or b=='blend'): self.set_adj(self.cubes[adj],cube.p,False)
elif a!='blend' and b!='solid': self.set_adj(self.cubes[adj],cube.p,False); self.set_adj(cube,adj,True)
if now: self.update_cube(cube)
def remove_water(self,face):
if face in self.water.still_faces: del self.water.still_faces[face]
elif face in self.water.flow_faces: del self.water.flow_faces[face]
def remove(self,p):
if p not in self.cubes: return
cube = self.cubes.pop(p)
for side,face in cube.faces.items():
if face: face.delete()
self.remove_water(face)
for adj in adjacent(*cube.p):
if adj in self.cubes:
self.set_adj(self.cubes[adj],cube.p,True)
self.update_cube(self.cubes[adj])
class Cube:
def __init__(self,name,p,t,typ):
self.name,self.p,self.t,self.type = name,p,t,typ
self.shown = {'left':False,'right':False,'bottom':False,'top':False,'back':False,'front':False}
self.faces = {'left':None,'right':None,'bottom':None,'top':None,'back':None,'front':None}
class TimeLoop:
def __init__(self,duration): self.unit = 0; self.int = 0; self.duration = duration; self.prev = 0
def update(self,dt):
self.unit+=dt; self.unit-=int(self.unit); self.int = int(self.unit*self.duration)
if self.prev!=self.int: self.prev = self.int; return True
def cube_vertices(pos,n=0.5):
x,y,z = pos; v = tuple((x+X,y+Y,z+Z) for X in (-n,n) for Y in (-n,n) for Z in (-n,n))
return tuple(tuple(k for j in i for k in v[j]) for i in ((0,1,3,2),(5,4,6,7),(0,4,5,1),(3,7,6,2),(4,0,2,6),(1,5,7,3)))
def flatten(lst): return sum(map(list,lst),[])
def normalize(pos): x,y,z = pos; return round(x),round(y),round(z)
def adjacent(x,y,z):
for p in ((x-1,y,z),(x+1,y,z),(x,y-1,z),(x,y+1,z),(x,y,z-1),(x,y,z+1)): yield p
class Player:
WALKING_SPEED = 5
FLYING_SPEED = 15
GRAVITY = 20
JUMP_SPEED = (2*GRAVITY)**.5
TERMINAL_VELOCITY = 50
def push(self): glPushMatrix(); glRotatef(-self.rot[0],1,0,0); glRotatef(self.rot[1],0,1,0); glTranslatef(-self.pos[0],-self.pos[1],-self.pos[2])
def __init__(self,cubes,pos=(0,0,0),rot=(0,0)):
self.cubes = cubes
self.pos,self.rot = list(pos),list(rot)
self.flying = True
self.noclip = True
self.dy = 0
def mouse_motion(self,dx,dy):
dx/=8; dy/=8; self.rot[0]+=dy; self.rot[1]+=dx
if self.rot[0]>90: self.rot[0] = 90
elif self.rot[0]<-90: self.rot[0] = -90
def jump(self):
if not self.dy: self.dy = self.JUMP_SPEED
def get_sight_vector(self):
rotX,rotY = self.rot[0]/180*math.pi,self.rot[1]/180*math.pi
dx,dz = math.sin(rotY),-math.cos(rotY)
dy,m = math.sin(rotX),math.cos(rotX)
return dx*m,dy,dz*m
def update(self,dt,keys):
DX,DY,DZ = 0,0,0; s = dt*self.FLYING_SPEED if self.flying else dt*self.WALKING_SPEED
rotY = self.rot[1]/180*math.pi
dx,dz = s*math.sin(rotY),s*math.cos(rotY)
if self.flying:
if keys[key.LSHIFT]: DY-=s
if keys[key.SPACE]: DY+=s
elif keys[key.SPACE]: self.jump()
if keys[key.W]: DX+=dx; DZ-=dz
if keys[key.S]: DX-=dx; DZ+=dz
if keys[key.A]: DX-=dz; DZ-=dx
if keys[key.D]: DX+=dz; DZ+=dx
if dt<0.2:
dt/=10; DX/=10; DY/=10; DZ/=10
for i in range(10): self.move(dt,DX,DY,DZ)
def move(self,dt,dx,dy,dz):
if not self.flying:
self.dy -= dt*self.GRAVITY
self.dy = max(self.dy,-self.TERMINAL_VELOCITY)
dy += self.dy*dt
x,y,z = self.pos
self.pos = self.collide((x+dx,y+dy,z+dz))
def collide(self,pos):
if self.noclip and self.flying: return pos
pad = 0.25; p = list(pos); np = normalize(pos)
for face in ((-1,0,0),(1,0,0),(0,-1,0),(0,1,0),(0,0,-1),(0,0,1)):
for i in (0,1,2):
if not face[i]: continue
d = (p[i]-np[i])*face[i]
if d<pad: continue
for dy in (0,1):
op = list(np); op[1]-=dy; op[i]+=face[i]; op = tuple(op)
if op in self.cubes:
p[i]-=(d-pad)*face[i]
if face[1]: self.dy = 0
break
return tuple(p)
class Window(pyglet.window.Window):
def set2d(self): glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0,self.width,0,self.height)
def set3d(self): glLoadIdentity(); gluPerspective(65,self.width/self.height,0.1,320); glMatrixMode(GL_MODELVIEW); glLoadIdentity()
def on_resize(self,w,h): glViewport(0,0,w,h); self.load_vertex_lists(w,h)
def setLock(self,state): self.set_exclusive_mouse(state); self.mouseLock = state
mouseLock = False; mouse_lock = property(lambda self:self.mouseLock,setLock)
def __init__(self,*args):
super().__init__(*args)
pyglet.clock.schedule(self.update)
self.keys = pyglet.window.key.KeyStateHandler()
self.push_handlers(self.keys)
self.model = Model()
self.player = Player(self.model.cubes.cubes)
self.mouse_lock = True
self.fps = pyglet.clock.ClockDisplay()
self.reticle = None
self.block = 0
def load_vertex_lists(self,w,h):
x,y = w/2,h/2; m = 10
if self.reticle: self.reticle.delete()
self.reticle = pyglet.graphics.vertex_list(4,('v2f',(x-m,y, x+m,y, x,y-m, x,y+m)),('c3f',(0,0,0, 0,0,0, 0,0,0, 0,0,0)))
self.water = pyglet.graphics.vertex_list(4,('v2f',(0,0, w,0, w,h, 0,h)),('c4f',[0.15,0.3,1,0.5]*4))
def update(self,dt):
self.player.update(dt,self.keys)
self.model.update(dt)
def on_mouse_motion(self,x,y,dx,dy):
if self.mouse_lock: self.player.mouse_motion(dx,dy)
def on_mouse_press(self,x,y,button,MOD):
if button == mouse.LEFT:
block = self.model.cubes.hit_test(self.player.pos,self.player.get_sight_vector())[0]
if block: self.model.cubes.remove(block)
elif button == mouse.RIGHT:
block = self.model.cubes.hit_test(self.player.pos,self.player.get_sight_vector())[1]
if block: self.model.cubes.add(block,self.model.ids[self.block],True)
def on_key_press(self,KEY,MOD):
if KEY == key.ESCAPE: self.dispatch_event('on_close')
elif KEY == key.E: self.mouse_lock = not self.mouse_lock
elif KEY == key.F: self.player.flying = not self.player.flying; self.player.dy = 0; self.player.noclip = True
elif KEY == key.C: self.player.noclip = not self.player.noclip
elif KEY == key.UP: self.block = (self.block-1)%len(self.model.ids)
elif KEY == key.DOWN: self.block = (self.block+1)%len(self.model.ids)
def on_draw(self):
self.clear()
self.set3d()
self.player.push()
self.model.draw()
block = self.model.cubes.hit_test(self.player.pos,self.player.get_sight_vector())[0]
if block:
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); glColor3d(0,0,0)
pyglet.graphics.draw(24,GL_QUADS,('v3f/static',flatten(cube_vertices(block,0.52))))
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); glColor3d(1,1,1)
glPopMatrix()
self.set2d()
cubes = self.model.cubes.cubes; p = normalize(self.player.pos)
if p in cubes and cubes[p].name=='water': self.water.draw(GL_POLYGON)
self.fps.draw()
self.reticle.draw(GL_LINES)
def main():
window = Window(800,600,'Minecraft',True)
glClearColor(0.5,0.7,1,1)
glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glAlphaFunc(GL_GEQUAL,1)
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
pyglet.app.run()
if __name__ == '__main__':
main()

Why is my pyglet program slow while rendering 128 particles?

Why is my program slow while rendering 128 particles? I think that's not enough to get less than 30 fps.
All I do is rendering 128 particles and giving them some basic gravitation
on_draw function
def on_draw(self, time=None):
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
self.particles.append(Particle())
for particle in self.particles:
particle.draw()
if particle.is_dead:
self.particles.remove(particle)
Particle class
class Particle:
def __init__(self, **kwargs):
self.acceleration = Vector2(0, 0.05)
self.velocity = Vector2(random.uniform(-1, 1), random.uniform(-1, 0))
self.position = Vector2()
self.time_to_live = 255
self.numpoints = 50
self._vertices = []
for i in range(self.numpoints):
angle = math.radians(float(i) / self.numpoints * 360.0)
x = 10 * math.cos(angle) + self.velocity[0] + 300
y = 10 * math.sin(angle) + self.velocity[1] + 400
self._vertices += [x, y]
def update(self, time=None):
self.velocity += self.acceleration
self.position -= self.velocity
self.time_to_live -= 2
def draw(self):
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPushMatrix()
glTranslatef(self.position[0], self.position[1], 0)
pyglet.graphics.draw(self.numpoints, GL_TRIANGLE_FAN, ('v2f', self._vertices), ('c4B', self.color))
glPopMatrix()
self.update()
#property
def is_dead(self):
if self.time_to_live <= 0:
return True
return False
#property
def color(self):
return tuple(color for i in range(self.numpoints) for color in (255, 255, 255, self.time_to_live))
I'm not overly happy about using GL_TRIANGLE_FAN because it's caused a lot of odd shapes when using batched rendering. So consider moving over to GL_TRIANGLES instead and simply add all the points to the object rather than leaning on GL to close the shape for you.
That way, you can easily move over to doing batched rendering:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time, sleep
from math import *
from random import randint
key = pyglet.window.key
class CustomGroup(pyglet.graphics.Group):
def set_state(self):
#pyglet.gl.glLineWidth(5)
#glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
#glColor4f(1, 0, 0, 1) #FFFFFF
#glLineWidth(1)
#glEnable(texture.target)
#glBindTexture(texture.target, texture.id)
pass
def unset_state(self):
glLineWidth(1)
#glDisable(texture.target)
class Particle():
def __init__(self, x, y, batch, particles):
self.batch = batch
self.particles = particles
self.group = CustomGroup()
self.add_point(x, y)
def add_point(self, x, y):
colors = ()#255,0,0
sides = 50
radius = 25
deg = 360/sides
points = ()#x, y # Starting point is x, y?
prev = None
for i in range(sides):
n = ((deg*i)/180)*pi # Convert degrees to radians
point = int(radius * cos(n)) + x, int(radius * sin(n)) + y
if prev:
points += x, y
points += prev
points += point
colors += (255, i*int(255/sides), 0)*3 # Add a color pair for each point (r,g,b) * points[3 points added]
prev = point
points += x, y
points += prev
points += points[2:4]
colors += (255, 0, 255)*3
self.particles[len(self.particles)] = self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
class main(pyglet.window.Window):
def __init__ (self, demo=False):
super(main, self).__init__(800, 600, fullscreen = False, vsync = True)
#print(self.context.config.sample_buffers)
self.x, self.y = 0, 0
self.sprites = OrderedDict()
self.batches = OrderedDict()
self.batches['default'] = pyglet.graphics.Batch()
self.active_batch = 'default'
for i in range(1000):
self.sprites[len(self.sprites)] = Particle(randint(0, 800), randint(0, 600), self.batches[self.active_batch], self.sprites)
self.alive = True
self.fps = 0
self.last_fps = time()
self.fps_label = pyglet.text.Label(str(self.fps) + ' fps', font_size=12, x=3, y=self.height-15)
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
#self.bg.draw()
self.batches[self.active_batch].draw()
self.fps += 1
if time()-self.last_fps > 1:
self.fps_label.text = str(self.fps) + ' fps'
self.fps = 0
self.last_fps = time()
self.fps_label.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main(demo=True)
x.run()
Bare in mind, on my nVidia 1070 I managed to get roughly 35fps out of this code, which isn't mind blowing. But it is 1000 objects * sides, give or take.
What I've changed is essentially this:
self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
and in your draw loop, you'll do:
self.batch.draw()
Instead of calling Particle.draw() for each particle object.
What this does is that it'll send all the objects to the graphics card in one gigantic batch rather than having to tell the graphics card what to render object by object.
As #thokra pointed out, your code is more CPU intensive than GPU intensive.
Hopefully this fixes it or gives you a few pointers.
Most of this code is taking from a LAN project I did with a good friend of mine a while back:
https://github.com/Torxed/pyslither/blob/master/main.py
Because I didn't have all your code, mainly the main loop. I applied your problem to my own code and "solved " it by tweaking it a bit. Again, hope it helps and steal ideas from that github project if you need to. Happy new year!

How to move Canvas Polygons around?

I want to move around objects in python tkinter, specifically polygons. The problem is in is_click function. I can't seem to figure out how to determine if I clicked the object. The code is not 100% complete yet, and moving around needs still need to be finished but I need to figure this out for now. I also have similar class where you can move around Circles and Rectangles, where is_click function is working, but as polygon has from 3 to 4 coordinates it is a bit more complicated. Run The classes for yourself to see what they are doing.
My code for polygons:
import tkinter
class Polygon:
def __init__(self, ax, ay, bx, by, cx, cy, dx=None, dy=None, color=None):
self.ax = ax
self.ay = ay
self.bx = bx
self.by = by
self.cx = cx
self.cy = cy
self.dx = dx
self.dy = dy
self.color = color
def is_click(self, event_x, event_y):
pass
def paint(self, g):
self.g = g
if self.dx is None:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
fill=self.color)
else:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
self.dx,self.dy,
fill=self.color)
def move(self, d_ax=0, d_ay=0, d_bx=0, d_by=0, d_cx=0, d_cy=0, d_dx=None, d_dy=None):
if d_dx is None:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy)
else:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.dx += d_dx
self.dy += d_dy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy, d_dx, d_dy)
class Tangram:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(width=800,height=800)
self.g.pack()
#all objects
self.add(Polygon(500,300,625,175,750,300, color='SeaGreen'))
self.add(Polygon(750,50,625,175,750,300, color='Tomato'))
self.add(Polygon(500,175,562.6,237.5,500,300, color='SteelBlue'))
self.add(Polygon(500,175,562.5,237.5,625,175,562.5,112.5, color='FireBrick'))
self.add(Polygon(562.5,112.5,625,175,687.5,112.5, color='DarkMagenta'))
self.add(Polygon(500,50,500,175,625,50, color='Gold'))
self.add(Polygon(562.5,112.5,687.5,112.5,750,50,625,50, color='DarkTurquoise'))
#end of all objects
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event.x, event.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = event.x, event.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self):
pass
def event_release(self):
pass
Tangram()
and code for Circle and Rectangle moving:
import tkinter, random
class Circle:
def __init__(self, x, y, r, color='red'):
self.x = x
self.y = y
self.r = r
self.color = color
def is_click(self, x, y):
return (self.x-x)**2+(self.y-y)**2 < self.r**2
def paint(self, g):
self.g = g
self.id = self.g.create_oval(self.x-self.r,self.y-self.r,
self.x+self.r,self.y+self.r,
fill=self.color)
def move(self, dx=0, dy=0):
self.g.delete(self.id)
self.x += dx
self.y += dy
self.paint(self.g)
class Rectangle:
def __init__(self, x, y, width, height, color='red'):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def is_click(self, x, y):
return self.x<=x<self.x+self.width and self.y<=y<self.y+self.height
def paint(self, g):
self.g = g
self.id = self.g.create_rectangle(self.x,self.y,
self.x+self.width,self.y+self.height,
fill=self.color)
def move(self, dx=0, dy=0):
self.x += dx
self.y += dy
self.g.move(self.id, dx, dy)
class Program:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(bg='white', width=400, height=400)
self.g.pack()
for i in range(20):
if random.randrange(2):
self.add(Circle(random.randint(50, 350),random.randint(50, 350), 20, 'blue'))
else:
self.add(Rectangle(random.randint(50, 350),random.randint(50, 350), 40, 30))
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, e):
ix = len(self.array)-1
while ix >= 0 and not self.array[ix].is_click(e.x, e.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = e.x, e.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self, e):
self.Object.move(e.x-self.ex, e.y-self.ey)
self.ex, self.ey = e.x, e.y
def event_release(self, e):
self.g.unbind('<B1-Motion>')
self.g.unbind('<ButtonRelease-1>')
self.Object = None
Program()
This is not the full anwser to your questions, but its too long for a comment. One way, that I would centrally consider, is to change/amend your code so that it uses find_closes method. With this method, you can determine which widget (i.e. polygon) is clicked very easily. As a quick prof of concept, you can make the following changes in the Tangram and ploygon class:
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event, self.g, event.x, event.y): # add event and canvas to argumetns
In polygon:
def is_click(self, event, g, event_x, event_y):
widget_id = event.widget.find_closest(event.x, event.y)
print(widget_id)
g.move(widget_id, 1, 1) # just dummy move for a clicked widget
pass
This will print the ids of clicked widgets/polygons and slightly move the clicked polygon. This can be used to select which object was clicked, and having this id object, you can for moving or whatever.
p.s. the full code that I used to test it is here.
Hope this helps.

Categories

Resources