void translate4d(double x, double y, double z, double b[4][4]) {
   int i;
   identity4(b);
   b[0][3] = x;
   b[1][3] = y;
   b[2][3] = z;
   }
void translate4(double a[3], double b[4][4]) {
   int i;
   identity4(b);
   for (i = 0; i < 3; ++i) b[i][3] = a[i];
   }
void SetStandardHelix() {
   int numResidues = 9, type[9] = {8, 8, 8, 8, 8, 8, 8, 8, 8};
   double phi[9], psi[9], head[3], tail[3], diff[3];
   int i, j, k, l, m, n;
   double v0[4], v1[4], v2[4], v3[4], v4[4], pos[4];
   double mainpos[4]={0.0,0.0,0.0,0.0};
   double a[4][4], b[4][4], c[4][4], d[4][4], e[4][4], f[4][4], g[4][4];
   double chainN[9][3];
   double chainCA[9][3];
   double chainC[9][3];
   double center[9][3];
   double axis[3], plane[3], dd, radius;
   #if WRITEHELIXFILE
   FILE *fp;
   const char outfile[34] = "StandardHelix.pdb";
   #endif
   char chainId[2], pc;
   char elementName[2];
   int residueIndex;
   char* atomNamePtr;


   for (i = 0; i < numResidues; ++i) {
      phi[i] = -60.*PI/180.;
      psi[i] = -45.*PI/180.;
      }
   #if WRITEHELIXFILE
   fp = fopen(outfile, "w");
   if (fp == 0) {
      printf("unable to open file %s\n", outfile);
      }
   #endif
   n = 1;
   identity4(a);

//  Standard amino acids have CA at the origin and N at -residue_d2,
//  so translate to put N at the origin. Standard amino acids also
//  have C in the xy plane, but not necessarily carbonyl O.

   for (i = 0; i < numResidues; ++i)
         {
         j = type[i];

         translate4d(residued2[j], 0., 0., b);
         matmult4(a, b, c);
         rotX4(PI-StandardPhi[j], b);
         //   printf("StandardPhi[%d] = %f\n", j, StandardPhi[j]);
         matmult4(c, b, f);              // f is for the amide H

         //  First do NH

         l = 2;
         for (k = 0; k < l; ++k)
         {
             for (m = 0; m < 3; ++m)
                 v0[m] = residueAtomPos[j][k][m];
             v0[3] = 1.;
             if(k == 0) {
                matrix_vector4(c, v0, mainpos);
                for (m = 0; m < 3; ++m) 
                   chainN[i][m] = mainpos[m];
                }
             else  matrix_vector4(f, v0, mainpos);
             #if WRITEHELIXFILE
             fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                     n, residueAtomName[j][k], Protein::Residue::abbreviatedNames[j],
                     i+1, mainpos[0], mainpos[1], mainpos[2], 0., 0.);
             #endif
             ++n;

         }
         rotX4(phi[i], b);
         matmult4(c, b, a);              // a continuing backbone
         rotZ4(StandardAlpha[j], b);
         matmult4(a, b, c);
         rotX4(psi[i], b);
         matmult4(c, b, d);              // d is to help continue backbone
         rotX4(StandardPsi[j], b);      // to bring O to plane of next N and CA
         matmult4(d, b, g);
         rotZ4(-StandardAlpha[j], b);
         matmult4(g, b, e);              // e is for carbonyl oxygen

         //  Now do rest of atoms

         for (k = l; k <  numbAtoms[j]; ++k)
             {
             for (m = 0; m < 3; ++m)
                 v0[m] = residueAtomPos[j][k][m];
             v0[3] = 1.;
             if (k == l+3)
                 matrix_vector4(e, v0, pos);
             else
                 matrix_vector4(a, v0, pos);
	     if(k == 2)
	        for (m = 0; m < 3; ++m)
		   chainCA[i][m] = pos[m];
	     if(k == 4)
		for (m = 0; m < 3; ++m)
		   chainC[i][m] = pos[m];
             #if WRITEHELIXFILE
             fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                     n, residueAtomName[j][k], Protein::Residue::abbreviatedNames[j],
                     i+1, pos[0], pos[1], pos[2], 0., 0.);
             #endif
             ++n;
             }
         // Now build up rest of main chain effect on matrix a

         translate4d(residued3[j], 0., 0., b);
         matmult4(d, b, c);
         rotZ4(StandardBeta[j], b);
         matmult4(c, b, d);
         translate4d(1.325, 0., 0., b);
         matmult4(d, b, c);
         rotX4(PI, b);
         matmult4(c, b, d);
         rotZ4(StandardGamma[j], b);
         matmult4(d, b, a);
         //   printf("Alpha %f  Beta %f  Gamma %f\n", 180*StandardAlpha[j]/PI,
         //      180*StandardBeta[j]/PI, 180*StandardGamma[j]/PI);
         }  
   for (i = 1; i < numResidues-1; ++i) {
      for(m = 0; m < 3; ++m) {
         v0[m] = chainN[i-1][m] - chainN[i][m];
	 v1[m] = chainN[i+1][m] - chainN[i][m];
	 }
      normalize(v0);
      normalize(v1);
      for(m = 0; m < 3; ++m)
         center[i][m] = v0[m] + v1[m];
      normalize(center[i]);
      }
   cross(center[1], center[2], axis);
   normalize(axis);
   cross(axis, center[2], plane);
   dd = dot(plane, chainN[2]) - dot(plane, chainN[1]) ;
   radius = dd/dot(plane,center[1]);
   for(m = 0; m < 3; ++m) {
      tail[m] = chainN[1][m] + radius*center[1][m];
      head[m] = chainN[7][m] + radius*center[7][m];
      diff[m] = (head[m] - tail[m])/6.;
      tail[m] -= diff[m];
      }
   for(m = 0; m < 3; ++m) {
     v0[m] = chainN[0][m] - chainCA[0][m];
     v1[m] = chainC[0][m] - chainCA[0][m];
     }
   normalize(v0);
   normalize(v1);
   dd = dot(v0, v1);
   for(m = 0; m < 3; ++m)
      v1[m] -= dd*v0[m];
   normalize(v1);
   cross(v0, v1, v2);
   normalize(v2);
   saxis[0] = dot(v0, diff);
   saxis[1] = dot(v1, diff);
   saxis[2] = dot(v2, diff);
   stail[0] = dot(v0, tail);
   stail[1] = dot(v1, tail);
   stail[2] = dot(v2, tail);
   cross(saxis, stail, ptail);
   normalize(ptail);
   dtail= sqrt( stail[0]*stail[0] + stail[1]*stail[1] + stail[2]*stail[2] );
   daxis= sqrt( saxis[0]*saxis[0] + saxis[1]*saxis[1] + saxis[2]*saxis[2] );
   for(m = 0; m < 3; ++m)
      ptail[m] = dtail*ptail[m];

