Game Development Community

blend2map

by Jason Howard · 09/17/2006 (8:58 pm) · 27 comments

Exporting blender geometry to Quark maps:

First of all, I must preface this by stating the obvious. Blender was not made for brush modeling -- Quark was. I'm not a complete idiot. So I'll ignore you if you rant about blender here...

Secondly, I love blender. So I'll ignore you if you want to rant about blender here...

Ok... If you're like me and you want to do movie sequences for your game in blender, you're not excited about re-modeling your Quark buildings and structures in blender. I read a little about some people who experimented with blender to map exporters, but I couldn't find their scripts anywhere. Am I looking in the wrong places, or are they no longer published?

Anyway - I decided to go ahead and make my own blender to Quark map exporter, knowing that blender wasn't meant for that kind of thing but wanting to do it anyway.

There are some ground rules. To export properly each you need follow these guidelines:
- Model using brushes only (convex polygons -- each as a seperate mesh object). For example, a cube is suitable completely-enclosed convex polygon. Modeling out of bricks or cubes would work.
- Dimples or any other sort of concave shapes are not allowed, but you can break them up into several smaller convex shapes. Perhaps future versions will support automatically splitting up concave shapes, but don't expect that anytime soon... that's very complex.
- The early versions of this script really only export the geometry. Exporting UV textures and lamps is a natural next step for development. It's complex, but not as difficult as breaking up concave geometry into convex shapes. For now, retexture inside Quark. Currently it just uses a default texture called "concrete". You'll need import a concrete.jpg to your Quark setup so that this will work.
- Use quark and the map2dif tools to export all the way into Torque.
- Expect this to have a lot of issues. It's in the early stages of development, and I don't have a lot of time. Plenty of interest, but not a lot of time...

If anyone wants to help, feel free -- but please share your updates to the script. If anyone wants to complain, I'd love to listen but I don't have time. I'm busy making things happen and suggest that you do the same. If anyone is glad to see this script posted, I'll be glad to make time to listen to that. For those who have been longing for a script like this -- here's my little contribution back to a GREAT Garage Games community. Thanks for everything.

Here goes nothing... I hope this doesn't result in total chaos when it hits the community, but I want to share it anyway...

Oh... and lastly -- I will be a Constructor fan when it is published. Maybe after that comes out I'll start working on a map2blend import script to get Constructor based geometry into blender rendered movie scenes...
Page «Previous 1 2
#1
09/17/2006 (8:58 pm)
#!BPY


""" Registration info for Blender menus:
Name: 'Quake Army Knife (.map)...'
Blender: 242
Group: 'Export'
Top: 'Export to QuArK map file format.'
"""
__author__ = "Jason Howard"
__url__ = ("Author's site, http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11288")
__version__ = "2006.09.23"

__bpydoc__ = """\
This script creates a new export menu option (File, Export menu) option called Quark Army Knife (.map).
Select or create the export name for the new map file, then check the console to see if it worked.
This script attempts to export Blender meshes to Quake Army Knife's map format.
Users must be careful to make sure each mesh is concave, or it will not result
in a valid brush.  Furthermore, each "brush" mesh should be a separate object.
Small or concave brushes cause issues.  The default material is named concrete.
Script History:
2006.09.17 Posted initial attempt at this script on GarageGames.com
2006.09.18 Received tremendous GarageGames community support, including source code from the brilliant 
           work of legends like Joe Sulewski, Benoit Touchette, Scott Coursey, and James Urquhart.  
           Modernized some code to work with blender v2.42.  Thanks to JOE SULEWSKI, BENOIT TOUCHETTE, 
           SCOTT COURSEY, and JAMES URQUHART for sharing thier projects with the community.  
2006.09.23 After brushing up on 3D matrices math, enhanced to prevent errors that prevented some
           exported faces from being editable in Quark.
"""
# blend2map_export.py
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# .map face (plane) format notes:
# ( x1 y1 z1 ) ( x2 y2 z2 ) ( x3 y3 z3 ) texture_name [ tx1 ty1 tz1 toffs1 ] [ tx2 ty2 tz2 toffs2 ] rotation scaleX scaleY
# --- clockwise points on plane -------- texture FN--- vector&offset right----vector&offset "up"---- rotatED-- strech X & Y (neg = mirror; < 1 = squeeze)
# texture offsets are multiples of 16...

import Blender, meshtools 
from Blender import Mesh, NMesh, Scene, Window, sys, Image, Draw, Mathutils
from Blender.Mathutils import *
import BPyMesh
import struct, os, cStringIO, time

def face_count(mesh): 
    i = 0; 
    for face in mesh.faces: 
        i = i + 1
    return i 

def getWorldCoordinates(v, obj):
    vector = Vector(v)
    vector.resize4D()
    v = vector * obj.matrix
    v.resize3D()
    return v

