www.xbdev.net
xbdev - software development
Wednesday April 24, 2024
home | contact | Support | 3D Formats Where else are you going to keep your 3D data?...

     
 

3D Formats

Where else are you going to keep your 3D data?...

 

3DS Part 6- "object orientated" - 3DSClass

by bkenwright@xbdev.net

<code v9>

At the end of this we'll have our self contained re-usable 3DSClass...and the best thing about it is...its free to be used with DirectX or OpenGL or any Exporter you want....

 

 

DownloadCode

// Our .3DS file that we'll use for testing.

#define FILENAME "multishape.3ds"

 

/***************************************************************************/

#include <windows.h>

 

 

#include "3ds.h"

 

 

/***************************************************************************/

/*                                                                         */

/* The program entry point, this is where we start and end.                */

/*                                                                         */

/***************************************************************************/

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)

{

      C3DS myObj;

      myObj.Create(FILENAME);

     

      DisplayRawData( &myObj);

 

      myObj.Release();

 

      return 0;

}

 

/***************************************************************************/

/*                                                                         */

/*                            www.xbdev.net                                */

/*                         bkenwright@xbdev.net                            */

/*                                3ds.h                                    */

/*                                                                         */

/***************************************************************************/

/***************************************************************************/

/*

   What we have here is a totally self contained 3DS file loader, that is

   easy to use and extremely powerful.

 

   Using it?

   <1> #include "3ds.h"

   <2> C3DS myObj;

   <3> myObj.Create("cube.3ds");

 

   <4> Access the 3D data through the m_Object member..vertices etc

       myObj.m_Object.m_iNumMaterials;

         myObj.m_Object.m_iNumMeshs;

         .. etc

 

   <5> myObj.Release(); // Tidy up when you've finished.

 

*/

/***************************************************************************/

 

#pragma once

 

#include <stdio.h>

 

#include <vector>

using namespace std ;

 

 

 

//>----- Entry point (Primary Chunk at the start of the file ----------------

#define           PRIMARY                 0x4D4D

 

//>----- Main Chunks --------------------------------------------------------

#define           EDIT3DS                 0x3D3D  // Start of our actual objects

#define           KEYF3DS                 0xB000  // Start of the keyframe information

 

//>----- General Chunks -----------------------------------------------------

#define           VERSION                 0x0002

#define           MESH_VERSION      0x3D3E

#define           KFVERSION         0x0005

#define           COLOR_F                 0x0010

#define           COLOR_24          0x0011

#define           LIN_COLOR_24      0x0012

#define           LIN_COLOR_F       0x0013

#define           INT_PERCENTAGE    0x0030

#define           FLOAT_PERC        0x0031

#define           MASTER_SCALE      0x0100

#define           IMAGE_FILE        0x1100

#define           AMBIENT_LIGHT     0X2100

 

//>----- Object Chunks -----------------------------------------------------

#define           NAMED_OBJECT      0x4000

#define           OBJ_MESH          0x4100

#define           MESH_VERTICES     0x4110

#define           VERTEX_FLAGS      0x4111

#define           MESH_FACES        0x4120

#define           MESH_MATER        0x4130

#define           MESH_TEX_VERT     0x4140

#define           MESH_XFMATRIX     0x4160

#define           MESH_COLOR_IND    0x4165

#define           MESH_TEX_INFO     0x4170

#define           HEIRARCHY         0x4F00

 

 

//>----- Material Chunks ---------------------------------------------------

#define           MATERIAL          0xAFFF

#define           MAT_NAME          0xA000

#define           MAT_AMBIENT       0xA010

#define           MAT_DIFFUSE       0xA020

#define           MAT_SPECULAR      0xA030

#define           MAT_SHININESS     0xA040

#define           MAT_FALLOFF       0xA052

#define           MAT_EMISSIVE      0xA080

#define           MAT_SHADER        0xA100

#define           MAT_TEXMAP        0xA200

#define           MAT_TEXFLNM       0xA300

 

#define           OBJ_LIGHT         0x4600

#define           OBJ_CAMERA        0x4700

 

//>----- KeyFrames Chunks --------------------------------------------------

#define           ANIM_HEADER       0xB00A

#define           ANIM_OBJ          0xB002

 

#define           ANIM_NAME         0xB010

#define           ANIM_POS          0xB020

#define           ANIM_ROT          0xB021

#define           ANIM_SCALE        0xB022

 

 

 

 

class C3DS

{

public:

 

      struct stMaterial

      {

            char szName[256];

            struct{ unsigned char r,g,b ; }Colour;

            char szTextureFile[256];

      };

 

      struct stVert

      {

            float x, y, z;

      };

      struct stFace

      {   // 3 Sides of a triangle make a face.

            unsigned int A, B, C;

            int MaterialID;

      };

      struct stTex

      {

            float tu, tv;

      };

 

      struct stMesh

      {

            char         szMeshName[256];

            int          iNumVerts;

            stVert*      pVerts;

            int          iNumFaces;

            stFace*      pFaces;

            stTex*       pTexs;

 

            stMesh()

            {

                  iNumVerts  = 0;

                  pVerts     = NULL;

                  iNumFaces  = 0;

                  pFaces     = NULL;

                  pTexs      = NULL;

            }

           

      };

 

 

protected:

      struct stChunk

      {

            unsigned short ID;

            unsigned int length;

            unsigned int bytesRead;

      };

 

public:

      C3DS(void);

      ~C3DS(void);

 

      bool Create(char* szFileName);

      void Release();

public:

 

      int                m_iNumMeshs;

      vector<stMesh>     m_pMeshs;

 

      int                m_iNumMaterials;

      vector<stMaterial> m_pMaterials;

 

protected:

 

      FILE* m_fp;

 

      void ParseChunk        (stChunk* Chunk);

      void GetMaterialName   (stChunk* Chunk);

      void GetDiffuseColour  (stChunk* Chunk);

      void GetTexFileName    (stChunk* Chunk);

      void GetMeshObjectName (stChunk* Chunk);

      void ReadMeshTexCoords (stChunk* Chunk);

      void ReadMeshVertices  (stChunk* Chunk);

      void ReadMeshFaces     (stChunk* Chunk);

      void ReadMeshMaterials (stChunk* Chunk);

      int GetString          (char* pBuffer);

      void SkipChunk         (stChunk *pChunk);

      void ReadChunk         (stChunk *pChunk);

 

};

 

 

 

 

// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG

// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG

// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG

// Debugging Function

void debug_op(char *s);

void DisplayRawData(C3DS* pObj);

 

/***************************************************************************/

/*                                                                         */

/*                            www.xbdev.net                                */

/*                         bkenwright@xbdev.net                            */

/*                                3ds.cpp                                  */

/*                                                                         */

/***************************************************************************/

 

#include "3ds.h"

 

C3DS::C3DS(void)

{

    m_iNumMeshs     = 0;

 

      m_iNumMaterials = 0;

}

 

C3DS::~C3DS(void){}

 

 

/***************************************************************************/

/*                                                                         */

/* Some user functions to make the reading of the .3ds file easier         */

/*                                                                         */

/***************************************************************************/

/* Helper Functions that make our parsing of the chunks easier             */

/***************************************************************************/

 

 

void C3DS::ReadChunk(stChunk *pChunk)

{

      unsigned short ID             = 0;

      unsigned int bytesRead        = 0;

      unsigned int bChunkLength     = 0;

 

      bytesRead = (unsigned int)fread(&ID, 1, 2, m_fp);

 

      bytesRead += (unsigned int)fread(&bChunkLength, 1, 4, m_fp);

 

      pChunk->ID          = ID;

      pChunk->length      = bChunkLength;

      pChunk->bytesRead = bytesRead;

 

}

 

void C3DS::SkipChunk(stChunk *pChunk)

{

      int buffer[50000] = {0};

 

      fread(buffer, 1, pChunk->length - pChunk->bytesRead, m_fp);

}

 

/***************************************************************************/

/*                                                                         */

/* Helper Fuction, simply reads in the string from the file pointer, until */

/* a null is reached, then returns how many bytes was read.                */

/*                                                                         */

/***************************************************************************/

int C3DS::GetString(char* pBuffer)

{

      int index = 0;

 

      char buffer[100] = {0};

 

      fread(buffer, 1, 1, m_fp);

 

      while( *(buffer + index++) != 0)

      {

            fread(buffer + index, 1, 1, m_fp);

      }

 

      strcpy( pBuffer, buffer );

 

      return (int)(strlen(buffer) + 1);

}

 

/***************************************************************************/

/*                                                                         */

/* This little function reads the matrial data for our individual object,  */

/* So it determines which face references which material, in our material  */

/* list.                                                                   */

/*                                                                         */

/***************************************************************************/

void C3DS::ReadMeshMaterials(stChunk* Chunk)

{

      // Material Name Where Referencing

      char str[256];

      unsigned int characterlen = GetString(str);

      Chunk->bytesRead += characterlen;

 

      unsigned short iNumFaces = 0;

      Chunk->bytesRead += (unsigned int)fread(&iNumFaces, 1, 2, m_fp);

 

      unsigned short *FaceAssignedThisMaterial = new unsigned short[iNumFaces];

      Chunk->bytesRead += (unsigned int)fread(FaceAssignedThisMaterial, 1,

                                                      iNumFaces*sizeof(unsigned short), m_fp);

 

      // Determine Which Material It Is In Our List

      int MaterialID = 0;

      for( int cc=0; cc<m_iNumMaterials; cc++)

      {

            if( strcmp( str, m_pMaterials[cc].szName ) == 0 )

                  MaterialID = cc;

      }

 

      stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);

      for(int i=0; i<iNumFaces; i++)

      {

            int iIndex = FaceAssignedThisMaterial[i];

            pMesh->pFaces[iIndex].MaterialID = MaterialID;

      }

 

      return;

}

 

/***************************************************************************/

/*                                                                         */

/* We get all the faces...e.g. Triangle Index's into our vertex array, so  */

/* we can actually put our shape together.                                 */

/*                                                                         */

/***************************************************************************/

void C3DS::ReadMeshFaces(stChunk* Chunk)

{

      unsigned int iNumberFaces = 0; //= Chunk->length - 6;

      Chunk->bytesRead += (unsigned int)fread(&iNumberFaces, 1, 2, m_fp);    

 

      // Each face is 3 points A TRIANGLE!..WOW

      struct stXFace{ unsigned short p1, p2, p3, visibityflag; };

      stXFace *pFaces = new stXFace[iNumberFaces];

 

      Chunk->bytesRead += (unsigned int)fread(pFaces, 1, iNumberFaces*sizeof(stXFace), m_fp);

 

      stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);

      pMesh->pFaces = new stFace[iNumberFaces];

      pMesh->iNumFaces = iNumberFaces;

 

      for( unsigned int i=0; i<iNumberFaces; i++ )

      {

            pMesh->pFaces[i].A = pFaces[i].p1;

            pMesh->pFaces[i].B = pFaces[i].p2;

            pMesh->pFaces[i].C = pFaces[i].p3;

      }

 

      delete[] pFaces;

     

 

      // Our face material information is a sub-chunk.

      ParseChunk(Chunk);

}

 

/***************************************************************************/

/*                                                                         */

/* You know all those x,y,z things...yup I mean vertices...well this reads */

/* them all in.                                                            */

/*                                                                         */

/***************************************************************************/

void C3DS::ReadMeshVertices(stChunk* Chunk)

