1 #include <math/bitarray.h>
2 #include "particle/ParticleSource.h"
3 #include "weapon/weapon.h"
4 #include "ParticleSource.h"
5 
6 namespace particle {
SourceOrigin()7 SourceOrigin::SourceOrigin() : m_originType(SourceOriginType::NONE),
8 							   m_weaponState(WeaponState::INVALID),
9 							   m_offset(vmd_zero_vector) {
10 }
11 
getGlobalPosition(vec3d * posOut) const12 void SourceOrigin::getGlobalPosition(vec3d* posOut) const {
13 	Assertion(posOut != nullptr, "Invalid vector pointer passed!");
14 	Assertion(m_originType != SourceOriginType::NONE, "Invalid origin type!");
15 
16 	vec3d offset;
17 	switch (m_originType) {
18 		case SourceOriginType::OBJECT: {
19 			*posOut = m_origin.m_object.objp->pos;
20 			vm_vec_unrotate(&offset, &m_offset, &m_origin.m_object.objp->orient);
21 			break;
22 		}
23 		case SourceOriginType::PARTICLE: {
24 			*posOut = m_origin.m_particle.lock()->pos;
25 
26 			matrix m = vmd_identity_matrix;
27 			vec3d dir = m_origin.m_particle.lock()->velocity;
28 
29 			vm_vec_normalize_safe(&dir);
30 			vm_vector_2_matrix_norm(&m, &dir);
31 
32 			vm_vec_unrotate(&offset, &m_offset, &m);
33 
34 			break;
35 		}
36 		case SourceOriginType::VECTOR: {
37 			*posOut = m_origin.m_pos;
38 			offset = m_offset;
39 			break;
40 		}
41 		default: {
42 			*posOut = vmd_zero_vector;
43 			offset = m_offset;
44 			break;
45 		}
46 	}
47 
48 	vm_vec_add2(posOut, &offset);
49 }
getHostOrientation(matrix * matOut) const50 void SourceOrigin::getHostOrientation(matrix* matOut) const {
51 	switch (m_originType) {
52 	case SourceOriginType::OBJECT:
53 		*matOut = m_origin.m_object.objp->orient;
54 		break;
55 	case SourceOriginType::PARTICLE:
56 		vm_vector_2_matrix(matOut, &m_origin.m_particle.lock()->velocity, nullptr, nullptr);
57 		break;
58 	case SourceOriginType::VECTOR: // Intentional fall-through, plain vectors have no orientation
59 	default:
60 		*matOut = vmd_identity_matrix;
61 		break;
62 	}
63 }
64 
applyToParticleInfo(particle_info & info,bool allowRelative) const65 void SourceOrigin::applyToParticleInfo(particle_info& info, bool allowRelative) const {
66 	Assertion(m_originType != SourceOriginType::NONE, "Invalid origin type!");
67 
68 	if (allowRelative) {
69 		switch (m_originType) {
70 			case SourceOriginType::OBJECT: {
71 				info.attached_objnum = static_cast<int>(OBJ_INDEX(m_origin.m_object.objp));
72 				info.attached_sig = m_origin.m_object.objp->signature;
73 
74 				info.pos = m_offset;
75 				break;
76 			}
77 			case SourceOriginType::PARTICLE: // Intentional fall-through
78 			case SourceOriginType::VECTOR: // Intentional fall-through
79 			default: {
80 				this->getGlobalPosition(&info.pos);
81 				info.attached_objnum = -1;
82 				info.attached_sig = -1;
83 				break;
84 			}
85 		}
86 	}
87 	else {
88 		this->getGlobalPosition(&info.pos);
89 		info.attached_objnum = -1;
90 		info.attached_sig = -1;
91 	}
92 }
93 
getVelocity() const94 vec3d SourceOrigin::getVelocity() const {
95 	switch (this->m_originType) {
96 		case SourceOriginType::OBJECT:
97 			return m_origin.m_object.objp->phys_info.vel;
98 		case SourceOriginType::PARTICLE:
99 			return m_origin.m_particle.lock()->velocity;
100 		default:
101 			return vmd_zero_vector;
102 	}
103 }
104 
setWeaponState(WeaponState state)105 void SourceOrigin::setWeaponState(WeaponState state) {
106 	m_weaponState = state;
107 }
108 
moveTo(vec3d * pos)109 void SourceOrigin::moveTo(vec3d* pos) {
110 	Assertion(pos, "Invalid vector pointer passed!");
111 
112 	m_originType = SourceOriginType::VECTOR;
113 	m_origin.m_pos = *pos;
114 }
115 
moveToObject(object * objp,vec3d * offset)116 void SourceOrigin::moveToObject(object* objp, vec3d* offset) {
117 	Assertion(objp, "Invalid object pointer passed!");
118 	Assertion(offset, "Invalid vector pointer passed!");
119 
120 	m_originType = SourceOriginType::OBJECT;
121 	m_origin.m_object = object_h(objp);
122 
123 	m_offset = *offset;
124 }
125 
moveToParticle(const WeakParticlePtr & weakParticlePtr)126 void SourceOrigin::moveToParticle(const WeakParticlePtr& weakParticlePtr) {
127 	m_originType = SourceOriginType::PARTICLE;
128 	m_origin.m_particle = weakParticlePtr;
129 }
130 
isValid() const131 bool SourceOrigin::isValid() const {
132 	switch (m_originType) {
133 		case SourceOriginType::NONE:
134 			return false;
135 		case SourceOriginType::OBJECT: {
136 			if (!m_origin.m_object.IsValid()) {
137 				return false;
138 			}
139 
140 			auto objp = m_origin.m_object.objp;
141 
142 			if (objp->type != OBJ_WEAPON) {
143 				// The following checks are only relevant for weapons
144 				return true;
145 			}
146 
147 			if (m_weaponState == WeaponState::INVALID) {
148 				// If no state is specified, ignore it.
149 				return true;
150 			}
151 
152 			weapon* wp = &Weapons[objp->instance];
153 
154 			// Make sure we stay in the same weapon state
155 			return wp->weapon_state == m_weaponState;
156 		}
157 		case SourceOriginType::PARTICLE:
158 			return !m_origin.m_particle.expired();
159 		case SourceOriginType::VECTOR:
160 			return true;
161 	}
162 
163 	return false;
164 }
165 
SourceOrientation()166 SourceOrientation::SourceOrientation() : m_orientation(vmd_identity_matrix) {}
167 
setFromVector(const vec3d & vec,bool relative)168 void SourceOrientation::setFromVector(const vec3d& vec, bool relative) {
169 	vec3d workVec = vec;
170 
171 	vm_vec_normalize(&workVec);
172 
173 	this->setFromNormalizedVector(workVec, relative);
174 }
175 
setFromNormalizedVector(const vec3d & vec,bool relative)176 void SourceOrientation::setFromNormalizedVector(const vec3d& vec, bool relative) {
177 	vm_vector_2_matrix_norm(&m_orientation, &vec);
178 	m_isRelative = relative;
179 }
180 
setFromMatrix(const matrix & mat,bool relative)181 void SourceOrientation::setFromMatrix(const matrix& mat, bool relative) {
182 	m_orientation = mat;
183 	m_isRelative = relative;
184 }
185 
setNormal(const vec3d & normal)186 void SourceOrientation::setNormal(const vec3d& normal) {
187 	m_hasNormal = true;
188 	m_normal = normal;
189 }
190 
getDirectionVector(const SourceOrigin * origin) const191 vec3d SourceOrientation::getDirectionVector(const SourceOrigin* origin) const {
192 	if (!m_isRelative) {
193 		return m_orientation.vec.fvec;
194 	}
195 
196 	matrix finalOrient;
197 
198 	matrix hostOrient;
199 	origin->getHostOrientation(&hostOrient);
200 
201 	vm_matrix_x_matrix(&finalOrient, &hostOrient, &m_orientation);
202 
203 	return finalOrient.vec.fvec;
204 }
205 
getNormal(vec3d * outNormal) const206 bool SourceOrientation::getNormal(vec3d* outNormal) const {
207 	Assert(outNormal != nullptr);
208 
209 	*outNormal = m_normal;
210 
211 	return m_hasNormal;
212 }
213 
SourceTiming()214 SourceTiming::SourceTiming() : m_creationTimestamp(timestamp(-1)), m_beginTimestamp(timestamp(-1)),
215 							   m_endTimestamp(timestamp(-1)) {}
216 
setCreationTimestamp(int time)217 void SourceTiming::setCreationTimestamp(int time) {
218 	m_creationTimestamp = time;
219 	m_nextCreation      = time;
220 }
221 
setLifetime(int begin,int end)222 void SourceTiming::setLifetime(int begin, int end) {
223 	m_beginTimestamp = begin;
224 	m_endTimestamp = end;
225 }
226 
isActive() const227 bool SourceTiming::isActive() const {
228 	if (!timestamp_valid(m_beginTimestamp) && !timestamp_valid(m_endTimestamp)) {
229 		// No valid timestamps => default is to be active
230 		return true;
231 	}
232 
233 	if (!timestamp_valid(m_beginTimestamp) && timestamp_valid(m_endTimestamp)) {
234 		// Active until the end has elapsed
235 		return !timestamp_elapsed(m_endTimestamp);
236 	}
237 
238 	if (timestamp_valid(m_beginTimestamp) && !timestamp_valid(m_endTimestamp)) {
239 		// If begin is valid, check if it already happened
240 		return timestamp_elapsed(m_beginTimestamp) != 0;
241 	}
242 
243 	// Check if we are in the range [begin, end]
244 	return timestamp_elapsed(m_beginTimestamp) && !timestamp_elapsed(m_endTimestamp);
245 }
246 
isFinished() const247 bool SourceTiming::isFinished() const {
248 	// If end isn't valid the timing is never finished
249 	if (!timestamp_valid(m_endTimestamp)) {
250 		return false;
251 	}
252 
253 	return timestamp_elapsed(m_endTimestamp) != 0;
254 }
255 
getLifeTimeProgress() const256 float SourceTiming::getLifeTimeProgress() const {
257 	// The progress can only be given when we have a valid time range
258 	if (!timestamp_valid(m_beginTimestamp) && !timestamp_valid(m_endTimestamp)) {
259 		return -1.f;
260 	}
261 
262 	if (!timestamp_valid(m_beginTimestamp) && timestamp_valid(m_endTimestamp)) {
263 		return -1.f;
264 	}
265 
266 	if (timestamp_valid(m_beginTimestamp) && !timestamp_valid(m_endTimestamp)) {
267 		return -1.f;
268 	}
269 
270 	auto total = m_endTimestamp - m_beginTimestamp;
271 	auto done = timestamp() - m_beginTimestamp;
272 
273 	return done / (float) total;
274 }
nextCreationTimeExpired() const275 bool SourceTiming::nextCreationTimeExpired() const { return timestamp_elapsed(m_nextCreation); }
incrementNextCreationTime(int time_diff)276 void SourceTiming::incrementNextCreationTime(int time_diff) { m_nextCreation += time_diff; }
277 
ParticleSource()278 ParticleSource::ParticleSource() : m_effect(nullptr), m_processingCount(0) {}
279 
isValid() const280 bool ParticleSource::isValid() const {
281 	if (m_timing.isFinished()) {
282 		return false;
283 	}
284 
285 	if (m_effect == nullptr) {
286 		return false;
287 	}
288 
289 	if (!m_origin.isValid()) {
290 		return false;
291 	}
292 
293 	return true;
294 }
295 
initializeThrusterOffset(weapon *,weapon_info * wip)296 void ParticleSource::initializeThrusterOffset(weapon*  /*wp*/, weapon_info* wip) {
297 	polymodel* pm = model_get(wip->model_num);
298 
299 	if (pm->n_thrusters < 1) {
300 		return;
301 	}
302 
303 	// Only use the first thruster, for multiple thrusters we need more sources
304 	auto thruster = &pm->thrusters[0];
305 
306 	if (thruster->num_points < 1) {
307 		return;
308 	}
309 
310 	// Only use the first point in the bank
311 	auto point = &thruster->points[0];
312 
313 	model_find_world_point(&this->m_origin.m_offset, &point->pnt, pm, thruster->submodel_num,
314 						   &vmd_identity_matrix, &vmd_zero_vector);
315 }
316 
finishCreation()317 void ParticleSource::finishCreation() {
318 	if (m_origin.m_originType == SourceOriginType::OBJECT) {
319 		if (IS_VEC_NULL(&m_origin.m_offset)) {
320 			object* obj = m_origin.m_origin.m_object.objp;
321 
322 			if (obj->type == OBJ_WEAPON) {
323 				weapon* wp = &Weapons[obj->instance];
324 				weapon_info* wip = &Weapon_info[wp->weapon_info_index];
325 
326 				if (wip->subtype == WP_MISSILE && wip->model_num >= 0) {
327 					// Now that we are here we know that this is a missile which has no offset set
328 					// The particles of a missile should be created at its thruster
329 					this->initializeThrusterOffset(wp, wip);
330 				}
331 			}
332 		}
333 	}
334 }
335 
process()336 bool ParticleSource::process() {
337 	if (m_timing.isActive()) {
338 		auto result = this->m_effect->processSource(this);
339 
340 		++m_processingCount;
341 
342 		return result;
343 	}
344 	else {
345 		// If not active, try the next frame
346 		return true;
347 	}
348 }
349 }
350