# Filename: A16a_BodyTypes.py
# Written by: James D. Miller

# This file is based on the test_BodyTypes.py file in the examples directory of the 
# pybox2d distribution. This depends on the pygame framework in that distribution so 
# all the framework files must be in the same directory as this file.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# C++ version Copyright (c) 2006-2007 Erin Catto http://www.box2d.org
# Python version by Ken Lauer / sirkne at gmail dot com
# 
# This software is provided 'as-is', without any express or implied
# warranty.  In no event will the authors be held liable for any damages
# arising from the use of this software.
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.


from framework import *
from math import cos, sin, pi

class BodyTypes (Framework):
    name="Body Types, Guns, and Slingshot."
    description="Change body type keys: (d)dynamic, (s)static, (k)kinematic.\n(p)pyramid, (l)load, (c)circle.\nShoot gun: g or b. b has variable speed based on mouse string.\nSlingshot: shift and mouse drag."
    speed = 3 # platform speed
    
    def __init__(self):
        super(BodyTypes, self).__init__()

        self.time = 0
        
        # A list for keeping track of the age of the bullets.
        self.bullet_list = []
        
        # Collision masking (also see test_CollisionFiltering.py)
        # This positive (+5) non_bullets group index insures collisions in this group. 
        self.non_bullets = 5
        # Categories
        self.gunCategory = 0x0002
        self.bulletCategory = 0x0004
        # Masks
        self.gunMask = 0xFFFF
        # An exclusive OR to keep bullets from colliding with the gun parts.
        self.bulletMask = 0xFFFF ^ self.gunCategory         

        # The ground
        ground = self.world.CreateBody(
                    shapes=b2EdgeShape(vertices=[(-30,0),(30,0)]) 
                )

                
        # The attachment
        attachment_fixture = b2FixtureDef(
                             shape=b2PolygonShape(box=(0.5,2)), 
                             density=2.0,
                             filter = b2Filter(
                                groupIndex = self.non_bullets,
                                categoryBits = self.gunCategory,
                                maskBits = self.gunMask
                                )
                             )
        
        self.attachment=self.world.CreateDynamicBody(
                    position=(0,3), 
                    fixtures=attachment_fixture,
                )

        # The platform
        fixture=b2FixtureDef(
                shape=b2PolygonShape(box=(4,0.5)), 
                density=2,
                friction=0.6,
                filter = b2Filter(
                    groupIndex = self.non_bullets,
                    categoryBits = self.gunCategory,
                    maskBits = self.gunMask
                    )
                )
        
        self.platform=self.world.CreateDynamicBody(
                    position=(0,5), 
                    fixtures=fixture,
                )
        
        # The joints joining the attachment/platform and ground/platform
        self.RJ = self.world.CreateRevoluteJoint(
                bodyA=self.attachment,
                bodyB=self.platform,
                anchor=(0,5),
                maxMotorTorque=50,
                enableMotor=True
            )

        self.PJ = self.world.CreatePrismaticJoint(
                bodyA=ground,
                bodyB=self.platform,
                anchor=(0,5),
                axis=(1,0),
                maxMotorForce = 1000,
                enableMotor = True,
                lowerTranslation = -10,
                upperTranslation = 10,
                enableLimit = True 
            )

    def newpayload(self):
        # And the payload that initially sits upon the platform in the original demo.
        # Reusing the fixture we previously defined above.
        payload_fixture=b2FixtureDef(
                shape=b2PolygonShape(box=(0.75, 0.75)), 
                density=2,
                friction=0.6,
                filter = b2Filter(
                    groupIndex = self.non_bullets,
                    categoryBits = self.bulletCategory,
                    maskBits = self.bulletMask
                    )
                )
        
        self.payload=self.world.CreateDynamicBody(
                    position=(0,8), 
                    fixtures=payload_fixture,
                )

    def newCircle(self):
        # Some circles for testing contact normals.
        circle_fixture=b2FixtureDef(
                shape=b2CircleShape(radius=2.0), 
                density=2,
                friction=0.6,
                filter = b2Filter(
                    groupIndex = self.non_bullets,
                    categoryBits = self.bulletCategory,
                    maskBits = self.bulletMask
                    )
                )
        
        self.circle=self.world.CreateDynamicBody(
                    position=(0,8), 
                    fixtures=circle_fixture,
                ) 
                
    def newpyramid(self):
        # Pyramid on the ground        
        box_half_size = (0.5, 0.5)
        box_density = 5.0
        box_rows = 20

        x=b2Vec2(-27, 0.75)
        deltaX=(0.5625, 1.25)
        deltaY=(1.125, 0)

        for i in range(box_rows):
            y = x.copy()
            for j in range(i, box_rows):
                self.world.CreateDynamicBody(
                    position=y,
                    fixtures=b2FixtureDef(
                            shape=b2PolygonShape(box=box_half_size),
                            density=box_density)
                    )
                y += deltaY
            x += deltaX
                
                
    def newbullet(self, location_init, angle_radians_init, speed_control):
        # Speed
        v = 30
        
        # Note: angle_degrees = angle_radians_init * 180/pi

        # Scale the bullet speed by the length of the mouse vector.
        if self.mouseJoint and (speed_control == "on"):
            joint_vector = self.mouseJoint.anchorB - self.mouseJoint.target
            
            joint_vector_length = (joint_vector.x**2 + joint_vector.y**2)**0.5
            #print joint_vector_length
            v = v * joint_vector_length/2.0  # Cut this by a factor of 2...
        
        v_x = v * cos(pi/2- angle_radians_init)
        v_y = v * sin(pi/2- angle_radians_init)        
        
        bullet_fixture = b2FixtureDef(shape=b2PolygonShape(box=(0.25, 0.25)), density=100.0)  #restitution=1.0
        bullet_fixture.filter.groupIndex = 0
        bullet_fixture.filter.categoryBits = self.bulletCategory
        bullet_fixture.filter.maskBits = self.bulletMask
        
        self.bullet=self.world.CreateDynamicBody(
                    position=location_init,
                    bullet=True,
                    fixtures=bullet_fixture,
                    linearVelocity=(v_x, v_y)
                )    
        
        # Put the bullet into a list for clean-up (deletion) later. Tag it with
        # a birth-time stamp so can calculate age later.
        self.bullet_list.append([self.bullet, self.time])
        # print "self.time =", self.time
        
    def Keyboard(self, key):
        if key==Keys.K_d:
            self.platform.type=b2_dynamicBody
        elif key==Keys.K_s:
            self.platform.type=b2_staticBody
        elif key==Keys.K_k:
            self.platform.type=b2_kinematicBody
            self.platform.linearVelocity=(-self.speed, 0)
            self.platform.angularVelocity=0
        elif (key==Keys.K_g) or (key==Keys.K_b):
            if (key==Keys.K_b):
                speed_control = "on"
            else:
                speed_control = "off"
            bullet_angle_init = self.RJ.angle
            #print "angle =", bullet_angle_init
            # Get the world position of the tip (0,2) of the gun barrel.
            bullet_xy_init = self.attachment.transform * (0,2)
            #print "world point = ", bullet_xy_init
            self.newbullet(bullet_xy_init, bullet_angle_init, speed_control)
        elif key==Keys.K_p:
            self.newpyramid()
        elif key==Keys.K_l:
            self.newpayload()
        elif key==Keys.K_c:
            self.newCircle()
            
    def Step(self, settings):
        super(BodyTypes, self).Step(settings)
        
        # Move the platform if it's kinematic.
        if self.platform.type==b2_kinematicBody:
            p = self.platform.transform.position
            if ((p.x < -10) or (p.x > 10)):
                self.platform.linearVelocity *= -1

        # Every step (or second if use commented value), check the age of each bullet in the list.
        self.time += 1.0/60.0
        if (self.stepCount % 1)==0:  # change the 1 to 60 to delay check to every second.
            #print "--------------------", self.time, "--------------------"
            # Copy the list so you are not deleting in the list used by the for loop.
            bullet_list_copy = self.bullet_list[:]
            for bullet in bullet_list_copy:
                bullet_age = self.time - bullet[1]
                if (bullet_age) > 10.0:
                    #print "age of bullet =", bullet_age
                    # Delete the bullet
                    self.world.DestroyBody(bullet[0])
                    # Delete the sub-list element in the main list.
                    self.bullet_list.remove(bullet)
            # Delete the copy of the list.
            del bullet_list_copy

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