more fun collisions
This commit is contained in:
parent
46a6af6a11
commit
4af3cf270b
|
|
@ -62,7 +62,7 @@
|
|||
<script type="module">
|
||||
import '../src/standalone/folk-shape.ts';
|
||||
import './src/record-player.ts';
|
||||
import { collisionDetection } from '../src/common/collision.ts';
|
||||
import { aabbIntersection } from '../src/common/collision.ts';
|
||||
|
||||
let proximityDistance = 150;
|
||||
const proximitySet = new Set();
|
||||
|
|
@ -82,9 +82,9 @@
|
|||
function updateFlowerProximity(flower) {
|
||||
const alreadyIntersection = proximitySet.has(flower);
|
||||
// TODO: refactor this hack once resizing and the vertices API are figured out
|
||||
const isNowIntersecting = collisionDetection(
|
||||
recordPlayerGeometry.getClientRect(),
|
||||
flower.getClientRect(),
|
||||
const isNowIntersecting = aabbIntersection(
|
||||
recordPlayerGeometry.getTransformDOMRect(),
|
||||
flower.getTransformDOMRect(),
|
||||
proximityDistance
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,21 +25,29 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<folk-shape x="100" y="100" width="50" height="50"> Hello World </folk-shape>
|
||||
<folk-shape x="100" y="100" width="50" height="50"></folk-shape>
|
||||
<folk-shape x="200" y="200" width="50" height="50"></folk-shape>
|
||||
<folk-shape x="200" y="275" width="50" height="50"></folk-shape>
|
||||
<folk-shape x="200" y="150" width="50" height="50"></folk-shape>
|
||||
<folk-shape x="200" y="100" width="50" height="50"></folk-shape>
|
||||
|
||||
<script type="module">
|
||||
import '../src/standalone/folk-shape.ts';
|
||||
import { collisionDetection } from '../src/common/collision.ts';
|
||||
import { aabbHitDetection } from '../src/common/collision.ts';
|
||||
|
||||
const geometryElements = document.querySelectorAll('folk-shape');
|
||||
const shapes = Array.from(document.querySelectorAll('folk-shape'));
|
||||
|
||||
function handleCollision(e) {
|
||||
geometryElements.forEach((el) => {
|
||||
if (el !== e.target && collisionDetection(el.getClientRect(), e.target.getClientRect())) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
for (const shape of shapes) {
|
||||
if (shape === e.target) continue;
|
||||
|
||||
const hit = aabbHitDetection(shape.getTransformDOMRect(), e.target.getTransformDOMRect());
|
||||
|
||||
if (hit === null) continue;
|
||||
|
||||
shape.x -= hit.delta.x;
|
||||
shape.y -= hit.delta.y;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('transform', handleCollision);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,51 @@
|
|||
export function collisionDetection(rect1: DOMRect, rect2: DOMRect, proximity = 0) {
|
||||
import { TransformDOMRect } from './TransformDOMRect';
|
||||
import { Point } from './types';
|
||||
|
||||
const sign = (value: number): -1 | 1 => (value < 0 ? -1 : 1);
|
||||
|
||||
export class Hit {
|
||||
/** The point of contact between the two objects. */
|
||||
pos: Point = { x: 0, y: 0 };
|
||||
|
||||
/** The a vector representing the overlap between the two objects. */
|
||||
delta: Point = { x: 0, y: 0 };
|
||||
|
||||
/** The surface normal at the point of contact. */
|
||||
normal: Point = { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
/** Test collisions of axis-aligned bounding boxes. */
|
||||
export function aabbHitDetection(rect1: DOMRect, rect2: DOMRect, proximity = 0): Hit | null {
|
||||
const dx = rect2.x - rect1.x;
|
||||
const px = rect2.width / 2 + rect1.width / 2 - Math.abs(dx);
|
||||
|
||||
if (px <= 0) return null;
|
||||
|
||||
const dy = rect2.y - rect1.y;
|
||||
const py = rect2.height / 2 + rect1.height / 2 - Math.abs(dy);
|
||||
|
||||
if (py <= 0) return null;
|
||||
|
||||
const hit = new Hit();
|
||||
|
||||
if (px < py) {
|
||||
const sx = sign(dx);
|
||||
hit.delta.x = px * sx;
|
||||
hit.normal.x = sx;
|
||||
hit.pos.x = rect1.x + (rect1.width / 2) * sx;
|
||||
hit.pos.y = rect2.y;
|
||||
} else {
|
||||
const sy = sign(dy);
|
||||
hit.delta.y = py * sy;
|
||||
hit.normal.y = sy;
|
||||
hit.pos.x = rect2.x;
|
||||
hit.pos.y = rect1.y + (rect1.height / 2) * sy;
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
export function aabbIntersection(rect1: DOMRect, rect2: DOMRect, proximity = 0) {
|
||||
return (
|
||||
rect1.left - rect2.right < proximity &&
|
||||
rect2.left - rect1.right < proximity &&
|
||||
|
|
@ -6,3 +53,8 @@ export function collisionDetection(rect1: DOMRect, rect2: DOMRect, proximity = 0
|
|||
rect2.top - rect1.bottom < proximity
|
||||
);
|
||||
}
|
||||
|
||||
export function collisionDetection(rect1: TransformDOMRect, rect2: TransformDOMRect) {
|
||||
// Performance optimization to test if
|
||||
if (!aabbIntersection(rect1, rect2)) return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { collisionDetection } from './common/collision.ts';
|
||||
import { aabbIntersection } from './common/collision.ts';
|
||||
import { FolkHull } from './folk-hull';
|
||||
import { FolkShape } from './folk-shape.ts';
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ export class FolkCluster extends FolkHull {
|
|||
|
||||
isElementInProximity(element: FolkShape) {
|
||||
for (const el of this.sourceElements as FolkShape[]) {
|
||||
if (collisionDetection(el.getClientRect(), element.getClientRect(), PROXIMITY)) return true;
|
||||
if (aabbIntersection(el.getTransformDOMRect(), element.getTransformDOMRect(), PROXIMITY)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ export class FolkProximity extends HTMLElement {
|
|||
for (const geometry of this.#geometries) {
|
||||
if (geometry === el) break;
|
||||
|
||||
if (collisionDetection(geometry.getClientRect(), el.getClientRect(), PROXIMITY)) {
|
||||
if (aabbIntersection(geometry.getTransformDOMRect(), el.getTransformDOMRect(), PROXIMITY)) {
|
||||
const cluster = document.createElement('folk-cluster');
|
||||
cluster.addElements(geometry, el);
|
||||
this.#clusters.add(cluster);
|
||||
|
|
@ -175,7 +175,7 @@ export class FolkProximity extends HTMLElement {
|
|||
} else {
|
||||
const isInCluster = (cluster.sourceElements as FolkShape[])
|
||||
.filter((element) => el !== element)
|
||||
.some((element) => collisionDetection(el.getClientRect(), element.getClientRect(), PROXIMITY));
|
||||
.some((element) => aabbIntersection(el.getTransformDOMRect(), element.getTransformDOMRect(), PROXIMITY));
|
||||
|
||||
if (!isInCluster) {
|
||||
cluster.removeElement(el);
|
||||
|
|
|
|||
|
|
@ -182,8 +182,10 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
|
||||
set x(x) {
|
||||
if (this.#rect.x === x) return;
|
||||
this.#previousRect.x = this.#rect.x;
|
||||
this.#rect.x = x;
|
||||
this.#requestUpdate('x');
|
||||
this.#requestUpdate();
|
||||
}
|
||||
|
||||
get y() {
|
||||
|
|
@ -191,8 +193,10 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
|
||||
set y(y) {
|
||||
if (this.#rect.y === y) return;
|
||||
this.#previousRect.y = this.#rect.y;
|
||||
this.#rect.y = y;
|
||||
this.#requestUpdate('y');
|
||||
this.#requestUpdate();
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
|
|
@ -203,13 +207,14 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
|
||||
set width(width: Dimension) {
|
||||
if (this.#attrWidth === width) return;
|
||||
if (width === 'auto') {
|
||||
resizeObserver.observe(this, this.#onAutoResize);
|
||||
} else if (this.#attrWidth === 'auto' && this.#attrHeight !== 'auto') {
|
||||
resizeObserver.unobserve(this, this.#onAutoResize);
|
||||
}
|
||||
this.#attrWidth = width;
|
||||
this.#requestUpdate('width');
|
||||
this.#requestUpdate();
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
|
|
@ -220,6 +225,7 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
|
||||
set height(height: Dimension) {
|
||||
if (this.#attrHeight === height) return;
|
||||
if (height === 'auto') {
|
||||
resizeObserver.observe(this, this.#onAutoResize);
|
||||
} else if (this.#attrHeight === 'auto' && this.#attrWidth !== 'auto') {
|
||||
|
|
@ -227,7 +233,7 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
|
||||
this.#attrHeight = height;
|
||||
this.#requestUpdate('height');
|
||||
this.#requestUpdate();
|
||||
}
|
||||
|
||||
get rotation(): number {
|
||||
|
|
@ -235,8 +241,10 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
|
||||
set rotation(rotation: number) {
|
||||
if (this.#rect.rotation === rotation) return;
|
||||
this.#previousRect.rotation = this.#rect.rotation;
|
||||
this.#rect.rotation = rotation;
|
||||
this.#requestUpdate('rotation');
|
||||
this.#requestUpdate();
|
||||
}
|
||||
|
||||
#rect: TransformDOMRect;
|
||||
|
|
@ -364,13 +372,11 @@ export class FolkShape extends HTMLElement {
|
|||
if (event.altKey) {
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
this.#rect.rotation -= ROTATION_DELTA;
|
||||
this.#requestUpdate('rotation');
|
||||
this.rotation -= ROTATION_DELTA;
|
||||
event.preventDefault();
|
||||
return;
|
||||
case 'ArrowRight':
|
||||
this.#rect.rotation += ROTATION_DELTA;
|
||||
this.#requestUpdate('rotation');
|
||||
this.rotation += ROTATION_DELTA;
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
|
@ -378,23 +384,19 @@ export class FolkShape extends HTMLElement {
|
|||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
this.#rect.x -= MOVEMENT_DELTA;
|
||||
this.#requestUpdate('x');
|
||||
this.x -= MOVEMENT_DELTA;
|
||||
event.preventDefault();
|
||||
return;
|
||||
case 'ArrowRight':
|
||||
this.#rect.x += MOVEMENT_DELTA;
|
||||
this.#requestUpdate('x');
|
||||
this.x += MOVEMENT_DELTA;
|
||||
event.preventDefault();
|
||||
return;
|
||||
case 'ArrowUp':
|
||||
this.#rect.y -= MOVEMENT_DELTA;
|
||||
this.#requestUpdate('y');
|
||||
this.y -= MOVEMENT_DELTA;
|
||||
event.preventDefault();
|
||||
return;
|
||||
case 'ArrowDown':
|
||||
this.#rect.y += MOVEMENT_DELTA;
|
||||
this.#requestUpdate('y');
|
||||
this.y += MOVEMENT_DELTA;
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
|
@ -433,10 +435,8 @@ export class FolkShape extends HTMLElement {
|
|||
if (target === null) return;
|
||||
|
||||
if (target === this) {
|
||||
this.#rect.x += event.movementX;
|
||||
this.#rect.y += event.movementY;
|
||||
this.#requestUpdate('x');
|
||||
this.#requestUpdate('y');
|
||||
this.x += event.movementX;
|
||||
this.y += event.movementY;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -452,9 +452,9 @@ export class FolkShape extends HTMLElement {
|
|||
if (handle.startsWith('rotation')) {
|
||||
const center = this.#rect.center;
|
||||
const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, center);
|
||||
this.#rect.rotation = this.#initialRotation + (currentAngle - this.#startAngle);
|
||||
const rotation = this.#initialRotation + (currentAngle - this.#startAngle);
|
||||
|
||||
let degrees = (this.#rect.rotation * 180) / Math.PI;
|
||||
let degrees = (rotation * 180) / Math.PI;
|
||||
switch (handle) {
|
||||
case 'rotation-ne':
|
||||
degrees = (degrees + 90) % 360;
|
||||
|
|
@ -470,8 +470,7 @@ export class FolkShape extends HTMLElement {
|
|||
const target = event.composedPath()[0] as HTMLElement;
|
||||
const rotateCursor = getRotateCursorUrl(degrees);
|
||||
target.style.setProperty('cursor', rotateCursor);
|
||||
this.#requestUpdate('rotation');
|
||||
|
||||
this.rotation = rotation;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -495,21 +494,17 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
#updatedProperties = new Set<string>();
|
||||
#isUpdating = false;
|
||||
|
||||
async #requestUpdate(property: string) {
|
||||
async #requestUpdate() {
|
||||
if (!this.#isConnected) return;
|
||||
|
||||
this.#updatedProperties.add(property);
|
||||
|
||||
if (this.#isUpdating) return;
|
||||
|
||||
this.#isUpdating = true;
|
||||
await true;
|
||||
this.#isUpdating = false;
|
||||
this.#update();
|
||||
this.#updatedProperties.clear();
|
||||
}
|
||||
|
||||
// Any updates that should be batched should happen here like updating the DOM or emitting events should be executed here.
|
||||
|
|
@ -666,9 +661,6 @@ export class FolkShape extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
this.#requestUpdate('x');
|
||||
this.#requestUpdate('y');
|
||||
this.#requestUpdate('width');
|
||||
this.#requestUpdate('height');
|
||||
this.#requestUpdate();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue