1 from math
import pi
, sin
, cos
2 from array
import array
3 from random
import uniform
4 from itertools
import chain
5 from logging
import info
6 from panda3d
.core
import Geom
, GeomVertexFormat
, GeomVertexData
, GeomPoints
, \
7 OmniBoundingVolume
, GeomNode
, Vec3
, ShaderAttrib
, TexGenAttrib
, \
8 TextureStage
, Texture
, GeomEnums
, NodePath
9 from lib
.lib
.p3d
.shader
import load_shader
10 from lib
.lib
.p3d
.gfx
import P3dNode
11 from lib
.gameobject
import GameObject
14 class P3dParticle(GameObject
):
16 _vdata
= {} # don't regenerate input structures
19 self
, emitter
, texture
, color
=(1, 1, 1, 1), ampl
=pi
/6,
20 ray
=.5, rate
=.001, gravity
=-9.81, vel
=1.0, part_duration
=1.0,
21 autodestroy
=None, size
=10):
22 GameObject
.__init
__(self
)
23 self
.__tex
_pos
= self
.__tex
_curr
_pos
= self
.__tex
_times
= \
24 self
.__tex
_start
_vel
= self
.__tex
_curr
_vel
= self
.__emitternode
= \
26 self
.__texture
= texture
31 self
.__gravity
= gravity
33 self
.__part
_duration
= part_duration
35 self
.__npart
= int(round(part_duration
* 1 / rate
))
36 if emitter
.__class
__ != P3dNode
: # emitter is a position
37 self
.__emitternode
= P3dNode(NodePath('tmp'))
38 self
.__emitternode
.set_pos(emitter
)
39 self
.__emitternode
.reparent_to(self
.eng
.gfx
.root
)
40 emitter
= self
.__emitternode
41 self
.__emitter
= emitter
42 self
.__old
_pos
= (0, 0, 0)
43 self
._nodepath
= render
.attach_new_node(self
.__node
())
44 self
._nodepath
.set_transparency(True)
45 self
._nodepath
.set_bin('fixed', 0)
47 self
._nodepath
.set_render_mode_thickness(10)
48 self
._nodepath
.set_tex_gen(TextureStage
.getDefault(),
49 TexGenAttrib
.MPointSprite
)
50 self
._nodepath
.set_depth_write(False)
51 self
.upd_tsk
= taskMgr
.add(self
._update
, 'update')
52 if autodestroy
: self
.eng
.do_later(autodestroy
, self
.destroy
)
55 points
= GeomPoints(GeomEnums
.UH_static
)
56 points
.add_next_vertices(self
.__npart
)
57 geom
= Geom(self
.__vdata
())
58 geom
.add_primitive(points
)
59 geom
.set_bounds(OmniBoundingVolume())
60 node
= GeomNode('node')
65 entry
= (self
.__texture
, self
.__npart
, self
.__color
, self
.__ampl
,
66 self
.__ray
, self
.__rate
, self
.__gravity
)
67 if entry
in P3dParticle
._vdata
:
68 vdata
, pos
, times
, vels
= P3dParticle
._vdata
[entry
]
69 self
.__set
_textures
(pos
, times
, vels
)
71 pos
, times
, vels
= self
.__init
_textures
()
72 self
.__set
_textures
(pos
, times
, vels
)
73 format_
= GeomVertexFormat
.get_empty()
74 vdata
= GeomVertexData('abc', format_
, GeomEnums
.UH_static
)
75 P3dParticle
._vdata
[self
.__texture
, self
.__npart
, self
.__color
,
76 self
.__ampl
, self
.__ray
, self
.__rate
,
78 vdata
, pos
, times
, vels
79 return P3dParticle
._vdata
[self
.__texture
, self
.__npart
, self
.__color
,
80 self
.__ampl
, self
.__ray
, self
.__rate
,
83 def __init_textures(self
):
84 positions
= [self
.__rnd
_pos
() for i
in range(self
.__npart
)]
85 pos_lst
= [[pos
.x
, pos
.y
, pos
.z
, 1] for pos
in positions
]
86 pos_lst
= list(chain
.from_iterable(pos_lst
))
88 (self
.__rate
* i
, 0, 0, 0) for i
in range(self
.__npart
)]
89 times_lst
= list(chain
.from_iterable(emission_times
))
90 velocities
= self
.__init
_velocities
()
91 vel_lst
= [[v_vel
[0], v_vel
[1], v_vel
[2], 1] for v_vel
in velocities
]
92 vel_lst
= list(chain
.from_iterable(vel_lst
))
93 return pos_lst
, times_lst
, vel_lst
95 def __set_textures(self
, pos_lst
, times_lst
, vel_lst
):
96 self
.__tex
_pos
= self
.__buff
_tex
(pos_lst
)
97 self
.__tex
_curr
_pos
= self
.__buff
_tex
(pos_lst
)
98 self
.__tex
_times
= self
.__buff
_tex
(times_lst
)
99 self
.__tex
_start
_vel
= self
.__buff
_tex
(vel_lst
)
100 self
.__tex
_curr
_vel
= self
.__buff
_tex
(vel_lst
)
102 def __buff_tex(self
, vals
):
103 data
= array('f', vals
)
105 tex
.setup_buffer_texture(
106 self
.__npart
, Texture
.T_float
, Texture
.F_rgba32
,
108 tex
.set_ram_image(data
)
112 ro
= uniform(0, self
.__ray
)
113 alpha
= uniform(0, 2 * pi
)
114 return Vec3(ro
* cos(alpha
), ro
* sin(alpha
), 0)
116 def __init_velocities(self
):
118 for _
in range(self
.__npart
):
119 vec
= self
.__rnd
_vel
()
120 vels
+= [(vec
.x
, vec
.y
, vec
.z
)]
124 theta
= uniform(0, self
.__ampl
)
125 phi
= uniform(0, 2 * pi
)
127 sin(theta
) * cos(phi
),
128 sin(theta
) * sin(phi
),
130 return vec
* uniform(self
.__vel
* .8, self
.__vel
* 1.2)
132 def __set_shader(self
):
133 path
= 'assets/shaders/'
134 shader
= load_shader(path
+ 'particle.vert', path
+ 'particle.frag')
135 if not shader
: return
136 self
._nodepath
.set_shader(shader
)
137 sha_attr
= ShaderAttrib
.make(shader
)
138 sha_attr
= sha_attr
.set_flag(ShaderAttrib
.F_shader_point_size
, True)
139 self
._nodepath
.set_attrib(sha_attr
)
140 img
= loader
.loadTexture('assets/images/game/%s.dds' % self
.__texture
)
141 self
._nodepath
.set_shader_inputs(
142 start_pos
=self
.__tex
_pos
,
143 positions
=self
.__tex
_curr
_pos
,
144 emitter_old_pos
=self
.__old
_pos
,
145 emitter_pos
=self
.__emitter
.get_pos(P3dNode(render
)),
146 start_vel
=self
.__tex
_start
_vel
,
147 velocities
=self
.__tex
_curr
_vel
,
148 accel
=(0, 0, self
.__gravity
),
149 start_time
=globalClock
.get_frame_time(),
150 emission_times
=self
.__tex
_times
,
151 part_duration
=self
.__part
_duration
,
157 def _update(self
, task
):
158 if self
.__emitter
and not self
.__emitter
.is_empty
:
159 pos
= self
.__emitter
.get_pos(P3dNode(render
))
160 else: pos
= (0, 0, 0)
162 self
._nodepath
.set_shader_inputs(
163 emitter_old_pos
=self
.__old
_pos
,
167 except AttributeError:
168 # _nodepath may be None on menu/pause
169 info('_nodepath: %s' % self
._nodepath
)
171 def destroy(self
, now
=False):
172 #TODO: the signature differs from the parent's one
174 self
._nodepath
.set_shader_input('emitting', 0)
175 except AttributeError:
176 # _nodepath may be None on menu/pause
177 info('_nodepath: %s' % self
._nodepath
)
178 self
.eng
.do_later(0 if now
else 1.2 * self
.__part
_duration
,
183 self
.upd_tsk
= taskMgr
.remove(self
.upd_tsk
)
185 info("can't remove %s" % self
.upd_tsk
)
186 # it may happen on pause/menu
188 self
._nodepath
= self
._nodepath
.remove_node()
189 except AttributeError:
190 info("_nodepath %s" % self
._nodepath
)
191 # it may happen on pause/menu
192 if self
.__emitternode
:
193 self
.__emitternode
= self
.__emitternode
.destroy()
194 GameObject
.destroy(self
)