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