QB64 Phoenix Edition
Unit circle visualizer - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Code and Stuff (https://qb64phoenix.com/forum/forumdisplay.php?fid=3)
+---- Forum: Works in Progress (https://qb64phoenix.com/forum/forumdisplay.php?fid=9)
+---- Thread: Unit circle visualizer (/showthread.php?tid=695)



Unit circle visualizer - OldMoses - 07-28-2022

I have always had an issue remembering what trig function does what. I'm doing a graphic visualization aid so I can finally get my gourd wrapped around it all. I find it easier to absorb this sort of thing if I have a visual aid. It has some math issues to be ironed out, some rather clumsy code and I have more things I'm thinking of adding, but generally is doing what I wanted. I zipped the whole thing since it's relying on a library, but here's the base code.

EDIT: 7-29-2022 Upload changes
8-13-2022 added bolt circle calculator feature

Code: (Select All)
'Unit Circle.bas
_TITLE "Unit Circle Visualizer"
'MAIN MODULE
'Type declarations
TYPE V2
    x AS INTEGER
    y AS INTEGER
END TYPE

TYPE U2
    x AS SINGLE
    y AS SINGLE
END TYPE

TYPE object
    x AS INTEGER
    y AS INTEGER
    r AS DOUBLE
END TYPE

'Global variables
DIM SHARED baselayer AS LONG
DIM SHARED ang(24) AS object

'Constants
CONST TwoPI = 2 * _PI

'Display Setup
SCREEN _NEWIMAGE(1024, 512, 32)
WINDOW (-512, 256)-(512, -256)
Draw_Base
Main_Loop

'DATA SECTION
DATA 0,0,"pi/12"
DATA 0,0,"pi/6",0,0,"pi/4"
DATA 0,0,"pi/3",0,0,"5(pi)/12"
DATA -16,0,"pi/2",0,0,"7(pi)/12"
DATA 0,0,"2(pi)/3",0,0,"3(pi)/4"
DATA 0,0,"5(pi)/6",0,0,"11(pi)/12"
DATA 0,8,"(180) pi",0,0,"13(pi)/12"
DATA 0,0,"7(pi)/6",0,0,"5(pi)/4"
DATA 0,0,"4(pi)/3",0,0,"17(pi)/12"
DATA 24,0,"3(pi)/2",0,0,"19(pi)/12"
DATA 0,0,"5(pi)/3",0,0,"7(pi)/4"
DATA 0,0,"11(pi)/6",0,0,"23(pi)/12"
DATA 0,8,"2(pi) (0)"


'SUBROUTINE SECTION


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Bolt_Circ
    WINDOW
    Dialog_Box "Bolt Circle", 512, 512, 0, &HFF00FF00, &HFFFFFFFF, "c"
    _PRINTMODE _KEEPBACKGROUND
    LOCATE 4, _SHR(_WIDTH(0), 4) - 6
    INPUT "# of holes: ", h%
    LOCATE 5, _SHR(_WIDTH(0), 4) - 6
    INPUT "radius:     ", r!
    _PRINTSTRING (_SHR(_WIDTH(0), 1) - 112, 140), "Hole coordinates relative to..."
    DIM lb$(6)
    lb$(1) = "Center": lb$(2) = "Quad 1": lb$(3) = "Quad 2"
    lb$(4) = "Quad 3": lb$(5) = "Quad 4": lb$(6) = "From 1"
    ref% = Chs_Key_Button%("c1234f", "h", 160, 6, 70, 32, 10, _SHR(_WIDTH(0), 1), lb$())
    WINDOW (-512, 256)-(512, -256)
    Clear_MB 1
    IF ref% = -1 THEN EXIT SUB
    _PUTIMAGE , baselayer
    LINE (295, 205)-(500, 168 - (h% * 16) - 48), &HFF7F7FFF, BF
    COLOR &HFF010101
    _PRINTSTRING (PMAP(300, 0), PMAP(200, 1)), "Bolt Circle Coordinates"
    _PRINTSTRING (PMAP(300, 0), PMAP(184, 1)), "        X        Y"
    DIM bolt(h% - 1) AS object
    DIM drill(h% - 1) AS U2
    maxx! = 0: maxy! = 0
    FOR x% = 0 TO UBOUND(bolt)
        angle! = ((TwoPI) / h%) * x%
        bolt(x%).r = angle!
        bolt(x%).x = COS(angle!) * 200
        bolt(x%).y = SIN(angle!) * 200
        drill(x%).x = CINT(COS(angle!) * (r! * 1000)) / 1000
        drill(x%).y = CINT(SIN(angle!) * (r! * 1000)) / 1000
        IF x% > 0 THEN
            maxx! = maxx! + (-(ABS(drill(x%).x) - maxx!) * (ABS(drill(x%).x) > maxx!))
        END IF
        maxy! = maxy! + (-(ABS(drill(x%).y) - maxy!) * (ABS(drill(x%).y) > maxy!))
    NEXT x%
    'find limits for ref%, modify maxx! & maxy! for quad or hole #1
    SELECT CASE ref%
        CASE IS = 1: maxx! = 0: maxy! = 0 '                     no change
        CASE IS = 2: maxx! = drill(0).x '                       quad 1
        CASE IS = 3: maxx! = -maxx! '                           quad 2
        CASE IS = 4: maxx! = -maxx!: maxy! = -maxy! '           quad 3
        CASE IS = 5: maxx! = drill(0).x: maxy! = -maxy! '       quad 4
        CASE IS = 6: maxx! = drill(0).x: maxy! = drill(0).y '   subtract hole 1 position
    END SELECT
    'reference crosshairs
    LINE (-512, maxy! / r! * 200)-(1023, maxy! / r! * 200), &HFFFF0000, , 15 'horizontal
    LINE (maxx! / r! * 200, 256)-(maxx! / r! * 200, -255), &HFFFF0000, , 15 'vertical

    FOR x% = 0 TO UBOUND(bolt)
        'apply origin modifier
        drill(x%).x = drill(x%).x - maxx!: drill(x%).y = drill(x%).y - maxy!
        hole& = _NEWIMAGE(40, 40, 32)
        _DEST hole&
        CLS
        _CLEARCOLOR _RGB(0, 0, 0)
        FCirc 20, 20, 19, &HFF00FF00
        _PRINTMODE _KEEPBACKGROUND
        COLOR &HFF010101
        _PRINTSTRING (8, 12), STR$(x% + 1)
        _DEST 0
        _PUTIMAGE (bolt(x%).x - 20, bolt(x%).y + 20), hole&, 0
        _FREEIMAGE hole&
        COLOR &HFF010101
        _PRINTSTRING (PMAP(300, 0), PMAP(168 - (x% * 16), 1)), STR$(x% + 1)
        _PRINTSTRING (PMAP(Align%(348, drill(x%).x), 0), PMAP(168 - (x% * 16), 1)), STR$(drill(x%).x)
        _PRINTSTRING (PMAP(Align%(424, drill(x%).y), 0), PMAP(168 - (x% * 16), 1)), STR$(drill(x%).y)
    NEXT x%
    chrd! = CINT(2 * (r! * 1000) * SIN(angle! / 2)) / 1000
    _PRINTSTRING (PMAP(300, 0), PMAP(168 - ((x% + 1) * 16), 1)), "Chord= " + STR$(chrd!)
    COLOR &HFFFFFFFF
    Press_Click

END SUB 'Bolt_Circ


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Details (var AS object, hand AS _BYTE)

    hd$ = Fix_Float(CDBL(_R2D(var.r)), 2) + CHR$(248) + " Properties"
    sn$ = LEFT$(Fix_Float$(SIN(var.r), 4), 6)
    cs$ = LEFT$(Fix_Float$(COS(var.r), 4), 6)
    IF hand THEN
        IF var.r = _PI / 2 OR var.r = (3 * _PI) / 2 THEN
            tn$ = "undefined"
        ELSE
            tn$ = LEFT$(Fix_Float$(TAN(var.r), 4), 6)
        END IF
    ELSE
        IF var.x = 0 THEN
            tn$ = "undefined"
        ELSE
            tn$ = LEFT$(Fix_Float$(TAN(var.r), 4), 6)
        END IF
    END IF
    rd$ = LEFT$(Fix_Float$(var.r, 8), 8)
    LINE (295, 205)-(500, -100), &HFF7F7FFF, BF
    c& = _DEFAULTCOLOR
    COLOR &HFF000000
    _PRINTMODE _KEEPBACKGROUND
    _PRINTSTRING (PMAP(300, 0), PMAP(200, 1)), hd$
    _PRINTSTRING (PMAP(300, 0), PMAP(168, 1)), "Sin: " + sn$ 'STR$(SIN(var.r))
    _PRINTSTRING (PMAP(300, 0), PMAP(152, 1)), "Cos: " + cs$ 'STR$(COS(var.r))
    _PRINTSTRING (PMAP(300, 0), PMAP(136, 1)), "Tan: " + tn$

    _PRINTSTRING (PMAP(300, 0), PMAP(112, 1)), "Radians= " + rd$
    CIRCLE (0, 0), 200, &H3F00FF00, 0, var.r
    Press_Click
    COLOR c&
    _PRINTMODE _FILLBACKGROUND

END SUB 'Details


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Draw_Base
    CLS
    '(xpos AS INTEGER, ypos AS INTEGER, xsiz AS INTEGER, ysiz AS INTEGER, label AS STRING, high AS INTEGER, col AS _UNSIGNED LONG)
    Con_Blok PMAP(0, 2), PMAP(120, 3), 100, 32, "Enter Angle", 0, &HFF7F7F7F
    Con_Blok PMAP(0, 2), PMAP(168, 3), 100, 32, "Enter SIN", 0, &HFFFF3F3F
    Con_Blok PMAP(0, 2), PMAP(216, 3), 100, 32, "Enter COS", 0, &HFF3F3FFF
    Con_Blok PMAP(924, 2), PMAP(120, 3), 100, 32, "Bolt Circle", 0, &HFF7F7F7F
    DO UNTIL i% > 500 '                                         Grid loop
        IF i% = 0 THEN
            c& = &H7FFF0000: tk% = 5
            DO UNTIL j% > 500 '                                 Axis tick loop
                j% = j% + 20
                LINE (-tk%, j%)-(tk%, j%), c& 'vert
                LINE (-tk%, -j%)-(tk%, -j%), c& 'vert
                LINE (j%, -tk%)-(j%, tk%), c& 'horiz
                LINE (-j%, -tk%)-(-j%, tk%), c& 'horiz
            LOOP
        END IF
        c& = &H7F9F9F9F * (i% > 0) - &HFF0F3FFF * (i% = 0)
        LINE (i%, -256)-(i%, 256), c&
        IF i% > 0 THEN LINE (-i%, -256)-(-i%, 256), c&
        LINE (-512, i%)-(512, i%), c&
        IF i% > 0 THEN LINE (-512, -i%)-(512, -i%), c&
        i% = i% + 100
    LOOP '                                                      end: grid loop
    CIRCLE (0, 0), 200, &HFFFFFFFF '                            unit circle
    FOR sp# = _PI / 12 TO TwoPI STEP _PI / 12 '                 common angle points & leaders
        q% = q% + 1
        ang(q%).r = sp#: ang(q%).x = COS(sp#) * 200: ang(q%).y = SIN(sp#) * 200
        FCirc ang(q%).x, ang(q%).y, 3, &HFFFF00FF
        LINE (COS(sp#) * 20, SIN(sp#) * 20)-(COS(sp#) * 215, SIN(sp#) * 215), &H2FFFFFFF
        READ x%, y%, l$
        sb% = LEN(l$) * 8 * (SGN(COS(sp#)) < 0)
        su% = -16 * (SGN(SIN(sp#)) > 0) '                       setup for quad I & II
        _PRINTSTRING (PMAP(COS(sp#) * 220 + sb% + x%, 0), PMAP(SIN(sp#) * 220 + su% + y%, 1)), l$
    NEXT sp#
    _PRINTSTRING (0, 0), "Quadrant II (-, +)"
    _PRINTSTRING (0, 496), "Quadrant III (-, -)"
    _PRINTSTRING (887, 0), "Quadrant I (+, +)"
    _PRINTSTRING (879, 496), "Quadrant IV (+, -)"
    _PRINTSTRING (PMAP(-12, 0), PMAP(252, 1)), "(90)"
    _PRINTSTRING (PMAP(-20, 0), PMAP(-236, 1)), "(270)"
    _PRINTSTRING (0, 416), "SOH:"
    _PRINTSTRING (0, 432), "CAH:"
    _PRINTSTRING (0, 448), "TOA:"
    FOR y% = 416 TO 448 STEP 16
        _PRINTSTRING (72, y%), "="
        _PRINTSTRING (120, y%), "/"
    NEXT y%
    _PRINTSTRING (631, 496), "Esc to quit"

    'represent (SIN, COS) here
    COLOR &HFFFF0000
    _PRINTSTRING (160, 0), "+SIN"
    _PRINTSTRING (168, 496), "-SIN"
    _PRINTSTRING (799, 0), "+SIN"
    _PRINTSTRING (791, 496), "-SIN"
    _PRINTSTRING (160, 16), "+CSC"
    _PRINTSTRING (168, 480), "-CSC"
    _PRINTSTRING (799, 16), "+CSC"
    _PRINTSTRING (791, 480), "-CSC"
    _PRINTSTRING (40, 416), "Sin"
    _PRINTSTRING (88, 416), "Opp"
    _PRINTSTRING (88, 448), "Opp"
    COLOR &HFF0000FF
    _PRINTSTRING (200, 0), "-COS"
    _PRINTSTRING (208, 496), "-COS"
    _PRINTSTRING (839, 0), "+COS"
    _PRINTSTRING (831, 496), "+COS"
    _PRINTSTRING (200, 16), "-SEC"
    _PRINTSTRING (208, 480), "-SEC"
    _PRINTSTRING (839, 16), "+SEC"
    _PRINTSTRING (831, 480), "+SEC"
    _PRINTSTRING (40, 432), "Cos"
    _PRINTSTRING (88, 432), "Adj"
    _PRINTSTRING (136, 448), "Adj"
    COLOR &HFFFF00FF
    _PRINTSTRING (240, 0), "-TAN"
    _PRINTSTRING (248, 496), "+TAN"
    _PRINTSTRING (759, 0), "+TAN"
    _PRINTSTRING (751, 496), "-TAN"
    _PRINTSTRING (240, 16), "-COT"
    _PRINTSTRING (248, 480), "+COT"
    _PRINTSTRING (759, 16), "+COT"
    _PRINTSTRING (751, 480), "-COT"
    _PRINTSTRING (40, 448), "Tan"
    COLOR &HFF00FF00
    _PRINTSTRING (136, 416), "Hyp"
    _PRINTSTRING (136, 432), "Hyp"
    COLOR &HFFFFFFFF
    baselayer = _COPYIMAGE(0)

END SUB 'Draw_Base


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Main_Loop
    DO '                                                        DISPLAY REFRESH LOOP
        DO '                                                    INPUT LOOP
            DIM AS V2 m, inpnt
            DIM inang AS object
            CLS
            _PUTIMAGE , baselayer

            k$ = INKEY$ '                                       get keypresses- set in% when valid
            IF k$ <> "" THEN
                IF k$ = CHR$(27) THEN SYSTEM '                  exit program
            END IF
            ms% = MBS '                                         get mouse clicks
            m.x = PMAP(_MOUSEX, 2)
            m.y = PMAP(_MOUSEY, 3)
            Tracer m
            IF ms% AND 1 THEN '                                 if left click
                Clear_MB 1
                _AUTODISPLAY
                FOR g% = 1 TO 24
                    IF _HYPOT(ang(g%).x - m.x, ang(g%).y - m.y) < 5 THEN
                        m.x = ang(g%).x: m.y = ang(g%).y
                        _PUTIMAGE , baselayer
                        Tracer m
                        Details ang(g%), 0
                    END IF
                NEXT g%
                IF ABS(_MOUSEX - 50) < 50 THEN
                    IF ABS(_MOUSEY - 136) < 16 THEN '           Enter angle code
                        LOCATE 9, 15
                        INPUT ": ", a!
                        _PUTIMAGE , baselayer
                        inang.r = _D2R(a!)
                        inpnt.x = 200 * COS(inang.r)
                        inpnt.y = 200 * SIN(inang.r)
                        Tracer inpnt
                        Details inang, -1
                    END IF
                    IF ABS(_MOUSEY - 184) < 16 THEN '           Enter SIN code
                        LOCATE 12, 15
                        INPUT ": ", s!
                        c! = SQR(1 - s! ^ 2) '                  get COS
                        _PUTIMAGE , baselayer
                        LOCATE 12, 15
                        PRINT s!
                        LOCATE 15, 15
                        PRINT c!
                        inpnt.x = 200 * c!
                        inpnt.y = 200 * s!
                        Tracer inpnt
                        LOCATE 9, 15
                        PRINT _R2D(_ATAN2(inpnt.y, inpnt.x))
                        Press_Click
                    END IF
                    IF ABS(_MOUSEY - 232) < 16 THEN '           Enter COS code
                        LOCATE 15, 15
                        INPUT ": ", c!
                        s! = SQR(1 - c! ^ 2) '                  get SIN
                        _PUTIMAGE , baselayer
                        LOCATE 15, 15
                        PRINT c!
                        LOCATE 12, 15
                        PRINT s!
                        inpnt.x = 200 * c!
                        inpnt.y = 200 * s!
                        Tracer inpnt
                        LOCATE 9, 15
                        PRINT _R2D(_ATAN2(inpnt.y, inpnt.x))
                        Press_Click
                    END IF
                ELSEIF ABS(_MOUSEX - 999) < 50 THEN '           right side buttons
                    IF ABS(_MOUSEY - 136) < 16 THEN
                        Bolt_Circ
                    END IF
                END IF
                in% = -1
            END IF
            'IF ms% AND 2 THEN '                                 if right click
            '    Clear_MB 2
            '    'right click code here
            '    in% = -1
            'END IF
            _DISPLAY
            _LIMIT 30
        LOOP UNTIL in% '                                        END: INPUT LOOP
        'refresh main program displays
        _DISPLAY
    LOOP '                                                      END: DISPLAY REFRESH LOOP
END SUB 'Main_Loop


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Tracer (p AS V2)

    DIM AS V2 ci, tn
    LINE (0, 0)-(p.x, p.y), &H7F7F7F7F '                        mouse leader line
    ci = p: Vec_Norm ci, 200 '                                  circle intersection point
    LINE (ci.x, 0)-(ci.x, ci.y), &HFFFF0000 '                   red sine? line

    IF ABS(ci.x) > 15 AND ABS(ci.y) > 15 THEN '                 if room draw ortho box
        LINE (ci.x - 10 * SGN(ci.x), 0)-(ci.x - 10 * SGN(ci.x), 10 * SGN(ci.y)), &HFFFF0000
        LINE (ci.x - 10 * SGN(ci.x), 10 * SGN(ci.y))-(ci.x, 10 * SGN(ci.y)), &HFFFF0000
    END IF
    LINE (0, ci.y)-(ci.x, ci.y), &HFF0000FF, , 63 '             blue cosine? line
    tn = ci: Vec_Orth tn: Vec_Norm tn, 550 '                    compute tangent line
    LINE (ci.x + tn.x, ci.y + tn.y)-(ci.x - tn.x, ci.y - tn.y), &HFFFF00FF 'purple tangent line

    LINE (0, 0)-(ci.x, ci.y), &HFF00FF00 '                      green radius line

    COLOR &HFFFF0000
    _PRINTSTRING (0, 32), "SIN= " + Fix_Float$((ci.y / _HYPOT(ci.x, ci.y)), 6)
    COLOR &HFF0000FF
    _PRINTSTRING (0, 48), "COS= " + Fix_Float$((ci.x / _HYPOT(ci.x, ci.y)), 6)
    COLOR &HFFFF00FF
    IF ci.x = 0 THEN
        _PRINTSTRING (0, 64), "TAN= undefined"
    ELSE
        _PRINTSTRING (0, 64), "TAN= " + Fix_Float((ci.y / ci.x), 6)
    END IF
    COLOR &HFFFFFFFF
    IF ci.y < 0 THEN
        rad = TwoPI - ABS(_ATAN2(ci.y, ci.x))
    ELSE
        rad = _ATAN2(ci.y, ci.x)
    END IF
    _PRINTSTRING (0, 80), "Radians= " + Fix_Float$(rad, 10)
    _PRINTSTRING (0, 96), "Degrees= " + Fix_Float$(_R2D(rad), 10)

END SUB 'Tracer


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Vec_Norm (var AS V2, scalar AS INTEGER) 'normalize and scale a 2D vector
    mag! = _HYPOT(var.x, var.y)
    IF mag! = 0 THEN
        var.x = 0: var.y = 0
    ELSE
        var.x = (var.x / mag!) * scalar
        var.y = (var.y / mag!) * scalar
    END IF
END SUB 'Vec_Norm


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
SUB Vec_Orth (var AS V2) 'return 2D vector (var) as an orthogonal vector
    x% = var.x: y% = var.y
    var.x = -y%
    var.y = x%
END SUB 'Vec_Orth


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
FUNCTION Fix_Float$ (x##, dec AS INTEGER)

    'fix_float is appropriately named as it needs fixing...
    bs$ = STR$(x##) '                                           string of input number x##
    ex = INSTR(bs$, "D") + INSTR(bs$, "E")
    IF ex <> 0 THEN '                                           an exponential has been thrown
        pwr = VAL(MID$(bs$, ex + 3))
        'use pwr to loop pwr-1 0's after a "." then use left$(bs,1)
        n$ = " ."
        FOR z% = 1 TO pwr - 1
            n$ = n$ + "0"
        NEXT z%
        Fix_Float$ = n$ + LEFT$(_TRIM$(bs$), 1)
    ELSE '                                                      a decimal number has been thrown
        pnt = INSTR(bs$, ".")
        IF pnt = 0 THEN
            Fix_Float$ = bs$
        ELSE
            Fix_Float$ = LEFT$(bs$, pnt + dec)
        END IF
    END IF

END FUNCTION 'Fix_Float##


'²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
'$include: 'omlib.bm'



RE: Unit circle visualizer - OldMoses - 08-13-2022

A few years back, I had a project to do some jig boring of indexing plates on my tired and ancient milling machine. No CNC, no DRO, just a copy of "Machinery's Handbook" a calculator and some manual handcranks with lots of lash in the screws. It was a laborious job, for an old dude that needs reading glasses. So it occurred to me that this little utility could become a real shop tool with the addition of a bolt circle calculator SUB. It wasn't too challenging, all things considered.

Click on the bolt circle button and a no frills dialog opens querying for the number of holes and the radius of the circle. Then a button click on where the user would prefer the reference position to be. The result is a listing of the holes and their X & Y axis positions from the reference datum, in thousandths. That is followed by a chordal measurement between the hole centers to help with determining what size hole can be drilled without weakening the part. A red dashed crosshair marks the reference point on the screen.

The code and zip is updated in the original post.