Page 1 of 1

Python .OBJ to .REO Converter

Posted: Wed Aug 10, 2016 11:12 pm
by unterbuchse
Heyho,
Just finished the .obj to .reo converter. As always, it is not perfect but it works. It uses .obj and .mtl files to generate a textured model in .reo format.

Main problems:
- Of course there is no 1:1 conversion possible (bspheres, bboxes, lighting etc are not used in .obj definitions)
- Biggest problem so far: we have no normals! Or let me rephrase this: .reo files do not have explicit normals. This means that the definition that a face is directing in one or the other direction is depending on the order of the vertices during face definition. In an .obj file we have explicit normals, but they cannot be used in the .reo files. Of course one could interprete the normals and then try to find out the correct order of the vertices and then put that in the .reo file but seriously, that would just blow things up.
=> Right now, I used the blender obj exporter and found that without using the vn entries (explicit .obj normals) the vertex order of the faces always generates flipped (meaning: the normals pointed in the "wrong" direction) normals. So the current solution is that the converter just inverts the order of the vertices and hopes for the best.

Some eye candy (Look whos your new best buddy,... Spyro!)
OBJtoREOMonkey.jpg
(230.24 KiB) Downloaded 2095 times
Spyro.jpg
(249.7 KiB) Downloaded 2080 times
IngameSpyro.jpg
IngameSpyro.jpg (165.87 KiB) Viewed 12378 times
Heres the code:
OBJClass.py

Code: Select all

'''
Created on 11.08.2016

@author: BuXXe
'''

import time

class OBJ(object):
    '''
    classdocs
    '''


    def __init__(self):
        '''
        Constructor
        '''
        # Format: Dictionary of meterialname -> filename
        self.materials = {}
        
        self.vertices = []
        self.vertexTexcoord = []
        # keys are material names and values are lists of face definitions
        self.faces = {}
        
        self.facecount = 0
        self.version = "2.2"
        self.name = "Unnamed Model"
        self.author = "Anonymous"
        self.creationDate = time.strftime("%d.%m.%Y") 
        self.lighting = 1
        self.transform = [[1,0,0,0],[0,1,0,0],[0,0,1,0]]

        # bounding data
        self.bspheres = []
        self.bboxes = []
OBJConverter.py

Code: Select all

'''
Created on 11.08.2016

@author: BuXXe
'''


import OBJClass
import os

