
# Invivo AI challenge simulation environment
# Copyright 2009 Simon Funk
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

import ming
import Model as m
from math import pi, cos, sin

class FlashMovie(object):

	"""This object creates a flash movie of a world model."""

	def __init__(self, model, size=(640,480), fps=30.):
		"""Fps is an approximate target frame rate.
		The actual frame rate will be at least fps (unless the model frame rate is less),
		specifically the smallest integer fraction of the model's frame rate which isn't
		less than fps.
		"""
		margin = (0.5, 0.5)

		self.model       = model
		self.skip        = max(int(model.fps/fps), 1)

		range            = m.psub(model.bounds[1], model.bounds[0])	# Size of the model bounds
		range            = m.padd(range, m.pscale(margin, 2))
		self.scale       = min(size[0]/range[0], size[1]/range[1])	# Scale maps from model coords to view coords; use min to avoid clipping.
		size             = m.pscale(range, self.scale)				# Recompute size at proper aspect ratio.
		self.xo, self.yo = m.psub(model.bounds[0], margin)			# View origin of the model is the minimum bound less margin.
		self.yflip       = size[1] - 1
		self.onepixel    = 1./self.scale							# How big is a pixel in world coordinates?

		self.movie = ming.SWFMovie()
		self.movie.setDimension(size[0], size[1])
		self.movie.setRate(float(model.fps)/self.skip)
		self.movie.setBackground(model.bgcolor[0], model.bgcolor[1], model.bgcolor[2])

		self.bodies = [BodyView(self, body, self.movie) for body in model.bodies]

		self.skipcount = 0

		model.watch(self.update)

	def save(self, filename):

		self.movie.save(filename)

	def update(self, model):

		if self.skipcount > 0:
			self.skipcount -= 1
			return
		self.skipcount = self.skip-1

		for body in self.bodies:
			body.move(self)

		self.movie.nextFrame()

	def modelToViewXfm(self, (x, y)):
		"Transforms a coordinate from model space to view (integer) space."
		return (int(self.scale * (x-self.xo)), self.yflip - int(self.scale * (y-self.yo)))

class ShapeState(object):
	def __init__(self, color):
		self.color = color
		self.pos   = None
		self.angle = None

class BodyView(object):

	def __init__(self, fm, body, movie):

		self.body   = body	# Model body
		self.shapes = []	# List of (model shape, swf shape, ref shapestate)

		for shape in body.shapes:

			s = ming.SWFShape()

			r, g, b = shape.color
			s.setRightFill(s.addFill(r, g, b))	# Right fill because we're inverting y which turns counterclockwise to clockwise.

			if isinstance(shape, m.Circle):
				x, y = m.pscale(shape.center, fm.scale)
				rad  = shape.radius * fm.scale

				#s.drawArc(x, y, rad, 0, 360)	# Broken

				s.movePenTo(x+rad, -y)
				for i in xrange(30):
					a = 2.*pi*(i+1.)/30.
					s.drawLineTo(x + rad*cos(a), -y-rad*sin(a))
				if shape.marker:
					s.setRightFill(None)
					s.setLine(fm.onepixel, 0, 0, 0)
					s.movePenTo(0., 0.)
					s.drawLineTo(rad, 0.)

			elif isinstance(shape, m.Poly):

				#print "Poly: (%d,%d,%d) %s"%(r, g, b, shape.points,)

				x, y = shape.points[-1]
				x *=  fm.scale
				y *= -fm.scale
				s.movePenTo(x, y)
				for x, y in shape.points:
					x *=  fm.scale
					y *= -fm.scale
					s.drawLineTo(x, y)
			else:
				raise Exception("Unknown Shape class: %s"%(shape.__class__.__name__,))

			s = movie.add(s)
			# Scaling is now done in the shape definition.
			#s.scaleTo(fm.scale, fm.scale)
			ref = ShapeState(shape.color)
			self.shapes.append((shape, s, ref))

	def move(self, fm):

		body = self.body

		for shape, s, ref in self.shapes:

			pos = fm.modelToViewXfm(body.position)
			if pos != ref.pos:
				x, y = pos
				s.moveTo(x, y)
				ref.pos = pos

			if body.angle != ref.angle:
				s.rotateTo(body.angle * 180./pi)
				ref.angle = body.angle

			if shape.color != ref.color:
				r, g, b = m.csub(shape.color, ref.color)
				s.addColor(r, g, b)

