From 7114387198def65781dd7aefd30c87f08c5dead3 Mon Sep 17 00:00:00 2001
From: Yuriy Maksymets <iurii.maksymets@gmail.com>
Date: Mon, 18 Nov 2019 18:32:34 +0000
Subject: [PATCH] Much simpler and more effective line recognition

---
 __tests__/shape.test.js |   4 +-
 src/app.js              |   8 +-
 src/room.js             |  17 ----
 src/shapes.js           | 186 ++++++++++++++++++++--------------------
 4 files changed, 98 insertions(+), 117 deletions(-)

diff --git a/__tests__/shape.test.js b/__tests__/shape.test.js
index 06b1ab1..ffe3fce 100644
--- a/__tests__/shape.test.js
+++ b/__tests__/shape.test.js
@@ -40,7 +40,7 @@ describe("shape recognition", () => {
     })
 
     test("should recognize a slightly curve horizontal line", () => {
-      const points = [[0, 0], [30, 5], [100, 2]]
+      const points = [[0, 0], [30, 5], [100, 0]]
       const result = recognizeFromPoints(points)
       expect(result.shape).toBe(Shapes.line)
       expect(result.angle).toBe(0)
@@ -192,7 +192,7 @@ describe("shape recognition", () => {
 
       const result = recognizeFromPoints(points)
       expect(result.shape).toBe(Shapes.line)
-      expect(result.angle).toBe(20)
+      expect(result.angle).toBeGreaterThan(12)
     })
   })
 
diff --git a/src/app.js b/src/app.js
index dd5c44d..552aebd 100644
--- a/src/app.js
+++ b/src/app.js
@@ -161,20 +161,14 @@ function attributedPoint(x, y, pressure = 0) {
   ]
 }
 