def getFaceCenter(p1, p2, p3):
    # return the average of x, y, and z for 3 points (a plane) and return it as the center point c
    cx = (p1[0] + p2[0] + p3[0]) / 3.0
    cy = (p1[1] + p2[1] + p3[1]) / 3.0
    cz = (p1[2] + p2[2] + p3[2]) / 3.0
    vC = Vector([cx, cy, cz])
    return vC

def export(filename, EXPORT_SCALE=100.0): 
    file = open(filename,"wb") 
    file.write("// Originally exported by blend2map_export.py verion %s\n" %( __version__ ) )
    file.write("// exported from blender v%s filename %s\n\n" %( Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\')[-1]) )
    file.write("// Entity 0\n// worldspawn\n")
    file.write("{\n")
    file.write("  \"classname\" \"worldspawn\"\n")
    file.write("  \"detail_number\" \"0\"\n")
    file.write("  \"min_pixels\" \"250\"\n")
    file.write("  \"geometry_scale\" \"32.0\"\n")
    file.write("  \"light_geometry_scale\" \"32.0\"\n")
    file.write("  \"ambient_color\" \"0 0 0\"\n")
    file.write("  \"emergency_ambient_color\" \"0 0 0\"\n")
    file.write("  \"mapversion\" \"220\"\n")


    ## make lists of applicable blender objects (from current scene)
    Meshes = []
    Lamps = []
    scn = Scene.GetCurrent()
    objects = scn.getChildren()
    for obj in objects:
    #    # if the object can be a mesh
    #    mesh = BPyMesh.getMeshFromObject(obj, containerMesh, True, False, scn)
    #    if mesh:
    #        Meshes.append(obj.getName())
    #    else:
    #        if obj.getType() == "Lamp":
    #            Lamps.append(obj.getName())
        objType = obj.getType()
        if objType == "Mesh":
            Meshes.append(obj.getName())
        if objType == "Lamp":
            Lamps.append(obj.getName())
   

    # for each mesh brush
    for meshName in Meshes:
        obj = Blender.Object.Get(meshName)
        mesh = obj.getData()

        #export the mesh 
        print "Exporting mesh %s" %obj.name
        file.write("// Brush based on blender object named %s with %d faces\n" %(obj.name, face_count(mesh)))
        file.write("  {\n")
        faceindex = 0 
        for face in mesh.faces: 
            # prepare face
            scaleFactor = EXPORT_SCALE
            v1 = getWorldCoordinates(face.v[0].co, obj)  # do scale factor here instead? 
            v2 = getWorldCoordinates(face.v[1].co, obj) 
            v3 = getWorldCoordinates(face.v[2].co, obj) 
            c1 = getFaceCenter(v1, v2, v3)
            
            # Create a smaller triangle that includes c1 to define the plane
            #    translate triangle to c1 = origin (0,0,0)
            translated_v1 = v1 - c1
            translated_v2 = v2 - c1
            translated_c1 = c1 - c1
            #    reset v1 and v2 lenths to 1
            translated_v1.normalize
            translated_v2.normalize
            #    scale v1 and v2 lengths
            new_triangle_scale = 10.0
            small_translated_v1 = translated_v1 * new_triangle_scale
            small_translated_v2 = translated_v2 * new_triangle_scale
            #    "untranslate" triangle
            small_v1 = small_translated_v1 + c1
            small_v2 = small_translated_v2 + c1
            small_c1 = c1


            # prepare UV and image
            print ".defaultUVs"
            vTextureName = "concrete"
            vU = translated_v1
            vV = translated_v2
            # project to create right angle... etc...  scale properly...
            vOffsetU = 0.0
            vOffsetV = 0.0
            vRotation = 0
            vScaleU = 1.0
            vScaleV = 1.0

          # write 3 vertices to define the plane for this face of the brush (convex mesh)
            file.write("    ");
            if len(face.v) >= 3:
                print ".write"
                # write vertexes
                file.write("( %f %f %f ) " %(small_c1[0] * scaleFactor, small_c1[1] * scaleFactor, small_c1[2] * scaleFactor )) 
                file.write("( %f %f %f ) " %(small_v2[0] * scaleFactor, small_v2[1] * scaleFactor, small_v2[2] * scaleFactor )) 
                file.write("( %f %f %f ) " %(small_v1[0] * scaleFactor, small_v1[1] * scaleFactor, small_v1[2] * scaleFactor )) 

                # write texture data (image name, UV right and up directions, texture offsets (right and up), rotation, and scale (X&Y)
                file.write("%s [ %f %f %f %f ] [ %f %f %f %f ] %d %f %f\n" %(vTextureName, vU[0], vU[1], vU[2], vOffsetU, vV[0], vV[1], vV[2], vOffsetV, vRotation, vScaleU, vScaleV))

                # write comments about normals for debugging purposes
                vtri = TriangleNormal(Vector(v3[0], v3[1], v3[2]), Vector(v2[0], v2[1], v2[2]), Vector(v3[0], v3[1], v3[2]))
                file.write("//Brush Norm = (%f %f %f)\n" %(vtri[0], vtri[1], vtri[2]))
                vfnor = face.no
                file.write("// Face Norm = (%f %f %f)\n" %(vfnor[0], vfnor[1], vfnor[2]))
            faceindex = faceindex + 1 
            if faceindex > 16:
                print("Warning: mesh %s exceeds 16 faces!  Invalid brush.\n" %obj.name)
                file.write("//Warning: mesh exceeds 16 faces!  Invalid brush.\n")
        file.write("  }\n")

    file.write("}\n")

    # export lamps
    # for each lamp in Lamps:

    # ##########################
    # insert lamp stuff...
    # ##########################
        

    file.flush() 
    file.close() 
    print "Done." 


def fs_callback(filename):
    if filename.find('.map', -4) <= 0: filename += '.map'
    print "---- blend2map export ----"
    export(filename, 10)
    print" --------------------------"

Blender.Window.FileSelector(fs_callback, "Export MAP")
#2
09/17/2006 (9:00 pm)
So... you can use the code above, save it in a file called "blend2map_export.py", and copy it to your blender scripts subdirectory. Have fun.
#3
09/17/2006 (9:07 pm)
Would it be worth making a formal resource out of this? Maybe I'll go that route after the intial bugs are worked out of it...
#4
09/18/2006 (12:22 am)
Jason,

If you look around, you might find that this has been attempted before, although i don't think any current attempts have been updated in a while.

Still, its nice to see people still attempting this feat. Good luck :)
#5
09/18/2006 (3:43 am)
James,

Thanks! Most of the links are broken in the historical attempts... but draekko.atspace.com/ has the python script from Benoit Touchette. His script is probably better, as this is my first attempt at programming Python...

By the way - your work on the dts exporter is just incredible... legendary even. Thanks.
#6
09/18/2006 (4:53 am)
Hi, Jason. I'm glad you're taking up this banner and hope you will have some success. I've played around with the map exporter quite a long time ago with Joe and James is correct: the scripts have not been messed with in a long, long time. If you'd like what I have, you might like the following:

Map_Blender.py
Map_TxtConfig.py

I honestly don't know the condition of the code, but from what I recall, it worked fairly well. This subset of code also has the underpinnings of light features (spot, strobe, runway, etc) which could use some attention.

Have fun!
#7
09/18/2006 (12:16 pm)
Not this is handy as hell! Too bad i don't know blender :( Do you know where i can learn what everything in the .map means? I may write a lightwave expoter :)

www.garagegames.com/blogs/11127/10274

I did write a lightwave scene->MIS exporter type thingy here. so i've got a little experience with this sorta thing
#8
09/18/2006 (3:36 pm)
@Ramen-sama. I like youre work.
I learned the basics of the map format from www.gamers.org/dEngine/quake/QDP/. There is a more updated version of that material at collective.valve-erc.com/index.php?go=map_format The links above from James also go into a lot of detail. Good luck.
#9
09/18/2006 (3:38 pm)
@Scott & James: THANKS!!! I'll see what I can do to get all the historical progress bright people made into an updated version that works on blender v2.42. This community is so awesome.
#10
09/18/2006 (7:15 pm)
You should check out John Ratcliff's Code Suppository website - it has a neat convex decomposition library that could be useful to you.
#11
09/18/2006 (8:15 pm)
@Ben: Holy cow! That convex decomposition code will change my life! :) Neat stuff. ... just the exuse I needed to pull an all nighter -- who needs sleep anyway...
#12
09/19/2006 (3:20 am)
This looks fantastic! I'm really interested in tracking this progress -- I too am a big fan of Blender who is a little hesitant to switch everything over to learn Quark. Thanks loads!
#13
09/23/2006 (5:49 pm)
I have some new code that I'll post this weekend. The work of my predecessors was magnificent. I'm working on a couple enhancements (after brushing up on matrices and 3d vectors) that should prevent some bugs that cause some exported faces to be uneditable in Quark... This is a fun project.
It would be really fun to add some simple map building tools (raising walls & adding floor and roof from a 2D floor-map, etc).
I've still got my eye on automatically reducing invalid brushes down to smaller valid ones. It seems possible, in the future.
#14
09/24/2006 (12:06 pm)
I posted the "2006.09.23" version of the code above. It should export geometry that is "editable" in Quark without producing overflow errors. It does a good job at setting up the defautl texture for square faces, but warps the texture for non-square faces. That will be corrected very soon -- I have the algorithm ready and I'm coding it now...

