Blender to MAP exporter

Post anything to do with editing Nexuiz here. Whether its problems you've had, questions, or if you just want to show off your work.

Moderators: Nexuiz Moderators, Moderators

Blender to MAP exporter

Postby z80 » Sun Jul 26, 2009 7:54 pm

Hi, here are links for the latest content related to this thread.
The exporter: http://www.box.net/shared/r5fqthmusy
Example "how to" map: http://www.box.net/shared/u2zm53r7i4.


Dear Sirs,
I've tried to use common Blender plugin (export_map.py) to use with NetRadiant. I've found a few drawbacks.
1) It export AS IS only cubes or cube like meshes. All other geometry is splitted into triangles and is extruded into prisms.
2) In doesn't work with textures at all.
3) It has no options concerning light properties Blender doesn't have but NetRadiant has.
4) Sometimes exported meshes appear to have strange artifacts.
5) It's impossible to see the result right in Blender. One is to open file in NetRadiant to check the export results.


I've spent a week and designed my one exporter form Blender to MAP format. From my point of view it has several fixes of problems met in current one. And these are the following.


1) It does check if mesh is convex or not and split it into triangles only if it doesn't And doesn't depend on mesh complexity.
2) It uses Game Logic Blender menu for storing properties Blender object doesn't have. For example for light it's "sun" "target" and "radius" properties.
3) It export textures and texture coordinates for each face in mesh!!!!!! (It was really hard to transform uv pairs into MAP formap).
4) It shows the export result right in Blender. So it's possible to check it and to change slightly right in Blender.

I would be very appreciate to everyone who test my plugin. It's code is the following:

Code: Select all
#!BPY

"""
Name: 'Nexuiz (.map)'
Blender: 243
Group: 'Export'
Tooltip: 'Export to Nexuiz map format'
"""

__author__ = 'sergey bashkirov'
__version__ = '0.1'
__email__ = "bashkirov.sergey@gmail.com"
__bpydoc__ = """\
This script Exports a Nexuiz map format.
Supports meshes, lights and nurbs patch surfaces
"""

from Blender import *
import BPyMesh
import os
import math
import string
from os import path

# export options.
opts = []
g_nexuiz_path = "c:/programs/nexuiz"
g_fname       = "mymap.map"
g_scale = 32
g_extrudeHeight = 0.3

def nexifyVector( x, y, z ):
    global g_scale
    a = round( x * g_scale )
    b = round( y * g_scale )
    c = round( z * g_scale )
    return a, b, c

def faceNormal( face, scaleVals=1, roundVals=1 ):
    # Calculating normal:
    x1, y1, z1 = face.v[0].co.x, \
                 face.v[0].co.y, \
                 face.v[0].co.z
    x2, y2, z2 = face.v[1].co.x, \
                 face.v[1].co.y, \
                 face.v[1].co.z
    x3, y3, z3 = face.v[2].co.x, \
                 face.v[2].co.y, \
                 face.v[2].co.z
    if scaleVals:
        global g_scale
        x1, y1, z1 = x1 * g_scale, y1 * g_scale, z1 * g_scale
        x2, y2, z2 = x2 * g_scale, y2 * g_scale, z2 * g_scale
        x3, y3, z3 = x3 * g_scale, y3 * g_scale, z3 * g_scale
    if roundVals:
        x1, y1, z1 = round( x1 ), round( y1 ), round( z1 )
        x2, y2, z2 = round( x2 ), round( y2 ), round( z2 )
        x3, y3, z3 = round( x3 ), round( y3 ), round( z3 )
    nx = (y2-y1)*(z3-z1)-(y3-y1)*(z2-z1)
    ny = (x3-x1)*(z2-z1)-(x2-x1)*(z3-z1)
    nz = (x2-x1)*(y3-y1)-(x3-x1)*(y2-y1)
    l = math.sqrt( nx * nx + ny * ny + nz * nz )
    nx = nx / l
    ny = ny / l
    nz = nz / l
    return nx, ny, nz


def main():
    if ( nexify() ):
        showGui()
        writeFile( g_nexuiz_path + "/data/maps/" + g_fname )
    else:
        warningGui()

def showGui():
    global g_nexuiz_path
    global g_fname
    global g_scale
    global g_extrudeHeight

    path = Get( "datadir" )
    fname = path + "/nexify.txt"
    if ( os.path.exists( fname ) ):
        file = open( fname, 'r' )
        if ( file ):
            g_nexuiz_path = file.readline()
            if ( g_nexuiz_path[ len(g_nexuiz_path) - 1 ] == "\n" ):
                g_nexuiz_path = g_nexuiz_path[ :(len(g_nexuiz_path) - 1) ]
            g_fname = file.readline()
            if ( g_fname[ len(g_fname) - 1 ] == "\n" ):
                g_fname = g_fname[ :(len(g_fname) - 1) ]
            g_scale = int( file.readline() )
            g_extrudeHeight = float( file.readline() )
            file.close()
    guiNexuizPath          = Draw.Create( g_nexuiz_path )
    guiFileName            = Draw.Create( g_fname )
    guiNexuizScale         = Draw.Create( g_scale )
    guiNexuizExtrudeHeight = Draw.Create( g_extrudeHeight )
   
    pup_block = [\
                    ( 'Nexuiz path:',   guiNexuizPath,          1, 128, 'Path to nexuiz root directory.'),\
                    ( 'Map file:',      guiFileName,            1, 128, 'Map file name.'),\
                    ( 'Grid scale',     guiNexuizScale,         1, 128, 'Grid scale (32).'),\
                    ( 'Extrude height', guiNexuizExtrudeHeight, 1, 128, 'Extrude height'),\
                ]
    if not Draw.PupBlock( 'Nexuiz map export', pup_block ):
        return
    Window.WaitCursor(1)
   
    g_nexuiz_path   = guiNexuizPath.val
    guiFileName     = guiFileName.val
    g_scale         = guiNexuizScale.val
    g_extrudeHeight = guiNexuizExtrudeHeight.val

    file = open( fname, 'w' )
    if ( file ):
        file.write( g_nexuiz_path + "\n" )
        file.write( g_fname + "\n" )
        file.write( str( g_scale ) + "\n" )
        file.write( str( g_extrudeHeight ) + "\n" )
        file.close()
       
def warningGui():
    global g_nexuiz_path
    guiNexuizPath = Draw.Create( g_nexuiz_path )
    pup_block = [ ( 'Nexuiz path:',   guiNexuizPath,          1, 128, 'Path to nexuiz root directory.') ]
    if not Draw.PupBlock( 'Some concave meshes were modified, \ncheck changes and launch again!', pup_block ):
        return
    Window.WaitCursor(1)   

def nexify():
    print( "*************************************************************" )
    print( "*************************************************************" )
    print( "*************************************************************" )
    res = 1
    # Disable edit mode!!!!
    if Window.EditMode():
        Window.EditMode( 0 )
    scene = Scene.GetCurrent()
    #List copy because I modify it inside the loop.
    objList = scene.objects
    for obj in objList:
        print( "\n____________________________________________" )
        print( "inspecting object: " )
        print( "        type = " + obj.getType() )
        print( "        name = " + obj.getName() )
        if ( obj.getType() == 'Mesh' ):
            propsList = obj.getAllProperties()
            print( "        object properties:" )
            for prop in propsList:
                print( "        " + prop.getName() + " = " + str( prop.getData() ) )
            skipObject = 0
            for prop in propsList:
                if prop.getName() == 'ignore':
                    if prop.getData():
                        skipObject = 1
                        break
            if ( skipObject ):
                print( "skipping this object due to 'ignore' property" )
                continue
            res = checkConvex( obj )
            obj.removeProperty( "convex" )
            if ( res ):
                print( "        shape = convex" )
                obj.addProperty( "convex", 1, 'BOOL' )
            else:
                print( "        shape = concave" )
                extrudeMesh( obj, g_extrudeHeight )
                obj.addProperty( "convex", 0, 'BOOL' )
                obj.addProperty( "ignore", 1, 'BOOL' )
                res = 0
            obj.makeDisplayList()
        elif ( obj.getType() == "Lamp" ):
            setLightDefaultFlags( obj );
   
    Window.RedrawAll()
    Redraw()
    return res


def checkConvex( obj ):
    mesh = NMesh.GetRawFromObject( obj.getName() )
    # First, check if each edge belongs to exactly two different faces.
    # Each edge belongs to exactly two different faces.
    for edge in mesh.edges:
        v1 = edge.v1
        v2 = edge.v2
        n = 0
        for face in mesh.faces:
            if ( ( face.v.count( v1 ) == 1 ) and ( face.v.count( v2 ) == 1 ) ):
                n = n + 1
        if ( n != 2 ):
            print( "        faces cnt doesn't match, expected 2, appeared " + str( n ) )
            return 0

    for edge in mesh.edges:
        v1 = edge.v1
        v2 = edge.v2
        nextEdge = 0
        for face_t in mesh.faces:
            if ( face_t.v.count( v1 ) == 1 ) and ( face_t.v.count( v2 ) == 1 ):
                # Looking for second face with
                for face_n in mesh.faces:
                    if ( face_n.v.count( v1 ) == 1 ) and ( face_n.v.count( v2 ) == 1 ) and ( face_n != face_t ):
                        # *************************************************************************************
                        # Faces desc:
                        # print( "face_t %d\n" % ( len( face_t.v ) ) )
                        # for v in face_t.v:
                            # print( "v[%d] = %.4f, %.4f, %.4f\n" % (v.index, v.co.x, v.co.y, v.co.z) )
                        # print( "face_n %d\n" % ( len( face_n.v ) ) )
                        # for v in face_n.v:
                            # print( "v[%d] = %.4f, %.4f, %.4f\n" % (v.index, v.co.x, v.co.y, v.co.z) )
                        # print( "n = %.4f, %.4f, %.4f\n" % (n[0], n[1], n[2]) )
                        # *************************************************************************************
                       
                        # For all edges must not be positive!
                        d = normal_x_tangent( v1, v2, face_t, face_n )
                        # if ( d <= 0 ):
                            # print( "convex\n" )
                        if ( d > 0 ):
                            return 0
                        nextEdge = 1
                        break
            if ( nextEdge ):
                break
       
    return 1
   
def normal_x_tangent( v1, v2, face_t, face_n ):
    global g_scale
    # Calculating normal:
    nx, ny, nz = faceNormal( face_n )
   
    x1, y1, z1 = nexifyVector( v1.co.x, v1.co.y, v1.co.z * g_scale )
    # x2, y2, z2 = round( v2.co.x * g_scale ), \
                 # round( v2.co.y * g_scale ), \
                 # round( v2.co.z * g_scale )
    for v in face_t.v:
        if ( v != v1 ) and ( v != v2 ):
            x3, y3, z3 = nexifyVector( v.co.x, v.co.y, v.co.z )

    # Looking for tangent vector:
    # t = ((z2-z1)*z3-z1*z2+z1*z1+(y2-y1)*y3-y1*y2+y1*y1+(x2-x1)*x3-x1*x2+x1*x1)/(z2*z2-2*z1*z2+z1*z1+y2*y2-2*y1*y2+y1*y1+x2*x2-2*x1*x2+x1*x1)
   
    # x = ( x2 - x1 ) * t + x1
    # y = ( y2 - y1 ) * t + y1
    # z = ( z2 - z1 ) * t + z1
   
    # vx = x3 - x
    # vy = y3 - y
    # vz = z3 - z

    vx, vy, vz = x3 - x1, y3 - y1, z3 - z1
   
    d = vx * nx + vy * ny + vz * nz

    # print( "v1: %.4f, %.4f, %.4f\n" % ( x1, y1, z1 ) )
    # print( "v2: %.4f, %.4f, %.4f\n" % ( x2, y2, z2 ) )
    # print( "v3: %.4f, %.4f, %.4f\n" % ( x3, y3, z3 ) )
    # print( "v4: %.4f, %.4f, %.4f\n" % ( x4, y4, z4 ) )
    # print( "n2: %.4f, %.4f, %.4f\n" % ( nx, ny, nz ) )
    # print( "v:  %.4f, %.4f, %.4f\n" % ( vx, vy, vz ) )
   
    return d


def checkConvexEx( obj ):
    global g_scale
    mesh = NMesh.GetRawFromObject( obj.getName() )
    # All points from one size of the mesh.
    for face in mesh.faces:
        nx, ny, nz = nexifyVector( face.no[0], face.no[1], face.no[2] )
        x0, y0, z0 = nexifyVector( face.v[0].co.x, face.v[0].co.y, face.v[0].co.z )
        for v in mesh.verts:
            x, y, z = nexifyVector( v.co.x, v.co.y, v.co.z )
            dot = nx * (x - x0) + ny * (y - y0) + nz * (z - z0)
            if ( dot > 0 ):
                print( "        positive dot product means concave region, data is the following:" )
                print( "            nx, ny, nz: %.4f, %.4f, %.4f\n" % ( nx, ny, nz ) )
                print( "            dx, dy, dz:  %.4f, %.4f, %.4f\n" % ( x-x0, y-y0, z-z0 ) )
                print( "            x0, y0, z0:  %.4f, %.4f, %.4f\n" % ( x0, y0, z0 ) )
                print( "            x, y, z:  %.4f, %.4f, %.4f\n" % ( x, y, z ) )
                return -1
    # Each edge belongs to exactly two different faces.
    for edge in mesh.edges:
        v1 = edge.v1
        v2 = edge.v2
        n = 0
        for face in mesh.faces:
            if ( ( face.v.count( v1 ) == 1 ) and ( face.v.count( v2 ) == 1 ) ):
                n = n + 1
        if ( n != 2 ):
            print( "        faces cnt doesn't match, expected 2, appeared " + str( n ) )
            return 0
    return 1
   
