import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;

public class GameModel {

    private static int sine9[];
    private static int sine11[];
    private static int base64Alphabet[];
    public int numVertices;
    public int projectVertexX[];
    public int projectVertexY[];
    public int projectVertexZ[];
    public int vertexViewX[];
    public int vertexViewY[];
    public int vertexIntensity[];
    public byte vertexAmbience[];
    public int numFaces;
    public int faceNumVertices[];
    public int faceVertices[][];
    public int faceFillFront[];
    public int faceFillBack[];
    public int normalMagnitude[];
    public int normalScale[];
    public int faceIntensity[];
    public int faceNormalX[];
    public int faceNormalY[];
    public int faceNormalZ[];
    public int depth;
    public int transformState;
    public boolean visible;
    public int x1;
    public int x2;
    public int y1;
    public int y2;
    public int z1;
    public int z2;
    public boolean textureTranslucent;
    public boolean transparent;
    public int key;
    public int faceTag[];
    public byte isLocalPlayer[];
    public boolean isolated;
    public boolean unlit;
    public boolean unpickable;
    public boolean projected;
    public int maxVerts;
    public int vertexX[];
    public int vertexY[];
    public int vertexZ[];
    public int vertexTransformedX[];
    public int vertexTransformedY[];
    public int vertexTransformedZ[];
    protected int lightDiffuse;
    protected int lightAmbience;
    private boolean autocommit;
    private int magic;
    private int maxFaces;
    private int faceTransStateThing[][];
    private int faceBoundLeft[];
    private int faceBoundRight[];
    private int faceBoundBottom[];
    private int faceBoundTop[];
    private int faceBoundNear[];
    private int faceBoundFar[];
    private int baseX;
    private int baseY;
    private int baseZ;
    private int orientationYaw;
    private int orientationPitch;
    private int orientationRoll;
    private int scaleFx;
    private int scaleFy;
    private int scaleFz;
    private int shearXy;
    private int shearXz;
    private int shearYx;
    private int shearYz;
    private int shearZx;
    private int shearZy;
    private int transformKind;
    private int diameter;
    private int lightDirectionX;
    private int lightDirectionY;
    private int lightDirectionZ;
    private int lightDirectionMagnitude;
    private int dataPtr;

    static {
        sine9 = new int[512];
        sine11 = new int[2048];

        base64Alphabet = new int[256];
        for (int i = 0; i < 256; i++) {
            sine9[i] = (int) (Math.sin((double) i * 0.02454369D) * 32768D);
            sine9[i + 256] = (int) (Math.cos((double) i * 0.02454369D) * 32768D);
        }

        for (int j = 0; j < 1024; j++) {
            sine11[j] = (int) (Math.sin((double) j * 0.00613592315D) * 32768D);
            sine11[j + 1024] = (int) (Math.cos((double) j * 0.00613592315D) * 32768D);
        }

        for (int j1 = 0; j1 < 10; j1++)
            base64Alphabet[48 + j1] = j1;

        for (int k1 = 0; k1 < 26; k1++)
            base64Alphabet[65 + k1] = k1 + 10;

        for (int l1 = 0; l1 < 26; l1++)
            base64Alphabet[97 + l1] = l1 + 36;

        base64Alphabet[163] = 62;
        base64Alphabet[36] = 63;
    }

    public GameModel(int numVertices, int numFaces) {
        transformState = 1;
        visible = true;
        textureTranslucent = false;
        transparent = false;
        key = -1;
        autocommit = false;
        isolated = false;
        unlit = false;
        unpickable = false;
        projected = false;
        magic = World.colourTransparent;
        diameter = World.colourTransparent;
        lightDirectionX = 180;
        lightDirectionY = 155;
        lightDirectionZ = 95;
        lightDirectionMagnitude = 256;
        lightDiffuse = 512;
        lightAmbience = 32;
        allocate(numVertices, numFaces);
        faceTransStateThing = new int[numFaces][1];
        for (int v = 0; v < numFaces; v++)
            faceTransStateThing[v][0] = v;

    }

    public GameModel(int numVertices, int numFaces, boolean autocommit, boolean isolated, boolean unlit, boolean unpickable, boolean projected) {
        transformState = 1;
        visible = true;
        textureTranslucent = false;
        transparent = false;
        key = -1;
        magic = World.colourTransparent;
        diameter = World.colourTransparent;
        lightDirectionX = 180;
        lightDirectionY = 155;
        lightDirectionZ = 95;
        lightDirectionMagnitude = 256;
        lightDiffuse = 512;
        lightAmbience = 32;
        this.autocommit = autocommit;
        this.isolated = isolated;
        this.unlit = unlit;
        this.unpickable = unpickable;
        this.projected = projected;
        allocate(numVertices, numFaces);
    }

