Added a trail to the pointer
This commit is contained in:
parent
eed47e644c
commit
142b3f804f
@ -1,11 +1,15 @@
|
|||||||
import React, { useContext, useEffect } from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
import { Group, Circle } from "react-konva";
|
import { Group } from "react-konva";
|
||||||
|
|
||||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||||
import MapStageContext from "../../contexts/MapStageContext";
|
import MapStageContext from "../../contexts/MapStageContext";
|
||||||
|
|
||||||
import { getStrokeWidth } from "../../helpers/drawing";
|
import { getStrokeWidth } from "../../helpers/drawing";
|
||||||
import { getRelativePointerPositionNormalized } from "../../helpers/konva";
|
import {
|
||||||
|
getRelativePointerPositionNormalized,
|
||||||
|
Trail,
|
||||||
|
} from "../../helpers/konva";
|
||||||
|
import { multiply } from "../../helpers/vector2";
|
||||||
|
|
||||||
import colors from "../../helpers/colors";
|
import colors from "../../helpers/colors";
|
||||||
|
|
||||||
@ -63,12 +67,11 @@ function MapPointer({
|
|||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
{visible && (
|
{visible && (
|
||||||
<Circle
|
<Trail
|
||||||
x={position.x * mapWidth}
|
position={multiply(position, { x: mapWidth, y: mapHeight })}
|
||||||
y={position.y * mapHeight}
|
color={colors.red}
|
||||||
fill={colors.red}
|
size={size}
|
||||||
width={size}
|
duration={200}
|
||||||
height={size}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Line, Group, Path, Circle } from "react-konva";
|
import { Line, Group, Path, Circle } from "react-konva";
|
||||||
|
import { lerp } from "./shared";
|
||||||
|
import * as Vector2 from "./vector2";
|
||||||
|
|
||||||
// Holes should be wound in the opposite direction as the containing points array
|
// Holes should be wound in the opposite direction as the containing points array
|
||||||
export function HoleyLine({ holes, ...props }) {
|
export function HoleyLine({ holes, ...props }) {
|
||||||
@ -140,6 +142,96 @@ export function Tick({ x, y, scale, onClick, cross }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Trail({ position, size, duration, segments }) {
|
||||||
|
const trailRef = useRef();
|
||||||
|
const pointsRef = useRef([]);
|
||||||
|
const prevPositionRef = useRef(position);
|
||||||
|
// Add a new point every time position is changed
|
||||||
|
useEffect(() => {
|
||||||
|
if (Vector2.compare(position, prevPositionRef.current, 0.0001)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pointsRef.current.push({ ...position, lifetime: duration });
|
||||||
|
prevPositionRef.current = position;
|
||||||
|
}, [position, duration]);
|
||||||
|
|
||||||
|
// Advance lifetime of trail
|
||||||
|
useEffect(() => {
|
||||||
|
let prevTime = performance.now();
|
||||||
|
let request = requestAnimationFrame(animate);
|
||||||
|
function animate(time) {
|
||||||
|
request = requestAnimationFrame(animate);
|
||||||
|
const deltaTime = time - prevTime;
|
||||||
|
prevTime = time;
|
||||||
|
|
||||||
|
if (pointsRef.current.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expired = 0;
|
||||||
|
for (let point of pointsRef.current) {
|
||||||
|
point.lifetime -= deltaTime;
|
||||||
|
if (point.lifetime < 0) {
|
||||||
|
expired++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expired > 0) {
|
||||||
|
pointsRef.current = pointsRef.current.slice(
|
||||||
|
expired,
|
||||||
|
pointsRef.current.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (trailRef.current) {
|
||||||
|
trailRef.current.getLayer().draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(request);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Custom scene function for drawing a trail from a line
|
||||||
|
function sceneFunc(context) {
|
||||||
|
// Resample points to ensure a smooth trail
|
||||||
|
const resampledPoints = Vector2.resample(pointsRef.current, segments);
|
||||||
|
for (let i = 1; i < resampledPoints.length; i++) {
|
||||||
|
const from = resampledPoints[i - 1];
|
||||||
|
const to = resampledPoints[i];
|
||||||
|
const alpha = i / resampledPoints.length;
|
||||||
|
context.beginPath();
|
||||||
|
context.lineJoin = "round";
|
||||||
|
context.lineCap = "round";
|
||||||
|
context.lineWidth = alpha * size;
|
||||||
|
context.strokeStyle = `hsl(0, 63%, ${lerp(90, 50, alpha)}%)`;
|
||||||
|
context.moveTo(from.x, from.y);
|
||||||
|
context.lineTo(to.x, to.y);
|
||||||
|
context.stroke();
|
||||||
|
context.closePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
<Line sceneFunc={sceneFunc} ref={trailRef} />
|
||||||
|
<Circle
|
||||||
|
x={position.x}
|
||||||
|
y={position.y}
|
||||||
|
fill="hsl(0, 63%, 50%)"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trail.defaultProps = {
|
||||||
|
// Duration of each point in milliseconds
|
||||||
|
duration: 200,
|
||||||
|
// Number of segments in the trail, resampled from the points
|
||||||
|
segments: 50,
|
||||||
|
};
|
||||||
|
|
||||||
export function getRelativePointerPosition(node) {
|
export function getRelativePointerPosition(node) {
|
||||||
let transform = node.getAbsoluteTransform().copy();
|
let transform = node.getAbsoluteTransform().copy();
|
||||||
transform.invert();
|
transform.invert();
|
||||||
|
@ -246,3 +246,51 @@ export function distance(a, b, type) {
|
|||||||
export function lerp(a, b, alpha) {
|
export function lerp(a, b, alpha) {
|
||||||
return { x: lerpNumber(a.x, b.x, alpha), y: lerpNumber(a.y, b.y, alpha) };
|
return { x: lerpNumber(a.x, b.x, alpha), y: lerpNumber(a.y, b.y, alpha) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns total length of a an array of points treated as a path
|
||||||
|
* @param {Array} points the array of points in the path
|
||||||
|
*/
|
||||||
|
export function pathLength(points) {
|
||||||
|
let l = 0;
|
||||||
|
for (let i = 1; i < points.length; i++) {
|
||||||
|
l += distance(points[i - 1], points[i], "euclidean");
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resample a path to n number of evenly distributed points
|
||||||
|
* based off of http://depts.washington.edu/acelab/proj/dollar/index.html
|
||||||
|
* @param {Array} points the points to resample
|
||||||
|
* @param {number} n the number of new points
|
||||||
|
*/
|
||||||
|
export function resample(points, n) {
|
||||||
|
if (points.length === 0 || n <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let localPoints = [...points];
|
||||||
|
const intervalLength = pathLength(localPoints) / (n - 1);
|
||||||
|
let resampledPoints = [localPoints[0]];
|
||||||
|
let currentDistance = 0;
|
||||||
|
for (let i = 1; i < localPoints.length; i++) {
|
||||||
|
let d = distance(localPoints[i - 1], localPoints[i], "euclidean");
|
||||||
|
if (currentDistance + d >= intervalLength) {
|
||||||
|
let newPoint = lerp(
|
||||||
|
localPoints[i - 1],
|
||||||
|
localPoints[i],
|
||||||
|
(intervalLength - currentDistance) / d
|
||||||
|
);
|
||||||
|
resampledPoints.push(newPoint);
|
||||||
|
localPoints.splice(i, 0, newPoint);
|
||||||
|
currentDistance = 0;
|
||||||
|
} else {
|
||||||
|
currentDistance += d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resampledPoints.length === n - 1) {
|
||||||
|
resampledPoints.push(localPoints[localPoints.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resampledPoints;
|
||||||
|
}
|
||||||
|
@ -7,8 +7,8 @@ import MapPointer from "../components/map/MapPointer";
|
|||||||
import { isEmpty } from "../helpers/shared";
|
import { isEmpty } from "../helpers/shared";
|
||||||
import { lerp } from "../helpers/vector2";
|
import { lerp } from "../helpers/vector2";
|
||||||
|
|
||||||
// Send pointer updates every 100ms
|
// Send pointer updates every 50ms
|
||||||
const sendTickRate = 100;
|
const sendTickRate = 50;
|
||||||
|
|
||||||
function NetworkedMapPointer({ session, active, gridSize }) {
|
function NetworkedMapPointer({ session, active, gridSize }) {
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user