Lesson 7
Trig Functions

AutoLISP includes only three trigonometric functions:

These sin and cos functions accept radians, and the atan function returns radians.  The following diagram relates radians to degrees.

Circle of degrees and radians

To convert radians to degrees, multiply radians by 57.29578 or 180/π.
To convert degrees to radians, multiply degrees by 0.017453293 or π/180.

Sine and cosine

The sin and cos functions each take a single argument, which is an angle in radians.  See radian in the Glossary.  For example, given the fact that π/6 = 30°, π/4 = 45°, π/3 = 60°, and 2π/3 = 120°

    (sin (/ pi 6))  returns 0.5
    (sin (/ pi 4))  returns 0.707107
    (sin (/ pi 3))  returns 0.866025

    (cos (/ pi 6))  returns 0.866025
    (cos (/ pi 4))  returns 0.707107
    (cos (/ pi 3))  returns 0.5
    (cos (* 2 (/ pi 3)))  returns -0.5

Defining your own tangent function

Since AutoLISP has no built-in tangent function, you have to define your own, as follows:

    (defun tan (ang) (/ (sin ang) (cos ang)))

When you have a program than needs a tangent function, you can add the above definition to the program file and it will be loaded into memory when the program is loaded and thus be available for the program to use.

Arc tangent

The arc tangent is the opposite of the tangent.  The tangent function accepts an angle (in radians) and returns a ratio -- the ratio of the opposite side to the adjacent side, which is the same as the ratio of the sine to the cosine.  In contrast, the arc tangent function accepts the ratio and returns the corresponding angle (in radians).

The atan function accepts the ratio by accepting either one or two arguments.  When only one argument is used, the atan function returns values in a limited range (from -π/2 to +π/2, that is, from -90° to +90°, or quadrants I and IV).  But when two arguments are used, it can return any angle in a full circle.  (See atan under "2 - Geometry & Trigonometry" in the "Catalog of function definitions".)

When only one argument is used, that argument can be either positive or negative, and the angle that is returned is limited to quadrants I and IV as illustrated below.

    (atan 1.0)  returns 0.785398  (the radian equivalent of 45°, in quadrant I)
    (atan -0.2)  returns -0.197396  (the radian equivalent of -11.31°, in quadrant IV)

But when two arguments are used, both arguments can be either positive or negative, which yields four possible combinations of positive and negative arguments, which allows atan to return angles in all four quadrants as illustrated below.

    (atan 2.5 2.5)  returns 0.785398  (angle is in quadrant I)
    (atan 2.5 -3.0)  returns 2.44685  (angle is in quadrant II)
    (atan -4.5 -5.0)  returns -2.40878  (angle is in quadrant III)
    (atan -3.0 2.5)  returns -0.876058  (angle is in quadrant IV)

The atan function divides the first argument by the second to determine the ratio.  It also uses the signs of both numbers to determine the quadrant and thus the size of the angle.  The sign of the angle that is returned is always the same as the sign of the first number.

When atan divides the first argument by the second, it floats the arguments in order to avoid integer division.  Consider the following two examples.

    Correct:    (atan 18 -13)  returns 2.19628 (= 125.84°)
    Incorrect:  (atan (/ 18 -13))  returns -0.785398 (= -45°)

Some beginning AutoLISP programmers are tempted to divide the two numbers before submitting them to the atan function, as shown in the "incorrect" example above.  But the result is incorrect on two accounts:  First, the quotient of positive 18 and negative 13 is negative, and when a single negative argument is submitted to atan, a negative angle is returned.  Second, submitting 18 and -13 to the divide function yields -1 because of integer division, but the atan function promotes both integers to reals and calculates the correct quotient.  Thus, using the "incorrect" approach produces an angle that is incorrect both in magnitude and sign.  When you know the vertical and horizontal vectors, it is better to submit them as two separate arguments to the atan function.

Defining your own arc sine and arc cosine functions

Suppose you are writing a program which knows the sine or cosine of an angle and needs to find the angle.  Your program can use the Pythagorean theorem (c² = a² + b²).  When the hypotenuse is 1 and the two legs of the right triangle represent the sine and the cosine as shown in the diagram above, the pythagorean theorem can be reduced to

      cosine = ±(1 - sine²)½      --or--      sine = ±(1 - cosine²)½

