Added default map inset and grid size heuristic

This commit is contained in:
Mitchell McCaffrey 2020-10-08 19:20:14 +11:00
parent f9a69a422b
commit cce339224b
4 changed files with 138 additions and 40 deletions

View File

@ -6,6 +6,7 @@ import ReactResizeDetector from "react-resize-detector";
import useMapImage from "../../helpers/useMapImage";
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
import useStageInteraction from "../../helpers/useStageInteraction";
import { getMapDefaultInset } from "../../helpers/map";
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
@ -36,6 +37,13 @@ function MapEditor({ map, onSettingsChange }) {
mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
}
const defaultInset = getMapDefaultInset(
map.width,
map.height,
map.grid.size.x,
map.grid.size.y
);
const stageTranslateRef = useRef({ x: 0, y: 0 });
const mapLayerRef = useRef();
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
@ -94,7 +102,7 @@ function MapEditor({ map, onSettingsChange }) {
function handleMapReset() {
onSettingsChange("grid", {
...map.grid,
inset: { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: 1 } },
inset: defaultInset,
});
}
@ -110,10 +118,10 @@ function MapEditor({ map, onSettingsChange }) {
};
const gridChanged =
map.grid.inset.topLeft.x !== 0 ||
map.grid.inset.topLeft.y !== 0 ||
map.grid.inset.bottomRight.x !== 1 ||
map.grid.inset.bottomRight.y !== 1;
map.grid.inset.topLeft.x !== defaultInset.topLeft.x ||
map.grid.inset.topLeft.y !== defaultInset.topLeft.y ||
map.grid.inset.bottomRight.x !== defaultInset.bottomRight.x ||
map.grid.inset.bottomRight.y !== defaultInset.bottomRight.y;
return (
<Box

View File

@ -35,13 +35,42 @@ function MapSettings({
}
}
function handleGridSizeChange(event, dimension) {
function handleGridSizeXChange(event) {
const value = parseInt(event.target.value);
const gridY = map.grid.size.y;
let inset = map.grid.inset;
const gridScale =
((inset.bottomRight.x - inset.topLeft.x) * map.width) / value;
inset.bottomRight.y = (gridY * gridScale) / map.height;
onSettingsChange("grid", {
...map.grid,
inset,
size: {
...map.grid.size,
[dimension]: value,
x: value,
},
});
}
function handleGridSizeYChange(event) {
const value = parseInt(event.target.value);
const gridX = map.grid.size.x;
let inset = map.grid.inset;
const gridScale =
((inset.bottomRight.x - inset.topLeft.x) * map.width) / gridX;
inset.bottomRight.y = (value * gridScale) / map.height;
onSettingsChange("grid", {
...map.grid,
inset,
size: {
...map.grid.size,
y: value,
},
});
}
@ -69,7 +98,7 @@ function MapSettings({
type="number"
name="gridX"
value={`${(map && map.grid.size.x) || 0}`}
onChange={(e) => handleGridSizeChange(e, "x")}
onChange={handleGridSizeXChange}
disabled={mapEmpty || map.type === "default"}
min={1}
my={1}
@ -81,7 +110,7 @@ function MapSettings({
type="number"
name="gridY"
value={`${(map && map.grid.size.y) || 0}`}
onChange={(e) => handleGridSizeChange(e, "y")}
onChange={handleGridSizeYChange}
disabled={mapEmpty || map.type === "default"}
min={1}
my={1}

53
src/helpers/map.js Normal file
View File

@ -0,0 +1,53 @@
export function getMapDefaultInset(width, height, gridX, gridY) {
// Max the width
const gridScale = width / gridX;
const y = gridY * gridScale;
const yNorm = y / height;
return { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: yNorm } };
}
// Get all factors of a number
function factors(n) {
const numbers = Array.from(Array(n + 1), (_, i) => i);
return numbers.filter((i) => n % i === 0);
}
// Greatest common divisor
// Euclidean algorithm https://en.wikipedia.org/wiki/Euclidean_algorithm
function gcd(a, b) {
while (b !== 0) {
const t = b;
b = a % b;
a = t;
}
return a;
}
// Find all dividers that fit into two numbers
function dividers(a, b) {
const d = gcd(a, b);
return factors(d);
}
const commonGridScales = [70, 111, 140, 300];
export function gridSizeHeuristic(width, height) {
const div = dividers(width, height);
if (div.length > 0) {
// default to middle divider
let scale = div[Math.floor(div.length / 2)];
for (let common of commonGridScales) {
// Check common but make sure the grid size is above 10
if (div.includes(common) && width / common > 10 && height / common > 10) {
scale = common;
}
}
const x = Math.floor(width / scale);
const y = Math.floor(height / scale);
// Check grid size is below 100
if (x < 100 && y < 100) {
return { x, y };
}
}
return { x: 22, y: 22 };
}

View File

@ -14,11 +14,11 @@ import blobToBuffer from "../helpers/blobToBuffer";
import useKeyboard from "../helpers/useKeyboard";
import { resizeImage } from "../helpers/image";
import { useSearch, useGroup, handleItemSelect } from "../helpers/select";
import { getMapDefaultInset, gridSizeHeuristic } from "../helpers/map";
import MapDataContext from "../contexts/MapDataContext";
import AuthContext from "../contexts/AuthContext";
const defaultMapSize = 22;
const defaultMapProps = {
// Grid type
showGrid: false,
@ -99,34 +99,6 @@ function SelectMapModal({
if (!file) {
return Promise.reject();
}
let fileGridX = defaultMapSize;
let fileGridY = defaultMapSize;
let name = "Unknown Map";
if (file.name) {
// TODO: match all not supported on safari, find alternative
if (file.name.matchAll) {
// Match against a regex to find the grid size in the file name
// e.g. Cave 22x23 will return [["22x22", "22", "x", "23"]]
const gridMatches = [...file.name.matchAll(/(\d+) ?(x|X) ?(\d+)/g)];
if (gridMatches.length > 0) {
const lastMatch = gridMatches[gridMatches.length - 1];
const matchX = parseInt(lastMatch[1]);
const matchY = parseInt(lastMatch[3]);
if (!isNaN(matchX) && !isNaN(matchY)) {
fileGridX = matchX;
fileGridY = matchY;
}
}
}
// Remove file extension
name = file.name.replace(/\.[^/.]+$/, "");
// Removed grid size expression
name = name.replace(/(\[ ?|\( ?)?\d+ ?(x|X) ?\d+( ?\]| ?\))?/, "");
// Clean string
name = name.replace(/ +/g, " ");
name = name.trim();
}
let image = new Image();
setImageLoading(true);
@ -138,6 +110,37 @@ function SelectMapModal({
return new Promise((resolve, reject) => {
image.onload = async function () {
// Find name and grid size
let gridSize;
let name = "Unknown Map";
if (file.name) {
if (file.name.matchAll) {
// Match against a regex to find the grid size in the file name
// e.g. Cave 22x23 will return [["22x22", "22", "x", "23"]]
const gridMatches = [...file.name.matchAll(/(\d+) ?(x|X) ?(\d+)/g)];
if (gridMatches.length > 0) {
const lastMatch = gridMatches[gridMatches.length - 1];
const matchX = parseInt(lastMatch[1]);
const matchY = parseInt(lastMatch[3]);
if (!isNaN(matchX) && !isNaN(matchY)) {
gridSize = { x: matchX, y: matchY };
}
}
}
if (!gridSize) {
gridSize = gridSizeHeuristic(image.width, image.height);
}
// Remove file extension
name = file.name.replace(/\.[^/.]+$/, "");
// Removed grid size expression
name = name.replace(/(\[ ?|\( ?)?\d+ ?(x|X) ?\d+( ?\]| ?\))?/, "");
// Clean string
name = name.replace(/ +/g, " ");
name = name.trim();
}
// Create resolutions
const resolutions = {};
for (let resolution of mapResolutions) {
@ -166,8 +169,13 @@ function SelectMapModal({
name,
type: "file",
grid: {
size: { x: fileGridX, y: fileGridY },
inset: { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: 1 } },
size: gridSize,
inset: getMapDefaultInset(
image.width,
image.height,
gridSize.x,
gridSize.y
),
type: "square",
},
width: image.width,