[BACK]Return to pfprojtex.C CVS log [TXT][DIR] Up to [Development] / performer / src / lib / libpfdb / libpfprojtex

File: [Development] / performer / src / lib / libpfdb / libpfprojtex / pfprojtex.C (download)

Revision 1.1, Tue Nov 21 21:39:34 2000 UTC (16 years, 11 months ago) by flynnt
Branch: MAIN
CVS Tags: HEAD

Initial check-in based on OpenGL Performer 2.4 tree.
-flynnt

/*
 * Copyright 1996, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * This source code ("Source Code") was originally derived from a
 * code base owned by Silicon Graphics, Inc. ("SGI")
 * 
 * LICENSE: SGI grants the user ("Licensee") permission to reproduce,
 * distribute, and create derivative works from this Source Code,
 * provided that: (1) the user reproduces this entire notice within
 * both source and binary format redistributions and any accompanying
 * materials such as documentation in printed or electronic format;
 * (2) the Source Code is not to be used, or ported or modified for
 * use, except in conjunction with OpenGL Performer; and (3) the
 * names of Silicon Graphics, Inc.  and SGI may not be used in any
 * advertising or publicity relating to the Source Code without the
 * prior written permission of SGI.  No further license or permission
 * may be inferred or deemed or construed to exist with regard to the
 * Source Code or the code base of which it forms a part. All rights
 * not expressly granted are reserved.
 * 
 * This Source Code is provided to Licensee AS IS, without any
 * warranty of any kind, either express, implied, or statutory,
 * including, but not limited to, any warranty that the Source Code
 * will conform to specifications, any implied warranties of
 * merchantability, fitness for a particular purpose, and freedom
 * from infringement, and any warranty that the documentation will
 * conform to the program, or any warranty that the Source Code will
 * be error free.
 * 
 * IN NO EVENT WILL SGI BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT
 * LIMITED TO DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES,
 * ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THE
 * SOURCE CODE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT OR
 * OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM,
 * OR AROSE OUT OF USE OR RESULTS FROM USE OF, OR LACK OF ABILITY TO
 * USE, THE SOURCE CODE.
 * 
 * Contact information:  Silicon Graphics, Inc., 
 * 1600 Amphitheatre Pkwy, Mountain View, CA  94043, 
 * or:  http://www.sgi.com
*/

/*
 * pfprojtex.c
 *
 * Loader that creates creates a scene graph with a texture projected
 * orthographically through the scene.  The projected texture is applied
 * as the first texture if the pfGeoState has no texture and as the
 * second texture if the pfGeoState alread has a first texture.  Hardware
 * that can do multi-texturing is required in order for a second texture
 * to be visible.  The input format is a text file that consists of the
 * following commands:
 *
 *      scene             <scene filename>
 *      texture           <texture filename>
 *      texgenmode        <gen_mode>
 *      texenvmode        <env_mode>
 *      texrepeatmode     <repeat_mode>
 *      useboundingsphere <use_bsphere>
 *      newboundingsphere <center_x> <center_y> <center_z> <radius>
 *      rotate            <angle> <axis_x> <axis_y> <axis_z>
 *      translate         <xlate_x> <xlate_y> <xlate_z>
 *      scale             <scale_x> <scale_y> <scale_z>
 *      perframe
 *      
 * gen_mode defaults to PFTG_EYE_LINEAR_IDENT and can be one of
 *      PFTG_EYE_LINEAR
 *      PFTG_EYE_LINEAR_IDENT
 *      PFTG_OBJECT_LINEAR
 *      PFTG_SPHERE_MAP
 *      PFTG_OBJECT_DISTANCE_TO_LINE
 *      PFTG_EYE_DISTANCE_TO_LINE
 * For more info see the pfTexGen man page.
 *
 * env_mode defaults to PFTE_MODULATE and can be one of
 *      PFTE_MODULATE
 *      PFTE_BLEND
 *      PFTE_DECAL
 *      PFTE_REPLACE
 *      PFTE_ADD
 *      PFTE_ALPHA
 * For more info see the pfTexEnv man page.
 *
 * repeat_mode defaults to PFTEX_REPEAT and can be one of
 *      PFTEX_REPEAT
 *      PFTEX_CLAMP
 * For more info see the pfTexture man page.
 *
 * use_bsphere defaults to TRUE and can be one of
 *      TRUE
 *      FALSE
 * Using the bounding sphere causes the projected texture to be 
 * centered at the bounding sphere center and scaled to the diameter
 * of the bounding sphere.  When the bounding sphere is being used
 * rotations and scales first have the bounding sphere translated to
 * the origin, then the rotations and scales are done, then the
 * bounding sphere is translated back to its original position.
 * Note that the bounding sphere can be enabled and disabled as
 * many times as necessary in the transform list.  The texture is
 * only centered at the bounding sphere center and scaled to the
 * diameter of the bounding sphere if useboundingsphere is enabled
 * at the end of the parse.
 *
 * perframe causes all subsequent rotate, translate, and scale
 * commands to be applied every frame, instead of as a one time
 * only transformation.
 *
 * The rotate, translate, and scale commands are identical to those
 * of the pfMatrix class and are used to generate additional
 * transformations to the texture matrix (otherwise it is the matrix
 * generated by the use_bsphere command).  The rotate, translate, and
 * scale commands can be applied in any order and as many times as
 * necessary to generate the appropriate matrix.
 *
 * author - Steve Kilthau
 */

