Python .OBJ to .REO Converter

Anything to do with Drakan level editing and modifications, post it here! Also this is the place to tell us about your new levels and get player feedback.
Post Reply
unterbuchse
Whelp
Posts: 17
Joined: Thu Feb 27, 2014 2:46 am

Python .OBJ to .REO Converter

Post 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 10620 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()
    
Last edited by unterbuchse on Thu Aug 11, 2016 4:31 am, edited 1 time in total.

UCyborg
Dragon
Posts: 433
Joined: Sun Jul 07, 2013 7:24 pm
Location: Slovenia

Re: Python .OBJ to .REO Converter

Post 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.
"When a human being takes his life in depression, this is a natural death of spiritual causes. The modern barbarity of 'saving' the suicidal is based on a hair-raising misapprehension of the nature of existence." - Peter Wessel Zapffe

unterbuchse
Whelp
Posts: 17
Joined: Thu Feb 27, 2014 2:46 am

Re: Python .OBJ to .REO Converter

Post 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.

UCyborg
Dragon
Posts: 433
Joined: Sun Jul 07, 2013 7:24 pm
Location: Slovenia

Re: Python .OBJ to .REO Converter

Post 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.
"When a human being takes his life in depression, this is a natural death of spiritual causes. The modern barbarity of 'saving' the suicidal is based on a hair-raising misapprehension of the nature of existence." - Peter Wessel Zapffe

Post Reply