How to Use the Code: Copy the code above into a text file called "blend2map_export.py" and place the file in the scripts subdirectory of your blender 2.42 installation. The script creates a new menu option in the File, Export menu for exporting Quark Army Knife maps. Give your map file a name, like "test.map", when you export. After you get it into map format, feel free to edit it in Quark (change and position textures, add lights and portals, etc). When it's ready, use map2dif to create your Torque building or structure.

Soon the texturing you do inside blender will export more precisely. I need to find a way to identify portals, and maybe even use seams for zones or something. I'll use the work of my predecessors to add lights to the code... Then this will be ready for prime time. In the meantime, have fun...
#15
10/01/2006 (6:50 pm)
Here is another approach that should give the artist a lot more freedom. It takes every polygon and extrudes it inward (opposite the normal) to create brushes. That way you don't have to really worry about convex vs concave and stuff like that when designing your structure/building.

The code below has some issues that I'm working out, but it gives you an idea where it's going. I intend to make both methods an option by the time this is done.

#!BPY


""" Registration info for Blender menus:
Name: 'Quake Army Knife (.map)...'
Blender: 242
Group: 'Export'
Top: 'Export to QuArK map file format.'
"""
__author__ = "Jason Howard"
__url__ = ("Author's site, http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11288")
__version__ = "2006.10.15"

