~facundo/enjuewemela/trunk

« back to all changes in this revision

Viewing changes to cocos/particle.py

  • Committer: facundo at com
  • Date: 2011-05-14 18:13:25 UTC
  • mfrom: (67.1.4 v3rel)
  • Revision ID: facundo@taniquetil.com.ar-20110514181325-614h8kjz32w5cmoy
Refactor back

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# ----------------------------------------------------------------------------
2
 
# cocos2d
3
 
# Copyright (c) 2008 Daniel Moisset, Ricardo Quesada, Rayentray Tappa, Lucio Torre
4
 
# All rights reserved.
5
 
#
6
 
# Redistribution and use in source and binary forms, with or without
7
 
# modification, are permitted provided that the following conditions are met:
8
 
#
9
 
#   * Redistributions of source code must retain the above copyright
10
 
#     notice, this list of conditions and the following disclaimer.
11
 
#   * Redistributions in binary form must reproduce the above copyright 
12
 
#     notice, this list of conditions and the following disclaimer in
13
 
#     the documentation and/or other materials provided with the
14
 
#     distribution.
15
 
#   * Neither the name of cocos2d nor the names of its
16
 
#     contributors may be used to endorse or promote products
17
 
#     derived from this software without specific prior written
18
 
#     permission.
19
 
#
20
 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
 
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
 
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23
 
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24
 
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25
 
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26
 
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27
 
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
 
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
 
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
 
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
 
# POSSIBILITY OF SUCH DAMAGE.
32
 
# ----------------------------------------------------------------------------
33
 
'''Particle system engine'''
34
 
 
35
 
import random
36
 
import pyglet
37
 
from pyglet.gl import *
38
 
import math
39
 
import copy
40
 
import numpy
41
 
import ctypes
42
 
 
43
 
from cocosnode import CocosNode
44
 
from euclid import Point2
45
 
 
46
 
rand = lambda: random.random() * 2 - 1
47
 
 
48
 
# PointerToNumpy by Gary Herron
49
 
# from pyglet's user list
50
 
def PointerToNumpy(a, ptype=ctypes.c_float):
51
 
    a = numpy.ascontiguousarray(a)           # Probably a NO-OP, but perhaps not
52
 
    return a.ctypes.data_as(ctypes.POINTER(ptype)) # Ugly and undocumented! 
53
 
 
54
 
class Color( object ):
55
 
    def __init__( self, r,g,b,a ):
56
 
        self.r = r
57
 
        self.g = g
58
 
        self.b = b
59
 
        self.a = a
60
 
 
61
 
    def to_array(self):
62
 
        return self.r, self.g, self.b, self.a
63
 
 
64
 
 
65
 
class ParticleSystem( CocosNode ):
66
 
 
67
 
    # type of particle
68
 
    POSITION_FREE, POSITION_GROUPED = range(2)
69
 
 
70
 
    #: is the particle system active ?
71
 
    active = True
72
 
 
73
 
    #: duration in seconds of the system. -1 is infinity
74
 
    duration = 0
75
 
 
76
 
    #: time elapsed since the start of the system (in seconds)
77
 
    elapsed = 0
78
 
 
79
 
    #: Gravity of the particles
80
 
    gravity = Point2(0.0, 0.0)
81
 
 
82
 
    #: position is from "superclass" CocosNode
83
 
    #: Position variance
84
 
    pos_var = Point2(0.0, 0.0)
85
 
 
86
 
    #: The angle (direction) of the particles measured in degrees
87
 
    angle = 0.0
88
 
    #: Angle variance measured in degrees;
89
 
    angle_var = 0.0
90
 
 
91
 
    #: The speed the particles will have.
92
 
    speed = 0.0
93
 
    #: The speed variance
94
 
    speed_var = 0.0
95
 
 
96
 
    #: Tangential acceleration
97
 
    tangential_accel = 0.0
98
 
    #: Tangential acceleration variance
99
 
    tangential_accel_var = 0.0
100
 
 
101
 
    #: Radial acceleration
102
 
    radial_accel = 0.0
