home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-386-Vol-2of3.iso / t / triq.zip / TRIANGLE.ASM < prev    next >
Assembly Source File  |  1991-09-10  |  30KB  |  1,295 lines

  1.     page    ,132
  2.  
  3. ;     (C) Copyright Microsoft Corp. 1991.  All rights reserved.
  4. ;
  5. ;     You have a royalty-free right to use, modify, reproduce and 
  6. ;     distribute the Sample Files (and/or any modified version) in 
  7. ;     any way you find useful, provided that you agree that 
  8. ;     Microsoft has no warranty obligations or liability for any 
  9. ;     Sample Application Files which are modified. 
  10.  
  11.     .xlist
  12.     include    cmacros.inc
  13.     include windows.inc
  14.     .list
  15.  
  16.     externA    __WinFlags        ; in KERNEL
  17.     externA __AHSHIFT
  18.     externA __AHINCR
  19.  
  20. LONG    struc
  21. lo    dw    ?
  22. hi    dw    ?
  23. LONG    ends
  24.  
  25. FARPOINTER      struc
  26. off     dw      ?
  27. sel     dw      ?
  28. FARPOINTER    ends
  29.  
  30. ; DDA structure
  31. DDASTRUC    struc
  32. pixCur        dw    ?        ; current X position
  33. pixXinc        dw    ?        ; amount to add to X (-1, 0, or 1)
  34. pixSlope    dw    ?        ; BOOL: non-zero if DX <= DY
  35. pixError    dw    ?        ; current amount of error
  36. pixErrReset    dw    ?        ; amount to add to update error
  37. pixRem        dw    ?        ; amount to add to reset error
  38. DDASTRUC    ends
  39.  
  40.  
  41. smov    macro    segreg1,segreg2
  42.     push    segreg2
  43.     pop    segreg1
  44.     endm
  45.  
  46. jmps    macro    there
  47.     jmp    short there
  48.     endm
  49.  
  50. wptr    equ    word ptr
  51. bptr    equ    byte ptr
  52.  
  53. sBegin  DATA
  54. sEnd    DATA
  55.  
  56. sBegin  CODE
  57.  
  58. assumes CS,CODE
  59. assumes DS,DATA
  60.  
  61.     externFP PatBlt
  62.  
  63. ;
  64. ; Draw a triangle using GID's PatBlit.
  65. ;
  66. ; This code was the source for the DIBTriangle and is left here
  67. ; as an example.
  68. ;
  69. ; There is a lot of duplication between the routines, but that is
  70. ; because they are meant to be stand-alond examples
  71. ;
  72. cProc    Triangle,<FAR,PUBLIC>,<si,di>
  73.     parmW    hDC
  74.     parmD    lpPoints
  75.  
  76.     localD  dummy
  77.  
  78.     localW    wNextScan
  79.     localW    nScans            ; # scans in first part of triangle
  80.     localW    curY            ; current scan
  81.     localW    pLineToChange        ; address of DDA struct that will
  82.                     ; be updated for the second part of
  83.                                         ;  triangle
  84.  
  85.     localV    pts, %(3*(size POINT))    ; local (sorted) copy of points
  86.     localV    lineL, %(size DDASTRUC)    ; DDA info for left line
  87.     localV    lineR, %(size DDASTRUC)    ; DDA info for right line
  88. cBegin
  89.  
  90. ;---------------------------------------------------------------------------
  91. ; Sort the points by increasing Y order.  Points are assumed to be in device
  92. ; coordinates by now.
  93. ;---------------------------------------------------------------------------
  94.     smov    es, ss            ; point es:di to pts buffer 
  95.     lea    di, pts
  96.  
  97.     lds    si, lpPoints        ; point ds:si to input points 
  98.     
  99.     lodsw                ; get first point in cx, dx
  100.     xchg    cx, ax            ;
  101.     lodsw                ;
  102.     xchg    dx, ax            ; cx = X1  dx = Y1
  103.  
  104.     lodsw                ; get second point in bx, ax
  105.     xchg    bx, ax            ;
  106.     lodsw                ; bx = X2  ax = Y2
  107.     xchg    ax, bx            ; ax = X2  bx = Y2
  108.  
  109.     cmp    bx, dx            ; sort so that (ax, bx) <= (cx, dx)
  110.     jle    @F            ; if Y1 <= Y2 then we're fine
  111.     xchg    ax, cx            ; swap Y1 and Y2
  112.     xchg    bx, dx            ; swap X1 and X2
  113. @@:
  114.     cmp    bx, [si+2]        ; is (ax, bx) min coordinate?
  115.     jle    @F
  116.  
  117.     movsw                ; copy smallest point to buffer
  118.     movsw                ;
  119.     jmps    trisort10        ; copy remainder to buffer
  120.  
  121. @@:
  122.     stosw                ; copy smallest point to buffer
  123.     xchg    ax, bx            ;
  124.     stosw                ;
  125.  
  126.     lodsw                ; get third point in (ax, bx)
  127.     xchg    ax, bx
  128.     lodsw
  129.     xchg    ax, bx            ; ax = X3  bx = Y3
  130.  
  131.     cmp    bx, dx            ; sort so that (ax, bx) <= (cx, dx)
  132.     jle    trisort10        ; if Y1 <= Y2 then we're fine
  133.     xchg    ax, cx            ; swap Y1 and Y2
  134.     xchg    bx, dx            ; swap X1 and X2
  135.  
  136. trisort10:
  137.     stosw                ; copy middle point to buffer
  138.     xchg    ax, bx            ;
  139.     stosw                ;
  140.  
  141.     xchg    ax, cx            ; copy largest point to buffer
  142.     stosw                ;
  143.     xchg    ax, dx            ;
  144.     stosw                ;
  145.  
  146. EndTriSort:
  147.  
  148. ;---------------------------------------------------------------------------
  149. ; Initialize the DDA structures so that we can fill between lineL and lineR.
  150. ;---------------------------------------------------------------------------
  151.  
  152.     smov    ds, ss            ; point DS to stack
  153.     lea    si, pts            ; point DS:SI to points
  154.  
  155.     lodsw                ; get first two points:
  156.     xchg    bx, ax            ;   (dx,ax) = pt 2, (bx,cx) = pt 1
  157.     lodsw                ;
  158.     xchg    cx, ax            ;
  159.     lodsw                ;
  160.     xchg    dx, ax            ;
  161.     lodsw                ;
  162.  
  163.     mov    curY, cx        ; start at top scan of triangle
  164.  
  165.     lea    si, lineL        ; point SI to left DDA structure
  166.     lea    di, lineR        ; point DI to right DDA structure
  167.     cmp    dx, bx            ; is X2 > X1 ?
  168.     jle    @F            ; no, we chose correctly
  169.     xchg    si, di            ; switch left/right pointers
  170.  
  171. @@:
  172.     mov    pLineToChange, si    ; remember which DDA structure will
  173.                     ; need to be updated at the cut.
  174.  
  175.     call    TriInitDDA        ; fill in left DDA structure
  176.     mov    nScans, ax        ; save # scans to first endpoint
  177.  
  178.     push    si
  179.     lea    si, pts            ; point DS:SI to points
  180.     lodsw                ; get first & third points:
  181.     xchg    bx, ax            ;   (dx,ax) = pt 3, (bx,cx) = pt 1
  182.     lodsw                ;
  183.     xchg    cx, ax            ;
  184.     add    si, size POINT        ; skip middle point
  185.     lodsw                ;
  186.     xchg    dx, ax            ;
  187.     lodsw                ;
  188.     pop    si
  189.  
  190.     xchg    si, di            ; fill in right DDA structure
  191.     call    TriInitDDA        ;
  192.  
  193. ;---------------------------------------------------------------------------
  194. ; Walk the DDAs of the left and right lines, which are pointed to by SI and
  195. ; BX respectively.  For each scan call the routine that plots the scanline.
  196. ;---------------------------------------------------------------------------
  197.  
  198.     xchg    si, di            ; SI = left line, DI = right line
  199.     mov    bx, di            ; BX = right line
  200.  
  201. TriRenderPiece:
  202.     mov    cx, nScans        ; setup loop count
  203.     jcxz    TriNextPiece        ; if current piece is 
  204.  
  205. TriRenderPieceLoop:
  206.     push    cx
  207.     push    bx
  208.     mov    ax, [si].pixCur        ; Tell output routine to fill [ax,dx)
  209.     mov    dx, [bx].pixCur        ;
  210.     mov    cx, curY        ; on scanline cx
  211.     mov    bx, hDC            ; ** pass in hDC for now...
  212.     call    OutScan            ;
  213.     pop    bx
  214.  
  215.     mov    cx, 2            ; two structures to update
  216.  
  217. UpdateDDAs:
  218.     xchg    si, bx            ; alternate between two structures
  219.  
  220.     mov    ax, [si].pixXinc    ; get X increment in a register
  221.     or    ax, ax            ; is line vertical?
  222.     jz    EndOfDDAThing        ; yes, no need to update DDA
  223.  
  224.     cmp    [si].pixSlope, 0    ; is DX > DY?
  225.     jz    DoDDADXMajor        ; yes, handle appropriately
  226.  
  227.     cmp    [si].pixError, 0    ; is the error > 0
  228.     jle    AddToError        ; no, just add to error term
  229.     add    [si].pixCur, ax        ; add X increment to current pixel
  230.     mov    ax, [si].pixErrReset    ; update error term
  231.     add    [si].pixError, ax    ;
  232.     jmps    EndOfDDAThing        ; we're done with DDA now
  233. AddToError:
  234.     mov    ax, [si].pixRem        ; add to error term
  235.     add    [si].pixError, ax    ;
  236.     jmps    EndOfDDAThing        ; we're done with DDA now
  237.  
  238. DoDDADXMajor:
  239.     push    bx            ;
  240.     push    cx            ;
  241.     mov    cx, ax            ; put pixXinc in cx for now
  242.     mov    dx, [si].pixError    ; put error in dx for now
  243.     mov    ax, [si].pixErrReset    ; update error term
  244.     add    dx, ax            ;
  245.     mov    ax, [si].pixCur        ; ax will have pixCur
  246.     add    ax, cx            ; update current position
  247.     mov    bx, [si].pixRem        ; bx will have pixRem
  248. DXMajorLoop:
  249.     cmp    dx, 0            ; is error <= 0?
  250.     jg    DoneDXMajorLoop        ; no, so we're done
  251.     add    ax, cx            ; pixCur += pixXinc
  252.     add    dx, bx            ; pixError += pixRem
  253.     jmps    DXMajorLoop        ; keep going
  254. DoneDXMajorLoop:
  255.     mov    [si].pixError, dx    ; save values back in structure
  256.     mov    [si].pixCur, ax        ;
  257.     pop    cx            ;
  258.     pop    bx            ;
  259. EndOfDDAThing:
  260.     loop    UpdateDDAs
  261.  
  262.     inc    curY            ; move to next scanline
  263.  
  264.     pop    cx            ; restore scan count
  265.     loop    TriRenderPieceLoop    ; fill rest of triangle
  266.  
  267. TriNextPiece:
  268.     cmp    pLineToChange, 0    ; if 0 we're done
  269.     je    TriDone            ;
  270.  
  271.     push    si            ; save left line
  272.     push    bx
  273.  
  274.     lea    si, pts + size POINT    ; point si to second point
  275.     lodsw                ; get second & third points:
  276.     xchg    bx, ax            ;   (dx,ax) = pt 3, (bx,cx) = pt 2
  277.     lodsw                ;
  278.     xchg    cx, ax            ;
  279.     lodsw                ;
  280.     xchg    dx, ax            ;
  281.     lodsw                ;
  282.     
  283.     mov    si, pLineToChange    ; point to DDA that will change
  284.     call    TriInitDDA        ; and initialize for next line
  285.     mov    nScans, ax        ;
  286.     pop    bx            ;
  287.     pop    si            ;
  288.     mov    pLineToChange, 0    ; signal this is last iteration
  289.     jmp    TriRenderPiece        ;
  290.  
  291. TriDone:
  292. cEnd
  293.  
  294. ; Fill in DDA structure pointed to by DS:SI with line from (bx,cx) - (dx, ax)
  295. ; return DY in ax
  296. cProc    TriInitDDA,<NEAR>,<si,di>
  297. cBegin
  298.     ; Compute DX and DY, while preserving X values
  299.     sub    ax, cx        
  300.     xchg    di, ax
  301.     mov    cx, dx
  302.     sub    cx, bx
  303.  
  304.     ; at this point the registers are as follows:
  305.     ; di = DY            cx = DX
  306.     ; dx = iPtCur+1.X    bx = iPtCur.X
  307.  
  308.     ; set the pixXinc
  309.     xor    ax, ax            ; assume zero
  310.     jcxz    @F            ; if it is great
  311.     inc    ax            ; nope, assume it's positive
  312.     or    cx, cx            ; is sign bit set?
  313.     jns    @F            ; no, we chose correctly
  314.     neg    ax            ; flip pixXinc
  315.     neg    cx            ; force DX positive
  316. @@:
  317.  
  318. ; Note: we don't need to check DY since the triangle code always passes in
  319. ;       ax >= cx
  320.  
  321.     mov    [si].pixXinc, ax    ; save X increment in structure
  322.     mov    [si].pixCur, bx        ; save pixCur in structure
  323.     mov    dx, di            ; save DY in dx
  324.  
  325.     ; compute pixSlope (0 if DY > DX, non-zero otherwise)
  326.     xor    ax, ax            ; assume DY > DX
  327.     mov    bx, 1            ; assume error roundup is 1
  328.     cmp    cx, di            ; check if DY is > DX
  329.     jg    @F            ; yup, we picked correctly
  330.     not    ax            ; nope, set pixSlope
  331.     xchg    cx, di            ; swap DX and DY
  332.     or    di, di            ; if minor axis >= 0 then error
  333.     jns    @F            ;    roundup is okay
  334.     xor    bx, bx             ; nope, clear error roundoff
  335. @@:
  336.     mov    [si].pixSlope, ax
  337.  
  338.     shl    di, 1            ; double minor axis length
  339.  
  340.     mov    [si].pixRem, di
  341.     sub    di, cx
  342.     add    di, bx
  343.     mov    [si].pixError, di
  344.     sub    di, bx
  345.     sub    di, cx
  346.     mov    [si].pixErrReset, di
  347.  
  348.     mov    ax, dx            ; return DY
  349. cEnd
  350.  
  351. ; draw line segment [ax,dx) on scanline cx on hDC in bx
  352. cProc    OutScan,<NEAR>,<si,di>
  353. cBegin
  354.     push    bx            ; hDC
  355.     push    ax            ; X
  356.     push    cx            ; Y
  357.     sub    dx, ax
  358.     inc    dx
  359.     push    dx            ; nWidth
  360.     mov    ax, 1
  361.     push    ax            ; nHeight
  362.     mov    ax, 00F0h
  363.     push    ax            ; PATCOPY
  364.     mov    ax, 0021h
  365.     push    ax
  366.     cCall    PatBlt            ; use PatBlt to draw scanline
  367. cEnd
  368.  
  369. ;
  370. ; This triangle code writes directly into the DIB memory
  371. ;
  372. ;
  373. ; WARNING: Known Bug:
  374. ;
  375. ; This code does not clip, and if it is asked to draw outside of image, 
  376. ; it will try and usually cause GP Fault
  377. ;
  378. ; Clip points before calling this routine (or add clipping)
  379. ;
  380. ; adding clipping is left as an excercise for the reader.
  381. ;
  382.  
  383. cProc    DIBTriangle,<FAR,PUBLIC>,<si,di>
  384.     parmD    lpbi
  385.     parmD    lpPoints
  386.     parmW    crBrush    
  387.     
  388.     localW  wcrossflag        ; set to 1 if sel crossing on next scan
  389.         localW  fnOutScan               ; scanline output function
  390.     localW  wNextScan        ; # of bytes to next scan line
  391.     localW    nScans            ; # scans in first part of triangle
  392.     localW    curY            ; current scan
  393.     localW    pLineToChange        ; address of DDA struct that will
  394.                     ; be updated for the second part of
  395.                                         ;  triangle
  396.  
  397.     localV    pts, %(3*(size POINT))    ; local (sorted) copy of points
  398.     localV    lineL, %(size DDASTRUC)    ; DDA info for left line
  399.     localV    lineR, %(size DDASTRUC)    ; DDA info for right line
  400. cBegin
  401.  
  402. ;---------------------------------------------------------------------------
  403. ; Sort the points by increasing Y order.  Points are assumed to be in device
  404. ; coordinates by now.
  405. ;---------------------------------------------------------------------------
  406.     smov    es, ss            ; point es:di to pts buffer 
  407.     lea    di, pts
  408.  
  409.     lds    si, lpPoints        ; point ds:si to input points 
  410.     
  411.     lodsw                ; get first point in cx, dx
  412.     xchg    cx, ax            ;
  413.     lodsw                ;
  414.     xchg    dx, ax            ; cx = X1  dx = Y1
  415.  
  416.     lodsw                ; get second point in bx, ax
  417.     xchg    bx, ax            ;
  418.     lodsw                ; bx = X2  ax = Y2
  419.     xchg    ax, bx            ; ax = X2  bx = Y2
  420.  
  421.     cmp    bx, dx            ; sort so that (ax, bx) <= (cx, dx)
  422.     jle    @F            ; if Y1 <= Y2 then we're fine
  423.     xchg    ax, cx            ; swap Y1 and Y2
  424.     xchg    bx, dx            ; swap X1 and X2
  425. @@:
  426.     cmp    bx, [si+2]        ; is (ax, bx) min coordinate?
  427.     jle    @F
  428.  
  429.     movsw                ; copy smallest point to buffer
  430.     movsw                ;
  431.     jmps    ctrisort10        ; copy remainder to buffer
  432.  
  433. @@:
  434.     stosw                ; copy smallest point to buffer
  435.     xchg    ax, bx            ;
  436.     stosw                ;
  437.  
  438.     lodsw                ; get third point in (ax, bx)
  439.     xchg    ax, bx
  440.     lodsw
  441.     xchg    ax, bx            ; ax = X3  bx = Y3
  442.  
  443.     cmp    bx, dx            ; sort so that (ax, bx) <= (cx, dx)
  444.     jle    ctrisort10        ; if Y1 <= Y2 then we're fine
  445.     xchg    ax, cx            ; swap Y1 and Y2
  446.     xchg    bx, dx            ; swap X1 and X2
  447.  
  448. ctrisort10:
  449.     stosw                ; copy middle point to buffer
  450.     xchg    ax, bx            ;
  451.     stosw                ;
  452.  
  453.     xchg    ax, cx            ; copy largest point to buffer
  454.     stosw                ;
  455.     xchg    ax, dx            ;
  456.     stosw                ;
  457.  
  458. cEndTriSort:
  459.  
  460. ;---------------------------------------------------------------------------
  461. ; Initialize the DDA structures so that we can fill between lineL and lineR.
  462. ;---------------------------------------------------------------------------
  463.  
  464.     smov    ds, ss            ; point DS to stack
  465.     lea    si, pts            ; point DS:SI to points
  466.  
  467.     lodsw                ; get first two points:
  468.     xchg    bx, ax            ;   (dx,ax) = pt 2, (bx,cx) = pt 1
  469.     lodsw                ;
  470.     xchg    cx, ax            ;
  471.     lodsw                ;
  472.     xchg    dx, ax            ;
  473.     lodsw                ;
  474.  
  475.     mov    curY, cx        ; start at top scan of triangle
  476.  
  477.     lea    si, lineL        ; point SI to left DDA structure
  478.     lea    di, lineR        ; point DI to right DDA structure
  479.     cmp    dx, bx            ; is X2 > X1 ?
  480.     jle    @F            ; no, we chose correctly
  481.     xchg    si, di            ; switch left/right pointers
  482.  
  483. @@:
  484.     mov    pLineToChange, si    ; remember which DDA structure will
  485.                     ; need to be updated at the cut.
  486.  
  487.     call    TriInitDDA        ; fill in left DDA structure
  488.     mov    nScans, ax        ; save # scans to first endpoint
  489.  
  490.     push    si
  491.     lea    si, pts            ; point DS:SI to points
  492.     lodsw                ; get first & third points:
  493.     xchg    bx, ax            ;   (dx,ax) = pt 3, (bx,cx) = pt 1
  494.     lodsw                ;
  495.     xchg    cx, ax            ;
  496.     add    si, size POINT        ; skip middle point
  497.     lodsw                ;
  498.     xchg    dx, ax            ;
  499.     lodsw                ;
  500.     pop    si
  501.  
  502.     xchg    si, di            ; fill in right DDA structure
  503.     call    TriInitDDA        ;
  504.  
  505. ;---------------------------------------------------------------------------
  506. ; Walk the DDAs of the left and right lines, which are pointed to by SI and
  507. ; BX respectively.  For each scan call the routine that plots the scanline.
  508. ;---------------------------------------------------------------------------
  509.  
  510.     xchg    si, di            ; SI = left line, DI = right line
  511.     mov    bx, di            ; BX = right line
  512.  
  513.     les    di,lpbi
  514.     mov    ax,curY         ; get y value of first scan line
  515.     call    tri_init
  516.  
  517. ;;;    mov    ax,lpBuf.sel        ; get segment of buffer
  518. ;;;    mov    es,ax            ; into ES
  519. ;;;    mov    di,lpBuf.off        ; lpbuf in ES:DI
  520.  
  521. cTriRenderPiece:
  522.     mov    cx, nScans        ; setup loop count
  523.     jcxz    cTriNextPiece        ; if current piece is 
  524.  
  525. cTriRenderPieceLoop:
  526.     push    cx
  527.  
  528.     mov    ax, [si].pixCur        ; Tell output routine to fill [ax,dx)
  529.     mov    dx, [bx].pixCur        ;
  530. ;;;;;;;;mov    cx, curY        ; on scanline cx
  531.  
  532.     cmp     ax, dx            ; Make sure AX <= DX
  533.     jle     @f
  534.     xchg    ax, dx            ; swap point and wall pointers if not
  535.     xchg    bx, si            ;
  536. @@:
  537. ;;;;;;;;arg    lpBuf
  538. ;;;;;;;;arg    <cx>
  539. ;;;;;;;;arg    <ax>    ;[bx].pixcur
  540. ;;;;;;;;arg    <dx>    ;[si].pixcur
  541. ;;;;;;;;arg    crBrush
  542.     push    bx
  543.     call    [fnOutScan]
  544.     pop    bx
  545. ;;;;;;;;    cCall    DIBOutScan
  546.  
  547.     mov    cx, 2            ; two structures to update
  548.  
  549. cUpdateDDAs:
  550.     xchg    si, bx            ; alternate between two structures
  551.  
  552.     mov    ax, [si].pixXinc    ; get X increment in a register
  553.     or    ax, ax            ; is line vertical?
  554.     jz    cEndOfDDAThing        ; yes, no need to update DDA
  555.  
  556.     cmp    [si].pixSlope, 0    ; is DX > DY?
  557.     jz    cDoDDADXMajor        ; yes, handle appropriately
  558.  
  559.     cmp    [si].pixError, 0    ; is the error > 0
  560.     jle    cAddToError        ; no, just add to error term
  561.     add    [si].pixCur, ax        ; add X increment to current pixel
  562.     mov    ax, [si].pixErrReset    ; update error term
  563.     add    [si].pixError, ax    ;
  564.     jmps    cEndOfDDAThing        ; we're done with DDA now
  565. cAddToError:
  566.     mov    ax, [si].pixRem        ; add to error term
  567.     add    [si].pixError, ax    ;
  568.     jmps    cEndOfDDAThing        ; we're done with DDA now
  569.  
  570. cDoDDADXMajor:
  571.     push    bx            ;
  572.     push    cx            ;
  573.     mov    cx, ax            ; put pixXinc in cx for now
  574.     mov    dx, [si].pixError    ; put error in dx for now
  575.     mov    ax, [si].pixErrReset    ; update error term
  576.     add    dx, ax            ;
  577.     mov    ax, [si].pixCur        ; ax will have pixCur
  578.     add    ax, cx            ; update current position
  579.     mov    bx, [si].pixRem        ; bx will have pixRem
  580. cDXMajorLoop:
  581.     cmp    dx, 0            ; is error <= 0?
  582.     jg    cDoneDXMajorLoop        ; no, so we're done
  583.     add    ax, cx            ; pixCur += pixXinc
  584.     add    dx, bx            ; pixError += pixRem
  585.     jmps    cDXMajorLoop        ; keep going
  586. cDoneDXMajorLoop:
  587.     mov    [si].pixError, dx    ; save values back in structure
  588.     mov    [si].pixCur, ax        ;
  589.     pop    cx            ;
  590.     pop    bx            ;
  591. cEndOfDDAThing:
  592.     loop    cUpdateDDAs
  593.  
  594.     inc    curY            ; move to next scanline
  595.  
  596.     pop    cx            ; restore scan count
  597.     loop    cTriRenderPieceLoop    ; fill rest of triangle
  598.  
  599. cTriNextPiece:
  600.     cmp    pLineToChange, 0    ; if 0 we're done
  601.     je    cTriDone            ;
  602.  
  603.     push    si            ; save left line
  604.     push    bx
  605.  
  606.     lea    si, pts + size POINT    ; point si to second point
  607.     lodsw                ; get second & third points:
  608.     xchg    bx, ax            ;   (dx,ax) = pt 3, (bx,cx) = pt 2
  609.     lodsw                ;
  610.     xchg    cx, ax            ;
  611.     lodsw                ;
  612.     xchg    dx, ax            ;
  613.     lodsw                ;
  614.     
  615.     mov    si, pLineToChange    ; point to DDA that will change
  616.     call    TriInitDDA        ; and initialize for next line
  617.     mov    nScans, ax        ;
  618.     pop    bx            ;
  619.     pop    si            ;
  620.     mov    pLineToChange, 0    ; signal this is last iteration
  621.     jmp    cTriRenderPiece        ;
  622.  
  623. cTriDone:
  624. cEnd
  625.  
  626. if 1
  627. ;--------------------------Private-Routine-------------------------------;
  628. ; tri_init
  629. ;
  630. ;    init the bitmap pointer  ES:DI to point to proper scan
  631. ;
  632. ; Entry:
  633. ;    ES:DI --> BITMAPINFO
  634. ;    AX    -   start scanline
  635. ;       SS:BP --> frame of DibTriangle
  636. ; Return:
  637. ;       ES:DI points to start of first scan
  638. ;       carry clear
  639. ;       SS:BP  --> frame of DIBTriangle
  640. ; Error Returns:
  641. ;       carry set
  642. ; Registers Preserved:
  643. ;       none
  644. ; Registers Destroyed:
  645. ;       AX,BX,CX,DX,DS,ES,SI,DI,FLAGS
  646. ; Calls:
  647. ;       exclude  !!!
  648. ; History:
  649. ;       Mon 26-Mar-1990 -by-  Todd Laney [ToddLa]
  650. ;       Stole it from KenSy
  651. ;-----------------------------------------------------------------------;
  652.         assumes ds,nothing
  653.         assumes es,nothing
  654. ifdef DEBUG
  655. public tri_init
  656. endif
  657.  
  658. tri_init proc near
  659.     xor    dx,dx
  660.     mov    wcrossflag,dx
  661.  
  662.     mov    dx,wptr es:[di].biWidth     ; compute DWORD aligned
  663.     add    dx,3                ; ...scan width and save it
  664.     and    dx,not 3
  665.     mov    wNextScan,dx
  666.  
  667.     sub    ax,wptr es:[di].biHeight    ; Y = biHeight-1-Y
  668.     inc    ax
  669.     neg    ax
  670.  
  671.     add    di,wptr es:[di].biSize
  672.     add    di,256 * 4            ; !!!assumes full color table
  673.  
  674.     mul    dx                ; DX:AX = offset start of scan
  675.     add    di,ax
  676.     adc    dx,0                ; DX:DI --> start of scan
  677.  
  678.     mov    cx,__AHSHIFT            ; do the selector tile stuff
  679.     shl    dx,cl
  680.     mov    ax,es
  681.     add    ax,dx
  682.     mov    es,ax                ; ES:DI --> start of scan
  683.  
  684.         mov     ax,CodeOFFSET DIBOutScan
  685.         mov     fnOutScan,ax
  686.  
  687. tri_init_success:
  688.         clc
  689.         ret
  690.  
  691. tri_init_fail:
  692.         stc
  693.         ret
  694.  
  695. tri_init endp
  696.  
  697.  
  698. endif 
  699.  
  700.  
  701. ;--------------------------Private-Routine-------------------------------;
  702. ; DIBOutScan
  703. ;
  704. ;   output a single scanline of a triangle [ax,dx)
  705. ;
  706. ; Entry:
  707. ;       AX    - start X
  708. ;       DX    - end X    (inclusive!)
  709. ;       ES:DI - points to start of scanline
  710. ;       SS:BP - Frame of Custom Triangle
  711. ; Return:
  712. ;       ES:DI advanced to start of next scan
  713. ; Error Returns:
  714. ;       none
  715. ; Registers Preserved:
  716. ;       BX,DS,ES,SI,DI
  717. ; Registers Destroyed:
  718. ;       AX,CX,DX,FLAGS
  719. ; Calls:
  720. ;       none
  721. ; History:
  722. ;       Mon 26-Mar-1990 -by-  Todd Laney [ToddLa]
  723. ;       Stole it from KenSy
  724. ;-----------------------------------------------------------------------;
  725.         assumes ds,nothing
  726.         assumes es,nothing
  727.  
  728. ifdef DEBUG
  729. public diboutscan    ;; for debugging
  730. endif
  731.  
  732. DIBOutScan proc near
  733.     mov    cx,__WinFlags
  734.     test    cx,WF_CPU286
  735.     jnz    DIBOutScan286
  736.     errn$   DIBOutScan386
  737.  
  738. DIBOutScan386:
  739.  
  740. ; 386 specific version goes here.
  741. ; for now, just use 286 version
  742. ;
  743. DIBOutScan286:
  744.         mov     cx,dx
  745.         sub     cx,ax                   ; calc delta
  746.         inc     cx            ; cx is # of pels
  747.  
  748.     cmp    wcrossflag,1
  749.     je    DIBOutCrossing
  750.     mov    dx,di            
  751.     sub    dx,wNextScan        ; advance to next scan
  752.     jb    DIBOutNextCrossing    ; we have a seg crossing on next line
  753. DIBOutSafe:
  754.     
  755.     add    di,ax            ; point ES:DI to start of scan
  756.     mov    al,bptr crBrush     ; get color to store in al and ah
  757.     mov    ah,al
  758.  
  759.     shr    cx,1
  760.     rep    stosw
  761.     adc    cl,cl
  762.     rep    stosb
  763.  
  764.     mov    di,dx        ; advance to next scan
  765.     ret
  766.  
  767. DIBOutNextCrossing:
  768. ;; next scan line has a crossing...
  769.     mov    wcrossflag,1
  770.     mov    dx,di        ; save old (unmodified) di
  771.     ; don't update es:di, yet
  772.     jmps    DIBOutSafe
  773.  
  774.  
  775.  
  776. DIBOutCrossing:
  777.  
  778. ;; there is a crossing somewhere in the current scanline.
  779.     mov    wcrossflag,0        ; we've handled it...
  780.  
  781.     mov    cx,dx
  782. ;; ax is start
  783. ;; cx is end
  784. ;; di is old offset
  785.  
  786. ;;;
  787.     mov    dx,di
  788.     mov    di,wNextScan
  789.     sub    di,dx            ; di = wNextScan - old offset
  790.  
  791.     sub    dx,wNextScan
  792.  
  793.  
  794.  
  795. ;;; still need to check for the old 'off-by-one' errors
  796.     
  797.     ;; di is wNextScan - old offset
  798.     ;; dx is new offset
  799.     ; es is old sel
  800.     ; es - __AHINCR is new sel
  801.     
  802.     ;     V-------------------wNextScan-------------------V
  803.     ;
  804.     ;     V--wNextScan - old_offset----V----old_offset----V
  805.     ;
  806.     ;
  807.     ;        |-----------------------------------------------|
  808.     ;
  809.     ; current scan line    es-__AHINCR:dx-->
  810.     ;        |----------------------------|------------------|
  811.     ;                              es:0---^
  812.     ; 
  813.     ;        |-----------------------------------------------|
  814.     ;
  815.     ;
  816.     ; Three posibilities:
  817.     ;
  818.     ;   1    |----------------------------|------------------|
  819.     ;                ^                        ^                  
  820.     ;                Start                    End
  821.     ;
  822.     ;   2    |----------------------------|------------------|
  823.     ;                                       ^          ^                  
  824.     ;                                       Start      End
  825.     ;
  826.     ;   3    |----------------------------|------------------|
  827.     ;                ^                ^                  
  828.     ;                Start            End
  829.     ;
  830.  
  831.     cmp    ax,di            ; is start > di?
  832.     jg    DIBOutCase2
  833.     cmp    cx,di            ; is end <= di?
  834.     jle    DIBOutCase3
  835. ; else:
  836. ; case 1
  837.     ; do from es:0 -> end - di first
  838.     sub    cx,di
  839.     mov    di,0            ; by definition, start of seg
  840.  
  841.     push    ax            ; save start
  842.     mov    al,bptr crBrush
  843.     mov    ah,al
  844.     shr    cx,1
  845.     rep    stosw
  846.     adc    cl,cl
  847.     rep    stosb
  848.  
  849.     
  850.     ; now do from es-__AHINCR:dx+start -> ffff
  851.     mov    ax,es
  852.     sub    ax,__AHINCR
  853.     cmp    ax,lpbi.sel        ; compare to buffer
  854.     jl    DIBOutFirstpop        ; don't mod if before buffer
  855.     mov    es,ax
  856.  
  857.     pop    ax            ; restore start
  858.  
  859.     mov    di,dx
  860.     add    di,ax            ; es:di ->start
  861.  
  862.     mov    cx,dx            ; get # to move:
  863.     add    cx,ax            ;     add start offset to line
  864.     not    cx            ;     get # to end
  865.  
  866.     inc    cx            ; fill in last byte
  867.     
  868.     ; move the data...
  869.     mov    al,bptr crBrush
  870.     mov    ah,al
  871.     shr    cx,1
  872.     rep    stosw
  873.     adc    cl,cl
  874.     rep    stosb
  875.  
  876.     mov    di,dx            ; setup next line...
  877.     sub    di,wNextScan
  878.  
  879.     ret
  880.  
  881.  
  882. DIBOutCase3:    
  883.     mov    di,dx            ; we won't cross... do it
  884.     mov    dx,es
  885.     sub    dx,__AHINCR
  886.     cmp    dx,lpbi.sel        ; compare to buffer
  887.     jl    DIBOutFirst        ; don't mod if before buffer
  888.     mov    es,dx
  889.  
  890.     mov    dx,di
  891.     sub    dx,wNextScan        ; setup next scan line
  892.                     ; (can't have another seg crossing)
  893.  
  894.     sub    cx,ax            ; get count
  895.     inc    cx
  896.  
  897.     jmp    DIBOutSafe
  898.  
  899. DIBOutCase2:
  900. ; we won't cross, but special calcs are needed to get next sel
  901.  
  902.     sub    cx,ax
  903.     inc    cx            ; cx is # of pels
  904.  
  905.     sub    ax,di            ; start - offset
  906.     mov    di,ax            ; point ES:DI to start of scan
  907.  
  908.     mov    al,bptr crBrush     ; get color to store in al and ah
  909.     mov    ah,al
  910.  
  911.     shr    cx,1
  912.     rep    stosw
  913.     adc    cl,cl
  914.     rep    stosb
  915.  
  916. ; fall through...
  917.     
  918. DIBOutScanWrap:
  919.     sub    dx,wNextScan        ; point to next scan line
  920.     mov    di,dx
  921.     mov    ax,es
  922.     sub    ax,__AHINCR
  923.     cmp    ax,lpbi.sel        ; compare to buffer
  924.     jl    DIBOutFirst        ; don't mod if before buffer
  925.     mov    es,ax
  926. DIBOutFirst:
  927.     ret
  928. DIBOutFirstpop:
  929.     pop    ax
  930.     ret
  931.  
  932. DIBOutScan endp
  933.  
  934.  
  935. ;---------------------------Public-Routine------------------------------;
  936. ; hmemcpy
  937. ;
  938. ;   copy memory
  939. ;
  940. ; Entry:
  941. ;    lpSrc    HPSTR to copy from
  942. ;    lpDst    HPSTR to copy to
  943. ;    cbMem    DWORD count of bytes to move
  944. ;
  945. ;    NOTE: overlapped copies will work iff lpSrc.sel == lpDst.sel
  946. ;        [This is a lie.     They will always work.]
  947. ;
  948. ; Returns:
  949. ;    destination pointer
  950. ; Error Returns:
  951. ;    None
  952. ; Registers Preserved:
  953. ;    BP,DS,SI,DI
  954. ; Registers Destroyed:
  955. ;    AX,BX,CX,DX,FLAGS
  956. ; Calls:
  957. ;    nothing
  958. ;-----------------------------------------------------------------------;
  959.  
  960. cProc hmemcpy,<FAR,PASCAL,PUBLIC,NODATA>,<>
  961. ;     ParmD     lpDst
  962. ;     ParmD     lpSrc
  963. ;     ParmD     cbMem
  964. cBegin    <nogen>
  965.     mov    ax,__WinFlags
  966.     test    ax,WF_CPU286
  967.     jz    fmemcpy386
  968.     jmp    FAR PTR    fmemcpy286
  969. cEnd <nogen>
  970.  
  971. cProc fmemcpy386,<FAR,PASCAL,PUBLIC,NODATA>,<ds>
  972.     ParmD    lpDst
  973.     ParmD    lpSrc
  974.     ParmD    cbMem
  975. cBegin
  976.     .386
  977.     push    edi
  978.     push    esi
  979.     cld
  980.  
  981.     mov    ecx,cbMem
  982.     jecxz    mc386_exit
  983.  
  984.     movzx    edi,di
  985.     movzx    esi,si
  986.     lds    si,lpSrc
  987.     les    di,lpDst
  988. ;
  989. ; calculate differance of pointers in "selector" space
  990. ;
  991.     mov    ax,si        ; DX:AX = lpSrc
  992.     mov    dx,ds
  993.  
  994.     mov    bx,es        ; BX = selector of ptr B
  995.  
  996.     mov    cx,__AHSHIFT    ; number of selector bits per 64K 'segment'
  997.     shr    dx,cl        ; linearize ptr A
  998.     shr    bx,cl        ; linearize ptr B
  999. ;
  1000. ; DX and BX contain normalized selectors
  1001. ;
  1002.     sub    ax,di
  1003.     sbb    dx,bx        ; do long subtraction.
  1004.  
  1005.     mov    ecx,cbMem
  1006.  
  1007.     or    dx,dx
  1008.     jns    mc_copy_forward
  1009.  
  1010.     std
  1011.     add    edi,ecx
  1012.     add    esi,ecx
  1013.  
  1014.     sub    edi,4
  1015.     sub    esi,4
  1016.  
  1017.     push    ecx
  1018.     shr    ecx,2        ; get count in DWORDs
  1019.     rep    movs dword ptr es:[edi], dword ptr ds:[esi]
  1020.     db    67H        ; Fix strange 386 bug
  1021.     add    edi,3
  1022.     add    esi,3
  1023.     pop    ecx
  1024.     and    ecx,3
  1025.     rep    movs byte ptr es:[edi], byte ptr ds:[esi]
  1026.     db    67H        ; Fix strange 386 bug
  1027.     jmp    mc386_exit
  1028.  
  1029. mc_copy_forward:
  1030.     push    ecx
  1031.     shr    ecx,2        ; get count in DWORDs
  1032.     rep    movs dword ptr es:[edi], dword ptr ds:[esi]
  1033.     db    67H
  1034.     pop    ecx
  1035.     and    ecx,3
  1036.     rep    movs byte ptr es:[edi], byte ptr ds:[esi]
  1037.     db    67H
  1038.     nop
  1039. mc386_exit:
  1040.     cld
  1041.     pop    esi
  1042.     pop    edi
  1043.     mov    dx,lpDst.sel    ; return destination address
  1044.     mov    ax,lpDst.off
  1045.     .286
  1046.  
  1047. ; return to .286 mode for cEnd and 286 version
  1048. ;
  1049.  
  1050. cEnd
  1051.  
  1052. cProc fmemcpy286,<FAR,PASCAL,PUBLIC,NODATA>,<ds,si,di>
  1053.     ParmD    lpDst
  1054.     ParmD    lpSrc
  1055.     ParmD    cbMem
  1056. cBegin
  1057.     mov    cx,cbMem.lo    ; CX holds count
  1058.     or    cx,cbMem.hi    ; or with high word
  1059.     jnz    @f
  1060.     jmp    empty_copy
  1061. @@:
  1062.     lds    si,lpSrc      ; DS:SI = src
  1063.     les    di,lpDst      ; ES:DI = dst
  1064. ;
  1065. ; calculate differance of pointers in "selector" space
  1066. ;
  1067.     mov    ax,si        ; DX:AX = lpSrc
  1068.     mov    dx,ds
  1069.  
  1070.     mov    bx,es        ; BX = selector of ptr B
  1071.  
  1072.     mov    cx,__AHSHIFT    ; number of selector bits per 64K 'segment'
  1073.     shr    dx,cl        ; linearize ptr A
  1074.     shr    bx,cl        ; linearize ptr B
  1075. ;
  1076. ; DX and BX contain normalized selectors
  1077. ;
  1078.     sub    ax,di
  1079.     sbb    dx,bx        ; do long subtraction.
  1080.  
  1081.     mov    cx,cbMem.lo
  1082.  
  1083.     or    dx,dx
  1084.     jns    forward_copy    ; difference is positive, so copy forward
  1085.  
  1086. ; see if the blocks intersect: is source + count > dest?
  1087. ; equivalently, is source-dest + count > 0 ?
  1088. ;    sub    ax,cx
  1089. ;    sbb    dx,0
  1090. ;    jnc    next        ; This looks wrong.  Recheck!
  1091.  
  1092.     add    ax,cx
  1093.     adc    dx,cbMem.hi
  1094.     jc    reverse_copy    ; carry, so >0, thus they do hit.
  1095.  
  1096. forward_copy:
  1097.     jmp    next
  1098.     
  1099. reverse_copy:
  1100. ; first, we have to set ds:si and es:di to the _ends_ of the blocks
  1101.  
  1102.     sub    cx,2
  1103.     sbb    cbMem.hi,0    ; subtract 2 from (long) count
  1104.     
  1105.     xor    ax,ax        
  1106.     add    si,cx
  1107.     adc    ax,cbMem.hi
  1108.  
  1109.     push    cx
  1110.     mov    cx,__AHSHIFT
  1111.     shl    ax,cl
  1112.     pop    cx
  1113.     mov    bx,ds
  1114.     add    ax,bx        ; advance DS
  1115.     mov    ds,ax
  1116.  
  1117.     xor    ax,ax
  1118.     add    di,cx
  1119.     adc    ax,cbMem.hi
  1120.  
  1121.     push    cx
  1122.     mov    cx,__AHSHIFT
  1123.     shl    ax,cl
  1124.     pop    cx
  1125.     mov    bx,es
  1126.     add    ax,bx        ; advance ES
  1127.     mov    es,ax
  1128.  
  1129.     add    cx,2
  1130.     adc    cbMem.hi,0    ; restore count
  1131. ;
  1132. ;    DS:SI += Count
  1133. ;    ES:DI += Count
  1134. ;    While Count != 0 Do
  1135. ;        Num = MIN(Count,SI+1,DI+1)
  1136. ;        Reverse Copy "Num" Bytes from DS:SI to ES:DI
  1137. ;            (SI -= Num, DI -= Num)
  1138. ;        Count -= Num
  1139. ;        If Count == 0 Then
  1140. ;            BREAK
  1141. ;        If SI == 0xFFFF Then
  1142. ;            DS -= __AHINCR
  1143. ;        If DI == 0xFFFF Then
  1144. ;            ES -= __AHINCR
  1145. ;
  1146. next_r:
  1147.     mov    ax,si
  1148.  
  1149.     sub    ax,di
  1150.     sbb    bx,bx
  1151.     and    ax,bx
  1152.     add    ax,di        ; AX = MIN(SI, DI)
  1153.     
  1154.     xor    bx,bx
  1155.     add    ax,2        ; AX = Num = MIN(SI+2,DI+2)
  1156.     adc    bx,0        ; bx==1 if exactly 64k
  1157.  
  1158.     test    cbMem.hi,-1    ; is high word not zero?
  1159.     jnz    @f        ; at least 64k to go
  1160.  
  1161.     sub    ax,cx
  1162.     sbb    bx,bx
  1163.     and    ax,bx
  1164.     add    ax,cx        ; AX = Num = MIN(Count,SI+2,DI+2)
  1165.     adc    bx,0
  1166.  
  1167. @@:
  1168.     xchg    ax,cx
  1169.     sub    ax,cx        ; Count -= Num
  1170.     sbb    cbMem.hi,bx
  1171.  
  1172.     std
  1173.     shr    bx,1
  1174.     rcr    cx,1        ; if bx==1, then cx ends up 0x8000
  1175.     jnc    @f
  1176.     inc    si        ; adjust pointers for byte move
  1177.     inc    di
  1178.     movsb            ; move first byte, if necessary
  1179.     dec    si        ; realign pointers
  1180.     dec    di
  1181. @@:
  1182.     rep    movsw
  1183.     cld
  1184.  
  1185.     mov    cx,ax        ; restore cx
  1186.     or    ax,cbMem.hi
  1187.  
  1188.     jz    done        ; If Count == 0 Then BREAK
  1189.  
  1190.     cmp    si,-2        ; if SI wraps, update DS
  1191.     jnz    @f        
  1192. ;
  1193.     mov    ax,ds
  1194.     sub    ax,__AHINCR
  1195.     mov    ds,ax        ; update DS if appropriate
  1196. @@:
  1197.     cmp    di,-2        ; if DI wraps, update ES
  1198.     jnz    next_r
  1199. ;
  1200.     mov    ax,es
  1201.     sub    ax,__AHINCR
  1202.     mov    es,ax        ; update ES if appropriate
  1203.     jmp    next_r
  1204.  
  1205. ;
  1206. ;    While Count != 0 Do
  1207. ;        If (Count + SI > 65536) OR (Count + DI > 65536) Then
  1208. ;            Num = Min(65536-SI, 65536-DI)
  1209. ;        Else
  1210. ;            Num = Count
  1211. ;        Copy "Num" Bytes from DS:SI to ES:DI (SI += Num, DI += Num)
  1212. ;        Count -= Num
  1213. ;        If Count == 0 Then
  1214. ;            BREAK
  1215. ;        If SI == 0 Then
  1216. ;            DS += __AHINCR
  1217. ;        If DI == 0 Then
  1218. ;            ES += __AHINCR
  1219. ;
  1220. next:
  1221.     mov    ax,cx
  1222.     dec    ax
  1223.  
  1224.     mov    ax,di
  1225.     not    ax        ; AX = 65535-DI
  1226.  
  1227.     mov    dx,si
  1228.     not    dx        ; DX = 65535-SI
  1229.  
  1230.     sub    ax,dx
  1231.     sbb    bx,bx
  1232.     and    ax,bx
  1233.     add    ax,dx        ; AX = MIN(AX,DX) = MIN(65535-SI,65535-DI)
  1234.  
  1235.     ; problem: ax might have wrapped to zero
  1236.  
  1237.     test    cbMem.hi,-1
  1238.     jnz    plentytogo    ; at least 64k still to copy
  1239.     
  1240.     dec    cx        ; this is ok, since high word is zero
  1241.     sub    ax,cx
  1242.     sbb    bx,bx
  1243.     and    ax,bx
  1244.     add    ax,cx        ; AX = MIN(AX,CX)
  1245.     inc    cx
  1246.  
  1247. plentytogo:
  1248.     xor    bx,bx
  1249.     add    ax,1        ; AX = Num = MIN(count,65536-SI,65536-DI)
  1250.                 ; we must check the carry here!
  1251.     adc    bx,0        ; BX could be 1 here, if CX==0 indicating
  1252.                 ; exactly 64k to copy
  1253.     xchg    ax,cx
  1254.     sub    ax,cx        ; Count -= Num
  1255.     sbb    cbMem.hi,bx
  1256.  
  1257.     shr    bx,1
  1258.     rcr    cx,1        ; if bx==1, then cx ends up 0x8000
  1259.     rep    movsw
  1260.     jnc    @f
  1261.     movsb            ; move last byte, if necessary
  1262. @@:
  1263.     mov    cx,ax        ; put low word of count back in cx
  1264.     or    ax,cbMem.hi
  1265.  
  1266.     jz    done        ; If Count == 0 Then BREAK
  1267.  
  1268.     or    si,si        ; if SI wraps, update DS
  1269.     jnz    @f
  1270. ;
  1271.     mov    ax,ds
  1272.     add    ax,__AHINCR
  1273.     mov    ds,ax        ; update DS if appropriate
  1274. @@:
  1275.     or    di,di        ; if DI wraps, update ES
  1276.     jnz    next
  1277. ;
  1278.     mov    ax,es
  1279.     add    ax,__AHINCR
  1280.     mov    es,ax        ; update ES if appropriate
  1281.     jmp    next
  1282. ;
  1283. ; Restore registers and return
  1284. ;
  1285. done:
  1286. empty_copy:
  1287.     mov    dx,lpDst.sel    ; return destination address
  1288.     mov    ax,lpDst.off
  1289. cEnd
  1290.  
  1291.  
  1292.  
  1293. sEnd    CODE
  1294. end
  1295.