Added map context to scale simplification by map scale
Added distance to quadratic functions to vector
This commit is contained in:
parent
3e5a80e7d1
commit
9975f564fa
@ -1,8 +1,7 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import React, { useRef, useEffect, useState, useContext } from "react";
|
||||
import shortid from "shortid";
|
||||
|
||||
import { compare as comparePoints } from "../../helpers/vector2";
|
||||
|
||||
import {
|
||||
getBrushPositionForTool,
|
||||
getDefaultShapeData,
|
||||
@ -13,6 +12,8 @@ import {
|
||||
getRelativePointerPosition,
|
||||
} from "../../helpers/drawing";
|
||||
|
||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||
|
||||
function MapDrawing({
|
||||
width,
|
||||
height,
|
||||
@ -36,6 +37,8 @@ function MapDrawing({
|
||||
selectedTool === "shape" ||
|
||||
selectedTool === "erase";
|
||||
|
||||
const { scaleRef } = useContext(MapInteractionContext);
|
||||
|
||||
// Reset pointer position when tool changes
|
||||
useEffect(() => {
|
||||
setPointerPosition({ x: -1, y: -1 });
|
||||
@ -128,7 +131,8 @@ function MapDrawing({
|
||||
}
|
||||
const simplified = simplifyPoints(
|
||||
[...prevPoints, brushPosition],
|
||||
gridSize
|
||||
gridSize,
|
||||
scaleRef.current
|
||||
);
|
||||
return {
|
||||
...prevShape,
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import React, { useRef, useEffect, useState, useContext } from "react";
|
||||
import shortid from "shortid";
|
||||
|
||||
import { compare as comparePoints } from "../../helpers/vector2";
|
||||
|
||||
import {
|
||||
getBrushPositionForTool,
|
||||
isShapeHovered,
|
||||
@ -11,6 +10,8 @@ import {
|
||||
getRelativePointerPosition,
|
||||
} from "../../helpers/drawing";
|
||||
|
||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||
|
||||
function MapFog({
|
||||
width,
|
||||
height,
|
||||
@ -32,6 +33,8 @@ function MapFog({
|
||||
isEditing &&
|
||||
(toolSettings.type === "toggle" || toolSettings.type === "remove");
|
||||
|
||||
const { scaleRef } = useContext(MapInteractionContext);
|
||||
|
||||
// Reset pointer position when tool changes
|
||||
useEffect(() => {
|
||||
setPointerPosition({ x: -1, y: -1 });
|
||||
@ -65,7 +68,6 @@ function MapFog({
|
||||
color: "black",
|
||||
blend: true, // Blend while drawing
|
||||
id: shortid.generate(),
|
||||
fogType: toolSettings.useGridSnapping ? "sharp" : "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -124,7 +126,14 @@ function MapFog({
|
||||
if (drawingShape.data.points.length > 1) {
|
||||
const shape = {
|
||||
...drawingShape,
|
||||
data: { points: simplifyPoints(drawingShape.data.points, gridSize) },
|
||||
data: {
|
||||
points: simplifyPoints(
|
||||
drawingShape.data.points,
|
||||
gridSize,
|
||||
// Downscale fog as smoothing doesn't currently work with edge snapping
|
||||
scaleRef.current / 2
|
||||
),
|
||||
},
|
||||
blend: false,
|
||||
};
|
||||
onShapeAdd(shape);
|
||||
|
@ -2,6 +2,8 @@ import React, { useRef, useEffect } from "react";
|
||||
import { Box } from "theme-ui";
|
||||
import interact from "interactjs";
|
||||
|
||||
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
||||
|
||||
const zoomSpeed = -0.005;
|
||||
const minZoom = 0.1;
|
||||
const maxZoom = 5;
|
||||
@ -134,7 +136,14 @@ function MapInteraction({ map, aspectRatio, isEnabled, children, controls }) {
|
||||
paddingBottom: `${(1 / aspectRatio) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<MapInteractionProvider
|
||||
value={{
|
||||
translateRef: mapTranslateRef,
|
||||
scaleRef: mapScaleRef,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MapInteractionProvider>
|
||||
</Box>
|
||||
</Box>
|
||||
{controls}
|
||||
|
9
src/contexts/MapInteractionContext.js
Normal file
9
src/contexts/MapInteractionContext.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
const MapInteractionContext = React.createContext({
|
||||
translateRef: null,
|
||||
scaleRef: null,
|
||||
});
|
||||
export const MapInteractionProvider = MapInteractionContext.Provider;
|
||||
|
||||
export default MapInteractionContext;
|
@ -34,19 +34,22 @@ export function getBrushPositionForTool(
|
||||
if (shape.type === "fog") {
|
||||
const points = shape.data.points;
|
||||
const isInShape = Vector2.pointInPolygon(position, points);
|
||||
|
||||
// Find the closest point to each line of the shape
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const a = points[i];
|
||||
// Wrap around points to the start to account for closed shape
|
||||
const b = points[(i + 1) % points.length];
|
||||
const distanceToLine = Vector2.distanceToLine(position, a, b);
|
||||
|
||||
const {
|
||||
distance: distanceToLine,
|
||||
point: pointOnLine,
|
||||
} = Vector2.distanceToLine(position, a, b);
|
||||
const isCloseToShape = distanceToLine < minGrid * snappingThreshold;
|
||||
if (
|
||||
(isInShape || isCloseToShape) &&
|
||||
distanceToLine < closestDistance
|
||||
) {
|
||||
closestPosition = Vector2.closestPointOnLine(position, a, b);
|
||||
closestPosition = pointOnLine;
|
||||
closestDistance = distanceToLine;
|
||||
}
|
||||
}
|
||||
@ -154,6 +157,30 @@ export function shapeHasFill(shape) {
|
||||
);
|
||||
}
|
||||
|
||||
export function pointsToQuadraticBezier(points) {
|
||||
const quadraticPoints = [];
|
||||
|
||||
// Draw a smooth curve between the points where each control point
|
||||
// is the current point in the array and the next point is the center of
|
||||
// the current point and the next point
|
||||
for (let i = 1; i < points.length - 2; i++) {
|
||||
const start = points[i - 1];
|
||||
const controlPoint = points[i];
|
||||
const next = points[i + 1];
|
||||
const end = Vector2.divide(Vector2.add(controlPoint, next), 2);
|
||||
|
||||
quadraticPoints.push({ start, controlPoint, end });
|
||||
}
|
||||
// Curve through the last two points
|
||||
quadraticPoints.push({
|
||||
start: points[points.length - 2],
|
||||
controlPoint: points[points.length - 1],
|
||||
end: points[points.length - 1],
|
||||
});
|
||||
|
||||
return quadraticPoints;
|
||||
}
|
||||
|
||||
export function pointsToPathSmooth(points, close, canvasWidth, canvasHeight) {
|
||||
const path = new Path2D();
|
||||
if (points.length < 2) {
|
||||
@ -161,27 +188,23 @@ export function pointsToPathSmooth(points, close, canvasWidth, canvasHeight) {
|
||||
}
|
||||
path.moveTo(points[0].x * canvasWidth, points[0].y * canvasHeight);
|
||||
|
||||
// Draw a smooth curve between the points
|
||||
for (let i = 1; i < points.length - 2; i++) {
|
||||
const pointScaled = Vector2.multiply(points[i], {
|
||||
const quadraticPoints = pointsToQuadraticBezier(points);
|
||||
for (let quadPoint of quadraticPoints) {
|
||||
const pointScaled = Vector2.multiply(quadPoint.end, {
|
||||
x: canvasWidth,
|
||||
y: canvasHeight,
|
||||
});
|
||||
const nextPointScaled = Vector2.multiply(points[i + 1], {
|
||||
const controlScaled = Vector2.multiply(quadPoint.controlPoint, {
|
||||
x: canvasWidth,
|
||||
y: canvasHeight,
|
||||
});
|
||||
var xc = (pointScaled.x + nextPointScaled.x) / 2;
|
||||
var yc = (pointScaled.y + nextPointScaled.y) / 2;
|
||||
path.quadraticCurveTo(pointScaled.x, pointScaled.y, xc, yc);
|
||||
}
|
||||
// Curve through the last two points
|
||||
path.quadraticCurveTo(
|
||||
points[points.length - 2].x * canvasWidth,
|
||||
points[points.length - 2].y * canvasHeight,
|
||||
points[points.length - 1].x * canvasWidth,
|
||||
points[points.length - 1].y * canvasHeight
|
||||
controlScaled.x,
|
||||
controlScaled.y,
|
||||
pointScaled.x,
|
||||
pointScaled.y
|
||||
);
|
||||
}
|
||||
|
||||
if (close) {
|
||||
path.closePath();
|
||||
@ -265,14 +288,6 @@ export function shapeToPath(shape, canvasWidth, canvasHeight) {
|
||||
return pointsToPathSharp(data.points, true, canvasWidth, canvasHeight);
|
||||
}
|
||||
} else if (shape.type === "fog") {
|
||||
if (shape.fogType === "smooth") {
|
||||
return pointsToPathSmooth(
|
||||
shape.data.points,
|
||||
true,
|
||||
canvasWidth,
|
||||
canvasHeight
|
||||
);
|
||||
} else if (shape.fogType === "sharp") {
|
||||
return pointsToPathSharp(
|
||||
shape.data.points,
|
||||
true,
|
||||
@ -281,7 +296,6 @@ export function shapeToPath(shape, canvasWidth, canvasHeight) {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isShapeHovered(
|
||||
shape,
|
||||
@ -330,8 +344,11 @@ export function drawShape(shape, context, gridSize, canvasWidth, canvasHeight) {
|
||||
}
|
||||
|
||||
const defaultSimplifySize = 1 / 100;
|
||||
export function simplifyPoints(points, gridSize) {
|
||||
return simplify(points, Vector2.min(gridSize) * defaultSimplifySize);
|
||||
export function simplifyPoints(points, gridSize, scale) {
|
||||
return simplify(
|
||||
points,
|
||||
(Vector2.min(gridSize) * defaultSimplifySize) / scale
|
||||
);
|
||||
}
|
||||
|
||||
export function getRelativePointerPosition(event, container) {
|
||||
|
@ -78,19 +78,93 @@ export function roundTo(p, to) {
|
||||
};
|
||||
}
|
||||
|
||||
export function sign(a) {
|
||||
return { x: Math.sign(a.x), y: Math.sign(a.y) };
|
||||
}
|
||||
|
||||
export function abs(a) {
|
||||
return { x: Math.abs(a.x), y: Math.abs(a.y) };
|
||||
}
|
||||
|
||||
export function pow(a, b) {
|
||||
if (typeof b === "number") {
|
||||
return { x: Math.pow(a.x, b), y: Math.pow(a.y, b) };
|
||||
} else {
|
||||
return { x: Math.pow(a.x, b.x), y: Math.pow(a.y, b.y) };
|
||||
}
|
||||
}
|
||||
|
||||
export function dot2(a) {
|
||||
return dot(a, a);
|
||||
}
|
||||
|
||||
export function clamp(a, min, max) {
|
||||
return {
|
||||
x: Math.min(Math.max(a.x, min), max),
|
||||
y: Math.min(Math.max(a.y, min), max),
|
||||
};
|
||||
}
|
||||
|
||||
// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d
|
||||
export function distanceToLine(p, a, b) {
|
||||
const pa = subtract(p, a);
|
||||
const ba = subtract(b, a);
|
||||
const h = Math.min(Math.max(dot(pa, ba) / dot(ba, ba), 0), 1);
|
||||
return length(subtract(pa, multiply(ba, h)));
|
||||
const distance = length(subtract(pa, multiply(ba, h)));
|
||||
const point = add(a, multiply(ba, h));
|
||||
return { distance, point };
|
||||
}
|
||||
|
||||
export function closestPointOnLine(p, a, b) {
|
||||
const pa = subtract(p, a);
|
||||
const ba = subtract(b, a);
|
||||
const h = dot(pa, ba) / lengthSquared(ba);
|
||||
return add(a, multiply(ba, h));
|
||||
// TODO: Fix the robustness of this to allow smoothing on fog layers
|
||||
// https://www.shadertoy.com/view/MlKcDD
|
||||
export function distanceToQuadraticBezier(pos, A, B, C) {
|
||||
let distance = 0;
|
||||
let point = { x: pos.x, y: pos.y };
|
||||
|
||||
const a = subtract(B, A);
|
||||
const b = add(subtract(A, multiply(B, 2)), C);
|
||||
const c = multiply(a, 2);
|
||||
const d = subtract(A, pos);
|
||||
|
||||
// Solve cubic roots to find closest points
|
||||
const kk = 1 / dot(b, b);
|
||||
const kx = kk * dot(a, b);
|
||||
const ky = (kk * (2 * dot(a, a) + dot(d, b))) / 3;
|
||||
const kz = kk * dot(d, a);
|
||||
|
||||
const p = ky - kx * kx;
|
||||
const p3 = p * p * p;
|
||||
const q = kx * (2 * kx * kx - 3 * ky) + kz;
|
||||
let h = q * q + 4 * p3;
|
||||
|
||||
if (h >= 0) {
|
||||
// 1 root
|
||||
h = Math.sqrt(h);
|
||||
const x = divide(subtract({ x: h, y: -h }, q), 2);
|
||||
const uv = multiply(sign(x), pow(abs(x), 1 / 3));
|
||||
const t = Math.min(Math.max(uv.x + uv.y - kx, 0), 1);
|
||||
point = add(A, multiply(add(c, multiply(b, t)), t));
|
||||
distance = dot2(add(d, multiply(add(c, multiply(b, t)), t)));
|
||||
} else {
|
||||
// 3 roots but ignore the 3rd one as it will never be closest
|
||||
// https://www.shadertoy.com/view/MdXBzB
|
||||
const z = Math.sqrt(-p);
|
||||
const v = Math.acos(q / (p * z * 2)) / 3;
|
||||
const m = Math.cos(v);
|
||||
const n = Math.sin(v) * 1.732050808;
|
||||
|
||||
const t = clamp(subtract(multiply({ x: m + m, y: -n - m }, z), kx), 0, 1);
|
||||
const d1 = dot2(add(d, multiply(add(c, multiply(b, t.x)), t.x)));
|
||||
const d2 = dot2(add(d, multiply(add(c, multiply(b, t.y)), t.y)));
|
||||
distance = Math.min(d1, d2);
|
||||
if (d1 < d2) {
|
||||
point = add(d, multiply(add(c, multiply(b, t.x)), t.x));
|
||||
} else {
|
||||
point = add(d, multiply(add(c, multiply(b, t.y)), t.y));
|
||||
}
|
||||
}
|
||||
|
||||
return { distance: Math.sqrt(distance), point: point };
|
||||
}
|
||||
|
||||
export function getBounds(points) {
|
||||
|
Loading…
Reference in New Issue
Block a user