Skip to content

Instantly share code, notes, and snippets.

@skyzh
Created July 25, 2019 01:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skyzh/cf663f4adc0e2bffb6b15415e7d554e2 to your computer and use it in GitHub Desktop.
Save skyzh/cf663f4adc0e2bffb6b15415e7d554e2 to your computer and use it in GitHub Desktop.
Use d3.js to visualize collision between point and segment
<!DOCTYPE html>
<html>
<head>
<title>Physics Test</title>
</head>
<body>
<svg id="main" width="800" height="800"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="main.js"></script>
</body>
</html>
const svg = d3.select('#main')
const lines = [
[10, 10, 10, 790],
[10, 10, 790, 10],
[790, 790, 10, 790],
[790, 790, 790, 10]]
const balls = []
const BALL_RADIUS = 5
const WIDTH = 800
const HEIGHT = 800
function generate_balls() {
for (let i = 0; i < 30; i++) {
balls.push([
Math.random() * WIDTH,
Math.random() * HEIGHT,
Math.random() * BALL_RADIUS + 2,
Math.random() * BALL_RADIUS + 2])
}
for (let i = 0; i < 10; i++) {
const x1 = Math.random() * WIDTH
const y1 = Math.random() * HEIGHT
lines.push([
x1,
y1,
x1 + Math.random() * 100,
y1 + Math.random() * 100
])
}
}
generate_balls()
function update() {
svg.selectAll('.block')
.data(lines)
.attr('x1', d => d[0])
.attr('y1', d => d[1])
.attr('x2', d => d[2])
.attr('y2', d => d[3])
.enter()
.append('line')
.attr('class', 'block')
.attr('stroke', '#000')
.attr('stroke-width', '1')
.attr('stroke-linecap', 'round')
.exit()
.remove()
svg.selectAll('.ball')
.data(balls)
.attr('cx', d => d[0])
.attr('cy', d => d[1])
.attr('r', BALL_RADIUS)
.enter()
.append('circle')
.attr('class', 'ball')
.attr('stroke', '#000')
.exit()
.remove()
svg.selectAll('.velocity')
.data(balls)
.attr('x1', d => d[0])
.attr('y1', d => d[1])
.attr('x2', d => d[0] + d[2] * BALL_RADIUS)
.attr('y2', d => d[1] + d[3] * BALL_RADIUS)
.enter()
.append('line')
.attr('class', 'velocity')
.attr('stroke', '#f00')
.exit()
.remove()
}
function vec_d(line) {
return [line[2] - line[0], line[3] - line[1]]
}
function vec_len(vec) {
return Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
}
function vec_unit(vec) {
const length = vec_len(vec)
if (length == 0) return [0, 0]
return [vec[0] / length, vec[1] / length]
}
function vec_n(unit_d) {
return [-unit_d[1], unit_d[0]]
}
function vec_dot(vec1, vec2) {
return vec1[0] * vec2[0] + vec1[1] * vec2[1]
}
function distance_to(point, line) {
const vec_p = [line[0] - point[0], line[1] - point[1]]
return Math.abs(vec_dot(vec_p, vec_n(vec_unit(vec_d(line)))))
}
function in_range(point, line) {
const x1 = Math.min(line[0], line[2])
const x2 = Math.max(line[0], line[2])
const y1 = Math.min(line[1], line[3])
const y2 = Math.max(line[1], line[3])
return x1 <= point[0] && point[0] <= x2 || y1 <= point[1] && point[1] <= y2
}
function delta(point, line) {
const [x, y] = point
const [x1, y1, x2, y2] = line
return (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1)
}
function run() {
update()
balls.forEach(ball => {
const [px, py] = [ball[0], ball[1]]
let [vx, vy] = [ball[2], ball[3]]
vy += 0.1
lines.forEach(line => {
const ball_pos = [px, py]
const [nx, ny] = [px + vx, py + vy]
if (in_range(ball_pos, line)) {
if (delta([px, py], line) * delta([nx, ny], line) < 0) {
const u_n = vec_unit(vec_n(vec_d(line)))
const v_proj = vec_dot(u_n, [vx, vy])
vx -= 2 * u_n[0] * v_proj
vy -= 2 * u_n[1] * v_proj
}
}
})
ball[0] = px + vx
ball[1] = py + vy
ball[2] = vx
ball[3] = vy
})
window.requestAnimationFrame(run)
}
run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment