diff --git a/.gitignore b/.gitignore index 87a44e0..709398b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ local.properties # Eclipse project files .classpath .project +libs/ # Proguard folder generated by Eclipse proguard/ diff --git a/src/ve/ucv/ciens/ccg/networkdata/MotorEvent.java b/src/ve/ucv/ciens/ccg/networkdata/MotorEvent.java new file mode 100644 index 0000000..5cdcfc9 --- /dev/null +++ b/src/ve/ucv/ciens/ccg/networkdata/MotorEvent.java @@ -0,0 +1,37 @@ +package ve.ucv.ciens.ccg.networkdata; + +import java.io.Serializable; + +public class MotorEvent implements Serializable{ + private static final long serialVersionUID = 9989L; + + public enum motor_t {NONE, MOTOR_A, MOTOR_B, MOTOR_C, MOTOR_AC}; + + private motor_t motor; + private byte power; + + public MotorEvent(){ + motor = motor_t.NONE; + power = 0; + } + + public void setMotor(motor_t motor){ + this.motor = motor; + } + + public void setPower(byte power) throws IllegalArgumentException{ + if(power > 100 || power < -100){ + throw new IllegalArgumentException("Motor power must be a number between -100 and 100"); + }else{ + this.power = power; + } + } + + public motor_t getMotor(){ + return this.motor; + } + + public byte getPower(){ + return this.power; + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/systems/RenderSystem.java b/src/ve/ucv/ciens/ccg/networkdata/MotorEventACK.java similarity index 54% rename from src/ve/ucv/ciens/ccg/nxtar/systems/RenderSystem.java rename to src/ve/ucv/ciens/ccg/networkdata/MotorEventACK.java index 614a69b..a2911f9 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/systems/RenderSystem.java +++ b/src/ve/ucv/ciens/ccg/networkdata/MotorEventACK.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Miguel Angel Astor Romero + * Copyright (C) 2014 Miguel Angel Astor Romero * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,24 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ve.ucv.ciens.ccg.nxtar.systems; +package ve.ucv.ciens.ccg.networkdata; -import ve.ucv.ciens.ccg.nxtar.components.VideoFrame; +import java.io.Serializable; -import com.artemis.Aspect; -import com.artemis.Entity; -import com.artemis.systems.EntityProcessingSystem; +public class MotorEventACK implements Serializable { + private static final long serialVersionUID = 9989L; -public class RenderSystem extends EntityProcessingSystem { + private boolean clientQueueIsFull; - @SuppressWarnings("unchecked") - public RenderSystem(Aspect aspect) { - super(Aspect.getAspectForAll(VideoFrame.class)); + public MotorEventACK(boolean isQueueFull){ + this.clientQueueIsFull = isQueueFull; } - @Override - protected void process(Entity arg0) { - + public boolean isClientQueueFull(){ + return this.clientQueueIsFull; } - } diff --git a/src/ve/ucv/ciens/ccg/nxtar/NxtARCore.java b/src/ve/ucv/ciens/ccg/nxtar/NxtARCore.java index 533bfa6..e90ad5f 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/NxtARCore.java +++ b/src/ve/ucv/ciens/ccg/nxtar/NxtARCore.java @@ -20,150 +20,302 @@ import ve.ucv.ciens.ccg.nxtar.interfaces.NetworkConnectionListener; import ve.ucv.ciens.ccg.nxtar.interfaces.Toaster; import ve.ucv.ciens.ccg.nxtar.network.RobotControlThread; import ve.ucv.ciens.ccg.nxtar.network.ServiceDiscoveryThread; -import ve.ucv.ciens.ccg.nxtar.network.VideoFrameMonitor; import ve.ucv.ciens.ccg.nxtar.network.VideoStreamingThread; +import ve.ucv.ciens.ccg.nxtar.states.BaseState; +import ve.ucv.ciens.ccg.nxtar.states.InGameState; +import ve.ucv.ciens.ccg.nxtar.states.MainMenuStateBase; +import ve.ucv.ciens.ccg.nxtar.states.OuyaMainMenuState; +import ve.ucv.ciens.ccg.nxtar.states.PauseState; +import ve.ucv.ciens.ccg.nxtar.states.TabletMainMenuState; import ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants; -import ve.ucv.ciens.ccg.nxtar.utils.Size; +import aurelienribon.tweenengine.Tween; +import aurelienribon.tweenengine.TweenEquations; +import aurelienribon.tweenengine.primitives.MutableFloat; import com.badlogic.gdx.Application; -import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.Game; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.GL10; +import com.badlogic.gdx.controllers.Controllers; +import com.badlogic.gdx.controllers.mappings.Ouya; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.Texture.TextureFilter; -import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.graphics.g2d.TextureRegion; -public class NxtARCore implements ApplicationListener, NetworkConnectionListener { +/** + *

Core of the application.

+ * + *

This class has three basic resposibilities:

