Re: Wavefront MTL file format

New Message Reply Date view Thread view Subject view Author view

From: Simon C Mills (simon++at++wgs.estec.esa.nl)
Date: 04/17/2001 07:04:29


> Jo Mantelers wrote:
>
> I am looking for a Wavefront MTL file format specification, but can
> only find Wavefront OBJ formats specifications. Can somebody please
> help? In specific: I am looking only for the def of emissive
> (self-illum), reflection map, and opacity.

I have the full Wavefront documentation describing the MTL format.
However, MTL files do not support emissive material components directly.
My solution was to use the "illum" keyword which specifies "illumination
model". "illum 0" is taken to mean constant colour, i.e. an emissive
material, in which case I use the diffuse colour as the emissive.

I attach my modified version of the loader incorporating these changes.

Hope it helps, Simon
_______________________________________________________________________

Simon Mills
Silicon Worlds S.A.
c/o Modelling & Simulation Section (TOS-EMM) Tel: +31 (0)71 565 3725
European Space Agency (ESA/ESTEC) Fax: +31 (0)71 565 5419
Postbus 299, 2200AG Noordwijk e-mail: simon++at++wgs.estec.esa.nl
The Netherlands http://www.estec.esa.nl/wmwww/EMM
_______________________________________________________________________

static char RCS_ID[] = "$Id: pfobj.c,v 2.0 1997/03/18 09:21:52 simon Exp $";

/*
 * $Log: pfobj.c,v $
 * Revision 2.0 1997/03/18 09:21:52 simon
 * Performer 2.0 compatible release.
 *
 */

/*
 * ESTEC specific version of standard Performer .obj file loader
 *
 * Changes
 * -------
 *
 * 1) Support for "illum 0" command which sets emissive material colors
 * 2) Removed duplicate material library message - it's annoying
 * 3) Support for "d" command which sets material transparency
 * 4) Support for "illum 4" command which sets material geostate to turn off
 * backface culling i.e. makes material "double sided"
 * 5) Support for "-clamp on|off" option for "map_Kd" token, sets texture clamping
 *
 * Simon Mills ESA/ESTEC/WAS 28-Sep-95
 * Version: 1.0
 * Comments: Initial version.
 *
 * Guy Premont Silicon Worlds 5 juin 1996
 * Version: 2.0
 * Comments: adapted from pfobj.c Version 1.2 and from Performer 2.0 loader
 */
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef _POSIX_SOURCE
extern int strcasecmp (const char *s1, const char *s2);
extern char *strdup (const char *s1);
#endif

#include <Performer/pf.h>
#include <Performer/pfdu.h>

/* length of longest input line in ".obj" file (including continuations) */
#define BUFFER_SIZE 8192

/* maximum number of vertices in a single polygon */
#define FACE_SIZE 4096

/* initial allocation size for growable arrays */
#define CHUNK 4096

/* how many different material library files */
#define MAX_MTL_FILES 512

/* case insensitive string equality test */
#define SAME(_a, _b) (strcasecmp(_a,_b) == 0)

/* number of input lines skipped (recognized but not processed) or unknown */
static int numSkip = 0;
static int numOther = 0;

/* function type and argument declarations */
static void loadMtl (char *fileName);
static void useMtl (char *name);
static void forgetMaterials (void);
static void forgetMaterialFiles (void);