So, if your program knows the sine, it can calculate the cosine, and vice versa.  Then it can submit both sine and cosine to the atan function to yield the angle.  But remember the plus-or-minus sign in the above formulas.  This introduces some ambiguity into your program, which may have to use the atan function twice (once with the positive, once with the negative) for a complete solution.

The above discussion correlates with the nature of the tangent function itself.  Two angles can have the same tangent.  For example, 150° and -30° both have a tangent of -0.57735, and 45° and -135° both have a tangent of 1.00.  (The two angles that have the same tangent are always 180° apart.)  So your program may have to take both angles into account.

Since AutoLISP has no arc sine or arc cosine functions, you can define your own functions as shown below using the formulas discussed above.  These definitions are subject to the same ambiguity described above.

    (defun asin (sine) (atan sine (sqrt (- 1 (* sine sine)))))

    (defun acos (cosine) (atan (sqrt (- 1 (* cosine cosine))) cosine))

Both of the above definitions use the positive root, therefore the asin function only returns angles in quadrants I and IV, and the acos function only returns angles in quadrants I and II.  In each case your program may have to evaluate the other angle (180° larger or smaller) for a complete solution.

Practice session

Use all three built-in trig functions, sin, cos, and atan, with a variety of positive and negative numbers until you are comfortable "thinking" in radians.  Predict the results before you press 'Enter'.

Sample program 1, rightss

The program rightss (which stands for "right-triangle-side-side") solves a right triangle when the two sides are known.  It uses the atan function in line 2 of section 30.  Notice that the output of the atan function, which is in radians, is converted to degrees by multiplying it by 180/π.

If you understand how rightss works, write one called righths (for right-triangle-hypotenuse-side) which solves a right triangle when the hypotenuse and one side are known.
 

------ marker ------ beginning of working program ------ try it out ------

;|  RIGHTSS, short for RIGHT triangle with "Side-Side" known
    Copyright © 1998 Ronald W. Leigh
    Input: two sides of a right triangle
    Output: solution of unknown sides/angles
Variables:
a/b/c  Sides
aa/bb  Angles                           |;

;10====PROGRAM SETUP

(defun c:rightss ()
(setq pi 3.1415926535897932385)

;20====INPUT

(princ "\nEnter two sides (not hypotenuse).")
(initget 7)
(setq a (getreal "\nLength of one side: "))
(initget 7)
(setq b (getreal "\nLength of other side: "))

;30====CALCULATE & REPORT

(setq c (sqrt (+ (* a a) (* b b))))
(setq aa (* (/ 180 pi) (atan a b)))
(setq bb (- 90 aa))
(princ (strcat
  "\n  Hypotenuse is " (rtos c)))
(princ (strcat
  "\n  Angle " (rtos aa 2 4) "° is opposite side " (rtos a 2 4)))
(princ (strcat
  "\n  Angle " (rtos bb 2 4) "° is opposite side " (rtos b 2 4)))

(princ)
)

-------- marker -------- end of working program -------- try it out --------
 

Sample program 2, rightsa

The program rightsa (which stands for "right-triangle-side-angle") solves a right triangle when the one side and one angle are known.  It uses the sin function at the beginning of section 30.  Notice that the angle entered by the user (in degrees) is converted to radians by multiplying it by π/180 before it is submitted to the sin function.

The initget and getkword functions in section 20 are explained in a later lesson.

If you understand how rightsa works, write one called rightha (for right-triangle-hypotenuse-angle) which solves a right triangle when the hypotenuse and one angle are known.
 

------ marker ------ beginning of working program ------ try it out ------

;|  RIGHTSA, short for RIGHT triangle with "Side-Angle" known
    Copyright © 1998 Ronald W. Leigh
    Input: side and angle of a right triangle
    Output: solution of unknown sides/angles
Variables:
a/b/c  Sides
aa/bb  Angles                           |;
ang    Temporary angle
loc    Location of angle

