Fitting a polyBézier curve to data points

Discussions related to mathematics, numerical methods, graph plotting etc.
RichardRussell

Fitting a polyBézier curve to data points

Post by RichardRussell »

As announced elsewhere, the bezierfit example program supplied with BBC BASIC for SDL 2.0 (version 1.08a or later) is also fully compatible with BBC BASIC for Windows. Although there's nothing to stop a BB4W user from downloading BBCSDL (free) just to get this example, I've listed it here to save him/her the trouble:

Code: Select all

      REM Program to demonstrate fitting a PolyBézier curve to several data points
      REM Version 1.0 by Richard Russell, http://www.rtrussell.co.uk/, 19-Nov-2019
      REM Compatible with both 'BBC BASIC for Windows' and 'BBC BASIC for SDL 2.0'

      BB4W% = INKEY$(-256) == "W"
      VDU 23,22,640;500;8,20,16,128+8
      ON ERROR PROCcleanup : IF ERR=17 CHAIN @lib$+"../examples/tools/touchide" ELSE MODE 3 : PRINT REPORT$ : END
      ON CLOSE PROCcleanup : QUIT

      IF BB4W% THEN
        INSTALL @lib$ + "GDIPLIB"
        PROC_gdipinit
        *FONT Arial, 18
      ELSE
        INSTALL @lib$ + "aagfxlib"
        OSCLI "FONT """ + @lib$ + "DejaVuSans"", 18"
      ENDIF
      INSTALL @lib$ + "arraylib"
      OFF : VDU 5

      DIM X(9), Y(9)
      Y(0) = 500 : X(9) = 1280 : Y(9) = 500
      FOR I% = 1 TO 8
        X(I%) = (I%-1) * 160 + RND(160)
        Y(I%) = 300 + RND(300)
      NEXT

      title$ = "Drag the points to make a new polyBézier curve"
      title% = (1280 - WIDTH(title$)) / 2

      *REFRESH OFF
      selected% = 0
      oldB% = 0
      oldX% = 0
      oldY% = 0
      REPEAT
        CLS
        GCOL 0 : VDU 30 : PLOT 0, title%, 0 : PRINT title$;

        FOR I% = 0 TO 9
          PROCbezierspline(X(I%), Y(I%), I% = 0)
        NEXT

        FOR I% = 1 TO 8
          IF BB4W% THEN
            IF I% = selected% C% = &FF008000 ELSE C% = &FF0000FF
            brush% = FN_gdipcreatebrush(C%)
            PROC_gdipsector(brush%, X(I%), Y(I%), 16, 16, 0, 360)
            PROC_gdipdeletebrush(brush%)
          ELSE
            IF I% = selected% C% = &FF008000 ELSE C% = &FFFF0000
            PROC_aasector(X(I%), Y(I%), 16, 16, 0, 360, C%)
          ENDIF
        NEXT

        change% = FALSE
        REPEAT
          MOUSE X%, Y%, B%

          IF B% IF B% <> oldB% THEN
            near% = -1
            dist% = &7FFFFFFF
            oldsel% = selected%
            FOR I% = 1 TO 8
              D% = (X% - X(I%))^2 + (Y% - Y(I%))^2
              IF D% < dist% dist% = D% : near% = I%
            NEXT
            IF dist% < 50*50 selected% = near% ELSE selected% = 0
            IF selected% <> oldsel% change% = TRUE
            oldX% = X% : oldY% = Y%
          ENDIF

          IF B% IF selected% > 0 THEN
            IF X% <> oldX% X(selected%) += X% - oldX% : change% = TRUE
            IF Y% <> oldY% Y(selected%) += Y% - oldY% : change% = TRUE
          ENDIF

          oldX% = X% : oldY% = Y% : oldB% = B%

          CASE INKEY(1) OF
            WHEN 9:   selected% += 1 : change% = TRUE : IF selected% > 8 selected% = 1
            WHEN 155: selected% -= 1 : change% = TRUE : IF selected% < 1 selected% = 8
            WHEN 136: IF selected% > 0 X(selected%) -= 4 : change% = TRUE
            WHEN 137: IF selected% > 0 X(selected%) += 4 : change% = TRUE
            WHEN 138: IF selected% > 0 Y(selected%) -= 4 : change% = TRUE
            WHEN 139: IF selected% > 0 Y(selected%) += 4 : change% = TRUE
          ENDCASE

          IF BB4W% SYS "InvalidateRect", @hwnd%, FALSE
          *REFRESH

        UNTIL change%

      UNTIL FALSE
      END

      REM Calculate and draw a PolyBézier curve from a set of data points:
      DEF PROCbezierspline(x, y, F%)
      LOCAL pen%, cpx(), cpy()
      PRIVATE N%, x(),y() : IF F% N% = 0
      DIM x(3), y(3), cpx(2), cpy(2)

      x() = x(1), x(2), x(3), x
      y() = y(1), y(2), y(3), y

      N% += 1 : IF N% < 4 ENDPROC

      PROCcpcalc(x(), cpx())
      PROCcpcalc(y(), cpy())

      IF BB4W% THEN
        pen% = FN_gdipcreatepen(&FFFF0000, 0, 6)
        PROC_gdipbezier(pen%, x(1), y(1), cpx(1), cpy(1), cpx(2), cpy(2), x(2), y(2))
        PROC_gdipdeletepen(pen%)
      ELSE
        PROC_aabezier(x(1), y(1), cpx(1), cpy(1), cpx(2), cpy(2), x(2), y(2), 6, &FF0000FF, 0)
      ENDIF
      ENDPROC

      REM Calculate control points:
      DEF PROCcpcalc(v(), cp())
      LOCAL I%, b, rhs(), tmp()
      DIM rhs(2), tmp(2)
      rhs(0) = v(0) + 2*v(1)
      rhs(1) = 4*v(1) + 2*v(2)
      rhs(2) = (8*v(2) + v(3)) / 2
      b = 2
      cp(0) = rhs(0) / b
      FOR I% = 1 TO 2
        tmp(I%) = 1 / b
        IF I% = 1 b = 4.0 - tmp(I%) ELSE b = 3.5 - tmp(I%)
        cp(I%) = (rhs(I%) - cp(I%-1)) / b
      NEXT
      FOR I% = 1 TO 2
        cp(2-I%) -= tmp(3-I%) * cp(3-I%)
      NEXT
      cp(2) = 2*v(2) - cp(2)
      ENDPROC

      DEF PROCcleanup
      ON ERROR OFF
      *REFRESH ON
      IF BB4W% PROC_gdipexit
      ENDPROC
You can drag the points with the mouse (or your finger/stylus on a touchscreen) or you can use the keyboard: Tab and Shift+Tab select the required point and the four arrow keys move it.

Image