/**************************************************************************** * Trail functions ****************************************************************************/ /// <summary> /// Creates a trail and assigns it to a particle. /// </summary> /// <param name="particleInfo">Information about the particle.</param> public void AddTrail(TrailParticleInfo particleInfo) { // Check parent object if (_parentGameObject == null) { _parentGameObject = new GameObject("Playground Trails (" + playgroundSystem.name + ")", typeof(PlaygroundTrailParent)); _parentTransform = _parentGameObject.transform; _parentGameObject.GetComponent <PlaygroundTrailParent>().trailsReference = this; } ParticlePlaygroundTrail newTrail = new ParticlePlaygroundTrail(maxPoints); newTrail.trailGameObject = new GameObject("Playground Trail " + particleInfo.particleId); newTrail.trailTransform = newTrail.trailGameObject.transform; newTrail.trailTransform.parent = _parentTransform; newTrail.trailRenderer = newTrail.trailGameObject.AddComponent <MeshRenderer>(); newTrail.trailMeshFilter = newTrail.trailGameObject.AddComponent <MeshFilter>(); newTrail.trailMesh = new Mesh(); newTrail.trailMesh.MarkDynamic(); newTrail.trailMeshFilter.sharedMesh = newTrail.trailMesh; newTrail.trailRenderer.sharedMaterial = material; newTrail.particleId = particleInfo.particleId; if (createFirstPointOnParticleBirth) { newTrail.SetFirstPoint(particleInfo.position, particleInfo.velocity, EvaluateWidth(0), time, _calculationStartTime); } _trails.Add(newTrail); }
/// <summary> /// This function will be called whenever a particle is colliding. /// </summary> /// <param name="particle">The collided particle.</param> void OnParticleCollisionEvent(PlaygroundEventParticle particle) { if (createPointsOnCollision) { int trailIndex = GetNewestTrailWithParticleId(particle.particleId); if (trailIndex < 0) { return; } ParticlePlaygroundTrail trailAtIndex = _trails[trailIndex]; trailAtIndex.AddPoint(playgroundSystem.particleCache[particle.particleId].position, EvaluateWidth(0), time, _calculationStartTime); } }
/// <summary> /// Creates a trail and assigns it to a particle. /// </summary> /// <param name="particleInfo">Information about the particle.</param> public void CreateTrail(TrailParticleInfo particleInfo) { // Check parent object if (_parentGameObject == null) { _parentGameObject = new GameObject("Playground Trails (" + playgroundSystem.name + ")", typeof(PlaygroundTrailParent)); _parentTransform = _parentGameObject.transform; _parentGameObject.GetComponent <PlaygroundTrailParent>().trailsReference = this; } ParticlePlaygroundTrail newTrail = new ParticlePlaygroundTrail(maxPoints); newTrail.trailGameObject = new GameObject("Playground Trail " + trails.Count); layer = Mathf.Clamp(layer, 0, 32); newTrail.trailGameObject.layer = layer; newTrail.trailTransform = newTrail.trailGameObject.transform; newTrail.trailTransform.parent = _parentTransform; newTrail.trailRenderer = newTrail.trailGameObject.AddComponent <MeshRenderer>(); newTrail.trailMeshFilter = newTrail.trailGameObject.AddComponent <MeshFilter>(); newTrail.trailMesh = new Mesh(); newTrail.trailMesh.MarkDynamic(); newTrail.trailMeshFilter.sharedMesh = newTrail.trailMesh; newTrail.trailRenderer.sharedMaterial = material; newTrail.particleId = particleInfo.particleId; if (createFirstPointOnParticleBirth) { float w = EvaluateWidth(0); newTrail.SetFirstPoint(particleInfo.position, particleInfo.velocity, w, time, _calculationStartTime); // Send trail point event to listeners if (_hasTrailPointEventListener) { _eventTrailPoint.Update( trails.Count, 0, particleInfo.position, w, time, _calculationStartTime ); trailPointEvent(_eventTrailPoint); } } trails.Add(newTrail); }
/**************************************************************************** Trail functions ****************************************************************************/ /// <summary> /// Creates a trail and assigns it to a particle. /// </summary> /// <param name="particleInfo">Information about the particle.</param> public void AddTrail (TrailParticleInfo particleInfo) { // Check parent object if (_parentGameObject == null) { _parentGameObject = new GameObject("Playground Trails ("+playgroundSystem.name+")", typeof(PlaygroundTrailParent)); _parentTransform = _parentGameObject.transform; _parentGameObject.GetComponent<PlaygroundTrailParent>().trailsReference = this; } ParticlePlaygroundTrail newTrail = new ParticlePlaygroundTrail(maxPoints); newTrail.trailGameObject = new GameObject("Playground Trail "+particleInfo.particleId); newTrail.trailTransform = newTrail.trailGameObject.transform; newTrail.trailTransform.parent = _parentTransform; newTrail.trailRenderer = newTrail.trailGameObject.AddComponent<MeshRenderer>(); newTrail.trailMeshFilter = newTrail.trailGameObject.AddComponent<MeshFilter>(); newTrail.trailMesh = new Mesh(); newTrail.trailMesh.MarkDynamic(); newTrail.trailMeshFilter.sharedMesh = newTrail.trailMesh; newTrail.trailRenderer.sharedMaterial = material; newTrail.particleId = particleInfo.particleId; if (createFirstPointOnParticleBirth) newTrail.SetFirstPoint(particleInfo.position, particleInfo.velocity, EvaluateWidth(0), time, _calculationStartTime); _trails.Add (newTrail); }
/**************************************************************************** * Internal ****************************************************************************/ void CalculateTrail() { // Iterate through all trails for (int i = 0; i < _trails.Count; i++) { ParticlePlaygroundTrail trail = _trails[i]; // Skip this trail if it's prepared to be removed if (trail.CanRemoveTrail()) { continue; } if (trail.particleId >= 0 && !trail.IsDead()) { if (trail.GetBirthIterator() > 0) { // New point creation float pointDistance = Vector3.Distance(trail.GetParticlePosition(), trail.GetLastAddedPointPosition()); if (pointDistance > minVertexDistance) { float pathDeviationAngle = trail.GetPathDeviation(); if (pointDistance > maxVertexDistance || pathDeviationAngle > maxPathDeviation) { trail.AddPoint(playgroundSystem.particleCache[trail.particleId].position, EvaluateWidth(0), time, _calculationStartTime); } } } else { // First point creation trail.SetFirstPoint(playgroundSystem.particleCache[trail.particleId].position, playgroundSystem.particleCache[trail.particleId].velocity, EvaluateWidth(0), time, _calculationStartTime); } // Set the particle position info trail.SetParticlePosition(playgroundSystem.particleCache[trail.particleId].position); } // Update the trail points for (int x = 0; x < trail.trailPoints.Count; x++) { TrailPoint trailPoint = trail.trailPoints[x]; if (trailPoint.CanRemove()) { trail.RemovePoint(x); if (!_localSpace) { continue; } } float normalizedLifetime = trailPoint.GetNormalizedLifetime(); // Update trail points data trailPoint.Update( _calculationStartTime, EvaluateWidth(normalizedLifetime) ); // Set end point to follow particle if (!trail.IsDead() && x == trail.trailPoints.Count - 1) { trailPoint.position = trail.GetParticlePosition(); } // Rotation of trail points Vector3 currentPosition = trailPoint.position; Vector3 nextPosition = x < trail.trailPoints.Count - 1? trail.trailPoints[x + 1].position : currentPosition + (currentPosition - trail.trailPoints[x - 1].position); Vector3 lookDirection = Vector3.up; switch (renderMode) { case TrailRenderMode.Vertical: lookDirection = Vector3.forward; break; case TrailRenderMode.Billboard: lookDirection = (_billboardTransformPosition - currentPosition).normalized; break; } // If this is local space then recompute current & next position based on the local matrix if (_localSpace) { currentPosition = _localMatrix.MultiplyPoint3x4(currentPosition); nextPosition = _localMatrix.MultiplyPoint3x4(nextPosition); } Vector3 dir = renderMode != TrailRenderMode.CustomRenderScale? (Vector3.Cross(lookDirection, nextPosition - currentPosition)).normalized : customRenderScale; Vector3 lPoint = currentPosition + (dir * (trailPoint.width * .5f)); Vector3 rPoint = currentPosition - (dir * (trailPoint.width * .5f)); // Set mesh vertices into the rotated position trail.meshVerticesCache[x * 2] = lPoint; trail.meshVerticesCache[(x * 2) + 1] = rPoint; // Set uv float uvRatio = uvMode == TrailUvMode.Lifetime? normalizedLifetime : (x * 1f) / (trail.GetBirthIterator() - 1); trail.meshUvsCache[x * 2] = new Vector2(uvRatio, 0); trail.meshUvsCache[(x * 2) + 1] = new Vector2(uvRatio, 1f); // Update colors if (colorMode == TrailColorMode.Lifetime) { Color32 color = EvaluateColor(normalizedLifetime); color.a = (byte)(color.a * (pointArrayAlpha.Evaluate((x * 1f) / (trail.GetBirthIterator() - 1)))); trail.SetColor(x, color); } else { trail.SetColor(x, EvaluateColor(i, x)); } } } }
void Update() { // Clamp values maxPoints = Mathf.Clamp(maxPoints, 2, 32767); // Set asynchronous available values if (billboardTransform != null) { _billboardTransformPosition = billboardTransform.position; } // Early out if no particles exist yet if (playgroundSystem == null || !playgroundSystem.IsReady() || playgroundSystem.IsSettingParticleCount() || playgroundSystem.IsSettingLifetime() || playgroundSystem.particleCache == null || playgroundSystem.particleCache.Length == 0) { return; } // Reset trails if a crucial state is changed if (_currentParticleCount != playgroundSystem.particleCount || _currentParticleMinLifetime != playgroundSystem.lifetimeMin || _currentParticleMaxLifetime != playgroundSystem.lifetime || _localSpace != (playgroundSystem.shurikenParticleSystem.simulationSpace == ParticleSystemSimulationSpace.Local)) { ResetTrails(); } // Set calculation matrix if this is local space if (_localSpace) { _localMatrix.SetTRS(playgroundSystem.particleSystemTransform.position, playgroundSystem.particleSystemTransform.rotation, playgroundSystem.particleSystemTransform.lossyScale); } // Check material if (material != _materialCache) { SetMaterial(material); } // Remove any trails that has ended if (_isDoneThread) { for (int i = 0; i < _trails.Count; i++) { if (_trails[i].trailPoints != null && _trails[i].trailPoints.Count > 1 && _trails[i].trailPoints[_trails[i].trailPoints.Count - 1] != null && _trails[i].CanRemoveTrail()) { RemoveTrail(i); i--; if (i < 0) { i = 0; } continue; } } } // Consume the particle birth queue while (_birthQueue.Count > 0) { AddTrail(_birthQueue.Dequeue()); } // Update all trail meshes and their render settings for (int i = 0; i < _trails.Count; i++) { ParticlePlaygroundTrail trail = _trails[i]; // Set shadow casting/receiving trail.trailRenderer.receiveShadows = receiveShadows; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 trail.trailRenderer.castShadows = castShadows; #else trail.trailRenderer.shadowCastingMode = shadowCastingMode; #endif if (_isDoneThread) { trail.UpdateMesh(); } } // Finally calculate all trails if (multithreading) { if (_isDoneThread) { _calculationStartTime = Application.isPlaying? Time.time : Time.realtimeSinceStartup; _isDoneThread = false; PlaygroundC.RunAsync(() => { lock (_locker) { if (_isDoneThread) { return; } CalculateTrail(); _isDoneThread = true; } }); } } else { _calculationStartTime = Application.isPlaying? Time.time : Time.realtimeSinceStartup; CalculateTrail(); } }
void Update() { // Clamp values maxPoints = Mathf.Clamp(maxPoints, 2, 32767); // Set asynchronous available values if (billboardTransform != null) { _billboardTransformPosition = billboardTransform.position; } // Early out if no particles exist yet if (playgroundSystem == null || !playgroundSystem.IsReady() || playgroundSystem.IsSettingParticleCount() || playgroundSystem.IsSettingLifetime() || playgroundSystem.particleCache == null || playgroundSystem.particleCache.Length == 0) { return; } // Reset trails if a crucial state is changed if (_currentParticleCount != playgroundSystem.particleCount || _currentLoopValue != playgroundSystem.loop || _currentParticleMinLifetime != playgroundSystem.lifetimeMin || _currentParticleMaxLifetime != playgroundSystem.lifetime || _localSpace != (playgroundSystem.shurikenParticleSystem.simulationSpace == ParticleSystemSimulationSpace.Local)) { ResetTrails(); } // Set calculation matrix if this is local space if (_localSpace) { _localMatrix.SetTRS(playgroundSystem.particleSystemTransform.position, playgroundSystem.particleSystemTransform.rotation, playgroundSystem.particleSystemTransform.lossyScale); } // Check material if (material != _materialCache) { SetMaterial(material); } // Consume the particle birth queue while (_isDoneThread && _birthQueue.Count > 0) { // Reuse or create new trails bool canReuseTrail = trails.Count > 0 && _trailReuseQueue.Count > 0; int reuseQueuePeekValue = canReuseTrail? _trailReuseQueue.Peek() : 0; if (canReuseTrail && (reuseQueuePeekValue < 0 || reuseQueuePeekValue > trails.Count - 1 || !trails[reuseQueuePeekValue].IsQueuedForReuse())) { _trailReuseQueue.Dequeue(); canReuseTrail = false; } if (canReuseTrail) { ReuseTrail(_trailReuseQueue.Dequeue(), _birthQueue.Dequeue()); } else { CreateTrail(_birthQueue.Dequeue()); } } // Remove any trails that has ended if (_isDoneThread) { for (int i = 0; i < trails.Count; i++) { if (trails[i].trailPoints != null && trails[i].trailPoints.Count > 1 && trails[i].trailPoints[trails[i].trailPoints.Count - 1] != null && trails[i].CanRemoveTrail()) { if (!trails[i].IsQueuedForReuse()) { _trailReuseQueue.Enqueue(i); trails[i].QueueForReuse(); } } } } // Update all trail meshes and their render settings for (int i = 0; i < trails.Count; i++) { ParticlePlaygroundTrail trail = trails[i]; // Set shadow casting/receiving trail.trailRenderer.receiveShadows = receiveShadows; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 trail.trailRenderer.castShadows = castShadows; #else trail.trailRenderer.shadowCastingMode = shadowCastingMode; #endif if (_isDoneThread && !trail.IsQueuedForReuse()) { trail.UpdateMesh(); } } // Check if there's any event listeners _hasTrailPointEventListener = trailPointEvent != null; // Check that the calculation delegate is assigned if (_trailsCalculationAction == null) { _trailsCalculationAction = CalculationDelegate; } _calculationStartTime = Application.isPlaying? Time.time : Time.realtimeSinceStartup; // Finally calculate all trails if (multithreading) { if (_isDoneThread) { _isDoneThread = false; PlaygroundC.RunAsync(_trailsCalculationAction); } } else { _isDoneThread = false; _trailsCalculationAction(); } }