    public GameModel(byte data[], int offset, boolean unused) {
        transformState = 1;
        visible = true;
        textureTranslucent = false;
        transparent = false;
        key = -1;
        autocommit = false;
        isolated = false;
        unlit = false;
        unpickable = false;
        projected = false;
        magic = World.colourTransparent;
        diameter = World.colourTransparent;
        lightDirectionX = 180;
        lightDirectionY = 155;
        lightDirectionZ = 95;
        lightDirectionMagnitude = 256;
        lightDiffuse = 512;
        lightAmbience = 32;
        int j = Utility.getUnsignedShort(data, offset);
        offset += 2;
        int k = Utility.getUnsignedShort(data, offset);
        offset += 2;
        allocate(j, k);
        faceTransStateThing = new int[k][1];
        for (int l = 0; l < j; l++) {
            vertexX[l] = Utility.getSignedShort(data, offset);
            offset += 2;
        }

        for (int i1 = 0; i1 < j; i1++) {
            vertexY[i1] = Utility.getSignedShort(data, offset);
            offset += 2;
        }

        for (int j1 = 0; j1 < j; j1++) {
            vertexZ[j1] = Utility.getSignedShort(data, offset);
            offset += 2;
        }

        numVertices = j;
        for (int k1 = 0; k1 < k; k1++)
            faceNumVertices[k1] = data[offset++] & 0xff;

        for (int l1 = 0; l1 < k; l1++) {
            faceFillFront[l1] = Utility.getSignedShort(data, offset);
            offset += 2;
            if (faceFillFront[l1] == 32767)
                faceFillFront[l1] = magic;
        }

        for (int i2 = 0; i2 < k; i2++) {
            faceFillBack[i2] = Utility.getSignedShort(data, offset);
            offset += 2;
            if (faceFillBack[i2] == 32767)
                faceFillBack[i2] = magic;
        }

        for (int j2 = 0; j2 < k; j2++) {
            int k2 = data[offset++] & 0xff;
            if (k2 == 0)
                faceIntensity[j2] = 0;
            else
                faceIntensity[j2] = magic;
        }

        for (int l2 = 0; l2 < k; l2++) {
            faceVertices[l2] = new int[faceNumVertices[l2]];
            for (int i3 = 0; i3 < faceNumVertices[l2]; i3++)
                if (j < 256) {
                    faceVertices[l2][i3] = data[offset++] & 0xff;
                } else {
                    faceVertices[l2][i3] = Utility.getUnsignedShort(data, offset);
                    offset += 2;
                }

        }

        numFaces = k;
        transformState = 1;
    }

    public GameModel(String name) {
        transformState = 1;
        visible = true;
        textureTranslucent = false;
        transparent = false;
        key = -1;
        autocommit = false;
        isolated = false;
        unlit = false;
        unpickable = false;
        projected = false;
        magic = World.colourTransparent;
        diameter = World.colourTransparent;
        lightDirectionX = 180;
        lightDirectionY = 155;
        lightDirectionZ = 95;
        lightDirectionMagnitude = 256;
        lightDiffuse = 512;
        lightAmbience = 32;
        byte data[];
        try {
            InputStream inputstream = Utility.openFile(name);
            DataInputStream datainputstream = new DataInputStream(inputstream);
            data = new byte[3];
            dataPtr = 0;
            for (int i = 0; i < 3; i += datainputstream.read(data, i, 3 - i)) ;
            int sz = readBase64(data);
            data = new byte[sz];
            dataPtr = 0;
            for (int j = 0; j < sz; j += datainputstream.read(data, j, sz - j)) ;
            datainputstream.close();
        } catch (IOException Ex) {
            numVertices = 0;
            numFaces = 0;
            return;
        }
        int nV = readBase64(data);
        int nF = readBase64(data);
        allocate(nV, nF);
        faceTransStateThing = new int[nF][];
        for (int j3 = 0; j3 < nV; j3++) {
            int x = readBase64(data);
            int y = readBase64(data);
            int z = readBase64(data);
            vertexAt(x, y, z);
        }

        for (int k3 = 0; k3 < nF; k3++) {
            int n = readBase64(data);
            int front = readBase64(data);
            int back = readBase64(data);
            int l2 = readBase64(data);
            lightDiffuse = readBase64(data);
            lightAmbience = readBase64(data);
            int gouraud = readBase64(data);
            int vs[] = new int[n];
            for (int i = 0; i < n; i++)
                vs[i] = readBase64(data);

            int ai1[] = new int[l2];
            for (int i4 = 0; i4 < l2; i4++)
                ai1[i4] = readBase64(data);

            int j4 = createFace(n, vs, front, back);
            faceTransStateThing[k3] = ai1;
            if (gouraud == 0)
                faceIntensity[j4] = 0;
            else
                faceIntensity[j4] = magic;
        }

        transformState = 1;
    }

