code:
#!BPY

"""
Name: 'Quake3 (.md3)...'
Blender: 242
Group: 'Export'
Tooltip: 'Export to Quake3 file format. (.md3)'
"""
__author__ = "PhaethonH, Bob Holcomb, Damien McGinnes, Robert (Tr3B) Beckebans"
__url__ = ("http://xreal.sourceforge.net")
__version__ = "0.7 2006-11-12"

__bpydoc__ = """\
This script exports a Quake3 file (MD3).

Supported:

    Surfaces, Materials and Tags.

Missing:

    None.

Known issues:

    None.

Notes:

    TODO
"""

import sys, os, os.path, struct, string, math

import Blender
from Blender import *
from Blender.Draw import *
from Blender.BGL import *
from Blender.Window import *

import types

import textwrap

import logging
reload(logging)

import sys, struct, string, math
from types import *

import os
from os import path

GAMEDIR = 'D:/Games/XreaL_testing/base/'
#GAMEDIR = '/opt/XreaL/base/'
MAX_QPATH = 64

import sys, struct, string, math
from types import *

import os
from os import path

import q_shared
from q_shared import *

MD3_IDENT = "IDP3"
MD3_VERSION = 15
MD3_MAX_TAGS = 16
MD3_MAX_SURFACES = 32
MD3_MAX_FRAMES = 1024
MD3_MAX_SHADERS = 256
MD3_MAX_VERTICES = 4096
MD3_MAX_TRIANGLES = 8192
MD3_XYZ_SCALE = (1.0 / 64.0)
MD3_BLENDER_SCALE = (1.0 / 1.0)

class md3Vert:
	xyz = []
	normal = 0
	binaryFormat = "<3hh"

	def __init__(self):
		self.xyz = [0, 0, 0]
		self.normal = 0

	def GetSize(self):
		return struct.calcsize(self.binaryFormat)

	# copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
	def Decode(self, latlng):
		lat = (latlng >> 8) & 0xFF;
		lng = (latlng) & 0xFF;
		lat *= math.pi / 128;
		lng *= math.pi / 128;
		x = math.cos(lat) * math.sin(lng)
		y = math.sin(lat) * math.sin(lng)
		z =                 math.cos(lng)
		retval = [ x, y, z ]
		return retval

	# copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
	def Encode(self, normal):
		x, y, z = normal

		# normalise
		l = math.sqrt((x*x) + (y*y) + (z*z))
		if l == 0:
			return 0
		x = x/l
		y = y/l
		z = z/l

		if (x == 0.0) & (y == 0.0) :
			if z > 0.0:
				return 0
			else:
				return (128 << 8)

		# Encode a normal vector into a 16-bit latitude-longitude value
		#lng = math.acos(z)
		#lat = math.acos(x / math.sin(lng))
		#retval = ((lat & 0xFF) << 8) | (lng & 0xFF)
		lng = math.acos(z) * 255 / (2 * math.pi)
		lat = math.atan2(y, x) * 255 / (2 * math.pi)
		retval = ((int(lat) & 0xFF) << 8) | (int(lng) & 0xFF)
		return retval

	def Load(self, file):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		self.xyz[0] = data[0] * MD3_XYZ_SCALE
		self.xyz[1] = data[1] * MD3_XYZ_SCALE
		self.xyz[2] = data[2] * MD3_XYZ_SCALE
		self.normal = data[3]
		return self

	def Save(self, file):
		tmpData = [0] * 4
		tmpData[0] = self.xyz[0] / MD3_XYZ_SCALE
		tmpData[1] = self.xyz[1] / MD3_XYZ_SCALE
		tmpData[2] = self.xyz[2] / MD3_XYZ_SCALE
		tmpData[3] = self.normal
		data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1], tmpData[2], tmpData[3])
		file.write(data)
		#print "Wrote MD3 Vertex: ", data

	def Dump(self):
		log.info("MD3 Vertex")
		log.info("X: %s", self.xyz[0])
		log.info("Y: %s", self.xyz[1])
		log.info("Z: %s", self.xyz[2])
		log.info("Normal: %s", self.normal)
		log.info("")

class md3TexCoord:
	u = 0.0
	v = 0.0

	binaryFormat = "<2f"

	def __init__(self):
		self.u = 0.0
		self.v = 0.0

	def GetSize(self):
		return struct.calcsize(self.binaryFormat)

	def Load(self, file):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		# for some reason quake3 texture maps are upside down, flip that
		self.u = data[0]
		self.v = 1.0 - data[1]
		return self

	def Save(self, file):
		tmpData = [0] * 2
		tmpData[0] = self.u
		tmpData[1] = 1.0 - self.v
		data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
		file.write(data)
		#print "wrote MD3 texture coordinate structure: ", data

	def Dump(self):
		log.info("MD3 Texture Coordinates")
		log.info("U: %s", self.u)
		log.info("V: %s", self.v)
		log.info("")

class md3Triangle:
	indexes = []

	binaryFormat = "<3i"

	def __init__(self):
		self.indexes = [ 0, 0, 0 ]

	def GetSize(self):
		return struct.calcsize(self.binaryFormat)

	def Load(self, file):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		self.indexes[0] = data[0]
		self.indexes[1] = data[2] # reverse
		self.indexes[2] = data[1] # reverse
		return self

	def Save(self, file):
		tmpData = [0] * 3
		tmpData[0] = self.indexes[0]
		tmpData[1] = self.indexes[2] # reverse
		tmpData[2] = self.indexes[1] # reverse
		data = struct.pack(self.binaryFormat,tmpData[0], tmpData[1], tmpData[2])
		file.write(data)
		#print "wrote MD3 face structure: ",data

	def Dump(self, log):
		log.info("MD3 Triangle")
		log.info("Indices: %s", self.indexes)
		log.info("")

class md3Shader:
	name = ""
	index = 0

	binaryFormat = "<%dsi" % MAX_QPATH

	def __init__(self):
		self.name = ""
		self.index = 0

	def GetSize(self):
		return struct.calcsize(self.binaryFormat)

	def Load(self, file):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		self.name = asciiz(data[0])
		self.index = data[1]
		return self

	def Save(self, file):
		tmpData = [0] * 2
		tmpData[0] = self.name
		tmpData[1] = self.index
		data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
		file.write(data)
		#print "wrote MD3 shader structure: ",data

	def Dump(self, log):
		log.info("MD3 Shader")
		log.info("Name: %s", self.name)
		log.info("Index: %s", self.index)
		log.info("")

class md3Surface:
	ident = ""
	name = ""
	flags = 0
	numFrames = 0
	numShaders = 0
	numVerts = 0
	numTriangles = 0
	ofsTriangles = 0
	ofsShaders = 0
	ofsUV = 0
	ofsVerts = 0
	ofsEnd = 0
	shaders = []
	triangles = []
	uv = []
	verts = []

	binaryFormat = "<4s%ds10i" % MAX_QPATH  # 1 int, name, then 10 ints

	def __init__(self):
		self.ident = ""
		self.name = ""
		self.flags = 0
		self.numFrames = 0
		self.numShaders = 0
		self.numVerts = 0
		self.numTriangles = 0
		self.ofsTriangles = 0
		self.ofsShaders = 0
		self.ofsUV = 0
		self.ofsVerts = 0
		self.ofsEnd
		self.shaders = []
		self.triangles = []
		self.uv = []
		self.verts = []

	def GetSize(self):
		sz = struct.calcsize(self.binaryFormat)
		self.ofsTriangles = sz
		for t in self.triangles:
			sz += t.GetSize()
		self.ofsShaders = sz
		for s in self.shaders:
			sz += s.GetSize()
		self.ofsUV = sz
		for u in self.uv:
			sz += u.GetSize()
		self.ofsVerts = sz
		for v in self.verts:
			sz += v.GetSize()
		self.ofsEnd = sz
		return self.ofsEnd

	def Load(self, file, log):
		# where are we in the file (for calculating real offsets)
		ofsBegin = file.tell()
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		self.ident = data[0]
		self.name = asciiz(data[1])
		self.flags = data[2]
		self.numFrames = data[3]
		self.numShaders = data[4]
		self.numVerts = data[5]
		self.numTriangles = data[6]
		self.ofsTriangles = data[7]
		self.ofsShaders = data[8]
		self.ofsUV = data[9]
		self.ofsVerts = data[10]
		self.ofsEnd = data[11]

		# load the tri info
		file.seek(ofsBegin + self.ofsTriangles, 0)
		for i in range(0, self.numTriangles):
			self.triangles.append(md3Triangle())
			self.triangles[i].Load(file)
			#self.triangles[i].Dump(log)

		# load the shader info
		file.seek(ofsBegin + self.ofsShaders, 0)
		for i in range(0, self.numShaders):
			self.shaders.append(md3Shader())
			self.shaders[i].Load(file)
			#self.shaders[i].Dump(log)

		# load the uv info
		file.seek(ofsBegin + self.ofsUV, 0)
		for i in range(0, self.numVerts):
			self.uv.append(md3TexCoord())
			self.uv[i].Load(file)
			#self.uv[i].Dump(log)

		# load the verts info
		file.seek(ofsBegin + self.ofsVerts, 0)
		for i in range(0, self.numFrames):
			for j in range(0, self.numVerts):
				self.verts.append(md3Vert())
				#i*self.numVerts+j=where in the surface vertex list the vert position for this frame is
				self.verts[(i * self.numVerts) + j].Load(file)
				#self.verts[j].Dump(log)

		# go to the end of this structure
		file.seek(ofsBegin+self.ofsEnd, 0)

		return self

	def Save(self, file):
		self.GetSize()
		tmpData = [0] * 12
		tmpData[0] = self.ident
		tmpData[1] = self.name
		tmpData[2] = self.flags
		tmpData[3] = self.numFrames
		tmpData[4] = self.numShaders
		tmpData[5] = self.numVerts
		tmpData[6] = self.numTriangles
		tmpData[7] = self.ofsTriangles
		tmpData[8] = self.ofsShaders
		tmpData[9] = self.ofsUV
		tmpData[10] = self.ofsVerts
		tmpData[11] = self.ofsEnd
		data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7],tmpData[8],tmpData[9],tmpData[10],tmpData[11])
		file.write(data)

		# write the tri data
		for t in self.triangles:
			t.Save(file)

		# save the shader coordinates
		for s in self.shaders:
			s.Save(file)

		# save the uv info
		for u in self.uv:
			u.Save(file)

		# save the verts
		for v in self.verts:
			v.Save(file)

	def Dump(self, log):
		log.info("MD3 Surface")
		log.info("Ident: %s", self.ident)
		log.info("Name: %s", self.name)
		log.info("Flags: %s", self.flags)
		log.info("Number of Frames: %s", self.numFrames)
		log.info("Number of Shaders: %s", self.numShaders)
		log.info("Number of Verts: %s", self.numVerts)
		log.info("Number of Triangles: %s", self.numTriangles)
		log.info("Offset to Triangles: %s", self.ofsTriangles)
		log.info("Offset to Shaders: %s", self.ofsShaders)
		log.info("Offset to UV: %s", self.ofsUV)
		log.info("Offset to Verts: %s", self.ofsVerts)
		log.info("Offset to end: %s", self.ofsEnd)
		log.info("")

class md3Tag:
	name = ""
	origin = []
	axis = []

	binaryFormat="<%ds3f9f" % MAX_QPATH

	def __init__(self):
		self.name = ""
		self.origin = [0, 0, 0]
		self.axis = [0, 0, 0, 0, 0, 0, 0, 0, 0]

	def GetSize(self):
		return struct.calcsize(self.binaryFormat)

	def Load(self, file):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		self.name = asciiz(data[0])
		self.origin[0] = data[1]
		self.origin[1] = data[2]
		self.origin[2] = data[3]
		self.axis[0] = data[4]
		self.axis[1] = data[5]
		self.axis[2] = data[6]
		self.axis[3] = data[7]
		self.axis[4] = data[8]
		self.axis[5] = data[9]
		self.axis[6] = data[10]
		self.axis[7] = data[11]
		self.axis[8] = data[12]
		return self

	def Save(self, file):
		tmpData = [0] * 13
		tmpData[0] = self.name
		tmpData[1] = float(self.origin[0])
		tmpData[2] = float(self.origin[1])
		tmpData[3] = float(self.origin[2])
		tmpData[4] = float(self.axis[0])
		tmpData[5] = float(self.axis[1])
		tmpData[6] = float(self.axis[2])
		tmpData[7] = float(self.axis[3])
		tmpData[8] = float(self.axis[4])
		tmpData[9] = float(self.axis[5])
		tmpData[10] = float(self.axis[6])
		tmpData[11] = float(self.axis[7])
		tmpData[12] = float(self.axis[8])
		data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6], tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11], tmpData[12])
		file.write(data)
		#print "wrote MD3 Tag structure: ",data

	def Dump(self, log):
		log.info("MD3 Tag")
		log.info("Name: %s", self.name)
		log.info("Origin: %s", self.origin)
		log.info("Axis: %s", self.axis)
		log.info("")

class md3Frame:
	mins = 0
	maxs = 0
	localOrigin = 0
	radius = 0.0
	name = ""

	binaryFormat="<3f3f3ff16s"

	def __init__(self):
		self.mins = [0, 0, 0]
		self.maxs = [0, 0, 0]
		self.localOrigin = [0, 0, 0]
		self.radius = 0.0
		self.name = ""

	def GetSize(self):
		return struct.calcsize(self.binaryFormat)

	def Load(self, file):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)
		self.mins[0] = data[0]
		self.mins[1] = data[1]
		self.mins[2] = data[2]
		self.maxs[0] = data[3]
		self.maxs[1] = data[4]
		self.maxs[2] = data[5]
		self.localOrigin[0] = data[6]
		self.localOrigin[1] = data[7]
		self.localOrigin[2] = data[8]
		self.radius = data[9]
		self.name = asciiz(data[10])
		return self

	def Save(self, file):
		tmpData = [0] * 11
		tmpData[0] = self.mins[0]
		tmpData[1] = self.mins[1]
		tmpData[2] = self.mins[2]
		tmpData[3] = self.maxs[0]
		tmpData[4] = self.maxs[1]
		tmpData[5] = self.maxs[2]
		tmpData[6] = self.localOrigin[0]
		tmpData[7] = self.localOrigin[1]
		tmpData[8] = self.localOrigin[2]
		tmpData[9] = self.radius
		tmpData[10] = self.name
		data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10])
		file.write(data)
		#print "wrote MD3 frame structure: ",data

	def Dump(self, log):
		log.info("MD3 Frame")
		log.info("Min Bounds: %s", self.mins)
		log.info("Max Bounds: %s", self.maxs)
		log.info("Local Origin: %s", self.localOrigin)
		log.info("Radius: %s", self.radius)
		log.info("Name: %s", self.name)
		log.info("")

class md3Object:
	# header structure
	ident = ""			# this is used to identify the file (must be IDP3)
	version = 0			# the version number of the file (Must be 15)
	name = ""
	flags = 0
	numFrames = 0
	numTags = 0
	numSurfaces = 0
	numSkins = 0
	ofsFrames = 0
	ofsTags = 0
	ofsSurfaces = 0
	ofsEnd = 0
	frames = []
	tags = []
	surfaces = []

	binaryFormat="<4si%ds9i" % MAX_QPATH  # little-endian (<), 17 integers (17i)

	def __init__(self):
		self.ident = 0
		self.version = 0
		self.name = ""
		self.flags = 0
		self.numFrames = 0
		self.numTags = 0
		self.numSurfaces = 0
		self.numSkins = 0
		self.ofsFrames = 0
		self.ofsTags = 0
		self.ofsSurfaces = 0
		self.ofsEnd = 0
		self.frames = []
		self.tags = []
		self.surfaces = []

	def GetSize(self):
		self.ofsFrames = struct.calcsize(self.binaryFormat)
		self.ofsTags = self.ofsFrames
		for f in self.frames:
			self.ofsTags += f.GetSize()
		self.ofsSurfaces += self.ofsTags
		for t in self.tags:
			self.ofsSurfaces += t.GetSize()
		self.ofsEnd = self.ofsSurfaces
		for s in self.surfaces:
			self.ofsEnd += s.GetSize()
		return self.ofsEnd

	def Load(self, file, log):
		tmpData = file.read(struct.calcsize(self.binaryFormat))
		data = struct.unpack(self.binaryFormat, tmpData)

		self.ident = data[0]
		self.version = data[1]

		if(self.ident != "IDP3" or self.version != 15):
			log.error("Not a valid MD3 file")
			log.error("Ident: %s", self.ident)
			log.error("Version: %s", self.version)
			Exit()

		self.name = asciiz(data[2])
		self.flags = data[3]
		self.numFrames = data[4]
		self.numTags = data[5]
		self.numSurfaces = data[6]
		self.numSkins = data[7]
		self.ofsFrames = data[8]
		self.ofsTags = data[9]
		self.ofsSurfaces = data[10]
		self.ofsEnd = data[11]

		# load the frame info
		file.seek(self.ofsFrames, 0)
		for i in range(0, self.numFrames):
			self.frames.append(md3Frame())
			self.frames[i].Load(file)
			#self.frames[i].Dump(log)

		# load the tags info
		file.seek(self.ofsTags, 0)
		for i in range(0, self.numFrames):
			for j in range(0, self.numTags):
				tag = md3Tag()
				tag.Load(file)
				#tag.Dump(log)
				self.tags.append(tag)

		# load the surface info
		file.seek(self.ofsSurfaces, 0)
		for i in range(0, self.numSurfaces):
			self.surfaces.append(md3Surface())
			self.surfaces[i].Load(file, log)
			self.surfaces[i].Dump(log)
		return self

	def Save(self, file):
		self.GetSize()
		tmpData = [0] * 12
		tmpData[0] = self.ident
		tmpData[1] = self.version
		tmpData[2] = self.name
		tmpData[3] = self.flags
		tmpData[4] = self.numFrames
		tmpData[5] = self.numTags
		tmpData[6] = self.numSurfaces
		tmpData[7] = self.numSkins
		tmpData[8] = self.ofsFrames
		tmpData[9] = self.ofsTags
		tmpData[10] = self.ofsSurfaces
		tmpData[11] = self.ofsEnd

		data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11])
		file.write(data)

		for f in self.frames:
			f.Save(file)

		for t in self.tags:
			t.Save(file)

		for s in self.surfaces:
			s.Save(file)

	def Dump(self, log):
		log.info("Header Information")
		log.info("Ident: %s", self.ident)
		log.info("Version: %s", self.version)
		log.info("Name: %s", self.name)
		log.info("Flags: %s", self.flags)
		log.info("Number of Frames: %s",self.numFrames)
		log.info("Number of Tags: %s", self.numTags)
		log.info("Number of Surfaces: %s", self.numSurfaces)
		log.info("Number of Skins: %s", self.numSkins)
		log.info("Offset Frames: %s", self.ofsFrames)
		log.info("Offset Tags: %s", self.ofsTags)
		log.info("Offset Surfaces: %s", self.ofsSurfaces)
		log.info("Offset end: %s", self.ofsEnd)
		log.info("")

def asciiz(s):
	n = 0
	while(ord(s[n]) != 0):
		n = n + 1
	return s[0:n]

# strips the slashes from the back of a string
def StripPath(path):
	for c in range(len(path), 0, -1):
		if path[c-1] == "/" or path[c-1] == "\\":
			path = path[c:]
			break
	return path

# strips the model from path
def StripModel(path):
	for c in range(len(path), 0, -1):
		if path[c-1] == "/" or path[c-1] == "\\":
			path = path[:c]
			break
	return path

# strips file type extension
def StripExtension(name):
	n = 0
	best = len(name)
	while(n != -1):
		n = name.find('.',n+1)
		if(n != -1):
			best = n
	name = name[0:best]
	return name

# strips gamedir
def StripGamePath(name):
	gamepath = GAMEDIR.replace( '\\', '/' )
	namepath = name.replace( '\\', '/' )
	if namepath[0:len(gamepath)] == gamepath:
		namepath= namepath[len(gamepath):len(namepath)]
	return namepath

import sys, struct, string, math

def ANGLE2SHORT(x):
		return int((x * 65536 / 360) & 65535)

def SHORT2ANGLE(x):
		return x * (360.0 / 65536.0)

def DEG2RAD(a):
	return (a * math.pi) / 180.0

def RAD2DEG(a):
	return (a * 180.0) / math.pi

def DotProduct(x, y):
	return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]

def CrossProduct(a,b):
	return [a[1]*b[2] - a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]]

def VectorLength(v):
	return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])

def VectorSubtract(a, b):
	return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]

def VectorAdd(a, b):
	return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]

def VectorCopy(v):
	return [v[0], v[1], v[2]]

def VectorInverse(v):
	return [-v[0], -v[1], -v[2]]

#define VectorCopy(a,b)			((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
#define	VectorScale(v, s, o)	((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
#define	VectorMA(v, s, b, o)	((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s))

def RadiusFromBounds(mins, maxs):
	corner = [0, 0, 0]
	a = 0
	b = 0

	for i in range(0, 3):
		a = abs(mins[i])
		b = abs(maxs[i])
		if a > b:
			corner[i] = a
		else:
			corner[i] = b

	return VectorLength(corner)

# NOTE: Tr3B - matrix is in column-major order
def MatrixIdentity():
	return [[1.0, 0.0, 0.0, 0.0],
			[0.0, 1.0, 0.0, 0.0],
			[0.0, 0.0, 1.0, 0.0],
			[0.0, 0.0, 0.0, 1.0]]

def MatrixFromAngles(pitch, yaw, roll):
	sp = math.sin(DEG2RAD(pitch))
	cp = math.cos(DEG2RAD(pitch))

	sy = math.sin(DEG2RAD(yaw))
	cy = math.cos(DEG2RAD(yaw))

	sr = math.sin(DEG2RAD(roll))
	cr = math.cos(DEG2RAD(roll))

#	return [[cp * cy, (sr * sp * cy + cr * -sy), (cr * sp * cy + -sr * -sy), 0.0],
#			[cp * sy, (sr * sp * sy + cr * cy),  (cr * sp * sy + -sr * cy),  0.0],
#			[-sp,      sr * cp,                   cr * cp,                   0.0],
#			[0.0,      0.0,                       0.0,                       1.0]]

	return [[cp * cy,                     cp * sy,                 -sp,       0.0],
			[(sr * sp * cy + cr * -sy),  (sr * sp * sy + cr * cy),  sr * cp,  0.0],
			[(cr * sp * cy + -sr * -sy), (cr * sp * sy + -sr * cy), cr * cp,  0.0],
			[0.0,                         0.0,                      0.0,      1.0]]

def MatrixTransformPoint(m, p):
	return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2] + m[3][0],
			m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2] + m[3][1],
			m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2] + m[3][2]]

def MatrixTransformNormal(m, p):
	return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2],
			m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2],
			m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2]]

def MatrixMultiply(b, a):
	return [[
			a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
			a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
			a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2],
			0.0,
			],[
			a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
			a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
			a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2],
			0.0,
			],[
			a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
			a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
			a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2],
			0.0,
			],[
			a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0],
			a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1],
			a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2],
			1.0,
			]]

def MatrixSetupTransform(forward, left, up, origin):
	return [[forward[0], forward[1], forward[2], origin[0]],
			[left[0],    left[1],   left[2],     origin[1]],
			[up[0],      up[1],     up[2],       origin[2]],
			[0.0,        0.0,       0.0,         1.0]]

# our own logger class. it works just the same as a normal logger except
# all info messages get show.
class Logger(logging.Logger):
	def __init__(self, name,level = logging.NOTSET):
		logging.Logger.__init__(self, name, level)

		self.has_warnings = False
		self.has_errors = False
		self.has_critical = False

	def info(self, msg, *args, **kwargs):
		apply(self._log,(logging.INFO, msg, args), kwargs)

	def warning(self, msg, *args, **kwargs):
		logging.Logger.warning(self, msg, *args, **kwargs)
		self.has_warnings = True

	def error(self, msg, *args, **kwargs):
		logging.Logger.error(self, msg, *args, **kwargs)
		self.has_errors = True

	def critical(self, msg, *args, **kwargs):
		logging.Logger.critical(self, msg, *args, **kwargs)
		self.has_errors = True

# should be able to make this print to stdout in realtime and save MESSAGES
# as well. perhaps also have a log to file option
class LogHandler(logging.StreamHandler):
	def __init__(self):
		logging.StreamHandler.__init__(self, sys.stdout)

		if "md3_export_log" not in Blender.Text.Get():
			self.outtext = Blender.Text.New("md3_export_log")
		else:
			self.outtext = Blender.Text.Get('md3_export_log')
			self.outtext.clear()

		self.lastmsg = ''

	def emit(self, record):
		# print to stdout and  to a new blender text object
		msg = self.format(record)

		if msg == self.lastmsg:
			return

		self.lastmsg = msg
		self.outtext.write("%s\n" %msg)

		logging.StreamHandler.emit(self, record)

logging.setLoggerClass(Logger)
log = logging.getLogger('md3_export')

handler = LogHandler()
formatter = logging.Formatter('%(levelname)s %(message)s')
handler.setFormatter(formatter)

log.addHandler(handler)
# set this to minimum output level. eg. logging.DEBUG, logging.WARNING, logging.ERROR
# logging.CRITICAL. logging.INFO will make little difference as these always get
# output'd
log.setLevel(logging.WARNING)

class BlenderGui:
	def __init__(self):
		text = """A log has been written to a blender text window. Change this window type to
a text window and you will be able to select the file md3_export_log."""

		text = textwrap.wrap(text,40)
		text += ['']

		if log.has_critical:
			text += ['There were critical errors!!!!']

		elif log.has_errors:
			text += ['There were errors!']

		elif log.has_warnings:
			text += ['There were warnings']

		# add any more text before here
		text.reverse()

		self.msg = text

		Blender.Draw.Register(self.gui, self.event, self.button_event)

	def gui(self,):
		quitbutton = Blender.Draw.Button("Exit", 1, 0, 0, 100, 20, "Close Window")

		y = 35

		for line in self.msg:
			BGL.glRasterPos2i(10,y)
			Blender.Draw.Text(line)
			y+=15

	def event(self,evt, val):
		if evt == Blender.Draw.ESCKEY:
			Blender.Draw.Exit()
			return

	def button_event(self,evt):
		if evt == 1:
			Blender.Draw.Exit()
			return

def ApplyTransform(vert, matrix):
	return vert * matrix

def UpdateFrameBounds(v, f):
	for i in range(0, 3):
		f.mins[i] = min(v[i], f.mins[i])
	for i in range(0, 3):
		f.maxs[i] = max(v[i], f.maxs[i])

def UpdateFrameRadius(f):
	f.radius = RadiusFromBounds(f.mins, f.maxs)

def ProcessSurface(scene, blenderObject, md3, pathName, modelName):
	# because md3 doesnt suppoort faceUVs like blender, we need to duplicate
	# any vertex that has multiple uv coords

	vertDict = {}
	indexDict = {} # maps a vertex index to the revised index after duplicating to account for uv
	vertList = [] # list of vertices ordered by revised index
	numVerts = 0
	uvList = [] # list of tex coords ordered by revised index
	faceList = [] # list of faces (they index into vertList)
	numFaces = 0

	scene.makeCurrent()
	Blender.Set("curframe", 1)
	Blender.Window.Redraw()

	# get the object (not just name) and the Mesh, not NMesh
	mesh = blenderObject.getData(False, True)
	matrix = blenderObject.getMatrix('worldspace')

	surf = md3Surface()
	surf.numFrames = md3.numFrames
	surf.name = blenderObject.getName()
	surf.ident = MD3_IDENT

	# create shader for surface
	surf.shaders.append(md3Shader())
	surf.numShaders += 1
	surf.shaders[0].index = 0

	log.info("Materials: %s", mesh.materials)
	# :P
	#shaderpath=Blender.Draw.PupStrInput("shader path for "+blenderObject.name+":", "", MAX_QPATH )
        shaderpath=""
	if 	shaderpath == "" :
		if not mesh.materials:
			surf.shaders[0].name = pathName + blenderObject.name
		else:
			surf.shaders[0].name = pathName + mesh.materials[0].name
	else:
		if not mesh.materials:
			surf.shaders[0].name = shaderpath + blenderObject.name
		else:
			surf.shaders[0].name = shaderpath + mesh.materials[0].name

	# process each face in the mesh
	for face in mesh.faces:

		tris_in_this_face = []  #to handle quads and up...

		# this makes a list of indices for each tri in this face. a quad will be [[0,1,1],[0,2,3]]
		for vi in range(1, len(face.v)-1):
			tris_in_this_face.append([0, vi, vi + 1])

		# loop across each tri in the face, then each vertex in the tri
		for this_tri in tris_in_this_face:
			numFaces += 1
			tri = md3Triangle()
			tri_ind = 0
			for i in this_tri:
				# get the vertex index, coords and uv coords
				index = face.v[i].index
				v = face.v[i].co
				if mesh.faceUV == True:
					uv = (face.uv[i][0], face.uv[i][1])
				elif mesh.vertexUV:
					uv = (face.v[i].uvco[0], face.v[i].uvco[1])
				else:
					uv = (0.0, 0.0) # handle case with no tex coords

				if vertDict.has_key((index, uv)):
					# if we've seen this exact vertex before, simply add it
					# to the tris list of vertex indices
					tri.indexes[tri_ind] = vertDict[(index, uv)]
				else:
					# havent seen this tri before
					# (or its uv coord is different, so we need to duplicate it)

					vertDict[(index, uv)] = numVerts

					# put the uv coord into the list
					# (uv coord are directly related to each vertex)
					tex = md3TexCoord()
					tex.u = uv[0]
					tex.v = uv[1]
					uvList.append(tex)

					tri.indexes[tri_ind] = numVerts

					# now because we have created a new index,
					# we need a way to link it to the index that
					# blender returns for NMVert.index
					if indexDict.has_key(index):
						# already there - each of the entries against
						# this key represents  the same vertex with a
						# different uv value
						ilist = indexDict[index]
						ilist.append(numVerts)
						indexDict[index] = ilist
					else:
						# this is a new one
						indexDict[index] = [numVerts]

					numVerts += 1
				tri_ind +=1
			faceList.append(tri)

	# we're done with faces and uv coords
	for t in uvList:
		surf.uv.append(t)

	for f in faceList:
		surf.triangles.append(f)

	surf.numTriangles = len(faceList)
	surf.numVerts = numVerts

	# now vertices are stored as frames -
	# all vertices for frame 1, all vertices for frame 2...., all vertices for frame n
	# so we need to iterate across blender's frames, and copy out each vertex
	for	frameNum in range(1, md3.numFrames + 1):
		Blender.Set("curframe", frameNum)
		Blender.Window.Redraw()

		m = NMesh.GetRawFromObject(blenderObject.name)

		vlist = [0] * numVerts
		for vertex in m.verts:
			try:
				vindices = indexDict[vertex.index]
			except:
				log.warning("Found a vertex in %s that is not part of a face", blenderObject.name)
				continue

			vTx = ApplyTransform(vertex.co, matrix)
			nTx = ApplyTransform(vertex.no, matrix)
			UpdateFrameBounds(vTx, md3.frames[frameNum - 1])
			vert = md3Vert()
			#vert.xyz = vertex.co[0:3]
			#vert.normal = vert.Encode(vertex.no[0:3])
			vert.xyz = vTx[0:3]
			vert.normal = vert.Encode(vertex.no[0:3])
                        #print vertex.no
			for ind in vindices:  # apply the position to all the duplicated vertices
				vlist[ind] = vert

		UpdateFrameRadius(md3.frames[frameNum - 1])

		for vl in vlist:
			surf.verts.append(vl)

	surf.Dump(log)
	md3.surfaces.append(surf)
	md3.numSurfaces += 1

def Export(fileName):
	if(fileName.find('.md3', -4) <= 0):
		fileName += '.md3'

	log.info("Starting ...")

	log.info("Exporting MD3 format to: %s", fileName)

	pathName = StripGamePath(StripModel(fileName))
	log.info("Shader path name: %s", pathName)

	modelName = StripExtension(StripPath(fileName))
	log.info("Model name: %s", modelName)

	md3 = md3Object()
	md3.ident = MD3_IDENT
	md3.version = MD3_VERSION

	tagList = []

	# get the scene
	scene = Blender.Scene.getCurrent()
	context = scene.getRenderingContext()

	scene.makeCurrent()
	md3.numFrames = Blender.Get("curframe")
	Blender.Set("curframe", 1)

	# create a bunch of blank frames, they'll be filled in by 'ProcessSurface'
	for i in range(1, md3.numFrames + 1):
		frame = md3Frame()
		frame.name = "frame_" + str(i)
		md3.frames.append(frame)

	# export all selected objects
	objlist = Blender.Object.GetSelected()

	# process each object for the export
	for obj in objlist:
		# check if it's a mesh object
		if obj.getType() == "Mesh":
			log.info("Processing surface: %s", obj.name)
			if len(md3.surfaces) == MD3_MAX_SURFACES:
				log.warning("Hit md3 limit (%i) for number of surfaces, skipping ...", MD3_MAX_SURFACES, obj.getName())
			else:
				ProcessSurface(scene, obj, md3, pathName, modelName)
		elif obj.getType() == "Empty":   # for tags, we just put em in a list so we can process them all together
			if obj.name[0:4] == "tag_":
				log.info("Processing tag: %s", obj.name)
				tagList.append(obj)
				md3.numTags += 1
		else:
			log.info("Skipping object: %s", obj.name)

	# work out the transforms for the tags for each frame of the export
	for i in range(1, md3.numFrames + 1):

		# needed to update IPO's value, but probably not the best way for that...
		scene.makeCurrent()
		Blender.Set("curframe", i)
		Blender.Window.Redraw()
		for tag in tagList:
			t = md3Tag()
			matrix = tag.getMatrix('worldspace')
			t.origin[0] = matrix[3][0]
			t.origin[1] = matrix[3][1]
			t.origin[2] = matrix[3][2]

			t.axis[0] = matrix[0][0]
			t.axis[1] = matrix[0][1]
			t.axis[2] = matrix[0][2]

			t.axis[3] = matrix[1][0]
			t.axis[4] = matrix[1][1]
			t.axis[5] = matrix[1][2]

			t.axis[6] = matrix[2][0]
			t.axis[7] = matrix[2][1]
			t.axis[8] = matrix[2][2]
			t.name = tag.name
			#t.Dump(log)
			md3.tags.append(t)

	# export!
	file = open(fileName, "wb")
	md3.Save(file)
	file.close()
	md3.Dump(log)

def FileSelectorCallback(fileName):
	Export(fileName)

	BlenderGui()

Blender.Window.FileSelector(FileSelectorCallback, "Export Quake3 MD3")


code:
#include "compat.h"

struct hsv {
    float h, s, v;
};

struct rgb {
    float r, g, b;
};

struct gradient {
    int start, len, chompends;
    struct hsv startcolour, endcolour;
};

float min2(float x, float y);
float max2(float x, float y);
void convertHSVtoRGB(struct hsv *hsv, struct rgb *rgb);
int showusage(void);
int readscript(char *fn);

struct gradient ramps[256];
int nramps = 0;

int main(int argc, char **argv)
{
    struct hsv palette[256], lerpstep, lerped;
    struct rgb rgbidx;
    unsigned char rgbout[3];
    int idx, step, rampnum;
    FILE* fh;
    char const * outfile = "palette.dat";

    memset(palette,0,sizeof(palette));

    if (argc < 2) return showusage();
    if (readscript(argv[1])) return 1;
    if (argc >= 3) outfile = argv[2];

    for (rampnum = 0; rampnum < nramps; rampnum++) {
        idx = ramps[rampnum].start;
        step = ramps[rampnum].len;
        if (ramps[rampnum].chompends & 1) step++;
        if (ramps[rampnum].chompends & 2) step++;
        lerpstep.h = (ramps[rampnum].endcolour.h - ramps[rampnum].startcolour.h) / (float)step;
        lerpstep.s = (ramps[rampnum].endcolour.s - ramps[rampnum].startcolour.s) / (float)step;
        lerpstep.v = (ramps[rampnum].endcolour.v - ramps[rampnum].startcolour.v) / (float)step;
        lerped = ramps[rampnum].startcolour;
        if (ramps[rampnum].chompends & 1) {
            step--;
            lerped.h += lerpstep.h;
            lerped.s += lerpstep.s;
            lerped.v += lerpstep.v;
        }
        if (ramps[rampnum].chompends & 2) step--;

        for (; step > 0; step--,idx++) {
            palette[idx].h = lerped.h;
            palette[idx].s = lerped.s;
            palette[idx].v = lerped.v;
            lerped.h += lerpstep.h;
            lerped.s += lerpstep.s;
            lerped.v += lerpstep.v;
        }
    }

    fh = fopen(outfile,"wb");
    if (!fh) return 1;

    for (idx=0; idx<256; idx++) {
        convertHSVtoRGB(&palette[idx], &rgbidx);
        //printf("Index %d: r=%g g=%g b=%g\n",idx,rgbidx.r,rgbidx.g,rgbidx.b);
        rgbout[0] = (unsigned char)min2(255,max2(0,(int)(rgbidx.r * 255.0))) >> 2;
        rgbout[1] = (unsigned char)min2(255,max2(0,(int)(rgbidx.g * 255.0))) >> 2;
        rgbout[2] = (unsigned char)min2(255,max2(0,(int)(rgbidx.b * 255.0))) >> 2;
        fwrite(rgbout,3,1,fh);
    }

    fclose(fh);

    return 0;
}

float min2(float x, float y)
{
    return x < y ? x : y;
}

float max2(float x, float y)
{
    return x > y ? x : y;
}

// http://www.cs.rit.edu/~ncs/color/t_convert.html
void convertHSVtoRGB(struct hsv *hsv, struct rgb *rgb)
{
    int i;
    float f, p, q, t;
    if( hsv->s == 0 ) {
        // achromatic (grey)
        rgb->r = rgb->g = rgb->b = hsv->v;
        return;
    }
    hsv->h /= 60;            // sector 0 to 5
    i = floor( hsv->h );
    f = hsv->h - i;            // factorial part of h
    p = hsv->v * ( 1 - hsv->s );
    q = hsv->v * ( 1 - hsv->s * f );
    t = hsv->v * ( 1 - hsv->s * ( 1 - f ) );
    switch( i ) {
        case 0:
            rgb->r = hsv->v;
            rgb->g = t;
            rgb->b = p;
            break;
        case 1:
            rgb->r = q;
            rgb->g = hsv->v;
            rgb->b = p;
            break;
        case 2:
            rgb->r = p;
            rgb->g = hsv->v;
            rgb->b = t;
            break;
        case 3:
            rgb->r = p;
            rgb->g = q;
            rgb->b = hsv->v;
            break;
        case 4:
            rgb->r = t;
            rgb->g = p;
            rgb->b = hsv->v;
            break;
        default:        // case 5:
            rgb->r = hsv->v;
            rgb->g = p;
            rgb->b = q;
            break;
    }
}

int showusage(void)
{
    puts("mkpalette <palettescript.txt> [outputfile]");
    puts("If outputfile is not given, palette.dat is assumed");

    puts("\nPalette script format:\n"
    "  A line beginning with # is a comment, otherwise each line contains none\n"
    "values separated by spaces defining the gradient:\n"
    "\n"
    "  startindex rangesize skip starthue startsat startval endhue endsat endval\n"
    "\n"
    "Any text after the end of a gradient description is ignored, so may use it\n"
    "to describe the colour.\n"
    "\n"
    "* 'startindex' specifies the first palette index to write to\n"
    "* 'rangesize' specifies the length of the gradient\n"
    "* 'skip' specifies whether the first and/or last elements of the range should\n"
    "  be ignored and with 'rangesize' elements interpolated between. This is so\n"
    "  you can have a gradient starting at (potentially) pure black and ending at\n"
    "  (potentially) pure white but without wasting a palette index on those colours\n"
    "  if they already exist, eg. in a grey ramp.\n"
    "  Legal values are 0 (no skipping), 1 (skip start), 2 (skip end),\n"
    "  or 3 (skip start and end).\n"
    "* 'starthue', 'startsat', 'startval' are integers specifying the beginning\n"
    "  colour in Hue-Saturation-Value format.\n"
    "  'starthue' should be in the range 0-360 indicating a degrees value\n"
    "  'startsat' should be in the range 0-100 indicating a saturation percentage\n"
    "  'startval' should be in the range 0-100 indicating an intensity percentage\n"
    "* 'endhue', 'endsat', 'endval' specify the ending colour."
    );
    return 0;
}

int readscript(char *fn)
{
    int start, len, skip, shue, ssat, sval, ehue, esat, eval;
    FILE *fp;
    char line[1024];

    fp = fopen(fn,"rt");
    if (!fp) {
        puts("Error opening palette script");
        return 1;
    }

    while (fgets(line,sizeof(line),fp)) {
        {
            // test for comment
            char *p = line;
            while (*p && (*p == ' ' || *p == '\t')) p++;
            if (*p == '#') continue;
        }

        if (sscanf(line, "%d %d %d %d %d %d %d %d %d", &start,&len,&skip,&shue,&ssat,&sval,&ehue,&esat,&eval) < 9)
            continue;

        if (start < 0 || start > 255) {
            printf("start index of %d is out of range 0-255\n", start);
            continue;
        }
        if (len < 1 || len > 255) {
            printf("length %d is out of range 1-255\n", len);
            continue;
        }
        if (skip != (skip&3)) {
            printf("skip value of %d is out of range 0-3\n", skip);
            continue;
        }
        if (shue < 0 || shue > 360) {
            printf("start hue %d is out of range 0-360\n", shue);
            continue;
        }
        if (ssat < 0 || ssat > 100) {
            printf("start saturation %d is out of range 0-100\n", ssat);
            continue;
        }
        if (sval < 0 || sval > 100) {
            printf("start value %d is out of range 0-100\n", sval);
            continue;
        }
        if (ehue < 0 || ehue > 360) {
            printf("end hue %d is out of range 0-360\n", shue);
            continue;
        }
        if (esat < 0 || esat > 100) {
            printf("end saturation %d is out of range 0-100\n", ssat);
            continue;
        }
        if (eval < 0 || eval > 100) {
            printf("end value %d is out of range 0-100\n", sval);
            continue;
        }

        ramps[nramps].start = start;
        ramps[nramps].len = len;
        ramps[nramps].chompends = skip;
        ramps[nramps].startcolour.h = (float)shue;
        ramps[nramps].startcolour.s = (float)ssat / 100.0;
        ramps[nramps].startcolour.v = (float)sval / 100.0;
        ramps[nramps].endcolour.h = (float)ehue;
        ramps[nramps].endcolour.s = (float)esat / 100.0;
        ramps[nramps].endcolour.v = (float)eval / 100.0;
        nramps++;
    }

    fclose(fp);
    return 0;
}

repo_name: orochi/nblood

license: mit


code:
#!/usr/bin/python

import sys;

from numpy import array, zeros, ones, arange, uint32
from numpy import vstack, hstack, hsplit, dstack, dsplit

from PIL.Image import frombuffer

NBITS = 7;
RESIDBITS = 8-NBITS;
DIM = 1<<NBITS;

CONVFACT = 256;

###  target hues
green = .39;  # 11, 22
yellow = .17;  # 23
red = .04;  # 21

def genbasepal():
    "Generate base palette for highpalookup system. \
All other palettes must be based on this one."
    imint = zeros([DIM, DIM, DIM], 'uint32');
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                imint[i,j,k] = ((i<<16)|(k<<8)|j)<<RESIDBITS;

    imint = hstack(dsplit(imint, DIM));

    imbyte = zeros([DIM, DIM*DIM, 3], 'uint8');

    for i in range(3):
        imbyte[:,:,i] = (imint[:,:,0]>>(i*8))&255;

    return imbyte;

def getdispimg(im):
    "Get a reshaped version of the palette image IM suitable for viewing."
    # 2^NBITS x 2^NBITS*2^NBITS --> 2^(NBITS + NBITS/2) x 2^NBITS*2^(NBITS - NBITS/2)

    if (im.shape != (DIM, DIM*DIM, 3)):
        raise ValueError("image must have shape (DIM, DIM*DIM, 3)");

    dimfactor = 1<<(NBITS/2);
    return vstack(hsplit(im, dimfactor));

def getPILimg(im):
    sz = im.shape;
    return frombuffer("RGB", [sz[1], sz[0]], im, "raw", "RGB", 0, 1);

def saveimage(im, filename):
    getPILimg(im).save(filename);

def showpalimg(im):
    getPILimg(getdispimg(im)).show();

### utility functions

## port of Octave's rbg2hsv
def rgb2hsv(im):
    if (im.dtype=='uint8'):
        im = imitof(im);

    r, g, b = im[..., 0], im[..., 1], im[..., 2];
    s, v = im.min(2), im.max(2);
    dif = v-s;

    colored = (s != v);

    h = zeros(r.shape, im.dtype);

    # blue hue
    idx = ((v==b) & colored);
    h[idx] = 2./3 + 1./6*(r[idx]-g[idx])/dif[idx];

    # green hue
    idx = ((v==g) & colored);
    h[idx] = 1./3 + 1./6*(b[idx]-r[idx])/dif[idx];

    # red hue
    idx = ((v==r) & colored);
    h[idx] =        1./6*(g[idx]-b[idx])/dif[idx];
    h[idx] %= 1;

    s[~colored] = 0;
    s[colored] = 1.0 - s[colored]/v[colored];

    return dstack((h,s,v));

## port of Octave's hsv2rbg
def hsv2rgb(imh):
    imh[imh<0] = 0;
    imh[imh>1] = 1;

    h, s, v = imh[..., 0], imh[..., 1], imh[..., 2];

    rgb = v*(1.0-s);
    rgb = dstack((rgb, rgb, rgb));

    hue = dstack((h-2./3, h, h-1./3))%1;
    f = s*v;
    f = dstack((f, f, f));

    rgb += f * (6.0 * (hue < 1./6)*hue
                + ((hue >= 1./6) & (hue < 1./2))
                + ((hue >= 1./2) & (hue < 2./3))*(4.0 - 6.0*hue));

    return imftoi(rgb);

def imftoi(im):
    im = im.copy();
    if (im.dtype=='uint8'):
        return im
    im *= CONVFACT;
    im[im>255] = 255;
    return im.astype('uint8');

def imitof(im):
    if (im.dtype=='float32'):
        return im.copy();
    return im.astype('float32')/CONVFACT;

###
def genpal(basepal, basepalhsv, pal):
    "Generate a highpalookup image for palette number PAL. \
BASEPALHSV should the precomputed HSV representation of BASEPAL."

    if (basepal.dtype != 'uint8'):
        raise TypeError('BASEPAL should be uint8.');

    if (pal==0):
        return basepal;

    bph = basepalhsv;
    h,s,v = bph[..., 0], bph[..., 1], bph[..., 2];

    bluemask = (h>0.52) & (h<0.8) & (s>0.2);

    # all true mask will be used unless overridden
    mask = ones(h.shape, 'bool');

## PHRP r176 defs:
#
# tint { pal 1 red 100 green 120 blue 148 flags 1 }
# tint { pal 2 red 255 green  48 blue   0 flags 0 }
# tint { pal 4 red   0 green   0 blue   0 flags 0 }
# tint { pal 6 red 224 green 255 blue 112 flags 3 }
# tint { pal 7 red 172 green 157 blue 140 flags 0 }
# tint { pal 8 red 199 green 226 blue 113 flags 1 }
#
# bit 1: greyscale (max)
# bit 2: invert (255-x)
# colorization: min(int(C*C')/64, 255)

    if (pal in [1,2,4,6,7,8]):
        rgbf = { 1: [100, 120, 148, 1],
                 2: [255,  48,   0, 0],
                 4: [0,     0,   0, 0],
                 6: [224, 255, 112, 3],
                 7: [172, 157, 140, 0],
                 8: [199, 226, 113, 1]}

        newrgb = basepal.astype('uint32');

        flags = rgbf[pal][3]

        if (flags&1):  # greyscale
            newrgb = newrgb.max(2);
            newrgb = dstack((newrgb, newrgb, newrgb)).copy();

        if (flags&2):  # invert
            newrgb = 255-newrgb;

        # colorize
        for i in range(3):
            newrgb[:,:,i] *= rgbf[pal][i]
            newrgb[:,:,i] /= 255
        newrgb[newrgb>255] = 255

        return newrgb.astype('uint8');

# plagman:
#    if (pal==1):
#        h[:] = 0.66;

#    elif (pal==6):
#        h[:] = 0.33;
#        v = 1.0 - v;

    elif (pal==20):
        m1 = ((h>0.6) & (h<0.7));
        m2 = ((h>0.04) & (h<0.13));
        m3 = ((h>0.7) & (h<0.9));
        m4 = ((h>0.3) & (h<0.36));
        mask = m1 | m2 | m3 | m4;

        # blue to gray by removing all saturation
        s[m1] = 0.0;
        # orange and brown to blue
        h[m2] = 0.66;
        # purple and reddish to blue
        h[m3] = 0.66
        # green to blue
        h[m4] = 0.66;

# helixhorned:
    elif (pal==11):
        mask = bluemask;
        h[mask] = green;
        s[mask] += 0.1;

    elif (pal==12):
        mask = bluemask;
        h[mask] = 0.0;
        s[mask] = 0.0;

    elif (pal==13):
        mask = bluemask;
        h[mask] = 0.0;
        s[mask] = 0.0;
        v[mask] *= 0.7;

    elif (pal==16):
        mask = bluemask;
        s[mask] += 0.1;
        v[mask] -= 0.1;

    elif (pal==21):
        mask = bluemask;
        h[mask] = red;
        s[mask] += 0.3;

    elif (pal==23):
        mask = bluemask;
        h[mask] = yellow;
        s[mask] += 0.12;
        v[mask] *= 1.15;

    elif (pal==99):
        mask = bluemask;
        v[mask] = 0;

# user:
# ...

    else:
        raise ValueError("unknown pal {0}!".format(pal));

    # ---
    newrgb = hsv2rgb(dstack((h, s, v)));

    nmask = ~mask;

    r = mask*newrgb[:,:,0] + nmask*basepal[:,:,0];
    g = mask*newrgb[:,:,1] + nmask*basepal[:,:,1];
    b = mask*newrgb[:,:,2] + nmask*basepal[:,:,2];

    # PIL doesn't seem to like views/shallow copies
    return dstack((r, g, b)).copy();

## main
if (__name__ == "__main__"):

    argc = len(sys.argv);
    if (argc == 1):
        print "Usage: python prhighpal.py (palnums ...)"
        sys.exit();

    print "Generating base palette..."
    bp = genbasepal();
    bph = rgb2hsv(bp);

    for i in xrange(1, argc):
        palnum = int(sys.argv[i]);
        filename = "hipal{0}_gen.png".format(palnum);
        print "Generating palnum", palnum, "image ...";
        palimg = genpal(bp.copy(), bph.copy(), palnum);
        print "Writing", filename, "...";
        saveimage(palimg, filename);

    print "\nDEF code:\n"
    for i in xrange(1, argc):
        palnum = int(sys.argv[i]);
        if (palnum==0):
            continue;
        filename = "hipal{0}_gen.png".format(palnum);
        print "highpalookup { pal", palnum, "file", filename, "}";


code:
// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
// Ken Silverman's official web site: "http://www.advsys.net/ken"
// See the included license file "BUILDLIC.TXT" for license info.
//
// This file has been modified from Ken Silverman's original release
// by Jonathon Fowler (jf@jonof.id.au)

#include "compat.h"
#include "pragmas.h"
#include "colmatch.h"

#define MAXPALOOKUPS 256

static int numshades, transratio;
static char const * const palettefilename = "palette.dat";
static uint8_t origpalookup[MAXPALOOKUPS<<8], palookup[MAXPALOOKUPS<<8], transluc[65536];
static uint8_t origpalette[768], palette[768];

static char getshade(char dashade, char dacol)
{
    int r, g, b, t;
    char *ptr;

    ptr = (char *)&palette[dacol*3];
    t = divscale16(numshades-dashade,numshades);
    r = ((ptr[0]*t+32768)>>16);
    g = ((ptr[1]*t+32768)>>16);
    b = ((ptr[2]*t+32768)>>16);
    return(paletteGetClosestColor(r,g,b));
}

static char gettrans(char dat1, char dat2, int datransratio)
{
    int r, g, b;
    char *ptr, *ptr2;

    ptr = (char *)&palette[dat1*3];
    ptr2 = (char *)&palette[dat2*3];
    r = ptr[0]; r += (((ptr2[0]-r)*datransratio+128)>>8);
    g = ptr[1]; g += (((ptr2[1]-g)*datransratio+128)>>8);
    b = ptr[2]; b += (((ptr2[2]-b)*datransratio+128)>>8);
    return(paletteGetClosestColor(r,g,b));
}

int main(int argc, char **argv)
{
    char col, ch;
    short orignumshades;
    int fil, i, j, rscale, gscale, bscale;

    ch = 13;
    if (argc>1) {
        if (argv[1][0] == '-') {
            if (argv[1][1] == 't') { ch = 32; puts("Updating translucency table ONLY"); }
            argc--;
            argv++;
        }
    }

    if ((argc != 3) && (argc != 6))
    {
        Bprintf("TRANSPAL [-t]<numshades><trans#(0-inv,256-opa)><r><g><b>  by Kenneth Silverman\n");
        Bprintf("   Ex #1: transpal 32 170 30 59 11      (I use these values in my BUILD demo)\n");
        Bprintf("                          \xc0\xc4\xc4\xc1\xc4\xc4\xc1\xc4\xc4\xc4 The RGB scales are optional\n");
        Bprintf("   Ex #2: transpal 64 160\n\n");
        Bprintf("Once tables are generated, the optional -t switch determines what to save:\n");
        Bprintf("   Exclude -t to update both the shade table and transluscent table\n");
        Bprintf("   Include -t to update the transluscent table ONLY\n");
        exit(0);
    }

    numshades = Batol(argv[1]);
    transratio = Batol(argv[2]);

    if (argc == 6)
    {
        rscale = Batol(argv[3]);
        gscale = Batol(argv[4]);
        bscale = Batol(argv[5]);
    }
    else
    {
        rscale = 30;
        gscale = 59;
        bscale = 11;
    }

    if ((numshades < 1) || (numshades > 256))
        { Bprintf("Invalid number of shades\n"); exit(0); }
    if ((transratio < 0) || (transratio > 256))
        { Bprintf("Invalid transluscent ratio\n"); exit(0); }

    if ((fil = Bopen(palettefilename,BO_BINARY|BO_RDONLY,BS_IREAD)) == -1)
    {
        Bprintf("%s not found",palettefilename);
        return(0);
    }
    Bread(fil,origpalette,768);
    Bread(fil,&orignumshades,2); orignumshades = B_LITTLE16(orignumshades);
    orignumshades = min(max<int>(orignumshades,1),256);
    Bread(fil,origpalookup,(int)orignumshades<<8);
    Bclose(fil);

    for (int k = 0; k < 768; k++)
        palette[k] = origpalette[k] << 2;

    initdivtables();
    paletteInitClosestColorScale(rscale,gscale,bscale);
    paletteInitClosestColorMap(palette);
    paletteInitClosestColorGrid();

    for(i=0;i<numshades;i++)
        for(j=0;j<256;j++)
        {
            col = getshade((char)i,(char)j);
            palookup[(i<<8)+j] = col;
        }

    for(i=0;i<256;i++)
        for(j=0;j<256;j++)
        {
            col = gettrans((char)i,(char)j,transratio);
            transluc[(i<<8)+j] = col;
        }

    if (ch == 13)
    {
        short s;
        if ((fil = Bopen(palettefilename,BO_BINARY|BO_TRUNC|BO_CREAT|BO_WRONLY,BS_IREAD|BS_IWRITE)) == -1)
            { Bprintf("Couldn't save file %s",palettefilename); return(0); }
        Bwrite(fil,origpalette,768);
        s = B_LITTLE16(numshades); Bwrite(fil,&s,2);
        Bwrite(fil,palookup,numshades<<8);
        Bwrite(fil,transluc,65536);
        Bclose(fil);
        Bprintf("Shade table AND transluscent table updated\n");
    }
    else if (ch == 32)
    {
        short s;
        if ((fil = Bopen(palettefilename,BO_BINARY|BO_TRUNC|BO_CREAT|BO_WRONLY,BS_IREAD|BS_IWRITE)) == -1)
            { Bprintf("Couldn't save file %s",palettefilename); return(0); }
        Bwrite(fil,origpalette,768);
        s = B_LITTLE16(orignumshades); Bwrite(fil,&s,2);
        Bwrite(fil,origpalookup,(int)orignumshades<<8);
        Bwrite(fil,transluc,65536);
        Bclose(fil);
        Bprintf("Transluscent table updated\n");
    }
    else
        Bprintf("Palette file wasn't touched\n");

    return 0;
}


code:
/*
    .SSI File Unpacker
    Copyright (c) 2003 Jonathon Fowler

    This is a small program to extract the files from the .SSI package format
    which Sunstorm Interactive expansion packs for games like Duke Nukem 3D
    are distributed in. It is unsupported but should errors arise, bug reports
    are welcome.

    Update: 12 June 2003

    This updated version includes the ability to extract the SSI revision 2
    format as used in Duke Carribean.

    This program is distributed under the terms of the GNU General Public
    License Version 2 which can be found in the included GNU.txt file.

    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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Jonathon Fowler
    jf@jonof.id.au
    http://www.jonof.id.au/
*/
/*
    NOTE: This program does not fall under BUILDLIC.
*/

#include "compat.h"

int main(int argc, char **argv)
{
    FILE *fp, *ofp;
    int32_t i,j=0;
    int32_t version;
    int32_t numfiles;
    unsigned char numchars;
    char title[33];
    char description[3][71];
    struct file {
        char name[13];
        int32_t length;
    } *filenames;
    char runfile[13] = "<unknown>";
    char buf[1024];
    int32_t mode=0, param;

    puts("unpackssi - .SSI File Unpacker\n"
         "Copyright (c) 2003 Jonathon Fowler\n"
         "This software is distributed under the terms of the GNU General Public License.");

    if (argc<2) {
        puts("\nUsage: unpackssi [-l] <ssifile.ssi> [ssifile2.ssi ...]");
        puts("  Unpacks the contents of an SSI file (like those which Sunstorm Interactive");
        puts("  expansion packs for Duke Nukem 3D are distributed in) to the current");
        puts("  directory. NOTE: Any files which already exist and have the same name as the");
        puts("  files contained in the SSI file will be overwritten when extracting.");
        puts("\nSwitches:");
        puts("  -l  List files (no extraction)\n");
        return -1;
    } else {
        param = 1;
        if (argv[1][0] == '-') {
            param++;
            switch (argv[1][1]) {
                case 'l': mode = 1; break;
                default: printf("Unrecognised switch: %c.\n", argv[1][1]); break;
            }
        }
    }

    while (param < argc) {
        puts("");

        fp = fopen(argv[param],"rb");
        if (!fp) return -2;

        fread(&version, 4, 1, fp);
        if (version != 1 && version != 2) {
            fclose(fp);
            puts("Error: Unrecognized SSI version.");
            return -1;
        }

        printf("File is SSI Version %i\n", version);

        fread(&numfiles, 4, 1, fp);

        fread(&numchars, 1, 1, fp);
        if (numchars > 32) numchars = 32;
        fread(title, 32, 1, fp);
        title[numchars] = 0;

        if (version == 2) {
            fread(&numchars, 1, 1, fp);
            if (numchars > 12) numchars = 12;
            fread(runfile, 12, 1, fp);
            runfile[numchars] = 0;
        }

        for (i=0;i<3;i++) {
            fread(&numchars, 1, 1, fp);
            if (numchars > 70) numchars = 70;
            fread(description[i], 70, 1, fp);
            description[i][numchars] = 0;
        }

        filenames = (struct file *)malloc(sizeof(struct file) * numfiles);
        if (!filenames) {
            fclose(fp);
            puts("Error: Failed allocating memory for file index.");
            return -2;
        }

        for (i=0;i<numfiles;i++) {
            fread(&numchars, 1, 1, fp);
            if (numchars > 12) numchars = 12;
            fread(filenames[i].name, 12, 1, fp);
            filenames[i].name[numchars] = 0;

            fread(&filenames[i].length, 4, 1, fp);

            // seek past some stuff I can't seem to fully decipher at the moment
            fseek(fp, 34+1+69, SEEK_CUR);
        }

        printf("File:           %s\n"
               "Package Title:  %s\n"
               "Description:    %s\n"
               "                %s\n"
               "                %s\n"
               "Run Filename:   %s\n\n"
               , argv[param], title, description[0], description[1], description[2], runfile);

        if (mode == 1) {
            j=0;
            puts("File listing:");
        }

        for (i=0;i<numfiles;i++) {
            if (mode == 0) {
                ofp = fopen(filenames[i].name, "wb");
                if (!ofp) {
                    printf("Error: Failed creating %s. Unpack operation cancelled.\n", filenames[i].name);
                    break;
                }

                printf("Unpacking %s (%i bytes)...", filenames[i].name, filenames[i].length);

                for (j=filenames[i].length; j>1024; j-=1024) {
                    fread(buf, 1024, 1, fp);
                    fwrite(buf, 1024, 1, ofp);
                }
                if (j) {
                    fread(buf, j, 1, fp);
                    fwrite(buf, j, 1, ofp);
                }

                fclose(ofp);
                puts("done");
            } else if (mode == 1) {
                printf(" %-12s   %i bytes\n", filenames[i].name, filenames[i].length);
                j += filenames[i].length;
            }
        }

        if (mode == 1) {
            puts("");
            printf(" %i files, %i bytes\n", numfiles, j);
        }

        fclose(fp);
        free(filenames);

        param++;
    }

    return 0;
}


code:
// VGA Font Grabber
// Copyright (c) 1997 Jonathon Fowler
// This is a DOS program originally written with Borland Turbo C++ for DOS 3.1

#include <dos.h>
#include <stdio.h>

int main(void)
{
    int font, width, height, numchars;
    struct REGPACK r;
    FILE *fp;

    printf("VGA Font Grabber\n"
           "Copyright (c) 1997 Jonathon Fowler\n");

    do {
        printf("\nSelect which font to grab:\n"
               "  1.   8-by-8 ROM\n"
               "  2.   8-by-14 ROM\n"
               "  3.   8-by-16 ROM\n"
               "  4.   9-by-16 ROM\n"
               "  5.   9-by-14 ROM\n"
               "  6.   Quit\n"
               " > ");
        scanf("%d",&font);

        switch (font) {
            case 1:
                printf("Getting 8-by-8 ROM font...");

                if ((fp = fopen("88vga.dat", "wb")) != NULL) {
                    width = 8;
                    height = 8;
                    numchars = 256;

                    r.r_ax = 0x1130;    // locate the font (1st half)
                    r.r_bx = 0x0300;
                    intr(0x10, &r);

                    fwrite(MK_FP(r.r_es, r.r_bp), 1, (8 * 128), fp);

                    r.r_ax = 0x1130;    // locate the font (2nd half)
                    r.r_bx = 0x0400;
                    intr(0x10, &r);

                    fwrite(MK_FP(r.r_es, r.r_bp), 1, (8 * 128), fp);

                    fclose(fp);
                }

                printf("Done\n");
                break;
            case 2:
                printf("Getting 8-by-14 ROM font...");

                if ((fp = fopen("814vga.dat", "wb")) != NULL) {
                    width = 8;
                    height = 14;
                    numchars = 256;

                    r.r_ax = 0x1130;    // locate the font
                    r.r_bx = 0x0200;
                    intr(0x10, &r);

                    fwrite(MK_FP(r.r_es, r.r_bp), 1, (14 * 256), fp);

                    fclose(fp);
                }

                printf("Done\n");
                break;
            case 3:
                printf("Getting 8-by-16 ROM font...");

                if ((fp = fopen("816vga.dat", "wb")) != NULL) {
                    width = 8;
                    height = 16;
                    numchars = 256;

                    r.r_ax = 0x1130;    // locate the font
                    r.r_bx = 0x0600;
                    intr(0x10, &r);

                    fwrite(MK_FP(r.r_es, r.r_bp), 1, (16 * 256), fp);

                    fclose(fp);
                }

                printf("Done\n");
                break;
            case 4:
                printf("Getting 9-by-16 ROM font...");

                if ((fp = fopen("916vga.dat", "wb")) != NULL) {
                    width = 9;
                    height = 16;
                    numchars = 256;

                    r.r_ax = 0x1130;    // locate the font
                    r.r_bx = 0x0700;
                    intr(0x10, &r);

                    fwrite(MK_FP(r.r_es, r.r_bp), 1, (16 * 256) *2, fp);

                    fclose(fp);
                }

                printf("Done\n");
                break;
            case 5:
                printf("Getting 9-by-14 ROM font...");

                if ((fp = fopen("914vga.dat", "wb")) != NULL) {
                    width = 9;
                    height = 16;
                    numchars = 256;

                    r.r_ax = 0x1130;    // locate the font
                    r.r_bx = 0x0500;
                    intr(0x10, &r);

                    fwrite(MK_FP(r.r_es, r.r_bp), 1, (14 * 256)* 2, fp);

                    fclose(fp);
                }

                printf("Done\n");
                break;
            case 6:
                break;
            default:
                printf("Please try again\n");
                break;
        }
    } while (font != 6);

    return 0;
}


code:
// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
// Ken Silverman's official web site: "http://www.advsys.net/ken"
// See the included license file "BUILDLIC.TXT" for license info.
//
// This file has been modified from Ken Silverman's original release
// by Jonathon Fowler (jf@jonof.id.au)

#include "compat.h"
#include "pragmas.h"

#define MAXWADS 4096