def extrudeMesh( obj, h ):
    global g_scale
    mesh = NMesh.GetRawFromObject( obj.getName() )
    name = mesh.name
    propsList = obj.getAllProperties()
    for prop in propsList:
        if ( prop.getName() == 'height' ):
            h_self = prop.getData()
            if ( h_self != 0 ):
                h = h_self
    r0 = obj.getLocation( 'worldspace' )
    i = 0
    for face in mesh.faces:
        newname = name + "." + str( i )
        newmesh = Mesh.New( newname )
        scene = Scene.GetCurrent()
        newobject = scene.objects.new( newmesh, newname )
        # Fill newmesh with vertices and faces.
        # Calculating normal:
        nx, ny, nz = faceNormal( face )
        nx = nx * h
        ny = ny * h
        nz = nz * h
       
        verts = []
        for v in face.v:
            verts.append( [ v.co.x + r0[0], v.co.y + r0[1], v.co.z + r0[2] ] )
        for v in face.v:
            verts.append( [ v.co.x + r0[0] + nx, v.co.y + r0[1] + ny, v.co.z + r0[2] + nz ] )
        n = len( face.v )
        faces = []
        if ( h < 0 ):
            # Initial face.
            faces.append( range(n) )
            # New face
            faces.append( range(2*n)[ (2*n-1):(n-1):(-1) ] )
        else:
            # Initial face.
            faces.append( range(n)[n::-1] )
            # New face
            faces.append( range(2*n)[ n:(2*n) ] )           
        # walls depend on if h > 0 or not.
        if ( h > 0 ):
            for i in range(n):
                faces.append( [ i, (i+1) % n, n + (i+1)%n, i+n ] )
        else:
            for i in range(n):
                faces.append( [ i, i+n, n + (i+1)%n, (i+1) % n ] )
        # Extend new mesh with verts and faces.
        print( "verts: " + str( len(verts) ) )
        print( verts )
        print( "faces: " + str( len(faces) ) )
        print( faces )
        newmesh.verts.extend( verts )
        newmesh.faces.extend( faces )
        newmesh.calcNormals()
        newobject.addProperty( "convex", 1, 'BOOL' )
        # Updating properties and mesh data.
        newmesh.update()
        newobject.makeDisplayList()
        i = i + 1
       
def setLightDefaultFlags( obj ):
    propsList = obj.getAllProperties()
    # Currently I add special props: sun, has_target, target_x, target_y, target_z, radius.
    propsToAdd = [ "sun", "has_target", "target_x", "target_y", "target_z", "radius" ]
    propsAlreadyExist = []
    for prop in propsList:
        if ( propsToAdd.count( prop.getName() ) > 0 ):
            propsAlreadyExist.append( prop.getName() )
    for prop in propsToAdd:
        # If not exist
        if ( propsAlreadyExist.count( prop ) == 0 ):
            if ( prop == "sun" ):
                obj.addProperty( prop, 0, 'BOOL' )
            elif ( prop == "has_target" ):
                obj.addProperty( prop, 0, 'BOOL' )
            elif ( prop == "target_x" ):
                obj.addProperty( prop, 0.0, 'FLOAT' )
            elif ( prop == "target_y" ):
                obj.addProperty( prop, 0.0, 'FLOAT' )
            elif ( prop == "target_z" ):
                obj.addProperty( prop, 0.0, 'FLOAT' )
            elif ( prop == "radius" ):
                obj.addProperty( prop, 0.0, 'FLOAT' )
    obj.makeDisplayList()

def writeFile( fname ):
    print 'Exporting:'
    file = open( fname, 'w' )

    objList = Object.Get()
    # Writing meshes.
    file.write( "// entity 0\n" )
    file.write( "{\n" )
    file.write( '    "classname" "worldspawn"\n' )
    for obj in objList:
        if ( obj.getType() == 'Mesh' ):
            skipObject = 0
            propList = obj.getAllProperties()
            for prop in propList:
                if prop.getName() == 'ignore':
                    if prop.getData():
                        skipObject = 1
                        break
                elif prop.getName() == 'convex':
                    if not prop.getData():
                        skipObject = 1
                        break
            if ( skipObject ):
                print( "skipping this object due to 'ignore' property" )
                continue
            writeMesh( file, obj )
    file.write( '}\n' )
    # Writing lamps.
    for obj in objList:
        if ( obj.getType() == "Lamp" ):
            propsList = obj.getAllProperties()
            skipObject = 0
            for prop in propsList:
                if prop.getName() == 'ignore':
                    if prop.getData():
                        skipObject = 1
                        break
            if ( skipObject ):
                print( "skipping this object due to 'ignore' property" )
                continue
            writeLamp( file, obj )
           
    file.close()
   
def writeMesh( file, obj ):
    global g_scale
    mesh = NMesh.GetRawFromObject( obj.getName() )
    r0 = obj.getLocation( 'worldspace' )
    #Composing a unique planes list.
    planes = []
    faces = []
    for face in mesh.faces:
        nx, ny, nz = faceNormal( face )
        nx, ny, nz = nexifyVector( nx, ny, nz )
        r0x, r0y, r0z = nexifyVector( face.v[0].co.x + r0[0], face.v[0].co.y + r0[1], face.v[0].co.z + r0[2] )
        a, b, c = nx, ny, nz
        d = round( -( nx * r0x + ny * r0y + nz * r0z ) )
        pl = [ a, b, c, d ]
        if ( planes.count( pl ) == 0 ):
            planes.append( pl )
            faces.append( face )
    # Writing planes
    stri =        '    // mesh "' + mesh.name + '"\n'
    stri = stri + '    {\n'
    for face in faces:
        stri = stri + '        '
        for v in face.v[2::-1]:
            x, y, z = nexifyVector( v.co.x + r0[0], v.co.y + r0[1], v.co.z + r0[2] )
            stri = stri + ( '( %d %d %d ) ' % ( x, y, z ) )
        # Shift x, shift y, angle, scale x, scale y, ......
        texture, x, y, ang, w, h = calcFaceUv( face )
        stri = stri + ( '%s %.8f %.8f %.8f %.8f %.8f 0 0 0\n' % ( texture, x, y, ang, w, h ) )
    stri = stri + '    }\n'
    file.write( stri )
   
def writeLamp( file, obj ):
    global g_scale
    lamp = obj.data
    stri = '{\n    "classname" "light"\n'
    stri = stri + '    "light" %.6f\n' % ( lamp.dist * g_scale )
    r = obj.getLocation( 'worldspace' )
    x, y, z = nexifyVector( r[0], r[1], r[2] )
    stri = stri + ( '    "origin" "%d %d %d"\n' % (x, y, z) )
    stri = stri + ( '    "_color" "%.6f %.6f %.6f"\n' % tuple(lamp.col) )
    stri = stri + ( '    "style" "0"\n' )
    propsList = obj.getAllProperties()
    has_target = 0
    for prop in propsList:
        name = prop.getName()
        if ( name == "sun" ):
            if ( prop.getData() ):
                stri = stri + '    "sun" "1"\n'
        elif ( name == "has_target" ):
            if ( prop.getData() ):
                has_target = 1
                target_x, target_y, targer_z = 0, 0, 0
                radius = 0
        elif ( name == "target_x" ):
            target_x = prop.getData()
        elif ( name == "target_y" ):
            target_y = prop.getData()
        elif ( name == "target_z" ):
            target_z = prop.getData()
        elif ( name == "radius" ):
            radius = prop.getData()
    if ( has_target ):
        stri = stri + ( '    "target" "%.8f %.8f %.8f"' % ( target_x, target_y, target_z ) )
        stri = stri + ( '    "radius" "%.8f"\n' % ( radius ) )

    stri = stri + "}\n"
    file.write( stri )
   
def calcFaceUv( face ):
    if ( len(face.v) == len(face.uv) ):
        # I don't know how it works exactly. And the following code is just
        # my guess on this.
        # Find the most like plane by comparing dot product with face normal.
        # Calculating normal:
        nx, ny, nz = faceNormal( face )
        nxa, nya, nza = abs(nx), abs(ny), abs(nz)
        # (Oxy, Oxz, Oyz)
        ox0 = [ 1, 0, 0 ]
        n0 = [ 0, 0, 1 ]
        if ( nxa >= nya ) and ( nxa >= nza ):
            ox0 = [ 0, 1, 0 ]
            n0 = [ 1, 0, 0 ]
        elif ( nya >= nxa ) and ( nya >= nza ):
            ox0 = [ 1, 0, 0 ]
            n0 = [ 0, 1, 0 ]
        elif ( nza >= nxa ) and ( nza >= nya ):
            ox0 = [ 1, 0, 0 ]
            n0 = [ 0, 0, 1 ]
        # Looking for point 0 by intersecting face plane with n0.
        r0x, r0y, r0z = nexifyVector( face.v[0].co.x, face.v[0].co.y, face.v[0].co.z )
        t = ( r0x * nx + r0y * ny + r0z * nz ) / ( n0[0] * nx + n0[1] * ny + n0[2] * nz )
        Ox, Oy, Oz = n0[0] * t, n0[1] * t, n0[2] * t
        # Project ox0 on face plane to get ox using not face normal, but n0.
        ox = [ 0, 0, 0 ]
        d = 0
        for i in range(3):
            d = d + n0[i] * ox0[i]
        for i in range(3):
            ox[i] = ox0[i] - d * n0[i]
        # make ox unit length.
        l = math.sqrt( ox[0] * ox[0] + ox[1] * ox[1] + ox[2] * ox[2] )
        for i in range(3):
            ox[i] = ox[i] / l
        # Looking for oy as cross product (n x ox)
        oy = [ 0, 0, 0 ]
        oy[0], oy[1], oy[2] = cross( nx, ny, nz, ox[0], ox[1], ox[2] )
        # make oy unit length.
        l = math.sqrt( oy[0] * oy[0] + oy[1] * oy[1] + oy[2] * oy[2] )
        for i in range(3):
            oy[i] = oy[i] / l
       
        # Rename some variables to fit external soft output.
        oxx, oxy, oxz = ox[0], ox[1], ox[2]
        oyx, oyy, oyz = oy[0], oy[1], oy[2]
       
        # Matrix for transferring x,y,z to u0,v0.
        d = nx*(oxy*oyz-oxz*oyy)+oxx*(nz*oyy-ny*oyz)+(ny*oxz-nz*oxy)*oyx
        A = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]
        A[0][0] = (nz*oyy-ny*oyz) / d
        A[0][1] = (nx*oyz-nz*oyx) / d
        A[0][2] = (ny*oyx-nx*oyy) / d
        A[0][3] = (nx*(oyy*Oz-oyz*Oy)-oyx*(ny*Oz-nz*Oy)-(nz*oyy-ny*oyz)*Ox) / d
        A[1][0] = (ny*oxz-nz*oxy) / d
        A[1][1] = (nz*oxx-nx*oxz) / d
        A[1][2] = (nx*oxy-ny*oxx) / d
        A[1][3] = (-nx*(oxy*Oz-oxz*Oy)+oxx*(ny*Oz-nz*Oy)+(nz*oxy-ny*oxz)*Ox) / d
        A[2][0] = (oxy*oyz-oxz*oyy) / d
        A[2][1] = (oxz*oyx-oxx*oyz) / d
        A[2][2] = (oxx*oyy-oxy*oyx) / d
        A[2][3] = (-oxx*(oyy*Oz-oyz*Oy)+oyx*(oxy*Oz-oxz*Oy)-(oxy*oyz-oxz*oyy)*Ox) / d
        A[3][3] = (nx*(oxy*oyz-oxz*oyy)+oxx*(nz*oyy-ny*oyz)-(nz*oxy-ny*oxz)*oyx) / d
        # Calculating initial uv0
        xyz = []
        for i in range(3):
            x, y, z = nexifyVector( face.v[i].co.x, face.v[i].co.y, face.v[i].co.z )
            xyz.append( [ x, y, z, 1 ] )
        uv0 = [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ]
        for i in range(3):         # Vector number
            for j in range(4):     # Coordinate number
                for k in range(4): # Summing index
                    # print( "A[%d][%d] * xyz[%d][%d] = " % ( j, k, i, k ) )
                    # print( "                          %.8f   *   %.8f" % ( A[j][k], xyz[i][k] ) )
                    uv0[i][j] = uv0[i][j] + A[j][k] * xyz[i][k]
        # **********************************************
        print( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" )
        print( "    checking coordinates transformation:" )
        print( "ox: %.8f, %.8f, %.8f" % (ox[0], ox[1], ox[2]) )
        print( "oy: %.8f, %.8f, %.8f" % (oy[0], oy[1], oy[2]) )
        print( "O: %.8f, %.8f, %.8f" % (Ox, Oy, Oz) )
        print( "coordinates xyz:" )
        for i in range(3):
            print( "xyz[%d][:]: %.8f, %.8f, %.8f, %.8f" % ( i, xyz[i][0], xyz[i][1], xyz[i][2], xyz[i][3] ) )
        print( "coordinates uv:" )
        for i in range(3):
            print( "uv0[%d][:]: %.8f, %.8f, %.8f, %.8f" % ( i, uv0[i][0], uv0[i][1], uv0[i][2], uv0[i][3] ) )
        # it should be uv0[:][2] == 0 and uv0[:][3] == 1!!!!!
        # **********************************************
       
        # Initial UV coordinates after transferring xyz to uv according
        # to initial guess.
        u0, v0 = uv0[0][0], uv0[0][1]
        u1, v1 = uv0[1][0], uv0[1][1]
        u2, v2 = uv0[2][0], uv0[2][1]
        # Real uv coordinates (it's necessary to multiply them on image size, I suppose):
        w, h = 32, 32 # Now I take just some common texture resolution.
        if ( face.image ):
            sz = face.image.getSize()
            w = sz[0]
            h = sz[1]
        U0, V0 = face.uv[0][0] * w, face.uv[0][1] * h
        U1, V1 = face.uv[1][0] * w, face.uv[1][1] * h
        U2, V2 = face.uv[2][0] * w, face.uv[2][1] * h
        for i in range(3):
            print( "uv[%d][:]: %.8f, %.8f" % ( i, face.uv[i][0] * w, face.uv[i][1] * h ) )
        # matrix for transferring uv to UV.
        a = [ [0, 0, 0], [0, 0, 0], [0, 0, 1] ]
        d = (u1-u0)*v2+(u0-u2)*v1+(u2-u1)*v0
        a[0][0] = -((v1-v0)*U2+(v0-v2)*U1+(v2-v1)*U0) / d
        a[0][1] = ((u1-u0)*U2+(u0-u2)*U1+(u2-u1)*U0) / d
        a[0][2] = ((u0*v1-u1*v0)*U2+(u2*v0-u0*v2)*U1+(u1*v2-u2*v1)*U0) / d
        a[1][0] = -((v1-v0)*V2+(v0-v2)*V1+(v2-v1)*V0) / d
        a[1][1] = ((u1-u0)*V2+(u0-u2)*V1+(u2-u1)*V0) / d
        a[1][2] = ((u0*v1-u1*v0)*V2+(u2*v0-u0*v2)*V1+(u1*v2-u2*v1)*V0) / d
        print( "    matrix from uv0->uv:" )
        for i in range(2):
            print( "        a[%d][0], a[%d][1], a[%d][2] = %.8f, %.8f, %.8f" % ( i, i, i, a[i][0], a[i][1], a[i][2] ) )
       
        # To find scale, shift and rotation angle let's transform unit vectors.
        r00x, r00y = a[0][2], a[1][2]
        a10x, a10y = a[0][0], a[1][0]
        a01x, a01y = a[0][1], a[1][1]
        # New reference frame orientation. (It's cross product third component.)
        d = a10x * a01y - a10y * a01x
        if d > 0:
            signScaleY = 1
        else:
            signScaleY = -1
            a01x, a01y = -a01x, -a01y
        scaleX = math.sqrt( a10x * a10x + a10y * a10y )
        scaleY = signScaleY * math.sqrt( a01x * a01x + a01y * a01y )
        print( "scaleX, scaleY = %.8f, %.8f" % ( scaleX, scaleY ) )
        # Calculating angle.
        angle = math.acos( a10x / scaleX ) * 57.295779513082320876798154814105
        if ( a10y < 0 ):
            angle = 360 - angle
        print( "angle = %.8f" % ( angle ) )
        ang = angle / 57.295779513082320876798154814105
        c = math.cos( ang )
        s = math.sin( ang )
        inv_ang_sc = [ [c/scaleX, -s/scaleX, 0 ], [s/scaleY, c/scaleY, 0], [0, 0, 1] ]
        shift = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ]
        for i in range(3):
            for j in range(3):
                for k in range(3):
                    shift[i][j] = shift[i][j] + inv_ang_sc[i][k] * a[k][j]
        shiftX, shiftY = shift[0][2], shift[1][2]
    else:
        shiftX, shiftY, angle, scaleX, scaleY = 0, 0, 0, 1, 1
    # Texture name
    if ( face.image ):
        texture = face.image.getFilename().lower()
        # I need to cut off file extension and the
        # beginning "c:\programs\nexuiz\data\textures\".
        global g_nexuiz_path
        tex_path = g_nexuiz_path.replace( '\\', '/' )
        if ( tex_path[ len(path)-1 ] == '/' ):
            tex_path = tex_path[:(len(tex_path)-1)]
        tex_path = ( tex_path + "/data/textures/" ).lower()
        os.path.relpath( tex_path, texture )
        # Texture in on correct path.
        if ( texture.find( tex_path ) == 0 ):
            texture = texture[ len(tex_path): ]
            texture, ext = os.path.splitext( texture )
            print( "texture is: '" + texture + "', file type is: " + ext[ 1: ] )
        else:
            print( "Some misterious texture file path: '" + texture + "'" )
            texture = 'common/caulk'
       
    else:
        texture = 'common/caulk'
    return texture, shiftX, shiftY, angle, scaleX, scaleY
   