+ * + * @author Miguel Angel Astor Romero + */ +public class NxtARCore extends Game implements NetworkConnectionListener{ private static final String TAG = "NXTAR_CORE_MAIN"; private static final String CLASS_NAME = NxtARCore.class.getSimpleName(); - private OrthographicCamera camera; - private SpriteBatch batch; - private Texture texture; - private Sprite sprite; - private Toaster toaster; - private MulticastEnabler mcastEnabler; - private int connections; + /** + * Valid game states. + */ + public enum game_states_t { + MAIN_MENU(0), IN_GAME(1), PAUSED(2); - private VideoFrameMonitor frameMonitor; - private ServiceDiscoveryThread udpThread; + private int value; + + private game_states_t(int value){ + this.value = value; + } + + public int getValue(){ + return this.value; + } + }; + /** + * The current application state. + */ + private game_states_t currState; + /** + *

The state to change to.

+ *

Usually null. A state change is scheduled by setting this field to a {@link game_states_t} value.

+ */ + public game_states_t nextState; + + // Screens. + private BaseState[] states; + + // Assorted fields. + public SpriteBatch batch; + private Toaster toaster; + + // Networking related fields. + private int connections; + private MulticastEnabler mcastEnabler; + private ServiceDiscoveryThread serviceDiscoveryThread; private VideoStreamingThread videoThread; private RobotControlThread robotThread; + // Overlays. + private OrthographicCamera pixelPerfectCamera; + private float fontX; + private float fontY; + private BitmapFont font; + + private Texture fadeTexture; + private MutableFloat alpha; + private Tween fadeOut; + private Tween fadeIn; + private boolean fading; + + /** + *

Set up the basic application fields.

+ */ public NxtARCore(Application concreteApp){ super(); connections = 0; try{ this.toaster = (Toaster)concreteApp; + }catch(ClassCastException cc){ + Gdx.app.debug(TAG, CLASS_NAME + ".Main() :: concreteApp does not implement the Toaster interface. Toasting disabled."); + this.toaster = null; + } + + try{ this.mcastEnabler = (MulticastEnabler)concreteApp; }catch(ClassCastException cc){ - Gdx.app.debug(TAG, CLASS_NAME + ".Main() :: concreteApp does not implement any of the required interfaces."); - System.exit(ProjectConstants.EXIT_FAILURE); + Gdx.app.error(TAG, CLASS_NAME + ".Main() :: concreteApp does not implement MulticastEnabler. Quitting."); + Gdx.app.exit(); } } - @Override public void create(){ - float w = Gdx.graphics.getWidth(); - float h = Gdx.graphics.getHeight(); + // Create the state objects. + states = new BaseState[3]; + if(Ouya.runningOnOuya)states[game_states_t.MAIN_MENU.getValue()] = new OuyaMainMenuState(this); + else states[game_states_t.MAIN_MENU.getValue()] = new TabletMainMenuState(this); + states[game_states_t.IN_GAME.getValue()] = new InGameState(this); + states[game_states_t.PAUSED.getValue()] = new PauseState(this); - Gdx.app.setLogLevel(Application.LOG_DEBUG); + for(BaseState state : states){ + Controllers.addListener(state); + } - camera = new OrthographicCamera(1, h/w); + // Set up fields. batch = new SpriteBatch(); - texture = new Texture(Gdx.files.internal("data/libgdx.png")); - texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + pixelPerfectCamera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); - TextureRegion region = new TextureRegion(texture, 0, 0, 512, 275); + if(ProjectConstants.DEBUG){ + // Set up the overlay font. + fontX = -((Gdx.graphics.getWidth() * ProjectConstants.OVERSCAN) / 2) + 10; + fontY = ((Gdx.graphics.getHeight() * ProjectConstants.OVERSCAN) / 2) - 10; - sprite = new Sprite(region); - sprite.setSize(0.9f, 0.9f * sprite.getHeight() / sprite.getWidth()); - sprite.setOrigin(sprite.getWidth()/2, sprite.getHeight()/2); - sprite.setPosition(-sprite.getWidth()/2, -sprite.getHeight()/2); + font = new BitmapFont(); + font.setColor(1.0f, 1.0f, 0.0f, 1.0f); + if(!Ouya.runningOnOuya){ + font.setScale(1.0f); + }else{ + font.setScale(2.5f); + } + } + + // Start networking. + mcastEnabler.enableMulticast(); Gdx.app.debug(TAG, CLASS_NAME + ".create() :: Creating network threads"); - frameMonitor = VideoFrameMonitor.getInstance(); - mcastEnabler.enableMulticast(); - udpThread = ServiceDiscoveryThread.getInstance(); - videoThread = VideoStreamingThread.getInstance().setToaster(toaster); - //robotThread = RobotControlThread.getInstance().setToaster(toaster); + serviceDiscoveryThread = ServiceDiscoveryThread.getInstance(); + videoThread = VideoStreamingThread.getInstance(); + robotThread = RobotControlThread.getInstance(); + + serviceDiscoveryThread.start(); - udpThread.start(); videoThread.start(); videoThread.startStreaming(); - //robotThread.start(); + videoThread.addNetworkConnectionListener(this); + + robotThread.addNetworkConnectionListener(this); + robotThread.start(); + + // Set the current and next states. + currState = game_states_t.MAIN_MENU; + nextState = null; + this.setScreen(states[currState.getValue()]); + states[currState.getValue()].onStateSet(); + + // Prepare the fadeToBlack sprite; + Pixmap pixmap = new Pixmap(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), Format.RGBA4444); + pixmap.setColor(0, 0, 0, 1); + pixmap.fill(); + fadeTexture = new Texture(pixmap); + pixmap.dispose(); + + alpha = new MutableFloat(0.0f); + fadeOut = Tween.to(alpha, 0, 0.5f).target(1.0f).ease(TweenEquations.easeInQuint); + fadeIn = Tween.to(alpha, 0, 0.5f).target(0.0f).ease(TweenEquations.easeInQuint); + + fading = false; + + // Set initial input handlers. + Gdx.input.setInputProcessor(states[currState.getValue()]); + Controllers.addListener(states[currState.getValue()]); + + // Anything else. + Gdx.app.setLogLevel(Application.LOG_INFO); + //Gdx.app.setLogLevel(Application.LOG_DEBUG); + //Gdx.app.setLogLevel(Application.LOG_NONE); } - @Override - public void dispose() { - batch.dispose(); - texture.dispose(); - } - - @Override public void render(){ - Pixmap image; - Pixmap temp; - byte[] frame; - Size dimensions; + super.render(); - Gdx.gl.glClearColor(1, 1, 1, 1); - Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); + // If the current state set a value for nextState then switch to that state. + if(nextState != null){ + states[currState.getValue()].onStateUnset(); - frame = frameMonitor.getCurrentFrame(); - if(frame != null){ - texture.dispose(); + if(!fadeOut.isStarted()){ + Gdx.app.log(TAG, CLASS_NAME + ".onRender() :: Starting fade out."); + fadeOut.start(); + fading = true; + }else{ + Gdx.app.log(TAG, CLASS_NAME + ".onRender() :: Updating fade out."); + fadeOut.update(Gdx.graphics.getDeltaTime()); - dimensions = frameMonitor.getFrameDimensions(); - temp = new Pixmap(frame, 0, dimensions.getWidth() * dimensions.getHeight()); - image = new Pixmap(1024, 512, temp.getFormat()); - image.drawPixmap(temp, 0, 0); - texture = new Texture(image); - texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + if(fadeOut.isFinished()){ + currState = nextState; + nextState = null; - TextureRegion region = new TextureRegion(texture, 0, 0, dimensions.getWidth(), dimensions.getHeight()); + states[currState.getValue()].onStateSet(); - sprite = new Sprite(region); - sprite.setSize(0.9f, 0.9f * sprite.getHeight() / sprite.getWidth()); - sprite.setOrigin(sprite.getWidth()/2, sprite.getHeight()/2); - sprite.setPosition(-sprite.getWidth()/2, -sprite.getHeight()/2); + setScreen(states[currState.getValue()]); - batch.setProjectionMatrix(camera.combined); + Gdx.app.log(TAG, CLASS_NAME + ".onRender() :: Freeing fade out."); + fadeOut.free(); + fadeOut = Tween.to(alpha, 0, 0.5f).target(1.0f).ease(TweenEquations.easeInQuint); + fadeIn.start(); + } + } + } + + if(fadeIn.isStarted()){ + if(!fadeIn.isFinished()){ + fadeIn.update(Gdx.graphics.getDeltaTime()); + }else{ + fading = false; + fadeIn.free(); + fadeIn = Tween.to(alpha, 0, 0.5f).target(0.0f).ease(TweenEquations.easeInQuint); + } + } + + if(fading){ + batch.setProjectionMatrix(pixelPerfectCamera.combined); batch.begin();{ - sprite.draw(batch); + batch.setColor(1, 1, 1, alpha.floatValue()); + batch.draw(fadeTexture, -(Gdx.graphics.getWidth() / 2), -(Gdx.graphics.getHeight() / 2)); + batch.setColor(1, 1, 1, 1); }batch.end(); + } - texture.dispose(); - temp.dispose(); - image.dispose(); + if(ProjectConstants.DEBUG){ + batch.setProjectionMatrix(pixelPerfectCamera.combined); + batch.begin();{ + // Draw the FPS overlay. + font.draw(batch, String.format("Render FPS: %d", Gdx.graphics.getFramesPerSecond()), fontX, fontY); + font.draw(batch, String.format("Total stream FPS: %d", videoThread.getFps()), fontX, fontY - font.getCapHeight() - 5); + font.draw(batch, String.format("Lost stream FPS: %d", videoThread.getLostFrames()), fontX, fontY - (2 * font.getCapHeight()) - 10); + }batch.end(); } } - @Override - public void resize(int width, int height){ - } - - @Override public void pause(){ + if(videoThread != null) + videoThread.pause(); } - @Override public void resume(){ + if(videoThread != null) + videoThread.play(); + } + + public void dispose(){ + // Finish network threads. + videoThread.finish(); + robotThread.finish(); + + // Dispose graphic objects. + fadeTexture.dispose(); + batch.dispose(); + if(ProjectConstants.DEBUG){ + font.dispose(); + } + + // Dispose screens. + for(int i = 0; i < states.length; i++){ + states[i].dispose(); + } } @Override public synchronized void networkStreamConnected(String streamName){ - if(streamName.compareTo(VideoStreamingThread.THREAD_NAME) == 0 || streamName.compareTo(RobotControlThread.THREAD_NAME) == 0) - connections += 1; + //if(streamName.equals(VideoStreamingThread.THREAD_NAME) || streamName.equals(RobotControlThread.THREAD_NAME)) + Gdx.app.log(TAG, CLASS_NAME + ".networkStreamConnected() :: Stream " + streamName + " connected."); + connections += 1; if(connections >= 2){ Gdx.app.debug(TAG, CLASS_NAME + ".networkStreamConnected() :: Stopping service broadcast."); - udpThread.finish(); + serviceDiscoveryThread.finish(); mcastEnabler.disableMulticast(); + toaster.showShortToast("Client connected"); + ((MainMenuStateBase)states[game_states_t.MAIN_MENU.getValue()]).onClientConnected(); + } + } + + public void toast(String msg, boolean longToast){ + if(toaster != null){ + if(longToast) toaster.showLongToast(msg); + else toaster.showShortToast(msg); } } } diff --git a/src/ve/ucv/ciens/ccg/nxtar/components/VideoFrame.java b/src/ve/ucv/ciens/ccg/nxtar/components/VideoFrame.java deleted file mode 100644 index 6a85af6..0000000 --- a/src/ve/ucv/ciens/ccg/nxtar/components/VideoFrame.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2013 Miguel Angel Astor Romero - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ve.ucv.ciens.ccg.nxtar.components; - -import com.artemis.Component; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Texture; - -public class VideoFrame extends Component { - private Texture frame; - - public VideoFrame(){ } - - public VideoFrame(byte[] imageData){ - frame = new Texture(new Pixmap(imageData, 0, imageData.length)); - } - - public Texture getFrame(){ - return frame; - } - - public void setFrame(byte[] imageData){ - if(frame != null) - frame.dispose(); - frame = new Texture(new Pixmap(imageData, 0, imageData.length)); - } -} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/NxtARState.java b/src/ve/ucv/ciens/ccg/nxtar/exceptions/ImageTooBigException.java similarity index 66% rename from src/ve/ucv/ciens/ccg/nxtar/states/NxtARState.java rename to src/ve/ucv/ciens/ccg/nxtar/exceptions/ImageTooBigException.java index 90dee4f..d3196d7 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/states/NxtARState.java +++ b/src/ve/ucv/ciens/ccg/nxtar/exceptions/ImageTooBigException.java @@ -1,25 +1,24 @@ -/* - * Copyright (C) 2013 Miguel Angel Astor Romero - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ve.ucv.ciens.ccg.nxtar.states; - -public interface NxtARState{ - public void input(); - public void update(); - public void render(); - public void pause(); - public void resume(); - public void dispose(); -} +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.exceptions; + +public class ImageTooBigException extends Exception{ + private static final long serialVersionUID = 9989L; + + public ImageTooBigException(String msg){ + super(msg); + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/network/RobotControlThread.java b/src/ve/ucv/ciens/ccg/nxtar/network/RobotControlThread.java index eb120f5..3c1374f 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/network/RobotControlThread.java +++ b/src/ve/ucv/ciens/ccg/nxtar/network/RobotControlThread.java @@ -16,11 +16,16 @@ package ve.ucv.ciens.ccg.nxtar.network; import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; +import ve.ucv.ciens.ccg.networkdata.MotorEvent; +import ve.ucv.ciens.ccg.networkdata.MotorEventACK; import ve.ucv.ciens.ccg.nxtar.interfaces.NetworkConnectionListener; -import ve.ucv.ciens.ccg.nxtar.interfaces.Toaster; +import ve.ucv.ciens.ccg.nxtar.network.monitors.MotorEventQueue; import ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants; import com.badlogic.gdx.Gdx; @@ -33,20 +38,29 @@ public class RobotControlThread extends Thread { private NetworkConnectionListener netListener; private ServerSocket server; private Socket client; - private Toaster toaster; + private MotorEventQueue queue; + private Object pauseMonitor; + private boolean paused; + private boolean done; + private ObjectOutputStream os; + private ObjectInputStream is; private RobotControlThread(){ super(THREAD_NAME); netListener = null; + queue = MotorEventQueue.getInstance(); + pauseMonitor = new Object(); + paused = false; + done = false; try{ - server = new ServerSocket(ProjectConstants.SERVER_TCP_PORT_2); + server = new ServerSocket(ProjectConstants.MOTOR_CONTROL_PORT); }catch(IOException io){ Gdx.app.error(TAG, CLASS_NAME + ".RobotControlThread() :: Error creating server: " + io.getMessage(), io); } } - + private static class SingletonHolder{ public static final RobotControlThread INSTANCE = new RobotControlThread(); } @@ -55,25 +69,109 @@ public class RobotControlThread extends Thread { return SingletonHolder.INSTANCE; } - public RobotControlThread setToaster(Toaster toaster){ - this.toaster = toaster; - return this; - } - public void addNetworkConnectionListener(NetworkConnectionListener listener){ netListener = listener; } + public void pauseThread(){ + synchronized(pauseMonitor){ + paused = true; + } + } + + public void resumeThread(){ + synchronized(pauseMonitor){ + paused = false; + } + } + + public void finish(){ + done = true; + } + @Override public void run(){ + MotorEvent message; + MotorEventACK ack; + try{ client = server.accept(); - if(netListener != null) - netListener.networkStreamConnected(THREAD_NAME); - toaster.showShortToast("Client connected to RobotControlThread"); - client.close(); + if(netListener != null) netListener.networkStreamConnected(THREAD_NAME); + os = new ObjectOutputStream(client.getOutputStream()); + is = new ObjectInputStream(client.getInputStream()); + }catch(IOException io){ Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error accepting client: " + io.getMessage(), io); + return; + } + + while(!paused){ + if(done){ + break; + } + + // Send the motor event. + try{ + message = queue.getNextEvent(); + os.writeObject(message); + message = null; + + }catch(InvalidClassException ic){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during transmission: " + ic.getMessage(), ic); + break; + + }catch(IOException io){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: IOException during transmission: " + io.getMessage(), io); + break; + } + + // Receive ack. + try{ + ack = (MotorEventACK)is.readObject(); + }catch(ClassNotFoundException cn){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during reception: " + cn.getMessage(), cn); + break; + + }catch(ClassCastException cc){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during reception: " + cc.getMessage(), cc); + break; + + }catch(IOException io){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during reception: " + io.getMessage(), io); + break; + } + + if(ack.isClientQueueFull()){ + // Wait for client to notify. + // A client will never send two queue full acks in a row. + try{ + ack = (MotorEventACK)is.readObject(); + }catch(ClassNotFoundException cn){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during reception: " + cn.getMessage(), cn); + break; + + }catch(ClassCastException cc){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during reception: " + cc.getMessage(), cc); + break; + + }catch(IOException io){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: InvalidClassException during reception: " + io.getMessage(), io); + break; + } + + }else{ + // Clean and continue. + ack = null; + message = null; + continue; + } + } + + + try{ + client.close(); + }catch(IOException io){ + Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error closing client: " + io.getMessage(), io); } } } diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/DummyState.java b/src/ve/ucv/ciens/ccg/nxtar/network/SensorReportThread.java similarity index 57% rename from src/ve/ucv/ciens/ccg/nxtar/states/DummyState.java rename to src/ve/ucv/ciens/ccg/nxtar/network/SensorReportThread.java index 0aa0f8f..d6d935e 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/states/DummyState.java +++ b/src/ve/ucv/ciens/ccg/nxtar/network/SensorReportThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Miguel Angel Astor Romero + * Copyright (C) 2014 Miguel Angel Astor Romero * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,33 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ve.ucv.ciens.ccg.nxtar.states; +package ve.ucv.ciens.ccg.nxtar.network; -/** - * Empty state. - * - * Completely empty state for debugging purposes. - * - * @author miky - */ -public class DummyState implements NxtARState{ +public class SensorReportThread extends Thread { + + private SensorReportThread(){ + + } + + private static class SingletonHolder{ + public final static SensorReportThread INSTANCE = new SensorReportThread(); + } + + public static SensorReportThread getInstance(){ + return SingletonHolder.INSTANCE; + } @Override - public void input(){ } - - @Override - public void update(){ } - - @Override - public void render(){ } - - @Override - public void pause(){ } - - @Override - public void resume(){ } - - @Override - public void dispose(){ } + public void run(){ + } } diff --git a/src/ve/ucv/ciens/ccg/nxtar/network/ServiceDiscoveryThread.java b/src/ve/ucv/ciens/ccg/nxtar/network/ServiceDiscoveryThread.java index 305cb49..e740513 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/network/ServiceDiscoveryThread.java +++ b/src/ve/ucv/ciens/ccg/nxtar/network/ServiceDiscoveryThread.java @@ -31,7 +31,7 @@ import com.badlogic.gdx.Gdx; *

This thread performs an ad hoc service discovery protocol. A multicast datagram packet is sent every * 250 miliseconds carrying the string "NxtAR server is here!" on the multicast address defined * in {@link ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants#MULTICAST_ADDRESS}. The port defined in - * {@link ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants#SERVER_UDP_PORT} is used for the transmissions. The server stops + * {@link ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants#SERVICE_DISCOVERY_PORT} is used for the transmissions. The server stops * when another thread calls the {@link #finish()} method or the server fails to transmit {@link #MAX_RETRIES} packets in * a row, whichever happens first.

* @@ -89,7 +89,7 @@ public class ServiceDiscoveryThread extends Thread { // Create a UDP socket at the port defined in ProjectConstants.SERVER_UDP_PORT. Gdx.app.debug(TAG, CLASS_NAME + ".ServiceDiscoveryThread() :: Creating multicast server."); try{ - udpServer = new DatagramSocket(ProjectConstants.SERVER_UDP_PORT); + udpServer = new DatagramSocket(ProjectConstants.SERVICE_DISCOVERY_PORT); }catch(IOException io){ Gdx.app.error(TAG, CLASS_NAME + ".ServiceDiscoveryThread() :: Error creating UDP socket: " + io.getMessage()); udpServer = null; @@ -150,10 +150,11 @@ public class ServiceDiscoveryThread extends Thread { break; } // Send the packet and reset the retry counter. - DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, ProjectConstants.SERVER_UDP_PORT); + DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, ProjectConstants.SERVICE_DISCOVERY_PORT); udpServer.send(packet); retries = 0; try{ sleep(250L); }catch(InterruptedException ie){ } + }catch(IOException io){ Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error sending packet: " + io.getMessage()); retries += 1; diff --git a/src/ve/ucv/ciens/ccg/nxtar/network/VideoStreamingThread.java b/src/ve/ucv/ciens/ccg/nxtar/network/VideoStreamingThread.java index b7b5889..a8c12f2 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/network/VideoStreamingThread.java +++ b/src/ve/ucv/ciens/ccg/nxtar/network/VideoStreamingThread.java @@ -15,57 +15,57 @@ */ package ve.ucv.ciens.ccg.nxtar.network; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.net.ServerSocket; +import java.net.DatagramPacket; +import java.net.DatagramSocket; import java.net.Socket; import ve.ucv.ciens.ccg.networkdata.VideoFrameDataMessage; -import ve.ucv.ciens.ccg.networkdata.VideoStreamingControlMessage; import ve.ucv.ciens.ccg.nxtar.interfaces.NetworkConnectionListener; -import ve.ucv.ciens.ccg.nxtar.interfaces.Toaster; -import ve.ucv.ciens.ccg.nxtar.network.protocols.VideoStreamingProtocol; +import ve.ucv.ciens.ccg.nxtar.network.monitors.VideoFrameMonitor; import ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants; import com.badlogic.gdx.Gdx; -public class VideoStreamingThread extends Thread { +public class VideoStreamingThread extends Thread{ public static final String THREAD_NAME = "VideoStreamingThread"; private static final String TAG = "NXTAR_CORE_VIDEOTHREAD"; private static final String CLASS_NAME = VideoStreamingThread.class.getSimpleName(); - private enum ProtocolState_t {WAIT_FOR_START, SEND_CONTINUE, RECEIVE_DATA, SEND_ACK_NEXT, SEND_ACK_WAIT, PAUSED, END_STREAM}; - private NetworkConnectionListener netListener; - private ServerSocket server; - private Toaster toaster; - private ProtocolState_t protocolState; + private DatagramSocket socket; private boolean protocolStarted; - private boolean pauseProtocol; - private boolean endProtocol; private boolean done; + private boolean pause; + private boolean coreNotified; private Object protocolPauseMonitor; private Socket client; - private ObjectInputStream reader; - private ObjectOutputStream writer; private VideoFrameMonitor frameMonitor; + private long then; + private long now; + private long delta; + private int fps; + private int lostFramesPerSecond; + private int lostFrames; + private Object pauseMonitor; private VideoStreamingThread(){ super(THREAD_NAME); + pauseMonitor = new Object(); + fps = 0; + lostFramesPerSecond = 0; netListener = null; - toaster = null; protocolStarted = false; - endProtocol = false; - pauseProtocol = false; done = false; - protocolState = ProtocolState_t.WAIT_FOR_START; + coreNotified = false; protocolPauseMonitor = new Object(); frameMonitor = VideoFrameMonitor.getInstance(); try{ - server = new ServerSocket(ProjectConstants.SERVER_TCP_PORT_1); + socket = new DatagramSocket(ProjectConstants.VIDEO_STREAMING_PORT); }catch(IOException io){ Gdx.app.error(TAG, CLASS_NAME + ".VideoStreamingThread() :: Error creating server: " + io.getMessage(), io); } @@ -79,26 +79,15 @@ public class VideoStreamingThread extends Thread { return SingletonHolder.INSTANCE; } - public VideoStreamingThread setToaster(Toaster toaster){ - this.toaster = toaster; - return this; - } - public void addNetworkConnectionListener(NetworkConnectionListener listener){ netListener = listener; } - private void toast(String message){ - if(toaster != null) - toaster.showShortToast(message); - } - public void startStreaming(){ if(!protocolStarted){ Gdx.app.debug(TAG, CLASS_NAME + ".startStreaming() :: Requesting protocol start."); synchronized(protocolPauseMonitor){ protocolStarted = true; - protocolState = ProtocolState_t.SEND_CONTINUE; protocolPauseMonitor.notifyAll(); } } @@ -107,7 +96,6 @@ public class VideoStreamingThread extends Thread { public void pauseStreaming(){ if(protocolStarted){ Gdx.app.debug(TAG, CLASS_NAME + ".pauseStreaming() :: Requesting protocol pause."); - pauseProtocol = true; }else return; } @@ -116,7 +104,6 @@ public class VideoStreamingThread extends Thread { if(protocolStarted){ Gdx.app.debug(TAG, CLASS_NAME + ".resumeStreaming() :: Requesting protocol resume."); synchronized(protocolPauseMonitor){ - pauseProtocol = false; protocolPauseMonitor.notifyAll(); } }else @@ -126,7 +113,6 @@ public class VideoStreamingThread extends Thread { public void finishStreaming(){ if(protocolStarted){ Gdx.app.debug(TAG, CLASS_NAME + ".finishStreaming() :: Requesting protocol end."); - endProtocol = true; }else return; } @@ -135,222 +121,120 @@ public class VideoStreamingThread extends Thread { done = true; } - /*@Override - public void run(){ - Object tmpMessage; - VideoStreamingControlMessage controlMessage; - VideoFrameDataMessage dataMessage; + private int byteArray2Int(byte[] array){ + int number = 0; + for(int i = 0; i < 4; i++){ + number |= (array[3-i] & 0xff) << (i << 3); + } + return number; + } - // Listen on the server socket until a client successfully connects. - do{ + private void receiveUdp(){ + try{ + int intSize; + byte[] size = new byte[4]; + byte[] data; + DatagramPacket packet; + VideoFrameDataMessage dataMessage; + Object tmpMessage; + + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Reading message size from socket."); try{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Listening for client."); - client = server.accept(); - if(netListener != null) - netListener.networkStreamConnected(THREAD_NAME); - writer = new ObjectOutputStream(client.getOutputStream()); - reader = new ObjectInputStream(client.getInputStream()); - toast("Client connected"); + packet = new DatagramPacket(size, size.length); + socket.receive(packet); }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error accepting client: " + io.getMessage(), io); - client = null; + Gdx.app.error(TAG, CLASS_NAME + ".receiveUdp() :: IOException receiving size " + io.getMessage()); + lostFramesPerSecond += 1; + return; } - }while(client != null && !client.isConnected()); - while(!done){ - switch(protocolState){ - case WAIT_FOR_START: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is WAIT_FOR_START."); - // If the app has not started the protocol then wait. - synchronized(protocolPauseMonitor){ - while(!protocolStarted){ - try{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Protocol has not started, waiting."); - protocolPauseMonitor.wait(); - }catch(InterruptedException ie){ } - } - } - break; + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Creating buffers."); + intSize = byteArray2Int(size); + data = new byte[intSize]; - case SEND_CONTINUE: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is SEND_CONTINUE."); - // Prepare the message. - controlMessage = new VideoStreamingControlMessage(); - if(!endProtocol){ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Preparing STREAM_CONTROL_END message."); - controlMessage.message = VideoStreamingProtocol.STREAM_CONTROL_END; - }else{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Preparing FLOW_CONTROL_CONTINUE message."); - controlMessage.message = VideoStreamingProtocol.FLOW_CONTROL_CONTINUE; - } + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Reading message from socket."); + try{ + packet = new DatagramPacket(data, data.length); + socket.receive(packet); + }catch(IOException io){ + Gdx.app.error(TAG, CLASS_NAME + ".receiveUdp() :: IOException receiving data " + io.getMessage()); + lostFramesPerSecond += 1; + return; + } - // Send it! - try{ - writer.writeObject(controlMessage); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error sending message: " + io.getMessage(), io); - }finally{ - protocolState = ProtocolState_t.RECEIVE_DATA; - } - break; + ByteArrayInputStream bais = new ByteArrayInputStream(data); - case RECEIVE_DATA: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is RECEIVE_DATA."); + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Saving message in monitor."); + try{ + ObjectInputStream ois = new ObjectInputStream(bais); + tmpMessage = ois.readObject(); - try{ - tmpMessage = reader.readObject(); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: IOException while receiving message: " + io.getMessage(), io); - break; - }catch(ClassNotFoundException cn){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: ClassNotFoundException while receiving message: " + cn.getMessage(), cn); - break; - } - - if(tmpMessage instanceof VideoStreamingControlMessage){ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Received a control message."); - controlMessage = (VideoStreamingControlMessage) tmpMessage; - // TODO: handle this case correctly. - - }else if(tmpMessage instanceof VideoFrameDataMessage){ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Received a data message."); + if(tmpMessage instanceof VideoFrameDataMessage){ + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Received a data message."); dataMessage = (VideoFrameDataMessage) tmpMessage; - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Received frame dimensions are: " + + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Received frame dimensions are: " + Integer.toString(dataMessage.imageWidth) + "x" + Integer.toString(dataMessage.imageHeight)); frameMonitor.setFrameDimensions(dataMessage.imageWidth, dataMessage.imageHeight); frameMonitor.setNewFrame(dataMessage.data); - if(pauseProtocol) - protocolState = ProtocolState_t.SEND_ACK_WAIT; - else - protocolState = ProtocolState_t.SEND_ACK_NEXT; - }else{ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Unrecognized message received!."); - // TODO: handle this case correctly. - System.exit(ProjectConstants.EXIT_FAILURE); + Gdx.app.debug(TAG, CLASS_NAME + ".receiveUdp() :: Received something unknown."); + lostFramesPerSecond += 1; } - - break; - - case SEND_ACK_NEXT: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is SEND_ACK_NEXT."); - // Prepare the message. - controlMessage = new VideoStreamingControlMessage(); - if(!endProtocol){ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Preparing STREAM_CONTROL_END message."); - controlMessage.message = VideoStreamingProtocol.STREAM_CONTROL_END; - }else{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Preparing ACK_SEND_NEXT message."); - controlMessage.message = VideoStreamingProtocol.ACK_SEND_NEXT; - } - - // Send it! - try{ - writer.writeObject(controlMessage); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error sending message: " + io.getMessage(), io); - }finally{ - if(!endProtocol) - protocolState = ProtocolState_t.RECEIVE_DATA; - else - protocolState = ProtocolState_t.END_STREAM; - } - break; - - case SEND_ACK_WAIT: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is SEND_ACK_WAIT."); - // Prepare the message. - controlMessage = new VideoStreamingControlMessage(); - controlMessage.message = VideoStreamingProtocol.ACK_WAIT; - - // Send it! - try{ - writer.writeObject(controlMessage); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error sending message: " + io.getMessage(), io); - }finally{ - protocolState = ProtocolState_t.PAUSED; - } - break; - - case PAUSED: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is PAUSED."); - // The app requested to stop the protocol temporarily. - synchronized(protocolPauseMonitor){ - while(pauseProtocol){ - try{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Protocol pause requested, waiting."); - protocolPauseMonitor.wait(); - }catch(InterruptedException ie){ } - } - } - protocolState = ProtocolState_t.SEND_CONTINUE; - break; - - case END_STREAM: - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: State is END_STREAM."); - // Simply disconnect from the client and end the thread. - try{ - client.close(); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error closing client: " + io.getMessage(), io); - } - done = true; - break; + }catch(IOException io){ + Gdx.app.error(TAG, CLASS_NAME + ".receiveUdp() :: IOException received deserializing message " + io.getMessage()); + lostFramesPerSecond += 1; + return; + }catch(ClassNotFoundException cn){ + Gdx.app.error(TAG, CLASS_NAME + ".receiveUdp() :: ClassNotFoundException received " + cn.getMessage()); + lostFramesPerSecond += 1; + return; } - } - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Thread finished."); - }*/ - - private void receiveImage(){ - Object tmpMessage; - VideoFrameDataMessage dataMessage; - - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Receiving data."); - - try{ - tmpMessage = (VideoFrameDataMessage)reader.readObject(); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: IOException while receiving message: " + io.getMessage()); - return; - }catch(ClassNotFoundException cn){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: ClassNotFoundException while receiving message: " + cn.getMessage()); + }catch(Exception e){ + Gdx.app.error(TAG, CLASS_NAME + ".receiveUdp() :: Exception received " + e.getMessage()); + lostFramesPerSecond += 1; return; } + } - if(tmpMessage instanceof VideoFrameDataMessage){ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Received a data message."); - dataMessage = (VideoFrameDataMessage) tmpMessage; - frameMonitor.setFrameDimensions(dataMessage.imageWidth, dataMessage.imageHeight); - frameMonitor.setNewFrame(dataMessage.data); - - }else{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Received something unknown."); - } + public int getFps(){ + return fps; + } + + public int getLostFrames(){ + return lostFrames; } @Override public void run(){ - // Listen on the server socket until a client successfully connects. - do{ - try{ - Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Listening for client."); - client = server.accept(); - if(netListener != null) - netListener.networkStreamConnected(THREAD_NAME); - writer = new ObjectOutputStream(client.getOutputStream()); - reader = new ObjectInputStream(client.getInputStream()); - toast("Client connected"); - }catch(IOException io){ - Gdx.app.error(TAG, CLASS_NAME + ".run() :: Error accepting client: " + io.getMessage(), io); - client = null; - } - }while(client != null && !client.isConnected()); + int frames = 0; + lostFrames = 0; + then = System.currentTimeMillis(); while(!done){ - receiveImage(); + synchronized (pauseMonitor) { + while(pause){ + try{ pauseMonitor.wait(); }catch(InterruptedException ie){ } + } + } + Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Receiving."); + if(netListener != null && !coreNotified && frameMonitor.getCurrentFrame() != null){ + coreNotified = true; + netListener.networkStreamConnected(THREAD_NAME); + } + receiveUdp(); + frames++; + now = System.currentTimeMillis(); + delta = now - then; + if(delta >= 1000){ + fps = frames; + frames = 0; + lostFrames = lostFramesPerSecond; + lostFramesPerSecond = 0; + then = now; + delta = 0; + } } try{ @@ -362,4 +246,16 @@ public class VideoStreamingThread extends Thread { Gdx.app.debug(TAG, CLASS_NAME + ".run() :: Thread finished."); } + public void pause(){ + synchronized (pauseMonitor){ + pause = true; + } + } + + public void play(){ + synchronized (pauseMonitor){ + pause = false; + pauseMonitor.notifyAll(); + } + } } diff --git a/src/ve/ucv/ciens/ccg/nxtar/network/monitors/MotorEventQueue.java b/src/ve/ucv/ciens/ccg/nxtar/network/monitors/MotorEventQueue.java new file mode 100644 index 0000000..0a72a8d --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtar/network/monitors/MotorEventQueue.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.network.monitors; + +import java.util.LinkedList; +import java.util.Queue; + +import ve.ucv.ciens.ccg.networkdata.MotorEvent; + +/** + *

A simple monitor class that encapsulates a queue.

+ *

As it name says it stores motor events to be forwarded to the NXT robot.

+ *

This class implements the singleton design pattern.

+ * + * @author Miguel Angel Astor Romero + */ +public class MotorEventQueue{ + /** + * The event queue implemented as a linked list. + */ + private Queue motorEvents; + + private MotorEventQueue(){ + motorEvents = new LinkedList(); + } + + private static class SingletonHolder{ + public static final MotorEventQueue instance = new MotorEventQueue(); + } + + /** + * Return the singleton instance of this class. + * @return The singleton instance. + */ + public static MotorEventQueue getInstance(){ + return SingletonHolder.instance; + } + + /** + *

Get the first event on the queue.

+ *

If there are no events to return this method blocks until some thread calls the addEvent() method.

+ * @return The event at the front of the queue. + */ + public synchronized MotorEvent getNextEvent(){ + while(motorEvents.size() == 0){ + try{ wait(); }catch(InterruptedException ie){ } + } + return motorEvents.poll(); + } + + /** + *

Adds an event to the back of the queue.

+ * @param event The event to add. + */ + public synchronized void addEvent(MotorEvent event){ + motorEvents.add(event); + notifyAll(); + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/network/VideoFrameMonitor.java b/src/ve/ucv/ciens/ccg/nxtar/network/monitors/VideoFrameMonitor.java similarity index 97% rename from src/ve/ucv/ciens/ccg/nxtar/network/VideoFrameMonitor.java rename to src/ve/ucv/ciens/ccg/nxtar/network/monitors/VideoFrameMonitor.java index 939cea0..a0902ec 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/network/VideoFrameMonitor.java +++ b/src/ve/ucv/ciens/ccg/nxtar/network/monitors/VideoFrameMonitor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ve.ucv.ciens.ccg.nxtar.network; +package ve.ucv.ciens.ccg.nxtar.network.monitors; import ve.ucv.ciens.ccg.nxtar.utils.Size; @@ -63,9 +63,9 @@ public class VideoFrameMonitor{ Gdx.app.debug(TAG, CLASS_NAME + ".setNewFrame() :: Loading new frame in frameA."); frameA = frame; + temp = frameA; synchronized(frameMonitor){ Gdx.app.debug(TAG, CLASS_NAME + ".setNewFrame() :: Swapping frameA and frameB."); - temp = frameA; frameA = frameB; frameB = temp; Gdx.app.debug(TAG, CLASS_NAME + ".setNewFrame() :: Swapping done."); diff --git a/src/ve/ucv/ciens/ccg/nxtar/network/protocols/VideoStreamingProtocol.java b/src/ve/ucv/ciens/ccg/nxtar/network/protocols/VideoStreamingProtocol.java deleted file mode 100644 index 183fdb0..0000000 --- a/src/ve/ucv/ciens/ccg/nxtar/network/protocols/VideoStreamingProtocol.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2013 Miguel Angel Astor Romero - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ve.ucv.ciens.ccg.nxtar.network.protocols; - -public final class VideoStreamingProtocol{ - public static final byte STREAM_CONTROL_END = 0x10; - public static final byte ACK_SEND_NEXT = 0x20; - public static final byte ACK_WAIT = 0x30; - public static final byte FLOW_CONTROL_WAIT = 0x40; - public static final byte FLOW_CONTROL_CONTINUE = 0x50; - public static final byte IMAGE_DATA = 0x60; - public static final byte UNRECOGNIZED = (byte)0xFF; - - public static boolean checkValidityOfMessage(byte message){ - boolean validity; - - switch(message){ - case STREAM_CONTROL_END: - case ACK_SEND_NEXT: - case ACK_WAIT: - case FLOW_CONTROL_WAIT: - case FLOW_CONTROL_CONTINUE: - case IMAGE_DATA: - case UNRECOGNIZED: - validity = true; - break; - default: - validity = false; - } - - return validity; - } -} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/BaseState.java b/src/ve/ucv/ciens/ccg/nxtar/states/BaseState.java new file mode 100644 index 0000000..731bce3 --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtar/states/BaseState.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.states; + +import ve.ucv.ciens.ccg.nxtar.NxtARCore; + +import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.controllers.Controller; +import com.badlogic.gdx.controllers.ControllerListener; +import com.badlogic.gdx.controllers.PovDirection; +import com.badlogic.gdx.math.Vector3; + +public abstract class BaseState implements Screen, ControllerListener, InputProcessor { + protected NxtARCore core; + protected boolean stateActive; + + /* STATE METHODS */ + public abstract void onStateSet(); + public abstract void onStateUnset(); + + /* SCREEN METHODS*/ + @Override + public abstract void render(float delta); + @Override + public abstract void resize(int width, int height); + @Override + public abstract void show(); + @Override + public abstract void hide(); + @Override + public abstract void pause(); + @Override + public abstract void resume(); + @Override + public abstract void dispose(); + + /* INPUT PROCESSOR METHODS. */ + @Override + public abstract boolean keyDown(int keycode); + @Override + public abstract boolean keyUp(int keycode); + @Override + public abstract boolean keyTyped(char character); + @Override + public abstract boolean touchDown(int screenX, int screenY, int pointer, int button); + @Override + public abstract boolean touchUp(int screenX, int screenY, int pointer, int button); + @Override + public abstract boolean touchDragged(int screenX, int screenY, int pointer); + @Override + public abstract boolean mouseMoved(int screenX, int screenY); + @Override + public abstract boolean scrolled(int amount); + + /* CONTROLLER LISTENER METHODS. */ + @Override + public abstract void connected(Controller controller); + @Override + public abstract void disconnected(Controller controller); + @Override + public abstract boolean buttonDown(Controller controller, int buttonCode); + @Override + public abstract boolean buttonUp(Controller controller, int buttonCode); + @Override + public abstract boolean axisMoved(Controller controller, int axisCode, float value); + @Override + public abstract boolean povMoved(Controller controller, int povCode, PovDirection value); + @Override + public abstract boolean xSliderMoved(Controller controller, int sliderCode, boolean value); + @Override + public abstract boolean ySliderMoved(Controller controller, int sliderCode, boolean value); + @Override + public abstract boolean accelerometerMoved(Controller controller, int accelerometerCode, Vector3 value); +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/InGameState.java b/src/ve/ucv/ciens/ccg/nxtar/states/InGameState.java index 0e94ede..dfd5ea2 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/states/InGameState.java +++ b/src/ve/ucv/ciens/ccg/nxtar/states/InGameState.java @@ -1,56 +1,795 @@ -/* - * Copyright (C) 2013 Miguel Angel Astor Romero - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ve.ucv.ciens.ccg.nxtar.states; - -public class InGameState implements NxtARState{ - - @Override - public void input() { - // TODO Auto-generated method stub - - } - - @Override - public void update() { - // TODO Auto-generated method stub - - } - - @Override - public void render() { - // TODO Auto-generated method stub - - } - - @Override - public void pause() { - // TODO Auto-generated method stub - - } - - @Override - public void resume() { - // TODO Auto-generated method stub - - } - - @Override - public void dispose() { - // TODO Auto-generated method stub - - } - -} +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.states; + +import java.util.Arrays; + +import ve.ucv.ciens.ccg.networkdata.MotorEvent; +import ve.ucv.ciens.ccg.networkdata.MotorEvent.motor_t; +import ve.ucv.ciens.ccg.nxtar.NxtARCore; +import ve.ucv.ciens.ccg.nxtar.NxtARCore.game_states_t; +import ve.ucv.ciens.ccg.nxtar.network.monitors.MotorEventQueue; +import ve.ucv.ciens.ccg.nxtar.network.monitors.VideoFrameMonitor; +import ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants; +import ve.ucv.ciens.ccg.nxtar.utils.Size; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.controllers.Controller; +import com.badlogic.gdx.controllers.PovDirection; +import com.badlogic.gdx.controllers.mappings.Ouya; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.Texture.TextureWrap; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; + +public class InGameState extends BaseState{ + private static final String TAG = "IN_GAME_STATE"; + private static final String CLASS_NAME = InGameState.class.getSimpleName(); + + private static final String SHADER_PATH = "shaders/bckg/bckg"; + + private NxtARCore core; + + private float u_scaling[]; + protected Sprite background; + private Texture backgroundTexture; + private ShaderProgram backgroundShader; + + // Cameras. + private OrthographicCamera camera; + private OrthographicCamera pixelPerfectCamera; + + // Video stream graphics. + private Texture videoFrameTexture; + private Sprite renderableVideoFrame; + private Pixmap videoFrame; + + // Interface buttons. + private Texture buttonTexture; + private Texture buttonTexture2; + private Sprite motorA; + private Sprite motorB; + private Sprite motorC; + private Sprite motorD; + private Sprite headA; + private Sprite headB; + + // Button touch helper fields. + private Vector3 win2world; + private Vector2 touchPointWorldCoords; + private boolean[] motorButtonsTouched; + private int[] motorButtonsPointers; + private boolean[] motorGamepadButtonPressed; + private boolean[] axisStopSent; + + // Monitors. + private VideoFrameMonitor frameMonitor; + private MotorEventQueue queue; + + public InGameState(final NxtARCore core){ + this.core = core; + frameMonitor = VideoFrameMonitor.getInstance(); + queue = MotorEventQueue.getInstance(); + + // Set up rendering fields; + videoFrame = null; + + // Set up the cameras. + pixelPerfectCamera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + camera = new OrthographicCamera(1.0f, Gdx.graphics.getHeight() / Gdx.graphics.getWidth()); + + if(!Ouya.runningOnOuya) setUpButtons(); + + // Set up input handling support fields. + win2world = new Vector3(0.0f, 0.0f, 0.0f); + touchPointWorldCoords = new Vector2(); + + motorButtonsTouched = new boolean[6]; + motorButtonsTouched[0] = false; + motorButtonsTouched[1] = false; + motorButtonsTouched[2] = false; + motorButtonsTouched[3] = false; + motorButtonsTouched[4] = false; + motorButtonsTouched[5] = false; + + motorButtonsPointers = new int[6]; + motorButtonsPointers[0] = -1; + motorButtonsPointers[1] = -1; + motorButtonsPointers[2] = -1; + motorButtonsPointers[3] = -1; + motorButtonsPointers[4] = -1; + motorButtonsPointers[5] = -1; + + motorGamepadButtonPressed = new boolean[2]; + motorGamepadButtonPressed[0] = false; + motorGamepadButtonPressed[1] = false; + + axisStopSent = new boolean[4]; + axisStopSent[0] = true; + axisStopSent[1] = true; + axisStopSent[2] = true; + axisStopSent[3] = true; + + backgroundTexture = new Texture(Gdx.files.internal("data/gfx/textures/tile_aqua.png")); + backgroundTexture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); + backgroundTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + background = new Sprite(backgroundTexture); + background.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + background.setPosition(-(Gdx.graphics.getWidth() / 2), -(Gdx.graphics.getHeight() / 2)); + + backgroundShader = new ShaderProgram(Gdx.files.internal(SHADER_PATH + ".vert"), Gdx.files.internal(SHADER_PATH + ".frag")); + if(!backgroundShader.isCompiled()){ + Gdx.app.error(TAG, CLASS_NAME + ".MainMenuStateBase() :: Failed to compile the background shader."); + Gdx.app.error(TAG, CLASS_NAME + backgroundShader.getLog()); + backgroundShader = null; + } + + u_scaling = new float[2]; + u_scaling[0] = Gdx.graphics.getWidth() > Gdx.graphics.getHeight() ? 16.0f : 9.0f; + u_scaling[1] = Gdx.graphics.getHeight() > Gdx.graphics.getWidth() ? 16.0f : 9.0f; + } + + @Override + public void render(float delta){ + byte[] frame; + byte[] prevFrame = null; + Size dimensions = null; + + Gdx.gl.glClearColor(1, 1, 1, 1); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + + core.batch.setProjectionMatrix(pixelPerfectCamera.combined); + core.batch.begin();{ + if(backgroundShader != null){ + core.batch.setShader(backgroundShader); + backgroundShader.setUniform2fv("u_scaling", u_scaling, 0, 2); + } + background.draw(core.batch); + if(backgroundShader != null) core.batch.setShader(null); + }core.batch.end(); + + frame = frameMonitor.getCurrentFrame(); + if(frame != null && !Arrays.equals(frame, prevFrame)){ + dimensions = frameMonitor.getFrameDimensions(); + videoFrame = new Pixmap(frame, 0, dimensions.getWidth() * dimensions.getHeight()); + videoFrameTexture = new Texture(videoFrame); + videoFrameTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + videoFrame.dispose(); + + TextureRegion region = new TextureRegion(videoFrameTexture, 0, 0, dimensions.getWidth(), dimensions.getHeight()); + + renderableVideoFrame = new Sprite(region); + renderableVideoFrame.setOrigin(renderableVideoFrame.getWidth() / 2, renderableVideoFrame.getHeight() / 2); + if(!Ouya.runningOnOuya){ + renderableVideoFrame.setSize(1.0f, renderableVideoFrame.getHeight() / renderableVideoFrame.getWidth() ); + renderableVideoFrame.rotate90(true); + renderableVideoFrame.translate(-renderableVideoFrame.getWidth() / 2, 0.5f - renderableVideoFrame.getHeight()); + }else{ + float xSize = Gdx.graphics.getHeight() * (dimensions.getWidth() / dimensions.getHeight()); + renderableVideoFrame.setSize(xSize * ProjectConstants.OVERSCAN, Gdx.graphics.getHeight() * ProjectConstants.OVERSCAN); + renderableVideoFrame.rotate90(true); + renderableVideoFrame.translate(-renderableVideoFrame.getWidth() / 2, -renderableVideoFrame.getHeight() / 2); + } + + if(!Ouya.runningOnOuya){ + core.batch.setProjectionMatrix(camera.combined); + }else{ + core.batch.setProjectionMatrix(pixelPerfectCamera.combined); + } + core.batch.begin();{ + renderableVideoFrame.draw(core.batch); + }core.batch.end(); + + videoFrameTexture.dispose(); + } + + core.batch.setProjectionMatrix(pixelPerfectCamera.combined); + core.batch.begin();{ + if(!Ouya.runningOnOuya){ + motorA.draw(core.batch); + motorB.draw(core.batch); + motorC.draw(core.batch); + motorD.draw(core.batch); + headA.draw(core.batch); + headB.draw(core.batch); + } + }core.batch.end(); + + prevFrame = frame; + } + + @Override + public void resize(int width, int height){ + // TODO Auto-generated method stub + + } + + @Override + public void show(){ } + + @Override + public void hide(){ } + + @Override + public void pause(){ } + + @Override + public void resume(){ } + + @Override + public void dispose(){ + if(videoFrameTexture != null) + videoFrameTexture.dispose(); + if(buttonTexture != null) + buttonTexture.dispose(); + if(buttonTexture2 != null) + buttonTexture2.dispose(); + backgroundTexture.dispose(); + if(backgroundShader != null) backgroundShader.dispose(); + } + + /*;;;;;;;;;;;;;;;;;; + ; HELPER METHODS ; + ;;;;;;;;;;;;;;;;;;*/ + + @Override + public void onStateSet(){ + stateActive = true; + Gdx.input.setInputProcessor(this); + Gdx.input.setCatchBackKey(true); + Gdx.input.setCatchMenuKey(true); + } + + @Override + public void onStateUnset(){ + stateActive = false; + Gdx.input.setInputProcessor(null); + Gdx.input.setCatchBackKey(false); + Gdx.input.setCatchMenuKey(false); + } + + private void setUpButtons(){ + buttonTexture = new Texture(Gdx.files.internal("data/gfx/gui/PBCrichton_Flat_Button.png")); + buttonTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + + TextureRegion region = new TextureRegion(buttonTexture, 0, 0, buttonTexture.getWidth(), buttonTexture.getHeight()); + + motorA = new Sprite(region); + motorA.setSize(motorA.getWidth() * 0.7f, motorA.getHeight() * 0.7f); + + motorB = new Sprite(region); + motorB.setSize(motorB.getWidth() * 0.7f, motorB.getHeight() * 0.7f); + + motorC = new Sprite(region); + motorC.setSize(motorC.getWidth() * 0.7f, motorC.getHeight() * 0.7f); + + motorD = new Sprite(region); + motorD.setSize(motorD.getWidth() * 0.7f, motorD.getHeight() * 0.7f); + + motorA.setPosition(-(Gdx.graphics.getWidth() / 2) + 10, -(Gdx.graphics.getHeight() / 2) + motorB.getHeight() + 20); + motorB.setPosition(-(Gdx.graphics.getWidth() / 2) + 20 + (motorA.getWidth() / 2), -(Gdx.graphics.getHeight() / 2) + 10); + motorC.setPosition((Gdx.graphics.getWidth() / 2) - (1.5f * (motorD.getWidth())) - 20, -(Gdx.graphics.getHeight() / 2) + 10); + motorD.setPosition((Gdx.graphics.getWidth() / 2) - motorD.getWidth() - 10, -(Gdx.graphics.getHeight() / 2) + 20 + motorC.getHeight()); + + buttonTexture2 = new Texture(Gdx.files.internal("data/gfx/gui/orange_glowy_button.png")); + + headA = new Sprite(buttonTexture2); + headA.setSize(headA.getWidth() * 0.3f, headA.getHeight() * 0.6f); + + headB = new Sprite(buttonTexture2); + headB.setSize(headB.getWidth() * 0.3f, headB.getHeight() * 0.6f); + + headA.setPosition(-headA.getWidth() - 10, motorA.getY() + (headA.getHeight() / 2)); + headB.setPosition(10, motorA.getY() + (headA.getHeight() / 2)); + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; BEGIN INPUT PROCESSOR METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button){ + MotorEvent event; + + if(!Ouya.runningOnOuya){ + win2world.set(screenX, screenY, 0.0f); + camera.unproject(win2world); + touchPointWorldCoords.set(win2world.x * Gdx.graphics.getWidth(), win2world.y * Gdx.graphics.getHeight()); + + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchDown(%d, %d, %d, %d)", screenX, screenY, pointer, button)); + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchDown() :: Unprojected touch point: (%f, %f)", touchPointWorldCoords.x, touchPointWorldCoords.y)); + + if(motorA.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Motor A button pressed"); + + motorButtonsTouched[0] = true; + motorButtonsPointers[0] = pointer; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte)100); + queue.addEvent(event); + + }else if(motorB.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Motor B button pressed"); + + + motorButtonsTouched[1] = true; + motorButtonsPointers[1] = pointer; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte)-100); + queue.addEvent(event); + + }else if(motorC.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Motor C button pressed"); + + motorButtonsTouched[2] = true; + motorButtonsPointers[2] = pointer; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte)-100); + queue.addEvent(event); + + }else if(motorD.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Motor D button pressed"); + + motorButtonsTouched[3] = true; + motorButtonsPointers[3] = pointer; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte)100); + queue.addEvent(event); + + }else if(headA.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Head A button pressed"); + + motorButtonsTouched[4] = true; + motorButtonsPointers[4] = pointer; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte)-40); + queue.addEvent(event); + + }else if(headB.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Head B button pressed"); + + motorButtonsTouched[5] = true; + motorButtonsPointers[5] = pointer; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte)40); + queue.addEvent(event); + + } + } + return true; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button){ + MotorEvent event; + + if(!Ouya.runningOnOuya){ + win2world.set(screenX, screenY, 0.0f); + camera.unproject(win2world); + touchPointWorldCoords.set(win2world.x * Gdx.graphics.getWidth(), win2world.y * Gdx.graphics.getHeight()); + + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchUp(%d, %d, %d, %d)", screenX, screenY, pointer, button)); + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchUp() :: Unprojected touch point: (%f, %f)", touchPointWorldCoords.x, touchPointWorldCoords.y)); + + if(motorA.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Motor A button released"); + + motorButtonsPointers[0] = -1; + motorButtonsTouched[0] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[1]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(motorB.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Motor B button released"); + + motorButtonsPointers[1] = -1; + motorButtonsTouched[1] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[0]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(motorC.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Motor C button released"); + + motorButtonsPointers[2] = -1; + motorButtonsTouched[2] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[3]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(motorD.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Motor D button released"); + + motorButtonsPointers[3] = -1; + motorButtonsTouched[3] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[2]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(headA.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Head A button released"); + + motorButtonsPointers[4] = -1; + motorButtonsTouched[4] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[5]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(headB.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Head B button released"); + + motorButtonsPointers[5] = -1; + motorButtonsTouched[5] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[4]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte) 0); + queue.addEvent(event); + } + + } + } + return true; + } + + @Override + public boolean touchDragged(int screenX, int screenY, int pointer){ + MotorEvent event; + + if(!Ouya.runningOnOuya){ + win2world.set(screenX, screenY, 0.0f); + camera.unproject(win2world); + touchPointWorldCoords.set(win2world.x * Gdx.graphics.getWidth(), win2world.y * Gdx.graphics.getHeight()); + + if(pointer == motorButtonsPointers[0] && !motorA.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDragged() :: Motor A button released"); + + motorButtonsPointers[0] = -1; + motorButtonsTouched[0] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[1]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(pointer == motorButtonsPointers[1] && !motorB.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDragged() :: Motor B button released"); + + motorButtonsPointers[1] = -1; + motorButtonsTouched[1] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[0]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(pointer == motorButtonsPointers[2] && !motorC.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDragged() :: Motor C button released"); + + motorButtonsPointers[2] = -1; + motorButtonsTouched[2] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[3]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(pointer == motorButtonsPointers[3] && !motorD.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchDragged() :: Motor D button released"); + + motorButtonsPointers[3] = -1; + motorButtonsTouched[3] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[2]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(pointer == motorButtonsPointers[4] && headA.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Head A button released"); + + motorButtonsPointers[4] = -1; + motorButtonsTouched[4] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[5]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte) 0); + queue.addEvent(event); + } + + }else if(pointer == motorButtonsPointers[5] && headB.getBoundingRectangle().contains(touchPointWorldCoords)){ + Gdx.app.log(TAG, CLASS_NAME + ".touchUp() :: Head B button released"); + + motorButtonsPointers[5] = -1; + motorButtonsTouched[5] = false; + + // Enqueue the event corresponding to releasing this button if the opposing button is not pressed already. + if(!motorButtonsTouched[4]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte) 0); + queue.addEvent(event); + } + + } + } + return true; + } + + @Override + public boolean keyDown(int keycode){ + if(keycode == Input.Keys.BACK){ + // TODO: Go to pause state. + core.nextState = game_states_t.MAIN_MENU; + return true; + } + return false; + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; BEGIN CONTROLLER LISTENER METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean buttonDown(Controller controller, int buttonCode){ + MotorEvent event; + + if(stateActive && Ouya.runningOnOuya){ + Gdx.app.log(TAG, CLASS_NAME + ".buttonDown() :: " + controller.getName() + " :: " + Integer.toString(buttonCode)); + + if(buttonCode == Ouya.BUTTON_L1){ + motorGamepadButtonPressed[0] = true; + + if(!motorGamepadButtonPressed[1]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte)100); + queue.addEvent(event); + } + + }else if(buttonCode == Ouya.BUTTON_R1){ + motorGamepadButtonPressed[1] = true; + + if(!motorGamepadButtonPressed[0]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte)-100); + queue.addEvent(event); + } + + } + + return true; + }else{ + return false; + } + } + + @Override + public boolean buttonUp(Controller controller, int buttonCode){ + MotorEvent event; + + if(stateActive && Ouya.runningOnOuya){ + Gdx.app.log(TAG, CLASS_NAME + ".buttonDown() :: " + controller.getName() + " :: " + Integer.toString(buttonCode)); + + if(buttonCode == Ouya.BUTTON_L1){ + motorGamepadButtonPressed[0] = false; + + if(!motorGamepadButtonPressed[1]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte)0); + queue.addEvent(event); + } + + }else if(buttonCode == Ouya.BUTTON_R1){ + motorGamepadButtonPressed[1] = false; + + if(!motorGamepadButtonPressed[0]){ + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_B); + event.setPower((byte)0); + queue.addEvent(event); + } + + } + + return true; + }else{ + return false; + } + } + + @Override + public boolean axisMoved(Controller controller, int axisCode, float value){ + MotorEvent event; + + if(stateActive && Ouya.runningOnOuya){ + if(Math.abs(value) >= Ouya.STICK_DEADZONE * 3.0f){ + + if(axisCode == Ouya.AXIS_LEFT_Y){ + Gdx.app.log(TAG, CLASS_NAME + ".axisMoved() :: LEFT Y moved: "+ Float.toString(value)); + + axisStopSent[0] = false; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte)(100.0f * -value)); + queue.addEvent(event); + + }else if(axisCode == Ouya.AXIS_RIGHT_Y){ + Gdx.app.log(TAG, CLASS_NAME + ".axisMoved() :: RIGHT Y moved: "+ Float.toString(value)); + + axisStopSent[1] = false; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte)(100.0f * -value)); + queue.addEvent(event); + + } + + }else{ + + if(axisCode == Ouya.AXIS_LEFT_Y && !axisStopSent[0]){ + + axisStopSent[0] = true; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_A); + event.setPower((byte)0); + queue.addEvent(event); + + }else if(axisCode == Ouya.AXIS_RIGHT_Y && !axisStopSent[1]){ + + axisStopSent[1] = true; + + event = new MotorEvent(); + event.setMotor(motor_t.MOTOR_C); + event.setPower((byte)0); + queue.addEvent(event); + + } + } + return true; + }else{ + return false; + } + } + + @Override + public void connected(Controller controller){ } + + @Override + public void disconnected(Controller controller){ } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; UNUSED LISTENER METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean mouseMoved(int screenX, int screenY){ + return false; + } + + @Override + public boolean keyUp(int keycode){ + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyTyped(char character){ + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean povMoved(Controller controller, int povCode, + PovDirection value){ + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean xSliderMoved(Controller controller, int sliderCode, + boolean value){ + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ySliderMoved(Controller controller, int sliderCode, + boolean value){ + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean accelerometerMoved(Controller controller, + int accelerometerCode, Vector3 value){ + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean scrolled(int amount){ + // TODO Auto-generated method stub + return false; + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/MainMenuStateBase.java b/src/ve/ucv/ciens/ccg/nxtar/states/MainMenuStateBase.java new file mode 100644 index 0000000..13b5af2 --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtar/states/MainMenuStateBase.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.states; + +import ve.ucv.ciens.ccg.nxtar.NxtARCore.game_states_t; +import ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.controllers.Controller; +import com.badlogic.gdx.controllers.PovDirection; +import com.badlogic.gdx.controllers.mappings.Ouya; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.Texture.TextureWrap; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.NinePatch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle; +import com.badlogic.gdx.scenes.scene2d.utils.NinePatchDrawable; + + +public abstract class MainMenuStateBase extends BaseState{ + protected static final String TAG = "MAIN_MENU"; + private static final String CLASS_NAME = MainMenuStateBase.class.getSimpleName(); + + private static final String SHADER_PATH = "shaders/bckg/bckg"; + + // Helper fields. + protected boolean clientConnected; + private float u_scaling[]; + protected OrthographicCamera pixelPerfectCamera; + + // Buttons and other gui components. + protected TextButton startButton; + protected Rectangle startButtonBBox; + protected Sprite clientConnectedLedOn; + protected Sprite clientConnectedLedOff; + protected Sprite background; + + // Graphic data for the start button. + private Texture startButtonEnabledTexture; + private Texture startButtonDisabledTexture; + private Texture startButtonPressedTexture; + private NinePatch startButtonEnabled9p; + private NinePatch startButtonDisabled9p; + private NinePatch startButtonPressed9p; + private BitmapFont font; + + // Other graphics. + private Texture clientConnectedLedOffTexture; + private Texture clientConnectedLedOnTexture; + private Texture backgroundTexture; + private ShaderProgram backgroundShader; + + // Button touch helper fields. + private Vector3 win2world; + protected Vector2 touchPointWorldCoords; + protected boolean startButtonTouched; + protected int startButtonTouchPointer; + + public MainMenuStateBase(){ + TextureRegion region; + + this.pixelPerfectCamera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + + // Create the start button background. + startButtonEnabledTexture = new Texture(Gdx.files.internal("data/gfx/gui/Anonymous_Pill_Button_Yellow.png")); + startButtonEnabled9p = new NinePatch(new TextureRegion(startButtonEnabledTexture, 0, 0, startButtonEnabledTexture.getWidth(), startButtonEnabledTexture.getHeight()), 49, 49, 45, 45); + + startButtonDisabledTexture = new Texture(Gdx.files.internal("data/gfx/gui/Anonymous_Pill_Button_Cyan.png")); + startButtonDisabled9p = new NinePatch(new TextureRegion(startButtonDisabledTexture, 0, 0, startButtonDisabledTexture.getWidth(), startButtonDisabledTexture.getHeight()), 49, 49, 45, 45); + + startButtonPressedTexture = new Texture(Gdx.files.internal("data/gfx/gui/Anonymous_Pill_Button_Blue.png")); + startButtonPressed9p = new NinePatch(new TextureRegion(startButtonPressedTexture, 0, 0, startButtonPressedTexture.getWidth(), startButtonPressedTexture.getHeight()), 49, 49, 45, 45); + + // Create the start button font. + FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("data/fonts/d-puntillas-B-to-tiptoe.ttf")); + font = generator.generateFont(Ouya.runningOnOuya ? 60 : 40, ProjectConstants.FONT_CHARS, false); + generator.dispose(); + + // Create the start button itself. + TextButtonStyle tbs = new TextButtonStyle(); + tbs.font = font; + tbs.up = new NinePatchDrawable(startButtonEnabled9p); + tbs.checked = new NinePatchDrawable(startButtonPressed9p); + tbs.disabled = new NinePatchDrawable(startButtonDisabled9p); + tbs.disabledFontColor = new Color(0, 0, 0, 1); + startButton = new TextButton("Start server", tbs); + startButton.setText("Start game"); + startButton.setDisabled(true); + startButtonBBox = new Rectangle(0, 0, startButton.getWidth(), startButton.getHeight()); + + // Create the connection leds. + clientConnectedLedOnTexture = new Texture("data/gfx/gui/Anonymous_Button_Green.png"); + region = new TextureRegion(clientConnectedLedOnTexture); + clientConnectedLedOn = new Sprite(region); + + clientConnectedLedOffTexture = new Texture("data/gfx/gui/Anonymous_Button_Red.png"); + region = new TextureRegion(clientConnectedLedOffTexture); + clientConnectedLedOff = new Sprite(region); + + // Set up the background. + backgroundTexture = new Texture(Gdx.files.internal("data/gfx/textures/tile_aqua.png")); + backgroundTexture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); + backgroundTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + region = new TextureRegion(backgroundTexture); + background = new Sprite(backgroundTexture); + background.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + background.setPosition(-(Gdx.graphics.getWidth() / 2), -(Gdx.graphics.getHeight() / 2)); + + backgroundShader = new ShaderProgram(Gdx.files.internal(SHADER_PATH + ".vert"), Gdx.files.internal(SHADER_PATH + ".frag")); + if(!backgroundShader.isCompiled()){ + Gdx.app.error(TAG, CLASS_NAME + ".MainMenuStateBase() :: Failed to compile the background shader."); + Gdx.app.error(TAG, CLASS_NAME + backgroundShader.getLog()); + backgroundShader = null; + } + + u_scaling = new float[2]; + u_scaling[0] = Gdx.graphics.getWidth() > Gdx.graphics.getHeight() ? 16.0f : 9.0f; + u_scaling[1] = Gdx.graphics.getHeight() > Gdx.graphics.getWidth() ? 16.0f : 9.0f; + + + win2world = new Vector3(0.0f, 0.0f, 0.0f); + touchPointWorldCoords = new Vector2(); + startButtonTouched = false; + startButtonTouchPointer = -1; + + clientConnected = false; + stateActive = false; + } + + @Override + public abstract void render(float delta); + + @Override + public void resize(int width, int height){ } + + @Override + public void show(){ } + @Override + public void hide(){ } + + @Override + public void pause(){ } + + @Override + public void resume(){ } + + @Override + public void dispose(){ + startButtonEnabledTexture.dispose(); + startButtonDisabledTexture.dispose(); + startButtonPressedTexture.dispose(); + clientConnectedLedOnTexture.dispose(); + clientConnectedLedOffTexture.dispose(); + backgroundTexture.dispose(); + if(backgroundShader != null) backgroundShader.dispose(); + font.dispose(); + } + + protected void drawBackground(SpriteBatch batch){ + if(backgroundShader != null){ + batch.setShader(backgroundShader); + backgroundShader.setUniform2fv("u_scaling", u_scaling, 0, 2); + } + background.draw(batch); + if(backgroundShader != null) batch.setShader(null); + } + + @Override + public void onStateSet(){ + stateActive = true; + Gdx.input.setInputProcessor(this); + Gdx.input.setCatchBackKey(true); + Gdx.input.setCatchMenuKey(true); + } + + @Override + public void onStateUnset(){ + stateActive = false; + Gdx.input.setInputProcessor(null); + Gdx.input.setCatchBackKey(false); + Gdx.input.setCatchMenuKey(false); + } + + public void onClientConnected(){ + clientConnected = true; + startButton.setDisabled(false); + } + + /*;;;;;;;;;;;;;;;;;; + ; HELPER METHODS ; + ;;;;;;;;;;;;;;;;;;*/ + + protected void unprojectTouch(int screenX, int screenY){ + win2world.set(screenX, screenY, 0.0f); + pixelPerfectCamera.unproject(win2world); + touchPointWorldCoords.set(win2world.x, win2world.y); + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;; + ; INPUT LISTENER METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button){ + unprojectTouch(screenX, screenY); + + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchDown(%d, %d, %d, %d)", screenX, screenY, pointer, button)); + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchDown() :: Unprojected touch point: (%f, %f)", touchPointWorldCoords.x, touchPointWorldCoords.y)); + + if(!startButton.isDisabled() && startButtonBBox.contains(touchPointWorldCoords)){ + startButton.setChecked(true); + startButtonTouched = true; + startButtonTouchPointer = pointer; + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Start button pressed."); + } + + return true; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button){ + unprojectTouch(screenX, screenY); + + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchUp(%d, %d, %d, %d)", screenX, screenY, pointer, button)); + Gdx.app.log(TAG, CLASS_NAME + String.format(".touchUp() :: Unprojected touch point: (%f, %f)", touchPointWorldCoords.x, touchPointWorldCoords.y)); + + if(!startButton.isDisabled() && startButtonBBox.contains(touchPointWorldCoords)){ + startButton.setChecked(false); + startButtonTouched = false; + startButtonTouchPointer = -1; + core.nextState = game_states_t.IN_GAME; + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Start button released."); + } + + return true; + } + + @Override + public boolean touchDragged(int screenX, int screenY, int pointer){ + unprojectTouch(screenX, screenY); + + if(!startButton.isDisabled() && startButtonTouched && pointer == startButtonTouchPointer && !startButtonBBox.contains(touchPointWorldCoords)){ + startButtonTouchPointer = -1; + startButtonTouched = false; + startButton.setChecked(false); + Gdx.app.log(TAG, CLASS_NAME + ".touchDragged() :: Start button released."); + } + + return true; + } + + @Override + public boolean keyDown(int keycode){ + if(keycode == Input.Keys.BACK){ + // Ignore. + return true; + } + return false; + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; INPUT LISTENER METHOD STUBS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean keyUp(int keycode){ + return false; + } + + @Override + public boolean keyTyped(char character){ + return false; + } + + @Override + public boolean mouseMoved(int screenX, int screenY){ + return false; + } + + @Override + public boolean scrolled(int amount){ + return false; + } + + @Override + public void connected(Controller controller){ } + + @Override + public void disconnected(Controller controller){ } + + @Override + public boolean buttonDown(Controller controller, int buttonCode){ + return false; + } + + @Override + public boolean buttonUp(Controller controller, int buttonCode){ + return false; + } + + @Override + public boolean axisMoved(Controller controller, int axisCode, float value){ + return false; + } + + @Override + public boolean povMoved(Controller controller, int povCode, PovDirection value){ + return false; + } + + @Override + public boolean xSliderMoved(Controller controller, int sliderCode, boolean value){ + return false; + } + + @Override + public boolean ySliderMoved(Controller controller, int sliderCode, boolean value){ + return false; + } + + @Override + public boolean accelerometerMoved(Controller controller, int accelerometerCode, Vector3 value){ + return false; + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/OuyaMainMenuState.java b/src/ve/ucv/ciens/ccg/nxtar/states/OuyaMainMenuState.java new file mode 100644 index 0000000..ac1be8a --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtar/states/OuyaMainMenuState.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.states; + +import ve.ucv.ciens.ccg.nxtar.NxtARCore; +import ve.ucv.ciens.ccg.nxtar.NxtARCore.game_states_t; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.controllers.Controller; +import com.badlogic.gdx.controllers.mappings.Ouya; +import com.badlogic.gdx.graphics.GL10; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureRegion; + +public class OuyaMainMenuState extends MainMenuStateBase{ + private static final String CLASS_NAME = OuyaMainMenuState.class.getSimpleName(); + + private Texture ouyaOButtonTexture; + private Sprite ouyaOButton; + private boolean oButtonPressed; + + public OuyaMainMenuState(final NxtARCore core){ + this.core = core; + + startButton.setPosition(-(startButton.getWidth() / 2), -(startButton.getHeight() / 2)); + startButtonBBox.setPosition(startButton.getX(), startButton.getY()); + + float ledYPos = (-(Gdx.graphics.getHeight() / 2) * 0.5f) + (startButton.getY() * 0.5f); + clientConnectedLedOn.setSize(clientConnectedLedOn.getWidth() * 0.5f, clientConnectedLedOn.getHeight() * 0.5f); + clientConnectedLedOn.setPosition(-(clientConnectedLedOn.getWidth() / 2), ledYPos); + + clientConnectedLedOff.setSize(clientConnectedLedOff.getWidth() * 0.5f, clientConnectedLedOff.getHeight() * 0.5f); + clientConnectedLedOff.setPosition(-(clientConnectedLedOff.getWidth() / 2), ledYPos); + + ouyaOButtonTexture = new Texture("data/gfx/gui/OUYA_O.png"); + TextureRegion region = new TextureRegion(ouyaOButtonTexture, ouyaOButtonTexture.getWidth(), ouyaOButtonTexture.getHeight()); + ouyaOButton = new Sprite(region); + ouyaOButton.setSize(ouyaOButton.getWidth() * 0.6f, ouyaOButton.getHeight() * 0.6f); + ouyaOButton.setPosition(startButton.getX() - ouyaOButton.getWidth() - 20, startButton.getY() + (ouyaOButton.getHeight() / 2)); + + oButtonPressed = false; + } + + @Override + public void render(float delta) { + Gdx.gl.glClearColor(1, 1, 1, 1); + Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); + + core.batch.setProjectionMatrix(pixelPerfectCamera.combined); + core.batch.begin();{ + core.batch.disableBlending(); + drawBackground(core.batch); + core.batch.enableBlending(); + if(clientConnected){ + clientConnectedLedOn.draw(core.batch); + }else{ + clientConnectedLedOff.draw(core.batch); + } + startButton.draw(core.batch, 1.0f); + ouyaOButton.draw(core.batch); + }core.batch.end(); + } + + @Override + public void dispose(){ + super.dispose(); + ouyaOButtonTexture.dispose(); + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; BEGIN CONTROLLER LISTENER METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean buttonDown(Controller controller, int buttonCode) { + if(stateActive){ + if(buttonCode == Ouya.BUTTON_O){ + if(!clientConnected){ + core.toast("Can't start the game. No client is connected.", true); + }else{ + oButtonPressed = true; + startButton.setChecked(true); + } + } + + return true; + + }else{ + return false; + } + } + + @Override + public boolean buttonUp(Controller controller, int buttonCode) { + if(stateActive){ + if(buttonCode == Ouya.BUTTON_O){ + if(oButtonPressed){ + oButtonPressed = false; + startButton.setChecked(false); + core.nextState = game_states_t.IN_GAME; + Gdx.app.log(TAG, CLASS_NAME + ".touchDown() :: Start button released."); + } + } + + return true; + + }else{ + return false; + } + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/PauseState.java b/src/ve/ucv/ciens/ccg/nxtar/states/PauseState.java new file mode 100644 index 0000000..a7c51bb --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtar/states/PauseState.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.states; + +import ve.ucv.ciens.ccg.nxtar.NxtARCore; + +import com.badlogic.gdx.controllers.Controller; +import com.badlogic.gdx.controllers.PovDirection; +import com.badlogic.gdx.math.Vector3; + +public class PauseState extends BaseState { + + public PauseState(final NxtARCore core){ + this.core = core; + } + + @Override + public void render(float delta) { + // TODO Auto-generated method stub + + } + + @Override + public void resize(int width, int height) { + // TODO Auto-generated method stub + + } + + @Override + public void show() { + // TODO Auto-generated method stub + + } + + @Override + public void hide() { + // TODO Auto-generated method stub + + } + + @Override + public void pause() { + // TODO Auto-generated method stub + + } + + @Override + public void resume() { + // TODO Auto-generated method stub + + } + + @Override + public void dispose() { + // TODO Auto-generated method stub + + } + + /*;;;;;;;;;;;;;;;;;; + ; HELPER METHODS ; + ;;;;;;;;;;;;;;;;;;*/ + + @Override + public void onStateSet(){ + } + + @Override + public void onStateUnset(){ + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; BEGIN INPUT PROCESSOR METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + @Override + public boolean keyDown(int keycode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyUp(int keycode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyTyped(char character) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchDragged(int screenX, int screenY, int pointer) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean mouseMoved(int screenX, int screenY) { + // TODO Auto-generated method stub + return false; + } + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; END INPUT PROCESSOR METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; BEGIN CONTROLLER LISTENER METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ + @Override + public boolean scrolled(int amount) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void connected(Controller controller) { + // TODO Auto-generated method stub + + } + + @Override + public void disconnected(Controller controller) { + // TODO Auto-generated method stub + + } + + @Override + public boolean buttonDown(Controller controller, int buttonCode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean buttonUp(Controller controller, int buttonCode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean axisMoved(Controller controller, int axisCode, float value) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean povMoved(Controller controller, int povCode, + PovDirection value) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean xSliderMoved(Controller controller, int sliderCode, + boolean value) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ySliderMoved(Controller controller, int sliderCode, + boolean value) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean accelerometerMoved(Controller controller, + int accelerometerCode, Vector3 value) { + // TODO Auto-generated method stub + return false; + } + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; END CONTROLLER LISTENER METHODS ; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;*/ +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/states/TabletMainMenuState.java b/src/ve/ucv/ciens/ccg/nxtar/states/TabletMainMenuState.java new file mode 100644 index 0000000..13bcfbb --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtar/states/TabletMainMenuState.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ve.ucv.ciens.ccg.nxtar.states; + +import ve.ucv.ciens.ccg.nxtar.NxtARCore; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL10; + +public class TabletMainMenuState extends MainMenuStateBase{ + public TabletMainMenuState(final NxtARCore core){ + this.core = core; + startButton.setPosition(-(startButton.getWidth() / 2), -(startButton.getHeight() / 2)); + startButtonBBox.setPosition(startButton.getX(), startButton.getY()); + + float ledYPos = (-(Gdx.graphics.getHeight() / 2) * 0.5f) + (startButton.getY() * 0.5f); + clientConnectedLedOn.setSize(clientConnectedLedOn.getWidth() * 0.5f, clientConnectedLedOn.getHeight() * 0.5f); + clientConnectedLedOn.setPosition(-(clientConnectedLedOn.getWidth() / 2), ledYPos); + + clientConnectedLedOff.setSize(clientConnectedLedOff.getWidth() * 0.5f, clientConnectedLedOff.getHeight() * 0.5f); + clientConnectedLedOff.setPosition(-(clientConnectedLedOff.getWidth() / 2), ledYPos); + } + + @Override + public void render(float delta){ + Gdx.gl.glClearColor(1, 1, 1, 1); + Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); + + core.batch.setProjectionMatrix(pixelPerfectCamera.combined); + core.batch.begin();{ + core.batch.disableBlending(); + drawBackground(core.batch); + core.batch.enableBlending(); + + if(clientConnected){ + clientConnectedLedOn.draw(core.batch); + }else{ + clientConnectedLedOff.draw(core.batch); + } + startButton.draw(core.batch, 1.0f); + }core.batch.end(); + } +} diff --git a/src/ve/ucv/ciens/ccg/nxtar/utils/ProjectConstants.java b/src/ve/ucv/ciens/ccg/nxtar/utils/ProjectConstants.java index 1d2ab19..d53873a 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/utils/ProjectConstants.java +++ b/src/ve/ucv/ciens/ccg/nxtar/utils/ProjectConstants.java @@ -15,13 +15,29 @@ */ package ve.ucv.ciens.ccg.nxtar.utils; -public abstract class ProjectConstants { - public static final int SERVER_UDP_PORT = 8889; - public static final int SERVER_TCP_PORT_1 = 9989; - public static final int SERVER_TCP_PORT_2 = 9990; +import com.badlogic.gdx.controllers.mappings.Ouya; + +public abstract class ProjectConstants{ + public static final int SERVICE_DISCOVERY_PORT = 9988; + public static final int VIDEO_STREAMING_PORT = 9989; + public static final int MOTOR_CONTROL_PORT = 9990; + public static final int SENSOR_REPORT_PORT = 9991; + public static final int APP_CONTROL_PORT = 9992; + public static final String MULTICAST_ADDRESS = "230.0.0.1"; + public static final int EXIT_SUCCESS = 0; public static final int EXIT_FAILURE = 1; public static final boolean DEBUG = true; + + public static final int[] POWERS_OF_2 = {64, 128, 256, 512, 1024, 2048}; + + public static final float OVERSCAN; + + public static final String FONT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + + static{ + OVERSCAN = Ouya.runningOnOuya ? 0.9f : 1.0f; + } }