Line data Source code
1 0 : /*
2 :
3 : OOParticleSystem.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "OOParticleSystem.h"
26 :
27 : #import "Universe.h"
28 : #import "OOTexture.h"
29 : #import "PlayerEntity.h"
30 : #import "OOLightParticleEntity.h"
31 : #import "OOMacroOpenGL.h"
32 : #import "MyOpenGLView.h"
33 :
34 :
35 : // Testing toy: cause particle systems to stop after half a second.
36 0 : #define FREEZE_PARTICLES 0
37 :
38 :
39 : @implementation OOParticleSystem
40 :
41 0 : - (id) init
42 : {
43 : [self release];
44 : return nil;
45 : }
46 :
47 :
48 : /* Initialize shared aspects of the fragburst entities.
49 : Also stashes generated particle speeds in _particleSize[] array.
50 : */
51 : - (id) initWithPosition:(HPVector)pos
52 : velocity:(Vector)vel
53 : count:(unsigned)count
54 : minSpeed:(float)minSpeed
55 : maxSpeed:(float)maxSpeed
56 : duration:(OOTimeDelta)duration
57 : baseColor:(GLfloat[4])baseColor
58 : {
59 : NSParameterAssert(count <= kFragmentBurstMaxParticles);
60 :
61 : if ((self = [super init]))
62 : {
63 : _count = count;
64 : [self setPosition:pos];
65 :
66 : velocity = vel;
67 : _duration = duration;
68 : _maxSpeed = maxSpeed;
69 :
70 : for (unsigned i = 0; i < count; i++)
71 : {
72 : GLfloat speed = minSpeed + 0.5f * (randf()+randf()) * (maxSpeed - minSpeed); // speed tends toward middle of range
73 : _particleVelocity[i] = vector_multiply_scalar(OORandomUnitVector(), speed);
74 :
75 : Vector color = make_vector(baseColor[0] * 0.1f * (9.5f + randf()), baseColor[1] * 0.1f * (9.5f + randf()), baseColor[2] * 0.1f * (9.5f + randf()));
76 : color = vector_normal(color);
77 : _particleColor[i][0] = color.x;
78 : _particleColor[i][1] = color.y;
79 : _particleColor[i][2] = color.z;
80 : _particleColor[i][3] = baseColor[3];
81 :
82 : _particleSize[i] = speed;
83 : }
84 :
85 : [self setStatus:STATUS_EFFECT];
86 : scanClass = CLASS_NO_DRAW;
87 : }
88 :
89 : return self;
90 : }
91 :
92 :
93 0 : - (NSString *) descriptionComponents
94 : {
95 : return [NSString stringWithFormat:@"ttl: %.3fs", _duration - _timePassed];
96 : }
97 :
98 :
99 0 : - (BOOL) canCollide
100 : {
101 : return NO;
102 : }
103 :
104 :
105 0 : - (BOOL) checkCloseCollisionWith:(Entity *)other
106 : {
107 : if (other == [self owner]) return NO;
108 : return ![other isEffect];
109 : }
110 :
111 :
112 0 : - (void) update:(OOTimeDelta) delta_t
113 : {
114 : [super update:delta_t];
115 :
116 : _timePassed += delta_t;
117 : collision_radius += delta_t * _maxSpeed;
118 :
119 : unsigned i, count = _count;
120 : Vector *particlePosition = _particlePosition;
121 : Vector *particleVelocity = _particleVelocity;
122 :
123 : for (i = 0; i < count; i++)
124 : {
125 : particlePosition[i] = vector_add(particlePosition[i], vector_multiply_scalar(particleVelocity[i], delta_t));
126 : }
127 :
128 : // disappear eventually.
129 : if (_timePassed > _duration) [UNIVERSE removeEntity:self];
130 : }
131 :
132 :
133 0 : #define DrawQuadForView(x, y, z, sz) \
134 : do { \
135 : glTexCoord2f(0.0, 1.0); glVertex3f(x-sz, y-sz, z); \
136 : glTexCoord2f(1.0, 1.0); glVertex3f(x+sz, y-sz, z); \
137 : glTexCoord2f(1.0, 0.0); glVertex3f(x+sz, y+sz, z); \
138 : glTexCoord2f(0.0, 0.0); glVertex3f(x-sz, y+sz, z); \
139 : } while (0)
140 :
141 :
142 0 : - (void) drawImmediate:(bool)immediate translucent:(bool)translucent
143 : {
144 : if (!translucent || [UNIVERSE breakPatternHide]) return;
145 :
146 : OO_ENTER_OPENGL();
147 : OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING);
148 :
149 : OOGL(glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT));
150 :
151 : OOGL(glEnable(GL_TEXTURE_2D));
152 : OOGL(glEnable(GL_BLEND));
153 : OOGL(glBlendFunc(GL_SRC_ALPHA, GL_ONE));
154 : [[self texture] apply];
155 :
156 : HPVector viewPosition = [PLAYER viewpointPosition];
157 : HPVector selfPosition = [self position];
158 :
159 : unsigned i, count = _count;
160 : Vector *particlePosition = _particlePosition;
161 : GLfloat (*particleColor)[4] = _particleColor;
162 : GLfloat *particleSize = _particleSize;
163 :
164 : if ([UNIVERSE reducedDetail])
165 : {
166 : // Quick rendering - particle cloud is effectively a 2D billboard.
167 : OOGLPushModelView();
168 : OOGLMultModelView(OOMatrixForBillboard(selfPosition, viewPosition));
169 :
170 : OOGLBEGIN(GL_QUADS);
171 : for (i = 0; i < count; i++)
172 : {
173 : glColor4fv(particleColor[i]);
174 : DrawQuadForView(particlePosition[i].x, particlePosition[i].y, particlePosition[i].z, particleSize[i]);
175 : }
176 : OOGLEND();
177 :
178 : OOGLPopModelView();
179 : }
180 : else
181 : {
182 : float distanceThreshold = collision_radius * 2.0f; // Distance between player and middle of effect where we start to transition to "non-fast rendering."
183 : float thresholdSq = distanceThreshold * distanceThreshold;
184 : float distanceSq = cam_zero_distance;
185 :
186 : if (distanceSq > thresholdSq)
187 : {
188 : /* Semi-quick rendering - particle positions are volumetric, but
189 : orientation is shared. This can cause noticeable distortion
190 : if the player is close to the centre of the cloud.
191 : */
192 : OOMatrix bbMatrix = OOMatrixForBillboard(selfPosition, viewPosition);
193 :
194 : for (i = 0; i < count; i++)
195 : {
196 : OOGLPushModelView();
197 : OOGLTranslateModelView(particlePosition[i]);
198 : OOGLMultModelView(bbMatrix);
199 :
200 : glColor4fv(particleColor[i]);
201 : OOGLBEGIN(GL_QUADS);
202 : DrawQuadForView(0, 0, 0, particleSize[i]);
203 : OOGLEND();
204 :
205 : OOGLPopModelView();
206 : }
207 : }
208 : else
209 : {
210 : /* Non-fast rendering - each particle is billboarded individually.
211 : The "individuality" factor interpolates between this behavior
212 : and "semi-quick" to avoid jumping at the boundary.
213 : */
214 : float individuality = 3.0f * (1.0f - distanceSq / thresholdSq);
215 : individuality = OOClamp_0_1_f(individuality);
216 :
217 : for (i = 0; i < count; i++)
218 : {
219 : OOGLPushModelView();
220 : OOGLTranslateModelView(particlePosition[i]);
221 : OOGLMultModelView(OOMatrixForBillboard(HPvector_add(selfPosition, vectorToHPVector(vector_multiply_scalar(particlePosition[i], individuality))), viewPosition));
222 :
223 : glColor4fv(particleColor[i]);
224 : OOGLBEGIN(GL_QUADS);
225 : DrawQuadForView(0, 0, 0, particleSize[i]);
226 : OOGLEND();
227 :
228 : OOGLPopModelView();
229 : }
230 : }
231 :
232 : }
233 :
234 : OOGL(glPopAttrib());
235 :
236 : OOVerifyOpenGLState();
237 : OOCheckOpenGLErrors(@"OOParticleSystem after drawing %@", self);
238 : }
239 :
240 :
241 0 : - (BOOL) isEffect
242 : {
243 : return YES;
244 : }
245 :
246 : - (OOTexture *) texture
247 : {
248 : return [OOLightParticleEntity defaultParticleTexture];
249 : }
250 :
251 : #ifndef NDEBUG
252 0 : - (NSSet *) allTextures
253 : {
254 : return [NSSet setWithObject:[OOLightParticleEntity defaultParticleTexture]];
255 : }
256 : #endif
257 :
258 : @end
259 :
260 :
261 : @implementation OOSmallFragmentBurstEntity: OOParticleSystem
262 :
263 : - (id) initFragmentBurstFrom:(HPVector)fragPosition velocity:(Vector)fragVelocity size:(GLfloat)size
264 : {
265 : enum
266 : {
267 0 : kMinSpeed = 100, kMaxSpeed = 400
268 : };
269 :
270 0 : unsigned count = 0.4f * size;
271 : count = MIN(count | 12, (unsigned)kFragmentBurstMaxParticles);
272 :
273 : // Select base colour
274 : // yellow/orange (0.12) through yellow (0.1667) to yellow/slightly green (0.20)
275 0 : OOColor *hsvColor = [OOColor colorWithHue:0.12f + 0.08f * randf() saturation:1.0f brightness:1.0f alpha:1.0f];
276 0 : GLfloat baseColor[4];
277 0 : [hsvColor getRed:&baseColor[0] green:&baseColor[1] blue:&baseColor[2] alpha:&baseColor[3]];
278 :
279 0 : if ((self = [super initWithPosition:fragPosition velocity:fragVelocity count:count minSpeed:kMinSpeed maxSpeed:kMaxSpeed duration:1.5 baseColor:baseColor]))
280 : {
281 : for (unsigned i = 0; i < count; i++)
282 : {
283 : // Note: initWithPosition:... stashes speeds in _particleSize[].
284 : _particleSize[i] = 32.0f * kMinSpeed / _particleSize[i];
285 : }
286 : }
287 :
288 0 : return self;
289 : }
290 :
291 :
292 : + (id) fragmentBurstFromEntity:(Entity *)entity
293 : {
294 : return [[[self alloc] initFragmentBurstFrom:[entity position] velocity:[entity velocity] size:[entity collisionRadius]] autorelease];
295 : }
296 :
297 :
298 0 : - (void) update:(OOTimeDelta) delta_t
299 : {
300 : #if FREEZE_PARTICLES
301 : if (_timePassed + delta_t > 0.5) delta_t = 0.5 - _timePassed;
302 : #endif
303 :
304 : [super update:delta_t];
305 :
306 : unsigned i, count = _count;
307 : GLfloat (*particleColor)[4] = _particleColor;
308 : GLfloat timePassed = _timePassed;
309 :
310 : for (i = 0; i < count; i++)
311 : {
312 : GLfloat du = 0.5f + (1.0f/32.0f) * (32 - i);
313 : particleColor[i][3] = OOClamp_0_1_f(1.0f - timePassed / du);
314 : }
315 : }
316 :
317 : @end
318 :
319 :
320 : @implementation OOBigFragmentBurstEntity: OOParticleSystem
321 :
322 : - (id) initFragmentBurstFrom:(HPVector)fragPosition velocity:(Vector)fragVelocity size:(GLfloat)size
323 : {
324 : float minSpeed = 1.0f + size * 0.5f;
325 0 : float maxSpeed = minSpeed * 4.0f;
326 :
327 0 : unsigned count = 0.2f * size;
328 : count = MIN(count | 3, (unsigned)kBigFragmentBurstMaxParticles);
329 :
330 0 : GLfloat baseColor[4] = { 1.0f, 1.0f, 0.5f, 1.0f };
331 :
332 : size *= 2.0f; // Account for margins in particle texture.
333 0 : if ((self = [super initWithPosition:fragPosition velocity:fragVelocity count:count minSpeed:minSpeed maxSpeed:maxSpeed duration:1.0 baseColor:baseColor]))
334 : {
335 : _baseSize = size;
336 :
337 : for (unsigned i = 0; i < count; i++)
338 : {
339 : _particleSize[i] = size;
340 : }
341 : }
342 :
343 0 : return self;
344 : }
345 :
346 :
347 : + (id) fragmentBurstFromEntity:(Entity *)entity
348 : {
349 : return [[[self alloc] initFragmentBurstFrom:[entity position] velocity:vector_multiply_scalar([entity velocity], 0.85) size:[entity collisionRadius]] autorelease];
350 : }
351 :
352 :
353 0 : - (void) update:(double)delta_t
354 : {
355 : #if FREEZE_PARTICLES
356 : if (_timePassed + delta_t > 0.5) delta_t = 0.5 - _timePassed;
357 : #endif
358 :
359 : [super update:delta_t];
360 :
361 : unsigned i, count = _count;
362 : GLfloat (*particleColor)[4] = _particleColor;
363 : GLfloat *particleSize = _particleSize;
364 : GLfloat timePassed = _timePassed;
365 : GLfloat duration = _duration;
366 :
367 : GLfloat size = (1.0f + timePassed) * _baseSize;
368 : GLfloat di = 1.0f / (count - 1);
369 :
370 : for (i = 0; i < count; i++)
371 : {
372 : GLfloat du = duration * (0.5f + di * i);
373 : particleColor[i][3] = OOClamp_0_1_f(1.0f - timePassed / du);
374 :
375 : particleSize[i] = size;
376 : }
377 : }
378 :
379 : @end
|