{

      unsigned int iNumberVertices = 0;

      Chunk->bytesRead += (unsigned int)fread(&iNumberVertices, 1, 2, m_fp); 

 

      // Allocate Memory and dump our vertices to the screen.

      stVert *pVerts = new stVert[iNumberVertices];

 

      Chunk->bytesRead += (unsigned int)fread( (void*)pVerts, 1, iNumberVertices*sizeof(stVert), m_fp);

 

      stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);

      pMesh->pVerts = pVerts;

      pMesh->iNumVerts = iNumberVertices;

     

      SkipChunk(Chunk);

}

 

/***************************************************************************/

/*                                                                         */

/* Well if we have a texture, e.g. coolimage.bmp, then we need to load in  */

/* our texture coordinates...tu and tv.                                    */

/*                                                                         */

/***************************************************************************/

void C3DS::ReadMeshTexCoords(stChunk* Chunk)

{

      unsigned short iNumberVertices = 0;

      Chunk->bytesRead += (unsigned int)fread(&iNumberVertices, 1, 2, m_fp); 

 

      // Allocate Memory and dump our texture for each vertice to the screen.

      stTex *pTex = new stTex[iNumberVertices];

 

      Chunk->bytesRead += (unsigned int)fread( (void*)pTex, 1, iNumberVertices*sizeof(stTex), m_fp);

 

      stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);

      pMesh->pTexs = pTex;

     

      SkipChunk(Chunk);

}

 

 

/***************************************************************************/

/*                                                                         */

/* Read in our objects name...as each object in our 3D world has a name,   */

/* for example Box1, HillMesh...whatever you called your object or object's*/

/* in 3d max before you saved it.                                          */

/*                                                                         */

/***************************************************************************/

void C3DS::GetMeshObjectName(stChunk* Chunk)

{

      // The strange thing is, the next few parts of this chunk represent

      // the name of the object.  Then we start chunking again.

      char str[256];

      unsigned int characterlen = GetString(str);

      Chunk->bytesRead += characterlen;

 

      stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);

      strcpy( pMesh->szMeshName, str );

 

      ParseChunk(Chunk);

}

 

// Read in our texture's file name (e.g. coolpic.jpg)

void C3DS::GetTexFileName(stChunk* Chunk)

{

      char str[256];

      Chunk->bytesRead += GetString(str);

 

      stMaterial* pMat = &(m_pMaterials[m_iNumMaterials-1]);

      strcpy( pMat->szTextureFile, str );

}

 

// Read in our diffuse colour (rgb)

void C3DS::GetDiffuseColour(stChunk* Chunk)

{

      struct stRGB{ unsigned char r, g, b; };

      stRGB DiffColour;

 

      char ChunkHeader[6];

      Chunk->bytesRead += (unsigned int)fread(ChunkHeader, 1, 6, m_fp);

 

      Chunk->bytesRead += (unsigned int)fread(&DiffColour, 1, 3, m_fp);

 

      stMaterial* pM = &(m_pMaterials[m_iNumMaterials-1]);

      pM->Colour.r = DiffColour.r;

      pM->Colour.g = DiffColour.g;

      pM->Colour.b = DiffColour.b;

 

      SkipChunk(Chunk);

}

 

// Get the materials name, e.g. default-2- etc

void C3DS::GetMaterialName(stChunk* Chunk)

{

      char str[256];

      Chunk->bytesRead += GetString(str);

 

      stMaterial* pM = &(m_pMaterials[m_iNumMaterials-1]);

      strcpy(pM->szName, str);

}

/***************************************************************************/

/*                                                                         */

/* If theres a nested sub-chunk, and we know its ID, e.g 0xA000 etc, then  */

/* we can simply add its ID to the switch list, and add a calling sub      */

/* functino which will deal with it.  Else just skip over that Chunk...    */

/* and carry on parsing the rest of our file.                              */

/*                                                                         */

/***************************************************************************/

 

void C3DS::ParseChunk(stChunk* Chunk)