/*
   normalize(diff);
   fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
           61, "  C", Protein::Residue::abbreviatedNames[j],
           9, head[0], head[1], head[2], 0., 0.);
   fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
           62, "  O", Protein::Residue::abbreviatedNames[j],
           9, tail[0], tail[1], tail[2], 0., 0.);
   cout << "radius  " << radius << "  dd " << dd << "  plane " << *plane <<
      "  center[0] " << *center[1] << "  tail " << *tail << "\n";

   fprintf(stderr, "axis %g %g %g\n", axis[0], axis[1], axis[2]);
   fprintf(stderr, "diff %g %g %g\n", diff[0], diff[1], diff[2]);
   fprintf(stderr, "head %g %g %g\n", head[0], head[1], head[2]);
   fprintf(stderr, "tail %g %g %g\n", tail[0], tail[1], tail[2]);
   fprintf(stderr, "chainN[7] %g %g %g\n", chainN[7][0], chainN[7][1], chainN[7][2]);
   fprintf(stderr, "chainN[1] %g %g %g\n", chainN[1][0], chainN[1][1], chainN[1][2]);
*/
   #if WRITEHELIXFILE
   fclose(fp);
   #endif
   }
Protein* SetDihedrals (int numResidues, const int type[],
      const char pred[], const double phi[], const double psi[]) {
   int i, j, k, l, m, n;
   double v0[4], v1[4], v2[4], v3[4], v4[4], pos[4];
     double mainpos[4]={0.0,0.0,0.0,0.0};
   double a[4][4], b[4][4], c[4][4], d[4][4], e[4][4], f[4][4], g[4][4];
   #if WRITEPDBFILE
   FILE *fp;
   const char outfile[14] = "StandardHelix.pdb";
   #endif
   Protein* result=new Protein;
   int currentResidueIndex=-1;
   Protein::ResidueCreator proteinCreator(result);
     Protein::SecondaryStructure::StructureType currentStructureType=Protein::SecondaryStructure::NONE;
   char chainId[2], pc;
   char elementName[2];
   int residueIndex;
   char* atomNamePtr;


   if(numResidues <= 0) return result;
   #if WRITEPDBFILE
   fp = fopen(outfile, "w");
   if (fp == 0) {
      printf("unable to open file %s\n", outfile);
      assert (fp != 0);
      }
   #endif
  n = 1;
  identity4(a);

//  Standard amino acids have CA at the origin and N at -residue_d2,
//  so translate to put N at the origin. Standard amino acids also
//  have C in the xy plane, but not necessarily carbonyl O.

    for (i = -1; i < numResidues+1; ++i)
    {
        if(i==-1||i==numResidues)
        {
            const char* residuePdbName;
            if (i == -1)
            {
                j = 0;
                residuePdbName="ACE";
            }
            else
            {
                j = 14;
                residuePdbName="NME";
            }
            Protein::SecondaryStructure::StructureType newStructureType;
            newStructureType=Protein::SecondaryStructure::COIL;
            if(newStructureType!=currentStructureType)
            {
                proteinCreator.newSecondaryStructure(newStructureType);
                currentStructureType=newStructureType;
            }
            proteinCreator.newResidue(residuePdbName,i+1);
            for (k = 0; k <  numbAtoms[j]; ++k)
            {
                for (m = 0; m < 3; ++m)
                    pos[m] = residueAtomPos[j][k][m] + mainpos[m];
                pos[1] += 1;
                pos[0] -= 0.8;
                #if WRITEPDBFILE
                fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                        n, residueAtomName[j][k], residuePdbName,
                        i+1, pos[0], pos[1], pos[2], 0., 0.);
                #endif
                ++n;
                atomNamePtr=residueAtomName[j][k];
                while(isspace(*atomNamePtr))
                    ++atomNamePtr;
                elementName[0]=*atomNamePtr;
                ++atomNamePtr;
                elementName[1]='\0';
                proteinCreator.addAtom(elementName, n-1 ,Position(pos), atomNamePtr);
            }
        }
        else
        {
            j = type[i];
            pc = pred[i];
            Protein::SecondaryStructure::StructureType newStructureType;
            if (pc == 'C')
                newStructureType=Protein::SecondaryStructure::COIL;
            else if (pc == 'H')
                newStructureType=Protein::SecondaryStructure::ALPHA_HELIX;
            else if (pc == 'E')
                newStructureType=Protein::SecondaryStructure::BETA_STRAND;
            else
            {
                printf("Unknown secondary structure type.\n");
                exit(-1);
            }
            if(newStructureType!=currentStructureType)
            {
                proteinCreator.newSecondaryStructure(newStructureType);
                currentStructureType=newStructureType;
            }
            proteinCreator.newResidue(Protein::Residue::abbreviatedNames[j],i+1);

            translate4d(residued2[j], 0., 0., b);
            matmult4(a, b, c);
            rotX4(PI-StandardPhi[j], b);
            //   printf("StandardPhi[%d] = %f\n", j, StandardPhi[j]);
            matmult4(c, b, f);              // f is for the amide H

            //  First do NH

            if(j == 16)
                l = 1; 
            else
                l = 2;
            for (k = 0; k < l; ++k)
            {
                for (m = 0; m < 3; ++m)
                    v0[m] = residueAtomPos[j][k][m];
                v0[3] = 1.;
                if(k == 0) matrix_vector4(c, v0, mainpos);
                else  matrix_vector4(f, v0, mainpos);
                #if WRITEPDBFILE
                fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                        n, residueAtomName[j][k], Protein::Residue::abbreviatedNames[j],
                        i+1, mainpos[0], mainpos[1], mainpos[2], 0., 0.);
                #endif
                ++n;

                atomNamePtr=residueAtomName[j][k];
                while(isspace(*atomNamePtr))
                    ++atomNamePtr;
                elementName[0]=*atomNamePtr;
                ++atomNamePtr;
                elementName[1]='\0';
                proteinCreator.addAtom(elementName, n-1 ,Position(mainpos), atomNamePtr);
                //  printf("%d %4s %4s %4s\n",
                //            n-1, elementName, atomNamePtr, residueAtomName[j][k]);
            }
            rotX4(phi[i], b);
            matmult4(c, b, a);              // a continuing backbone
            rotZ4(StandardAlpha[j], b);
            matmult4(a, b, c);
            rotX4(psi[i], b);
            matmult4(c, b, d);              // d is to help continue backbone
            rotX4(StandardPsi[j], b);      // to bring O to plane of next N and CA
            matmult4(d, b, g);
            rotZ4(-StandardAlpha[j], b);
            matmult4(g, b, e);              // e is for carbonyl oxygen

            //  Now do rest of atoms

            for (k = l; k <  numbAtoms[j]; ++k)
            {
                for (m = 0; m < 3; ++m)
                    v0[m] = residueAtomPos[j][k][m];
                v0[3] = 1.;
                if (k == l+3)
                    matrix_vector4(e, v0, pos);
                else
                    matrix_vector4(a, v0, pos);
                #if WRITEPDBFILE
                fprintf(fp, "ATOM %6d %4s%4s  %4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                        n, residueAtomName[j][k], Protein::Residue::abbreviatedNames[j],
                        i+1, pos[0], pos[1], pos[2], 0., 0.);
                #endif
                ++n;
                atomNamePtr=residueAtomName[j][k];
                while(isspace(*atomNamePtr))
                    ++atomNamePtr;
                elementName[0]=*atomNamePtr;
                ++atomNamePtr;
                elementName[1]='\0';
                proteinCreator.addAtom(elementName, n-1 ,Position(pos), atomNamePtr);
            }
            // Now build up rest of main chain effect on matrix a

            translate4d(residued3[j], 0., 0., b);
            matmult4(d, b, c);
            rotZ4(StandardBeta[j], b);
            matmult4(c, b, d);
            translate4d(1.325, 0., 0., b);
            matmult4(d, b, c);
            rotX4(PI, b);
            matmult4(c, b, d);
            rotZ4(StandardGamma[j], b);
            matmult4(d, b, a);
            //   printf("Alpha %f  Beta %f  Gamma %f\n", 180*StandardAlpha[j]/PI,
            //      180*StandardBeta[j]/PI, 180*StandardGamma[j]/PI);
        }
    }

  #if WRITEPDBFILE
  fclose(fp);
  #endif
  proteinCreator.finishProtein();
  SetStandardHelix();
  return result;
  }
