view atc_plane.py @ 13:4a95c5552d3b

Stall speed and normal rate of climb adjustment committer: Jeff Sipek <jeffpc@jeff.(none)> 1120872321 -0400
author Jeff Sipek <jeffpc@jeff.(none)>
date Sat, 09 Jul 2005 01:25:21 -0400
parents 243941ec0a36
children d9c394a5ef77
line wrap: on
line source

#/*
# * ATC - Air Traffic Controller simulation game
# *
# * Copyright (C) 2004, 2005 Josef "Jeff" Sipek <jeffpc@optonline.net>
# *
# * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# *
# * @(#) %M% %I% %E% %U%
# */

import os
import sys
import time
import pygame
from math import *

import atc_colors
import atc_utils
from atc_plane_specs import *

mperpix		= 30000.0/800.0		# 800 px == 30 km
mperdeg		= 1852.0*60.0		# == 1852 m/min * 60 min/deg

def m2pix(m):
	""" Meters to pixels conversion fn """
	return m/mperpix

def pix2m(pix):
	""" Pixels to meters conversion fn """
	return pix*mperpix
	
def pix2geo(pix):
	return pix2m(pix)/mperdeg

plane_OK	= 0
plane_RANGE	= 1
plane_CRASHED	= 2
plane_STALL	= 3
plane_DEAD	= 4
plane_PROXY	= 5

class Plane(pygame.sprite.Sprite):
	""" Class to manage one plane's motion """
	def __init__(self,callsign,flightno,pos=(0.0, 0.0, 0.0),vel=(0.0, 0.0, 0.0)):
		""" Set up everything """
		pygame.sprite.Sprite.__init__(self)
		
		# screen
		screen = pygame.display.get_surface()
		self.area = screen.get_rect()
		
		# plane image
		(self.image, self.rect) = atc_utils.load_png('data/image/plane.png')
		
		# call sign
		self.callsign	= callsign
		
		# flight number
		self.flightno	= flightno
		
		# position (X, Y, Z)
		self.pos	= { \
			"X":pos[0], \
			"Y":pos[1], \
			"Z":pos[2]}
		
		# target position
		self.targetPos	= { \
			"X":None, \
			"Y":None, \
			"Z":None}
		
		# velocity
		self.vel	= { \
			"i":vel[0], \
			"j":vel[1], \
			"k":vel[2], \
			
			"heading":0, \
			"speed":0, \
			"climb":0}
		self.complete_vel(ijk=1)
		
		self.timer = 0 # this is timeout the next time we try to recalculate position
		
		# plane specs
		self.specs	= plane_SPECS[0] # default to a simple prop plane, FIXME: add param to override
		
		# status flag
		self.status	= plane_OK
	
	def complete_vel(self, ijk=None, hac=None):
		""" Recalculate all the velocity variables """
		if ijk == None and hac == None:
			print "Nothing calculated"
			return
		
		if ijk == None:
			self.vel["i"] = sin(self.vel["heading"]) * sqrt(self.vel["speed"]**2 - self.vel["climb"]**2)
			self.vel["j"] = cos(self.vel["heading"]) * sqrt(self.vel["speed"]**2 - self.vel["climb"]**2)
			self.vel["k"] = self.vel["climb"]
		else:
			self.vel["heading"] = self.calc_head()
			self.vel["speed"] = sqrt(self.vel["i"]**2 + self.vel["j"]**2 + self.vel["k"]**2)
			self.vel["climb"] = self.vel["k"]
	
	def update(self):
		""" Move the plane, and check for collisions """
		if (self.timer > time.time()):
			return

		if (self.status != plane_OK):
			return
		
		self.pos["X"] += self.vel["i"]/10.0
		self.pos["Y"] += self.vel["j"]/10.0
		self.pos["Z"] += self.vel["k"]/10.0
		
		# FIXME: need physics
		if (self.targetPos["Z"] != None) and (self.pos["Z"] >= (self.targetPos["Z"] - 25)) and (self.pos["Z"] <= (self.targetPos["Z"] + 25)):
			self.vel["k"] = 0
			self.targetPos["Z"] = None
			
			self.complete_vel(ijk=1)
		
		#if (self.pos["X"] < 0) or (self.pos["Y"] < 0): # FIXME: max values
		#	self.status = plane_RANGE
		
		if (self.pos["Z"] < 0):
			self.vel["i"] = self.vel["j"] = self.vel["k"] = 0
			self.status = plane_CRASHED
		
		if (self.vel["speed"]<m2pix(self.specs["stall_speed"])) and (not self.status==plane_CRASHED):
			self.status = plane_STALL
		
		self.rect.move(m2pix(self.vel["i"])/10.0,m2pix(self.vel["j"])/10.0)
		
		self.timer = time.time() + m2pix(self.vel["speed"])**-1/10.0
		
	def calc_head(self):
		""" Calculate heading of the airplane (in radians)"""
		if (self.vel["i"] == self.vel["j"] == 0): # no movement
			return 0
		
		try:
			head = fabs(atan(self.vel["i"]/-self.vel["j"]))
		except ZeroDivisionError:
			if (self.vel["i"]>0):
				return pi/2	# east
			return 3*pi/2		# west
		
		return head
	
	def calc_geo(self, xy, prefix):
		""" Return a geographic coodrinate-formated xy """
		df = m2pix(pix2geo(xy))
		minus = prefix[0]
		if df<0:
			df *= -1
			minus = prefix[1]
		d  = floor(df)
		mf = (df-d) * 60.0
		m  = floor(mf)
		s  = (mf-m) * 60.0
		
		return "%s %dd %02dm %02ds" % (minus, int(d), int(m), int(s))
	
	def process(self,cmd):
		""" Process a user command """
		
		# FIXME: convert multiple spaces to single
		parts = cmd.split(' ')
		
		if (parts[0] == "ALT"):
			print "Changing altitude to " + parts[1] + "m"
			self.targetPos["Z"] = int(parts[1])
			self.vel["k"] = self.specs["climb_normal"]
			if (self.pos["Z"] > self.targetPos["Z"]):
				self.vel["k"] *= -1
			self.complete_vel(ijk=1)
			
		if (parts[0] == "HEAD"):
			self.vel["heading"] = int(parts[1])
			while(True):
				if self.vel["heading"]>=360:
					self.vel["heading"] -= 360
				elif self.vel["heading"]<0:
					self.vel["heading"] += 360
				else:
					break
			
			print "Changing heading to " + str(self.vel["heading"])
			self.vel["heading"] *= pi/180.0
			self.complete_vel(hac=1)
	
	def display(self,screen):
		""" Put everything onto the screen """
		screen.blit(self.image, (int(1024/2+m2pix(self.pos["X"])), int(768/2-m2pix(self.pos["Y"]))))
		
		# Plane info
		font = pygame.font.Font(None, 16)
		
		color = atc_colors.planeinfo_ok
		if (self.status == plane_CRASHED) or (self.status == plane_DEAD):
			color = atc_colors.planeinfo_crashed
		
		x = int(1024/2+m2pix(self.pos["X"])) + 10
		y = int(768/2-m2pix(self.pos["Y"])) - 5
		
		# FIXME: display geographic coordinates with NSWE appended/prepended
		#strings = (	self.flightno,
		#		self.callsign + " " + str(self.status),
		#		"Alt:  " + str(int(self.pos["Z"])),
		#		"Lat:  " + self.calc_geo(self.pos["Y"],"NS"),
		#		"Long: " + self.calc_geo(self.pos["X"],"EW"),
		#		"Head: " + str(int(atc_utils.todeg(self.vel["heading"]))),
		#		"AS:   " + str(int(self.vel["speed"]*3.6)))
		strings = (	self.flightno,
				"%03d %03d" % (int(atc_utils.todeg(self.vel["heading"])), int(self.vel["speed"]*3.6)),
				self.specs["name"])
				
		for stri in strings:
			alt = font.render(stri, 1, color)
			altpos = alt.get_rect()
			altpos.topleft = (x,y)
			screen.blit(alt, altpos)
			(x,y) = (x,y+10)