def cross( ax, ay, az, bx, by, bz ):
    cx = ay * bz - az * by
    cy = az * bx - ax * bz
    cz = ax * by - ay * bx
    return cx, cy, cz


if __name__ == '__main__':
    main()



It's necessary just to save the code to a file with "py" extension and copy to ".../blender/.blender/plugins" folder. It should appear as "Nexify" menu option in export file menu directory.
Last edited by z80 on Mon Mar 29, 2010 3:45 pm, edited 1 time in total.
z80
Advanced member
 
Posts: 92
Joined: Sun Jul 26, 2009 7:35 pm
Location: Russia

Postby Rad Ished » Sun Jul 26, 2009 10:51 pm

Hi, i have not tried this yet but if it works it will be very useful, i for one have spent a lot of time trying to get blender and radiant to be nice to each other, this could be a big step in improving their compatibility. I'll get back when i've tested it, thanks!
fishsticks
Rad Ished
Keyboard killer
 
Posts: 609
Joined: Wed Jun 27, 2007 8:00 am
Location: Not the Netherlands

Postby toneddu2000 » Sun Jul 26, 2009 11:30 pm

ok, first of all a big thank you for having written a so important script! Imho one of the biggest improvements in Nexuiz development would be using Blender as level editor (like one of the best Fps ever for me, PainKiller; it used Maya but it's the same!!), so maps would be more organic and less schematic and orthogonal.

I used your script and , after setting up my path( I use linux) it works perfectly! Only few question:
1.When I didn't set up the path( it was c:\program files\Nexuix\) and I exported a simple extruded cube, it makes a beautiful thing: it hollow the cube automatically!Now, that I set up my path correctly, it doesn't compute this action anymore!Nevermind is not so important infact I made a hollow nexuiz-style cube and the problem is over!
2.Which path I have to use in Blender material panel for object? Maybe /home/user/games/Nexuiz/data/textures or something else because I put the texture in the same folder of the .blend files, I used relative paths and , after exported it used caulk texture
3.You said that the script name is Nexify but I see Nexuiz(.map),is it right?

Some tricks for users
1If you model your map and THEN resize select all obects 1 by 1 and press Ctrl + a and choose "Scale and Rotation to OBdata" other wise objects scaled are bigger than others
2ALWAYS before exporting shift + c and restore cursor and then select objects 1 by 1 and press f9 and down in the Mesh tab select "center cursor" otherwise your obects will show "scattered" in the universe!

So, great script, thank you. I'll try in the next days to "squeeze" it up to make more detailed maps! :P
toneddu2000
Alien trapper
 
Posts: 251
Joined: Mon Mar 09, 2009 7:56 pm
Location: Italy

Postby z80 » Mon Jul 27, 2009 7:30 am

Dear Rad Ished and toneddu2000,
Thank you for testing my script. I'm very sory for little mistakes. Script may contain several bugs, it's inavoidable, because it performs very complex caclulations. Please inform me about bugs met and any suggestions. I'd like to make really usefull tool and try to fix bugs and take into account any ideas on how to improve this exporter.

Concerning the questions.

1) It's really necessary to set path to Nexuiz correctly. Export is performed into "nexuiz/data/maps" folder.