if __name__ == '__main__':
    
    # Filename without extension! (.reo .obj and .mtl will be appended in code)
    # TODO: Right now we assume the obj file is in the same directory as the OBJConverter.py
    filename="monkey"

    # read the obj file
    with open(filename+".obj", 'r') as f:
        read_data = f.readlines()
        f.close()
    
    # TODO: perhaps use only regex in future but for now keep it simple
    # TODO: perhaps we should use some kind of grammar or state machine to parse the file
    
    # iterate through the read_data
    linecounter = 0
    
    # create new object
    OBJ = OBJClass.OBJ()
    
    # set filename as model name
    # Change this if you want a different name
    OBJ.name = filename
    
    
    
    # INFO: Comments and empty lines will be ignored as for now
    while(linecounter < len(read_data)):
        if(read_data[linecounter][0] == '#' or read_data[linecounter][0] == '\n'):
            linecounter += 1
            continue
        
        # Build material dictionary
        elif(read_data[linecounter].startswith("mtllib")):
            mtlfile = read_data[linecounter].replace("mtllib ","",1).replace("\n","")
            # read the mtl file
            with open(mtlfile, 'r') as f:
                mtl_data = f.readlines()
                f.close()
            
            mtl_linecounter = 0
            while(mtl_linecounter < len(mtl_data)):
                if(mtl_data[mtl_linecounter].startswith("newmtl")):
                    # get material name
                    materialname = mtl_data[mtl_linecounter].replace("newmtl ","",1).replace("\n","")
                    # search for a map_Kd entry to get the texture
                if(mtl_data[mtl_linecounter].startswith("map_Kd")):
                    # set only filename as texture entry
                    print "happens"
                    OBJ.materials[materialname] = os.path.basename(mtl_data[mtl_linecounter].replace("map_Kd ","",1).replace("\n",""))
                
                mtl_linecounter+=1
            
        
                       
        # Build up lists of vertices vt and faces
        elif(read_data[linecounter].startswith("v ")):
            OBJ.vertices.append(read_data[linecounter].replace("v ","",1).replace("\n",""))
        
        elif(read_data[linecounter].startswith("vt ")):
            OBJ.vertexTexcoord.append(read_data[linecounter].replace("vt ","",1).replace("\n",""))
            
        elif(read_data[linecounter].startswith("usemtl")):
            # set active material which will be used by the following faces
            activeMaterial = read_data[linecounter].replace("usemtl ","",1).replace("\n","")
            # add entry to faces dictionary if not yet existing and initialize empty list
            if not activeMaterial in OBJ.faces:
                OBJ.faces[activeMaterial] = []
        
        elif(read_data[linecounter].startswith("f ")):
            OBJ.faces[activeMaterial].append(read_data[linecounter].replace("f ","",1).replace("\n",""))
            OBJ.facecount +=1
        
        linecounter += 1    
            
    

    
    # Create the .reo file
    with open(filename+".reo", 'w') as f:
        f.write("# Riot Engine Object\n")
        f.write("# Created with the .obj to .reo converter by BuXXe\n\n")
        
        f.write("version " + OBJ.version + "\n")
        f.write("name "+ OBJ.name +"\n")
        
        f.write("created by "+ OBJ.author+ " on " + OBJ.creationDate + "\n\n")
        
        f.write("Lighting " + str(OBJ.lighting) + "\n\n")
        
        #write materials
        f.write("materials " + str(len(OBJ.materials)) + "\n")
        for index,entry in enumerate(sorted(OBJ.materials)):
            f.write(str(index)+ " texture " + OBJ.materials[entry] + "\n")
        
        f.write("\n")
        f.write("transform\n")
        for en in OBJ.transform:
            f.write(" ".join([str(d) for d in en])+"\n")
        
        f.write("\n")
        f.write("vertices "+ str(len(OBJ.vertices)) +"\n")
        
        for index, vert in enumerate(OBJ.vertices):
            f.write(str(index) +" " + vert + "\n")
            
        f.write("\n")
        f.write("faces " +str(OBJ.facecount)+ "\n")
        f.write("\n")
        
        # create blocks and print them
        facecounter = 0
        
        
        for material in sorted(OBJ.faces):
            for face in OBJ.faces[material]:
                f.write("polygon "+str(facecounter)+"\n")
                facecounter+=1
                fparts = face.split(" ")
                               
                f.write("vt "+str(len(fparts))+":" + " ".join([str(int(d.split("/")[0]) - 1) for d in reversed(fparts)]) + "\n")
                f.write("ma " + str(sorted(OBJ.materials).index(material)) + "\n")
                f.write("tu "+ " ".join(  [ OBJ.vertexTexcoord[(int(d.split("/")[1]) - 1)].split(" ")[0] for d in reversed(fparts)]) + "\n")
                f.write("tv "+ " ".join(  [ OBJ.vertexTexcoord[(int(d.split("/")[1]) - 1)].split(" ")[1] for d in reversed(fparts)]) + "\n")
                f.write("\n")
        
        f.close()
    

Re: Python .OBJ to .REO Converter

Posted: Thu Aug 11, 2016 12:52 am
by UCyborg
Nice work! So to put it simply, REO format is a bit inconvenient in practice, but it's a necessary evil to get custom models in the game and be able to have properly working collision detection? Well, as "properly" as it works anyway, you know how easy it is to get through some locked doors due to collision detection bugs.

Re: Python .OBJ to .REO Converter

Posted: Thu Aug 11, 2016 4:37 am
by unterbuchse
I just added some more screenshots from a little test. Perhaps my skills using the editor are just bad but I think there might be a bug. As you can see I positioned Spyro with custom rotation and scale parameters (Instance Properties) and these changes were also correctly displayed in the 3D View. But in the game, the model appears with the standard parameters without any rotation and scaling.

The boundings for the collision detection could be created using blender as well but then we need some special converter which interleaves the pure model data with the defined bounding spheres/boxes. But this would be a really complex thing so right now i would stick to the modeler as you have to use it anyway to ensure everything is working fine.

Re: Python .OBJ to .REO Converter

Posted: Thu Aug 11, 2016 1:04 pm
by UCyborg
I'm not the expert on the editor neither, but does changing scale/rotation work at least if you work with original files? I can change it for the stock model and it shows both in 3D and game.