    public GameModel(GameModel pieces[], int count, boolean autocommit, boolean isolated, boolean unlit, boolean unpickable) {
        transformState = 1;
        visible = true;
        textureTranslucent = false;
        transparent = false;
        key = -1;
        projected = false;
        magic = World.colourTransparent;
        diameter = World.colourTransparent;
        lightDirectionX = 180;
        lightDirectionY = 155;
        lightDirectionZ = 95;
        lightDirectionMagnitude = 256;
        lightDiffuse = 512;
        lightAmbience = 32;
        this.autocommit = autocommit;
        this.isolated = isolated;
        this.unlit = unlit;
        this.unpickable = unpickable;
        merge(pieces, count, false);
    }

    public GameModel(GameModel pieces[], int count) {
        transformState = 1;
        visible = true;
        textureTranslucent = false;
        transparent = false;
        key = -1;
        autocommit = false;
        isolated = false;
        unlit = false;
        unpickable = false;
        projected = false;
        magic = World.colourTransparent;
        diameter = World.colourTransparent;
        lightDirectionX = 180;
        lightDirectionY = 155;
        lightDirectionZ = 95;
        lightDirectionMagnitude = 256;
        lightDiffuse = 512;
        lightAmbience = 32;
        merge(pieces, count, true);
    }

    private void allocate(int numV, int numF) {
        vertexX = new int[numV];
        vertexY = new int[numV];
        vertexZ = new int[numV];
        vertexIntensity = new int[numV];
        vertexAmbience = new byte[numV];
        faceNumVertices = new int[numF];
        faceVertices = new int[numF][];
        faceFillFront = new int[numF];
        faceFillBack = new int[numF];
        faceIntensity = new int[numF];
        normalScale = new int[numF];
        normalMagnitude = new int[numF];
        if (!projected) {
            projectVertexX = new int[numV];
            projectVertexY = new int[numV];
            projectVertexZ = new int[numV];
            vertexViewX = new int[numV];
            vertexViewY = new int[numV];
        }
        if (!unpickable) {
            isLocalPlayer = new byte[numF];
            faceTag = new int[numF];
        }
        if (autocommit) {
            vertexTransformedX = vertexX;
            vertexTransformedY = vertexY;
            vertexTransformedZ = vertexZ;
        } else {
            vertexTransformedX = new int[numV];
            vertexTransformedY = new int[numV];
            vertexTransformedZ = new int[numV];
        }
        if (!unlit || !isolated) {
            faceNormalX = new int[numF];
            faceNormalY = new int[numF];
            faceNormalZ = new int[numF];
        }
        if (!isolated) {
            faceBoundLeft = new int[numF];
            faceBoundRight = new int[numF];
            faceBoundBottom = new int[numF];
            faceBoundTop = new int[numF];
            faceBoundNear = new int[numF];
            faceBoundFar = new int[numF];
        }
        numFaces = 0;
        numVertices = 0;
        maxVerts = numV;
        maxFaces = numF;
        baseX = baseY = baseZ = 0;
        orientationYaw = orientationPitch = orientationRoll = 0;
        scaleFx = scaleFy = scaleFz = 256;
        shearXy = shearXz = shearYx = shearYz = shearZx = shearZy = 256;
        transformKind = 0;
    }

    public void projectionPrepare() {
        projectVertexX = new int[numVertices];
        projectVertexY = new int[numVertices];
        projectVertexZ = new int[numVertices];
        vertexViewX = new int[numVertices];
        vertexViewY = new int[numVertices];
    }

    public void clear() {
        numFaces = 0;
        numVertices = 0;
    }

    public void reduce(int df, int dz) {
        numFaces -= df;
        if (numFaces < 0)
            numFaces = 0;
        numVertices -= dz;
        if (numVertices < 0)
            numVertices = 0;
    }