//-------------------------------------------------------------
// writes Wavefront OBJ into stream
//-------------------------------------------------------------
void WriteMODELToObjStream(	IVirtualStream* pStream, MODEL* model, int model_index, 
							bool debugInfo = true,
							const Matrix4x4& translation = identity4(),
							int* first_v = NULL,
							int* first_t = NULL,
							RegionModels_t* regModels = NULL)
{
	if(!model)
	{
		MsgError("no model %d!!!\n", model_index);
		return;
	}

	// export OBJ with points
	if(debugInfo)
		pStream->Print("#vert count %d\r\n", model->num_vertices);

	
	pStream->Print("g lev_model_%d\r\n", model_index);
	pStream->Print("o lev_model_%d\r\n", model_index);


	MODEL* vertex_ref = model;

	if(model->instance_number > 0) // car models have vertex_ref=0
	{
		pStream->Print("#vertex data ref model: %d (count = %d)\r\n", model->instance_number, model->num_vertices);

		vertex_ref = FindModelByIndex(model->instance_number, regModels);

		if(!vertex_ref)
		{
			Msg("vertex ref not found %d\n", model->instance_number);
			return;
		}
	}

	for(int j = 0; j < vertex_ref->num_vertices; j++)
	{
		Vector3D vertexTransformed = Vector3D(vertex_ref->pVertex(j)->x*-0.00015f, vertex_ref->pVertex(j)->y*-0.00015f, vertex_ref->pVertex(j)->z*0.00015f);

		vertexTransformed = (translation * Vector4D(vertexTransformed, 1.0f)).xyz();

		pStream->Print("v %g %g %g\r\n",	vertexTransformed.x,
											vertexTransformed.y,
											vertexTransformed.z);
	}

	int face_ofs = 0;

	if(debugInfo)
	{
		pStream->Print("#poly ofs %d\r\n", model->poly_block);
		pStream->Print("#poly count %d\r\n", model->num_polys);
	}

	pStream->Print("usemtl none\r\n", model->num_vertices);

	int numVertCoords = 0;
	int numVerts = 0;

	if(first_t)
		numVertCoords = *first_t;

	if(first_v)
		numVerts = *first_v;

	bool bSmooth = false;

	for(int j = 0; j < model->num_polys; j++)
	{
		char* facedata = model->pPolyAt(face_ofs);

		dface_t dec_face;
		int face_size = decode_poly(facedata, &dec_face);

		//if((dec_face.flags & FACE_SMOOTH) > 0 != bSmooth)
		//{
		//	bSmooth = (dec_face.flags & FACE_SMOOTH) > 0;
		//	pStream->Print("s %s\r\n", bSmooth ? "1" : "off");
		//}

		if((dec_face.flags & FACE_TEXTURED) || (dec_face.flags & FACE_TEXTURED2))
		{
			pStream->Print("usemtl page_%d\r\n", dec_face.page);
		}

		if(debugInfo)
			pStream->Print("#ft=%d ofs=%d size=%d\r\n", dec_face.flags,  model->poly_block+face_ofs, face_size);

		if(dec_face.flags & FACE_IS_QUAD)
		{
			// flip
			int vertex_index[4] = {
				dec_face.vindex[3],
				dec_face.vindex[2],
				dec_face.vindex[1],
				dec_face.vindex[0]
			};

			if( dec_face.vindex[0] < 0 || dec_face.vindex[0] > vertex_ref->num_vertices ||
				dec_face.vindex[1] < 0 || dec_face.vindex[1] > vertex_ref->num_vertices ||
				dec_face.vindex[2] < 0 || dec_face.vindex[2] > vertex_ref->num_vertices ||
				dec_face.vindex[3] < 0 || dec_face.vindex[3] > vertex_ref->num_vertices)
			{
				MsgError("quad %d (type=%d ofs=%d) has invalid indices (or format is unknown)\n", j, dec_face.flags, model->poly_block+face_ofs);
				face_ofs += face_size;
				continue;
			}

			if((dec_face.flags & FACE_TEXTURED) || (dec_face.flags & FACE_TEXTURED2))
			{
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[0][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[0][1])) / 256.0f+halfTexelSize);
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[1][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[1][1])) / 256.0f+halfTexelSize);
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[2][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[2][1])) / 256.0f+halfTexelSize);
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[3][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[3][1])) / 256.0f+halfTexelSize);

				pStream->Print("f %d/%d %d/%d %d/%d %d/%d\r\n", 
						vertex_index[0]+1+numVerts, numVertCoords+4,
						vertex_index[1]+1+numVerts, numVertCoords+3,
						vertex_index[2]+1+numVerts, numVertCoords+2,
						vertex_index[3]+1+numVerts, numVertCoords+1);

				numVertCoords += 4;
			}
			else
			{
				pStream->Print("f %d %d %d %d\r\n",
						vertex_index[0]+1+numVerts,
						vertex_index[1]+1+numVerts,
						vertex_index[2]+1+numVerts,
						vertex_index[3]+1+numVerts);
			}
		}
		else
		{
			// flip
			int vertex_index[3] = {
				dec_face.vindex[2],
				dec_face.vindex[1],
				dec_face.vindex[0]
			};

			if( dec_face.vindex[0] < 0 || dec_face.vindex[0] > vertex_ref->num_vertices ||
				dec_face.vindex[1] < 0 || dec_face.vindex[1] > vertex_ref->num_vertices ||
				dec_face.vindex[2] < 0 || dec_face.vindex[2] > vertex_ref->num_vertices)
			{
				MsgError("triangle %d (type=%d ofs=%d) has invalid indices (or format is unknown)\n", j, dec_face.flags, model->poly_block+face_ofs);
				face_ofs += face_size;
				continue;
			}

			if((dec_face.flags & FACE_TEXTURED) || (dec_face.flags & FACE_TEXTURED2))
			{
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[0][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[0][1])) / 256.0f+halfTexelSize);
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[1][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[1][1])) / 256.0f+halfTexelSize);
				pStream->Print("vt %g %g\r\n", float(dec_face.texcoord[2][0]) / 256.0f+halfTexelSize, (255.0f-float(dec_face.texcoord[2][1])) / 256.0f+halfTexelSize);

				pStream->Print("f %d/%d %d/%d %d/%d\r\n", 
						vertex_index[0]+1+numVerts, numVertCoords+3,
						vertex_index[1]+1+numVerts, numVertCoords+2,
						vertex_index[2]+1+numVerts, numVertCoords+1);

				numVertCoords += 3;
			}
			else
			{
				pStream->Print("f %d %d %d\r\n", 
						vertex_index[0]+1+numVerts,
						vertex_index[1]+1+numVerts,
						vertex_index[2]+1+numVerts);
			}
						
		}

		face_ofs += face_size;
	}

	if(first_t)
		*first_t = numVertCoords;

	if(first_v)
		*first_v = numVerts+vertex_ref->num_vertices;
}