# ##### BEGIN GPL LICENSE BLOCK #####
#
#  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.
#
# ##### END GPL LICENSE BLOCK #####

bl_info = {
	"name": "Export MX Simulator Shape (.shp)",
	"description": "Export MX Simulator Shape (.shp)",
	"author": "Josh Vanderhoof",
	"version": (0, 1),
	"blender": (2, 6, 3),
	"api": 36079,
	"location": "File > Export > MX Simulator Shape (.shp)",
	"warning": "",
	"category": "Import-Export"
}

import bpy
from bpy.props import *
import mathutils, math, struct
from os import remove
from bpy_extras.io_utils import ExportHelper

def is_connected_to(t, isle, vidx):
	for i in range(0, 3):
		if isle[t[i]] == vidx:
			return True
	return False

def find_islands(v, t):
	isle = [-1] * len(v)

	for n in range(0, len(t)):
		keep_going = False

		for i in range(0, len(t)):
			if isle[t[i][0]] == -1:
				keep_going = True
				for j in range(0, 3):
					isle[t[i][j]] = n
				break

		if not keep_going:
			return isle

		keep_going = True

		while keep_going:
			keep_going = False
			for i in range(0, len(t)):
				if is_connected_to(t[i], isle, n):
					for j in range(0, 3):
						if isle[t[i][j]] == -1:
							keep_going = True
							isle[t[i][j]] = n

def distance(a, b):
	x = a[0] - b[0]
	y = a[1] - b[1]
	z = a[2] - b[2]
	return math.sqrt(x * x + y * y + z * z)

def island_position_and_radius(isle, verts, tris, islands):
	c = [ 0.0, 0.0, 0.0 ]
	n = 0
	r = -1.0

	for i in range(0, len(verts)):
		if islands[i] == isle:
			n = n + 1
			for j in range(0, 3):
				c[j] += verts[i][j]

	if n == 0:
		return False

	for i in range(0, 3):
		c[i] /= n

	for i in range(0, len(verts)):
		if islands[i] == isle:
			d = distance(verts[i], c)
			if r == -1.0:
				r = d
			elif abs(r - d) > max(r, d) / 1024.0:
				raise RuntimeError("Non-spherical sub-mesh")

	return (c[0], c[1], c[2], r)

def object_get_spheres(obj, props):
	mesh = obj.to_mesh(bpy.context.scene, props.apply_modifiers, "RENDER")
	verts = []
	tris = []
	spheres = []

	if props.rot_x90:
		mesh.transform(mathutils.Matrix.Rotation(-math.pi * 0.5, 4, 'X'))
		mesh.calc_normals()

	for v in mesh.vertices:
		verts.append((v.co[0], v.co[1], v.co[2]))

	for face in mesh.tessfaces:
		tris.append((face.vertices[0], face.vertices[1], face.vertices[2]))
		if len(face.vertices) == 4:
			tris.append((face.vertices[2], face.vertices[3], face.vertices[0]))

	islands = find_islands(verts, tris)

	for i in range(0, len(verts)):
		s = island_position_and_radius(i, verts, tris, islands)
		if not s:
			break
		spheres.append(s)

	bpy.data.meshes.remove(mesh)

	return spheres

def do_export(context, props, filepath):
	spheres = []

	for obj in bpy.context.selected_objects:
		spheres.extend(object_get_spheres(obj, props))

	if len(spheres) > 25:
		raise RuntimeError("Too many spheres")

	for s in spheres:
		if s[3] < 0.49:
			raise RuntimeError("Undersized sphere detected")

	file = open(filepath, "w", encoding="ascii", newline="")

	file.write("%d\n" % (len(spheres),))

	for s in spheres[:0x19]:
		file.write("%f %f %f %f 1.0 25000 2500\n" % s)

	file.flush()
	file.close()
	return True

class Export_shp(bpy.types.Operator, ExportHelper):
	'''Exports the active Object as a .shp file.'''
	bl_idname = "export_shape.shp"
	bl_label = "Export MX Simulator (.shp)"

	filename_ext = ".shp"
    
	rot_x90 = BoolProperty(name="Convert to Y-up",
		description="Rotate 90 degrees around X to convert to y-up",
		default=True)
	apply_modifiers = BoolProperty(name="Apply Modifiers",
		description="Applies the Modifiers",
		default=True)
    
	# "poll" reports if this operator makes sense in the current conditions.
	# (I think.)
	@classmethod
	def poll(cls, context):
		return context.active_object.type == 'MESH'

	# "execute" is called non interactively.
	def execute(self, context):
		props = self.properties
		filepath = self.filepath
		filepath = bpy.path.ensure_ext(filepath, self.filename_ext)

		exported = do_export(context, props, filepath)

		return {'FINISHED'}

	# "invoke" is called because of user interaction.
	def invoke(self, context, event):
		wm = context.window_manager

		if True:
			# File selector
			wm.fileselect_add(self) # will run self.execute()
			return {'RUNNING_MODAL'}
		elif True:
			# search the enum
			wm.invoke_search_popup(self)
			return {'RUNNING_MODAL'}
		elif False:
			# Redo popup
			return wm.invoke_props_popup(self, event)
		elif False:
			return self.execute(context)


def menu_func(self, context):
	self.layout.operator(Export_shp.bl_idname, text="MX Simulator Shape (.shp)")

def register():
	bpy.utils.register_module(__name__)
	bpy.types.INFO_MT_file_export.append(menu_func)

def unregister():
	bpy.utils.unregister_module(__name__)
	bpy.types.INFO_MT_file_export.remove(menu_func)

if __name__ == "__main__":
	register()
