hyperstellar 0.1.64__tar.gz → 0.1.65__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/PKG-INFO +1 -1
  2. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/_native/windows-x64/shaders/math.comp +204 -72
  3. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/_native/windows-x64/stellar.pyd +0 -0
  4. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar.egg-info/PKG-INFO +1 -1
  5. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/pyproject.toml +2 -1
  6. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/LICENSE +0 -0
  7. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/README.md +0 -0
  8. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/__init__.py +0 -0
  9. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/__init__.pyi +0 -0
  10. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/_native/windows-x64/shaders/quad.frag +0 -0
  11. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/_native/windows-x64/shaders/quad.geom +0 -0
  12. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/_native/windows-x64/shaders/quad.vert +0 -0
  13. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar/py.typed +0 -0
  14. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar.egg-info/SOURCES.txt +0 -0
  15. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar.egg-info/dependency_links.txt +0 -0
  16. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/hyperstellar/src/hyperstellar.egg-info/top_level.txt +0 -0
  17. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/setup.cfg +0 -0
  18. {hyperstellar-0.1.64 → hyperstellar-0.1.65}/tests/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperstellar
3
- Version: 0.1.64
3
+ Version: 0.1.65
4
4
  Summary: GPU-accelerated physics simulation engine
5
5
  Author-email: Aiden Jabari <Jabariaiden15@email.com>
6
6
  License: MIT License
@@ -1110,7 +1110,7 @@ void applyConstraints(inout vec2 pos, inout vec2 vel, int objectIndex, vec2 orig
1110
1110
  }
1111
1111
 
1112
1112
  // ============================================================================
1113
- // COLLISION DETECTION FUNCTIONS
1113
+ // COLLISION DETECTION FUNCTIONS (UNCHANGED - KEEP YOUR EXISTING CODE)
1114
1114
  // ============================================================================
1115
1115
 
1116
1116
  // Circle-Circle collision detection
@@ -1120,54 +1120,52 @@ CollisionInfo detectCircleCircle(vec2 posA, float radiusA, vec2 posB, float radi
1120
1120
  info.hasCollision = false;
1121
1121
  info.otherObjectID = objB;
1122
1122
 
1123
+ // Calculate distance between centers
1123
1124
  vec2 delta = posB - posA;
1124
1125
  float distSq = dot(delta, delta);
1125
1126
  float radiusSum = radiusA + radiusB;
1126
1127
 
1128
+ // Check if circles overlap
1127
1129
  if (distSq < radiusSum * radiusSum && distSq > EPSILON)
1128
1130
  {
1129
1131
  float dist = sqrt(distSq);
1130
1132
  info.hasCollision = true;
1131
- info.normal = delta / dist;
1132
- info.penetration = radiusSum - dist;
1133
+ info.normal = delta / dist; // Collision normal (A→B)
1134
+ info.penetration = radiusSum - dist; // Overlap depth
1133
1135
  }
1134
1136
 
1135
1137
  return info;
1136
1138
  }
1137
1139
 
1138
- // Axis-Aligned Bounding Box collision detection with fix for "sticky" behavior.
1139
- // The fix: Use old positions only for determining the sign of the normal,
1140
- // while penetration depth still uses the current (predicted) positions.
1141
- // This prevents the normal from flipping when A leaps past B in one timestep.
1142
- CollisionInfo detectAABBAABB(vec2 posA, vec2 halfExtA, vec2 posB, vec2 halfExtB,
1143
- vec2 oldPosA, vec2 oldPosB, int objB)
1140
+ // Axis-Aligned Bounding Box collision detection
1141
+ CollisionInfo detectAABBAABB(vec2 posA, vec2 halfExtA, vec2 posB, vec2 halfExtB, int objB)
1144
1142
  {
1145
1143
  CollisionInfo info;
1146
1144
  info.hasCollision = false;
1147
1145
  info.otherObjectID = objB;
1148
-
1149
- vec2 delta = posB - posA; // current (possibly overshot)
1146
+
1147
+ // Calculate separation vector
1148
+ vec2 delta = posB - posA;
1150
1149
  vec2 overlap = halfExtA + halfExtB - abs(delta);
1151
-
1150
+
1151
+ // Check for overlap on both axes
1152
1152
  if (overlap.x > 0.0 && overlap.y > 0.0)
1153
1153
  {
1154
1154
  info.hasCollision = true;
1155
-
1156
- // Use the OLD relative position to decide the normal direction.
1157
- // This preserves physical sign even after a large velocity causes overshoot.
1158
- vec2 oldDelta = oldPosB - oldPosA;
1159
-
1155
+
1156
+ // Resolve along minimum penetration axis
1160
1157
  if (overlap.x < overlap.y)
1161
1158
  {
1162
- info.normal = vec2(sign(oldDelta.x), 0.0);
1159
+ info.normal = vec2(sign(delta.x), 0.0);
1163
1160
  info.penetration = overlap.x;
1164
1161
  }
1165
1162
  else
1166
1163
  {
1167
- info.normal = vec2(0.0, sign(oldDelta.y));
1164
+ info.normal = vec2(0.0, sign(delta.y));
1168
1165
  info.penetration = overlap.y;
1169
1166
  }
1170
1167
  }
1168
+
1171
1169
  return info;
1172
1170
  }
1173
1171
 
