Skip to content
Snippets Groups Projects
shapes.js 6.66 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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 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])
      const ys = points.map((p) => p[1])
      return {
        maxX: Math.max(...xs),
        minX: Math.min(...xs),
        maxY: Math.max(...ys),
        minY: Math.min(...ys),
      }
    }
    
    
    function vectorLength([x, y]) {
      return Math.hypot(x, y)
    }
    
    function diffVector([x0, y0], [x1, y1]) {
      return [x0 - x1, y0 - y1]
    }
    
    function angleBetweenVectors(p1, p2) {
      const [[x0, y0], [x1, y1]] = [p1, p2]
      return Math.acos((x0 * x1 + y0 * y1) / (vectorLength(p1) * vectorLength(p2)))
    }
    
    
    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++) {
        const p1 = points[i - 1]
        const p2 = points[i]
        const d1 = diffVector(pivot, p1)
        const d2 = diffVector(p1, p2)
    
        const d2Len = vectorLength(d2)
    
        const angle = angleBetweenVectors(d1, d2)
        if (Math.abs(angle) > LINE_ANGLE_THRESHOLD) {
    
          if (cumulativeThreshold < vectorThreshold && d2Len < vectorThreshold) {
    
            cumulativeThreshold += d2Len
            continue
          }
    
          return false
        }
      }
      return true
    
    const MATRIX_SIZE = 3
    
    const MATRIX_CENTER_RATIO = 0.65
    
    
    function mArray(min, max) {
    
      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) {
      return {
        x: xBounds.findIndex((bound) => x <= bound),
        y: yBounds.findIndex((bound) => y <= bound),
      }
    }
    
    function computeClusters(points, xBounds, yBounds) {
      const clusters = Array(MATRIX_SIZE)
        .fill(0)
        .map(() =>
          Array(MATRIX_SIZE)
            .fill()
    
            .map(() => ({ arr: [], sum: 0 })),
    
      const intervals = points.map((point, i) => ({
        point,
        dist: getDistance(point, points[i + 1]),
      }))
    
    
      let totalSum = 0
    
      intervals.forEach((interval) => {
        const { x, y } = getCluster(interval.point, xBounds, yBounds)
    
        clusters[x][y].arr.push(interval)
        clusters[x][y].sum += interval.dist
        totalSum += interval.dist
    
      return { arr: clusters, totalSum }
    
    function clusterCoefficients(clusters) {
      return clusters.arr.map((rowCluster) =>
        rowCluster.map((cluster) => cluster.sum / clusters.totalSum),
    
    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)
      const coefficients = clusterCoefficients(clusters, points)
      return coefficients
    }
    
    
    const RECT_THRESHOLD_CENTER = 0
    const RECT_THRESHOLD_SIDE_VARIANCE = 0.25
    
    
    function couldBeRect(points) {
      if (points.length < 4) return false
    
    
      const boundingRect = boundingCoords(points)
      const matrixCoefficients = computeMatrixCoefficients(points, boundingRect)
    
    
      let [maxC, minC] = [0, 1]
    
      for (let i = 0; i < MATRIX_SIZE; i++) {
        for (let j = 0; j < MATRIX_SIZE; j++) {
    
          if (!(i === j && j === 1)) {
            maxC = Math.max(maxC, matrixCoefficients[i][j])
            minC = Math.min(minC, matrixCoefficients[i][j])
          }
        }
      }
    
      if (
    
        matrixCoefficients[1][1] <= RECT_THRESHOLD_CENTER &&
        maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE
    
        return { coefficients: matrixCoefficients, boundingRect }
    
      return undefined
    
    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) {
    
      const [p1, p2] = [points[0], points[points.length - 1]]
    
      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),
    
    Yuriy Maksymets's avatar
    Yuriy Maksymets committed
      }
    }
    
    // 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 {}
    // }
    
    Yuriy Maksymets's avatar
    Yuriy Maksymets committed
    
    
    function recognizeFromPoints(points) {
    
      const rectDetectData = couldBeRect(points)
      if (rectDetectData) {
        return recognizeRect(points, rectDetectData)
    
      } else if (couldBeLine(points)) {
        return recognizeLine(points)
      }
    
      return {}
    }
    
    
    Yuriy Maksymets's avatar
    Yuriy Maksymets committed
    export const Shapes = {
      rectangle: "rect",
    
      line: "line",
    
    Yuriy Maksymets's avatar
    Yuriy Maksymets committed
    }
    
    export default recognizeFromPoints