103
 
    #: Radial acceleration variance
104
 
    radial_accel_var = 0.0
105
 
 
106
 
    #: Size of the particles
107
 
    size = 0.0
108
 
    #: Size variance
109
 
    size_var = 0.0
110
 
 
111
 
    #: How many seconds will the particle live
112
 
    life = 0
113
 
    #: Life variance
114
 
    life_var = 0
115
 
 
116
 
    #: Start color of the particles
117
 
    start_color = Color(0.0,0.0,0.0,0.0)
118
 
    #: Start color variance
119
 
    start_color_var = Color(0.0,0.0,0.0,0.0)
120
 
    #: End color of the particles
121
 
    end_color = Color(0.0,0.0,0.0,0.0)
122
 
    #: End color variance
123
 
    end_color_var = Color(0.0,0.0,0.0,0.0)
124
 
 
125
 
    #: Maximum particles
126
 
    total_particles = 0
127
 
 
128
 
    #:texture of the particles
129
 
    texture = pyglet.resource.image('fire.png').texture
130
 
 
131
 
    #:blend additive
132
 
    blend_additive = False
133
 
 
134
 
    #:color modulate
135
 
    color_modulate = True
136
 
 
137
 
    # position type
138
 
    position_type = POSITION_GROUPED
139
 
 
140
 
    def __init__(self):
141
 
        super(ParticleSystem,self).__init__()
142
 
 
143
 
        # particles
144
 
        # position x 2
145
 
        self.particle_pos = numpy.zeros( (self.total_particles, 2), numpy.float32 )
146
 
        # direction x 2
147
 
        self.particle_dir = numpy.zeros( (self.total_particles, 2), numpy.float32 )
148
 
        # rad accel x 1
149
 
        self.particle_rad = numpy.zeros( (self.total_particles, 1), numpy.float32 )
150
 
        # tan accel x 1
151
 
        self.particle_tan = numpy.zeros( (self.total_particles, 1), numpy.float32 )
152
 
        # gravity x 2
153
 
        self.particle_grav = numpy.zeros( (self.total_particles, 2), numpy.float32 )
154
 
        # colors x 4
155
 
        self.particle_color = numpy.zeros( (self.total_particles, 4), numpy.float32 )
156
 
        # delta colors x 4
157
 
        self.particle_delta_color = numpy.zeros( (self.total_particles, 4), numpy.float32 )
158
 
        # life x 1
159
 
        self.particle_life = numpy.zeros( (self.total_particles, 1), numpy.float32 )
160
 
        self.particle_life.fill(-1.0)
161
 
        # size x 1
162
 
        self.particle_size = numpy.zeros( (self.total_particles, 1), numpy.float32 )
163
 
        # start position
164
 
        self.start_pos = numpy.zeros( (self.total_particles, 2), numpy.float32 )
165
 
 
166
 
        #: How many particles can be emitted per second
167
 
        self.emit_counter = 0
168
 
        
169
 
        #: Count of particles
170
 
        self.particle_count = 0
171
 
 
172
 
        #: auto remove when particle finishes
173
 
        self.auto_remove_on_finish = False
174
 
 
175
 
        self.schedule( self.step )
176
 
 
177
 
    def on_enter( self ):
178
 
        super( ParticleSystem, self).on_enter()
179
 
        self.add_particle()
180
 
 
181
 
    def draw( self ):
182
 
        glPushMatrix()
183
 
        self.transform()
184
 
 
185
 
        glPointSize( self.size )
186
 
 
187
 
        glEnable(GL_TEXTURE_2D)
188
 
        glBindTexture(GL_TEXTURE_2D, self.texture.id )
189
 
 
190
 
        glEnable(GL_POINT_SPRITE)
191
 
        glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE )
192
 
 
193
 
 
194
 
        glEnableClientState(GL_VERTEX_ARRAY)
195
 
        vertex_ptr = PointerToNumpy( self.particle_pos )
196
 
        glVertexPointer(2,GL_FLOAT,0,vertex_ptr);