#include <stdio.h>
#include <math.h>

#include <Performer/pr.h>
#include <Performer/pf.h>
#include <Performer/pfdu.h>
#include <Performer/pfutil.h>
#include <Performer/pf/pfNode.h>
#include <Performer/pf/pfGeode.h>
#include <Performer/pf/pfGroup.h>
#include <Performer/pr/pfGeoSet.h>
#include <Performer/pr/pfGeoState.h>
#include <Performer/pr/pfTexture.h>
#include <Performer/pr/pfStruct.h>


//
// Exported symbols.
//
extern "C" {
  pfNode* pfdLoadFile_projtex(const char *);
}


//
// Structure for internal data.
//
typedef struct
{
PFSTRUCT_DECLARE

  pfGroup*   root;
  pfTexture* texture;
  pfTexGen*  texgen;
  pfTexEnv*  texenv;
  pfMatrix*  texmat;
  pfMatrix*  accumtexmat;
  pfMatrix*  perframetexmat;
  pfMatrix*  finaltexmat;
  int        texgenmode;
  int        texenvmode;
  int        texrepeatmode;
  int        usebsphere;
  float      scale;
  pfVec3     translate;
  int        maxtextures;
  int        frame;

} InternalData;


//
// Traversal function to apply per-frame transformations
// to the texture matrix before the draw process.
//
static int predrawCallback(pfTraverser* trav, void* userdata)
{
  InternalData* data = (InternalData*)userdata;
  int f;
  f = pfGetFrameCount();
  if (f != data->frame)
  {
    data->frame = f;
    data->accumtexmat->postMult(*data->perframetexmat);
    *data->texmat = *data->accumtexmat;
    data->texmat->postMult(*data->finaltexmat);
  }
  return PFTRAV_CONT;
}


//
// Initialize the internal data structures values.
//
void initInternalData(InternalData* data)
{
  data->root = 0;
  data->texture = 0;
  data->texgen  = 0;
  data->texenv  = 0;
  data->texmat  = 0;
  data->accumtexmat = 0;
  data->perframetexmat = 0;
  data->finaltexmat = 0;
  data->texgenmode = PFTG_EYE_LINEAR_IDENT;
  data->texenvmode = PFTE_MODULATE;
  data->texrepeatmode = PFTEX_REPEAT;
  data->usebsphere = 0;
  data->scale   = 1.0f;
  data->translate.set(0.0f, 0.0f, 0.0f);
  pfQuerySys(PFQSYS_MAX_TEXTURES, &(data->maxtextures));
  data->frame = -10;
}