;10====PROGRAM SETUP

(defun c:rightsa ()
(setq pi 3.1415926535897932385)

;20====INPUT

(princ "\nEnter a side (not hypotenuse) and an angle.")
(initget 7)
(setq a (getreal "\nLength of side: "))
(initget 7)
(setq ang (getreal "\nSize of angle in degrees: "))
(initget "Opposite Adjacent")
(setq loc (getkword "\nIs angle Opposite side or Adjacent to side? (O/A): "))
(if (= loc "Opposite")
  (setq aa ang bb (- 90 aa))
  (setq bb ang aa (- 90 bb))
)

;30====CALCULATE & REPORT

(setq c (/ a (sin (* aa (/ pi 180)))))
(setq b (sqrt (- (* c c) (* a a))))
(princ (strcat
  "\n  Hypotenuse is " (rtos c)))
(princ (strcat
  "\n  Angle " (rtos aa 2 4) "° is opposite side " (rtos a 2 4)))
(princ (strcat
  "\n  Angle " (rtos bb 2 4) "° is opposite side " (rtos b 2 4)))

(princ)
)

-------- marker -------- end of working program -------- try it out --------
 

Sample program 3, bctable

Holes are often found in a circular pattern, and if you are using coordinate (X-Y) dimensions, one of the cleanest ways to dimension them is with a table of X and Y dimensions placed somewhere near the hole pattern.  This program is called bctable, which is short for bolt circle table.  It creates a table of coordinate (X-Y) dimensions for equally spaced holes on a bolt circle, and illustrates the use of the sin and cos functions.  Several aspects of this program call for explanation.

Bolt circle and table

The value of pi -- section 10

AutoLISP presets the variable pi to 3.14159 . . . .   However, pi can be accidentally reset to any other value, so there is no guarantee that pi holds its intended value when your program needs it.  So, whenever you write a program that depends on pi, set it at the beginning of the program.  The value used in the program is accurate to 20 significant digits.

Mono spaced text with variable height -- section 10

In order to make the columns of numbers line up properly, the current text style must use a mono spaced font.  The style must also a variable height (set to zero).  A more sophisticated program would check the current text style, and if needed, create a text style that meets these requirements.  But that would make the program more complex than we would like at this stage in the lessons.  So a simple message is displayed telling the user what is needed.
 

Holes numbered -- sections 20 and 40

The holes are numbered starting at the top and moving clockwise.  At the end of section 20 the user indicates whether or not he/she wants hole numbers placed near the holes.  Then in lines 6 - 8 of section 40, the numbers are arbitrarily placed a certain distance outside the circle (15% of the radius).  This is a quick and simple placement, and the numbers will have to be moved any many cases.  To place them more precisely would require additional input regarding the size of the holes and even then individual numbers would often need to be moved because of other geometry or dimensions.  So this simpler procedure is used.  By the way, an easy procedure for moving all numbers in or out a small distance is to pick them all then scale them around the center of the bolt circle.

Repeat statements -- section 40

Section 40 contains nested iteration loops.  In other words, it has one large repeat statement which includes several nested repeat statements.  All the lines in the larger repeat statement are executed once for each hole.  Notice that the repeat function's first argument is variable num.  Also, each time the loop is executed, the program must know which number hole it is working on, and where that hole is located around the bolt circle.  That is why a counter (variable cnt) is initialized at zero just before the repeat statement is entered, and is incremented by one with each iteration (second line inside the loop).

The smaller, nested repeat statements are used to add spaces at the beginning of the dimension numbers so they will line up vertically in the table.

More is explained about iteration in a later lesson.
 

------ marker ------ beginning of working program ------ try it out ------

;|  BCTABLE, short for Bolt Circle TABLE
    Copyright © 1998 Ronald W. Leigh
    Input: Bolt circle center & diameter,
     Number of holes, Angle offset, dimension offset
     Location of table, height of text in table
    Output: A table of X-Y dimensions for each hole
Variables:

ang     Angle for each successive hole
angoff  Angle offset
anginc  Angular increment between holes
bccen   Bolt circle center
bcdia   Bolt circle diameter
cnt     Counter
dimori  Dimension origin
hn      Hole number as string
hnpref  Hole number preference ("Yes" or "No")
llen    Line length
num     Number of holes
rad     Radius
sp      Start point for successive lines & text
txthei  Text height
ul      Upper left corner of table
xadd    X added distance from origin
xdim    X dimension for hole
yadd    Y added distance from origin
ydim    Y dimension for hole                  |;

;10====PROGRAM SETUP

(defun c:bctable (/ ang angoff anginc bccen bcdia cnt dimori
          hn hnpref llen num rad sp txthei ul xadd xdim yadd ydim)
(setq pi 3.1415926535897932385)
(setq obm (getvar "blipmode") oom (getvar "osmode"))
(princ "\n** Current text style should use one of the following fonts,")
(princ "\n** Monotxt or Monospac821BT, with variable (zero) height.")

;20====INPUT

(initget 1)
(setq dimori (getpoint "\nLocate dimensioning origin: "))

(initget 1)
(setq bccen (getpoint "\nLocate center of bolt circle: "))
(setq xadd (- (car bccen) (car dimori)))
(setq yadd (- (cadr bccen) (cadr dimori)))

(initget 7)
(setq bcdia (getreal "\nDiameter of bolt circle: "))
(setq rad (/ bcdia 2))

(initget 7)
(setq num (getint "\nNumber of equally spaced holes: "))
(setq anginc (/ (* 2 pi) num))

(initget 4)
(setq angoff (getreal "\nClockwise angle offset of hole 1 from vert. <0>: "))
(if (null angoff) (setq angoff 0.0))
(setq angoff (* angoff (/ pi 180)))

(initget 1)
(setq ul (getpoint "\nUpper left corner of table: "))

(initget 7)
(setq txthei (getreal "\nText height: "))
(setq sp ul llen (* txthei 28))

(initget "Yes No")
(setq hnpref (getkword "\nPlace hole numbers near holes? (Y/N) <Y>: "))
(if (null hnpref) (setq hnpref "Yes"))

;30====DRAW TABLE HEADER

(setvar "osmode" 0)
(setvar "blipmode" 0)
(setvar "cmdecho" 0)
(command ".line" sp (polar sp 0 llen) "")
(setq sp (polar sp (* pi 1.5) txthei))
(command ".text" "ML" sp txthei 0 " Hole#    X          Y")
(setq sp (polar sp (* pi 1.5) txthei))
(command ".line" sp (polar sp 0 llen) "")

;40====DRAW TABLE ENTRIES & HOLE NUM'S, VERTICAL LINES

(setq cnt 0)
(repeat num
  (setq ang (+ angoff (* cnt anginc)))
  (setq cnt (1+ cnt))
  (setq hn (itoa cnt))
  (if (= hnpref "Yes")
    (command ".text" "M"
      (polar bccen (- (/ pi 2) ang) (* 1.15 rad)) txthei 0 hn))
  (repeat (- 4 (strlen hn)) (setq hn (strcat " " hn)))
  (setq xdim (* rad (sin ang)) ydim (* rad (cos ang)))
  (setq xdim (+ xadd xdim) ydim (+ yadd ydim))
  (setq xdim (rtos xdim 2 4) ydim (rtos ydim 2 4))
  (repeat (- 11 (strlen xdim)) (setq xdim (strcat " " xdim)))
  (repeat (- 11 (strlen ydim)) (setq ydim (strcat " " ydim)))
  (setq sp (polar sp (* pi 1.5) txthei))
  (command ".text" "ML" sp txthei 0 (strcat hn xdim ydim))
  (setq sp (polar sp (* pi 1.5) txthei))
  (command ".line" sp (polar sp 0 llen) "")
)

(command ".line" ul sp "")
(command ".line" (polar ul 0 llen) (polar sp 0 llen) "")

;====RESET

(setvar "blipmode" obm)
(setvar "osmode" oom)
(setvar "cmdecho" 1)
(princ)
)

-------- marker -------- end of working program -------- try it out --------
 

HOME


Copyright © 1988, 1998 Ronald W. Leigh