197
 
 
198
 
        glEnableClientState(GL_COLOR_ARRAY)
199
 
        color_ptr = PointerToNumpy( self.particle_color)
200
 
        glColorPointer(4,GL_FLOAT,0,color_ptr);
201
 
 
202
 
        glPushAttrib(GL_COLOR_BUFFER_BIT)
203
 
        glEnable(GL_BLEND)
204
 
        if self.blend_additive:
205
 
            glBlendFunc(GL_SRC_ALPHA, GL_ONE);
206
 
        else:
207
 
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
208
 
 
209
 
#        mode = GLint()
210
 
#        glTexEnviv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode )
211
 
#
212
 
#        if self.color_modulate:
213
 
#            glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE )
214
 
#        else:
215
 
#            glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE )
216
 
 
217
 
 
218
 
        glDrawArrays(GL_POINTS, 0, self.total_particles);
219
 
 
220
 
        # un -blend
221
 
        glPopAttrib()
222
 
 
223
 
#        # restore env mode
224
 
#        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode)
225
 
 
226
 
        # disable states
227
 
        glDisableClientState(GL_COLOR_ARRAY);
228
 
        glDisableClientState(GL_VERTEX_ARRAY);
229
 
        glDisable(GL_POINT_SPRITE);
230
 
        glDisable(GL_TEXTURE_2D);
231
 
 
232
 
        glPopMatrix()
233
 
 
234
 
 
235
 
    def step( self, delta ):
236
 
 
237
 
        # update particle count
238
 
        self.particle_count = numpy.sum( self.particle_life >= 0 )
239
 
 
240
 
        if self.active:
241
 
            rate = 1.0 / self.emission_rate
242
 
            self.emit_counter += delta
243
 
 
244
 
#            if random.random() < 0.01:
245
 
#                delta += 0.5
246
 
 
247
 
            while self.particle_count < self.total_particles and self.emit_counter > rate:
248
 
                self.add_particle()
249
 
                self.emit_counter -= rate
250
 
 
251
 
            self.elapsed += delta
252
 
 
253
 
            if self.duration != -1 and self.duration < self.elapsed:
254
 
                self.stop_system()
255
 
 
256
 
        self.update_particles( delta )
257
 
 
258
 
        if self.particle_count == 0 and self.auto_remove_on_finish == True:
259
 
            self.unschedule( self.step )
260
 
            self.parent.remove( self )
261
 
 
262
 
    def add_particle( self ):
263
 
        self.init_particle()
264
 
        self.particle_count += 1
265
 
 
266
 
    def stop_system( self ):
267
 
        self.active = False
268
 
        self.elapsed= self.duration
269
 
        self.emit_counter = 0
270
 
 
271
 
    def reset_system( self ):
272
 
        self.elapsed= self.duration
273
 
        self.emit_counter = 0
274
 
 
275
 
    def update_particles( self, delta ):
276
 
        # radial: posx + posy
277
 
        norm = numpy.sqrt( self.particle_pos[:,0] ** 2 + self.particle_pos[:,1] ** 2 )
278
 
        # XXX prevent div by 0
279
 
        norm = numpy.select( [norm==0], [0.0000001], default=norm )
280
 
        posx = self.particle_pos[:,0] / norm
281
 
        posy = self.particle_pos[:,1] / norm
282
 
 
283
 
        radial = numpy.array( [posx, posy] )
284
 
        tangential = numpy.array( [-posy, posx] )
285
 
 
286
 
        # update dir
287
 
        radial = numpy.swapaxes(radial,0,1)
288
 
        radial *= self.particle_rad
289
 
        tangential = numpy.swapaxes(tangential,0,1)
290
 
        tangential *= self.particle_tan
291
 
 
292
 
        self.particle_dir +=  (tangential + radial + self.particle_grav) * delta
293
 
 
294
 
        # update pos with updated dir
295
 
        self.particle_pos += self.particle_dir * delta
296
 
 
297
 
        # life
298
 
        self.particle_life -= delta
299
 
 
300
 
 
301
 
        # position: free or grouped