__bpydoc__ = """\
This script creates a new export menu option (File, Export menu) option called Quark Army Knife (.map).
Select or create the export name for the new map file, then check the console to see if it worked.
This script attempts to export Blender meshes to Quake Army Knife's map format.
Users must be careful to make sure each mesh is concave, or it will not result
in a valid brush.  Furthermore, each "brush" mesh should be a separate object.
Small or concave brushes cause issues.  The default material is named concrete.
Script History:
2006.09.17 Posted initial attempt at this script on GarageGames.com
2006.09.18 Received tremendous GarageGames community support, including source code from the brilliant 
           work of legends like Joe Sulewski, Benoit Touchette, Scott Coursey, and James Urquhart.  
           Modernized some code to work with blender v2.42.  Thanks to JOE SULEWSKI, BENOIT TOUCHETTE, 
           SCOTT COURSEY, JAMES URQUHART, and others for sharing thier projects with the community.  
2006.09.23 Enhanced to prevent overflow errors that prevented some exported faces from being editable in Quark.
2006.09.24 Improved texture export quality (UV at right angle to each other) to make texture placement in Quark easier.
2006.10.01 Switched to new approach -- complete re-write.  Extrude inward (away from normal) to convert each face to its
           own brush.  Will debug and make either approach an option.
2006.10.14 Finally got some more time (at the expense of sleep) to work on this...  Made some improvements to the
           face-to-polygon method, including auto-scaling, which fixed a lot of problems.  Still some issues to work out
           but really seeing some progress.
2006.10.15 It is now required that you convert your meshes to triangles before exporting to a Quark map with this script.
"""
# blend2map_export.py
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# .map face (plane) format notes:
# ( x1 y1 z1 ) ( x2 y2 z2 ) ( x3 y3 z3 ) texture_name [ tx1 ty1 tz1 toffs1 ] [ tx2 ty2 tz2 toffs2 ] rotation scaleX scaleY
# --- clockwise points on plane -------- texture FN--- vector&offset right----vector&offset "up"---- rotatED-- strech X & Y (neg = mirror; < 1 = squeeze)
# texture offsets are multiples of 16...

import Blender, meshtools 
from Blender import Mesh, NMesh, Scene, Window, sys, Image, Draw, Mathutils
from Blender.Mathutils import *
import BPyMesh
import struct, os, cStringIO, time

def face_count(mesh): 
    i = 0; 
    for face in mesh.faces: 
        i = i + 1
    return i 

def vertex_count(face):
    i=0;
    for vert in face.v:
        i = i + 1
    return i

def getWorldCoordinates(v, obj):
    vector = Vector(v)
    vector.resize4D()
    v = vector * obj.matrix
    v.resize3D()
    return v

def getFaceCenter(p1, p2, p3):
    # return the average of x, y, and z for 3 points (a plane) and return it as the center point c
    cx = (p1[0] + p2[0] + p3[0]) / 3.0
    cy = (p1[1] + p2[1] + p3[1]) / 3.0
    cz = (p1[2] + p2[2] + p3[2]) / 3.0
    vC = Vector([cx, cy, cz])
    return vC

