Add custom intersection for token vehicles using their outline

This commit is contained in:
Mitchell McCaffrey 2021-06-12 20:53:40 +10:00
parent e81430be80
commit d0fba917a2
2 changed files with 119 additions and 44 deletions

View File

@ -2,7 +2,6 @@ import React, { useState, useRef } from "react";
import { Image as KonvaImage, Group } from "react-konva";
import { useSpring, animated } from "react-spring/konva";
import useImage from "use-image";
import Konva from "konva";
import usePrevious from "../../hooks/usePrevious";
import useGridSnapping from "../../hooks/useGridSnapping";
@ -20,6 +19,8 @@ import TokenStatus from "../token/TokenStatus";
import TokenLabel from "../token/TokenLabel";
import TokenOutline from "../token/TokenOutline";
import { Intersection, getScaledOutline } from "../../helpers/token";
import { tokenSources } from "../../tokens";
function MapToken({
@ -49,11 +50,14 @@ function MapToken({
function handleDragStart(event) {
const tokenGroup = event.target;
const tokenImage = imageRef.current;
if (tokenState.category === "vehicle") {
// Enable hit detection for .intersects() function
Konva.hitOnDragEnabled = true;
const tokenIntersection = new Intersection(
getScaledOutline(tokenState, tokenWidth, tokenHeight),
{ x: tokenX - tokenWidth / 2, y: tokenY - tokenHeight / 2 },
{ x: tokenX, y: tokenY },
tokenState.rotation
);
// Find all other tokens on the map
const layer = tokenGroup.getLayer();
@ -62,12 +66,7 @@ function MapToken({
if (other === tokenGroup) {
continue;
}
const otherRect = other.getClientRect();
const otherCenter = {
x: otherRect.x + otherRect.width / 2,
y: otherRect.y + otherRect.height / 2,
};
if (tokenImage.intersects(otherCenter)) {
if (tokenIntersection.intersects(other.position())) {
// Save and restore token position after moving layer
const position = other.absolutePosition();
other.moveTo(tokenGroup);
@ -92,8 +91,6 @@ function MapToken({
const mountChanges = {};
if (tokenState.category === "vehicle") {
Konva.hitOnDragEnabled = false;
const parent = tokenGroup.getParent();
const mountedTokens = tokenGroup.find(".character");
for (let mountedToken of mountedTokens) {
@ -177,8 +174,6 @@ function MapToken({
const tokenWidth = minCellSize * tokenState.size;
const tokenHeight = (minCellSize / tokenAspectRatio) * tokenState.size;
const imageRef = useRef();
// Animate to new token positions if edited by others
const tokenX = tokenState.x * mapWidth;
const tokenY = tokenState.y * mapHeight;
@ -206,34 +201,6 @@ function MapToken({
tokenName = tokenName + "-locked";
}
function getScaledOutline() {
let outline = tokenState.outline;
if (outline.type === "rect") {
return {
...outline,
x: (outline.x / tokenState.width) * tokenWidth,
y: (outline.y / tokenState.height) * tokenHeight,
width: (outline.width / tokenState.width) * tokenWidth,
height: (outline.height / tokenState.height) * tokenHeight,
};
} else if (outline.type === "circle") {
return {
...outline,
x: (outline.x / tokenState.width) * tokenWidth,
y: (outline.y / tokenState.height) * tokenHeight,
radius: (outline.radius / tokenState.width) * tokenWidth,
};
} else {
let points = [...outline.points]; // Copy array so we can edit it imutably
for (let i = 0; i < points.length; i += 2) {
// Scale outline to the token
points[i] = (points[i] / tokenState.width) * tokenWidth;
points[i + 1] = (points[i + 1] / tokenState.height) * tokenHeight;
}
return { ...outline, points };
}
}
return (
<animated.Group
{...props}
@ -264,10 +231,12 @@ function MapToken({
offsetX={tokenWidth / 2}
offsetY={tokenHeight / 2}
>
<TokenOutline outline={getScaledOutline()} hidden={!!tokenImage} />
<TokenOutline
outline={getScaledOutline(tokenState, tokenWidth, tokenHeight)}
hidden={!!tokenImage}
/>
</Group>
<KonvaImage
ref={imageRef}
width={tokenWidth}
height={tokenHeight}
x={0}

View File

@ -3,6 +3,7 @@ import Case from "case";
import blobToBuffer from "./blobToBuffer";
import { createThumbnail, getImageOutline } from "./image";
import Vector2 from "./Vector2";
export function createTokenState(token, position, userId) {
let tokenState = {
@ -139,3 +140,108 @@ export function clientPositionToMapPosition(
return normalizedPosition;
}
export function getScaledOutline(tokenState, tokenWidth, tokenHeight) {
let outline = tokenState.outline;
if (outline.type === "rect") {
return {
...outline,
x: (outline.x / tokenState.width) * tokenWidth,
y: (outline.y / tokenState.height) * tokenHeight,
width: (outline.width / tokenState.width) * tokenWidth,
height: (outline.height / tokenState.height) * tokenHeight,
};
} else if (outline.type === "circle") {
return {
...outline,
x: (outline.x / tokenState.width) * tokenWidth,
y: (outline.y / tokenState.height) * tokenHeight,
radius: (outline.radius / tokenState.width) * tokenWidth,
};
} else {
let points = [...outline.points]; // Copy array so we can edit it imutably
for (let i = 0; i < points.length; i += 2) {
// Scale outline to the token
points[i] = (points[i] / tokenState.width) * tokenWidth;
points[i + 1] = (points[i + 1] / tokenState.height) * tokenHeight;
}
return { ...outline, points };
}
}
export class Intersection {
/**
*
* @param {Outline} outline
* @param {Vector2} position - Top left position of the token
* @param {Vector2} center - Center position of the token
* @param {number} rotation - Rotation of the token in degrees
*/
constructor(outline, position, center, rotation) {
this.outline = outline;
this.position = position;
this.center = center;
this.rotation = rotation;
// Save points for intersection
if (outline.type === "rect") {
this.points = [
Vector2.rotate(
Vector2.add(new Vector2(outline.x, outline.y), position),
center,
rotation
),
Vector2.rotate(
Vector2.add(
new Vector2(outline.x + outline.width, outline.y),
position
),
center,
rotation
),
Vector2.rotate(
Vector2.add(
new Vector2(outline.x + outline.width, outline.y + outline.height),
position
),
center,
rotation
),
Vector2.rotate(
Vector2.add(
new Vector2(outline.x, outline.y + outline.height),
position
),
center,
rotation
),
];
} else if (outline.type === "path") {
this.points = [];
for (let i = 0; i < outline.points.length; i += 2) {
this.points.push(
Vector2.rotate(
Vector2.add(
new Vector2(outline.points[i], outline.points[i + 1]),
position
),
center,
rotation
)
);
}
}
}
/**
* @param {Vector2} point
* @returns {boolean}
*/
intersects(point) {
if (this.outline.type === "rect" || this.outline.type === "path") {
return Vector2.pointInPolygon(point, this.points);
} else if (this.outline.type === "circle") {
return Vector2.distance(this.center, point) < this.outline.radius;
}
return false;
}
}