static int artversion, localtilestart, localtileend;
static int fil1, fil2;
static char wadata[MAXWADS][8];
static int wadplc[MAXWADS], wadlen[MAXWADS], numwads;
static int xoffses[1024], ylookup[256], picanm[MAXWADS];
static short tilesizx[MAXWADS], tilesizy[MAXWADS];
static char pal[768], palookup[8192];
static char screen[65536], tempbuf[131072];

void loadwadheader(void)
{
    int i, j;

    Bread(fil1,&tempbuf[0],12);
    numwads = ((int)tempbuf[4])+(((int)tempbuf[5])<<8)+(((int)tempbuf[6])<<16)+(((int)tempbuf[7])<<24);
    i = ((int)tempbuf[8])+(((int)tempbuf[9])<<8)+(((int)tempbuf[10])<<16)+(((int)tempbuf[11])<<24);
    Blseek(fil1,i,BSEEK_SET);
    Bread(fil1,&tempbuf[0],numwads*16);
    j = 0;
    for(i=0;i<numwads;i++)
    {
        wadplc[i] = ((int)tempbuf[j])+(((int)tempbuf[j+1])<<8)+(((int)tempbuf[j+2])<<16)+(((int)tempbuf[j+3])<<24);
        j += 4;
        wadlen[i] = ((int)tempbuf[j])+(((int)tempbuf[j+1])<<8)+(((int)tempbuf[j+2])<<16)+(((int)tempbuf[j+3])<<24);
        j += 4;
        wadata[i][0] = tempbuf[j+0]; wadata[i][1] = tempbuf[j+1];
        wadata[i][2] = tempbuf[j+2]; wadata[i][3] = tempbuf[j+3];
        wadata[i][4] = tempbuf[j+4]; wadata[i][5] = tempbuf[j+5];
        wadata[i][6] = tempbuf[j+6]; wadata[i][7] = tempbuf[j+7];
        j += 8;
    }
}

void convpalette(void)
{
    int i, fil3;
    short danumshades;

    i = 0;
    while (Bstrncasecmp(wadata[i],"PLAYPAL",7) != 0) i++;
    Blseek(fil1,wadplc[i],BSEEK_SET);
    Bread(fil1,pal,768);
    for(i=0;i<768;i++) pal[i] >>= 2;

    i = 0;
    while (Bstrncasecmp(wadata[i],"COLORMAP",8) != 0) i++;
    Blseek(fil1,wadplc[i],BSEEK_SET);
    Bread(fil1,palookup,8192);

    if ((fil3 = Bopen("palette.dat",BO_BINARY|BO_TRUNC|BO_CREAT|BO_WRONLY,BS_IREAD|BS_IWRITE)) == -1)
        { Bprintf("Cannot save palette.dat\n"); exit(0); }
    Bwrite(fil3,pal,768);
    danumshades = 32;
    Bwrite(fil3,&danumshades,2);
    Bwrite(fil3,palookup,8192);
    Bclose(fil3);
}

void saveart (short tilenum, short xlen, short ylen)
{
    int i, x, p, pend;

    pend = ylookup[ylen];

    tilesizx[tilenum] = xlen;
    tilesizy[tilenum] = ylen;
    i = 0;
    for(x=0;x<xlen;x++)
        for(p=x;p<pend;p+=320)
            tempbuf[i++] = screen[p];
    if (Bwrite(fil2,&tempbuf[0],i) < 0)
    {
        Bprintf("NOT ENOUGH DISK SPACE!\n");
        exit(0);
    }
}

void savenames(void)
{
    BFILE * fil3;
    int i;

    fil3 = Bfopen("names.h","w");

    if (fil3 != NULL)
    {
        Bfprintf(fil3,"//Be careful when changing this file - it is parsed by Editart and Build.\n");

        for(i=0; i<numwads; i++)
            if (wadata[i][0] != 0)
                Bfprintf(fil3,"#define %s %d\n", wadata[i], i);

        Bfclose(fil3);
    }
}

void showart (char const * part)
{
    char yoff;
    short xsiz, ysiz;
    int i, z, zx, x, p, pend, curplc;

    curplc = -1;
    if ((Bstrncasecmp(part,"L_START",7) == 0) || (Bstrncasecmp(part,"S_START",7) == 0) || (Bstrncasecmp(part,"P_START",7) == 0))
    {
        if (Bstrncasecmp(part,"L_START",7) == 0)
            z = 462;
        else
        {
            z = 0;
            while (Bstrncasecmp(wadata[z],part,7) != 0) z++;
            z++;
        }

        do
        {
            if (Bstrncasecmp(wadata[z],"P1_START",8) == 0) z++;
            if (Bstrncasecmp(wadata[z],"P1_END",6) == 0) z++;
            if (Bstrncasecmp(wadata[z],"P2_START",8) == 0) z++;
            if (Bstrncasecmp(wadata[z],"S_START",7) == 0) break;
            if (Bstrncasecmp(wadata[z],"S_END",5) == 0) break;
            if (Bstrncasecmp(wadata[z],"P_END",5) == 0) break;

            if (curplc != wadplc[z]) Blseek(fil1,wadplc[z],BSEEK_SET);
            read(fil1,&tempbuf[0],wadlen[z]);
            curplc = wadplc[z]+wadlen[z];

            xsiz = (int)tempbuf[0]+(((int)tempbuf[1])<<8);
            ysiz = (int)tempbuf[2]+(((int)tempbuf[3])<<8);
            if ((xsiz <= 0) || (ysiz <= 0) || (xsiz > 320) || (ysiz > 200)) goto skipit;
            i = 8;
            for(zx=0;zx<xsiz;zx++)
            {
                xoffses[zx] = ((int)tempbuf[i])+(((int)tempbuf[i+1])<<8)+(((int)tempbuf[i+2])<<16)+(((int)tempbuf[i+3])<<24);
                i += 4;
            }

            clearbuf(screen,ylookup[ysiz]>>2,0xffffffff);

            for(x=0;x<xsiz;x++)
            {
                i = xoffses[x];
                yoff = tempbuf[i++];
                while (yoff != 255)
                {
                    p = ylookup[yoff]+x;
                    pend = p+ylookup[tempbuf[i]];
                    i += 2;
                    for(;p<pend;p+=320)
                        screen[p] = tempbuf[i++];
                    i++;
                    yoff = tempbuf[i++];
                }
            }

            saveart(z,xsiz,ysiz);
skipit:
            z++;
        } while (z < numwads);
    }
    else
    {
        z = 0;
        while (Bstrncasecmp(wadata[z],part,7) != 0) z++;
        z++;

        while (1)
        {
            if (Bstrncasecmp(wadata[z],"F1_START",8) == 0) z++;
            if (Bstrncasecmp(wadata[z],"F1_END",6) == 0) z++;
            if (Bstrncasecmp(wadata[z],"F_END",5) == 0) break;

            if (wadlen[z] == 4096)
            {
                if (curplc != wadplc[z]) Blseek(fil1,wadplc[z],BSEEK_SET);
                Bread(fil1,&tempbuf[0],4096);
                curplc = wadplc[z]+4096;
                i = 0;
                for(x=0;x<64;x++)
                    for(p=x;p<320*64;p+=320)
                        screen[p] = tempbuf[i++];

                saveart(z,64,64);
            }

            z++;
        }
    }
}

int main(int argc, char **argv)
{
    int i, j, endoffile;
    char wadfile[80];

    Bprintf("Wad2Art!                                       Copyright 1995 by Ken Silverman\n");

    if (argc != 2)
    {
        Bprintf("Command line parameters: Wad2Art <Doom IWAD file>\n");
        Bprintf("   Creates TILES000.ART, PALETTE.DAT, and NAMES.H in current directory.\n");
        Bprintf("   Ex: wad2art c:\\doom\\doom.wad\n");
        exit(0);
    }

    strcpy(wadfile,argv[1]);
    if (strchr(wadfile,'.') == 0) strcat(wadfile,".wad");
    if ((fil1 = Bopen(wadfile,BO_BINARY|BO_RDONLY,BS_IREAD)) == -1)
        { Bprintf("Wad not found\n"); exit(0); }
    if ((fil2 = Bopen("tiles000.art",BO_BINARY|BO_TRUNC|BO_CREAT|BO_WRONLY,BS_IREAD|BS_IWRITE)) == -1)
        { Bprintf("Can't open art file\n"); exit(0); }

    j = 0;
    for(i=0;i<256;i++) { ylookup[i] = j; j += 320; }

    Bprintf("Loading wad header...\n");
    loadwadheader();
    Blseek(fil2,16+(numwads<<3),SEEK_SET);
    for(i=0;i<numwads;i++)
        { tilesizx[i] = 0; tilesizy[i] = 0; picanm[i] = 0L; }

    Bprintf("Saving names.h\n");
    savenames();
    Bprintf("Converting palette\n");
    convpalette();

    Bprintf("Saving tiles000.art\n");
    showart("L_START");
    showart("S_START");
    showart("P_START");
    showart("F_START");

    Bprintf("Saving tiles000.art header\n");
    artversion = 1; localtilestart = 0; localtileend = numwads-1;

    endoffile = Btell(fil2);
    Blseek(fil2,0,BSEEK_SET);
    Bwrite(fil2,&artversion,4);
    Bwrite(fil2,&numwads,4);
    Bwrite(fil2,&localtilestart,4);
    Bwrite(fil2,&localtileend,4);
    Bwrite(fil2,&tilesizx[0],numwads<<1);
    Bwrite(fil2,&tilesizy[0],numwads<<1);
    Bwrite(fil2,&picanm[0],numwads<<2);
    Blseek(fil2,endoffile,BSEEK_SET);

    Bclose(fil2);
    Bclose(fil1);

    Bprintf("Congratulations!  Your disk actually had enough space this time!\n");

    return 0;
}