def isConcave(mesh):
    #check every edge for > 180 degrees...
    concaveFlag = 0
    for firstEdge in mesh.edges:
        for secondEdge in mesh.edges:
            if (firstEdge.v1 <> secondEdge.v1) and (firstEdge.v2 <> secondEdge.v2):
                match = 0
                if (firstEdge.v1 == secondEdge.v1):
                    match = 1
                    Vec1 = Vector(firstEdge.v1) - Vector(firstEdge.v2)
                    Vec2 = Vector(secondEdge.v1) - Vector(secondEdge.v2)
                if (firstEdge.v2 == secondEdge.v1):
                    match = 1
                    Vec1 = Vector(firstEdge.v2) - Vector(firstEdge.v1)
                    Vec2 = Vector(secondEdge.v1) - Vector(secondEdge.v2)
                if (firstEdge.v1 == secondEdge.v2):
                    match = 1
                    Vec1 = Vector(firstEdge.v1) - Vector(firstEdge.v2)
                    Vec2 = Vector(secondEdge.v2) - Vector(secondEdge.v1)
                if (firstEdge.v2 == secondEdge.v2):
                    match = 1
                    Vec1 = Vector(firstEdge.v2) - Vector(firstEdge.v1)
                    Vec2 = Vector(secondEdge.v2) - Vector(secondEdge.v1)
                if match == 1:
                    Vec1.normalize
                    Vec2.normalize
                    if AngleBetweenVecs(Vec1, Vec2) > 180:
                        concaveFlag = concaveFlag + 1
                        print("Large angle: %f\n" %AngleBetweenVecs(Vec1, Vec2))
    return concaveFlag

def getBestScale():
    # get min & max vertex coordinates for each axis
    scn = Scene.GetCurrent()
    objects = scn.getChildren()
    maxV1 = 0
    maxV2 = 0
    maxV3 = 0
    minV1 = 0
    minV2 = 0
    minV3 = 0
    for obj in objects:
        objType = obj.getType()
        if objType == "Mesh":
            mesh = obj.getData()
            for face in mesh.faces:
                for v in face.v:
                    wv = getWorldCoordinates(v, obj)

                    v1 = wv[0]
                    if v1 < minV1:
                        minV1 = v1
                    if v1 > maxV1:
                        maxV1 = v1

                    v2 = wv[1]
                    if v2 < minV2:
                        minV2 = v2
                    if v2 > maxV2:
                        maxV2 = v2
 
                    v3 = wv[2]
                    if v3 < minV3:
                        minV3 = v3
                    if v3 > maxV3:
                        maxV3 = v3
    
  # get optimal scale for each axis (try to use as much of the available scale as possible)
    v1Scale = 1.0
    V1Span = maxV1 - minV1
    if V1Span > 0:
        v1Scale = 8000/V1Span    

    v2Scale = 1.0
    V2Span = maxV2 - minV2
    if V2Span > 0:
        v2Scale = 8000/V2Span    

    v3Scale = 1.0
    V3Span = maxV3 - minV3
    if V3Span > 0:
        v3Scale = 8000/V3Span   

    # choose the smalles of the optimal axis scales
    Scale = v1Scale
    if v2Scale < v1Scale:
        Scale = v2Scale
    if v3Scale < Scale:
        Scale = v3Scale

    print("ScaleFactor: %f %f (%f - %f = %f)\n" %(Scale, v1Scale, V1Span, maxV1, minV1))
    print("ScaleFactor: %f %f (%f - %f = %f)\n" %(Scale, v2Scale, V2Span, maxV2, minV2))
    print("ScaleFactor: %f %f (%f - %f = %f)\n" %(Scale, v3Scale, V3Span, maxV3, minV3))

    #return it
    return Scale

