diff --git a/ParticleSimulation/ParticleNode.tscn b/ParticleSimulation/ParticleNode.tscn index 4f37f7e..450c027 100644 --- a/ParticleSimulation/ParticleNode.tscn +++ b/ParticleSimulation/ParticleNode.tscn @@ -3,7 +3,6 @@ [ext_resource path="res://ParticleSimulation/ParticleNode.cs" type="Script" id=1] [ext_resource path="res://textures/particle_noborder.png" type="Texture" id=2] - [node name="ParticleNode" type="Node2D"] script = ExtResource( 1 ) diff --git a/ParticleSimulation/ParticleSimulationScene.cs b/ParticleSimulation/ParticleSimulationScene.cs index b9a66fa..92b3fa3 100644 --- a/ParticleSimulation/ParticleSimulationScene.cs +++ b/ParticleSimulation/ParticleSimulationScene.cs @@ -21,8 +21,13 @@ public class ParticleSimulationScene : Node2D private ParticleSimulation _particleSimulation; public float PhysicsInterpolationFraction; + private bool _wasInteractPrevEnabled; + private Vector2 _prevMousePos; + public void Initialize(int seed, int nParticles, float zoom) { + _wasInteractPrevEnabled = false; + _prevMousePos = new Vector2(); _maxZoom = zoom; _camera = GetNode("Camera2D"); _cameraTween = GetNode("CameraTween"); @@ -51,6 +56,16 @@ public class ParticleSimulationScene : Node2D if (Input.IsActionJustPressed("reset")) GetParent
().RestartSimulation(); + if (Input.IsActionPressed("enable_interaction")) + { + GetNode("InteractionCircleSprite").Show(); + GetNode("InteractionCircleSprite").Position = GetGlobalMousePosition(); + } + else + { + GetNode("InteractionCircleSprite").Hide(); + } + var shouldTweenStop = false; if (Input.IsActionJustReleased("zoom_in")) @@ -136,6 +151,27 @@ public class ParticleSimulationScene : Node2D public override void _PhysicsProcess(float delta) { + if (Input.IsActionPressed("enable_interaction")) + { + if (_wasInteractPrevEnabled) + { + var mouseVel = GetGlobalMousePosition() - _prevMousePos; + mouseVel /= 5f; + + _prevMousePos = GetGlobalMousePosition(); + _particleSimulation.SetInteractionCircle(GetGlobalMousePosition() + (_spaceSize / 2.0f), 70f, mouseVel); + } + else + { + _wasInteractPrevEnabled = true; + _prevMousePos = GetGlobalMousePosition(); + _particleSimulation.SetInteractionCircle(GetGlobalMousePosition() + (_spaceSize / 2.0f), 70f, Vector2.Zero); + } + } + else + { + _wasInteractPrevEnabled = false; + } _particleSimulation.Update(); foreach (var id in _particleSimulation.LastParticlesRemoved) { diff --git a/ParticleSimulation/ParticleSimulationScene.tscn b/ParticleSimulation/ParticleSimulationScene.tscn index 60c7f12..ca056e4 100644 --- a/ParticleSimulation/ParticleSimulationScene.tscn +++ b/ParticleSimulation/ParticleSimulationScene.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=2] +[gd_scene load_steps=3 format=2] [ext_resource path="res://ParticleSimulation/ParticleSimulationScene.cs" type="Script" id=1] +[ext_resource path="res://textures/interaction_circle.png" type="Texture" id=2] [node name="ParticleSimulationScene" type="Node2D"] script = ExtResource( 1 ) @@ -13,3 +14,8 @@ zoom = Vector2( 1.35, 1.35 ) smoothing_speed = 100.0 [node name="CameraTween" type="Tween" parent="."] + +[node name="InteractionCircleSprite" type="Sprite" parent="."] +modulate = Color( 1, 1, 1, 0.313726 ) +scale = Vector2( 0.75, 0.75 ) +texture = ExtResource( 2 ) diff --git a/ParticleSimulation/Simulation/ParticleSimulation.cs b/ParticleSimulation/Simulation/ParticleSimulation.cs index 2cb8e22..c59e81c 100644 --- a/ParticleSimulation/Simulation/ParticleSimulation.cs +++ b/ParticleSimulation/Simulation/ParticleSimulation.cs @@ -13,27 +13,27 @@ namespace Particles.ParticleSimulation { // size of simulation space public Vector2 SpaceSize; - + // dictionary of particles with particle Id being the key private readonly Dictionary _particles = new Dictionary(); - + private readonly List _particleTypes = new List(); - + // task list if multi-threaded - #if MULTITHREADED - private readonly List _tasks = new List(); - #endif - +#if MULTITHREADED + private readonly List _tasks = new List(); +#endif + // updated on every simulation update public List LastParticlesAdded { get; private set; } = new List(); public List LastParticlesRemoved { get; private set; } = new List(); // counts up for each particle added private int _idCount; - + private int _maxParticles; private const int MaxParticleTypes = 10; - + private const float HealthDelta = 0.005f; private const float NegativeHealthMultiplier = 2f; private const float PositiveHealthMultiplier = 4f; @@ -51,21 +51,21 @@ namespace Particles.ParticleSimulation { LastParticlesRemoved.Clear(); LastParticlesAdded.Clear(); - + // update all particles - #if MULTITHREADED - _tasks.Clear(); - foreach (var id in _particles.Keys) - _tasks.Add(Task.Factory.StartNew(UpdateParticle, id)); - Task.WaitAll(_tasks.ToArray()); - #else +#if MULTITHREADED + _tasks.Clear(); + foreach (var id in _particles.Keys) + _tasks.Add(Task.Factory.StartNew(UpdateParticle, id)); + Task.WaitAll(_tasks.ToArray()); +#else foreach (var id in _particles.Keys) UpdateParticle(id); - #endif - +#endif + // used to ensure only one particle is moved per update var movedParticle = false; - + foreach (var particle in _particles.Select(p => p.Value)) { particle.WasTeleportedLast = false; @@ -80,10 +80,11 @@ namespace Particles.ParticleSimulation continue; } } + var position = particle.Position; particle.Velocity = particle.Velocity.Clamped(5f); position += particle.Velocity; - particle.Velocity *= 0.855f; // friction + particle.Velocity *= 0.855f; // friction if (position.x > SpaceSize.x) { position.x -= SpaceSize.x; @@ -105,6 +106,7 @@ namespace Particles.ParticleSimulation position.y += SpaceSize.y; particle.WasTeleportedLast = true; } + /* particle.AddAverageSpeedValue(particle.Velocity.Length()); @@ -164,8 +166,8 @@ namespace Particles.ParticleSimulation foreach (var type1 in _particleTypes) foreach (var type2 in _particleTypes) type1.AddRelationship(type2, - new ParticleRelationshipProps(ParticleCollisionRadius, (float) GD.RandRange(25, 55), - (float)GD.RandRange(-0.675, 0.7))); + new ParticleRelationshipProps(ParticleCollisionRadius, (float) GD.RandRange(25, 55), + (float) GD.RandRange(-0.675, 0.7))); } private void CreateRandomParticle() @@ -194,7 +196,7 @@ namespace Particles.ParticleSimulation (float) GD.RandRange(0, SpaceSize.y)); return position; } - + private Vector2 GetScreenWrapPosition(Vector2 p1, Vector2 p2) { var newPosition = p2; @@ -208,15 +210,15 @@ namespace Particles.ParticleSimulation newPosition.y = p2.y + SpaceSize.y; return newPosition; } - + public Particle GetParticle(int id) { return _particles[id]; } - + private void UpdateParticle(object i) { - var id = (int)i; + var id = (int) i; var particle1 = _particles[id]; var closeCount = 0; foreach (var p2 in _particles) @@ -231,11 +233,11 @@ namespace Particles.ParticleSimulation if (distanceSquared < (35f * 35f)) closeCount++; - + // collision force float distance; Vector2 direction; - + if (distanceSquared < (ParticleCollisionRadius * ParticleCollisionRadius)) { direction = particle1.Position.DirectionTo(position); @@ -243,7 +245,7 @@ namespace Particles.ParticleSimulation var collisionForce = 1f / (0.35f + Mathf.Pow(Mathf.E, -1.15f * (distance - 12f))) - 1f / 0.35f; particle1.Velocity += direction * collisionForce; } - + // particle relationship force var props = particle1.Type.GetRelationship(particle2.Type); if (props.Force != 0f && distanceSquared >= props.MinRadius * props.MinRadius && @@ -257,34 +259,49 @@ namespace Particles.ParticleSimulation { if (distance <= mid) { - particleForce = 1f / ((1f / props.Force) + Mathf.Pow(Mathf.E, -4 * (distance - props.MinRadius - 1f))); + particleForce = 1f / ((1f / props.Force) + + Mathf.Pow(Mathf.E, -4 * (distance - props.MinRadius - 1f))); } else { - particleForce = 1f / ((1f / props.Force) + Mathf.Pow(Mathf.E, 4 * (distance - props.MaxRadius + 1f))); + particleForce = 1f / ((1f / props.Force) + + Mathf.Pow(Mathf.E, 4 * (distance - props.MaxRadius + 1f))); } } else { if (distance <= mid) { - particleForce = -1f / ((-1f / props.Force) + Mathf.Pow(Mathf.E, -4 * (distance - props.MinRadius - 1f))); + particleForce = -1f / ((-1f / props.Force) + + Mathf.Pow(Mathf.E, -4 * (distance - props.MinRadius - 1f))); } else { - particleForce = -1f / ((-1f / props.Force) + Mathf.Pow(Mathf.E, 4 * (distance - props.MaxRadius + 1f))); + particleForce = -1f / ((-1f / props.Force) + + Mathf.Pow(Mathf.E, 4 * (distance - props.MaxRadius + 1f))); } } - - + + particle1.Velocity += direction * particleForce; } } - + if (closeCount > 70) particle1.Health -= HealthDelta * NegativeHealthMultiplier; else particle1.Health += HealthDelta * PositiveHealthMultiplier; } + + public void SetInteractionCircle(Vector2 position, float radius, Vector2 velocity) + { + foreach (var p in _particles.Select(i => i.Value)) + { + if (position.DistanceTo(p.Position) <= radius) + { + p.Velocity += velocity; + } + } + } } } diff --git a/project.godot b/project.godot index 1463a46..1d20818 100644 --- a/project.godot +++ b/project.godot @@ -73,6 +73,11 @@ key_zoom_out={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":81,"physical_scancode":0,"unicode":0,"echo":false,"script":null) ] } +enable_interaction={ +"deadzone": 0.5, +"events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) + ] +} [mono] diff --git a/textures/interaction_circle.png b/textures/interaction_circle.png new file mode 100644 index 0000000..90053d4 Binary files /dev/null and b/textures/interaction_circle.png differ diff --git a/textures/interaction_circle.png.import b/textures/interaction_circle.png.import new file mode 100644 index 0000000..9d92fcd --- /dev/null +++ b/textures/interaction_circle.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/interaction_circle.png-10aab2660a95f68c88825c76c59d9be8.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://textures/interaction_circle.png" +dest_files=[ "res://.import/interaction_circle.png-10aab2660a95f68c88825c76c59d9be8.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0