@@ -1179,6 +1177,7 @@ vec2 projectPolygon(vec2 center, float radius, int sides, float rotation, vec2 a
1179
1177
 
1180
1178
  float angleStep = 2.0 * PI / float(sides);
1181
1179
 
1180
+ // Project each vertex onto axis
1182
1181
  for (int i = 0; i < sides; i++)
1183
1182
  {
1184
1183
  float angle = rotation + float(i) * angleStep;
@@ -1197,9 +1196,11 @@ vec2 getPolygonNormal(int side, int totalSides, float rotation)
1197
1196
  float angleStep = 2.0 * PI / float(totalSides);
1198
1197
  float angle = rotation + float(side) * angleStep;
1199
1198
 
1199
+ // Calculate edge vector
1200
1200
  vec2 edge = vec2(cos(angle + angleStep), sin(angle + angleStep)) -
1201
1201
  vec2(cos(angle), sin(angle));
1202
1202
 
1203
+ // Return perpendicular (normal) to edge
1203
1204
  return normalize(vec2(-edge.y, edge.x));
1204
1205
  }
1205
1206
 
@@ -1214,6 +1215,7 @@ CollisionInfo detectPolygonPolygon(
1214
1215
  info.otherObjectID = objB;
1215
1216
  info.penetration = 1e10;
1216
1217
 
1218
+ // Test all edge normals from both polygons
1217
1219
  int totalTests = sidesA + sidesB;
1218
1220
 
1219
1221
  for (int i = 0; i < totalTests; i++)
@@ -1225,18 +1227,24 @@ CollisionInfo detectPolygonPolygon(
1225
1227
  else
1226
1228
  axis = getPolygonNormal(i - sidesA, sidesB, rotB);
1227
1229
 
1230
+ // Project both polygons onto axis
1228
1231
  vec2 projA = projectPolygon(posA, radiusA, sidesA, rotA, axis);
1229
1232
  vec2 projB = projectPolygon(posB, radiusB, sidesB, rotB, axis);
1230
1233
 
1234
+ // Check for separating axis
1231
1235
  if (projA.y < projB.x || projB.y < projA.x)
1232
- return info;
1236
+ return info; // Separating axis found, no collision
1233
1237
 
1238
+ // Calculate overlap
1234
1239
  float overlap = min(projA.y, projB.y) - max(projA.x, projB.x);
1235
1240
 
1241
+ // Track minimum overlap (minimum translation vector)
1236
1242
  if (overlap < info.penetration)
1237
1243
  {
1238
1244
  info.penetration = overlap;
1239
1245
  info.normal = axis;
1246
+
1247
+ // Ensure normal points from A to B
1240
1248
  if (dot(posB - posA, axis) < 0.0)
1241
1249
  info.normal = -axis;
1242
1250
  }
@@ -1254,6 +1262,7 @@ CollisionInfo detectCircleAABB(vec2 circlePos, float radius,
1254
1262
  info.hasCollision = false;
1255
1263
  info.otherObjectID = objB;
1256
1264
 
1265
+ // Find closest point on AABB to circle center
1257
1266
  vec2 closest = clamp(circlePos, boxPos - halfExt, boxPos + halfExt);
1258
1267
  vec2 delta = circlePos - closest;
1259
1268
  float distSq = dot(delta, delta);
@@ -1265,11 +1274,13 @@ CollisionInfo detectCircleAABB(vec2 circlePos, float radius,
1265
1274
 
1266
1275
  if (dist > EPSILON)
1267
1276
  {
1277
+ // Circle center outside AABB
1268
1278
  info.normal = -(delta / dist);
1269
1279
  info.penetration = radius - dist;
1270
1280
  }
1271
1281
  else
1272
1282
  {
1283
+ // Circle center inside AABB - push out nearest edge
1273
1284
  vec2 toEdge = boxPos - circlePos;
1274
1285
  vec2 absToEdge = abs(toEdge);
1275
1286
  vec2 edgeDist = halfExt - absToEdge;
@@ -1300,24 +1311,30 @@ CollisionInfo detectCirclePolygon(vec2 circlePos, float circleRadius,
1300
1311
  info.otherObjectID = objB;
1301
1312
  info.penetration = 1e10;
1302
1313
 
1314
+ // Test polygon edge normals
1303
1315
  float angleStep = 2.0 * PI / float(polySides);
1304
1316
 
1305
1317
  for (int i = 0; i < polySides; i++)
1306
1318
  {
1307
1319
  vec2 axis = getPolygonNormal(i, polySides, polyRot);
1308
1320
 
1321
+ // Project circle onto axis (capsule projection)
1309
1322
  float circleProj = dot(circlePos, axis);
1310
1323
  vec2 circleProjRange = vec2(circleProj - circleRadius,
1311
1324
  circleProj + circleRadius);
1312
1325
 
1326
+ // Project polygon onto axis
1313
1327
  vec2 polyProj = projectPolygon(polyPos, polyRadius, polySides, polyRot, axis);
1314
1328
 
1329
+ // Check for separation
1315
1330
  if (circleProjRange.y < polyProj.x || polyProj.y < circleProjRange.x)
1316
1331
  return info;
1317
1332
 
1333
+ // Calculate overlap
1318
1334
  float overlap = min(circleProjRange.y, polyProj.y) -
1319
1335
  max(circleProjRange.x, polyProj.x);
1320
1336
 
1337
+ // Track minimum overlap
1321
1338
  if (overlap < info.penetration)
1322
1339
  {
1323
1340
  info.penetration = overlap;
@@ -1327,9 +1344,11 @@ CollisionInfo detectCirclePolygon(vec2 circlePos, float circleRadius,
1327
1344
  }
1328
1345
  }
1329
1346
 
1347
+ // Test axis from circle center to closest polygon vertex
1330
1348
  vec2 closestVertex = polyPos;
1331
1349
  float closestDistSq = 1e10;
1332
1350
 
1351
+ // Find closest polygon vertex to circle center
1333
1352
  for (int i = 0; i < polySides; i++)
1334
1353
  {
1335
1354
  float angle = polyRot + float(i) * angleStep;
@@ -1343,11 +1362,13 @@ CollisionInfo detectCirclePolygon(vec2 circlePos, float circleRadius,
1343
1362
  }
1344
1363
  }
1345
1364
 
1365
+ // Test axis from circle center to closest vertex
1346
1366
  vec2 axis = normalize(circlePos - closestVertex);
1347
1367
  float circleProj = dot(circlePos, axis);
1348
1368
  vec2 circleProjRange = vec2(circleProj - circleRadius, circleProj + circleRadius);
1349
1369
  vec2 polyProj = projectPolygon(polyPos, polyRadius, polySides, polyRot, axis);
1350
1370
 
1371
+ // Final separation check
1351
1372
  if (circleProjRange.y < polyProj.x || polyProj.y < circleProjRange.x)
1352
1373
  return info;
1353
1374
 
@@ -1366,38 +1387,43 @@ CollisionInfo detectCirclePolygon(vec2 circlePos, float circleRadius,
1366
1387
  return info;
1367
1388
  }
1368
1389
 
1369
- // Master collision detection dispatcher (now with old positions for AABB fix)
1370
- CollisionInfo detectCollision(Object objA, Object objB, int objIndexB,
1371
- vec2 oldPosA, vec2 oldPosB)
1390
+ // Master collision detection dispatcher
1391
+ CollisionInfo detectCollision(Object objA, Object objB, int objIndexB)
1372
1392
  {
1373
1393
  CollisionInfo info;
1374
1394
  info.hasCollision = false;
1375
1395
 
1396
+ // Get collision properties for both objects
1376
1397
  CollisionProperties propsA = collisionProps[gl_GlobalInvocationID.x];
1377
1398
  CollisionProperties propsB = collisionProps[objIndexB];
1378
1399
 
1400
+ // Check if collisions are enabled for both objects
1379
1401
  if (propsA.enabled == 0 || propsB.enabled == 0)
1380
1402
  return info;
1381
1403
 
1382
1404
  int shapeA = propsA.shapeType;
1383
1405
  int shapeB = propsB.shapeType;
1384
1406
 
1407
+ // Skip if either object has no collision shape
1385
1408
  if (shapeA == COLLISION_NONE || shapeB == COLLISION_NONE)
1386
1409
  return info;
1387
1410
 
1411
+ // Dispatch to appropriate collision detection function based on shape types
1388
1412
  if (shapeA == COLLISION_CIRCLE && shapeB == COLLISION_CIRCLE)
1389
1413
  {
1414
+ // Circle vs Circle
1390
1415
  info = detectCircleCircle(objA.position, objA.visualData.x,
1391
1416
  objB.position, objB.visualData.x, objIndexB);
1392
1417
  }
1418
+ // AABB vs AABB
1393
1419
  else if (shapeA == COLLISION_AABB && shapeB == COLLISION_AABB)
1394
1420
  {
1395
1421
  vec2 halfExtA = objA.visualData.xy * 0.5;
1396
1422
  vec2 halfExtB = objB.visualData.xy * 0.5;
1397
- info = detectAABBAABB(objA.position, halfExtA,
1398
- objB.position, halfExtB,
1399
- oldPosA, oldPosB, objIndexB);
1423
+ info = detectAABBAABB(objA.position, halfExtA,
1424
+ objB.position, halfExtB, objIndexB);
1400
1425
  }
1426
+ // Polygon vs Polygon
1401
1427
  else if (shapeA == COLLISION_POLYGON && shapeB == COLLISION_POLYGON)
1402
1428
  {
1403
1429
  info = detectPolygonPolygon(
@@ -1405,6 +1431,7 @@ CollisionInfo detectCollision(Object objA, Object objB, int objIndexB,
1405
1431
  objB.position, objB.visualData.x, int(objB.visualData.y), objB.visualData.z,
1406
1432
  objIndexB);
1407
1433
  }
1434
+ // Circle vs AABB
1408
1435
  else if (shapeA == COLLISION_CIRCLE && shapeB == COLLISION_AABB)
1409
1436
  {
1410
1437
  vec2 halfExtB = objB.visualData.xy * 0.5;
@@ -1413,11 +1440,13 @@ CollisionInfo detectCollision(Object objA, Object objB, int objIndexB,
1413
1440
  }
1414
1441
  else if (shapeA == COLLISION_AABB && shapeB == COLLISION_CIRCLE)
1415
1442
  {
1443
+ // Swap roles and flip normal
1416
1444
  vec2 halfExtA = objA.visualData.xy * 0.5;
1417
1445
  info = detectCircleAABB(objB.position, objB.visualData.x,
1418
1446
  objA.position, halfExtA, objIndexB);
1419
- info.normal = -info.normal;
1447
+ info.normal = -info.normal; // Flip normal
1420
1448
  }
1449
+ // Circle vs Polygon
1421
1450
  else if (shapeA == COLLISION_CIRCLE && shapeB == COLLISION_POLYGON)
1422
1451
  {
1423
1452
  info = detectCirclePolygon(objA.position, objA.visualData.x,
@@ -1431,14 +1460,15 @@ CollisionInfo detectCollision(Object objA, Object objB, int objIndexB,
1431
1460
  objA.position, objA.visualData.x,
1432
1461
  int(objA.visualData.y), objA.visualData.z,
1433
1462
  objIndexB);
1434
- info.normal = -info.normal;
1463
+ info.normal = -info.normal; // Flip normal
1435
1464
  }
1465
+ // AABB vs Polygon (simplified - treat polygon as circle for collision)
1436
1466
  else if (shapeA == COLLISION_AABB && shapeB == COLLISION_POLYGON)
1437
1467
  {
1438
1468
  vec2 halfExtA = objA.visualData.xy * 0.5;
1439
1469
  info = detectCircleAABB(objB.position, objB.visualData.x,
1440
1470
  objA.position, halfExtA, objIndexB);
1441
- info.normal = -info.normal;
1471
+ info.normal = -info.normal; // Flip normal
1442
1472
  }
1443
1473
  else if (shapeA == COLLISION_POLYGON && shapeB == COLLISION_AABB)
1444
1474
  {
@@ -1451,7 +1481,11 @@ CollisionInfo detectCollision(Object objA, Object objB, int objIndexB,
1451
1481
  }
1452
1482
 
1453
1483
  // ============================================================================
1454
- // NEW: RESEARCH-GRADE COLLISION RESOLUTION SYSTEM
1484
+ // RESEARCH-GRADE COLLISION RESOLUTION SYSTEM
1485
+ // ============================================================================
1486
+
1487
+ // ============================================================================
1488
+ // BASIC COLLISION RESPONSE (Momentum-Conserving)
1455
1489
  // ============================================================================
1456
1490
 
1457
1491
  // Apply collision response to two objects (conserves momentum and energy)
@@ -1461,51 +1495,73 @@ void applyCollisionResponse(
1461
1495
  vec2 normal, float penetration,
1462
1496
  float restitution, float friction
1463
1497
  ) {
1498
+ // Ensure valid masses
1464
1499
  if (massA < EPSILON || massB < EPSILON) return;
1465
1500
 
1466
- const float positionCorrectionFactor = 0.8;
1467
- const float penetrationSlop = 0.001;
1501
+ // ========================================================================
1502
+ // 1. POSITION CORRECTION (Separating objects)
1503
+ // ========================================================================
1504
+ const float positionCorrectionFactor = 0.8; // Baumgarte stabilization
1505
+ const float penetrationSlop = 0.001; // Allow small penetration
1468
1506
 
1469
1507
  if (penetration > penetrationSlop) {
1470
1508
  float totalMass = massA + massB;
1471
1509
  float invTotalMass = 1.0 / totalMass;
1472
1510
 
1511
+ // Calculate correction vector
1473
1512
  vec2 correction = (penetration - penetrationSlop) * positionCorrectionFactor * normal;
1474
1513
 
1514
+ // Apply position correction based on mass ratio
1475
1515
  posA -= correction * (massB * invTotalMass);
1476
1516
  posB += correction * (massA * invTotalMass);
1477
1517
  }
1478
1518
 
1519
+ // ========================================================================
1520
+ // 2. VELOCITY CORRECTION (Impulse-based, conserves momentum)
1521
+ // ========================================================================
1522
+ // Relative velocity
1479
1523
  vec2 relativeVel = velB - velA;
1480
1524
  float velocityAlongNormal = dot(relativeVel, normal);
1481
1525
 
1526
+ // Only resolve if objects are moving toward each other
1482
1527
  if (velocityAlongNormal > 0.0) return;
1483
1528
 
1484
- float e = min(max(restitution, 0.0), 1.0);
1529
+ // Calculate impulse scalar (conserves momentum and energy)
1530
+ float e = min(max(restitution, 0.0), 1.0); // Clamp restitution
1485
1531
  float impulseScalar = -(1.0 + e) * velocityAlongNormal;
1486
1532
  impulseScalar /= (1.0 / massA + 1.0 / massB);
1487
1533
 
1534
+ // Apply impulse to both velocities (equal and opposite)
1488
1535
  vec2 impulse = impulseScalar * normal;
1489
1536
  velA -= impulse / massA;
1490
1537
  velB += impulse / massB;
1491
1538
 
1539
+ // ========================================================================
1540
+ // 3. FRICTION (Coulomb model)
1541
+ // ========================================================================
1492
1542
  if (friction > 0.0) {
1543
+ // Recompute relative velocity after normal impulse
1493
1544
  relativeVel = velB - velA;
1494
1545
 
1546
+ // Tangent vector (perpendicular to normal)
1495
1547
  vec2 tangent = relativeVel - normal * dot(relativeVel, normal);
1496
1548
  float tangentLength = length(tangent);
1497
1549
 
1498
1550
  if (tangentLength > EPSILON) {
1499
1551
  tangent = normalize(tangent);
1500
1552
 
1553
+ // Relative velocity in tangent direction
1501
1554
  float relVelTangent = dot(relativeVel, tangent);
1502
1555
 
1556
+ // Friction impulse scalar
1503
1557
  float frictionImpulse = -relVelTangent * friction;
1504
1558
  frictionImpulse /= (1.0 / massA + 1.0 / massB);
1505
1559
 
1560
+ // Coulomb's law: friction impulse cannot exceed normal impulse
1506
1561
  float maxFriction = abs(impulseScalar) * friction;
1507
1562
  frictionImpulse = clamp(frictionImpulse, -maxFriction, maxFriction);
1508
1563
 
1564
+ // Apply friction impulse
1509
1565
  vec2 frictionVec = frictionImpulse * tangent;
1510
1566
  velA -= frictionVec / massA;
1511
1567
  velB += frictionVec / massB;
@@ -1513,7 +1569,12 @@ void applyCollisionResponse(
1513
1569
  }
1514
1570
  }
1515
1571
 
1516
- // Simplified collision resolution for parallel execution
1572
+ // ============================================================================
1573
+ // SIMPLIFIED COLLISION RESOLUTION FOR PARALLEL EXECUTION
1574
+ // ============================================================================
1575
+
1576
+ // This version handles collisions in a thread-safe way by only modifying
1577
+ // the current object and letting the other object handle its own updates
1517
1578
  void resolveCollisionSimple(
1518
1579
  inout vec2 posA, inout vec2 velA, float massA,
1519
1580
  Object objB, int objBIndex,
@@ -1521,21 +1582,26 @@ void resolveCollisionSimple(
1521
1582
  ) {
1522
1583
  if (!collision.hasCollision) return;
1523
1584
 
1585
+ // Get collision properties
1524
1586
  CollisionProperties propsA = collisionProps[gl_GlobalInvocationID.x];
1525
1587
  CollisionProperties propsB = collisionProps[objBIndex];
1526
1588
 
1589
+ // Effective restitution and friction
1527
1590
  float restitution = min(propsA.restitution, propsB.restitution);
1528
1591
  float friction = sqrt(propsA.friction * propsB.friction);
1529
1592
 
1593
+ // Other object's state
1530
1594
  vec2 posB = objB.position;
1531
1595
  vec2 velB = objB.velocity;
1532
1596
  float massB = max(EPSILON, objB.mass);
1533
1597
 
1598
+ // Copy state for local processing
1534
1599
  vec2 tempPosA = posA;
1535
1600
  vec2 tempVelA = velA;
1536
1601
  vec2 tempPosB = posB;
1537
1602
  vec2 tempVelB = velB;
1538
1603
 
1604
+ // Apply collision response
1539
1605
  applyCollisionResponse(
1540
1606
  tempPosA, tempVelA, massA,
1541
1607
  tempPosB, tempVelB, massB,
@@ -1543,16 +1609,25 @@ void resolveCollisionSimple(
1543
1609
  restitution, friction
1544
1610
  );
1545
1611
 
1612
+ // Update this object's state
1546
1613
  posA = tempPosA;
1547
1614
  velA = tempVelA;
1615
+
1616
+ // Note: The other object (objB) will handle its own update in its thread
1617
+ // This ensures momentum is conserved across the entire system
1548
1618
  }
1549
1619
 
1550
- // Elastic collision formula (for perfect conservation)
1620
+ // ============================================================================
1621
+ // ANALYTICAL ELASTIC COLLISION FORMULA (For perfect conservation)
1622
+ // ============================================================================
1623
+
1624
+ // Perfectly elastic collision for circles (conserves both momentum and energy)
1551
1625
  void resolveElasticCollision(
1552
1626
  inout vec2 posA, inout vec2 velA, float massA,
1553
1627
  inout vec2 posB, inout vec2 velB, float massB,
1554
1628
  vec2 normal, float penetration
1555
1629
  ) {
1630
+ // Position correction
1556
1631
  const float positionPush = 0.5;
1557
1632
  if (penetration > 0.0) {
1558
1633
  float totalMass = massA + massB;
@@ -1560,17 +1635,21 @@ void resolveElasticCollision(
1560
1635
  posB += normal * penetration * positionPush * (massA / totalMass);
1561
1636
  }
1562
1637
 
1638
+ // Relative velocity along collision normal
1563
1639
  vec2 relativeVel = velB - velA;
1564
1640
  float velocityAlongNormal = dot(relativeVel, normal);
1565
1641
 
1642
+ // If objects are separating, no impulse needed
1566
1643
  if (velocityAlongNormal > 0.0) return;
1567
1644
 
1645
+ // Perfectly elastic collision formulas (conserves both momentum and energy)
1568
1646
  float totalMass = massA + massB;
1569
1647
  float massRatioA = (massA - massB) / totalMass;
1570
1648
  float massRatioB = (2.0 * massB) / totalMass;
1571
1649
  float massRatioC = (2.0 * massA) / totalMass;
1572
1650
  float massRatioD = (massB - massA) / totalMass;
1573
1651
 
1652
+ // Apply velocity changes
1574
1653
  vec2 impulseA = normal * velocityAlongNormal;
1575
1654
  vec2 impulseB = normal * velocityAlongNormal;
1576
1655
 
@@ -1578,7 +1657,11 @@ void resolveElasticCollision(
1578
1657
  velB = velB - massRatioC * impulseB;
1579
1658
  }
1580
1659
 
1581
- // Contact persistence system
1660
+ // ============================================================================
1661
+ // CONTACT PERSISTENCE SYSTEM (Optional, for stability)
1662
+ // ============================================================================
1663
+
1664
+ // Find a persistent contact in the contact buffer
1582
1665
  int findPersistentContact(int objectA, int objectB, vec2 normal) {
1583
1666
  int baseIndex = objectA * MAX_CONTACTS_PER_OBJECT;
1584
1667
 
@@ -1588,26 +1671,32 @@ int findPersistentContact(int objectA, int objectB, vec2 normal) {
1588
1671
 
1589
1672
  ContactPoint contact = contacts[contactIndex];
1590
1673
 
1674
+ // Check if this contact is between the same objects
1675
+ // and has a similar normal direction
1591
1676
  if (contact.frameCount > 0 && dot(contact.normal, normal) > 0.9) {
1592
1677
  return contactIndex;
1593
1678
  }
1594
1679
  }
1595
1680
 
1596
- return -1;
1681
+ return -1; // No persistent contact found
1597
1682
  }
1598
1683
 
1684
+ // Update or create a contact point
1599
1685
  void updateContactPoint(int objectA, int objectB, vec2 normal, vec2 position, float penetration) {
1600
1686
  int baseIndex = objectA * MAX_CONTACTS_PER_OBJECT;
1601
1687
  int oldestIndex = baseIndex;
1602
1688
  int oldestFrameCount = 9999;
1603
1689
 
1690
+ // Find slot for this contact
1604
1691
  for (int i = 0; i < MAX_CONTACTS_PER_OBJECT; i++) {
1605
1692
  int contactIndex = baseIndex + i;
1606
1693
  if (contactIndex >= contacts.length()) break;
1607
1694
 
1608
1695
  ContactPoint contact = contacts[contactIndex];
1609
1696
 
1697
+ // If this slot is empty or matches our contact, use it
1610
1698
  if (contact.frameCount == 0 || dot(contact.normal, normal) > 0.9) {
1699
+ // Update existing contact
1611
1700
  contacts[contactIndex].normal = normal;
1612
1701
  contacts[contactIndex].position = position;
1613
1702
  contacts[contactIndex].penetration = penetration;
@@ -1615,12 +1704,14 @@ void updateContactPoint(int objectA, int objectB, vec2 normal, vec2 position, fl
1615
1704
  return;
1616
1705
  }
1617
1706
 
1707
+ // Track the oldest contact for replacement
1618
1708
  if (contact.frameCount < oldestFrameCount) {
1619
1709
  oldestFrameCount = contact.frameCount;
1620
1710
  oldestIndex = contactIndex;
1621
1711
  }
1622
1712
  }
1623
1713
 
1714
+ // Replace the oldest contact
1624
1715
  if (oldestIndex >= 0 && oldestIndex < contacts.length()) {
1625
1716
  contacts[oldestIndex].normal = normal;
1626
1717
  contacts[oldestIndex].position = position;
@@ -1631,6 +1722,7 @@ void updateContactPoint(int objectA, int objectB, vec2 normal, vec2 position, fl
1631
1722
  }
1632
1723
  }
1633
1724
 
1725
+ // Age out old contacts
1634
1726
  void ageContacts(int objectIndex) {
1635
1727
  int baseIndex = objectIndex * MAX_CONTACTS_PER_OBJECT;
1636
1728
 
@@ -1638,9 +1730,11 @@ void ageContacts(int objectIndex) {
1638
1730
  int contactIndex = baseIndex + i;
1639
1731
  if (contactIndex >= contacts.length()) break;
1640
1732
 
1733
+ // Decrement frame count, remove if too old
1641
1734
  if (contacts[contactIndex].frameCount > 0) {
1642
1735
  contacts[contactIndex].frameCount--;
1643
1736
  if (contacts[contactIndex].frameCount == 0) {
1737
+ // Reset contact
1644
1738
  contacts[contactIndex].accumulatedNormalImpulse = 0.0;
1645
1739
  contacts[contactIndex].accumulatedTangentImpulse = 0.0;
1646
1740
  }
@@ -1649,14 +1743,16 @@ void ageContacts(int objectIndex) {
1649
1743
  }
1650
1744
 
1651
1745
  // ============================================================================
1652
- // MAIN COMPUTE SHADER ENTRY POINT - WITH AABB COLLISION FIX
1746
+ // MAIN COMPUTE SHADER ENTRY POINT - UPDATED WITH FIXED COLLISION RESOLUTION
1653
1747
  // ============================================================================
1654
1748
 
1655
1749
  void main() {
1656
1750
  uint gid = gl_GlobalInvocationID.x;
1657
1751
 
1752
+ // Early exit if beyond active object count
1658
1753
  if (int(gid) >= uNumObjects) return;
1659
1754
 
1755
+ // Read current object state
1660
1756
  Object p = objectsIn[gid];
1661
1757
  vec2 pos = p.position;
1662
1758
  vec2 vel = p.velocity;
@@ -1668,12 +1764,16 @@ void main() {
1668
1764
  int objectIndex = int(gid);
1669
1765
  vec2 originalPos = pos;
1670
1766
 
1767
+ // Initialize physics variables
1671
1768
  vec2 acceleration = vec2(0.0);
1672
1769
  float angular_accel = 0.0;
1673
1770
  vec4 new_color = color;
1674
1771
 
1675
- // Physics equation evaluation (unchanged)
1772
+ // ========================================================================
1773
+ // PHYSICS EQUATION EVALUATION (UNCHANGED)
1774
+ // ========================================================================
1676
1775
  if (uEquationMode == 0) {
1776
+ // Custom equation mode
1677
1777
  int eqID = p.equationID;
1678
1778
  bool isValidEquation = (eqID >= 0 && eqID < MAX_EQUATION_COUNT) &&
1679
1779
  (mappings[eqID].tokenCount_ax > 0 ||
@@ -1685,18 +1785,22 @@ void main() {
1685
1785
  mappings[eqID].tokenCount_a > 0);
1686
1786
 
1687
1787
  if (!isValidEquation) {
1788
+ // Use default physics if equation is invalid
1688
1789
  acceleration = calculateDefaultPhysics(pos, vel, mass);
1689
1790
  } else {
1791
+ // Evaluate custom physics equation components
1690
1792
  EquationMapping mapping = mappings[eqID];
1691
1793
  float ax = 0.0;
1692
1794
  float ay = 0.0;
1693
1795
 
1796
+ // Evaluate X acceleration component
1694
1797
  if (mapping.tokenCount_ax > 0) {
1695
1798
  ax = evaluateRPNComponent(pos.x, pos.y, vel.x, vel.y, p.collisionData.x, p.collisionData.y,
1696
1799
  rotation, angular_vel, color, mass, charge, objectIndex,
1697
1800
  0, mapping.tokenOffset_ax, mapping.tokenCount_ax, mapping.constantOffset_ax);
1698
1801
  }
1699
1802
 
1803
+ // Evaluate Y acceleration component
1700
1804
  if (mapping.tokenCount_ay > 0) {
1701
1805
  ay = evaluateRPNComponent(pos.x, pos.y, vel.x, vel.y, p.collisionData.x, p.collisionData.y,
1702
1806
  rotation, angular_vel, color, mass, charge, objectIndex,
@@ -1705,6 +1809,7 @@ void main() {
1705
1809
 
1706
1810
  acceleration = sanitizeVec2(vec2(ax, ay));
1707
1811
 
1812
+ // Evaluate angular acceleration
1708
1813
  if (mapping.tokenCount_angular > 0) {
1709
1814
  angular_accel = evaluateRPNComponent(pos.x, pos.y, vel.x, vel.y, p.collisionData.x, p.collisionData.y,
1710
1815
  rotation, angular_vel, color, mass, charge, objectIndex,
@@ -1712,6 +1817,7 @@ void main() {
1712
1817
  angular_accel = isInvalidFloat(angular_accel) ? 0.0 : angular_accel;
1713
1818
  }
1714
1819
 
1820
+ // Evaluate color components (dynamic coloring)
1715
1821
  if (mapping.tokenCount_r > 0) {
1716
1822
  new_color.r = evaluateRPNComponent(pos.x, pos.y, vel.x, vel.y, p.collisionData.x, p.collisionData.y,
1717
1823
  rotation, angular_vel, color, mass, charge, objectIndex,
@@ -1736,31 +1842,42 @@ void main() {
1736
1842
  new_color = sanitizeVec4(new_color);
1737
1843
  }
1738
1844
  } else {
1845
+ // Default physics mode (simple spring-mass-damper)
1739
1846
  acceleration = calculateDefaultPhysics(pos, vel, mass);
1740
1847
  }
1741
1848
 
1742
1849
  acceleration = sanitizeVec2(acceleration);
1743
1850
 
1744
- // Symplectic Euler integration
1851
+ // ========================================================================
1852
+ // STABLE INTEGRATION (Symplectic Euler - Energy Conserving)
1853
+ // ========================================================================
1854
+
1855
+ // 1. Update VELOCITY first using current acceleration
1745
1856
  vec2 new_vel = vel + acceleration * uDt;
1746
1857
  new_vel = sanitizeVec2(new_vel);
1747
1858
 
1859
+ // 2. Use NEW VELOCITY to update POSITION (energy-conserving)
1748
1860
  vec2 new_pos = pos + new_vel * uDt;
1749
1861
  new_pos = sanitizeVec2(new_pos);
1750
1862
 
1751
- Object selfNew = p;
1752
- selfNew.position = new_pos;
1753
-
1863
+ Object selfNew = p; // copy original
1864
+ selfNew.position = new_pos; // update position
1865
+
1866
+ // 3. Same for angular motion
1754
1867
  float new_angular_vel = angular_vel + angular_accel * uDt;
1755
1868
  float new_rotation = rotation + new_angular_vel * uDt;
1756
- new_rotation = mod(new_rotation, 2.0 * PI);
1869
+ new_rotation = mod(new_rotation, 2.0 * PI); // Wrap rotation
1757
1870
 
1871
+ // ========================================================================
1872
+ // CONSTRAINT RESOLUTION (UNCHANGED)
1873
+ // ========================================================================
1758
1874
  applyConstraints(new_pos, new_vel, objectIndex, originalPos);
1759
-
1875
+
1760
1876
  // ========================================================================
1761
- // COLLISION HANDLING - using old positions for normal sign in AABB case
1877
+ // COLLISION HANDLING - Keep selfNew for circles, but fix stickiness for rectangles
1762
1878
  // ========================================================================
1763
1879
  vec2 collision_vel = new_vel;
1880
+ vec2 final_pos = new_pos; // Accumulate corrections (prevents feedback loop)
1764
1881
  bool had_collision = false;
1765
1882
 
1766
1883
  for (int i = 0; i < uNumObjects; i++) {
@@ -1768,9 +1885,8 @@ void main() {
1768
1885
 
1769
1886
  Object other = objectsIn[i];
1770
1887
 
1771
- // Pass current positions for overlap detection, but also
1772
- // original (pre-update) positions for correct normal sign.
1773
- CollisionInfo collision = detectCollision(selfNew, other, i, p.position, other.position);
1888
+ // Keep your selfNew detection - this is what made circles bouncy
1889
+ CollisionInfo collision = detectCollision(selfNew, other, i); // <--- selfNew here
1774
1890
 
1775
1891
  if (collision.hasCollision) {
1776
1892
  had_collision = true;
@@ -1784,43 +1900,45 @@ void main() {
1784
1900
  float massA = mass;
1785
1901
  float massB = max(EPSILON, other.mass);
1786
1902
 
1903
+ // Velocity impulse (asymmetric - only affect this object)
1787
1904
  vec2 relativeVel = other.velocity - collision_vel;
1788
- float velocityAlongNormal = dot(relativeVel, collision.normal);
1905
+ float velAlongNormal = dot(relativeVel, collision.normal);
1789
1906
 
1790
- if (velocityAlongNormal < 0.0) {
1907
+ if (velAlongNormal < 0.0) { // approaching
1791
1908
  float e = clamp(restitution, 0.0, 1.0);
1792
- float j = -(1.0 + e) * velocityAlongNormal;
1793
- j /= (1.0 / massA + 1.0 / massB);
1909
+ float j = -(1.0 + e) * velAlongNormal / (1.0 / massA + 1.0 / massB);
1794
1910
 
1795
- vec2 impulse = j * collision.normal;
1796
- collision_vel -= impulse / massA;
1911
+ collision_vel -= (j * collision.normal) / massA;
1797
1912
 
1913
+ // Friction - conservative
1798
1914
  if (friction > 0.0) {
1799
1915
  vec2 tangent = relativeVel - collision.normal * dot(relativeVel, collision.normal);
1800
- float tangentLength = length(tangent);
1801
-
1802
- if (tangentLength > EPSILON) {
1916
+ float tLen = length(tangent);
1917
+ if (tLen > EPSILON) {
1803
1918
  tangent = normalize(tangent);
1804
1919
  float vt = dot(relativeVel, tangent);
1805
-
1806
- float jt = -vt * friction;
1807
- jt /= (1.0 / massA + 1.0 / massB);
1808
-
1809
- float maxFriction = abs(j) * friction;
1810
- jt = clamp(jt, -maxFriction, maxFriction);
1811
-
1812
- vec2 frictionImpulse = jt * tangent;
1813
- collision_vel -= frictionImpulse / massA;
1920
+ float jt = -vt * friction / (1.0 / massA + 1.0 / massB);
1921
+ float maxJt = abs(j) * friction;
1922
+ jt = clamp(jt, -maxJt, maxJt);
1923
+ collision_vel -= (jt * tangent) / massA;
1814
1924
  }
1815
1925
  }
1816
1926
  }
1817
1927
 
1818
- const float slop = 0.01;
1819
- const float percent = 0.4;
1928
+ // Position correction - ACCUMULATED + rectangle-tuned values
1929
+ const float slop = 0.025; // Increased to reduce micro-corrections on flat edges
1930
+ const float percent = 0.75; // Stronger push to separate cleanly
1931
+
1820
1932
  if (collision.penetration > slop) {
1821
1933
  float totalMass = massA + massB;
1822
- vec2 correction = collision.normal * (collision.penetration - slop) * percent;
1823
- new_pos -= correction * (massB / totalMass);
1934
+ vec2 correction = collision.normal * (collision.penetration - slop) * percent * (massB / totalMass);
1935
+
1936
+ // Force correction direction away from the other object
1937
+ if (dot(collision.normal, other.position - selfNew.position) < 0.0) {
1938
+ correction = -correction;
1939
+ }
1940
+
1941
+ final_pos -= correction;
1824
1942
  }
1825
1943
  }
1826
1944
  }
@@ -1828,21 +1946,35 @@ void main() {
1828
1946
  if (had_collision) {
1829
1947
  new_vel = sanitizeVec2(collision_vel);
1830
1948
  }
1949
+ new_pos = sanitizeVec2(final_pos); // Apply all corrections at once
1950
+
1951
+ // ========================================================================
1952
+ // REST OF CODE(boundaries, final write, etc.)
1953
+ // ========================================================================
1831
1954
 
1955
+ // Age out old contacts for this object
1832
1956
  if (uEnableWarmStart == 1) {
1833
1957
  ageContacts(objectIndex);
1834
1958
  }
1835
1959
 
1960
+ // Apply the collision-corrected state
1836
1961
  new_pos = sanitizeVec2(new_pos);
1837
1962
  new_vel = sanitizeVec2(new_vel);
1838
1963
 
1964
+ // ========================================================================
1965
+ // FINAL SANITY CHECKS
1966
+ // ========================================================================
1967
+
1968
+ // Ensure velocities aren't excessive
1839
1969
  float maxSpeed = 1000.0;
1840
1970
  float currentSpeed = length(new_vel);
1841
1971
  if (currentSpeed > maxSpeed) {
1842
1972
  new_vel = normalize(new_vel) * maxSpeed;
1843
1973
  }
1844
1974
 
1845
- // Write updated object state to output buffer
1975
+ // ========================================================================
1976
+ // WRITE UPDATED OBJECT STATE
1977
+ // ========================================================================
1846
1978
  objectsOut[gid].position = new_pos;
1847
1979
  objectsOut[gid].velocity = new_vel;
1848
1980
  objectsOut[gid].mass = mass;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperstellar
3
- Version: 0.1.64
3
+ Version: 0.1.65
4
4
  Summary: GPU-accelerated physics simulation engine
5
5
  Author-email: Aiden Jabari <Jabariaiden15@email.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hyperstellar"
7
- version = "0.1.64"
7
+ version = "0.1.65"
8
8
  description = "GPU-accelerated physics simulation engine"
9
9
  readme = "README.md"
10
10
  authors = [{name = "Aiden Jabari", email = "Jabariaiden15@email.com"}]
@@ -63,5 +63,6 @@ where = ["hyperstellar/src"]
63
63
 
64
64
 
65
65
 
66
+
66
67
 
67
68
 
File without changes
File without changes
File without changes