From c5ece4c478d2c771476830f235925eb299fdabf2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 21 May 2026 17:19:03 +0200
Subject: [PATCH 1/4] Use actual connection order for triangle extraction
Agent-Logs-Url: https://github.com/chrxh/alien/sessions/3fc550f8-0c1d-45f8-91e1-1367a8dc132a
Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com>
---
source/EngineKernels/GeometryKernels.cu | 81 +++++++++---
source/EngineTests/GeometryTests.cpp | 163 ++++++++++++++++++++++++
2 files changed, 229 insertions(+), 15 deletions(-)
diff --git a/source/EngineKernels/GeometryKernels.cu b/source/EngineKernels/GeometryKernels.cu
index 17446edd8b..6b115f5fc2 100644
--- a/source/EngineKernels/GeometryKernels.cu
+++ b/source/EngineKernels/GeometryKernels.cu
@@ -124,6 +124,50 @@ namespace
return pos.x >= context.visibleTopLeft.x - context.cullingMargin && pos.x <= context.visibleBottomRight.x + context.cullingMargin
&& pos.y >= context.visibleTopLeft.y - context.cullingMargin && pos.y <= context.visibleBottomRight.y + context.cullingMargin;
}
+
+ __device__ __inline__ float getActualConnectionAngle(SimulationData const& data, Object* object, int connectionIndex)
+ {
+ auto displacement = data.objectMap.getCorrectedDirection(object->connections[connectionIndex].object->pos - object->pos);
+ return Math::angleOfVector(displacement);
+ }
+
+ __device__ __inline__ void
+ getSortedConnectionIndicesByActualAngle(SimulationData const& data, Object* object, int sortedConnectionIndices[MAX_OBJECT_CONNECTIONS])
+ {
+ for (int i = 0; i < object->numConnections; ++i) {
+ sortedConnectionIndices[i] = i;
+ }
+ for (int i = 1; i < object->numConnections; ++i) {
+ auto currentConnectionIndex = sortedConnectionIndices[i];
+ auto currentAngle = getActualConnectionAngle(data, object, currentConnectionIndex);
+ auto currentObjectId = object->connections[currentConnectionIndex].object->id;
+
+ auto insertIndex = i;
+ while (insertIndex > 0) {
+ auto previousConnectionIndex = sortedConnectionIndices[insertIndex - 1];
+ auto previousAngle = getActualConnectionAngle(data, object, previousConnectionIndex);
+ auto previousObjectId = object->connections[previousConnectionIndex].object->id;
+ if (previousAngle < currentAngle || (fabsf(previousAngle - currentAngle) < NEAR_ZERO && previousObjectId < currentObjectId)) {
+ break;
+ }
+ sortedConnectionIndices[insertIndex] = previousConnectionIndex;
+ --insertIndex;
+ }
+ sortedConnectionIndices[insertIndex] = currentConnectionIndex;
+ }
+ }
+
+ __device__ __inline__ int
+ getSortedConnectionPosition(SimulationData const& data, Object* object, Object* connectedObject, int sortedConnectionIndices[MAX_OBJECT_CONNECTIONS])
+ {
+ getSortedConnectionIndicesByActualAngle(data, object, sortedConnectionIndices);
+ for (int sortedIndex = 0; sortedIndex < object->numConnections; ++sortedIndex) {
+ if (object->connections[sortedConnectionIndices[sortedIndex]].object == connectedObject) {
+ return sortedIndex;
+ }
+ }
+ return 0;
+ }
}
__global__ void cudaExtractObjectData(SimulationData data, ObjectVertexData* objectData, uint64_t* numObjects, GeometryExtractionContext context)
@@ -267,31 +311,38 @@ __global__ void cudaExtractTriangleIndices(SimulationData data, unsigned int* tr
if (object->numConnections <= 1) {
continue;
}
- bool first = true;
- int backIndices[MAX_OBJECT_CONNECTIONS];
- for (int i = 0, numConnections = object->numConnections; i < numConnections + 1; ++i) {
- auto connectionIndex = i % numConnections;
+ int sortedConnectionIndices[MAX_OBJECT_CONNECTIONS];
+ getSortedConnectionIndicesByActualAngle(data, object, sortedConnectionIndices);
+ for (int sortedIndex = 0, numConnections = object->numConnections; sortedIndex < numConnections; ++sortedIndex) {
+ auto connectionIndex = sortedConnectionIndices[sortedIndex];
+ auto prevIndex = sortedConnectionIndices[(sortedIndex + numConnections - 1) % numConnections];
auto const& connectedObject = object->connections[connectionIndex].object;
- auto backIndex = connectedObject->getConnectionIndex(object);
- backIndices[connectionIndex] = backIndex;
- if (first) {
- first = false;
- continue;
- }
- auto prevIndex = (connectionIndex + numConnections - 1) % numConnections;
auto const& prevConnectedObject = object->connections[prevIndex].object;
- auto prevBackIndex = backIndices[prevIndex];
+
+ int connectedSortedConnectionIndices[MAX_OBJECT_CONNECTIONS];
+ auto backIndex = getSortedConnectionPosition(data, connectedObject, object, connectedSortedConnectionIndices);
+ int prevConnectedSortedConnectionIndices[MAX_OBJECT_CONNECTIONS];
+ auto prevBackIndex = getSortedConnectionPosition(data, prevConnectedObject, object, prevConnectedSortedConnectionIndices);
// Triangle?
- if (prevConnectedObject->getConnectedObject(prevBackIndex - 1) == connectedObject) {
+ if (prevConnectedObject
+ ->connections
+ [prevConnectedSortedConnectionIndices[(prevBackIndex + prevConnectedObject->numConnections - 1) % prevConnectedObject->numConnections]]
+ .object
+ == connectedObject) {
if (object->id < connectedObject->id && object->id < prevConnectedObject->id) {
addTriangle(object, object->tempValue1.as_uint64, prevConnectedObject, connectedObject);
}
}
// Rectangle?
- auto fourthCellCandidate1 = connectedObject->getConnectedObject(backIndex + 1);
- auto fourthCellCandidate2 = prevConnectedObject->getConnectedObject(prevBackIndex - 1);
+ auto fourthCellCandidate1 =
+ connectedObject->connections[connectedSortedConnectionIndices[(backIndex + 1) % connectedObject->numConnections]].object;
+ auto fourthCellCandidate2 =
+ prevConnectedObject
+ ->connections
+ [prevConnectedSortedConnectionIndices[(prevBackIndex + prevConnectedObject->numConnections - 1) % prevConnectedObject->numConnections]]
+ .object;
if (fourthCellCandidate2 == fourthCellCandidate1 && fourthCellCandidate1 != object && fourthCellCandidate2 != object
&& connectedObject != prevConnectedObject) {
if (object->id < connectedObject->id && object->id < prevConnectedObject->id && object->id < fourthCellCandidate2->id) {
diff --git a/source/EngineTests/GeometryTests.cpp b/source/EngineTests/GeometryTests.cpp
index 63520d6426..4a0f3b77d9 100644
--- a/source/EngineTests/GeometryTests.cpp
+++ b/source/EngineTests/GeometryTests.cpp
@@ -5,6 +5,9 @@
#include
+#include
+#include
+#include
#include
#include
#include
@@ -35,6 +38,78 @@ class GeometryTests : public IntegrationTestFramework
}
protected:
+ GLuint createShader(GLenum type, std::string_view source)
+ {
+ auto shader = glCreateShader(type);
+ auto* sourcePtr = source.data();
+ auto sourceLength = static_cast(source.size());
+ glShaderSource(shader, 1, &sourcePtr, &sourceLength);
+ glCompileShader(shader);
+ return shader;
+ }
+
+ GLuint createTriangleShaderProgram()
+ {
+ auto vertexShader = createShader(GL_VERTEX_SHADER, Shaders::TriangleVS);
+ auto geometryShader = createShader(GL_GEOMETRY_SHADER, Shaders::TriangleGS);
+ auto fragmentShader = createShader(GL_FRAGMENT_SHADER, Shaders::TriangleFS);
+
+ auto program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, geometryShader);
+ glAttachShader(program, fragmentShader);
+ glLinkProgram(program);
+
+ glDeleteShader(vertexShader);
+ glDeleteShader(geometryShader);
+ glDeleteShader(fragmentShader);
+
+ return program;
+ }
+
+ void setupTriangleVao(GeometryBuffers const& geometryBuffers)
+ {
+ glBindVertexArray(geometryBuffers->getVaoForTriangles());
+ glBindBuffer(GL_ARRAY_BUFFER, geometryBuffers->getVboForObjects());
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(ObjectVertexData), (void*)0);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(ObjectVertexData), (void*)(3 * sizeof(float)));
+ glEnableVertexAttribArray(1);
+ glVertexAttribIPointer(2, 1, GL_INT, sizeof(ObjectVertexData), (void*)(6 * sizeof(float)));
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(ObjectVertexData), (void*)(6 * sizeof(float) + sizeof(int)));
+ glEnableVertexAttribArray(3);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryBuffers->getEboForTriangles());
+ }
+
+ int renderTrianglePixels(GeometryBuffers const& geometryBuffers)
+ {
+ setupTriangleVao(geometryBuffers);
+
+ auto program = createTriangleShaderProgram();
+ glUseProgram(program);
+ glUniform1f(glGetUniformLocation(program, "zoom"), 50.0f);
+ glUniform2f(glGetUniformLocation(program, "worldSize"), 1000.0f, 1000.0f);
+ glUniform2f(glGetUniformLocation(program, "rectUpperLeft"), 99.5f, 99.5f);
+ glUniform2f(glGetUniformLocation(program, "viewportSize"), 100.0f, 100.0f);
+
+ glViewport(0, 0, 100, 100);
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glDrawElements(GL_TRIANGLES, toInt(geometryBuffers->getNumObjects().triangleIndices), GL_UNSIGNED_INT, 0);
+ glDisable(GL_DEPTH_TEST);
+ glFinish();
+
+ std::vector pixels(100 * 100 * 3);
+ glReadPixels(0, 0, 100, 100, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
+ glDeleteProgram(program);
+
+ return std::count_if(pixels.begin(), pixels.end(), [](unsigned char value) { return value > 0; });
+ }
+
GLFWwindow* _window = nullptr;
};
@@ -216,6 +291,94 @@ TEST_F(GeometryTests, copyBuffers_triangle)
EXPECT_EQ(6u, triangles.size());
}
+TEST_F(GeometryTests, copyBuffers_triangleWithZeroReferenceAngle)
+{
+ auto data = Desc().addCreature({
+ ObjectDesc().id(1).pos({100.0f, 100.0f}),
+ ObjectDesc().id(2).pos({101.0f, 100.0f}),
+ ObjectDesc().id(3).pos({100.5f, 100.866f}),
+ });
+ data.addConnection(1, 2);
+ data.addConnection(2, 3);
+ data.addConnection(3, 1);
+ data.getConnectionRef(1, 2)._angleFromPrevious = 0.0f;
+ data.getConnectionRef(1, 3)._angleFromPrevious = 360.0f;
+
+ _simulationFacade->setSimulationData(data);
+ auto geometryBuffers = _GeometryBuffers::create();
+ RealRect visibleWorldRect{{0, 0}, {1000, 1000}};
+
+ _simulationFacade->tryCopyBuffersFromCudaToOpenGL(geometryBuffers, visibleWorldRect);
+
+ auto numObjects = geometryBuffers->getNumObjects();
+ EXPECT_EQ(3u, numObjects.objects);
+ EXPECT_EQ(6u, numObjects.lineIndices);
+ EXPECT_EQ(6u, numObjects.triangleIndices);
+
+ auto triangles = geometryBuffers->getTriangleIndices();
+ EXPECT_EQ(6u, triangles.size());
+}
+
+TEST_F(GeometryTests, renderTriangleWithZeroReferenceAngle)
+{
+ auto data = Desc().addCreature({
+ ObjectDesc().id(1).pos({100.0f, 100.0f}),
+ ObjectDesc().id(2).pos({101.0f, 100.0f}),
+ ObjectDesc().id(3).pos({100.5f, 100.866f}),
+ });
+ data.addConnection(1, 2);
+ data.addConnection(2, 3);
+ data.addConnection(3, 1);
+ data.getConnectionRef(1, 2)._angleFromPrevious = 0.0f;
+ data.getConnectionRef(1, 3)._angleFromPrevious = 360.0f;
+
+ _simulationFacade->setSimulationData(data);
+ auto geometryBuffers = _GeometryBuffers::create();
+ RealRect visibleWorldRect{{0, 0}, {1000, 1000}};
+
+ _simulationFacade->tryCopyBuffersFromCudaToOpenGL(geometryBuffers, visibleWorldRect);
+
+ EXPECT_GT(renderTrianglePixels(geometryBuffers), 0);
+}
+
+TEST_F(GeometryTests, renderTriangleFanWithZeroReferenceAnglesUsesActualConnectionOrder)
+{
+ auto createTriangleFan = [] {
+ auto result = Desc().addCreature({
+ ObjectDesc().id(1).pos({100.0f, 100.0f}),
+ ObjectDesc().id(2).pos({99.0f, 100.0f}),
+ ObjectDesc().id(3).pos({100.0f, 101.0f}),
+ ObjectDesc().id(4).pos({101.0f, 100.0f}),
+ });
+ result.addConnection(1, 2);
+ result.addConnection(1, 3);
+ result.addConnection(1, 4);
+ result.addConnection(2, 3);
+ result.addConnection(3, 4);
+ return result;
+ };
+
+ auto referenceData = createTriangleFan();
+ _simulationFacade->setSimulationData(referenceData);
+ auto referenceGeometryBuffers = _GeometryBuffers::create();
+ RealRect visibleWorldRect{{0, 0}, {1000, 1000}};
+ _simulationFacade->tryCopyBuffersFromCudaToOpenGL(referenceGeometryBuffers, visibleWorldRect);
+ auto const referencePixels = renderTrianglePixels(referenceGeometryBuffers);
+
+ auto zeroAngleData = createTriangleFan();
+ auto& centerConnections = zeroAngleData.getObjectRef(1)._connections;
+ std::swap(centerConnections.at(1), centerConnections.at(2));
+ centerConnections.at(0)._angleFromPrevious = 180.0f;
+ centerConnections.at(1)._angleFromPrevious = 180.0f;
+ centerConnections.at(2)._angleFromPrevious = 0.0f;
+
+ _simulationFacade->setSimulationData(zeroAngleData);
+ auto geometryBuffers = _GeometryBuffers::create();
+ _simulationFacade->tryCopyBuffersFromCudaToOpenGL(geometryBuffers, visibleWorldRect);
+
+ EXPECT_EQ(referencePixels, renderTrianglePixels(geometryBuffers));
+}
+
TEST_F(GeometryTests, copyBuffers_quad)
{
auto data = Desc().addCreature({
From 178dab3b27899398cefc7f420afc4ff0a59aac8c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 21 May 2026 17:31:50 +0200
Subject: [PATCH 2/4] Remove added zero-angle geometry tests
Agent-Logs-Url: https://github.com/chrxh/alien/sessions/3fc550f8-0c1d-45f8-91e1-1367a8dc132a
Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com>
---
source/EngineTests/GeometryTests.cpp | 163 ---------------------------
1 file changed, 163 deletions(-)
diff --git a/source/EngineTests/GeometryTests.cpp b/source/EngineTests/GeometryTests.cpp
index 4a0f3b77d9..63520d6426 100644
--- a/source/EngineTests/GeometryTests.cpp
+++ b/source/EngineTests/GeometryTests.cpp
@@ -5,9 +5,6 @@
#include
-#include
-#include
-#include
#include
#include
#include
@@ -38,78 +35,6 @@ class GeometryTests : public IntegrationTestFramework
}
protected:
- GLuint createShader(GLenum type, std::string_view source)
- {
- auto shader = glCreateShader(type);
- auto* sourcePtr = source.data();
- auto sourceLength = static_cast(source.size());
- glShaderSource(shader, 1, &sourcePtr, &sourceLength);
- glCompileShader(shader);
- return shader;
- }
-
- GLuint createTriangleShaderProgram()
- {
- auto vertexShader = createShader(GL_VERTEX_SHADER, Shaders::TriangleVS);
- auto geometryShader = createShader(GL_GEOMETRY_SHADER, Shaders::TriangleGS);
- auto fragmentShader = createShader(GL_FRAGMENT_SHADER, Shaders::TriangleFS);
-
- auto program = glCreateProgram();
- glAttachShader(program, vertexShader);
- glAttachShader(program, geometryShader);
- glAttachShader(program, fragmentShader);
- glLinkProgram(program);
-
- glDeleteShader(vertexShader);
- glDeleteShader(geometryShader);
- glDeleteShader(fragmentShader);
-
- return program;
- }
-
- void setupTriangleVao(GeometryBuffers const& geometryBuffers)
- {
- glBindVertexArray(geometryBuffers->getVaoForTriangles());
- glBindBuffer(GL_ARRAY_BUFFER, geometryBuffers->getVboForObjects());
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(ObjectVertexData), (void*)0);
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(ObjectVertexData), (void*)(3 * sizeof(float)));
- glEnableVertexAttribArray(1);
- glVertexAttribIPointer(2, 1, GL_INT, sizeof(ObjectVertexData), (void*)(6 * sizeof(float)));
- glEnableVertexAttribArray(2);
- glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(ObjectVertexData), (void*)(6 * sizeof(float) + sizeof(int)));
- glEnableVertexAttribArray(3);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryBuffers->getEboForTriangles());
- }
-
- int renderTrianglePixels(GeometryBuffers const& geometryBuffers)
- {
- setupTriangleVao(geometryBuffers);
-
- auto program = createTriangleShaderProgram();
- glUseProgram(program);
- glUniform1f(glGetUniformLocation(program, "zoom"), 50.0f);
- glUniform2f(glGetUniformLocation(program, "worldSize"), 1000.0f, 1000.0f);
- glUniform2f(glGetUniformLocation(program, "rectUpperLeft"), 99.5f, 99.5f);
- glUniform2f(glGetUniformLocation(program, "viewportSize"), 100.0f, 100.0f);
-
- glViewport(0, 0, 100, 100);
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- glEnable(GL_DEPTH_TEST);
- glDepthFunc(GL_LESS);
- glDrawElements(GL_TRIANGLES, toInt(geometryBuffers->getNumObjects().triangleIndices), GL_UNSIGNED_INT, 0);
- glDisable(GL_DEPTH_TEST);
- glFinish();
-
- std::vector pixels(100 * 100 * 3);
- glReadPixels(0, 0, 100, 100, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
- glDeleteProgram(program);
-
- return std::count_if(pixels.begin(), pixels.end(), [](unsigned char value) { return value > 0; });
- }
-
GLFWwindow* _window = nullptr;
};
@@ -291,94 +216,6 @@ TEST_F(GeometryTests, copyBuffers_triangle)
EXPECT_EQ(6u, triangles.size());
}
-TEST_F(GeometryTests, copyBuffers_triangleWithZeroReferenceAngle)
-{
- auto data = Desc().addCreature({
- ObjectDesc().id(1).pos({100.0f, 100.0f}),
- ObjectDesc().id(2).pos({101.0f, 100.0f}),
- ObjectDesc().id(3).pos({100.5f, 100.866f}),
- });
- data.addConnection(1, 2);
- data.addConnection(2, 3);
- data.addConnection(3, 1);
- data.getConnectionRef(1, 2)._angleFromPrevious = 0.0f;
- data.getConnectionRef(1, 3)._angleFromPrevious = 360.0f;
-
- _simulationFacade->setSimulationData(data);
- auto geometryBuffers = _GeometryBuffers::create();
- RealRect visibleWorldRect{{0, 0}, {1000, 1000}};
-
- _simulationFacade->tryCopyBuffersFromCudaToOpenGL(geometryBuffers, visibleWorldRect);
-
- auto numObjects = geometryBuffers->getNumObjects();
- EXPECT_EQ(3u, numObjects.objects);
- EXPECT_EQ(6u, numObjects.lineIndices);
- EXPECT_EQ(6u, numObjects.triangleIndices);
-
- auto triangles = geometryBuffers->getTriangleIndices();
- EXPECT_EQ(6u, triangles.size());
-}
-
-TEST_F(GeometryTests, renderTriangleWithZeroReferenceAngle)
-{
- auto data = Desc().addCreature({
- ObjectDesc().id(1).pos({100.0f, 100.0f}),
- ObjectDesc().id(2).pos({101.0f, 100.0f}),
- ObjectDesc().id(3).pos({100.5f, 100.866f}),
- });
- data.addConnection(1, 2);
- data.addConnection(2, 3);
- data.addConnection(3, 1);
- data.getConnectionRef(1, 2)._angleFromPrevious = 0.0f;
- data.getConnectionRef(1, 3)._angleFromPrevious = 360.0f;
-
- _simulationFacade->setSimulationData(data);
- auto geometryBuffers = _GeometryBuffers::create();
- RealRect visibleWorldRect{{0, 0}, {1000, 1000}};
-
- _simulationFacade->tryCopyBuffersFromCudaToOpenGL(geometryBuffers, visibleWorldRect);
-
- EXPECT_GT(renderTrianglePixels(geometryBuffers), 0);
-}
-
-TEST_F(GeometryTests, renderTriangleFanWithZeroReferenceAnglesUsesActualConnectionOrder)
-{
- auto createTriangleFan = [] {
- auto result = Desc().addCreature({
- ObjectDesc().id(1).pos({100.0f, 100.0f}),
- ObjectDesc().id(2).pos({99.0f, 100.0f}),
- ObjectDesc().id(3).pos({100.0f, 101.0f}),
- ObjectDesc().id(4).pos({101.0f, 100.0f}),
- });
- result.addConnection(1, 2);
- result.addConnection(1, 3);
- result.addConnection(1, 4);
- result.addConnection(2, 3);
- result.addConnection(3, 4);
- return result;
- };
-
- auto referenceData = createTriangleFan();
- _simulationFacade->setSimulationData(referenceData);
- auto referenceGeometryBuffers = _GeometryBuffers::create();
- RealRect visibleWorldRect{{0, 0}, {1000, 1000}};
- _simulationFacade->tryCopyBuffersFromCudaToOpenGL(referenceGeometryBuffers, visibleWorldRect);
- auto const referencePixels = renderTrianglePixels(referenceGeometryBuffers);
-
- auto zeroAngleData = createTriangleFan();
- auto& centerConnections = zeroAngleData.getObjectRef(1)._connections;
- std::swap(centerConnections.at(1), centerConnections.at(2));
- centerConnections.at(0)._angleFromPrevious = 180.0f;
- centerConnections.at(1)._angleFromPrevious = 180.0f;
- centerConnections.at(2)._angleFromPrevious = 0.0f;
-
- _simulationFacade->setSimulationData(zeroAngleData);
- auto geometryBuffers = _GeometryBuffers::create();
- _simulationFacade->tryCopyBuffersFromCudaToOpenGL(geometryBuffers, visibleWorldRect);
-
- EXPECT_EQ(referencePixels, renderTrianglePixels(geometryBuffers));
-}
-
TEST_F(GeometryTests, copyBuffers_quad)
{
auto data = Desc().addCreature({
From 5f158e947f46abdb94e9417c85e172f5204a2e11 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 21 May 2026 17:45:00 +0200
Subject: [PATCH 3/4] Handle zero-angle triangle extraction edgecase
Agent-Logs-Url: https://github.com/chrxh/alien/sessions/4d630b49-4f05-4c79-ab00-b8fa4cf652dd
Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com>
---
source/EngineKernels/GeometryKernels.cu | 85 +++++++++----------------
1 file changed, 30 insertions(+), 55 deletions(-)
diff --git a/source/EngineKernels/GeometryKernels.cu b/source/EngineKernels/GeometryKernels.cu
index 6b115f5fc2..37980cfe23 100644
--- a/source/EngineKernels/GeometryKernels.cu
+++ b/source/EngineKernels/GeometryKernels.cu
@@ -125,48 +125,29 @@ namespace
&& pos.y >= context.visibleTopLeft.y - context.cullingMargin && pos.y <= context.visibleBottomRight.y + context.cullingMargin;
}
- __device__ __inline__ float getActualConnectionAngle(SimulationData const& data, Object* object, int connectionIndex)
+ __device__ __inline__ int getPreviousConnectionIndexWithNonZeroAngle(Object* object, int connectionIndex)
{
- auto displacement = data.objectMap.getCorrectedDirection(object->connections[connectionIndex].object->pos - object->pos);
- return Math::angleOfVector(displacement);
- }
-
- __device__ __inline__ void
- getSortedConnectionIndicesByActualAngle(SimulationData const& data, Object* object, int sortedConnectionIndices[MAX_OBJECT_CONNECTIONS])
- {
- for (int i = 0; i < object->numConnections; ++i) {
- sortedConnectionIndices[i] = i;
- }
- for (int i = 1; i < object->numConnections; ++i) {
- auto currentConnectionIndex = sortedConnectionIndices[i];
- auto currentAngle = getActualConnectionAngle(data, object, currentConnectionIndex);
- auto currentObjectId = object->connections[currentConnectionIndex].object->id;
-
- auto insertIndex = i;
- while (insertIndex > 0) {
- auto previousConnectionIndex = sortedConnectionIndices[insertIndex - 1];
- auto previousAngle = getActualConnectionAngle(data, object, previousConnectionIndex);
- auto previousObjectId = object->connections[previousConnectionIndex].object->id;
- if (previousAngle < currentAngle || (fabsf(previousAngle - currentAngle) < NEAR_ZERO && previousObjectId < currentObjectId)) {
- break;
- }
- sortedConnectionIndices[insertIndex] = previousConnectionIndex;
- --insertIndex;
+ auto result = (connectionIndex + object->numConnections - 1) % object->numConnections;
+ for (int i = 0; i < object->numConnections - 1; ++i) {
+ auto angleToCurrent = object->connections[(result + 1) % object->numConnections].angleFromPrevious;
+ if (angleToCurrent >= NEAR_ZERO) {
+ return result;
}
- sortedConnectionIndices[insertIndex] = currentConnectionIndex;
+ result = (result + object->numConnections - 1) % object->numConnections;
}
+ return connectionIndex;
}
- __device__ __inline__ int
- getSortedConnectionPosition(SimulationData const& data, Object* object, Object* connectedObject, int sortedConnectionIndices[MAX_OBJECT_CONNECTIONS])
+ __device__ __inline__ int getNextConnectionIndexWithNonZeroAngle(Object* object, int connectionIndex)
{
- getSortedConnectionIndicesByActualAngle(data, object, sortedConnectionIndices);
- for (int sortedIndex = 0; sortedIndex < object->numConnections; ++sortedIndex) {
- if (object->connections[sortedConnectionIndices[sortedIndex]].object == connectedObject) {
- return sortedIndex;
+ auto result = (connectionIndex + 1) % object->numConnections;
+ for (int i = 0; i < object->numConnections - 1; ++i) {
+ if (object->connections[result].angleFromPrevious >= NEAR_ZERO) {
+ return result;
}
+ result = (result + 1) % object->numConnections;
}
- return 0;
+ return connectionIndex;
}
}
@@ -311,38 +292,32 @@ __global__ void cudaExtractTriangleIndices(SimulationData data, unsigned int* tr
if (object->numConnections <= 1) {
continue;
}
- int sortedConnectionIndices[MAX_OBJECT_CONNECTIONS];
- getSortedConnectionIndicesByActualAngle(data, object, sortedConnectionIndices);
- for (int sortedIndex = 0, numConnections = object->numConnections; sortedIndex < numConnections; ++sortedIndex) {
- auto connectionIndex = sortedConnectionIndices[sortedIndex];
- auto prevIndex = sortedConnectionIndices[(sortedIndex + numConnections - 1) % numConnections];
+ for (int connectionIndex = 0, numConnections = object->numConnections; connectionIndex < numConnections; ++connectionIndex) {
+ auto prevIndex = getPreviousConnectionIndexWithNonZeroAngle(object, connectionIndex);
+ if (prevIndex == connectionIndex) {
+ continue;
+ }
auto const& connectedObject = object->connections[connectionIndex].object;
auto const& prevConnectedObject = object->connections[prevIndex].object;
- int connectedSortedConnectionIndices[MAX_OBJECT_CONNECTIONS];
- auto backIndex = getSortedConnectionPosition(data, connectedObject, object, connectedSortedConnectionIndices);
- int prevConnectedSortedConnectionIndices[MAX_OBJECT_CONNECTIONS];
- auto prevBackIndex = getSortedConnectionPosition(data, prevConnectedObject, object, prevConnectedSortedConnectionIndices);
+ auto backIndex = connectedObject->getConnectionIndex(object);
+ auto prevBackIndex = prevConnectedObject->getConnectionIndex(object);
+ auto connectedNextIndex = getNextConnectionIndexWithNonZeroAngle(connectedObject, backIndex);
+ auto prevConnectedPrevIndex = getPreviousConnectionIndexWithNonZeroAngle(prevConnectedObject, prevBackIndex);
+ if (connectedNextIndex == backIndex || prevConnectedPrevIndex == prevBackIndex) {
+ continue;
+ }
// Triangle?
- if (prevConnectedObject
- ->connections
- [prevConnectedSortedConnectionIndices[(prevBackIndex + prevConnectedObject->numConnections - 1) % prevConnectedObject->numConnections]]
- .object
- == connectedObject) {
+ if (prevConnectedObject->connections[prevConnectedPrevIndex].object == connectedObject) {
if (object->id < connectedObject->id && object->id < prevConnectedObject->id) {
addTriangle(object, object->tempValue1.as_uint64, prevConnectedObject, connectedObject);
}
}
// Rectangle?
- auto fourthCellCandidate1 =
- connectedObject->connections[connectedSortedConnectionIndices[(backIndex + 1) % connectedObject->numConnections]].object;
- auto fourthCellCandidate2 =
- prevConnectedObject
- ->connections
- [prevConnectedSortedConnectionIndices[(prevBackIndex + prevConnectedObject->numConnections - 1) % prevConnectedObject->numConnections]]
- .object;
+ auto fourthCellCandidate1 = connectedObject->connections[connectedNextIndex].object;
+ auto fourthCellCandidate2 = prevConnectedObject->connections[prevConnectedPrevIndex].object;
if (fourthCellCandidate2 == fourthCellCandidate1 && fourthCellCandidate1 != object && fourthCellCandidate2 != object
&& connectedObject != prevConnectedObject) {
if (object->id < connectedObject->id && object->id < prevConnectedObject->id && object->id < fourthCellCandidate2->id) {
From 9f1c67d232b5969932acb3e016daf792cc502560 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 27 May 2026 09:33:24 +0200
Subject: [PATCH 4/4] Fix zero-angle absolute connection insertion
Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com>
---
source/EngineImpl/EngineWorker.cpp | 11 ++++
source/EngineImpl/EngineWorker.h | 1 +
source/EngineImpl/SimulationCudaFacade.cu | 13 +++++
source/EngineImpl/SimulationCudaFacade.cuh | 1 +
source/EngineImpl/SimulationFacadeImpl.cpp | 10 ++++
source/EngineImpl/SimulationFacadeImpl.h | 2 +
source/EngineImpl/TestKernelsService.cu | 12 +++++
source/EngineImpl/TestKernelsService.cuh | 8 +++
source/EngineInterface/SimulationFacade.h | 2 +
source/EngineKernels/GeometryKernels.cu | 52 +++++--------------
.../ObjectConnectionProcessor.cuh | 9 +++-
source/EngineKernels/TestKernels.cu | 29 +++++++++++
source/EngineKernels/TestKernels.cuh | 7 +++
source/EngineTests/ObjectConnectionTests.cpp | 35 +++++++++++++
14 files changed, 151 insertions(+), 41 deletions(-)
diff --git a/source/EngineImpl/EngineWorker.cpp b/source/EngineImpl/EngineWorker.cpp
index 6d6396e781..642494bcc4 100644
--- a/source/EngineImpl/EngineWorker.cpp
+++ b/source/EngineImpl/EngineWorker.cpp
@@ -472,6 +472,17 @@ void EngineWorker::testOnly_createConnection(uint64_t objectId1, uint64_t object
_simulationCudaFacade->testOnly_createConnection(objectId1, objectId2);
}
+void EngineWorker::testOnly_createConnectionWithAbsAngle(
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2)
+{
+ EngineWorkerGuard access(this);
+ _simulationCudaFacade->testOnly_createConnectionWithAbsAngle(objectId1, objectId2, desiredDistance, desiredAbsAngle1, desiredAbsAngle2);
+}
+
void EngineWorker::testOnly_cleanupAfterTimestep()
{
EngineWorkerGuard access(this);
diff --git a/source/EngineImpl/EngineWorker.h b/source/EngineImpl/EngineWorker.h
index dd673a975e..79d83d4dc3 100644
--- a/source/EngineImpl/EngineWorker.h
+++ b/source/EngineImpl/EngineWorker.h
@@ -120,6 +120,7 @@ class EngineWorker
// Only for tests
void testOnly_mutate(uint64_t objectId);
void testOnly_createConnection(uint64_t objectId1, uint64_t objectId2);
+ void testOnly_createConnectionWithAbsAngle(uint64_t objectId1, uint64_t objectId2, float desiredDistance, float desiredAbsAngle1, float desiredAbsAngle2);
void testOnly_cleanupAfterTimestep();
void testOnly_cleanupAfterDataManipulation();
void testOnly_resizeArrays(ArraySizesForGpuEntities const& sizeDelta);
diff --git a/source/EngineImpl/SimulationCudaFacade.cu b/source/EngineImpl/SimulationCudaFacade.cu
index 3b9d15e4ab..c828b30bfa 100644
--- a/source/EngineImpl/SimulationCudaFacade.cu
+++ b/source/EngineImpl/SimulationCudaFacade.cu
@@ -595,6 +595,19 @@ void _SimulationCudaFacade::testOnly_createConnection(uint64_t objectId1, uint64
syncAndCheck();
}
+void _SimulationCudaFacade::testOnly_createConnectionWithAbsAngle(
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2)
+{
+ checkAndProcessSimulationParameterChanges();
+ TestKernelsService::get().testOnly_createConnectionWithAbsAngle(
+ _settings.cudaSettings, getSimulationDataPtrCopy(), objectId1, objectId2, desiredDistance, desiredAbsAngle1, desiredAbsAngle2);
+ syncAndCheck();
+}
+
void _SimulationCudaFacade::testOnly_cleanupAfterTimestep()
{
checkAndProcessSimulationParameterChanges();
diff --git a/source/EngineImpl/SimulationCudaFacade.cuh b/source/EngineImpl/SimulationCudaFacade.cuh
index da0fcd493a..e8b5646bf0 100644
--- a/source/EngineImpl/SimulationCudaFacade.cuh
+++ b/source/EngineImpl/SimulationCudaFacade.cuh
@@ -106,6 +106,7 @@ public:
// Only for tests
void testOnly_mutate(uint64_t objectId);
void testOnly_createConnection(uint64_t objectId1, uint64_t objectId2);
+ void testOnly_createConnectionWithAbsAngle(uint64_t objectId1, uint64_t objectId2, float desiredDistance, float desiredAbsAngle1, float desiredAbsAngle2);
void testOnly_cleanupAfterTimestep();
void testOnly_cleanupAfterDataManipulation();
void testOnly_resizeArrays(ArraySizesForGpuEntities const& sizeDelta);
diff --git a/source/EngineImpl/SimulationFacadeImpl.cpp b/source/EngineImpl/SimulationFacadeImpl.cpp
index d55b86de92..3aa36e15b1 100644
--- a/source/EngineImpl/SimulationFacadeImpl.cpp
+++ b/source/EngineImpl/SimulationFacadeImpl.cpp
@@ -387,6 +387,16 @@ void _SimulationFacadeImpl::testOnly_createConnection(uint64_t objectId1, uint64
_worker.testOnly_createConnection(objectId1, objectId2);
}
+void _SimulationFacadeImpl::testOnly_createConnectionWithAbsAngle(
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2)
+{
+ _worker.testOnly_createConnectionWithAbsAngle(objectId1, objectId2, desiredDistance, desiredAbsAngle1, desiredAbsAngle2);
+}
+
void _SimulationFacadeImpl::testOnly_cleanupAfterTimestep()
{
_worker.testOnly_cleanupAfterTimestep();
diff --git a/source/EngineImpl/SimulationFacadeImpl.h b/source/EngineImpl/SimulationFacadeImpl.h
index ec495c0b66..e53322372e 100644
--- a/source/EngineImpl/SimulationFacadeImpl.h
+++ b/source/EngineImpl/SimulationFacadeImpl.h
@@ -108,6 +108,8 @@ class _SimulationFacadeImpl : public _SimulationFacade
// for tests only
void testOnly_mutate(uint64_t objectId) override;
void testOnly_createConnection(uint64_t objectId1, uint64_t objectId2) override;
+ void testOnly_createConnectionWithAbsAngle(uint64_t objectId1, uint64_t objectId2, float desiredDistance, float desiredAbsAngle1, float desiredAbsAngle2)
+ override;
void testOnly_cleanupAfterTimestep() override;
void testOnly_cleanupAfterDataManipulation() override;
void testOnly_resizeArrays(ArraySizesForGpuEntities const& sizeDelta) override;
diff --git a/source/EngineImpl/TestKernelsService.cu b/source/EngineImpl/TestKernelsService.cu
index c6b8ecc433..95400f17ba 100644
--- a/source/EngineImpl/TestKernelsService.cu
+++ b/source/EngineImpl/TestKernelsService.cu
@@ -25,6 +25,18 @@ void TestKernelsService::testOnly_createConnection(CudaSettings const& gpuSettin
KERNEL_CALL_1_1(cudaTestCreateConnection, data, objectId1, objectId2);
}
+void TestKernelsService::testOnly_createConnectionWithAbsAngle(
+ CudaSettings const& gpuSettings,
+ SimulationData const& data,
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2)
+{
+ KERNEL_CALL_1_1(cudaTestCreateConnectionWithAbsAngle, data, objectId1, objectId2, desiredDistance, desiredAbsAngle1, desiredAbsAngle2);
+}
+
bool TestKernelsService::testOnly_isDataValid(CudaSettings const& gpuSettings, SimulationData const& data)
{
setValueToDevice(_cudaBoolResult, true);
diff --git a/source/EngineImpl/TestKernelsService.cuh b/source/EngineImpl/TestKernelsService.cuh
index 798286677c..a7ae8d934f 100644
--- a/source/EngineImpl/TestKernelsService.cuh
+++ b/source/EngineImpl/TestKernelsService.cuh
@@ -16,6 +16,14 @@ public:
void testOnly_mutate(CudaSettings const& gpuSettings, SimulationData const& data, uint64_t objectId);
void testOnly_createConnection(CudaSettings const& gpuSettings, SimulationData const& data, uint64_t objectId1, uint64_t objectId2);
+ void testOnly_createConnectionWithAbsAngle(
+ CudaSettings const& gpuSettings,
+ SimulationData const& data,
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2);
bool testOnly_isDataValid(CudaSettings const& gpuSettings, SimulationData const& data);
private:
diff --git a/source/EngineInterface/SimulationFacade.h b/source/EngineInterface/SimulationFacade.h
index 13c40a68c0..904d0e2149 100644
--- a/source/EngineInterface/SimulationFacade.h
+++ b/source/EngineInterface/SimulationFacade.h
@@ -122,6 +122,8 @@ class _SimulationFacade
//****************
virtual void testOnly_mutate(uint64_t objectId) = 0;
virtual void testOnly_createConnection(uint64_t objectId1, uint64_t objectId2) = 0;
+ virtual void
+ testOnly_createConnectionWithAbsAngle(uint64_t objectId1, uint64_t objectId2, float desiredDistance, float desiredAbsAngle1, float desiredAbsAngle2) = 0;
virtual void testOnly_cleanupAfterTimestep() = 0;
virtual void testOnly_cleanupAfterDataManipulation() = 0;
virtual void testOnly_resizeArrays(ArraySizesForGpuEntities const& sizeDelta) = 0;
diff --git a/source/EngineKernels/GeometryKernels.cu b/source/EngineKernels/GeometryKernels.cu
index 37980cfe23..17446edd8b 100644
--- a/source/EngineKernels/GeometryKernels.cu
+++ b/source/EngineKernels/GeometryKernels.cu
@@ -124,31 +124,6 @@ namespace
return pos.x >= context.visibleTopLeft.x - context.cullingMargin && pos.x <= context.visibleBottomRight.x + context.cullingMargin
&& pos.y >= context.visibleTopLeft.y - context.cullingMargin && pos.y <= context.visibleBottomRight.y + context.cullingMargin;
}
-
- __device__ __inline__ int getPreviousConnectionIndexWithNonZeroAngle(Object* object, int connectionIndex)
- {
- auto result = (connectionIndex + object->numConnections - 1) % object->numConnections;
- for (int i = 0; i < object->numConnections - 1; ++i) {
- auto angleToCurrent = object->connections[(result + 1) % object->numConnections].angleFromPrevious;
- if (angleToCurrent >= NEAR_ZERO) {
- return result;
- }
- result = (result + object->numConnections - 1) % object->numConnections;
- }
- return connectionIndex;
- }
-
- __device__ __inline__ int getNextConnectionIndexWithNonZeroAngle(Object* object, int connectionIndex)
- {
- auto result = (connectionIndex + 1) % object->numConnections;
- for (int i = 0; i < object->numConnections - 1; ++i) {
- if (object->connections[result].angleFromPrevious >= NEAR_ZERO) {
- return result;
- }
- result = (result + 1) % object->numConnections;
- }
- return connectionIndex;
- }
}
__global__ void cudaExtractObjectData(SimulationData data, ObjectVertexData* objectData, uint64_t* numObjects, GeometryExtractionContext context)
@@ -292,32 +267,31 @@ __global__ void cudaExtractTriangleIndices(SimulationData data, unsigned int* tr
if (object->numConnections <= 1) {
continue;
}
- for (int connectionIndex = 0, numConnections = object->numConnections; connectionIndex < numConnections; ++connectionIndex) {
- auto prevIndex = getPreviousConnectionIndexWithNonZeroAngle(object, connectionIndex);
- if (prevIndex == connectionIndex) {
- continue;
- }
+ bool first = true;
+ int backIndices[MAX_OBJECT_CONNECTIONS];
+ for (int i = 0, numConnections = object->numConnections; i < numConnections + 1; ++i) {
+ auto connectionIndex = i % numConnections;
auto const& connectedObject = object->connections[connectionIndex].object;
- auto const& prevConnectedObject = object->connections[prevIndex].object;
-
auto backIndex = connectedObject->getConnectionIndex(object);
- auto prevBackIndex = prevConnectedObject->getConnectionIndex(object);
- auto connectedNextIndex = getNextConnectionIndexWithNonZeroAngle(connectedObject, backIndex);
- auto prevConnectedPrevIndex = getPreviousConnectionIndexWithNonZeroAngle(prevConnectedObject, prevBackIndex);
- if (connectedNextIndex == backIndex || prevConnectedPrevIndex == prevBackIndex) {
+ backIndices[connectionIndex] = backIndex;
+ if (first) {
+ first = false;
continue;
}
+ auto prevIndex = (connectionIndex + numConnections - 1) % numConnections;
+ auto const& prevConnectedObject = object->connections[prevIndex].object;
+ auto prevBackIndex = backIndices[prevIndex];
// Triangle?
- if (prevConnectedObject->connections[prevConnectedPrevIndex].object == connectedObject) {
+ if (prevConnectedObject->getConnectedObject(prevBackIndex - 1) == connectedObject) {
if (object->id < connectedObject->id && object->id < prevConnectedObject->id) {
addTriangle(object, object->tempValue1.as_uint64, prevConnectedObject, connectedObject);
}
}
// Rectangle?
- auto fourthCellCandidate1 = connectedObject->connections[connectedNextIndex].object;
- auto fourthCellCandidate2 = prevConnectedObject->connections[prevConnectedPrevIndex].object;
+ auto fourthCellCandidate1 = connectedObject->getConnectedObject(backIndex + 1);
+ auto fourthCellCandidate2 = prevConnectedObject->getConnectedObject(prevBackIndex - 1);
if (fourthCellCandidate2 == fourthCellCandidate1 && fourthCellCandidate1 != object && fourthCellCandidate2 != object
&& connectedObject != prevConnectedObject) {
if (object->id < connectedObject->id && object->id < prevConnectedObject->id && object->id < fourthCellCandidate2->id) {
diff --git a/source/EngineKernels/ObjectConnectionProcessor.cuh b/source/EngineKernels/ObjectConnectionProcessor.cuh
index 69c4343c44..e1fec97569 100644
--- a/source/EngineKernels/ObjectConnectionProcessor.cuh
+++ b/source/EngineKernels/ObjectConnectionProcessor.cuh
@@ -435,14 +435,19 @@ ObjectConnectionProcessor::tryAddConnectionWithAbsAngle_oneWay(Object* object1,
auto insertIndex = 0;
auto summedAngle = 0.0f;
desiredAbsAngle = Math::getNormalizedAngle(desiredAbsAngle, 0.0f);
+ if (desiredAbsAngle < NEAR_ZERO) {
+ desiredAbsAngle = 360.0f;
+ }
for (int i = 1; i <= n; ++i) {
auto const& angleFromPrevious = object1->getConnection(i).angleFromPrevious;
+ auto nextSummedAngle = summedAngle + angleFromPrevious;
- if (desiredAbsAngle >= summedAngle - NEAR_ZERO && desiredAbsAngle < summedAngle + angleFromPrevious) {
+ if (desiredAbsAngle > summedAngle + NEAR_ZERO && desiredAbsAngle <= nextSummedAngle + NEAR_ZERO) {
insertIndex = i;
+ desiredAbsAngle = min(desiredAbsAngle, nextSummedAngle);
break;
}
- summedAngle += angleFromPrevious;
+ summedAngle = nextSummedAngle;
}
DEVICE_CHECK(insertIndex > 0);
diff --git a/source/EngineKernels/TestKernels.cu b/source/EngineKernels/TestKernels.cu
index 4fa95167b3..4e66d3991f 100644
--- a/source/EngineKernels/TestKernels.cu
+++ b/source/EngineKernels/TestKernels.cu
@@ -71,6 +71,35 @@ __global__ void cudaTestCreateConnection(SimulationData data, uint64_t objectId1
}
}
+__global__ void cudaTestCreateConnectionWithAbsAngle(
+ SimulationData data,
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2)
+{
+ DEVICE_CHECK(blockDim.x == 1 && gridDim.x == 1);
+
+ auto& objects = data.entities.objects;
+ auto partition = calcSystemThreadPartition(objects.getNumEntries());
+ Object* object1 = nullptr;
+ Object* object2 = nullptr;
+ for (int index = partition.startIndex; index <= partition.endIndex; index += partition.step) {
+ auto& object = objects.at(index);
+ if (object->id == objectId1) {
+ object1 = object;
+ }
+ if (object->id == objectId2) {
+ object2 = object;
+ }
+ }
+
+ if (object1 != nullptr && object2 != nullptr) {
+ ObjectConnectionProcessor::tryAddConnectionWithAbsAngle(data, object1, object2, desiredDistance, desiredAbsAngle1, desiredAbsAngle2);
+ }
+}
+
namespace
{
__device__ bool isEnergyValid(float energy)
diff --git a/source/EngineKernels/TestKernels.cuh b/source/EngineKernels/TestKernels.cuh
index 6196f013e2..b980330e57 100644
--- a/source/EngineKernels/TestKernels.cuh
+++ b/source/EngineKernels/TestKernels.cuh
@@ -7,4 +7,11 @@
__global__ void cudaTestMutate(SimulationData data, uint64_t objectId);
__global__ void cudaTestCreateConnection(SimulationData data, uint64_t objectId1, uint64_t objectId2);
+__global__ void cudaTestCreateConnectionWithAbsAngle(
+ SimulationData data,
+ uint64_t objectId1,
+ uint64_t objectId2,
+ float desiredDistance,
+ float desiredAbsAngle1,
+ float desiredAbsAngle2);
__global__ void cudaTestIsDataValid(SimulationData data, bool* result);
diff --git a/source/EngineTests/ObjectConnectionTests.cpp b/source/EngineTests/ObjectConnectionTests.cpp
index 794d618b2f..eb7a430cd4 100644
--- a/source/EngineTests/ObjectConnectionTests.cpp
+++ b/source/EngineTests/ObjectConnectionTests.cpp
@@ -149,3 +149,38 @@ TEST_F(ObjectConnectionTests, addThirdConnection2)
EXPECT_TRUE(approxCompare(1.0f, connection3._distance));
EXPECT_TRUE(approxCompare(90.0f, connection3._angleFromPrevious));
}
+
+TEST_F(ObjectConnectionTests, addConnectionWithZeroAbsAngleInsertsBeforeReferenceConnection)
+{
+ auto data = Desc().objects({
+ ObjectDesc().id(1).pos({0, 0}).type(SolidDesc()),
+ ObjectDesc().id(2).pos({1, 0}).type(SolidDesc()),
+ ObjectDesc().id(3).pos({0, 1}).type(SolidDesc()),
+ ObjectDesc().id(4).pos({-1, 0}).type(SolidDesc()),
+ });
+ data.addConnection(1, 2);
+ data.addConnection(1, 3);
+ _simulationFacade->setSimulationData(data);
+ _simulationFacade->testOnly_createConnectionWithAbsAngle(1, 4, 1.0f, 0.0f, 0.0f);
+
+ auto actualData = _simulationFacade->getSimulationData();
+ ASSERT_EQ(4, actualData._objects.size());
+
+ auto object = actualData.getObjectRef(1);
+ ASSERT_EQ(3, object._connections.size());
+
+ auto connection1 = object._connections.at(0);
+ EXPECT_EQ(2, connection1._objectId);
+ EXPECT_TRUE(approxCompare(1.0f, connection1._distance));
+ EXPECT_TRUE(approxCompare(0.0f, connection1._angleFromPrevious));
+
+ auto connection2 = object._connections.at(1);
+ EXPECT_EQ(3, connection2._objectId);
+ EXPECT_TRUE(approxCompare(1.0f, connection2._distance));
+ EXPECT_TRUE(approxCompare(90.0f, connection2._angleFromPrevious));
+
+ auto connection3 = object._connections.at(2);
+ EXPECT_EQ(4, connection3._objectId);
+ EXPECT_TRUE(approxCompare(1.0f, connection3._distance));
+ EXPECT_TRUE(approxCompare(270.0f, connection3._angleFromPrevious));
+}