2) Concerning textures I calculate relative path to texture by subtracting the prefix ".../nexuiz/data/textures". So if texture is on this path or in any subfolder of this path it should work. Id it isn't exporter substitutes it with "common/caulk" invisible object texture.

3) Yes, you are right! My mistake. It's "Nexuiz(.MAP)" it's in the middle of the export formats list right under the MD2 format.

Also I've forgotten to tell that after export each mesh get several properties in "game logic" blender section. If it wasn't convex, it gain "ignore" property. And it will be skipped next time. If one want to modify it and export again he is to remove "ignore" property.

Please let me know about any problems met! I'll try to improve the script. I really want to make Blender usable for Nexuiz map creation.

By the way, does anybody know what is texture reference frame in Nexuiz map format exactly? And what is the use in last 3 zeros in texture transformations. There are 8 numbers. By modifying them I've succeded to get that first two are shift, 3-d is rotation angle, 4-th and 5-th are scales. And transformations apply sequence is "rotation * scale * shift * (uv)". Am I right? What are 6-th, 7-th and 8-th numbers? I tried to modify them, but got no effect.
Last edited by z80 on Mon Jul 27, 2009 7:39 am, edited 1 time in total.
z80
Advanced member
 
Posts: 92
Joined: Sun Jul 26, 2009 7:35 pm
Location: Russia

Postby morfar » Mon Jul 27, 2009 7:34 am

This is good news for Blender people I guess. But you still need a compiler of course :P
morfar
Site Admin
 
Posts: 938
Joined: Tue Feb 28, 2006 6:08 pm
Location: The Island

Postby z80 » Mon Jul 27, 2009 7:59 am

morfar wrote:This is good news for Blender people I guess. But you still need a compiler of course :P


Actually I don't understand exactly your suggestion. Do you mean it's necessary to compile map right in Blender? Could you please explain it in more detail?
z80
Advanced member
 
Posts: 92
Joined: Sun Jul 26, 2009 7:35 pm
Location: Russia

Postby FruitieX » Mon Jul 27, 2009 10:40 am

I believe morfar meant that you still need to compile the .map file with q3map2 to be able to play it in the game (and thus, open up NetRadiant to compile it, unless you want to fiddle around on the command line). This sure isn't a problem (other than that q3map2 pretty much sucks) :P
FruitieX
Keyboard killer
 
Posts: 588
Joined: Mon Nov 13, 2006 4:47 pm
Location: Finland

Postby z80 » Mon Jul 27, 2009 12:13 pm

Well, I planned to just make a good one exporter to NetRadiant MAP file format (compared with "export_map.py") and do entities work and compilation in NetRadiant :)

Because, as for me, Blender has very powerfull vertex editing abilities. By the way a lot of people know Blender very well. And it would be very convenient to do most part of the work concerning graphics in one and the same program.

Anyway thanks for the explanation!
z80
Advanced member
 
Posts: 92
Joined: Sun Jul 26, 2009 7:35 pm
Location: Russia

Postby [-z-] » Mon Jul 27, 2009 12:58 pm

Awesome effort and epic first post. Your work will help many modelers/mappers. Keep up the great work and welcome to the community.
[-z-]
Site Admin and Nexuiz Ninja
 
Posts: 1794
Joined: Mon Nov 13, 2006 12:20 am
Location: Florida

Postby toneddu2000 » Mon Jul 27, 2009 2:42 pm

Sorry z80 but I've to bother you again with 5 questions more!
1 I'm trying to export textures but the exporter seems to ignore my directives. I used a blender material for all the objects named cement, then I created a texture named base with orco coordinates and I use the same path of the map exporter( and maps work !) ,/home/user/games/Nexuiz/trunk/data/textures/evil8_wall/e8crete03c.jpg for example but after exported it returns a caulk texture for all the map,why?
2 what stand for grid scale and extrude height in the export window?
3 I've noticed that if I name a map example.map or whatever.map the exporter doesn't write anything but, if I leave the default name, mymap.map the exporter works!
4 Sadly, I tried to export a very high poligon map (about 70000 tris) but after 1 hour and half (!) it wass still crunching numbers in the terminal and I killed it. If I use a hierarchy structure do you think the exporter would be quicker?If I, for example create a plane, I delete all the vertices and I name "root", then i create a box named "house" and I parent it to root, then same way for windows parented to house and so on, do you think that this method could emulate OctTree technology and speed up exporting process?
5Why often,but not always, when you click export for the first time doesn't appear the usual window, but split every object in planes?This is very annoying because you then have the double count of polygons and don't
know if the map is exported!
Thank you for your patience!
toneddu2000
Alien trapper
 
Posts: 251
Joined: Mon Mar 09, 2009 7:56 pm
Location: Italy

Next

Return to Nexuiz - Editing

Who is online

Users browsing this forum: No registered users and 1 guest