    public void merge(GameModel pieces[], int count, boolean transState) {
        int numF = 0;
        int numV = 0;
        for (int i = 0; i < count; i++) {
            numF += pieces[i].numFaces;
            numV += pieces[i].numVertices;
        }

        allocate(numV, numF);
        if (transState)
            faceTransStateThing = new int[numF][];
        for (int i = 0; i < count; i++) {
            GameModel source = pieces[i];
            source.commit();
            lightAmbience = source.lightAmbience;
            lightDiffuse = source.lightDiffuse;
            lightDirectionX = source.lightDirectionX;
            lightDirectionY = source.lightDirectionY;
            lightDirectionZ = source.lightDirectionZ;
            lightDirectionMagnitude = source.lightDirectionMagnitude;
            for (int srcF = 0; srcF < source.numFaces; srcF++) {
                int dstVs[] = new int[source.faceNumVertices[srcF]];
                int srcVs[] = source.faceVertices[srcF];
                for (int v = 0; v < source.faceNumVertices[srcF]; v++)
                    dstVs[v] = vertexAt(source.vertexX[srcVs[v]], source.vertexY[srcVs[v]], source.vertexZ[srcVs[v]]);

                int dstF = createFace(source.faceNumVertices[srcF], dstVs, source.faceFillFront[srcF], source.faceFillBack[srcF]);
                faceIntensity[dstF] = source.faceIntensity[srcF];
                normalScale[dstF] = source.normalScale[srcF];
                normalMagnitude[dstF] = source.normalMagnitude[srcF];
                if (transState)
                    if (count > 1) {
                        faceTransStateThing[dstF] = new int[source.faceTransStateThing[srcF].length + 1];
                        faceTransStateThing[dstF][0] = i;
                        for (int i2 = 0; i2 < source.faceTransStateThing[srcF].length; i2++)
                            faceTransStateThing[dstF][i2 + 1] = source.faceTransStateThing[srcF][i2];

                    } else {
                        faceTransStateThing[dstF] = new int[source.faceTransStateThing[srcF].length];
                        for (int j2 = 0; j2 < source.faceTransStateThing[srcF].length; j2++)
                            faceTransStateThing[dstF][j2] = source.faceTransStateThing[srcF][j2];

                    }
            }

        }

        transformState = 1;
    }

    public int vertexAt(int x, int y, int z) {
        for (int l = 0; l < numVertices; l++)
            if (vertexX[l] == x && vertexY[l] == y && vertexZ[l] == z)
                return l;

        if (numVertices >= maxVerts) {
            return -1;
        } else {
            vertexX[numVertices] = x;
            vertexY[numVertices] = y;
            vertexZ[numVertices] = z;
            return numVertices++;
        }
    }

    public int createVertex(int i, int j, int k) {
        if (numVertices >= maxVerts) {
            return -1;
        } else {
            vertexX[numVertices] = i;
            vertexY[numVertices] = j;
            vertexZ[numVertices] = k;
            return numVertices++;
        }
    }

    public int createFace(int n, int vs[], int front, int back) {
        if (numFaces >= maxFaces) {
            return -1;
        } else {
            faceNumVertices[numFaces] = n;
            faceVertices[numFaces] = vs;
            faceFillFront[numFaces] = front;
            faceFillBack[numFaces] = back;
            transformState = 1;
            return numFaces++;
        }
    }

    public GameModel[] split(int unused1, int unused2, int pieceDx, int pieceDz, int rows, int count, int pieceMaxVertices, boolean pickable) {
        commit();
        int pieceNV[] = new int[count];
        int pieceNF[] = new int[count];
        for (int i = 0; i < count; i++) {
            pieceNV[i] = 0;
            pieceNF[i] = 0;
        }

        for (int f = 0; f < numFaces; f++) {
            int sumX = 0;
            int sumZ = 0;
            int n = faceNumVertices[f];
            int vs[] = faceVertices[f];
            for (int i = 0; i < n; i++) {
                sumX += vertexX[vs[i]];
                sumZ += vertexZ[vs[i]];
            }

            int p = sumX / (n * pieceDx) + (sumZ / (n * pieceDz)) * rows;
            pieceNV[p] += n;
            pieceNF[p]++;
        }

        GameModel pieces[] = new GameModel[count];
        for (int i = 0; i < count; i++) {
            if (pieceNV[i] > pieceMaxVertices)
                pieceNV[i] = pieceMaxVertices;
            pieces[i] = new GameModel(pieceNV[i], pieceNF[i], true, true, true, pickable, true);
            pieces[i].lightDiffuse = lightDiffuse;
            pieces[i].lightAmbience = lightAmbience;
        }

        for (int f = 0; f < numFaces; f++) {
            int sumX = 0;
            int sumZ = 0;
            int n = faceNumVertices[f];
            int vs[] = faceVertices[f];
            for (int i = 0; i < n; i++) {
                sumX += vertexX[vs[i]];
                sumZ += vertexZ[vs[i]];
            }

            int p = sumX / (n * pieceDx) + (sumZ / (n * pieceDz)) * rows;
            copyLighting(pieces[p], vs, n, f);
        }

        for (int p = 0; p < count; p++)
            pieces[p].projectionPrepare();

        return pieces;
    }

