@dimension-mismatch/2dphysics 0.0.2 → 0.0.4

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.
package/body.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Rotation, vec2 } from "../vec2/calc.js";
1
+ import { Rotation, vec2 } from "@dimension-mismatch/vec2";
2
2
  import { Circle, Polygon, Shape, ShapeType } from "./geometry.js";
3
3
  let uniqueID: number = 0;
4
4
 
@@ -167,9 +167,11 @@ export class PhysicsObject implements GameObject{
167
167
  private calculateProperties(skipCOM?: boolean){
168
168
  let COM = new vec2(0,0);
169
169
  let totalArea = 0;
170
+
170
171
  for(let i = 0; i < this.colliders.length; i++){
171
- totalArea += this.colliders[i].area;
172
- COM.add(vec2.times(this.colliders[i].COM, this.colliders[i].area));
172
+ let com = this.colliders[i].computeWeightedCOM();
173
+ totalArea += com.area;
174
+ COM.add(vec2.times(com.COM, com.area));
173
175
  }
174
176
  COM.divideBy(totalArea);
175
177
  this.mass = totalArea * this.material.density;
@@ -179,7 +181,7 @@ export class PhysicsObject implements GameObject{
179
181
  let inertia = 0;
180
182
  for(let i = 0; i < this.colliders.length; i++){
181
183
  this.colliders[i].translate(COM.inverse());
182
- inertia += this.colliders[i].inertia;
184
+ inertia += this.colliders[i].computeInertia();
183
185
  }
184
186
  this.inertia = inertia * this.material.density;
185
187
 
package/collision.ts CHANGED
@@ -1,20 +1,17 @@
1
1
  import { PhysicsObject } from "./body.js";
2
- import { Rotation, vec2 } from "../vec2/calc.js";
2
+ import { Rotation, vec2 } from "@dimension-mismatch/vec2";
3
3
  import { Circle, Polygon, Shape, ShapeType, Vertex } from "./geometry.js";
4
4
 
5
5
  export interface Contact{
6
6
  depth: number;
7
7
  normal: vec2;
8
- objectA: PhysicsObject;
9
- objectB: PhysicsObject;
10
-
11
- shapeA: Shape;
12
- shapeB: Shape;
8
+ objectA?: PhysicsObject;
9
+ objectB?: PhysicsObject;
13
10
 
14
11
  contactPoints: vec2[];
15
12
  }
16
13
 
17
- export interface SATresult{
14
+ interface SATresult{
18
15
  axis: vec2;
19
16
  depth: number;
20
17
  Aindex: number;
@@ -26,21 +23,31 @@ function invert(r: Contact){
26
23
  let objectB = r.objectB;
27
24
  r.objectB = r.objectA;
28
25
  r.objectA = objectB;
29
-
30
- let shapeB = r.shapeB;
31
- r.shapeB = r.shapeA;
32
- r.shapeA = shapeB;
33
26
  }
34
- function objectToVertexSpace(v: vec2, vert: Vertex): vec2{
35
- return vec2.worldToLocalSpace(v , vert.position, new Rotation(0, vert.normal.x, vert.normal.y))
27
+ function worldToVertexSpace(v: vec2, vert: Vertex): vec2{
28
+ return vec2.worldToLocalSpace(v , vert.position, new Rotation(0, vert.normal.x, vert.normal.y));
36
29
  }
37
- function worldToVertexSpace(v: vec2, objectA: PhysicsObject, objectB: PhysicsObject, vert: Vertex): vec2{
38
- return objectToVertexSpace(objectB.worldToLocalSpace(objectA.localToWorldSpace(v)), vert);
30
+ function vertexToWorldSpace(v: vec2, vert: Vertex): vec2{
31
+ return vec2.localToWorldSpace(v , vert.position, new Rotation(0, vert.normal.x, vert.normal.y));
39
32
  }
40
- function vertexToWorldSpace(v: vec2, objectB: PhysicsObject, vert: Vertex){
41
- return objectB.localToWorldSpace(vec2.localToWorldSpace(v , vert.position, {angle: 0, cos: vert.normal.x, sin: vert.normal.y} as Rotation));
33
+ function shapeFromObjectToWorldSpace(shape: Shape, object: PhysicsObject): Shape{
34
+ switch(shape.type){
35
+ case ShapeType.POLYGON:
36
+ const polygon = shape as Polygon;
37
+ return {vertices: polygon.vertices.map((v) =>
38
+ {return {isInternal: v.isInternal,
39
+ position: object.localToWorldSpace(v.position),
40
+ normal: object.localToAASpace(v.normal)
41
+ }}), type: ShapeType.POLYGON} as Polygon;
42
+ case ShapeType.CIRCLE:
43
+ const circle = shape as Circle;
44
+ return {position: object.localToWorldSpace(circle.position),
45
+ radius: circle.radius,
46
+ type: ShapeType.CIRCLE
47
+ } as Circle;
48
+ }
42
49
  }
43
- function SAT(shapeA: Polygon, shapeB: Polygon, objectA: PhysicsObject, objectB: PhysicsObject): Contact | false{
50
+ function SAT(shapeA: Polygon, shapeB: Polygon): Contact | false{
44
51
  let bestResult: SATresult = {axis: new vec2(0,0), depth: Infinity, Aindex: 0, Bindex: 0}
45
52
 
46
53
  //find which normal of shapeB has the least overlap
@@ -53,10 +60,8 @@ function SAT(shapeA: Polygon, shapeB: Polygon, objectA: PhysicsObject, objectB:
53
60
  let BmaxProjection = vec2.dot(shapeB.vertices[axis].position, normal);
54
61
 
55
62
  for(let vertex = 0; vertex < shapeA.vertices.length; vertex++){
56
- let worldPosition = objectA.localToWorldSpace(shapeA.vertices[vertex].position);
57
- let localPosition = objectB.worldToLocalSpace(worldPosition);
58
63
 
59
- let proj = vec2.dot(localPosition, normal);
64
+ let proj = vec2.dot(shapeA.vertices[vertex].position, normal);
60
65
  if(proj < AminProjection){
61
66
  AminProjection = proj;
62
67
  mindex = vertex;
@@ -88,10 +93,6 @@ function SAT(shapeA: Polygon, shapeB: Polygon, objectA: PhysicsObject, objectB:
88
93
 
89
94
  let n2 = shapeA.vertices[n1idx].normal;
90
95
 
91
-
92
- n1 = vec2.rotatedBy(n1, objectA.angle).rotateBy(Rotation.inverse(objectB.angle));
93
- n2 = vec2.rotatedBy(n2, objectA.angle).rotateBy(Rotation.inverse(objectB.angle));
94
-
95
96
  let contactPoints: vec2[];
96
97
  if(vec2.dot(n1, bestResult.axis) < vec2.dot(n2, bestResult.axis)){
97
98
  contactPoints = [shapeA.vertices[n0idx].position, shapeA.vertices[n1idx].position];
@@ -99,8 +100,8 @@ function SAT(shapeA: Polygon, shapeB: Polygon, objectA: PhysicsObject, objectB:
99
100
  else{
100
101
  contactPoints = [shapeA.vertices[n1idx].position, shapeA.vertices[n2idx].position];
101
102
  }
102
- contactPoints = contactPoints.map((v) => (worldToVertexSpace(v, objectA, objectB, shapeB.vertices[bestResult.Bindex])));
103
- let b2 = objectToVertexSpace(shapeB.vertices[b1idx].position, shapeB.vertices[bestResult.Bindex]);
103
+ contactPoints = contactPoints.map((v) => (worldToVertexSpace(v, shapeB.vertices[bestResult.Bindex])));
104
+ let b2 = worldToVertexSpace(shapeB.vertices[b1idx].position, shapeB.vertices[bestResult.Bindex]);
104
105
 
105
106
  for(let i = contactPoints.length - 1; i >= 0; i--){
106
107
  if(contactPoints[i].x > 0){
@@ -117,28 +118,19 @@ function SAT(shapeA: Polygon, shapeB: Polygon, objectA: PhysicsObject, objectB:
117
118
  contactPoints[i].y = b2.y;
118
119
  }
119
120
  }
120
- contactPoints = contactPoints.map((v) => (vertexToWorldSpace(v, objectB, shapeB.vertices[bestResult.Bindex])));
121
+ contactPoints = contactPoints.map((v) => (vertexToWorldSpace(v, shapeB.vertices[bestResult.Bindex])));
121
122
  return {
122
- shapeA: shapeA,
123
- shapeB: shapeB,
124
-
125
- objectA: objectA,
126
- objectB: objectB,
127
-
128
- normal: vec2.rotatedBy(bestResult.axis,objectB.angle),
123
+ normal: bestResult.axis,
129
124
  depth: bestResult.depth,
130
-
131
125
  contactPoints: contactPoints
132
-
133
126
  }
134
- //return bestResult;
135
127
  }
136
- export function PolygonCollsion(shapeA: Polygon, shapeB: Polygon, objectA: PhysicsObject, objectB: PhysicsObject): Contact | false{
137
- let rA = SAT(shapeA, shapeB, objectA, objectB);
128
+ function PolygonCollsion(shapeA: Polygon, shapeB: Polygon): Contact | false{
129
+ let rA = SAT(shapeA, shapeB);
138
130
  if(!rA){
139
131
  return false;
140
132
  }
141
- let rB = SAT(shapeB, shapeA, objectB, objectA);
133
+ let rB = SAT(shapeB, shapeA);
142
134
  if(!rB){
143
135
  return false;
144
136
  }
@@ -150,8 +142,7 @@ export function PolygonCollsion(shapeA: Polygon, shapeB: Polygon, objectA: Physi
150
142
  return rA;
151
143
  }
152
144
  }
153
- export function CirclePolygonCollision(shapeA: Circle, shapeB: Polygon, objectA: PhysicsObject, objectB: PhysicsObject): Contact | false{
154
- let localCenter = objectB.worldToLocalSpace(objectA.localToWorldSpace(shapeA.COM));
145
+ function CirclePolygonCollision(shapeA: Circle, shapeB: Polygon): Contact | false{
155
146
 
156
147
  let bestResult: {distance: number, normal: vec2, contact: vec2};
157
148
  bestResult = {distance: Infinity, normal: vec2.zero(), contact: vec2.zero()};
@@ -160,8 +151,8 @@ export function CirclePolygonCollision(shapeA: Circle, shapeB: Polygon, objectA:
160
151
  for(let i = 0; i < shapeB.vertices.length; i++){
161
152
  let vertex = shapeB.vertices[i];
162
153
 
163
- let relativeCenter = objectToVertexSpace(localCenter, vertex);
164
- let relativeLast = objectToVertexSpace(lastPoint, vertex);
154
+ let relativeCenter = worldToVertexSpace(shapeA.position, vertex);
155
+ let relativeLast = worldToVertexSpace(lastPoint, vertex);
165
156
  lastPoint = vertex.position;
166
157
 
167
158
 
@@ -171,8 +162,8 @@ export function CirclePolygonCollision(shapeA: Circle, shapeB: Polygon, objectA:
171
162
  if(relativeCenter.y > 0){
172
163
  currentResult = {
173
164
  distance: relativeCenter.mag(),
174
- contact: new vec2(0,0),
175
- normal: vec2.asUnitVector(vec2.minus(localCenter, vertex.position))}
165
+ contact: vertex.position,
166
+ normal: vec2.asUnitVector(vec2.minus(shapeA.position, vertex.position))}
176
167
 
177
168
  }
178
169
  else{
@@ -184,7 +175,7 @@ export function CirclePolygonCollision(shapeA: Circle, shapeB: Polygon, objectA:
184
175
  }
185
176
  currentResult = {
186
177
  distance: relativeCenter.x,
187
- contact: new vec2(0, relativeCenter.y),
178
+ contact: vertexToWorldSpace(new vec2(0, relativeCenter.y), vertex),
188
179
  normal: vertex.normal}
189
180
  }
190
181
 
@@ -194,29 +185,21 @@ export function CirclePolygonCollision(shapeA: Circle, shapeB: Polygon, objectA:
194
185
 
195
186
 
196
187
  if(Math.abs(currentResult.distance) < Math.abs(bestResult.distance)){
197
- bestResult.distance = currentResult.distance;
198
- bestResult.normal = currentResult.normal;
199
- bestResult.contact = vertexToWorldSpace(currentResult.contact, objectB, vertex);
188
+ bestResult = currentResult;
200
189
  }
201
190
  }
202
191
  if(bestResult.distance > shapeA.radius){
203
192
  return false;
204
193
  }
205
194
  return {
206
- shapeA: shapeA,
207
- shapeB: shapeB,
208
-
209
- objectA: objectA,
210
- objectB: objectB,
211
-
212
- normal: vec2.rotatedBy(bestResult.normal, objectB.angle),
195
+ normal: bestResult.normal,
213
196
  depth: shapeA.radius - bestResult.distance,
214
197
 
215
198
  contactPoints: [bestResult.contact]
216
199
  }
217
200
  }
218
- export function PolygonCircleCollsion(shapeA: Polygon, shapeB: Circle, objectA: PhysicsObject, objectB: PhysicsObject): Contact | false{
219
- let ret = CirclePolygonCollision(shapeB, shapeA, objectB, objectA);
201
+ function PolygonCircleCollsion(shapeA: Polygon, shapeB: Circle): Contact | false{
202
+ let ret = CirclePolygonCollision(shapeB, shapeA);
220
203
  if(!ret){
221
204
  return false;
222
205
  }
@@ -226,9 +209,8 @@ export function PolygonCircleCollsion(shapeA: Polygon, shapeB: Circle, objectA:
226
209
  }
227
210
  }
228
211
 
229
- export function CircleCircleCollision(shapeA: Circle, shapeB: Circle, objectA: PhysicsObject, objectB: PhysicsObject): Contact | false{
230
- let Acenter = objectB.worldToLocalSpace(objectA.localToWorldSpace(shapeA.COM));
231
- let between = vec2.minus(Acenter, shapeB.COM);
212
+ function CircleCircleCollision(shapeA: Circle, shapeB: Circle): Contact | false{
213
+ let between = vec2.minus(shapeA.position, shapeB.position);
232
214
  let dist = between.mag();
233
215
  if(between.x == 0 && between.y == 0){
234
216
  between.y = 1;
@@ -240,33 +222,27 @@ export function CircleCircleCollision(shapeA: Circle, shapeB: Circle, objectA: P
240
222
  let normal = vec2.dividedBy(between, dist);
241
223
  return{
242
224
  depth: shapeA.radius + shapeB.radius - dist,
243
- normal: normal.rotateBy(objectB.angle),
244
-
245
- shapeA: shapeA,
246
- shapeB: shapeB,
247
-
248
- objectA: objectA,
249
- objectB: objectB,
225
+ normal: normal,
250
226
 
251
- contactPoints: [objectB.localToWorldSpace(vec2.times(normal, shapeB.radius))]
227
+ contactPoints: [vec2.plus(shapeB.position, vec2.times(normal, shapeB.radius))]
252
228
  }
253
229
  }
254
230
  }
255
- export function ShapeCollision(shapeA: Shape, shapeB: Shape, objectA: PhysicsObject, objectB: PhysicsObject): Contact | false{
231
+ export function ShapeCollision(shapeA: Shape, shapeB: Shape): Contact | false{
256
232
  if(shapeA.type == ShapeType.CIRCLE){
257
233
  if(shapeB.type == ShapeType.CIRCLE){
258
- return CircleCircleCollision(shapeA as Circle, shapeB as Circle, objectA, objectB);
234
+ return CircleCircleCollision(shapeA as Circle, shapeB as Circle);
259
235
  }
260
236
  else{
261
- return CirclePolygonCollision(shapeA as Circle, shapeB as Polygon, objectA, objectB);
237
+ return CirclePolygonCollision(shapeA as Circle, shapeB as Polygon);
262
238
  }
263
239
  }
264
240
  else{
265
241
  if(shapeB.type == ShapeType.CIRCLE){
266
- return PolygonCircleCollsion(shapeA as Polygon, shapeB as Circle, objectA, objectB);
242
+ return PolygonCircleCollsion(shapeA as Polygon, shapeB as Circle);
267
243
  }
268
244
  else{
269
- return PolygonCollsion(shapeA as Polygon, shapeB as Polygon, objectA, objectB);
245
+ return PolygonCollsion(shapeA as Polygon, shapeB as Polygon);
270
246
  }
271
247
  }
272
248
  }
@@ -274,8 +250,12 @@ export function Collision(objectA: PhysicsObject, objectB: PhysicsObject): Conta
274
250
  let results: Contact[] = [];
275
251
  for(let i = 0; i < objectA.colliders.length; i++){
276
252
  for(let j = 0; j < objectB.colliders.length; j++){
277
- let res = ShapeCollision(objectA.colliders[i], objectB.colliders[j], objectA, objectB);
253
+ let transformedA = shapeFromObjectToWorldSpace(objectA.colliders[i], objectA);
254
+ let transformedB = shapeFromObjectToWorldSpace(objectB.colliders[j], objectB);
255
+ let res = ShapeCollision(transformedA, transformedB);
278
256
  if(res){
257
+ res.objectA = objectA;
258
+ res.objectB = objectB;
279
259
  results.push(res);
280
260
  }
281
261
  }
@@ -1,5 +1,4 @@
1
- import { PhysicsObject } from "../body.js";
2
- import { Rotation, vec2 } from "../../vec2/calc.js";
1
+
3
2
  export enum ConstraintType{
4
3
  Spring,
5
4
  Mouse,
@@ -1,5 +1,5 @@
1
1
  import { PhysicsObject } from "../body.js";
2
- import { vec2 } from "../../vec2/calc.js";
2
+ import { vec2 } from "@dimension-mismatch/vec2";
3
3
  import { Constraint, ConstraintType } from "./constraint.js";
4
4
  export interface SpringOptions{
5
5
  pointA?: vec2;
@@ -1,5 +1,5 @@
1
1
  import { PhysicsObject } from "../body.js";
2
- import { vec2, Rotation } from "../../vec2/calc.js";
2
+ import { vec2, Rotation } from "@dimension-mismatch/vec2";
3
3
  import { Constraint, ConstraintType } from "./constraint.js";
4
4
  export interface MotorDrivable extends Constraint{
5
5
  applyMotorTorque(torque: number): void;
package/geometry.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Rotation, vec2 } from "../vec2/calc.js";
1
+ import { Rotation, vec2 } from "@dimension-mismatch/vec2";
2
2
 
3
3
  export class Vertex{
4
4
  position: vec2;
@@ -14,42 +14,50 @@ export type Shape = Polygon | Circle;
14
14
 
15
15
  export class Polygon{
16
16
  type = ShapeType.POLYGON;
17
- area: number;
18
- inertia: number;
19
- COM: vec2;
20
17
  vertices: Vertex[];
21
18
  constructor(points: vec2[], internalEdges?: boolean[]){
22
- let totalArea = 0;
23
- this.COM = vec2.zero();
24
19
  let lastPoint = points[points.length - 1];
25
20
  this.vertices = [];
26
- this.inertia = 0;
27
21
  for(let i = 0; i < points.length; i++){
28
- let triArea = vec2.cross(lastPoint, points[i]) / 2;
29
- let triCom = vec2.times(vec2.plus(lastPoint, points[i]), triArea);
30
- this.inertia += (lastPoint.magSqr() + vec2.dot(lastPoint, points[i]) + points[i].magSqr()) * triArea;
31
-
32
- totalArea += triArea;
33
- this.COM.add(triCom);
34
-
35
22
  this.vertices.push(
36
23
  {position: points[i],
37
24
  normal: vec2.minus(points[i], lastPoint).normalize().rotateBy(Rotation.cw90deg()),
38
25
  isInternal: internalEdges? internalEdges[i]: false});
39
26
  lastPoint = points[i];
40
27
  }
41
- this.COM.divideBy(totalArea * 3);
42
- this.inertia /= 6;
43
- this.area = totalArea;
44
28
  }
45
29
  translate(t: vec2){
46
30
  for(let i = 0; i < this.vertices.length; i++){
47
31
  this.vertices[i].position.add(t);
48
32
  }
49
- this.inertia -= this.COM.magSqr() * this.area;
50
- this.COM.add(t);
51
- this.inertia += this.COM.magSqr() * this.area;
33
+ }
34
+ computeWeightedCOM(): {COM: vec2, area: number}{
35
+ let COM : vec2 = vec2.zero();
36
+ let totalArea = 0;
37
+ let lastPoint : vec2 = this.vertices[this.vertices.length - 1].position;
38
+ for(let i = 0; i < this.vertices.length; i++){
39
+ let currentPoint = this.vertices[i].position;
40
+ let triArea = vec2.cross(lastPoint, currentPoint) / 2;
41
+ let triCom = vec2.times(vec2.plus(lastPoint, currentPoint), triArea);
42
+ totalArea += triArea;
43
+ COM.add(triCom);
44
+ lastPoint = currentPoint;
45
+ }
46
+ COM.divideBy(totalArea);
47
+ return {COM: COM, area: totalArea};
48
+ }
49
+ computeInertia() : number{
50
+ let inertia = 0;
51
+ let lastPoint : vec2 = this.vertices[this.vertices.length - 1].position;
52
+ for(let i = 0; i < this.vertices.length; i++){
53
+ let currentPoint = this.vertices[i].position;
54
+ let triArea = vec2.cross(lastPoint, currentPoint) / 2;
52
55
 
56
+ inertia += (lastPoint.magSqr() + vec2.dot(lastPoint, currentPoint) + currentPoint.magSqr()) * triArea;
57
+ lastPoint = currentPoint;
58
+ }
59
+ inertia /= 6;
60
+ return inertia;
53
61
  }
54
62
  static rectangle(position: vec2, width: number , height: number){
55
63
  let x = position.x;
@@ -78,21 +86,22 @@ export class Polygon{
78
86
  }
79
87
  export class Circle{
80
88
  type = ShapeType.CIRCLE;
81
- area: number;
82
- inertia: number;
83
- COM: vec2;
89
+ position: vec2;
84
90
  radius: number;
85
91
 
86
92
  constructor(position: vec2 , radius: number){
87
- this.area = Math.PI * radius * radius;
88
- this.inertia = this.area * radius * radius + 0.5 * position.magSqr() * this.area;
89
- this.COM = position;
93
+ this.position = position;
90
94
  this.radius = radius;
91
95
  }
92
96
  translate(t: vec2){
93
- this.inertia -= this.COM.magSqr() * this.area;
94
- this.COM.add(t);
95
- this.inertia += this.COM.magSqr() * this.area;
96
-
97
+ this.position.add(t);
98
+ }
99
+ computeWeightedCOM(): {COM: vec2, area: number}{
100
+ return {COM: this.position.copy(), area: Math.PI * this.radius * this.radius};
101
+ }
102
+ computeInertia(): number{
103
+ let rSquared = this.radius * this.radius;
104
+ let area = Math.PI * rSquared;
105
+ return area * rSquared + 0.5 * this.position.magSqr() * area;
97
106
  }
98
107
  }
package/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./world";
2
+ export * from "./body";
3
+ export * from "./geometry";
4
+ export * from "./constraints/constraint"
5
+ export * from "./constraints/springs"
6
+ export * from "./constraints/wheel"
7
+ export * from "./collision"
8
+ export * from "./solver"
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@dimension-mismatch/2dphysics",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A simple 2d rigid-body physics engine",
5
- "main": "world.js",
5
+ "main": "index.js",
6
6
  "author": "dimension-mismatch",
7
7
  "license": "ISC",
8
8
  "dependencies": {