def exportMeshesAsBrushes(filename, EXPORT_SCALE=100.0): 
    file = open(filename,"wb") 
    file.write("// Originally exported by blend2map_export.py verion %s\n" %( __version__ ) )
    file.write("// exported from blender v%s filename %s\n\n" %( Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\')[-1]) )
    file.write("// Entity 0\n// worldspawn\n")
    file.write("{\n")
    file.write("  \"classname\" \"worldspawn\"\n")
    file.write("  \"detail_number\" \"0\"\n")
    file.write("  \"min_pixels\" \"250\"\n")
    file.write("  \"geometry_scale\" \"32.0\"\n")
    file.write("  \"light_geometry_scale\" \"32.0\"\n")
    file.write("  \"ambient_color\" \"0 0 0\"\n")
    file.write("  \"emergency_ambient_color\" \"0 0 0\"\n")
    file.write("  \"mapversion\" \"220\"\n")


    ## make lists of applicable blender objects (from current scene)
    Meshes = []
    Lamps = []
    scn = Scene.GetCurrent()
    objects = scn.getChildren()
    for obj in objects:
    #    # if the object can be a mesh
    #    mesh = BPyMesh.getMeshFromObject(obj, containerMesh, True, False, scn)
    #    if mesh:
    #        Meshes.append(obj.getName())
    #    else:
    #        if obj.getType() == "Lamp":
    #            Lamps.append(obj.getName())
        objType = obj.getType()
        if objType == "Mesh":
            Meshes.append(obj.getName())
        if objType == "Lamp":
            Lamps.append(obj.getName())

    # for each mesh brush
    scaleFactor = getBestScale()
    for meshName in Meshes:
        obj = Blender.Object.Get(meshName)
        mesh = obj.getData()

        # concave-convex test
        if isConcave(mesh):
            print("Error Warning: Mesh %s is concave!!!  Bad angles...\n" %obj.getName())

        #export the mesh 
        print "Exporting mesh %s" %obj.name
        file.write("// Brush based on blender object named %s with %d faces\n" %(obj.name, face_count(mesh)))
        file.write("  {\n")
        faceindex = 0 
        for face in mesh.faces: 
            # prepare face
            vCount = vertex_count(face)
            #scaleFactor = EXPORT_SCALE
            v1 = getWorldCoordinates(face.v[0].co, obj)  * scaleFactor
            v2 = getWorldCoordinates(face.v[1].co, obj)  * scaleFactor
            v3 = getWorldCoordinates(face.v[2].co, obj)  * scaleFactor
            c1 = getFaceCenter(v1, v2, v3)

            # Create a "standard size" triangle that includes c1 to define the plane the face is on
            #    translate triangle to c1 = origin (0,0,0)
            translated_v1 = v1 - c1
            translated_v2 = v2 - c1
            translated_c1 = c1 - c1
            #    reset v1 and v2 lenths to 1
            translated_v1.normalize
            translated_v2.normalize
            #    scale v1 and v2 lengths
            new_triangle_scale = 10.0
            std_translated_v1 = translated_v1 * new_triangle_scale
            std_translated_v2 = translated_v2 * new_triangle_scale
            #    "untranslate" triangle defining the face' plane
            std_v1 = std_translated_v1 + c1
            std_v2 = std_translated_v2 + c1
            std_c1 = c1


            # prepare default texture image name and UV; right angle between U and V... etc... 
            vTextureName = "concrete"
            vU = translated_v1   #std or translated?
            vV = translated_v2
            vOffsetU = 0.0
            vOffsetV = 0.0
            vRotation = 0
            vScaleU = 1.0	
            vScaleV = 1.0		

            #vV = vU * RotationMatrix(90, 3, "R", Vector(face.no))

            vRotation = AngleBetweenVecs(vU, vV)

            # write face data for this part of the brush
            file.write("    ");
            if len(face.v) >= 3:
                # write 3 vertices to define the plane for this face of the brush (convex mesh)
                file.write("( %f %f %f ) " %(std_c1[0], std_c1[1], std_c1[2] )) 
                file.write("( %f %f %f ) " %(std_v2[0], std_v2[1], std_v2[2] )) 
                file.write("( %f %f %f ) " %(std_v1[0], std_v1[1], std_v1[2] )) 

                # write texture data (image name, UV right and up directions, texture offsets (right and up), rotation, and scale (X&Y)
                file.write("%s [ %f %f %f %f ] [ %f %f %f %f ] %d %f %f\n" %(vTextureName, vU[0], vU[1], vU[2], vOffsetU, vV[0], vV[1], vV[2], vOffsetV, vRotation, vScaleU, vScaleV))

                # write comments about normals for debugging purposes
                #vtri = TriangleNormal(Vector(v3[0], v3[1], v3[2]), Vector(v2[0], v2[1], v2[2]), Vector(v3[0], v3[1], v3[2]))
                #file.write("//Brush Norm = (%f %f %f)\n" %(vtri[0], vtri[1], vtri[2]))
                #vfnor = face.no
                #file.write("// Face Norm = (%f %f %f)\n" %(vfnor[0], vfnor[1], vfnor[2]))
            faceindex = faceindex + 1 
            if faceindex > 16:
                print("Warning: mesh %s exceeds 16 faces!  Invalid brush.\n" %obj.name)
                file.write("//Warning: mesh exceeds 16 faces!  Invalid brush.\n")
        file.write("  }\n")

    file.write("}\n")

    # export lamps
    # for each lamp in Lamps:

    # ##########################
    # insert lamp stuff...
    # ##########################
        

    file.flush() 
    file.close() 
    print "Done." 



def exportPolygonsAsBrushes(filename, BRUSH__THICKNESS = 16.0, EXPORT_SCALE=0.0): 
    print("Opening file %s\n" %filename)
    file = open(filename,"wb") 
    file.write("// Originally exported by blend2map_export.py verion %s\n" %( __version__ ) )
    file.write("// exported from blender v%s filename %s\n\n" %( Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\')[-1]) )
    file.write("// Entity 0\n// worldspawn\n")
    file.write("{\n")
    file.write("  \"classname\" \"worldspawn\"\n")
    file.write("  \"detail_number\" \"0\"\n")
    file.write("  \"min_pixels\" \"250\"\n")
    file.write("  \"geometry_scale\" \"32.0\"\n")
    file.write("  \"light_geometry_scale\" \"32.0\"\n")
    file.write("  \"ambient_color\" \"0 0 0\"\n")
    file.write("  \"emergency_ambient_color\" \"0 0 0\"\n")
    file.write("  \"mapversion\" \"220\"\n")
    #file.write("{\n")
   

    ## make lists of applicable blender objects (from current scene)
    Meshes = []
    Lamps = []
    scn = Scene.GetCurrent()
    objects = scn.getChildren()
    for obj in objects:
        objType = obj.getType()
        if objType == "Mesh":
            Meshes.append(obj.getName())
        if objType == "Lamp":
            Lamps.append(obj.getName())

    # for each mesh brush
    scaleFactor = EXPORT_SCALE
    if scaleFactor == 0.0:
        scaleFactor = getBestScale()
        print("Best Scale Factor: %f\n" %scaleFactor)
    for meshName in Meshes:
        obj = Blender.Object.Get(meshName)
        mesh = obj.getData()

        # export the mesh 
        print "Exporting polygons in mesh %s" %obj.name
        file.write("\n// Brushes based on polygons (faces) in blender object named %s with %d faces\n" %(obj.name, face_count(mesh)))
        faceindex = 0 
        for face in mesh.faces: 
            file.write("  {\n")

            # prepare face
            vCount = vertex_count(face)

            #face vertex list (vertexList)
            vCount = vertex_count(face)
            i = 0
            vertexList = []
            while i < vCount:
                if i < 6:
                    vertexList.append(getWorldCoordinates(face.v[i].co, obj) * scaleFactor)
                if i == 6:
                    print "Warning: Polygon exceeds 6 faces...  only 6 were exported."
                i = i + 1

            #determine number of planes on this brush (2 more than sides on polygon -- because top & bottom will be added
            if vCount > 6:
                vCount = 6
            countBrushPlanes = vCount + 2
            if vCount < 3:
               print "Error: Invaide brush! no sides"
            
            #make a list of sides
            i = 0
            print("vCount = %d\n" %vCount) 
            sideListV1 = []
            sideListV2 = []
            while i < vCount:
                b = i + 1
                if b >= vCount:
                    b = 0
                sideListV1.append( Vector(vertexList[i]) )
                sideListV2.append( Vector(vertexList[b]) )
                i = i + 1

            #face normal
            polyNormal = Vector(face.no)

            #if vecLengthSquared(polyNormal) == 0.0:
            #    print "Invalid polygon: vecLengthSquared(polyNormal) = 0\n"
 
            brushThickness = BRUSH__THICKNESS
            thickness = Vector( polyNormal * -brushThickness );  #vecScale
            print("polyNormal:")
            print polyNormal
            print ("thickness:")
            print thickness
            
            # ---- TOP PLANE ---- of brush (from polygon)
            print "top..."
            brushPlaneV3 = [Vector(vertexList[0])]
            brushPlaneV2 = [Vector(vertexList[1])]
            brushPlaneV1 = [Vector(vertexList[2])]
            # future enhancement: make those points weren't on the same line; pick another point if they were
            brushPlaneScale1 = [0.5]
            brushPlaneScale2 = [0.5]
            brushPlaneNormal = [polyNormal * -1]
            #UV
            U = [ Vector(brushPlaneV1[0][0] - brushPlaneV3[0][0], brushPlaneV1[0][1] - brushPlaneV3[0][1], brushPlaneV1[0][2] - brushPlaneV3[0][2]) ]
            V = [ Vector(brushPlaneV2[0][0] - brushPlaneV3[0][0], brushPlaneV2[0][1] - brushPlaneV3[0][1], brushPlaneV2[0][2] - brushPlaneV3[0][2]) ]
            offsetU = [ 0.0 ]
            offsetV = [ 0.0 ]
            #rotation = [ AngleBetweenVecs(U[0], V[0]) ]
            rotation = [0]
            scaleU = [ 1.0 ]
            scaleV= [ 1.0 ]
            brushPlaneTexture = ["concrete"]
  
            # ---- BOTTOM PLANE ---- of brush (parallel to polygon; distance of thickness vector away from original poly)
            print "bottom..."
            # reverse order for opposite normal on "bottom" plane
            v1 = brushPlaneV3[0] + thickness
            v2 = brushPlaneV2[0] + thickness
            v3 = brushPlaneV1[0] + thickness
#            print brushPlaneV1[0]
#            print thickness
#            print v1
            brushPlaneV1.append( Vector(v1[0], v1[1], v1[2]) )
            brushPlaneV2.append( Vector(v2[0], v2[1], v2[2]) )
            brushPlaneV3.append( Vector(v3[0], v3[1], v3[2]) )
#            print brushPlaneV1
            brushPlaneScale1.append( 0.5 )
            brushPlaneScale2.append( 0.5 )
            brushPlaneNormal.append( polyNormal * -1 )
            brushPlaneTexture.append( "concrete" )
            #UV
            U.append( Vector(brushPlaneV1[1][0] - brushPlaneV3[1][0], brushPlaneV1[1][1] - brushPlaneV3[1][1], brushPlaneV1[1][2] - brushPlaneV3[1][2]) )
            V.append( Vector(brushPlaneV2[1][0] - brushPlaneV3[1][0], brushPlaneV2[1][1] - brushPlaneV3[1][1], brushPlaneV2[1][2] - brushPlaneV3[1][2]) )
            offsetU.append( 0.0 )
            offsetV.append( 0.0 )
            #rotation.append( AngleBetweenVecs(U[1], V[1]) )
            rotation.append(0)
            scaleU.append( 1.0 )
            scaleV.append( 1.0 )

            # ---- SIDE PLANES ---- (connect top to bottom)
            i = 2
            while i < countBrushPlanes:
                print("side %d..." %i)
                brushPlaneV2.append( sideListV2[i-2] )
                brushPlaneV1.append( sideListV1[i-2] )
                brushPlaneV3.append( sideListV1[i-2] + thickness )   # vecMA
                brushPlaneScale1.append ( 0.5 )
                brushPlaneScale2.append ( 0.5 )
#                tv1 = Vector(sideList[i-2][0], sideList[i-2][1])
#                tv2 = Vector(polyNormal[0]) 
#                brushPlaneNormal.append( CrossVecs( tv1, tv2 ) )  #crossProduct of 2 vectors
#                print "set"
                brushPlaneTexture.append( "concrete" )
                #UV
                U.append( brushPlaneV1[i] - brushPlaneV3[i] )
                V.append( brushPlaneV2[i] - brushPlaneV3[i] )
                offsetU.append( 0.0 )
                offsetV.append( 0.0 )
                #rotation.append( AngleBetweenVecs(U[i], V[i]) )
                rotation.append(0)
                scaleU.append( 1.0 )
                scaleV.append( 1.0 )
#                print "ok"
                i = i + 1

            # write data for each plan on this brush
            i = 0
            while i < countBrushPlanes:
                file.write("    ")
                # write 3 vertices to define the plane for this face of the brush (convex mesh)
                file.write("( %f %f %f ) " %(brushPlaneV1[i][0], brushPlaneV1[i][1], brushPlaneV1[i][2] )) 
                file.write("( %f %f %f ) " %(brushPlaneV2[i][0], brushPlaneV2[i][1], brushPlaneV2[i][2] )) 
                file.write("( %f %f %f ) " %(brushPlaneV3[i][0], brushPlaneV3[i][1], brushPlaneV3[i][2] )) 
                # write texture data (image name, UV right and up directions, texture offsets (right and up), rotation, and scale (X&Y)
                file.write("%s [ %f %f %f %f ] [ %f %f %f %f ] %d %f %f\n" %(brushPlaneTexture[i], U[i][0], U[i][1], U[i][2], offsetU[i], V[i][0], V[i][1], V[i][2], offsetV[i], rotation[i], scaleU[i], scaleV[i]))
                  
                i = i + 1 
            file.write("  }\n")

    # export lamps
    # for each lamp in Lamps:

    # ##########################
    # insert lamp stuff...
    # ##########################
        
                  
    file.write("}\n")


    file.flush() 
    file.close() 
    print "Done." 




def fs_callback(filename):
    if filename.find('.map', -4) <= 0: filename += '.map'
    print "---- blend2map export ----"
    #exportMeshesAsBrushes(filename)
    exportPolygonsAsBrushes(filename)
    print" --------------------------"

Blender.Window.FileSelector(fs_callback, "Export MAP")
#16
10/13/2006 (11:43 pm)
Ok... I updated the code above. It still has some issues, but its danger-close to completion. I have successfully designed some buildings in blender, exported to Quark, exported to DIF, and used them in TGE's starter.fps. Some buildings failed -- I'm looking into the cases that fail. It's really progressing now.
#17
10/14/2006 (6:35 am)
Tip: It is necessary to convert your meshes to triangles before exporting to Quark -- otherwise this program will mess up the normals. I tried that and it worked with some fairly complex buildings. If you get leakage (gaps where faces should join) you can try increasing the brush thickness. I like 16... It is really working well now.
#18
10/14/2006 (7:14 am)
Well done Jason ! WhooHoo !
#19
10/14/2006 (9:34 am)
Ok... this was created in blender, exported to Quark, and exported to Torque as a DIF interior. It's an odd shape for a building, but blender enthusiasts will appreciate its significance. :)
webpages.charter.net/jasonhoward/GG/testExportDIF.jpg
Page «Previous 1 2