UPDATE: Jan 11th, 2011
- Updated code below
- Function "LoadObj" now has an extra parameter "SmoothNormals". When set to 1, the object will have it's normals smoothed and be able to be saved out to a .dbo file correctly.
- Fix: changed "local_tci" to "local_ni" when updating VertexData normals.
UPDATE: Jan 3rd, 2011
- Updated code below:
- Some obj files have extra spaces in them for whatever reason.
- Now the parser skips the extra spaces.
Hi,
The OBJ file format is a simple data-format that represents 3D geometry. It is commonly used by various 3D modeling apps. One such app that I like and use is Sculptris. Found at:
http://www.zbrushcentral.com/showthread.php?t=090617
This program reads in the obj text file and stores each line in a dynamic array of strings. During that process, it retrieves the obj 3D data. This site is where I got obj info to help me extract the data I wanted:
http://en.wikipedia.org/wiki/Obj
The only data it currently extracts is the vertex positions, texture coords, normals and face info. It only extracts 3 point triangle faces. The obj can hold 4 point quad face polygons and more( If you need help for me to add at least quad face polygons support, just ask and supply me with an obj file that uses quads ). Sculptris doesn't supply normals when exporting for me( mabie I just don't know how to export normals correctly ). So, I had to use the
Set Object Normals and
Set Object Smoothing commands to get smoothed normals. Note that saving out to a dbo file after creating normals in this way will not save the normals to the dbo file. I would
assume creating a custom function to create normals for the object with the vertex data commands would solve that problem to save out to a dbo correctly.
After extracting the needed data, it creates a glob of polygons needed for building the obj object with. Basically, it starts by creating a triangle object, make a mesh of that object, add then add the needed amount of triangle polygons by adding the mesh triangle to the original object as a limbs. After that, make a new mesh from the object with all the limbs. Then make the final object from the mesh. This is so the object is made out of only 1 mesh of triangles. No limbs. This is what the code looks like:
MAKE OBJECT TRIANGLE ObjectID,0,0,0,1,1,0,2,0,0 ` here: triangle coordinates don't matter
MAKE MESH FROM OBJECT 1, ObjectID
FOR local_index=1 TO gNumberOfFaces-1
ADD LIMB ObjectID,local_index,1
NEXT
DELETE MESH 1
MAKE MESH FROM OBJECT 1, ObjectID
DELETE OBJECT ObjectID
MAKE OBJECT ObjectID,1,ImageID
DELETE MESH 1
After creating an object with the needed amount of polygons, it will then manipulate the object's data by reading in the stored obj data and applying it using the DBPro's VertexData Commands.
LoadObj Functions:
UPDATED: Jan 11th, 2011
rem Load in a .obj 3d file
FUNCTION LoadObj(Filename$,ObjectID,ImageID,SmoothNormals)
LOCAL local_index,local_index2 AS INTEGER
LOCAL local_count AS INTEGER
LOCAL vertex_index AS INTEGER
LOCAL indice_index AS INTEGER
LOCAL local_vi AS INTEGER
LOCAL local_tci AS INTEGER
LOCAL local_ni AS INTEGER
LOCAL local_vx AS FLOAT
LOCAL local_vy AS FLOAT
LOCAL local_vz AS FLOAT
LOCAL local_u AS FLOAT
LOCAL local_v AS FLOAT
LOCAL local_nx AS FLOAT
LOCAL local_ny AS FLOAT
LOCAL local_nz AS FLOAT
rem store file in memory/array of strings
OPEN TO READ 1,Filename$
IF FILE OPEN(1)=1
REPEAT
READ STRING 1,thisLine$
IF thisLine$<>""
ADD TO STACK array_string(0)
local_index = ARRAY COUNT(array_string(0))
array_string(local_index)=thisLine$
ENDIF
UNTIL FILE END(1)=1
ENDIF
CLOSE FILE 1
local_count = ARRAY COUNT(array_string(0))
FOR local_index=1 TO local_count
IF UPPER$(LEFT$(array_string(local_index),2))="V "
ADD TO STACK array_vertex(0)
local_index2 = ARRAY COUNT(array_vertex(0))
array_vertex(local_index2).x=GetFloatFromString(array_string(local_index),2)
array_vertex(local_index2).y=GetFloatFromString(array_string(local_index),gThisStringPosition)
array_vertex(local_index2).z=GetFloatFromString(array_string(local_index),gThisStringPosition)
ENDIF
IF UPPER$(LEFT$(array_string(local_index),3))="VT "
ADD TO STACK array_texcoord(0)
local_index2 = ARRAY COUNT(array_texcoord(0))
array_texcoord(local_index2).u=GetFloatFromString(array_string(local_index),3)
array_texcoord(local_index2).v=GetFloatFromString(array_string(local_index),gThisStringPosition)
ENDIF
IF UPPER$(LEFT$(array_string(local_index),3))="VN "
ADD TO STACK array_normal(0)
local_index2 = ARRAY COUNT(array_normal(0))
array_normal(local_index2).x=GetFloatFromString(array_string(local_index),3)
array_normal(local_index2).y=GetFloatFromString(array_string(local_index),gThisStringPosition)
array_normal(local_index2).z=GetFloatFromString(array_string(local_index),gThisStringPosition)
ENDIF
IF UPPER$(LEFT$(array_string(local_index),2))="F "
ADD TO STACK array_face(0)
local_index2 = ARRAY COUNT(array_face(0))
gIndiceStep=1 : array_face(local_index2).V1Vertex=GetIntegerFromString(array_string(local_index),2)
gIndiceStep=2 : array_face(local_index2).V1TexCoord=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=3 : array_face(local_index2).V1Normal=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=1 : array_face(local_index2).V2Vertex=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=2 : array_face(local_index2).V2TexCoord=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=3 : array_face(local_index2).V2Normal=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=1 : array_face(local_index2).V3Vertex=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=2 : array_face(local_index2).V3TexCoord=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=3 : array_face(local_index2).V3Normal=GetIntegerFromString(array_string(local_index),gThisStringPosition)
ENDIF
NEXT
gNumberOfVertices=ARRAY COUNT(array_vertex(0))
gNumberOfFaces=ARRAY COUNT(array_face(0))
rem Build Obj Mesh
MAKE OBJECT TRIANGLE ObjectID,0,0,0,1,1,0,2,0,0 ` here: triangle coordinates don't matter
MAKE MESH FROM OBJECT 1, ObjectID
FOR local_index=1 TO gNumberOfFaces-1
ADD LIMB ObjectID,local_index,1
NEXT
DELETE MESH 1
MAKE MESH FROM OBJECT 1, ObjectID
DELETE OBJECT ObjectID
MAKE OBJECT ObjectID,1,ImageID
DELETE MESH 1
indice_index=0
vertex_index=0
LOCK VERTEXDATA FOR LIMB ObjectID, 0
FOR local_index=1 TO gNumberOfFaces
local_vi=array_face(local_index).V1Vertex
local_vx=array_vertex(local_vi).x
local_vy=array_vertex(local_vi).y
local_vz=array_vertex(local_vi).z
SET VERTEXDATA POSITION vertex_index,local_vx,local_vy,local_vz
local_tci=array_face(local_index).V1TexCoord
IF local_tci>0
local_u = array_texcoord(local_tci).u
local_v = array_texcoord(local_tci).v
SET VERTEXDATA UV vertex_index,0,local_u,-local_v
ELSE
SET VERTEXDATA UV vertex_index,0,0
ENDIF
local_ni=array_face(local_index).V1Normal
IF local_ni>0
local_nx=array_normal(local_ni).x
local_ny=array_normal(local_ni).y
local_nz=array_normal(local_ni).z
SET VERTEXDATA NORMALS vertex_index,local_nx,local_ny,local_nz
ELSE
SET VERTEXDATA NORMALS vertex_index,0,0,0
ENDIF
SET INDEXDATA indice_index,vertex_index
INC vertex_index : INC indice_index
local_vi=array_face(local_index).V2Vertex
local_vx=array_vertex(local_vi).x
local_vy=array_vertex(local_vi).y
local_vz=array_vertex(local_vi).z
SET VERTEXDATA POSITION vertex_index,local_vx,local_vy,local_vz
local_tci=array_face(local_index).V2TexCoord
IF local_tci>0
local_u = array_texcoord(local_tci).u
local_v = array_texcoord(local_tci).v
SET VERTEXDATA UV vertex_index,0,local_u,-local_v
ELSE
SET VERTEXDATA UV vertex_index,0,0
ENDIF
local_ni=array_face(local_index).V2Normal
IF local_ni>0
local_nx=array_normal(local_ni).x
local_ny=array_normal(local_ni).y
local_nz=array_normal(local_ni).z
SET VERTEXDATA NORMALS vertex_index,local_nx,local_ny,local_nz
ELSE
SET VERTEXDATA NORMALS vertex_index,0,0,0
ENDIF
SET INDEXDATA indice_index,vertex_index
INC vertex_index : INC indice_index
local_vi=array_face(local_index).V3Vertex
local_vx=array_vertex(local_vi).x
local_vy=array_vertex(local_vi).y
local_vz=array_vertex(local_vi).z
SET VERTEXDATA POSITION vertex_index,local_vx,local_vy,local_vz
local_tci=array_face(local_index).V3TexCoord
IF local_tci>0
local_u = array_texcoord(local_tci).u
local_v = array_texcoord(local_tci).v
SET VERTEXDATA UV vertex_index,0,local_u,-local_v
ELSE
SET VERTEXDATA UV vertex_index,0,0
ENDIF
local_ni=array_face(local_index).V3Normal
IF local_ni>0
local_nx=array_normal(local_ni).x
local_ny=array_normal(local_ni).y
local_nz=array_normal(local_ni).z
SET VERTEXDATA NORMALS vertex_index,local_nx,local_ny,local_nz
ELSE
SET VERTEXDATA NORMALS vertex_index,0,0,0
ENDIF
SET INDEXDATA indice_index,vertex_index
INC vertex_index : INC indice_index
NEXT
UNLOCK VERTEXDATA
CALCULATE OBJECT BOUNDS ObjectID
rem Tip by Mattty Halewood, this code below will finalize
rem the object's smoothed normals when needed in order to
rem be saved out as a .dbo object correctly.
IF SmoothNormals=1
SET OBJECT NORMALS ObjectID
SET OBJECT SMOOTHING ObjectID,100
MAKE MESH FROM OBJECT 1, ObjectID
DELETE OBJECT ObjectID
MAKE OBJECT ObjectID,1,ImageID
DELETE MESH 1
ENDIF
ENDFUNCTION
rem used to parse a float out of the obj file
FUNCTION GetFloatFromString(thisString$,Position)
LOCAL local_length AS INTEGER
LOCAL local_index AS INTEGER
LOCAL local_done AS INTEGER
LOCAL local_string AS STRING
LOCAL local_float AS FLOAT
local_string=""
local_done=0
local_length=LEN(thisString$)
rem parse out empty spaces
IF (MID$(thisString$,Position)=" ")
REPEAT
Position=Position+1
rem If we hit the end of the line here, then the obj file wasn't written correct.
IF Position>=local_length THEN EXITFUNCTION 0.0
UNTIL (MID$(thisString$,Position)<>" ")
Position=Position-1
ENDIF
REPEAT
Position=Position+1
IF MID$(thisString$,Position)<>" "
local_string=local_string+MID$(thisString$,Position)
ELSE
gThisStringPosition=Position
local_done=1
ENDIF
UNTIL (Position=local_length) OR (local_done=1)
local_float=VAL(local_string)
ENDFUNCTION local_float
rem used to parse an integer out of the obj file
FUNCTION GetIntegerFromString(thisString$,Position)
LOCAL local_length AS INTEGER
LOCAL local_index AS INTEGER
LOCAL local_string AS STRING
LOCAL local_int AS INTEGER
local_string=""
local_done=0
local_length=LEN(thisString$)
rem In case of "f v1/vt1 v2/vt2 v3/vt3 ...", return 0 for normal indice
IF (gIndiceStep=3)
IF (Position>=local_length)
EXITFUNCTION 0
ELSE
IF (MID$(thisString$,Position)=" ")
EXITFUNCTION 0
ENDIF
ENDIF
ENDIF
rem In case of "f v1 v2 v3 v4 ..."
IF (gIndiceStep=2) AND (MID$(thisString$,Position)=" ")
gThisStringPosition=Position
EXITFUNCTION 0
ENDIF
rem In case of "f v1//vn1 v2//vn2 v3//vn3 ...", return 0 for texture coordinates indice
IF (gIndiceStep=2) AND (MID$(thisString$,Position)="/") AND (MID$(thisString$,Position+1)="/")
gThisStringPosition=Position+1
EXITFUNCTION 0
ENDIF
rem if string position > string length then nothing left to process
IF (Position>=local_length) THEN EXITFUNCTION 0
rem parse out empty spaces
IF (gIndiceStep=1) AND (MID$(thisString$,Position)=" ")
REPEAT
Position=Position+1
rem If we hit the end of the line here, then the obj file wasn't written correct.
IF Position>=local_length THEN EXITFUNCTION 0
UNTIL (MID$(thisString$,Position)<>" ")
Position=Position-1
ENDIF
REPEAT
Position=Position+1
IF (MID$(thisString$,Position)<>" ") AND (MID$(thisString$,Position)<>"/")
local_string=local_string+MID$(thisString$,Position)
ELSE
gThisStringPosition=Position
local_int=VAL(local_string)
EXITFUNCTION local_int
ENDIF
UNTIL Position>=local_length
gThisStringPosition=Position
local_int=VAL(local_string)
ENDFUNCTION local_int
Note: To render an obj object in a DirectX based program, I had to reverse order the obj's triangle vertices and also reverse the V texture coordinate to display texturing right. Again, note that if you try to load an obj file with normal data, it may not work right.
If you have any problems with this, please let me know. Thanks.
Full Example:
* I've attached the BadGuy1 obj if you need it to test with.
* Press W/S to go Forward/Backwards
* Right Mouse Click to rotate camera view
* Press Esc to escape
UPDATED: Jan 11th, 2011
NOTE: Sculptris create objects large in size.
In FUNCTION InitDisplaySetup(), I have positioned the camera -1000 to fully see the badguy1.obj in screen. If you have a small object in size, you might want to position the camera closer.
rem ###################################################################
rem Load Object - By: Todd Riggins - ExoDev.Com - Dec. 8, 2010
rem 3D Object Format Info: http://en.wikipedia.org/wiki/Obj
rem
rem History:
rem updated Jan. 11th, 2011
rem - Function "LoadObj" now has an extra parameter "SmoothNormals". When set to 1, the object will have it's normals smoothed and be able to be saved out to a .dbo file correctly.
rem - Fix: changed "local_tci" to "local_ni" when updating VertexData normals.
rem updated Jan. 3rd, 2011
rem - Parse out empty spaces that some obj files may have
rem ###################################################################
rem ###################################################################
rem GLOBALS
rem ###################################################################
GLOBAL global_CamID as INTEGER
DIM array_string(0) AS STRING
TYPE TYPE_VERTEX
x AS FLOAT
y AS FLOAT
z AS FLOAT
ENDTYPE
DIM array_vertex(0) AS TYPE_VERTEX
TYPE TYPE_TEXCOORD
u AS FLOAT
v AS FLOAT
ENDTYPE
DIM array_texcoord(0) AS TYPE_TEXCOORD
TYPE TYPE_NORMAL
x AS FLOAT
y AS FLOAT
z AS FLOAT
ENDTYPE
DIM array_normal(0) AS TYPE_NORMAL
TYPE TYPE_FACE
V1Vertex AS INTEGER
V1TexCoord AS INTEGER
V1Normal AS INTEGER
V2Vertex AS INTEGER
V2TexCoord AS INTEGER
V2Normal AS INTEGER
V3Vertex AS INTEGER
V3TexCoord AS INTEGER
V3Normal AS INTEGER
ENDTYPE
DIM array_face(0) AS TYPE_FACE
GLOBAL gThisStringPosition AS INTEGER
GLOBAL gIndiceStep AS INTEGER
GLOBAL gNumberOfVertices,gNumberOfFaces AS INTEGER
GLOBAL Global_CamID AS INTEGER
GLOBAL Global_RightMouseClick AS INTEGER
rem ###################################################################
rem Start Main Loop
rem ###################################################################
Main()
END
rem ###################################################################
rem Main Loop
rem ###################################################################
FUNCTION Main()
LOCAL local_ObjectID AS INTEGER
LOCAL local_SpaceHelm2Image AS INTEGER
rem Initialize Display Setup
InitDisplaySetup()
rem Retrieve image used by obj 3d object
local_SpaceHelm2Image = GetNewImageID()
LOAD IMAGE "BadGuy1.png",local_SpaceHelm2Image,0
rem Load the obj 3d file
local_ObjectID = GetNewObjectID()
LoadObj("BadGuy1.obj",local_ObjectID,local_SpaceHelm2Image,1)
rem Rotate the object to face the camera
ROTATE OBJECT local_ObjectID,0,180,0
rem Main loop
REPEAT
UserControls()
TEXT 10,30,"Faces:"+str$(gNumberOfFaces)
TEXT 1,1,"FPS:"+STR$(SCREEN FPS())
SYNC
UNTIL ESCAPEKEY()=1
ENDFUNCTION
rem ###################################################################
rem FUNCTIONS
rem ###################################################################
rem Setup Display and Camera
FUNCTION InitDisplaySetup()
SYNC ON
SYNC RATE 30
SET WINDOW POSITION 0,0
SET WINDOW LAYOUT 0,0,0
MAXIMIZE WINDOW
SET DISPLAY MODE DESKTOP WIDTH(),DESKTOP HEIGHT(),0, 0
SET WINDOW OFF
rem create camera
global_CamID = 1
MAKE CAMERA global_CamID
POSITION CAMERA global_CamID,0,0,-1000
COLOR BACKDROP global_CamID, RGB(16,16,16)
SET CAMERA FOV global_CamID,45
SET CAMERA RANGE global_CamID,1,10000
HIDE LIGHT 0
rem create light to follow camera
MAKE LIGHT 1
SET LIGHT RANGE 1,7000
POSITION LIGHT 1,CAMERA POSITION X(global_CamID),CAMERA POSITION Y(global_CamID),CAMERA POSITION Z(global_CamID)
DISABLE ESCAPEKEY
ENDFUNCTION
Function UserControls()
LOCAL local_CameraAngleX as FLOAT
LOCAL local_CameraAngleY as FLOAT
LOCAL local_mmovex as INTEGER
LOCAL local_mmovex as INTEGER
local_mmovex = mousemovex()
local_mmovey = mousemovey()
` Camera Control:
` Move Camera Position
`W=17
if (keystate(17)=1)
Move Camera global_CamID,20.0
endif
`S=31
if (keystate(31)=1)
Move Camera global_CamID,-20.0
endif
`A=30
if (keystate(30)=1)
`
endif
`D=32
if (keystate(32)=1)
`
endif
` Rotate camera on right click
if mouseclick()=2
if Global_RightMouseClick = 0
Global_RightMouseClick = 1
hide mouse
endif
` keep mouse centered when rotating camera
position mouse screen width()/2, screen height()/2
` Get Current Anlges
local_CameraAngleX = Camera Angle X(global_CamID)
local_CameraAngleY = Camera Angle Y(global_CamID)
` create a rotation axis based on controller movement
local_CameraAngleX = WrapValue ( local_CameraAngleX + (local_mmovey) )
local_CameraAngleY = WrapValue ( local_CameraAngleY + (local_mmovex) )
` rotate camera and lock x-axis between 0 and 90
if ((( local_CameraAngleX >= 0 ) and ( local_CameraAngleX <= 90 )) or (( local_CameraAngleX >= 270 ) and ( local_CameraAngleX < 360 )))
XRotate Camera global_CamID, local_CameraAngleX
endif
YRotate Camera global_CamID, local_CameraAngleY
else
if mouseclick()=0 and Global_RightMouseClick = 1
Global_RightMouseClick = 0
position mouse screen width()/2, screen height()/2
show mouse
endif
endif
rem keep light at current camera position
POSITION LIGHT 1,CAMERA POSITION X(global_CamID),CAMERA POSITION Y(global_CamID),CAMERA POSITION Z(global_CamID)
EndFunction
rem Load in a .obj 3d file
FUNCTION LoadObj(Filename$,ObjectID,ImageID,SmoothNormals)
LOCAL local_index,local_index2 AS INTEGER
LOCAL local_count AS INTEGER
LOCAL vertex_index AS INTEGER
LOCAL indice_index AS INTEGER
LOCAL local_vi AS INTEGER
LOCAL local_tci AS INTEGER
LOCAL local_ni AS INTEGER
LOCAL local_vx AS FLOAT
LOCAL local_vy AS FLOAT
LOCAL local_vz AS FLOAT
LOCAL local_u AS FLOAT
LOCAL local_v AS FLOAT
LOCAL local_nx AS FLOAT
LOCAL local_ny AS FLOAT
LOCAL local_nz AS FLOAT
rem store file in memory/array of strings
OPEN TO READ 1,Filename$
IF FILE OPEN(1)=1
REPEAT
READ STRING 1,thisLine$
IF thisLine$<>""
ADD TO STACK array_string(0)
local_index = ARRAY COUNT(array_string(0))
array_string(local_index)=thisLine$
ENDIF
UNTIL FILE END(1)=1
ENDIF
CLOSE FILE 1
local_count = ARRAY COUNT(array_string(0))
FOR local_index=1 TO local_count
IF UPPER$(LEFT$(array_string(local_index),2))="V "
ADD TO STACK array_vertex(0)
local_index2 = ARRAY COUNT(array_vertex(0))
array_vertex(local_index2).x=GetFloatFromString(array_string(local_index),2)
array_vertex(local_index2).y=GetFloatFromString(array_string(local_index),gThisStringPosition)
array_vertex(local_index2).z=GetFloatFromString(array_string(local_index),gThisStringPosition)
ENDIF
IF UPPER$(LEFT$(array_string(local_index),3))="VT "
ADD TO STACK array_texcoord(0)
local_index2 = ARRAY COUNT(array_texcoord(0))
array_texcoord(local_index2).u=GetFloatFromString(array_string(local_index),3)
array_texcoord(local_index2).v=GetFloatFromString(array_string(local_index),gThisStringPosition)
ENDIF
IF UPPER$(LEFT$(array_string(local_index),3))="VN "
ADD TO STACK array_normal(0)
local_index2 = ARRAY COUNT(array_normal(0))
array_normal(local_index2).x=GetFloatFromString(array_string(local_index),3)
array_normal(local_index2).y=GetFloatFromString(array_string(local_index),gThisStringPosition)
array_normal(local_index2).z=GetFloatFromString(array_string(local_index),gThisStringPosition)
ENDIF
IF UPPER$(LEFT$(array_string(local_index),2))="F "
ADD TO STACK array_face(0)
local_index2 = ARRAY COUNT(array_face(0))
gIndiceStep=1 : array_face(local_index2).V1Vertex=GetIntegerFromString(array_string(local_index),2)
gIndiceStep=2 : array_face(local_index2).V1TexCoord=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=3 : array_face(local_index2).V1Normal=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=1 : array_face(local_index2).V2Vertex=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=2 : array_face(local_index2).V2TexCoord=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=3 : array_face(local_index2).V2Normal=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=1 : array_face(local_index2).V3Vertex=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=2 : array_face(local_index2).V3TexCoord=GetIntegerFromString(array_string(local_index),gThisStringPosition)
gIndiceStep=3 : array_face(local_index2).V3Normal=GetIntegerFromString(array_string(local_index),gThisStringPosition)
ENDIF
NEXT
gNumberOfVertices=ARRAY COUNT(array_vertex(0))
gNumberOfFaces=ARRAY COUNT(array_face(0))
rem Build Obj Mesh
MAKE OBJECT TRIANGLE ObjectID,0,0,0,1,1,0,2,0,0 ` here: triangle coordinates don't matter
MAKE MESH FROM OBJECT 1, ObjectID
FOR local_index=1 TO gNumberOfFaces-1
ADD LIMB ObjectID,local_index,1
NEXT
DELETE MESH 1
MAKE MESH FROM OBJECT 1, ObjectID
DELETE OBJECT ObjectID
MAKE OBJECT ObjectID,1,ImageID
DELETE MESH 1
indice_index=0
vertex_index=0
LOCK VERTEXDATA FOR LIMB ObjectID, 0
FOR local_index=1 TO gNumberOfFaces
rem First index point of triangle
local_vi=array_face(local_index).V1Vertex
local_vx=array_vertex(local_vi).x
local_vy=array_vertex(local_vi).y
local_vz=array_vertex(local_vi).z
SET VERTEXDATA POSITION vertex_index,local_vx,local_vy,local_vz
local_tci=array_face(local_index).V1TexCoord
IF local_tci>0
local_u = array_texcoord(local_tci).u
local_v = array_texcoord(local_tci).v
SET VERTEXDATA UV vertex_index,0,local_u,-local_v
ELSE
SET VERTEXDATA UV vertex_index,0,0
ENDIF
local_ni=array_face(local_index).V1Normal
IF local_ni>0
local_nx=array_normal(local_ni).x
local_ny=array_normal(local_ni).y
local_nz=array_normal(local_ni).z
SET VERTEXDATA NORMALS vertex_index,local_nx,local_ny,local_nz
ELSE
SET VERTEXDATA NORMALS vertex_index,0,0,0
ENDIF
SET INDEXDATA indice_index,vertex_index
INC vertex_index : INC indice_index
rem Second index point of triangle
local_vi=array_face(local_index).V2Vertex
local_vx=array_vertex(local_vi).x
local_vy=array_vertex(local_vi).y
local_vz=array_vertex(local_vi).z
SET VERTEXDATA POSITION vertex_index,local_vx,local_vy,local_vz
local_tci=array_face(local_index).V2TexCoord
IF local_tci>0
local_u = array_texcoord(local_tci).u
local_v = array_texcoord(local_tci).v
SET VERTEXDATA UV vertex_index,0,local_u,-local_v
ELSE
SET VERTEXDATA UV vertex_index,0,0
ENDIF
local_ni=array_face(local_index).V2Normal
IF local_ni>0
local_nx=array_normal(local_ni).x
local_ny=array_normal(local_ni).y
local_nz=array_normal(local_ni).z
SET VERTEXDATA NORMALS vertex_index,local_nx,local_ny,local_nz
ELSE
SET VERTEXDATA NORMALS vertex_index,0,0,0
ENDIF
SET INDEXDATA indice_index,vertex_index
INC vertex_index : INC indice_index
rem Third index point of triangle
local_vi=array_face(local_index).V3Vertex
local_vx=array_vertex(local_vi).x
local_vy=array_vertex(local_vi).y
local_vz=array_vertex(local_vi).z
SET VERTEXDATA POSITION vertex_index,local_vx,local_vy,local_vz
local_tci=array_face(local_index).V3TexCoord
IF local_tci>0
local_u = array_texcoord(local_tci).u
local_v = array_texcoord(local_tci).v
SET VERTEXDATA UV vertex_index,0,local_u,-local_v
ELSE
SET VERTEXDATA UV vertex_index,0,0
ENDIF
local_ni=array_face(local_index).V3Normal
IF local_ni>0
local_nx=array_normal(local_ni).x
local_ny=array_normal(local_ni).y
local_nz=array_normal(local_ni).z
SET VERTEXDATA NORMALS vertex_index,local_nx,local_ny,local_nz
ELSE
SET VERTEXDATA NORMALS vertex_index,0,0,0
ENDIF
SET INDEXDATA indice_index,vertex_index
INC vertex_index : INC indice_index
NEXT
UNLOCK VERTEXDATA
CALCULATE OBJECT BOUNDS ObjectID
rem Tip by Mattty Halewood, this code below will finalize
rem the object's smoothed normals when needed in order to
rem be saved out as a .dbo object correctly.
IF SmoothNormals=1
SET OBJECT NORMALS ObjectID
SET OBJECT SMOOTHING ObjectID,100
MAKE MESH FROM OBJECT 1, ObjectID
DELETE OBJECT ObjectID
MAKE OBJECT ObjectID,1,ImageID
DELETE MESH 1
ENDIF
ENDFUNCTION
rem used to parse a float out of the obj file
FUNCTION GetFloatFromString(thisString$,Position)
LOCAL local_length AS INTEGER
LOCAL local_index AS INTEGER
LOCAL local_done AS INTEGER
LOCAL local_string AS STRING
LOCAL local_float AS FLOAT
local_string=""
local_done=0
local_length=LEN(thisString$)
rem parse out empty spaces
IF (MID$(thisString$,Position)=" ")
REPEAT
Position=Position+1
rem If we hit the end of the line here, then the obj file wasn't written correct.
IF Position>=local_length THEN EXITFUNCTION 0.0
UNTIL (MID$(thisString$,Position)<>" ")
Position=Position-1
ENDIF
REPEAT
Position=Position+1
IF MID$(thisString$,Position)<>" "
local_string=local_string+MID$(thisString$,Position)
ELSE
gThisStringPosition=Position
local_done=1
ENDIF
UNTIL (Position=local_length) OR (local_done=1)
local_float=VAL(local_string)
ENDFUNCTION local_float
rem used to parse an integer out of the obj file
FUNCTION GetIntegerFromString(thisString$,Position)
LOCAL local_length AS INTEGER
LOCAL local_index AS INTEGER
LOCAL local_string AS STRING
LOCAL local_int AS INTEGER
local_string=""
local_done=0
local_length=LEN(thisString$)
rem In case of "f v1/vt1 v2/vt2 v3/vt3 ...", return 0 for normal indice
IF (gIndiceStep=3)
IF (Position>=local_length)
EXITFUNCTION 0
ELSE
IF (MID$(thisString$,Position)=" ")
EXITFUNCTION 0
ENDIF
ENDIF
ENDIF
rem In case of "f v1 v2 v3 v4 ..."
IF (gIndiceStep=2) AND (MID$(thisString$,Position)=" ")
gThisStringPosition=Position
EXITFUNCTION 0
ENDIF
rem In case of "f v1//vn1 v2//vn2 v3//vn3 ...", return 0 for texture coordinates indice
IF (gIndiceStep=2) AND (MID$(thisString$,Position)="/") AND (MID$(thisString$,Position+1)="/")
gThisStringPosition=Position+1
EXITFUNCTION 0
ENDIF
rem if string position > string length then nothing left to process
IF (Position>=local_length) THEN EXITFUNCTION 0
rem parse out empty spaces
IF (gIndiceStep=1) AND (MID$(thisString$,Position)=" ")
REPEAT
Position=Position+1
rem If we hit the end of the line here, then the obj file wasn't written correct.
IF Position>=local_length THEN EXITFUNCTION 0
UNTIL (MID$(thisString$,Position)<>" ")
Position=Position-1
ENDIF
REPEAT
Position=Position+1
IF (MID$(thisString$,Position)<>" ") AND (MID$(thisString$,Position)<>"/")
local_string=local_string+MID$(thisString$,Position)
ELSE
gThisStringPosition=Position
local_int=VAL(local_string)
EXITFUNCTION local_int
ENDIF
UNTIL Position>=local_length
gThisStringPosition=Position
local_int=VAL(local_string)
ENDFUNCTION local_int
rem find unused object ID
Function GetNewObjectID()
ObjectID=0
repeat
inc ObjectID
until object exist(ObjectID)=0
EndFunction ObjectID
rem find unused image ID
Function GetNewImageID()
imageID=0
repeat
inc imageID
until image exist(imageID)=0
EndFunction imageID