-const INF_LINE_OFFSET = 1000
-
 function getRecognizedShapePoints(points) {
   const recognizedShape = recognizeFromPoints(points)
   if (!recognizedShape.shape) return undefined
   switch (recognizedShape.shape) {
     case Shapes.line: {
       const [x, y] = points[0]
-      const a = (recognizedShape.angle * Math.PI) / 180
-      const length = recognizedShape.length
-      const dx = length * Math.cos(a)
-      const dy = length * Math.sin(a)
       const p1 = [x, y]
-      const p2 = [x + dx, y - dy]
+      const p2 = recognizedShape.lastPoint
       return [p1, p2]
     }
     case Shapes.rectangle: {
diff --git a/src/room.js b/src/room.js
index b69f8f2..2f920ef 100644
--- a/src/room.js
+++ b/src/room.js
@@ -44,34 +44,17 @@ class Room extends EventTarget {
   }
 
   extendErasureIntervals(pathID, pointID, newIntervals) {
-    console.log(flattenErasureIntervals({ [pointID]: newIntervals }))
-
     this.shared.eraseIntervals
       .get(pathID)
       .merge(flattenErasureIntervals({ [pointID]: newIntervals }))
   }
 
-  // TODO: Refactor duplication
   replacePath(pathID, newPoints) {
     this.shared.eraseIntervals
       .get(pathID)
       .merge([[0, this.shared.strokePoints.get(pathID).length]])
 
     newPoints.forEach((point) => this.extendPath(pathID, point))
-    // // eslint-disable-next-line require-yield
-    // this._y.db.requestTransaction(function* requestTransaction() {
-    //   const prevJSON = self.shared.eraseIntervals.get(pathID) || "[]"
-    //   const postJSON = JSON.stringify([
-    //     [0, self.shared.strokePoints.get(pathID).length],
-    //   ])
-
-    //   if (prevJSON == postJSON) {
-    //     return
-    //   }
-
-    //   newPoints.forEach((point) => self.extendPath(pathID, point))
-    //   self.shared.eraseIntervals.set(pathID, postJSON)
-    // })
   }
 
   getPaths() {
diff --git a/src/shapes.js b/src/shapes.js
index 348990c..be083fe 100644
--- a/src/shapes.js
+++ b/src/shapes.js
@@ -1,17 +1,3 @@
-function dtor(a) {
-  return (Math.PI * a) / 180
-}
-function cos(a) {
-  return Math.cos(dtor(a))
-}
-function sin(a) {
-  return Math.sin(dtor(a))
-}
-
-const angleStep = 90
-const numAngleCells = 180 / angleStep
-const rhoMax = 10000
-
 const getDistance = (a, b) => {
   if (!(a && b)) return 0
   return Math.sqrt(
@@ -19,48 +5,60 @@ const getDistance = (a, b) => {
   )
 }
 
-function findMaxInHough(accum, threshold) {
-  let max = 0
-  //   let bestRho = 0
-  let bestTheta = 0
-  for (let i = 0; i < numAngleCells; i++) {
-    if (!accum[i]) continue
-    for (let j = 0; j < accum[i].length; j++) {
-      if (accum[i][j] > max) {
-        max = accum[i][j]
-        // bestRho = j
-        bestTheta = i
-      }
-    }
-  }
-  //   bestRho <<= 1
-  //   bestRho -= rhoMax
-  //   bestRho *= rhoStep
-  bestTheta *= angleStep
-
-  if (max > threshold) {
-    return bestTheta
-  }
-  return undefined
-}
-
-function constructHoughAccumulator(config, accumulator, x, y) {
-  for (let thetaIndex = 0; thetaIndex < numAngleCells; thetaIndex++) {
-    const theta = thetaIndex * angleStep
-    let rho = x * cos(theta) + y * sin(theta)
-    rho = Math.floor(rho)
-    rho += rhoMax
-    rho >>= 1
-    rho /= config.rhoStep
-    rho = Math.floor(rho)
-    if (accumulator[thetaIndex] == undefined) accumulator[thetaIndex] = []
-    if (accumulator[thetaIndex][rho] == undefined) {
-      accumulator[thetaIndex][rho] = 1
-    } else {
-      accumulator[thetaIndex][rho]++
-    }
-  }
-}
+// function dtor(a) {
+//   return (Math.PI * a) / 180
+// }
+
+// function cos(a) {
+//   return Math.cos(dtor(a))
+// }
+
+// function sin(a) {
+//   return Math.sin(dtor(a))
+// }
+
+// const angleStep = 90
+// const numAngleCells = 180 / angleStep
+// const rhoMax = 10000
+
+// function findMaxInHough(accum, threshold) {
+//   let max = 0
+//   //   let bestRho = 0
+//   let bestTheta = 0
+//   for (let i = 0; i < numAngleCells; i++) {
+//     if (!accum[i]) continue
+//     for (let j = 0; j < accum[i].length; j++) {
+//       if (accum[i][j] > max) {
+//         max = accum[i][j]
+//         bestTheta = i
+//       }
+//     }
+//   }
+//   bestTheta *= angleStep
+
+//   if (max > threshold) {
+//     return bestTheta
+//   }
+//   return undefined
+// }
+
+// function constructHoughAccumulator(config, accumulator, x, y) {
+//   for (let thetaIndex = 0; thetaIndex < numAngleCells; thetaIndex++) {
+//     const theta = thetaIndex * angleStep
+//     let rho = x * cos(theta) + y * sin(theta)
+//     rho = Math.floor(rho)
+//     rho += rhoMax
+//     rho >>= 1
+//     rho /= config.rhoStep
+//     rho = Math.floor(rho)
+//     if (accumulator[thetaIndex] == undefined) accumulator[thetaIndex] = []
+//     if (accumulator[thetaIndex][rho] == undefined) {
+//       accumulator[thetaIndex][rho] = 1
+//     } else {
+//       accumulator[thetaIndex][rho]++
+//     }
+//   }
+// }
 
 function boundingCoords(points) {
   const xs = points.map((p) => p[0])
@@ -86,11 +84,14 @@ function angleBetweenVectors(p1, p2) {
   return Math.acos((x0 * x1 + y0 * y1) / (vectorLength(p1) * vectorLength(p2)))
 }
 
-const LINE_ANGLE_THRESHOLD = Math.PI / 4
-const VECTOR_LEN_THRESHOLD = 5
+const LINE_ANGLE_THRESHOLD = Math.PI / 6
+const VECTOR_LEN_THRESHOLD_FRACTION = 0.15
 
 function couldBeLine(points) {
   if (points.length < 2) return false
+  const vectorThreshold = Math.floor(
+    points.length * VECTOR_LEN_THRESHOLD_FRACTION,
+  )
   const pivot = points[0]
   let cumulativeThreshold = 0
   for (let i = 2; i < points.length; i++) {
@@ -103,10 +104,7 @@ function couldBeLine(points) {
 
     const angle = angleBetweenVectors(d1, d2)
     if (Math.abs(angle) > LINE_ANGLE_THRESHOLD) {
-      if (
-        cumulativeThreshold < VECTOR_LEN_THRESHOLD &&
-        d2Len < VECTOR_LEN_THRESHOLD
-      ) {
+      if (cumulativeThreshold < vectorThreshold && d2Len < vectorThreshold) {
         cumulativeThreshold += d2Len
         continue
       }
@@ -217,39 +215,45 @@ function recognizeRect(points, rectDetectionData) {
   }
 }
 
-const MAX_RHO_STEP = 50
-const MIN_RHO_STEP = 5
-
-function rhoStepForPoints(points) {
-  return points.length > 50 ? MAX_RHO_STEP : MIN_RHO_STEP
-}
-
 function recognizeLine(points) {
   const [p1, p2] = [points[0], points[points.length - 1]]
-  const angle = angleBetweenVectors(p1, p2)
-  return { shape: Shapes.line, angle, points, length: getDistance(p1, p2) }
-}
-
-function recognizeLineHough(points) {
-  if (!(points && points.length)) return {}
-  const accum = Array(numAngleCells)
-  const houghConfig = {
-    rhoStep: rhoStepForPoints(points),
-  }
-  points.forEach((x) => constructHoughAccumulator(houghConfig, accum, ...x))
-  const angle = findMaxInHough(accum, points.length - 10)
-
-  if (angle !== undefined) {
-    return {
-      shape: Shapes.line,
-      angle: 90 - angle,
-      hough: accum,
-      points,
-    }
+  const angle =
+    (angleBetweenVectors(diffVector(p2, p1), [1, 0]) / Math.PI) * 180
+  return {
+    shape: Shapes.line,
+    angle,
+    points,
+    length: getDistance(p1, p2),
+    lastPoint: p2.slice(0, 2),
   }
-
-  return {}
 }
+// const MAX_RHO_STEP = 50
+// const MIN_RHO_STEP = 5
+
+// function rhoStepForPoints(points) {
+//   return points.length > 50 ? MAX_RHO_STEP : MIN_RHO_STEP
+// }
+
+// function recognizeLineHough(points) {
+//   if (!(points && points.length)) return {}
+//   const accum = Array(numAngleCells)
+//   const houghConfig = {
+//     rhoStep: rhoStepForPoints(points),
+//   }
+//   points.forEach((x) => constructHoughAccumulator(houghConfig, accum, ...x))
+//   const angle = findMaxInHough(accum, points.length - 10)
+
+//   if (angle !== undefined) {
+//     return {
+//       shape: Shapes.line,
+//       angle: 90 - angle,
+//       hough: accum,
+//       points,
+//     }
+//   }
+
+//   return {}
+// }
 
 function recognizeFromPoints(points) {
   const rectDetectData = couldBeRect(points)
-- 
GitLab