//
// Deletes the internal data structure values and resets all
// pointers to NULL.
//
void cleanInternalData(InternalData* data)
{
  if (data->texture)
    pfDelete(data->texture);

  if (data->texgen)
    pfDelete(data->texgen);

  if (data->texenv)
    pfDelete(data->texenv);

  if (data->texmat)
    pfDelete(data->texmat);

  if (data->accumtexmat)
    pfDelete(data->accumtexmat);

  if (data->perframetexmat)
    pfDelete(data->perframetexmat);

  if (data->finaltexmat)
    pfDelete(data->finaltexmat);

  if (data->root)
    pfDelete(data->root);

  initInternalData(data);
}


//
// Traverse the tree and add the projected texture to the
// scene-graph.
//
static void texAddTraversal(pfNode* node, InternalData* data)
{
  // If the node is a group then traverse all children recursively
  if (node->isOfType(pfGroup::getClassType()))
  {
    pfGroup* group;
    pfNode*  child;
    int      i;

    // Loop through children and recurse
    group = (pfGroup*)node;
    for(i=0;i<group->getNumChildren();++i)
    {
      child = group->getChild(i);
      texAddTraversal(child, data);
    }
  }
  else if (node->isOfType(pfGeode::getClassType()))
  {
    pfGeoState* gstate;
    pfGeode*    geode;
    int         i;
    int         j;
    int         done;

    // Get the geode and apply the projection texture to the
    // geostates of all geosets in the geode.
    geode = (pfGeode*)node;
    for(i=0;i<geode->getNumGSets();++i)
    {
      gstate = (geode->getGSet(i))->getGState();

      // Walk through the textures on the geostate and apply the
      // new texture at the end of the list of textures.  But only
      // apply it, if the texture has not been applied previously!
      j = 0;
      done = 0;
      while((j < data->maxtextures) && !done)
      {
	if (gstate->getMultiMode(PFSTATE_ENTEXTURE, j) == PF_OFF)
	{
	  gstate->setMultiMode(PFSTATE_ENTEXTURE, j, PF_ON);
	  gstate->setMultiMode(PFSTATE_ENTEXGEN, j, PF_ON);
	  gstate->setMultiMode(PFSTATE_ENTEXMAT, j, PF_ON);
	  gstate->setMultiAttr(PFSTATE_TEXTURE, j, data->texture);
	  gstate->setMultiAttr(PFSTATE_TEXGEN, j, data->texgen);
	  gstate->setMultiAttr(PFSTATE_TEXENV, j, data->texenv);
	  gstate->setMultiAttr(PFSTATE_TEXMAT, j, data->texmat);
	  done = 1;
	}
	if ((gstate->getMultiAttr(PFSTATE_TEXTURE, j) == (void*)data->texture))
	  done = 1;
	++j;
      }
    }
  }

  return;
}


