Add custom intersection for token vehicles using their outline
This commit is contained in:
parent
e81430be80
commit
d0fba917a2
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user