    public void copyLighting(GameModel model, int srcVs[], int nV, int inF) {
        int dstVs[] = new int[nV];
        for (int inV = 0; inV < nV; inV++) {
            int outV = dstVs[inV] = model.vertexAt(vertexX[srcVs[inV]], vertexY[srcVs[inV]], vertexZ[srcVs[inV]]);
            model.vertexIntensity[outV] = vertexIntensity[srcVs[inV]];
            model.vertexAmbience[outV] = vertexAmbience[srcVs[inV]];
        }

        int outF = model.createFace(nV, dstVs, faceFillFront[inF], faceFillBack[inF]);
        if (!model.unpickable && !unpickable)
            model.faceTag[outF] = faceTag[inF];
        model.faceIntensity[outF] = faceIntensity[inF];
        model.normalScale[outF] = normalScale[inF];
        model.normalMagnitude[outF] = normalMagnitude[inF];
    }

    public void setLight(boolean gouraud, int ambient, int diffuse, int x, int y, int z) {
        lightAmbience = 256 - ambient * 4;
        lightDiffuse = (64 - diffuse) * 16 + 128;
        if (unlit)
            return;
        for (int i = 0; i < numFaces; i++)
            if (gouraud)
                faceIntensity[i] = magic;
            else
                faceIntensity[i] = 0;

        lightDirectionX = x;
        lightDirectionY = y;
        lightDirectionZ = z;
        lightDirectionMagnitude = (int) Math.sqrt(x * x + y * y + z * z);
        light();
    }

    public void setLight(int ambience, int diffuse, int x, int y, int z) {
        lightAmbience = 256 - ambience * 4;
        lightDiffuse = (64 - diffuse) * 16 + 128;
        if (!unlit) {
            lightDirectionX = x;
            lightDirectionY = y;
            lightDirectionZ = z;
            lightDirectionMagnitude = (int) Math.sqrt(x * x + y * y + z * z);
            light();
        }
    }

    public void setLight(int x, int y, int z) {
        if (!unlit) {
            lightDirectionX = x;
            lightDirectionY = y;
            lightDirectionZ = z;
            lightDirectionMagnitude = (int) Math.sqrt(x * x + y * y + z * z);
            light();
        }
    }

    public void setVertexAmbience(int v, int ambience) {
        vertexAmbience[v] = (byte) ambience;
    }

    public void rotate(int yaw, int pitch, int roll) {
        orientationYaw = orientationYaw + yaw & 0xff;
        orientationPitch = orientationPitch + pitch & 0xff;
        orientationRoll = orientationRoll + roll & 0xff;
        determineTransformKind();
        transformState = 1;
    }

    public void orient(int yaw, int pitch, int roll) {
        orientationYaw = yaw & 0xff;
        orientationPitch = pitch & 0xff;
        orientationRoll = roll & 0xff;
        determineTransformKind();
        transformState = 1;
    }

    public void translate(int x, int y, int z) {
        baseX += x;
        baseY += y;
        baseZ += z;
        determineTransformKind();
        transformState = 1;
    }

    public void place(int x, int y, int z) {
        baseX = x;
        baseY = y;
        baseZ = z;
        determineTransformKind();
        transformState = 1;
    }

    private void determineTransformKind() {
        if (shearXy != 256 || shearXz != 256 || shearYx != 256 || shearYz != 256 || shearZx != 256 || shearZy != 256) {
            transformKind = 4;
        } else if (scaleFx != 256 || scaleFy != 256 || scaleFz != 256) {
            transformKind = 3;
        } else if (orientationYaw != 0 || orientationPitch != 0 || orientationRoll != 0) {
            transformKind = 2;
        } else if (baseX != 0 || baseY != 0 || baseZ != 0) {
            transformKind = 1;
        } else {
            transformKind = 0;
        }
    }