//
// Read and parse the .projtex file.  Set up the texture
// matrix, the texture generator, and the texutre environment.
//
static void parseProjTex(const char* filename, InternalData* data)
{
  int      stats[8] = {0,0,0,0,0,0,0,0};
  int      pos = 0;
  int      len = 0;
  float    f0, f1, f2, f3;
  char     str[1024];
  char*    buffer;
  FILE*    ifp;
  pfSphere boundsphere;

  // Read in the file
  ifp = fopen(filename, "r");
  if (ifp)
  {
    int length;

    fseek(ifp, 0, SEEK_END);
    length = ftell(ifp);
    fseek(ifp, 0, SEEK_SET);
    buffer = new char[length+1];
    fread(buffer, 1, length, ifp);
    buffer[length] = 0;
    fclose(ifp);
  }
  else
  {
    pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: unable to read file \"%s\"\n", filename);
    cleanInternalData(data);
    return;
  }

  // Reset the bounding sphere
  boundsphere.center.set(0.0f, 0.0f, 0.0f);
  boundsphere.radius = 0.0f;

  // Allocate the texture variables
  data->texgen = new pfTexGen;
  data->texenv = new pfTexEnv;
  data->texmat = new pfMatrix;
  data->accumtexmat = new pfMatrix;
  data->perframetexmat = new pfMatrix;
  data->finaltexmat = new pfMatrix;
  data->texmat->makeIdent();
  data->accumtexmat->makeIdent();
  data->perframetexmat->makeIdent();
  data->finaltexmat->makeIdent();

  do
  {
    stats[7] = 0;

    // Read and setup the scene-graph
    if (!stats[0] && (sscanf(&buffer[pos], " scene %s %n", str, &len) == 1))
    {
      pfNode* scene;

      // Load DSO for the input file
      pfdInitConverter(str);

      // Read in the file
      scene = pfdLoadFile(str);
      if (scene == NULL)
      {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: unable to load file \"%s\".\n", str);
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      // Create group node so that per-frame movement can be attached
      // to the texture.
      data->root = new pfGroup;
      data->root->addChild(scene);
      data->root->setTravData(PFTRAV_DRAW, (void*)data);
      data->root->setTravFuncs(PFTRAV_DRAW, predrawCallback, 0);

      // Get info from the scene's bounding sphere
      data->root->getBound(&boundsphere);
      data->scale = 1.0f/boundsphere.radius;
      data->translate.negate(boundsphere.center);

      stats[0] = 1;
      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read the input texture
    if (!stats[1] && (sscanf(&buffer[pos], " texture %s %n", str, &len) == 1))
    {
      // Read in the texture
      data->texture = new pfTexture;
      if (!(data->texture->loadFile(str)))
      {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: unable to load file \"%s\".\n", str);
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      stats[1] = 1;
      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read a texture generation mode command
    if (!stats[2] && (sscanf(&buffer[pos], " texgenmode %s %n", str, &len) == 1))
    {
      if (!strcmp(str, "PFTG_OBJECT_LINEAR"))
	data->texgenmode = PFTG_OBJECT_LINEAR;
      else if (!strcmp(str, "PFTG_EYE_LINEAR"))
	data->texgenmode = PFTG_EYE_LINEAR;
      else if (!strcmp(str, "PFTG_EYE_LINEAR_IDENT"))
	data->texgenmode = PFTG_EYE_LINEAR_IDENT;
      else if (!strcmp(str, "PFTG_SPHERE_MAP"))
	data->texgenmode = PFTG_SPHERE_MAP;
      else if (!strcmp(str, "PFTG_OBJECT_DISTANCE_TO_LINE"))
	data->texgenmode = PFTG_OBJECT_DISTANCE_TO_LINE;
      else if (!strcmp(str, "PFTG_EYE_DISTANCE_TO_LINE"))
	data->texgenmode = PFTG_EYE_DISTANCE_TO_LINE;
      else
      {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: illegal texgen mode \"%s\".\n", str);
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      stats[2] = 1;
      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read a texture environment mode command
    if (!stats[3] && (sscanf(&buffer[pos], " texenvmode %s %n", str, &len) == 1))
    {
      if (!strcmp(str, "PFTE_MODULATE"))
	data->texenvmode = PFTE_MODULATE;
      else if (!strcmp(str, "PFTE_BLEND"))
	data->texenvmode = PFTE_BLEND;
      else if (!strcmp(str, "PFTE_DECAL"))
	data->texenvmode = PFTE_DECAL;
      else if (!strcmp(str, "PFTE_REPLACE"))
	data->texenvmode = PFTE_REPLACE;
      else if (!strcmp(str, "PFTE_ADD"))
	data->texenvmode = PFTE_ADD;
      else if (!strcmp(str, "PFTE_ALPHA"))
	data->texenvmode = PFTE_ALPHA;
      else
      {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: illegal texenv mode \"%s\".\n", str);
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      stats[3] = 1;
      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read a texture repeat mode command
    if (!stats[4] && (sscanf(&buffer[pos], " texrepeatmode %s %n", str, &len) == 1))
    {
      if (!strcmp(str, "PFTEX_REPEAT"))
	data->texrepeatmode = PFTEX_REPEAT;
      else if (!strcmp(str, "PFTEX_CLAMP"))
	data->texrepeatmode = PFTEX_CLAMP;
      else
      {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: illegal texrepeat mode \"%s\".\n", str);
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      stats[4] = 1;
      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read whether to use boundsphere or not
    if (sscanf(&buffer[pos], " useboundingsphere %s %n", str, &len) == 1)
    {
      if (!strcmp(str, "TRUE"))
	data->usebsphere = 1;
      else if (!strcmp(str, "FALSE"))
	data->usebsphere = 0;
      else
      {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: illegal value for useboundingsphere \"%s\".\n", str);
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read the perframe state change command
    if (!stats[5] && (sscanf(&buffer[pos], " perframe %n", &len) == 0))
    {
      if (len != 0)
      {
	stats[5] = 1;
	stats[7] = 1;
	pos += len;
	len = 0;
      }
    }

    // Set a new bounding sphere for the object
    if (!stats[6] && sscanf(&buffer[pos], " setboundingsphere %f %f %f %f %n", &f0, &f1, &f2, &f3, &len) == 4)
    {
      pfSphere* newbsphere;

      if (!stats[0])
      {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: no scene to set bound sphere on.\n");
	cleanInternalData(data);
	delete [] buffer;
	return;
      }

      // Apply new bounding sphere to scene
      newbsphere = new pfSphere;
      newbsphere->center.set(f0, f1, f2);
      newbsphere->radius = f3;
      data->root->setBound(newbsphere, PFBOUND_STATIC);

      // Change the bounding sphere data
      data->scale = 1.0f/newbsphere->radius;
      data->translate.negate(newbsphere->center);

      stats[6] = 1;
      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read a rotate command
    if (sscanf(&buffer[pos], " rotate %f %f %f %f %n", &f0, &f1, &f2, &f3, &len) == 4)
    {
      if (data->usebsphere)
      {
	if (stats[5])
	{
	  data->perframetexmat->postTrans(*data->perframetexmat,
					   data->translate[0], data->translate[1], data->translate[2]);
	  data->perframetexmat->postRot(*data->perframetexmat, f0, f1, f2, f3);
	  data->perframetexmat->postTrans(*data->perframetexmat,
					   -data->translate[0], -data->translate[1], -data->translate[2]);
	}
	else
	{
	  data->accumtexmat->postTrans(*data->accumtexmat,
				       data->translate[0], data->translate[1], data->translate[2]);
	  data->accumtexmat->postRot(*data->accumtexmat, f0, f1, f2, f3);
	  data->accumtexmat->postTrans(*data->accumtexmat,
				       -data->translate[0], -data->translate[1], -data->translate[2]);
	}
      }
      else
      {
	if (stats[5])
	  data->perframetexmat->postRot(*data->perframetexmat, f0, f1, f2, f3);
	else
	  data->accumtexmat->postRot(*data->accumtexmat, f0, f1, f2, f3);
      }

      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read a translate command
    if (sscanf(&buffer[pos], " translate %f %f %f %n", &f0, &f1, &f2, &len) == 3)
    {
      if (stats[5])
	data->perframetexmat->postTrans(*data->perframetexmat, f0, f1, f2);
      else
	data->accumtexmat->postTrans(*data->accumtexmat, f0, f1, f2);

      stats[7] = 1;
      pos += len;
      len = 0;
    }

    // Read a scale command
    if (sscanf(&buffer[pos], " scale %f %f %f %n", &f0, &f1, &f2, &len) == 3)
    {
      if (data->usebsphere)
      {
	if (stats[5])
	{
	  data->perframetexmat->postTrans(*data->perframetexmat,
					   data->translate[0], data->translate[1], data->translate[2]);
	  data->perframetexmat->postScale(*data->perframetexmat, f0, f1, f2);
	  data->perframetexmat->postTrans(*data->perframetexmat,
					   -data->translate[0], -data->translate[1], -data->translate[2]);
	}
	else
	{
	  data->accumtexmat->postTrans(*data->accumtexmat,
				       data->translate[0], data->translate[1], data->translate[2]);
	  data->accumtexmat->postScale(*data->accumtexmat, f0, f1, f2);
	  data->accumtexmat->postTrans(*data->accumtexmat,
				       -data->translate[0], -data->translate[1], -data->translate[2]);
	}
      }
      else
      {
	if (stats[5])
	  data->perframetexmat->postScale(*data->perframetexmat, f0, f1, f2);
	else
	  data->accumtexmat->postScale(*data->accumtexmat, f0, f1, f2);
      }

      stats[7] = 1;
      pos += len;
      len = 0;
    }

  }while(stats[7]);

  // Check that the entire file was parsed
  if (buffer[pos] != 0)
  {
    pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: failed to parse entire file.\n");
    cleanInternalData(data);
    delete [] buffer;
    return;
  }

  // Check that a scene and a texture were provided
  if (!stats[0] || !stats[1])
  {
    pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdLoadFile_projtex: must load a scene and a texture.\n");
    cleanInternalData(data);
    return;
  }

  // Delete the internal buffer
  delete [] buffer;

  // Control the texture repeat parameter
  data->texture->setRepeat(PFTEX_WRAP, data->texrepeatmode);

  // Make the texture coordinate generator
  data->texgen->setMode(PF_S, data->texgenmode);
  data->texgen->setMode(PF_T, data->texgenmode);
  data->texgen->setMode(PF_R, data->texgenmode);
  data->texgen->setPlane(PF_S, 1.0f, 0.0f, 0.0f, 0.0f);
  data->texgen->setPlane(PF_T, 0.0f, 0.0f, 1.0f, 0.0f);
  data->texgen->setPlane(PF_R, 0.0f, -1.0f, 0.0f, 0.0f);

  // Adjust the texture environment mode
  data->texenv->setMode(data->texenvmode);

  // Create the texture matrix
  if (data->usebsphere)
  {
    data->finaltexmat->postTrans(*data->finaltexmat, data->translate[0], data->translate[1], data->translate[2]);
    data->finaltexmat->postScale(*data->finaltexmat, data->scale, data->scale, data->scale);
  }
  data->finaltexmat->postTrans(*data->finaltexmat, 1.0f, 1.0f, 1.0f);
  data->finaltexmat->postScale(*data->finaltexmat, 0.5f, 0.5f, 0.5f);

  // Add the texture to the scene-graph
  texAddTraversal(data->root, data);
}


//
// Parse and load the .projtex file.
//
pfNode* pfdLoadFile_projtex(const char* filename)
{
  InternalData* data;
  pfNode*       node;
  char          path[PF_MAXSTRING];

  // Initialize the internal data
  data = new InternalData;
  initInternalData(data);

  // Print notify about loader
  pfNotify( PFNFY_INFO, PFNFY_PRINT, 
	    "pfdLoadFile_projtex : Loading \"%s\".\n", filename );

  // File the input file
  if (!pfFindFile(filename, path, R_OK))
  {
    pfNotify(PFNFY_WARN, PFNFY_RESOURCE,
	     "pfdLoadFile_projtex : Could not find file \"%s\"\n", filename);
  }
  else
  {
    // Parse the .projtex input file
    parseProjTex(path, data);
  }

  // Clean up the internal data
  node = data->root;
  pfDelete(data);

  return node;
}