# Moving and Distorting a Cel

You can move and distort a cel in one of two ways:

- Manipulating a Cel by Changing CCB Values
- Using MapCel to Manipulate a Cel

## Manipulating a Cel by Changing CCB Values

You can move and distort a cel by changing its position fields. The `ccb_XPos` and `ccb_YPos` fields specify the location of the upper-left corner of the cel. These fields are stored as 16.16 numbers-that is, the upper 16 bits are an integer and the lower 16 bits are a fraction. Most of the time, this simply means that you'll shift the desired coordinate up 16 bits before storing it in the CCB. Changing the fields changes the location of the cel the next time the cel is rendered.

The `TargetAction()` function from jsinteractivesound.c moves and scales the UFO cel. The UFO starts out at distance 8000 (in arbitrary units) and flies toward the player until it reaches 200 or less, at which point it vanishes and the player is penalized for letting a UFO get away. As the UFO flies toward the camera, it is scaled appropriately. Meanwhile, the UFO also bounces off the edges of an imaginary rectangle 10 pixels in from the edges of the screen. The speed at which the UFO bounces around is divided by the distance, to reflect the way a real object would behave.

The example below shows a code fragment from jsinteractivesound.c.

Example 1: Moving a cel

``

    void TargetAction(CCB* aTargetCCB)
        /*
            Animate the UFO: If it's still exploding, play the explosion's next frame,
            otherwise display the UFO (after a nominal post-explosion delay).
        */
        {
        static int32 iDeltaX = Convert32_F16(1);
        static int32 iDeltaY = Convert32_F16(3) >> 1;

        int32 iTest;

        if (gDistance > UFO_MAXDISTANCE)
            {
            gDistance = UFO_MAXDISTANCE;
            gVelocity = -gVelocity;
            gMisses++;

            aTargetCCB->ccb_XPos = Convert32_F16(VISIBLE_INSET + randbits(8));
            aTargetCCB->ccb_YPos = Convert32_F16(VISIBLE_INSET + randbits(9));
            }
        else if (gDistance < UFO_MINDISTANCE)
            {
            gDistance = UFO_MINDISTANCE;
            gVelocity = -gVelocity;
            }

        if ( gHoverMode && (gDistance < UFO_HOVER_DISTANCE) )
            {
            gDistance += (gVelocity >> 5);
            aTargetCCB->ccb_YPos += iDeltaY >> 1;
            aTargetCCB->ccb_XPos += iDeltaX >> 1;
            }
        else
            {
            gDistance += gVelocity;
            iTest = PROJECTED(UFO_MINDISTANCE);
            aTargetCCB->ccb_YPos += (iDeltaY >> 7) * iTest;
            aTargetCCB->ccb_XPos += (iDeltaX >> 7) * iTest;

            if (!randbits(8))
                iDeltaX *= -1;

            if (!randbits(8))
                iDeltaY *= -1;
            }

        aTargetCCB->ccb_HDX = PROJECTED(Convert32_F20(1));
        aTargetCCB->ccb_VDY = PROJECTED(Convert32_F16(1));

        iTest = ConvertF16_32(aTargetCCB->ccb_YPos) +
                                                    PROJECTED( aTargetCCB->ccb_Height );
        if (aTargetCCB->ccb_YPos <= Convert32_F16(VISIBLE_INSET))
            {
            aTargetCCB->ccb_YPos = Convert32_F16(VISIBLE_INSET);
            iDeltaY *= -1;
            }
        else if (iTest >= 149)
            {
            aTargetCCB->ccb_YPos -= Convert32_F16(iTest - 149);
            iDeltaY *= -1;
            }

The jsmovecel program actually uses a convenience function for moving a cel, which is then used by `HandleControlPad()`.

Example 2: Using the MoveCCB convenience function.

``

    void MoveCCB( CCB *aCCB, int32 xPos, int32 yPos )
    /*
    Convenience routine to move a cel to the specified int32 coordinates
    */
    {
        aCCB->ccb_XPos = Convert32_F16(xPos);
        aCCB->ccb_YPos = Convert32_F16(yPos);
    }

### Distorting a Cel

In addition to moving a cel, you can stretch and rotate the cel. You can either use the `MapCel()` function, discussed below, or change field values:

- `ccb_HDX` and `ccb_HDY`-control the offset of the upper-right corner from the upper-left corner

- `ccb_VDX` and `ccb_VDY`-control the offset of the lower-left corner from the upper-left corner

- `ccb_HDDX` and `ccb_HDDY`-control the offset of the lower-right corner from the lower-left corner

Together, these six fields allow you to map the cel into any cel projection quadrilateral. You can scale, rotate, and skew cels and achieve a number of other effects. The operating system call `MapCel()`, discussed next, sets up these six fields and the x and y fields properly.

## Using MapCel to Manipulate a Cel

All cels start out as rectangles. Even when the object itself doesn't fill the rectangle, the transparent pixels surrounding the object still exist. Whenever you draw the rectangle to the screen, you can put its four corners wherever you want. The cel engine stretches and skews the image to fit the destination quadrilateral.

Cel mapping gives you access to many effects.

- Scale a cel up or down by specifying a destination rectangle that's larger or smaller than the source image.

- Rotate a cel around a point. By pinching two corners closer together and the other two further apart, you can achieve 3-D "vanishing point" effects.

- Transpose corners to get "bow-tie" effects.

The graphics folio function `MapCel()` takes two parameters:

- A pointer to the cel control block (CCB).

- A pointer to an array of `Point` structures that contains the simple XY locations of the four corners of the destination rectangle. The upper-left corner of the source gets mapped to `Corner[0]`, the upper-right corner to `Corner[1]`, the lower-right corner to `Corner[2]`, and the lower-left corner to `Corner[3]`.

The example below from jsinteractivesound.c uses MapCel to scale a cel.

Example 3: Code fragment using MapCel().

``

        /* While the C button and arrows are pressed distort the cel */
        if ( controlBits & ControlC )
        {
        if ( controlBits & ControlRight )
        {
            ++xDistPt;
        }
        else if ( controlBits & ControlLeft )
        {
            --xDistPt;
        }

        if ( controlBits & ControlUp )
        {
            --yDistPt;
        }
        else if ( controlBits & ControlDown )
        {
            ++yDistPt;
        }

        SetQuad (aQuad, gXPos, gYPos,
                gXPos + gCcb->ccb_Width + xMovePt,
                 gYPos + gCcb->ccb_Height+ yMovePt);

        aQuad[2].pt_X += xDistPt;
        aQuad[2].pt_Y += yDistPt;

        MapCel( gCcb, aQuad );

        goto DONE;
        }