    private void applyTranslate(int dx, int dy, int dz) {
        for (int v = 0; v < numVertices; v++) {
            vertexTransformedX[v] += dx;
            vertexTransformedY[v] += dy;
            vertexTransformedZ[v] += dz;
        }

    }

    private void applyRotation(int yaw, int roll, int pitch) {
        for (int v = 0; v < numVertices; v++) {
            if (pitch != 0) {
                int sin = sine9[pitch];
                int cos = sine9[pitch + 256];
                int x = vertexTransformedY[v] * sin + vertexTransformedX[v] * cos >> 15;
                vertexTransformedY[v] = vertexTransformedY[v] * cos - vertexTransformedX[v] * sin >> 15;
                vertexTransformedX[v] = x;
            }
            if (yaw != 0) {
                int sin = sine9[yaw];
                int cos = sine9[yaw + 256];
                int y = vertexTransformedY[v] * cos - vertexTransformedZ[v] * sin >> 15;
                vertexTransformedZ[v] = vertexTransformedY[v] * sin + vertexTransformedZ[v] * cos >> 15;
                vertexTransformedY[v] = y;
            }
            if (roll != 0) {
                int sin = sine9[roll];
                int cos = sine9[roll + 256];
                int x = vertexTransformedZ[v] * sin + vertexTransformedX[v] * cos >> 15;
                vertexTransformedZ[v] = vertexTransformedZ[v] * cos - vertexTransformedX[v] * sin >> 15;
                vertexTransformedX[v] = x;
            }
        }

    }

    private void applyShear(int xy, int xz, int yx, int yz, int zx, int zy) {
        for (int idx = 0; idx < numVertices; idx++) {
            if (xy != 0)
                vertexTransformedX[idx] += vertexTransformedY[idx] * xy >> 8;
            if (xz != 0)
                vertexTransformedZ[idx] += vertexTransformedY[idx] * xz >> 8;
            if (yx != 0)
                vertexTransformedX[idx] += vertexTransformedZ[idx] * yx >> 8;
            if (yz != 0)
                vertexTransformedY[idx] += vertexTransformedZ[idx] * yz >> 8;
            if (zx != 0)
                vertexTransformedZ[idx] += vertexTransformedX[idx] * zx >> 8;
            if (zy != 0)
                vertexTransformedY[idx] += vertexTransformedX[idx] * zy >> 8;
        }

    }

    private void applyScale(int fx, int fy, int fz) {
        for (int v = 0; v < numVertices; v++) {
            vertexTransformedX[v] = vertexTransformedX[v] * fx >> 8;
            vertexTransformedY[v] = vertexTransformedY[v] * fy >> 8;
            vertexTransformedZ[v] = vertexTransformedZ[v] * fz >> 8;
        }

    }

    private void computeBounds() {
        x1 = y1 = z1 = 0xf423f;
        diameter = x2 = y2 = z2 = 0xfff0bdc1;
        for (int face = 0; face < numFaces; face++) {
            int vs[] = faceVertices[face];
            int v = vs[0];
            int n = faceNumVertices[face];
            int x1;
            int x2 = x1 = vertexTransformedX[v];
            int y1;
            int y2 = y1 = vertexTransformedY[v];
            int z1;
            int z2 = z1 = vertexTransformedZ[v];
            for (int i = 0; i < n; i++) {
                v = vs[i];
                if (vertexTransformedX[v] < x1)
                    x1 = vertexTransformedX[v];
                else if (vertexTransformedX[v] > x2)
                    x2 = vertexTransformedX[v];
                if (vertexTransformedY[v] < y1)
                    y1 = vertexTransformedY[v];
                else if (vertexTransformedY[v] > y2)
                    y2 = vertexTransformedY[v];
                if (vertexTransformedZ[v] < z1)
                    z1 = vertexTransformedZ[v];
                else if (vertexTransformedZ[v] > z2)
                    z2 = vertexTransformedZ[v];
            }

            if (!isolated) {
                faceBoundLeft[face] = x1;
                faceBoundRight[face] = x2;
                faceBoundBottom[face] = y1;
                faceBoundTop[face] = y2;
                faceBoundNear[face] = z1;
                faceBoundFar[face] = z2;
            }
            if (x2 - x1 > diameter)
                diameter = x2 - x1;
            if (y2 - y1 > diameter)
                diameter = y2 - y1;
            if (z2 - z1 > diameter)
                diameter = z2 - z1;
            if (x1 < this.x1)
                this.x1 = x1;
            if (x2 > this.x2)
                this.x2 = x2;
            if (y1 < this.y1)
                this.y1 = y1;
            if (y2 > this.y2)
                this.y2 = y2;
            if (z1 < this.z1)
                this.z1 = z1;
            if (z2 > this.z2)
                this.z2 = z2;
        }

    }

