forked from ehmorris/lunar-lander
-
Notifications
You must be signed in to change notification settings - Fork 0
/
particle.js
150 lines (130 loc) · 3.95 KB
/
particle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { GRAVITY } from "./helpers/constants.js";
import { randomBetween, randomBool } from "./helpers/helpers.js";
export const makeParticle = (
state,
startPosition,
startVelocity,
width,
height,
fill,
customDraw = false,
onCollide = () => {}
) => {
const CTX = state.get("CTX");
const scaleFactor = state.get("scaleFactor");
const terrain = state.get("terrain");
const landingData = state.get("terrain").getLandingData();
const friction = 0.3;
const velocityThreshold = 2;
const rotationDirection = randomBool();
let position = { ...startPosition };
let positionLog = [];
let velocity = { ...startVelocity };
let rotationAngle = Math.PI * 2;
let rotationVelocity = 0;
let headingDeg = Math.atan2(velocity.y, velocity.x) * (180 / Math.PI);
let stopped = false;
const update = () => {
velocity.x = startVelocity.x + Math.cos((headingDeg * Math.PI) / 180);
velocity.y += GRAVITY;
rotationVelocity += rotationDirection ? 0.1 : -0.1;
rotationAngle = (rotationAngle + rotationVelocity) * friction;
let prospectiveNextPosition = {
x: position.x + velocity.x,
y: position.y + velocity.y,
};
if (prospectiveNextPosition.y >= landingData.terrainHeight) {
const collisionPoint = isShapeInPath(
CTX,
scaleFactor,
landingData.terrainPath2D,
prospectiveNextPosition.x,
prospectiveNextPosition.y,
width,
height
);
if (collisionPoint) {
const collisionAngle = terrain.getSegmentAngleAtX(collisionPoint.x);
headingDeg = angleReflect(headingDeg, collisionAngle);
velocity.x =
velocity.x < velocityThreshold ? 0 : velocity.x * -friction;
velocity.y =
velocity.y < velocityThreshold ? 0 : velocity.y * -friction;
rotationAngle = collisionAngle;
if (countSimilarCoordinates(positionLog) > 3) stopped = true;
prospectiveNextPosition = {
x: position.x + velocity.x,
y: position.y + velocity.y,
};
// Provide the point just prior to collision so particles reflect off
// terrain rather than getting stuck in it
onCollide(position, velocity);
}
// Track the last 20 positions to check for duplicates
positionLog.push({ ...position });
if (positionLog.length > 20) positionLog.shift();
} else {
positionLog = [];
}
position.x =
position.x > state.get("canvasWidth")
? 0
: position.x < 0
? state.get("canvasWidth")
: prospectiveNextPosition.x;
position.y = prospectiveNextPosition.y;
};
const draw = () => {
if (!stopped) update();
CTX.save();
if (customDraw) {
customDraw(
CTX,
position,
velocity,
rotationAngle,
fill,
rotationVelocity
);
} else {
CTX.fillStyle = fill;
CTX.translate(position.x, position.y);
CTX.rotate(rotationAngle);
CTX.fillRect(-width / 2, -height / 2, width, height);
}
CTX.restore();
};
return { draw, getPosition: () => position, getVelocity: () => velocity };
};
function angleReflect(incidenceAngle, surfaceAngle) {
const a = surfaceAngle * 2 - incidenceAngle;
return a >= 360 ? a - 360 : a < 0 ? a + 360 : a;
}
function countSimilarCoordinates(arr) {
return (
arr.length -
new Set(arr.map(({ x, y }) => `${Math.round(x)}|${Math.round(y)}`)).size
);
}
function isShapeInPath(
CTX,
scaleFactor,
path,
topLeftX,
topLeftY,
width,
height
) {
const radius = Math.sqrt(width ** 2 + height ** 2) / 3;
const numCollisionPoints = 5;
const dots = new Array(numCollisionPoints).fill().map((_, i) => {
const angle = (360 / numCollisionPoints) * i;
return {
x: topLeftX + radius * Math.cos((angle * Math.PI) / 180),
y: topLeftY + radius * Math.sin((angle * Math.PI) / 180),
};
});
return dots.find(({ x, y }) =>
CTX.isPointInPath(path, x * scaleFactor, y * scaleFactor)
);
}