Commit | Line | Data |
---|---|---|
8ee66edd FC |
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 | |
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) |