    public void light() {
        if (unlit)
            return;
        int divisor = lightDiffuse * lightDirectionMagnitude >> 8;
        for (int face = 0; face < numFaces; face++)
            if (faceIntensity[face] != magic)
                faceIntensity[face] = (faceNormalX[face] * lightDirectionX + faceNormalY[face] * lightDirectionY + faceNormalZ[face] * lightDirectionZ) / divisor;

        int normalX[] = new int[numVertices];
        int normalY[] = new int[numVertices];
        int normalZ[] = new int[numVertices];
        int normalMagnitude[] = new int[numVertices];
        for (int k = 0; k < numVertices; k++) {
            normalX[k] = 0;
            normalY[k] = 0;
            normalZ[k] = 0;
            normalMagnitude[k] = 0;
        }

        for (int face = 0; face < numFaces; face++)
            if (faceIntensity[face] == magic) {
                for (int v = 0; v < faceNumVertices[face]; v++) {
                    int k1 = faceVertices[face][v];
                    normalX[k1] += faceNormalX[face];
                    normalY[k1] += faceNormalY[face];
                    normalZ[k1] += faceNormalZ[face];
                    normalMagnitude[k1]++;
                }

            }

        for (int v = 0; v < numVertices; v++)
            if (normalMagnitude[v] > 0)
                vertexIntensity[v] = (normalX[v] * lightDirectionX + normalY[v] * lightDirectionY + normalZ[v] * lightDirectionZ) / (divisor * normalMagnitude[v]);

    }

    public void relight() {
        if (unlit && isolated)
            return;
        for (int face = 0; face < numFaces; face++) {
            int verts[] = faceVertices[face];
            int aX = vertexTransformedX[verts[0]];
            int aY = vertexTransformedY[verts[0]];
            int aZ = vertexTransformedZ[verts[0]];
            int bX = vertexTransformedX[verts[1]] - aX;
            int bY = vertexTransformedY[verts[1]] - aY;
            int bZ = vertexTransformedZ[verts[1]] - aZ;
            int cX = vertexTransformedX[verts[2]] - aX;
            int cY = vertexTransformedY[verts[2]] - aY;
            int cZ = vertexTransformedZ[verts[2]] - aZ;
            int normX = bY * cZ - cY * bZ;
            int normY = bZ * cX - cZ * bX;
            int normZ;
            for (normZ = bX * cY - cX * bY; normX > 8192 || normY > 8192 || normZ > 8192 || normX < -8192 || normY < -8192 || normZ < -8192; normZ >>= 1) {
                normX >>= 1;
                normY >>= 1;
            }

            int normMag = (int) (256D * Math.sqrt(normX * normX + normY * normY + normZ * normZ));
            if (normMag <= 0)
                normMag = 1;
            faceNormalX[face] = (normX * 0x10000) / normMag;
            faceNormalY[face] = (normY * 0x10000) / normMag;
            faceNormalZ[face] = (normZ * 65535) / normMag;
            normalScale[face] = -1;
        }

        light();
    }

    public void apply() {
        if (transformState == 2) {
            transformState = 0;
            for (int v = 0; v < numVertices; v++) {
                vertexTransformedX[v] = vertexX[v];
                vertexTransformedY[v] = vertexY[v];
                vertexTransformedZ[v] = vertexZ[v];
            }

            x1 = y1 = z1 = 0xff676981;
            diameter = x2 = y2 = z2 = 0x98967f;
            return;
        }
        if (transformState == 1) {
            transformState = 0;
            for (int v = 0; v < numVertices; v++) {
                vertexTransformedX[v] = vertexX[v];
                vertexTransformedY[v] = vertexY[v];
                vertexTransformedZ[v] = vertexZ[v];
            }

            if (transformKind >= 2)
                applyRotation(orientationYaw, orientationPitch, orientationRoll);
            if (transformKind >= 3)
                applyScale(scaleFx, scaleFy, scaleFz);
            if (transformKind >= 4)
                applyShear(shearXy, shearXz, shearYx, shearYz, shearZx, shearZy);
            if (transformKind >= 1)
                applyTranslate(baseX, baseY, baseZ);
            computeBounds();
            relight();
        }
    }