{

      while(Chunk->bytesRead < Chunk->length)

      {

            stChunk tempChunk = {0};

            ReadChunk(&tempChunk);

 

            switch( tempChunk.ID)

            {

            // HEADER OUR ENTRY POINT

            case EDIT3DS: //0x3D3D

                  ParseChunk(&tempChunk);

                  break;

 

            // MATERIALS

            case MATERIAL: //0xAFFF

                  stMaterial newMaterial;

                  m_pMaterials.push_back(newMaterial);

                  m_iNumMaterials++;

                  ParseChunk(&tempChunk);

                  break;

            case MAT_NAME: //0xA000 - sz for hte material name "e.g. default 2"

                  GetMaterialName(&tempChunk);

                  break;

            case MAT_DIFFUSE:  // Diffuse Colour  //0xA020

                  GetDiffuseColour(&tempChunk);

                  break;

            case MAT_TEXMAP:  //0xA200 - if there's a texture wrapped to it where here

                  ParseChunk(&tempChunk);

                  break;

            case MAT_TEXFLNM: // 0xA300 -  get filename of the material

                  GetTexFileName(&tempChunk);

                  break;

 

            // OBJECT - MESH'S

            case NAMED_OBJECT: //0x4000

                  {

                  stMesh newMesh;

                  m_pMeshs.push_back(newMesh);

                  m_iNumMeshs++;

                  GetMeshObjectName(&tempChunk);

                  }

                  break;

            case OBJ_MESH:     //0x4100

                  ParseChunk(&tempChunk);

                  break;

            case MESH_VERTICES: //0x4110

                  ReadMeshVertices(&tempChunk);

                  break;

            case MESH_FACES: //0x4120

                  ReadMeshFaces(&tempChunk);

                  break;

            case MESH_TEX_VERT: //0x4140

                  ReadMeshTexCoords(&tempChunk);

                  break;

            case MESH_MATER: //0x4130

                  ReadMeshMaterials(&tempChunk);

                  break;

 

            default:

                  SkipChunk(&tempChunk);

            }

           

            Chunk->bytesRead += tempChunk.length;

      }

}

 

/***************************************************************************/

/*                                                                         */

/* Read in .3ds file.                                                      */

/*                                                                         */

/***************************************************************************/

 

bool C3DS::Create(char* szFileName)

{

      m_fp = fopen(szFileName, "rb");

     

      stChunk Chunk = {0};

      ReadChunk(&Chunk);

 

      ParseChunk(&Chunk );

     

      fclose(m_fp);

 

      return true;

}

 

void C3DS::Release()

{

      for(int i=0; i<m_iNumMeshs; i++)

      {

            delete[] m_pMeshs[i].pVerts;

            delete[] m_pMeshs[i].pFaces;

            delete[] m_pMeshs[i].pTexs;

      }

}

 

 

 

 

 

 

 

 

 

 

// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG

// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG

// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG

// Debugging Function to dump raw valeus to a file

 

void debug_op(char *s)

{

      FILE *fp;

      fp = fopen("t.txt", "a+");

      fprintf(fp, "%s", s);

      fclose(fp);

}

 

// Debugging Function - Simply put it dumps our data to a file, just to check

// that it has read it all in correctly.

 

void DisplayRawData(C3DS* pObj)

