package advanced.physics.scenes;
import java.awt.event.KeyEvent;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.shapes.CircleDef;
import org.jbox2d.collision.shapes.PolygonDef;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.ContactListener;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.ContactPoint;
import org.jbox2d.dynamics.contacts.ContactResult;
import org.jbox2d.dynamics.joints.Joint;
import org.jbox2d.dynamics.joints.JointType;
import org.jbox2d.dynamics.joints.MouseJoint;
import org.mt4j.MTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.components.visibleComponents.font.FontManager;
import org.mt4j.components.visibleComponents.font.IFont;
import org.mt4j.components.visibleComponents.shapes.MTEllipse;
import org.mt4j.components.visibleComponents.shapes.MTLine;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.widgets.MTTextArea;
import org.mt4j.input.inputProcessors.IGestureEventListener;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.globalProcessors.CursorTracer;
import org.mt4j.sceneManagement.AbstractScene;
import org.mt4j.util.MTColor;
import org.mt4j.util.camera.MTCamera;
import org.mt4j.util.math.ToolsMath;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
import processing.core.PImage;
import advanced.physics.physicsShapes.IPhysicsComponent;
import advanced.physics.physicsShapes.PhysicsCircle;
import advanced.physics.physicsShapes.PhysicsRectangle;
import advanced.physics.util.PhysicsHelper;
import advanced.physics.util.UpdatePhysicsAction;
public class AirHockeyScene extends AbstractScene {
private float timeStep = 1.0f / 60.0f;
private int constraintIterations = 10;
/** THE CANVAS SCALE **/
private float scale = 20;
private MTApplication app;
private World world;
private MTComponent physicsContainer;
private MTTextArea t1;
private MTTextArea t2;
private int scorePlayer1;
private int scorePlayer2;
private HockeyBall ball;
/*
private Minim minim;
private AudioSnippet wallHit;
private AudioSample paddleBallClash;
private AudioSnippet goalHit;
private AudioSnippet paddleHit;
*/
private Paddle redCircle;
private Paddle blueCircle;
private boolean enableSound = true;
//TODO sometimes goal not recognized when shot at fast
//TODO after goal reset puck and make 3 second countdown -> dont use physics until countdown up!
//TODO get graphics, sounds, effects
// private String imagesPath = System.getProperty("user.dir") + File.separator + "examples" + File.separator +"advanced"+ File.separator + "physics" + File.separator + "data" + File.separator + "images" + File.separator;
private String imagesPath = "advanced" + MTApplication.separator + "physics" + MTApplication.separator + "data" + MTApplication.separator + "images" + MTApplication.separator;
public AirHockeyScene(MTApplication mtApplication, String name) {
super(mtApplication, name);
this.app = mtApplication;
// this.setClearColor(new MTColor(120,150,150));
// this.setClearColor(new MTColor(190, 190, 170, 255));
this.setClearColor(new MTColor(0, 0, 0, 255));
// this.setClearColor(new MTColor(40, 40, 40, 255));
this.registerGlobalInputProcessor(new CursorTracer(app, this));
this.scorePlayer1 = 0;
this.scorePlayer2 = 0;
float worldOffset = 10; //Make Physics world slightly bigger than screen borders
//Physics world dimensions
AABB worldAABB = new AABB(new Vec2(-worldOffset, -worldOffset), new Vec2((app.width)/scale + worldOffset, (app.height)/scale + worldOffset));
Vec2 gravity = new Vec2(0, 0);
boolean sleep = true;
//Create the pyhsics world
this.world = new World(worldAABB, gravity, sleep);
//Update the positions of the components according the the physics simulation each frame
this.registerPreDrawAction(new UpdatePhysicsAction(world, timeStep, constraintIterations, scale));
physicsContainer = new MTComponent(app);
//Scale the physics container. Physics calculations work best when the dimensions are small (about 0.1 - 10 units)
//So we make the display of the container bigger and add in turn make our physics object smaller
physicsContainer.scale(scale, scale, 1, Vector3D.ZERO_VECTOR);
this.getCanvas().addChild(physicsContainer);
//Create borders around the screen
this.createScreenBorders(physicsContainer);
//Create gamefield marks
MTLine line = new MTLine(mtApplication, mtApplication.width/2f/scale, 0, mtApplication.width/2f/scale, mtApplication.height/scale);
line.setPickable(false);
// line.setStrokeColor(new MTColor(0,0,0));
line.setStrokeColor(new MTColor(150,150,150));
line.setStrokeWeight(0.5f);
physicsContainer.addChild(line);
MTEllipse centerCircle = new MTEllipse(mtApplication, new Vector3D(mtApplication.width/2f/scale, mtApplication.height/2f/scale), 80/scale, 80/scale);
centerCircle.setPickable(false);
centerCircle.setNoFill(true);
// centerCircle.setStrokeColor(new MTColor(0,0,0));
centerCircle.setStrokeColor(new MTColor(150,150,150));
centerCircle.setStrokeWeight(0.5f);
physicsContainer.addChild(centerCircle);
MTEllipse centerCircleInner = new MTEllipse(mtApplication, new Vector3D(mtApplication.width/2f/scale, mtApplication.height/2f/scale), 10/scale, 10/scale);
centerCircleInner.setPickable(false);
centerCircleInner.setFillColor(new MTColor(160,160,160));
// centerCircleInner.setStrokeColor(new MTColor(150,150,150));
// centerCircleInner.setStrokeColor(new MTColor(0,0,0));
centerCircleInner.setStrokeColor(new MTColor(150,150,150));
centerCircleInner.setStrokeWeight(0.5f);
physicsContainer.addChild(centerCircleInner);
//Create the paddles
PImage paddleTex = mtApplication.loadImage(imagesPath + "paddle.png");
redCircle = new Paddle(app, new Vector3D(mtApplication.width - 60, mtApplication.height/2f), 50, world, 1.0f, 0.3f, 0.4f, scale);
redCircle.setTexture(paddleTex);
redCircle.setFillColor(new MTColor(255,50,50));
redCircle.setNoStroke(true);
redCircle.setName("red");
redCircle.setPickable(false);
physicsContainer.addChild(redCircle);
blueCircle = new Paddle(app, new Vector3D(80, mtApplication.height/2f), 50, world, 1.0f, 0.3f, 0.4f, scale);
blueCircle.setTexture(paddleTex);
blueCircle.setFillColor(new MTColor(50,50,255));
blueCircle.setNoStroke(true);
blueCircle.setName("blue");
blueCircle.setPickable(false);
physicsContainer.addChild(blueCircle);
//Create the ball
ball = new HockeyBall(app, new Vector3D(mtApplication.width/2f, mtApplication.height/2f), 38, world, 0.5f, 0.005f, 0.70f, scale);
// MTColor ballCol = new MTColor(0,255,0);
// ball.setFillColor(ballCol);
PImage ballTex = mtApplication.loadImage(imagesPath + "puk.png");
ball.setTexture(ballTex);
// ball.setFillColor(new MTColor(160,160,160,255));
ball.setFillColor(new MTColor(255,255,255,255));
ball.setNoStroke(true);
ball.setName("ball");
physicsContainer.addChild(ball);
ball.getBody().applyImpulse(new Vec2(ToolsMath.getRandom(-8f, 8),ToolsMath.getRandom(-8, 8)), ball.getBody().getWorldCenter());
//Create the GOALS
HockeyGoal goal1 = new HockeyGoal(new Vector3D(0, mtApplication.height/2f), 50, mtApplication.height/4f, mtApplication, world, 0.0f, 0.1f, 0.0f, scale);
goal1.setName("goal1");
goal1.setFillColor(new MTColor(0,0,255));
goal1.setStrokeColor(new MTColor(0,0,255));
physicsContainer.addChild(goal1);
HockeyGoal goal2 = new HockeyGoal(new Vector3D(mtApplication.width, mtApplication.height/2f), 50, mtApplication.height/4f, mtApplication, world, 0.0f, 0.1f, 0.0f, scale);
goal2.setName("goal2");
goal2.setFillColor(new MTColor(255,0,0));
goal2.setStrokeColor(new MTColor(255,0,0));
physicsContainer.addChild(goal2);
//Make two components for both game field sides to drag the puks upon
MTRectangle leftSide = new MTRectangle(
PhysicsHelper.scaleDown(0, scale), PhysicsHelper.scaleDown(0, scale),
PhysicsHelper.scaleDown(app.width/2f, scale), PhysicsHelper.scaleDown(app.height, scale)
, app);
leftSide.setName("left side");
leftSide.setNoFill(true); //Make it invisible -> only used for dragging
leftSide.setNoStroke(true);
leftSide.unregisterAllInputProcessors();
leftSide.removeAllGestureEventListeners(DragProcessor.class);
leftSide.registerInputProcessor(new DragProcessor(app));
leftSide.addGestureListener(DragProcessor.class, new GameFieldHalfDragListener(blueCircle));
physicsContainer.addChild(0, leftSide);
MTRectangle rightSide = new MTRectangle(
PhysicsHelper.scaleDown(app.width/2f, scale), PhysicsHelper.scaleDown(0, scale),
PhysicsHelper.scaleDown(app.width, scale), PhysicsHelper.scaleDown(app.height, scale)
, app);
rightSide.setName("right Side");
rightSide.setNoFill(true); //Make it invisible -> only used for dragging
rightSide.setNoStroke(true);
rightSide.unregisterAllInputProcessors();
rightSide.removeAllGestureEventListeners(DragProcessor.class);
rightSide.registerInputProcessor(new DragProcessor(app));
rightSide.addGestureListener(DragProcessor.class, new GameFieldHalfDragListener(redCircle));
physicsContainer.addChild(0, rightSide);
//Display Score UI
MTComponent uiLayer = new MTComponent(mtApplication, new MTCamera(mtApplication));
uiLayer.setDepthBufferDisabled(true);
getCanvas().addChild(uiLayer);
IFont font = FontManager.getInstance().createFont(mtApplication, "arial", 50, new MTColor(255,255,255), new MTColor(0,0,0));
t1 = new MTTextArea(mtApplication, font);
t1.setPickable(false);
t1.setNoFill(true);
t1.setNoStroke(true);
t1.setPositionGlobal(new Vector3D(5,30,0));
uiLayer.addChild(t1);
t2 = new MTTextArea(mtApplication, font);
t2.setPickable(false);
t2.setNoFill(true);
t2.setNoStroke(true);
t2.setPositionGlobal(new Vector3D(mtApplication.width - 65 , 30,0));
uiLayer.addChild(t2);
this.updateScores();
//Set up check for collisions between objects
this.addWorldContactListener(world);
/*
//Sound
if (enableSound){
minim = new Minim(mtApplication);
wallHit = minim.loadSnippet(MT4jSettings.getInstance().getDataFolderPath() + "sound" + File.separator + "paddleBallHit.wav");
// paddleBallClash = minim.loadSample(MT4jSettings.getInstance().getDataFolderPath() + "sound" + File.separator + "paddleBallHit.wav", 2048);
// goalHit = minim.loadSnippet(MT4jSettings.getInstance().getDataFolderPath() + "sound" + File.separator + "goal.wav");
// goalHit.play();
paddleHit = minim.loadSnippet(MT4jSettings.getInstance().getDataFolderPath() + "sound" + File.separator + "wallHit.wav");
}
*/
}
private class GameFieldHalfDragListener implements IGestureEventListener{
private MTComponent comp;
public GameFieldHalfDragListener(MTComponent dragComp){
this.comp = dragComp;
if (comp.getUserData("box2d") == null){
throw new RuntimeException("GameFieldHalfDragListener has to be given a physics object!");
}
}
public boolean processGestureEvent(MTGestureEvent ge) {
DragEvent de = (DragEvent)ge;
try{
Body body = (Body)comp.getUserData("box2d");
MouseJoint mouseJoint;
Vector3D to = new Vector3D(de.getTo());
//Un-scale position from mt4j to box2d
PhysicsHelper.scaleDown(to, scale);
switch (de.getId()) {
case DragEvent.GESTURE_DETECTED:
comp.sendToFront();
body.wakeUp();
body.setXForm(new Vec2(to.x, to.y), body.getAngle());
mouseJoint = PhysicsHelper.createDragJoint(world, body, to.x, to.y);
comp.setUserData(comp.getID(), mouseJoint);
break;
case DragEvent.GESTURE_UPDATED:
mouseJoint = (MouseJoint) comp.getUserData(comp.getID());
if (mouseJoint != null){
boolean onCorrectGameSide = ((MTComponent)de.getTargetComponent()).containsPointGlobal(de.getTo());
//System.out.println(((MTComponent)de.getTargetComponent()).getName() + " Contains " + to + " -> " + contains);
if (onCorrectGameSide){
mouseJoint.setTarget(new Vec2(to.x, to.y));
}
}
break;
case DragEvent.GESTURE_ENDED:
mouseJoint = (MouseJoint) comp.getUserData(comp.getID());
if (mouseJoint != null){
comp.setUserData(comp.getID(), null);
//Only destroy the joint if it isnt already (go through joint list and check)
for (Joint joint = world.getJointList(); joint != null; joint = joint.getNext()) {
JointType type = joint.getType();
switch (type) {
case MOUSE_JOINT:
MouseJoint mj = (MouseJoint)joint;
if (body.equals(mj.getBody1()) || body.equals(mj.getBody2())){
if (mj.equals(mouseJoint)) {
world.destroyJoint(mj);
}
}
break;
default:
break;
}
}
}
mouseJoint = null;
break;
default:
break;
}
}catch (Exception e) {
System.err.println(e.getMessage());
}
return false;
}
}
private class Paddle extends PhysicsCircle{
public Paddle(PApplet applet, Vector3D centerPoint, float radius,
World world, float density, float friction, float restitution, float worldScale) {
super(applet, centerPoint, radius, world, density, friction, restitution, worldScale);
}
@Override
protected void bodyDefB4CreationCallback(BodyDef def) {
super.bodyDefB4CreationCallback(def);
def.fixedRotation = true;
def.linearDamping = 0.5f;
}
}
private class HockeyBall extends PhysicsCircle{
public HockeyBall(PApplet applet, Vector3D centerPoint, float radius,
World world, float density, float friction, float restitution, float worldScale) {
super(applet, centerPoint, radius, world, density, friction, restitution, worldScale);
}
@Override
protected void circleDefB4CreationCallback(CircleDef def) {
super.circleDefB4CreationCallback(def);
def.radius = def.radius -5/scale;
}
@Override
protected void bodyDefB4CreationCallback(BodyDef def) {
super.bodyDefB4CreationCallback(def);
// def.linearDamping = 0.15f;
def.linearDamping = 0.25f;
def.isBullet = true;
def.angularDamping = 0.9f;
// def.fixedRotation = true;
}
}
private class HockeyGoal extends PhysicsRectangle {
public HockeyGoal(Vector3D centerPosition, float width, float height,
PApplet applet, World world, float density, float friction,float restitution, float scale) {
super(centerPosition, width, height, applet, world, density, friction,restitution, scale);
}
@Override
protected void bodyDefB4CreationCallback(BodyDef def) {
def.isBullet = true;
super.bodyDefB4CreationCallback(def);
}
@Override
protected void polyDefB4CreationCallback(PolygonDef def) {
super.polyDefB4CreationCallback(def);
def.isSensor = true; //THIS AS SENSOR!
}
}
private void addWorldContactListener(World world){
world.setContactListener(new ContactListener() {
public void result(ContactResult point) {
// System.out.println("Result contact");
}
//@Override
public void remove(ContactPoint point) {
// System.out.println("remove contact");
}
//@Override
public void persist(ContactPoint point) {
// System.out.println("persist contact");
}
//@Override
public void add(ContactPoint point) {
// /*
Shape shape1 = point.shape1;
Shape shape2 = point.shape2;
final Body body1 = shape1.getBody();
final Body body2 = shape2.getBody();
Object userData1 = body1.getUserData();
Object userData2 = body2.getUserData();
if (userData1 instanceof IPhysicsComponent && userData2 instanceof IPhysicsComponent) { //Check for ball/star collision
IPhysicsComponent physObj1 = (IPhysicsComponent) userData1;
IPhysicsComponent physObj2 = (IPhysicsComponent) userData2;
// System.out.println("Collided: " + mt4jObj1 + " with " + mt4jObj2);
if (physObj1 instanceof MTComponent && physObj2 instanceof MTComponent) {
MTComponent comp1 = (MTComponent) physObj1;
MTComponent comp2 = (MTComponent) physObj2;
//Check if one of the components is the BALL
MTComponent ball = isHit("ball", comp1, comp2);
final MTComponent theBall = ball;
//Check if one of the components is the GOAL
MTComponent goal1 = isHit("goal1", comp1, comp2);
MTComponent goal2 = isHit("goal2", comp1, comp2);
//Check if a puck was involved
MTComponent bluePuck = isHit("blue", comp1, comp2);
MTComponent redPuck = isHit("red", comp1, comp2);
//Check if a border was hit
MTComponent border = null;
if (comp1.getName() != null && comp1.getName().startsWith("border")){
border = comp1;
}else if (comp2.getName() != null && comp2.getName().startsWith("border")){
border = comp2;
}
if (ball != null){
//CHECK IF BALL HIT A PADDLE
if (enableSound && (bluePuck != null || redPuck != null)){
// System.out.println("PUCK HIT BALL!");
/*
triggerSound(paddleHit);
*/
}
//Check if BALL HIT A GOAL
if (goal1 != null || goal2 != null){
//BALL HIT A GOAL
if (goal1 != null){
System.out.println("GOAL FOR PLAYER 2!");
scorePlayer2++;
}else if (goal2 != null){
System.out.println("GOAL FOR PLAYER 1!");
scorePlayer1++;
}
//Update scores
updateScores();
//Play goal sound
// triggerSound(goalHit);
if (scorePlayer1 >= 15 || scorePlayer2 >= 15){
reset();
}else{
//Reset ball
if (theBall.getUserData("resetted") == null){ //To make sure that we call destroy only once
theBall.setUserData("resetted", true);
app.invokeLater(new Runnable() {
public void run() {
IPhysicsComponent a = (IPhysicsComponent)theBall;
a.getBody().setXForm(new Vec2(getMTApplication().width/2f/scale, getMTApplication().height/2f/scale), a.getBody().getAngle());
// a.getBody().setLinearVelocity(new Vec2(0,0));
a.getBody().setLinearVelocity(new Vec2(ToolsMath.getRandom(-8, 8),ToolsMath.getRandom(-8, 8)));
a.getBody().setAngularVelocity(0);
theBall.setUserData("resetted", null);
}
});
}
}
}
//If ball hit border Play sound
if (enableSound && border != null){
/*
triggerSound(wallHit);
*/
}
}
}
}else{ //if at lest one if the colliding bodies' userdata is not a physics shape
}
// */
}
});
}
/*
private void triggerSound(AudioSnippet snippet){
if (!snippet.isPlaying()){
snippet.pause();
snippet.rewind();
snippet.play();
}
}
*/
private MTComponent isHit(String componentName, MTComponent comp1, MTComponent comp2){
MTComponent hitComp = null;
if (comp1.getName() != null && comp1.getName().equalsIgnoreCase(componentName)){
hitComp = comp1;
}else if (comp2.getName() != null && comp2.getName().equalsIgnoreCase(componentName)){
hitComp = comp2;
}
return hitComp;
}
private void updateScores(){
t1.setText(Integer.toString(scorePlayer1));
t2.setText(Integer.toString(scorePlayer2));
}
private void reset(){
if (ball.getUserData("resetted") == null){ //To make sure that we call destroy only once
ball.setUserData("resetted", true);
app.invokeLater(new Runnable() {
public void run() {
IPhysicsComponent a = (IPhysicsComponent)ball;
a.getBody().setXForm(new Vec2(getMTApplication().width/2f/scale, getMTApplication().height/2f/scale), a.getBody().getAngle());
// a.getBody().setLinearVelocity(new Vec2(0,0));
a.getBody().setLinearVelocity(new Vec2(ToolsMath.getRandom(-8, 8),ToolsMath.getRandom(-8, 8)));
a.getBody().setAngularVelocity(0);
ball.setUserData("resetted", null);
}
});
}
this.scorePlayer1 = 0;
this.scorePlayer2 = 0;
this.updateScores();
}
private void createScreenBorders(MTComponent parent){
//Left border
float borderWidth = 50f;
float borderHeight = app.height;
Vector3D pos = new Vector3D(-(borderWidth/2f) , app.height/2f);
PhysicsRectangle borderLeft = new PhysicsRectangle(pos, borderWidth, borderHeight, app, world, 0,0,0, scale);
borderLeft.setName("borderLeft");
parent.addChild(borderLeft);
//Right border
pos = new Vector3D(app.width + (borderWidth/2), app.height/2);
PhysicsRectangle borderRight = new PhysicsRectangle(pos, borderWidth, borderHeight, app, world, 0,0,0, scale);
borderRight.setName("borderRight");
parent.addChild(borderRight);
//Top border
borderWidth = app.width;
borderHeight = 50f;
pos = new Vector3D(app.width/2, -(borderHeight/2));
PhysicsRectangle borderTop = new PhysicsRectangle(pos, borderWidth, borderHeight, app, world, 0,0,0, scale);
borderTop.setName("borderTop");
parent.addChild(borderTop);
//Bottom border
pos = new Vector3D(app.width/2 , app.height + (borderHeight/2));
PhysicsRectangle borderBottom = new PhysicsRectangle(pos, borderWidth, borderHeight, app, world, 0,0,0, scale);
borderBottom.setName("borderBottom");
parent.addChild(borderBottom);
}
@Override
public void init() {
this.getMTApplication().registerKeyEvent(this);
}
@Override
public void shutDown() {
this.getMTApplication().unregisterKeyEvent(this);
}
public void keyEvent(KeyEvent e){
int evtID = e.getID();
if (evtID != KeyEvent.KEY_PRESSED)
return;
switch (e.getKeyCode()){
case KeyEvent.VK_SPACE:
this.reset();
break;
default:
break;
}
}
}