302
 
        if self.position_type == self.POSITION_FREE:
303
 
            tuple = numpy.array( [self.x, self.y] )
304
 
            tmp = tuple - self.start_pos
305
 
            self.particle_pos -= tmp
306
 
 
307
 
 
308
 
        # color
309
 
        self.particle_color += self.particle_delta_color * delta
310
 
 
311
 
        # if life < 0, set alpha in 0
312
 
        self.particle_color[:,3] = numpy.select( [self.particle_life[:,0] < 0], [0], default=self.particle_color[:,3] )
313
 
 
314
 
#        print self.particles[0]
315
 
#        print self.pas[0,0:4]
316
 
 
317
 
    def init_particle( self ):
318
 
        # position
319
 
#        p=self.particles[idx]
320
 
 
321
 
        a = self.particle_life < 0
322
 
        idxs = a.nonzero()
323
 
 
324
 
        idx = -1
325
 
 
326
 
        if len(idxs[0]) > 0:
327
 
            idx = idxs[0][0] 
328
 
        else:
329
 
            raise Exception("No empty particle")
330
 
 
331
 
        # position
332
 
        self.particle_pos[idx][0] = self.pos_var.x * rand()
333
 
        self.particle_pos[idx][1] = self.pos_var.y * rand()
334
 
 
335
 
        # start position
336
 
        self.start_pos[idx][0] = self.x
337
 
        self.start_pos[idx][1] = self.y
338
 
 
339
 
        a = math.radians( self.angle + self.angle_var * rand() )
340
 
        v = Point2( math.cos( a ), math.sin( a ) )
341
 
        s = self.speed + self.speed_var * rand()
342
 
 
343
 
        dir = v * s
344
 
 
345
 
        # direction
346
 
        self.particle_dir[idx][0] = dir.x
347
 
        self.particle_dir[idx][1] = dir.y
348
 
 
349
 
        # radial accel
350
 
        self.particle_rad[idx] = self.radial_accel + self.radial_accel_var * rand()
351
 
 
352
 
 
353
 
        # tangential accel
354
 
        self.particle_tan[idx] = self.tangential_accel + self.tangential_accel_var * rand()
355
 
        
356
 
        # life
357
 
        life = self.particle_life[idx] = self.life + self.life_var * rand()
358
 
 
359
 
        # Color
360
 
        # start
361
 
        sr = self.start_color.r + self.start_color_var.r * rand()
362
 
        sg = self.start_color.g + self.start_color_var.g * rand()
363
 
        sb = self.start_color.b + self.start_color_var.b * rand()
364
 
        sa = self.start_color.a + self.start_color_var.a * rand()
365
 
 
366
 
        self.particle_color[idx][0] = sr
367
 
        self.particle_color[idx][1] = sg
368
 
        self.particle_color[idx][2] = sb
369
 
        self.particle_color[idx][3] = sa
370
 
 
371
 
        # end
372
 
        er = self.end_color.r + self.end_color_var.r * rand()
373
 
        eg = self.end_color.g + self.end_color_var.g * rand()
374
 
        eb = self.end_color.b + self.end_color_var.b * rand()
375
 
        ea = self.end_color.a + self.end_color_var.a * rand()
376
 
 
377
 
        delta_color_r = (er - sr) / life
378
 
        delta_color_g = (eg - sg) / life
379
 
        delta_color_b = (eb - sb) / life
380
 
        delta_color_a = (ea - sa) / life
381
 
 
382
 
        self.particle_delta_color[idx][0] = delta_color_r
383
 
        self.particle_delta_color[idx][1] = delta_color_g
384
 
        self.particle_delta_color[idx][2] = delta_color_b
385
 
        self.particle_delta_color[idx][3] = delta_color_a
386
 
 
387
 
        # size
388
 
        self.particle_size[idx] = self.size + self.size_var * rand()
389
 
 
390
 
        # gravity
391
 
        self.particle_grav[idx][0] = self.gravity.x
392
 
        self.particle_grav[idx][1] = self.gravity.y