ya2 · news · projects · code · about

6884ef6d986b388a36d2f47b39c8e2729f8811a7
[pmachines.git] / ya2 / lib / p3d / particle.py
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 ya2.lib.p3d.shader import load_shader
10 from ya2.lib.p3d.gfx import P3dNode
11 from ya2.gameobject import GameObject
12
13
14 class P3dParticle(GameObject):
15
16 _vdata = {} # don't regenerate input structures
17
18 def __init__(
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 = \
25 None
26 self.__texture = texture
27 self.__color = color
28 self.__ampl = ampl
29 self.__ray = ray
30 self.__rate = rate
31 self.__gravity = gravity
32 self.__vel = vel
33 self.__part_duration = part_duration
34 self.__size = size
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)
46 self.__set_shader()
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)
53
54 def __node(self):
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')
61 node.add_geom(geom)
62 return node
63
64 def __vdata(self):
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)
70 return vdata
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,
77 self.__gravity] = \
78 vdata, pos, times, vels
79 return P3dParticle._vdata[self.__texture, self.__npart, self.__color,
80 self.__ampl, self.__ray, self.__rate,
81 self.__gravity][0]
82
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))
87 emission_times = [
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
94
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)
101
102 def __buff_tex(self, vals):
103 data = array('f', vals)
104 tex = Texture('tex')
105 tex.setup_buffer_texture(
106 self.__npart, Texture.T_float, Texture.F_rgba32,
107 GeomEnums.UH_static)
108 tex.set_ram_image(data)
109 return tex
110
111 def __rnd_pos(self):
112 ro = uniform(0, self.__ray)
113 alpha = uniform(0, 2 * pi)
114 return Vec3(ro * cos(alpha), ro * sin(alpha), 0)
115
116 def __init_velocities(self):
117 vels = []
118 for _ in range(self.__npart):
119 vec = self.__rnd_vel()
120 vels += [(vec.x, vec.y, vec.z)]
121 return vels
122
123 def __rnd_vel(self):
124 theta = uniform(0, self.__ampl)
125 phi = uniform(0, 2 * pi)
126 vec = Vec3(
127 sin(theta) * cos(phi),
128 sin(theta) * sin(phi),
129 cos(theta))
130 return vec * uniform(self.__vel * .8, self.__vel * 1.2)
131
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,
152 emitting=1,
153 col=self.__color,
154 image=img,
155 size=self.__size)
156
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)
161 try:
162 self._nodepath.set_shader_inputs(
163 emitter_old_pos=self.__old_pos,
164 emitter_pos=pos)
165 self.__old_pos = pos
166 return task.again
167 except AttributeError:
168 # _nodepath may be None on menu/pause
169 info('_nodepath: %s' % self._nodepath)
170
171 def destroy(self, now=False):
172 #TODO: the signature differs from the parent's one
173 try:
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,
179 self.__destroy)
180
181 def __destroy(self):
182 try:
183 self.upd_tsk = taskMgr.remove(self.upd_tsk)
184 except TypeError:
185 info("can't remove %s" % self.upd_tsk)
186 # it may happen on pause/menu
187 try:
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)