    public void project(int cameraX, int cameraY, int cameraZ, int cameraPitch, int cameraRoll, int cameraYaw, int viewDist, int clipNear) {
        apply();
        if (z1 > Scene.frustumNearZ || z2 < Scene.furstumFarZ || x1 > Scene.frustumMinX || x2 < Scene.frustumMaxX || y1 > Scene.furstumMinY || y2 < Scene.furstumMaxY) {
            visible = false;
            return;
        }
        visible = true;
        int yawSin = 0;
        int yawCos = 0;
        int pitchSin = 0;
        int pitchCos = 0;
        int rollSin = 0;
        int rollCos = 0;
        if (cameraYaw != 0) {
            yawSin = sine11[cameraYaw];
            yawCos = sine11[cameraYaw + 1024];
        }
        if (cameraRoll != 0) {
            rollSin = sine11[cameraRoll];
            rollCos = sine11[cameraRoll + 1024];
        }
        if (cameraPitch != 0) {
            pitchSin = sine11[cameraPitch];
            pitchCos = sine11[cameraPitch + 1024];
        }
        for (int v = 0; v < numVertices; v++) {
            int x = vertexTransformedX[v] - cameraX;
            int y = vertexTransformedY[v] - cameraY;
            int z = vertexTransformedZ[v] - cameraZ;
            if (cameraYaw != 0) {
                int X = y * yawSin + x * yawCos >> 15;
                y = y * yawCos - x * yawSin >> 15;
                x = X;
            }
            if (cameraRoll != 0) {
                int X = z * rollSin + x * rollCos >> 15;
                z = z * rollCos - x * rollSin >> 15;
                x = X;
            }
            if (cameraPitch != 0) {
                int Y = y * pitchCos - z * pitchSin >> 15;
                z = y * pitchSin + z * pitchCos >> 15;
                y = Y;
            }
            if (z >= clipNear)
                vertexViewX[v] = (x << viewDist) / z;
            else
                vertexViewX[v] = x << viewDist;
            if (z >= clipNear)
                vertexViewY[v] = (y << viewDist) / z;
            else
                vertexViewY[v] = y << viewDist;
            projectVertexX[v] = x;
            projectVertexY[v] = y;
            projectVertexZ[v] = z;
        }

    }

    public void commit() {
        apply();
        for (int i = 0; i < numVertices; i++) {
            vertexX[i] = vertexTransformedX[i];
            vertexY[i] = vertexTransformedY[i];
            vertexZ[i] = vertexTransformedZ[i];
        }

        baseX = baseY = baseZ = 0;
        orientationYaw = orientationPitch = orientationRoll = 0;
        scaleFx = scaleFy = scaleFz = 256;
        shearXy = shearXz = shearYx = shearYz = shearZx = shearZy = 256;
        transformKind = 0;
    }

    public GameModel copy() {
        GameModel pieces[] = new GameModel[1];
        pieces[0] = this;
        GameModel gameModel = new GameModel(pieces, 1);
        gameModel.depth = depth;
        gameModel.transparent = transparent;
        return gameModel;
    }

    public GameModel copy(boolean autocommit, boolean isolated, boolean unlit, boolean pickable) {
        GameModel pieces[] = new GameModel[1];
        pieces[0] = this;
        GameModel gameModel = new GameModel(pieces, 1, autocommit, isolated, unlit, pickable);
        gameModel.depth = depth;
        return gameModel;
    }

    public void copyPosition(GameModel model) {
        orientationYaw = model.orientationYaw;
        orientationPitch = model.orientationPitch;
        orientationRoll = model.orientationRoll;
        baseX = model.baseX;
        baseY = model.baseY;
        baseZ = model.baseZ;
        determineTransformKind();
        transformState = 1;
    }

    public int readBase64(byte buff[]) {
        for (; buff[dataPtr] == 10 || buff[dataPtr] == 13; dataPtr++) ;
        int hi = base64Alphabet[buff[dataPtr++] & 0xff];
        int mid = base64Alphabet[buff[dataPtr++] & 0xff];
        int lo = base64Alphabet[buff[dataPtr++] & 0xff];
        int val = (hi * 4096 + mid * 64 + lo) - 0x20000;
        if (val == 0x1e240)
            val = magic;
        return val;
    }
}
