diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index d61e959..faeeae8 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -71,6 +71,7 @@ namespace GlitchyEditor.EditWindows ShowComponentEditor("Rigidbody 2D", entity, => ShowRigidBody2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Box collider 2D", entity, => ShowBoxCollider2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Polygon collider 2D", entity, => ShowPolygonCollider2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Script Component", entity, => ShowScriptComponentEditor, => ShowComponentContextMenu); ShowAddComponentButton(entity); @@ -399,7 +400,7 @@ namespace GlitchyEditor.EditWindows boxCollider.Offset = offset; float2 size = boxCollider.Size; - if (ImGui.Editfloat2("Size", ref size, .Zero, 0.1f, textWidth)) + if (ImGui.Editfloat2("Size", ref size, .Zero, 0.1f, textWidth, float2(0.01f, 0.01f), float.PositiveInfinity.XX)) boxCollider.Size = size; float density = boxCollider.Density; @@ -424,7 +425,6 @@ namespace GlitchyEditor.EditWindows float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; - float2 offset = circleCollider.Offset; if (ImGui.Editfloat2("Offset", ref offset, .Zero, 0.1f, textWidth)) circleCollider.Offset = offset; @@ -449,6 +449,51 @@ namespace GlitchyEditor.EditWindows if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) circleCollider.RestitutionThreshold = restitutionThreshold; } + + private static void ShowPolygonCollider2DComponentEditor(Entity entity, PolygonCollider2DComponent* polygonCollider) + { + float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; + textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; + + float2 offset = polygonCollider.Offset; + if (ImGui.Editfloat2("Offset", ref offset, .Zero, 0.1f, textWidth)) + polygonCollider.Offset = offset; + + // TODO: Handles in editor, etc... + if (ImGui.CollapsingHeader("Vertices")) + { + for (int i < polygonCollider.VertexCount) + { + ImGui.Editfloat2(scope $"{i}", ref polygonCollider.Vertices[i]); + } + } + + ImGui.BeginDisabled(polygonCollider.VertexCount >= 8); + if (ImGui.Button("Add Vertex")) + polygonCollider.VertexCount++; + ImGui.EndDisabled(); + + ImGui.BeginDisabled(polygonCollider.VertexCount <= 3); + if (ImGui.Button("Remove Vertex")) + polygonCollider.VertexCount--; + ImGui.EndDisabled(); + + float density = polygonCollider.Density; + if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) + polygonCollider.Density = density; + + float friction = polygonCollider.Friction; + if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) + polygonCollider.Friction = friction; + + float restitution = polygonCollider.Restitution; + if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) + polygonCollider.Restitution = restitution; + + float restitutionThreshold = polygonCollider.RestitutionThreshold; + if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) + polygonCollider.RestitutionThreshold = restitutionThreshold; + } private static void ShowScriptComponentEditor(Entity entity, ScriptComponent* scriptComponent) { @@ -1158,6 +1203,7 @@ namespace GlitchyEditor.EditWindows ShowComponentButton("Rigidbody 2D"); ShowComponentButton("Box collider 2D"); ShowComponentButton("Circle collider 2D"); + ShowComponentButton("Polygon collider 2D"); ShowComponentButton("Mesh"); ShowComponentButton("Mesh Renderer"); ShowComponentButton("C# Script"); diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index c28ed01..2d96ad1 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -405,12 +405,31 @@ namespace GlitchyEditor for (var (entity, transform, collider) in _activeScene.GetEntities()) { - Renderer2D.DrawRect(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Size.X * 2, collider.Size.Y * 2, 0), .Green); + Renderer2D.DrawRect(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Size.X * 2, collider.Size.Y * 2, 0), .Green, entity.Index); } for (var (entity, transform, collider) in _activeScene.GetEntities()) { - Renderer2D.DrawCircle(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Radius * 2), (Texture2D)null, ColorRGBA(0f, 1f, 0f), 0.01f); + Renderer2D.DrawCircle(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Radius * 2), (Texture2D)null, ColorRGBA(0f, 1f, 0f), 0.01f, .(0, 0, 1, 1), entity.Index); + } + + for (var (entity, transform, collider) in _activeScene.GetEntities()) + { + Matrix colliderTransform = transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0); + + float3 firstPosition = (colliderTransform * float4(collider.Vertices[0], 0, 1)).XYZ; + float3 lastPosition = firstPosition; + + for (int i = 1; i < collider.VertexCount; i++) + { + float3 position = (colliderTransform * float4(collider.Vertices[i], 0, 1)).XYZ; + + Renderer2D.DrawLine(lastPosition, position, .Green, entity.Index); + + lastPosition = position; + } + + Renderer2D.DrawLine(lastPosition, firstPosition, .Green, entity.Index); } } diff --git a/GlitchyEngine/src/World/Components/Components.bf b/GlitchyEngine/src/World/Components/Components.bf index c2318ce..9979e83 100644 --- a/GlitchyEngine/src/World/Components/Components.bf +++ b/GlitchyEngine/src/World/Components/Components.bf @@ -505,6 +505,34 @@ namespace GlitchyEngine.World internal ref b2Vec2 b2Offset mut => ref *(Box2D.b2Vec2*)(void*)&Offset; } + // Todo: This component is rather large (> 1 Cache line) + struct PolygonCollider2DComponent + { + public float2 Offset = .(0.0f, 0.0f); + + // TODO: move into 2D physics material + public float Density = 1.0f; + public float Friction = 0.5f; + public float Restitution = 0.0f; + public float RestitutionThreshold = 0.5f; + + public float2[8] Vertices = .(); + public int8 VertexCount = 3; + + private int _runtimeFixture = 0; + + internal b2Fixture* RuntimeFixture + { + [Inline] + get => (b2Fixture*)(void*)_runtimeFixture; + [Inline] + set mut => _runtimeFixture = (int)(void*)value; + } + + [Inline] + internal ref b2Vec2 b2Offset mut => ref *(Box2D.b2Vec2*)(void*)&Offset; + } + struct ScriptComponent : IDisposableComponent { private String _scriptClassName = null; diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index 7a3bed6..cbf4fc9 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -88,6 +88,7 @@ namespace GlitchyEngine.World CopyComponents(this, target); CopyComponents(this, target); CopyComponents(this, target); + CopyComponents(this, target); // Copy ScriptComponents... needs extra handling for the script instances for (let (sourceHandle, sourceComponent) in _ecsWorld.Enumerate()) @@ -365,6 +366,13 @@ namespace GlitchyEngine.World InitCircleCollider2D(entity, circleCollider); } + + for (let (ecsHandle, polygonCollider) in _ecsWorld.Enumerate()) + { + Entity entity = .(ecsHandle, this); + + InitPolygonCollider2D(entity, polygonCollider); + } } private void InitializeRigidbody2D(Entity entity, Rigidbody2DComponent* rigidBody) @@ -426,8 +434,6 @@ namespace GlitchyEngine.World let localToWorld = entity.Transform.WorldTransform; let worldToRigidbody = entityWithRigidbody.Transform.WorldTransform.Invert(); - let boxShape = Box2D.Shape.CreatePolygon(); - Box2D.b2Vec2[4] points = .( .(-1, -1), .( 1, -1), @@ -444,6 +450,7 @@ namespace GlitchyEngine.World points[i] = (worldToRigidbody * localToWorld * float4(points[i], 0, 1)).XY; } + let boxShape = Box2D.Shape.CreatePolygon(); Shape.PolygonSet(boxShape, &points, 4); b2FixtureDef fixtureDef = .(); @@ -486,6 +493,42 @@ namespace GlitchyEngine.World circleCollider.RuntimeFixture = fixture; } + private void InitPolygonCollider2D(Entity entity, PolygonCollider2DComponent* polygonCollider) + { + if (FindParentWithRigidbody(entity, let entityWithRigidbody, let rigidbody, let parentToRigidbody) case .Err) + return; + + let localToWorld = entity.Transform.WorldTransform; + let worldToRigidbody = entityWithRigidbody.Transform.WorldTransform.Invert(); + + Box2D.b2Vec2[8] points = .(); + + for (int i < polygonCollider.VertexCount) + { + points[i] = ((float2)polygonCollider.Vertices[i] + polygonCollider.Offset); + + if (entity == entityWithRigidbody) + points[i] = entity.Transform.Scale.XY * (float2)points[i]; + + points[i] = (worldToRigidbody * localToWorld * float4(points[i], 0, 1)).XY; + } + + let polygonShape = Box2D.Shape.CreatePolygon(); + Shape.PolygonSet(polygonShape, &points, polygonCollider.VertexCount); + + b2FixtureDef fixtureDef = .(); + fixtureDef.shape = polygonShape; + fixtureDef.density = polygonCollider.Density; + fixtureDef.friction = polygonCollider.Friction; + fixtureDef.restitution = polygonCollider.Restitution; + fixtureDef.restitutionThreshold = polygonCollider.RestitutionThreshold; + fixtureDef.userData = (void*)(uint)entity.Handle; + + Log.EngineLogger.AssertDebug(rigidbody.RuntimeBody != null); + b2Fixture* fixture = Box2D.Body.CreateFixture(rigidbody.RuntimeBody, &fixtureDef); + polygonCollider.RuntimeFixture = fixture; + } + public void OnSimulationStop() { Box2D.World.Delete(_physicsWorld2D); diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index 5ccb01a..cb15e5b 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -177,6 +177,19 @@ class SceneSerializer Serialize.Value(writer, "RestitutionThreshold", component.RestitutionThreshold); }); + SerializeComponent(writer, entity, "PolygonCollider2D", scope (component) => + { + Serialize.Value(writer, "Offset", component.Offset); + + Serialize.Value(writer, "Vertices", component.Vertices); + Serialize.Value(writer, "VertexCount", component.VertexCount); + + Serialize.Value(writer, "Density", component.Density); + Serialize.Value(writer, "Friction", component.Friction); + Serialize.Value(writer, "Restitution", component.Restitution); + Serialize.Value(writer, "RestitutionThreshold", component.RestitutionThreshold); + }); + SerializeComponent(writer, entity, "MeshComponent", scope (component) => { Serialize.Value(writer, "Mesh", component.Mesh); @@ -526,6 +539,28 @@ class SceneSerializer Deserialize.Value(reader, "Radius", out component.Radius); reader.EntryEnd(); + Deserialize.Value(reader, "Density", out component.Density); + reader.EntryEnd(); + Deserialize.Value(reader, "Friction", out component.Friction); + reader.EntryEnd(); + Deserialize.Value(reader, "Restitution", out component.Restitution); + reader.EntryEnd(); + Deserialize.Value(reader, "RestitutionThreshold", out component.RestitutionThreshold); + + return .Ok; + })); + case "PolygonCollider2D": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Deserialize.Value(reader, "Offset", out component.Offset); + reader.EntryEnd(); + + Deserialize.Value(reader, "Vertices", out component.Vertices); + reader.EntryEnd(); + Deserialize.Value(reader, "VertexCount", out component.VertexCount); + reader.EntryEnd(); + + Deserialize.Value(reader, "Density", out component.Density); reader.EntryEnd(); Deserialize.Value(reader, "Friction", out component.Friction);