#define GROW(_v, _t) \
    if (_v == NULL) \
    { \
        _v ## Available = CHUNK; \
        _v = (_t *)pfMalloc(sizeof(_t)*_v ## Available, NULL); \
    } \
    else \
    if (_v ## Count >= _v ## Available) \
    { \
        _v ## Available *= 2; \
        _v = (_t *)pfRealloc(_v, sizeof(_t)*_v ## Available); \
    }

/*
 * pfdLoadFile_obj -- Load Wavefront ".obj" files into IRIS Performer
 */

extern pfNode*
pfdLoadFile_obj (char *fileName)
{
    FILE *objFile;

    pfdGeom *polygon = NULL;
    int polySize = -1;

    char buffer[BUFFER_SIZE];
    char token[BUFFER_SIZE];
    char *next = NULL;
    char *backslash = NULL;

    pfGeode *geode = NULL;
    pfGeode *geodeRefl = NULL;
    pfGroup *group = NULL;
    pfNode *node = NULL;

    pfNode *hierarchy = NULL;

    int width = 0;

    int numTris = 0;
    int numPolys = 0;
    int numGroups = 0;

    /* growable vertex coordinate list (X, Y, Z) */
    pfVec3 *v = NULL;
    unsigned int vCount = 0;
    unsigned int vAvailable = 0;

    /* growable vertex normal list (Nx, Ny, Nz) */
    pfVec3 *n = NULL;
    unsigned int nCount = 0;
    unsigned int nAvailable = 0;

    /* growable texture coordinate list (S, T )*/
    pfVec2 *t = NULL;
    unsigned int tCount = 0;
    unsigned int tAvailable = 0;

    /* tmp count vars */
    int i, j;

    double startTime = pfGetTime();
    double elapsedTime = 0.0;

    /* restore builder to initial state */
    pfdResetBldrGeometry();
    pfdResetBldrState();

    /* open Wavefront ".obj" file file */
    if ((objFile = pfdOpenFile(fileName)) == NULL)
        return NULL;

    /* Obj loader uses strings to specify state names */
    pfdBldrAttr(PFDBLDR_STATE_NAME_COMPARE, strcmp);
    pfdBldrAttr(PFDBLDR_NODE_NAME_COMPARE, strcmp);
    pfdBldrAttr(PFDBLDR_EXTENSOR_NAME_COMPARE, strcmp);

    pfdBldrStateMode(PFSTATE_ENLIGHTING, PF_ON);
    pfdBldrStateMode(PFSTATE_ENTEXTURE, PF_OFF);
    pfdBldrStateMode(PFSTATE_CULLFACE,PFCF_BACK);
    pfdCaptureDefaultBldrState();

    /* read Wavefront ".obj" file */
    while (fgets(buffer, BUFFER_SIZE, objFile) != NULL)
    {
        /* concatenate continuation lines */
        while ((backslash = strchr(buffer, '\\')) != NULL)
        {
            /* replace backslash with space (thanks to Ken Sakai) */
            *backslash++ = ' ';
            *backslash = '\0';

            /* keep reading */
            if (fgets(backslash, BUFFER_SIZE - strlen(buffer), objFile) == NULL)
                break;
        }

        /* find first non-"space" character in line */
        for (next = buffer; *next != '\0' && isspace(*next); next++)
            /* EMPTY */ {};

        /* skip blank lines and comments ('$' is comment in "cow.obj") */
        if (*next == '\0' || *next == '#' || *next == '!' || *next == '$')
            continue;

        /* extract token */
        sscanf(next, "%s%n", token, &width);
        next += width;

        /* identify token */
        if (SAME(token, "v"))
        {
            /* enlarge vertex coordinate list */
            GROW(v, pfVec3);

            /* set default values for vertex coordinate */
            v[vCount][0] = v[vCount][1] = v[vCount][2] = 0.0f;

            /* read vertex coordinate into list */
            sscanf(next, "%f %f %f",
                &v[vCount][PF_X], &v[vCount][PF_Y], &v[vCount][PF_Z]);

            /* advance vertex count */
            ++vCount;
        }
        else
        if (SAME(token, "vn"))
        {
            /* enlarge vertex normal list */
            GROW(n, pfVec3);

            /* set default values for vertex normal */
            n[nCount][0] = n[nCount][1] = n[nCount][2] = 0.0f;

            /* read vertex normal into list */
            sscanf(next, "%f %f %f",
                &n[nCount][PF_X], &n[nCount][PF_Y], &n[nCount][PF_Z]);

            /* advance normal count */
            ++nCount;
        }
        else
        if (SAME(token, "vt"))
        {
            /* enlarge texture coordinate list */
            GROW(t, pfVec2);

            /* set default values for vertex normal */
            t[tCount][0] = t[tCount][1] = 0.0f;

            /* read texture coordinate into list */
            sscanf(next, "%f %f", &t[tCount][0], &t[tCount][1]);

            /* advance texture coordinate count */
            ++tCount;
        }
        else
        if (SAME(token, "g"))
         {
            ++numGroups;
        }
        else
        if (SAME(token, "f") ||
            SAME(token, "fo"))
        {
            int count;
            int textureValid = 1;
            int normalsValid = 1;
            int vi[FACE_SIZE];
            int ti[FACE_SIZE];
            int ni[FACE_SIZE];

            char *slash;
            char vertexData[256];

            /* parse vertex data from input buffer */
            for (count = 0; count < FACE_SIZE; count++)
            {
                /* read the next vertices' data packet */
                if (sscanf(next, "%s%n", vertexData, &width) != 1)
                    break;

                /* advance next pointer past data packet ("n/n/n") */
                next += width;

                /* get vertex coordinate index */
                vi[count] = (int)strtol(vertexData, NULL, 10);

                /* get texture coordinate index */
                ti[count] = 0;
                if ((slash = strchr(vertexData, '/')) == NULL ||
                    (ti[count] = (int)strtol(slash+1, NULL, 10)) == 0)
                    textureValid = 0;

                /* get vertex normal index */
                ni[count] = 0;
                if (slash == NULL || (slash = strchr(slash+1, '/')) == NULL ||
                    (ni[count] = (int)strtol(slash+1, NULL, 10)) == 0)
                    normalsValid = 0;

                /*
                 * form cannonical indices:
                 * convert ".obj" 1-based indices to 0-based (subtract 1)
                 * convert negative indices to positive (count from 0)
                 */
                if (vi[count] >= 0)
                    vi[count] -= 1;
                else
                    vi[count] = vCount - vi[count];

                if (ti[count] >= 0)
                    ti[count] -= 1;
                else
                    ti[count] = tCount - ti[count];

                if (ni[count] >= 0)
                    ni[count] -= 1;
                else
                    ni[count] = nCount - ni[count];
            }

            /* resize geom as needed */
            if (polygon == NULL)
                polygon = pfdNewGeom(polySize = count);
            else
            if (polySize < count)
                pfdResizeGeom(polygon, polySize = count);

            polygon->numVerts = count;
            polygon->primtype = PFGS_POLYS;

            /* setup vertex position information*/
            for (i = 0; i < count; i++)
                pfCopyVec3(polygon->coords[i], v[vi[i]]);

            /* setup (disable) color information */
            polygon->cbind = PFGS_OFF;

            /* setup normal vector information */
            if (normalsValid)
            {
                polygon->nbind = PFGS_PER_VERTEX;
                for (i = 0; i < count; i++)
                    pfCopyVec3(polygon->norms[i], n[ni[i]]);
            }
            else
                polygon->nbind = PFGS_OFF;

            /* setup texture coordinates information */
            if (pfdGetBldrStateMode(PFSTATE_ENTEXGEN) > 0)
            {
                polygon->tbind = PFGS_PER_PRIM;
            }
            else
            if (textureValid)
            {
                polygon->tbind = PFGS_PER_VERTEX;
                for (i = 0; i < count; i++)
                    pfCopyVec2(polygon->texCoords[i], t[ti[i]]);
            }
            else
                polygon->tbind = PFGS_OFF;

            if (count > 2)
            {
                pfdAddBldrGeom(polygon, 1);
                numTris += count - 2;
                numPolys++;
            }
        }
        else
        if (SAME(token, "usemtl"))
        {
            char mtlName[PF_MAXSTRING];
            sscanf(next, "%s", mtlName);
            useMtl(mtlName);
        }
        else
        if (SAME(token, "mtllib"))
        {
            char libName[PF_MAXSTRING];
            sscanf(next, "%s", libName);
            loadMtl(libName);
        }
        else
        if (
            SAME(token, "bevel") ||
            SAME(token, "bmat") ||
            SAME(token, "bsp") ||
            SAME(token, "bzp") ||
            SAME(token, "c_interp") ||
            SAME(token, "cdc") ||
            SAME(token, "con") ||
            SAME(token, "cstype") ||
            SAME(token, "ctech") ||
            SAME(token, "curv") ||
            SAME(token, "curv2") ||
            SAME(token, "d_interp") ||
            SAME(token, "deg") ||
            SAME(token, "end") ||
            SAME(token, "hole") ||
            SAME(token, "l") ||
            SAME(token, "lod") ||
            SAME(token, "maplib") ||
            SAME(token, "mg") ||
            SAME(token, "o") ||
            SAME(token, "p") ||
            SAME(token, "param") ||
            SAME(token, "parm") ||
            SAME(token, "res") ||
            SAME(token, "s") ||
            SAME(token, "scrv") ||
            SAME(token, "shadow_obj") ||
            SAME(token, "sp") ||
            SAME(token, "stech") ||
            SAME(token, "step") ||
            SAME(token, "surf") ||
            SAME(token, "trace_obj") ||
            SAME(token, "trim") ||
            SAME(token, "usemap") ||
            SAME(token, "vp"))
        {
            ++numSkip;
        }
#ifndef STRICT_OBJ_FORMAT
        /*
         * reset vertex data array counters -- this is not
         * part of the OBJ format, but proves quite handy.
         */
        else
        if (SAME(token, "RESET"))
        {
            vCount = 0;
            nCount = 0;
            tCount = 0;
        }
#endif
        else
        {
            pfNotify(PFNFY_WARN, PFNFY_INTERNAL, "unrecognized: %s", buffer);
            ++numOther;
        }
    }

    /* close Wavefront ".obj" file */
    fclose(objFile);

    /* release dynamically allocated vertex, normal, and texture data */
    if (v != NULL) pfFree(v);
    if (n != NULL) pfFree(n);
    if (t != NULL) pfFree(t);
    pfdDelGeom(polygon);

    /*
     * note -- if you define this, then material files will be read anew
     * for every file that references them. could be slow for big
     * material files and many models. on the other hand, if you
     * don't define it then the lists will linger after the files
     * have all been loaded -- wasting space.
     */
#ifdef FORGETFUL
    /* discard material info */
    forgetMaterials();
    forgetMaterialFiles();
#endif

    /* Build All geometry */
    node = pfdBuild();

    /* print statistics */
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "pfdLoadFile_obj: %s", fileName);
    pfNotify(PFNFY_INFO, PFNFY_MORE, " Input Data:");
    if (numSkip != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Skipped tokens: %8ld", numSkip);
    if (numOther != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Unrecognized tokens:%8ld", numOther);
    if (numGroups != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Groups processed: %8ld", numGroups);
    if (vCount != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Vertex coordinates: %8ld", vCount);
    if (nCount != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Vertex normals: %8ld", nCount);
    if (tCount != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Texture coordinates:%8ld", tCount);
    if (numTris != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Input polygons: %8ld", numPolys);
    if (numTris != 0)
        pfNotify(PFNFY_INFO, PFNFY_MORE,
            " Total triangles: %8ld", numTris);

    /* return address of hierarchy to caller */
    return node;
}

/***
 *** P R O C E S S M A T E R I A L F I L E S
 ***/

#define MTL_NOT_DEFINED 0x0001
#define MTL_HAS_GEOSTATE 0x0002
#define MTL_HAS_AMBIENT 0x0004
#define MTL_HAS_DIFFUSE 0x0008
#define MTL_HAS_ALPHA 0x0010
#define MTL_HAS_SPECULAR 0x0020
#define MTL_HAS_SHININESS 0x0040
#define MTL_HAS_TEXTURE 0x0080
#define MTL_HAS_REFLECTION 0x0100
#define MTL_IS_TWO_SIDED 0x0200

/* added by Guy Premont to allow emissive materials (illum 0) */
#define MTL_HAS_EMISSION 0x1000

/*
 * data for a single wavefront material
 */
typedef struct objMaterial
{
    char name[PF_MAXSTRING]; /* material name */
    int defined; /* defined fields */
    pfVec3 ambient; /* Ka field */
    pfVec3 diffuse; /* Kd field */
    pfVec3 specular; /* Ks field */
    pfVec3 emission; /* added by Guy Premont for ILLUM token */
    float alpha; /* Tr field (actually, 1-Tr) */
    float shininess; /* Ns field */
    char texture[PF_MAXSTRING]; /* texture map name */
    float su; /* u-axis tc scale */
    float sv; /* v-axis tc scale */
    int clamp;
    char reflect[PF_MAXSTRING]; /* reflection mode */
} objMaterial;

static objMaterial *mtlList = NULL;
static int mtlListCount = 0;
static int mtlListSize = 0;

static void
resetMaterial (objMaterial *m)
{
    if (m == NULL)
        return;

    memset(m->name, '\0', sizeof(m->name));
    m->defined = 0;
    pfSetVec3(m->ambient, 0.0f, 0.0f, 0.0f);
    pfSetVec3(m->diffuse, 0.0f, 0.0f, 0.0f);
    pfSetVec3(m->specular, 0.0f, 0.0f, 0.0f);
    pfSetVec3(m->emission, 0.0f, 0.0f, 0.0f);
    m->alpha = 1.0f;
    m->shininess = 0.0f;
    memset(m->texture, '\0', sizeof(m->texture));
    m->su = 0.0f;
    m->sv = 0.0f;
    m->clamp = PF_OFF;
    memset(m->reflect, '\0', sizeof(m->reflect));
}

static void
rememberMaterial (objMaterial *m)
{
    int i;

    if (m == NULL)
        return;

#ifdef PRINT_MATERIALS_AS_PARSED
    printf(" name: %s\n", m->name);
    printf(" defined: 0x%x\n", m->defined);
    printf(" ambient: %g %g %g\n", m->ambient[0], m->ambient[1], m->ambient[2]);
    printf(" diffuse: %g %g %g\n", m->diffuse[0], m->diffuse[1], m->diffuse[2]);
    printf(" specular: %g %g %g\n", m->specular[0], m->specular[1], m->specular[2]);
    printf(" emission: %g %g %g\n", m->emission[0], m->emission[1], m->emission[2]);
    printf(" alpha: %g\n", m->alpha);
    printf("shininess: %g\n", m->shininess);
    printf(" texture: %s\n", m->texture);
    printf(" su: %g\n", m->su);
    printf(" sv: %g\n", m->sv);
    printf(" clamp: %d\n", m->clamp);
    printf(" reflect: %s\n", m->reflect);
    printf("\n");
#endif

    /* check for "empty" definition -- don't add junk */
    if (m->name[0] == '\0' || m->defined == 0)
        return;
    
    /* look for name in material list -- don't add duplicates */
    for (i = 0; i < mtlListCount; i++)
        if (strcmp(m->name, mtlList[i].name) == 0)
        {
            /* XXX the best thing is to do a compare and warn user */
            /* XXX if the old and new definitions are not identical */
            return;
        }
    
    /* grow material list */
    if (mtlListSize == 0)
    {
        /* create material list */
        mtlListSize = 1;
        mtlList = (objMaterial *)pfMalloc(mtlListSize*sizeof(objMaterial), NULL);
    }
    else
    if (mtlListCount >= mtlListSize)
    {
        /* grow material list */
        mtlListSize *= 2;
        mtlList = (objMaterial *)pfRealloc(mtlList, mtlListSize*sizeof(objMaterial));
    }
#ifdef PRINT_MATERIALS_AS_PARSED
    printf("mtlListSize=%d, mtlListCount=%d, sizeof(objMaterial)=%d\n",
        mtlListSize, mtlListCount, sizeof(objMaterial));
#endif

    /* add material to list */
    mtlList[mtlListCount++] = *m;
}

static void
forgetMaterials (void)
{
    /* delete list */
    if (mtlList != NULL)
        pfFree(mtlList);
    mtlList = 0;
    mtlListSize = 0;
    mtlListCount = 0;
}

static void
defineMtl (objMaterial *m)
{
    static char *suffix[] =
    {
        ".rgb",
        ".rgba",
        ".int",
        ".inta",
        ".bw"
    };
    static int numSuffix = sizeof(suffix)/sizeof(suffix[0]);

    pfMaterial *material = (pfMaterial *)
        pfdGetTemplateObject(pfGetMtlClassType());
    pfTexture *texture = (pfTexture *)
        pfdGetTemplateObject(pfGetTexClassType());
    pfLightModel *lm = (pfLightModel *)
        pfdGetTemplateObject(pfGetLModelClassType());

    pfMtlColorMode(material, PFMTL_FRONT, PFMTL_CMODE_OFF);
    pfdMakeDefaultObject((pfObject *)material);

    if (m->defined & (MTL_HAS_AMBIENT | MTL_HAS_DIFFUSE |
        MTL_HAS_SPECULAR | MTL_HAS_ALPHA | MTL_HAS_EMISSION))
    {
        if (m->defined & MTL_HAS_AMBIENT)
        {
            pfMtlColor(material, PFMTL_AMBIENT, m->ambient[0], m->ambient[1], m->ambient[2]);
        }

        if (m->defined & MTL_HAS_EMISSION)
        {
            pfMtlColor(material, PFMTL_EMISSION, m->emission[0], m->emission[1], m->emission[2]);
        }

        if (m->defined & MTL_HAS_DIFFUSE)
        {
            pfMtlColor(material, PFMTL_DIFFUSE, m->diffuse[0], m->diffuse[1], m->diffuse[2]);
        }

        if (m->defined & MTL_HAS_SPECULAR)
        {
            pfMtlColor(material, PFMTL_SPECULAR, m->specular[0], m->specular[1], m->specular[2]);
        }

        if (m->defined & MTL_HAS_SHININESS)
        {
            pfMtlShininess(material, m->shininess);
        }

        if (m->defined & MTL_HAS_ALPHA)
        {
            pfMtlAlpha(material, m->alpha);
            pfdBldrStateMode(PFSTATE_TRANSPARENCY, PFTR_ON);
        }

        pfdBldrStateAttr(PFSTATE_FRONTMTL, material);

        if (m->defined & MTL_IS_TWO_SIDED)
        {
            pfLModelTwoSide(lm, PF_ON);
            pfdBldrStateAttr(PFSTATE_LIGHTMODEL, lm);
            pfdBldrStateMode(PFSTATE_CULLFACE, PFCF_OFF);

            pfdBldrStateAttr(PFSTATE_BACKMTL, material);
        }
    }

    if (m->defined & (MTL_HAS_TEXTURE | MTL_HAS_REFLECTION))
    {
        pfTexture *tex = NULL;
        int j, comp, foo;
        unsigned int *image;
        char *sp;

        /* convert texture name from blah.rla to blah.rgb etc. */
        for (j = 0; j < numSuffix; j++)
        {
            char texPath[PF_MAXSTRING];

            /* remove ".rla" or whatever suffix from file name */
            if ((sp = strrchr(m->texture, (int)'.')) != NULL)
                *sp = '\0';

            /* append one of the known image file extensions */
            strcat(m->texture, suffix[j]);

            /* see if file is locatable with this extension */
            if (pfFindFile(m->texture, texPath, R_OK))
                break;
        }

        if (j < numSuffix)
        {
            pfTexture *realTexture;

            pfTexName(texture, m->texture);

            if (m->clamp==PF_ON)
                pfTexRepeat(texture, PFTEX_WRAP, PFTEX_CLAMP);
            pfdBldrStateAttr(PFSTATE_TEXTURE, texture);

            realTexture = (pfTexture *)pfdGetBldrStateAttr(PFSTATE_TEXTURE);
            pfGetTexImage(realTexture, &image, &comp, &foo, &foo, &foo);

            if (comp == 2 || comp == 4)
            {
                pfdBldrStateMode(PFSTATE_ALPHAFUNC, PFAF_GREATER);
                pfdBldrStateVal(PFSTATE_ALPHAREF, (4.0f/255.0f));
                pfdBldrStateMode(PFSTATE_TRANSPARENCY, PFTR_ON);
            }

            if (m->defined & MTL_HAS_REFLECTION)
            {
                pfTexGen *texturegen = (pfTexGen *)
                    pfdGetTemplateObject(pfGetTGenClassType());

                if (strcmp(m->reflect, "sphere") == 0)
                {
                    pfTGenMode(texturegen, PF_S, PFTG_SPHERE_MAP);
                    pfTGenMode(texturegen, PF_T, PFTG_SPHERE_MAP);
                }
                pfdBldrStateAttr(PFSTATE_TEXGEN, texturegen);
                pfdBldrStateMode(PFSTATE_ENTEXGEN, PF_ON);

                pfdResetObject((pfObject *)texturegen);
            }
        }
        else
        {
            /* file not found -- set name to NULL string */
            if ((sp = strrchr(m->texture, (int)'.')) != NULL)
                *sp = '\0';
            pfNotify(PFNFY_WARN, PFNFY_RESOURCE,
                "can't find texture \"%s\" for material \"%s\"",
                m->texture, m->name);
        }
    }

    pfdSaveBldrState(strdup(m->name));

    pfdResetObject((pfObject *)material);
    pfdResetObject((pfObject *)texture);
    pfdResetObject((pfObject *)lm);

    pfdResetBldrState();

    m->defined |= MTL_HAS_GEOSTATE;
}

static void
useMtl (char *name)
{
    int i;

    /* reset to default state */
    pfdResetBldrState();

    /* look for name in material list */
    for (i = 0; i < mtlListCount; i++)
    {
        if (strcmp(name, mtlList[i].name) == 0)
        {
            /* is this a "missing" material */
            if (mtlList[i].defined & MTL_NOT_DEFINED)
            {
            }
            else
            {
                /* define state before first use */
                if ((mtlList[i].defined & MTL_HAS_GEOSTATE) == 0){
                    defineMtl(&mtlList[i]);
                }

                /* specify the state by name */
                pfdLoadBldrState(name);
            }

            return;
        }
    }
    
    /* if we can't find material, then use default */
    if (i >= mtlListCount)
    {
        objMaterial missing;

        /* print warning once */
        pfNotify(PFNFY_WARN, PFNFY_RESOURCE,
            "material \"%s\" not defined", name);

        /* remember missing material's name and status */
        resetMaterial(&missing);
        strcpy(missing.name, name);
        missing.defined = MTL_NOT_DEFINED;
        rememberMaterial(&missing);
    }
}

/*
 * parse wafefront material file texture definition
 */
static void
parseTexture (char *next, objMaterial *m)
{
    int width;

    /* set default texture scale factors */
    m->su = 1.0f;
    m->sv = 1.0f;
    m->clamp = PF_OFF;

    /* parse texture name */
    sscanf(next, "%s%n", m->texture, &width);

    /* parse texture option strings */
    do
    {
        next += width;

        if (strcmp(m->texture, "-mm") == 0)
            sscanf(next, "%f %f %s%n", &m->su, &m->sv, m->texture, &width);
        else
        if (strcmp(m->texture, "-s") == 0)
            sscanf(next, "%f %f %*f %s%n", &m->su, &m->sv, m->texture, &width);
        else
        if (strcmp(m->texture, "-t") == 0)
            sscanf(next, "%f %f %*f %s%n", &m->su, &m->sv, m->texture, &width);
        else
        if (strcmp(m->texture, "-type") == 0)
            sscanf(next, "%s %s%n", m->reflect, m->texture, &width);
        else
        if (strcmp(m->texture, "-clamp") == 0) {
            char val[32];
            sscanf(next, "%s %s%n", val, m->texture, &width);
            if (strcmp(val, "on") == 0)
                m->clamp = PF_ON;
        }
        else
            break;
    } while (1);
}

static char *mtlFileList[MAX_MTL_FILES];
static int mtlCount = 0;

static void
forgetMaterialFiles (void)
{
    int i;

    for (i = 0; i < mtlCount; i++)
        if (mtlFileList[i] != NULL)
            free(mtlFileList[i]);

    mtlCount = 0;
}

/*
 * load a wavefront-format material file (".mtl")
 */
static void
loadMtl (char *fileName)
{
    FILE *mtlFile;
    char buffer[BUFFER_SIZE];
    char token[BUFFER_SIZE];
    char *next;
    char *backslash;
    int width = 0;
    int i;
    int inProgress = 0;
    objMaterial current;

    /* have we already loaded this file ? */
    for (i = 0; i < mtlCount; i++)
        if (strcmp(fileName, mtlFileList[i]) == 0)
        {
#ifdef VERBOSE
            /* indicate redundant load request */
            pfNotify(PFNFY_DEBUG, PFNFY_INTERNAL,
                "material library %s already loaded", fileName);
#endif
            /* bail out */
            return;
        }

    /* remember file name for future reference */
    if (i < MAX_MTL_FILES)
    {
        mtlFileList[mtlCount++] = strdup(fileName);
    }
    else
    {
        pfNotify(PFNFY_DEBUG, PFNFY_INTERNAL,
            "more than %d unique mtllibs. enlarge MAX_MTL_FILES",
            MAX_MTL_FILES);
    }

    /* open file */
    if ((mtlFile = pfdOpenFile(fileName)) == NULL)
    {
        pfNotify(PFNFY_DEBUG, PFNFY_INTERNAL,
            "can't open material library %s", fileName);
        return;
    }

    /* read Wavefront ".mtl" file */
    while (fgets(buffer, BUFFER_SIZE, mtlFile) != NULL)
    {
        /* concatenate continuation lines */
        while ((backslash = strchr(buffer, '\\')) != NULL)
            if (fgets(backslash, BUFFER_SIZE - strlen(buffer), mtlFile) == NULL)
                break;

        /* find first non-"space" character in line */
        for (next = buffer; *next != '\0' && isspace(*next); next++)
            /* EMPTY */ {};

        /* skip blank lines and comments ('$' is comment in "cow.obj") */
        if (*next == '\0' || *next == '#' || *next == '!' || *next == '$')
            continue;

        /* extract token */
        sscanf(next, "%s%n", token, &width);
        next += width;

        /* identify token */
        if (SAME(token, "newmtl"))
        {
            /* save material definition */
            if (inProgress)
                rememberMaterial(&current);

            resetMaterial(&current);
            inProgress = 1;
            sscanf(next, "%s", current.name);
        }
        else
        if (SAME(token, "Ka"))
        {
            sscanf(next, "%f %f %f",
                &current.ambient[0],
                &current.ambient[1],
                &current.ambient[2]);
            current.defined |= MTL_HAS_AMBIENT;
        }
        else
        if (SAME(token, "Kd"))
        {
            sscanf(next, "%f %f %f",
                &current.diffuse[0],
                &current.diffuse[1],
                &current.diffuse[2]);
            current.defined |= MTL_HAS_DIFFUSE;
        }
        else
        if (SAME(token, "Ks"))
        {
            sscanf(next, "%f %f %f",
                &current.specular[0],
                &current.specular[1],
                &current.specular[2]);
            current.defined |= MTL_HAS_SPECULAR;
        }
        else
        if (SAME(token, "Tr"))
        {
            float alpha = 1.0f;
            sscanf(next, "%f", &alpha);
            current.alpha = 1.0f - alpha;
            current.defined |= MTL_HAS_ALPHA;
        }
        else
        if (SAME(token, "Ns"))
        {
            sscanf(next, "%f", &current.shininess);

            /* clamp shininess range */
            if (current.shininess < 0.0f)
                current.shininess = 0.0f;
            else
            if (current.shininess > 128.0f)
                current.shininess = 128.0f;

            current.defined |= MTL_HAS_SHININESS;
        }
        else
        if (SAME(token, "map_Kd"))
        {
            parseTexture(next, &current);
            current.defined |= MTL_HAS_TEXTURE;
        }
        else
        if (SAME(token, "refl")) /* to be modified */
        {
            strcpy(current.reflect, "sphere");
            parseTexture(next, &current);
            current.defined |= MTL_HAS_REFLECTION;
        }
        else
        if (SAME(token, "d"))
        {
            float alpha = 1.0f;
            sscanf(next, "%f", &alpha);
            current.alpha = alpha; /* Maybe it should be 1 - alpha ?? */
            current.defined |= MTL_HAS_ALPHA;
        }
        else
        if (SAME(token, "illum"))
        {
            long illumModel;
            
            sscanf(next, "%d", &illumModel);
            
            /* Constant illumination model */
            if (illumModel == 0) {
                /* Set emissive colour to the diffuse color, rest to black */
                PFCOPY_VEC3(current.emission, current.diffuse);
                PFSET_VEC3(current.ambient, 0.0f, 0.0f, 0.0f);
                PFSET_VEC3(current.diffuse, 0.0f, 0.0f, 0.0f);
                PFSET_VEC3(current.specular, 0.0f, 0.0f, 0.0f);
                current.defined |= MTL_HAS_EMISSION;
            }
            /* "transparent" material, take this here to mean geometry textured
             * with see through textures so that require to see the back face
             * of these polygons. Set up a suitable geostate.
             */
            else
            if (illumModel == 4) {
                current.defined |= MTL_IS_TWO_SIDED;
            }
        }
        else
        if (
            SAME(token, "Ni") ||
            SAME(token, "Tf") ||
            SAME(token, "bump") ||
            SAME(token, "decal") ||
            SAME(token, "map_Ka") ||
            SAME(token, "map_Ks") ||
            SAME(token, "map_Ns") ||
            SAME(token, "map_d") ||
            SAME(token, "sharpness") ||
            SAME(token, "vp"))
        {
            numSkip++;
        }
#ifndef STRICT_OBJ_FORMAT
        /*
         * indicate that this material is two-sided
         */
        else
        if (SAME(token, "TWOSIDE"))
        {
            current.defined |= MTL_IS_TWO_SIDED;
        }
#endif
        else
        {
            pfNotify(PFNFY_WARN, PFNFY_INTERNAL, " unrecognized: %s", buffer);
            numOther++;
        }
    }

    if (inProgress)
        rememberMaterial(&current);

    /* close Wavefront ".mtl" file */
    fclose(mtlFile);
}


New Message Reply Date view Thread view Subject view Author view

This archive was generated by hypermail 2b29 : Tue Apr 17 2001 - 07:00:30 PDT

This message has been cleansed for anti-spam protection. Replace '++at++' in any mail addresses with the '@' symbol.