{

      char buf[500];

      for( int i=0; i<pObj->m_iNumMeshs; i++ )

      {

            C3DS::stMesh* pMesh = &(pObj->m_pMeshs[i]);

 

            sprintf(buf, "Shape: %s\n", pMesh->szMeshName);

            debug_op(buf);

 

            sprintf(buf, "iNumFaces: %d\n", pMesh->iNumFaces);

            debug_op(buf);

            for(int cc=0; cc<pMesh->iNumFaces; cc++)

            {

                  sprintf(buf, "\t %d, \t %d \t %d\n",

                                    pMesh->pFaces[cc].A, pMesh->pFaces[cc].B, pMesh->pFaces[cc].C);

                  debug_op(buf);

            }

 

            sprintf(buf, "iNumVertices: %d\n", pMesh->iNumVerts);

            debug_op(buf);

            for(  cc=0; cc<pMesh->iNumVerts; cc++)

            {

                  sprintf(buf, "\t %.2f, \t %.2f \t %.2f\n",

                        pMesh->pVerts[cc].x,pMesh->pVerts[cc].y,pMesh->pVerts[cc].z);

                  debug_op(buf);

            }

 

            if( pMesh->pTexs != NULL )

            {

                  sprintf(buf, "iNumTex: %d\n", pMesh->iNumVerts);

                  debug_op(buf);

                  for(  cc=0; cc<pMesh->iNumVerts; cc++)

                  {

                        sprintf(buf, "\t %.2f, \t %.2f\n",

                              pMesh->pTexs[cc].tu, pMesh->pTexs[cc].tv );

                        debug_op(buf);

                  }

            }

 

            if( pObj->m_iNumMaterials > 0 )

            {

                  sprintf(buf, "Material vs Faces: %d\n", pMesh->iNumFaces);

                  debug_op(buf);

                  for(  cc=0; cc<pMesh->iNumFaces; cc++)

                  {

                        sprintf(buf, "\t MaterialID: %d",

                              pMesh->pFaces[cc].MaterialID );

                        debug_op(buf);

 

                        int ID = pMesh->pFaces[cc].MaterialID;

                        sprintf(buf, "\t, Name: %s\n", pObj->m_pMaterials[ID].szName);

                        debug_op(buf);

                  }

            }

      }

}

 

 

 

Simple output using the debugger function in 3ds.h/.cpp which outputs the data that we've loaded in:

 

Shape: Box03

iNumFaces: 12

             0,         2          3

             3,         1          0

             4,         5          7

             7,         6          4

             8,         9          10

             11,       12        13

             14,       15        16

             17,       18        19

             20,       21        22

             23,       24        25

             26,       27        28

             29,       30        31

iNumVertices: 32

             -6.79,  -2.30    0.00

             -2.93,  -2.30    0.00

             -6.79,  5.29     0.00

             -2.93,  5.29     0.00

             -6.79,  -2.30    4.13

             -2.93,  -2.30    4.13

             -6.79,  5.29     4.13

             -2.93,  5.29     4.13

             -6.79,  -2.30    0.00

             -2.93,  -2.30    0.00

             -2.93,  -2.30    4.13

             -2.93,  -2.30    4.13

             -6.79,  -2.30    4.13

             -6.79,  -2.30    0.00

             -2.93,  -2.30    0.00

             -2.93,  5.29     0.00

             -2.93,  5.29     4.13

             -2.93,  5.29     4.13

             -2.93,  -2.30    4.13

             -2.93,  -2.30    0.00

             -2.93,  5.29     0.00

             -6.79,  5.29     0.00

             -6.79,  5.29     4.13

             -6.79,  5.29     4.13

             -2.93,  5.29     4.13

             -2.93,  5.29     0.00

             -6.79,  5.29     0.00

             -6.79,  -2.30    0.00

             -6.79,  -2.30    4.13

             -6.79,  -2.30    4.13

             -6.79,  5.29     4.13

             -6.79,  5.29     0.00

iNumTex: 32

             1.00,    0.00

             0.00,    0.00

             1.00,    1.00

             0.00,    1.00

             0.00,    0.00

             1.00,    0.00

             0.00,    1.00

             1.00,    1.00

             0.00,    0.00

             1.00,    0.00

             1.00,    1.00

             1.00,    1.00

             0.00,    1.00

             0.00,    0.00

             0.00,    0.00

             1.00,    0.00

             1.00,    1.00

             1.00,    1.00

             0.00,    1.00

             0.00,    0.00

             0.00,    0.00

             1.00,    0.00

             1.00,    1.00

             1.00,    1.00

             0.00,    1.00

             0.00,    0.00

             0.00,    0.00

             1.00,    0.00

             1.00,    1.00

             1.00,    1.00

             0.00,    1.00

             0.00,    0.00

Material vs Faces: 12

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

             MaterialID: 0   , Name: 8 - Default

 

 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.