diff --git a/__tests__/shape.test.js b/__tests__/shape.test.js index 9ce3fa231f777ccd950b0d2ba9cf1ec33be6eecb..06b1ab16a9c8f1687f592021b56b46f8f7253f72 100644 --- a/__tests__/shape.test.js +++ b/__tests__/shape.test.js @@ -244,15 +244,15 @@ describe("shape recognition", () => { }) test("should recognize almost-closed rectangle", () => { - const points = [[-10, -10], [10, -10], [10, 10], [-9, -9]] + const points = [[-10, -10], [10, -10], [10, 10], [-9, 9]] const result = recognizeFromPoints(points) expect(result.shape).toBe(Shapes.rectangle) }) - test("should not recognize half-closed rectangle", () => { + test("should recognize half-closed rectangle", () => { const points = [[-10, -10], [10, -10], [10, 10], [-1, -9]] const result = recognizeFromPoints(points) - expect(result.shape).not.toBe(Shapes.rectangle) + expect(result.shape).toBe(Shapes.rectangle) }) }) }) diff --git a/src/app.js b/src/app.js index 9af5845459b9cd9f6acca70028a7cf29f241ff48..318c42576b37cc28f17db85b21306f64c8b66eaa 100644 --- a/src/app.js +++ b/src/app.js @@ -7,7 +7,7 @@ import * as canvas from "./canvas.js" import * as HTML from "./elements.js" import { connect } from "./room.js" import * as toolSelection from "./tool-selection.js" -import recognizeFromPoints from "./shapes.js" +import recognizeFromPoints, { Shapes } from "./shapes.js" const TEST_ROOM = "imperial" @@ -107,15 +107,22 @@ const onRoomConnect = (room_) => { } const mp = (x, y) => [x, y, 1, "black", true] + function drawRecognized(points) { const recognizedShape = recognizeFromPoints(points) - if (recognizedShape.shape) { + if (recognizedShape.shape === Shapes.line) { console.log(recognizedShape) const [x, y] = points[0] const a = (recognizedShape.angle * Math.PI) / 180 const [x0, y0] = [x - 2000 * Math.cos(a), y + 2000 * Math.sin(a)] const [x1, y1] = [x + 2000 * Math.cos(a), y - 2000 * Math.sin(a)] canvas.renderPath("lastRecognizedLine", [mp(x0, y0), mp(x1, y1)]) + } else if (recognizedShape.shape === Shapes.rectangle) { + console.log(recognizedShape) + canvas.renderPath( + "lastRecognizedLine", + recognizedShape.boundingPoints.map((x) => mp(...x)), + ) } else { canvas.renderPath("lastRecognizedLine", []) } diff --git a/src/shapes.js b/src/shapes.js index 4941821d82726881d2ed791ffb066585a71cddfc..fb4105ad2dd5167f2ab2937a916ccf39232239db 100644 --- a/src/shapes.js +++ b/src/shapes.js @@ -12,6 +12,13 @@ const angleStep = 10 const numAngleCells = 180 / angleStep const rhoMax = 1000 +const getDistance = (a, b) => { + if (!(a & b)) return 0 + return Math.sqrt( + (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]), + ) +} + function findMaxInHough(accum, threshold) { let max = 0 // let bestRho = 0 @@ -67,12 +74,14 @@ function boundingCoords(points) { } const MATRIX_SIZE = 3 +const MATRIX_CENTER_RATIO = 0.65 function mArray(min, max) { - const step = (max - min) / MATRIX_SIZE - return Array(MATRIX_SIZE) - .fill(min) - .map((x, i) => x + step * (i + 1)) + const d = max - min + const centerSegmentSize = d * MATRIX_CENTER_RATIO + const smallStep = (d - centerSegmentSize) / 2 + const p = [min + smallStep, min + smallStep + centerSegmentSize, max] + return p } function getCluster([x, y], xBounds, yBounds) { @@ -90,10 +99,16 @@ function computeClusters(points, xBounds, yBounds) { .fill() .map(() => []), ) - points.forEach((point) => { - const { x, y } = getCluster(point, xBounds, yBounds) - clusters[x][y].push(point) + const intervals = points.map((point, i) => ({ + point, + dist: getDistance(point, points[i + 1]), + })) + + intervals.forEach((interval) => { + const { x, y } = getCluster(interval.point, xBounds, yBounds) + clusters[x][y].push(interval) }) + return clusters } @@ -103,8 +118,8 @@ function clusterCoefficients(clusters, points) { ) } -export function computeMatrixCoefficients(points) { - const { maxX, minX, maxY, minY } = boundingCoords(points) +export function computeMatrixCoefficients(points, boundingRect) { + const { maxX, minX, maxY, minY } = boundingRect const xBounds = mArray(minX, maxX) const yBounds = mArray(minY, maxY) const clusters = computeClusters(points, xBounds, yBounds) @@ -112,16 +127,22 @@ export function computeMatrixCoefficients(points) { return coefficients } +const LINE_Q = 10 + function couldBeLine(points) { - return points.length >= 2 + const { maxX, minX, maxY, minY } = boundingCoords(points) + const [dx, dy] = [maxX - minX, maxY - minY].map((x) => x + 0.00001) + return dy / dx > LINE_Q || dx / dy > LINE_Q } const RECT_THRESHOLD_CENTER = 0.05 -const RECT_THRESHOLD_SIDE_VARIANCE = 0.2 +const RECT_THRESHOLD_SIDE_VARIANCE = 0.12 function couldBeRect(points) { if (points.length < 4) return false - const matrixCoefficients = computeMatrixCoefficients(points) + + const boundingRect = boundingCoords(points) + const matrixCoefficients = computeMatrixCoefficients(points, boundingRect) let [maxC, minC] = [0, 1] for (let i = 0; i < 3; i++) { @@ -133,24 +154,40 @@ function couldBeRect(points) { } } + console.log(matrixCoefficients) + if ( - matrixCoefficients[1][1] < RECT_THRESHOLD_CENTER && - maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE + (matrixCoefficients[1][1] < RECT_THRESHOLD_CENTER && + maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE) || + (maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE * 2 && + matrixCoefficients[1][1] === 0) ) { - return true + return { coefficients: matrixCoefficients, boundingRect } } - return false + return undefined } -function recognizeRect(points) { - return { points } +function recognizeRect(points, rectDetectionData) { + const { minX, minY, maxX, maxY } = rectDetectionData.boundingRect + return { + boundingRect: rectDetectionData.boundingRect, + boundingPoints: [ + [minX, minY], + [minX, maxY], + [maxX, maxY], + [maxX, minY], + [minX, minY], + ], + shape: Shapes.rectangle, + points, + } } function recognizeLine(points) { if (!(points && points.length)) return {} const accum = Array(numAngleCells) const houghConfig = { - rhoStep: points.length > 100 ? 50 : 5, + rhoStep: points.length > 50 ? 50 : 5, } points.forEach((x) => constructHoughAccumulator(houghConfig, accum, ...x)) const angle = findMaxInHough(accum, points.length - 1) @@ -168,8 +205,9 @@ function recognizeLine(points) { } function recognizeFromPoints(points) { - if (couldBeRect(points)) { - return recognizeRect(points) + const rectDetectData = couldBeRect(points) + if (rectDetectData) { + return recognizeRect(points, rectDetectData) } else if (couldBeLine(points)) { return recognizeLine(points) }