Flutter flame sprite duplication on Android but not Iphone? - android-studio

Making a game using Flutter Flame engine and AndroidStudio for development. The render function uses canvas.save() and canvas.render() functions that ensure that the screen is clear between frames (to prevent duplicate sprite drawing). On IPhone simulator the app runs as expected, however on multiple Android Simulators duplicate sprites are rendered. Can't figure out why this is happening... Any help appreciated!
import 'dart:ui';
import 'package:flame/components/component.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ThisGame extends Game {
final SharedPreferences storage;
Size screenSize;
double tileSize;
Player player;
double spriteSize = 128;
double timeElapsed = 0;
ThisGame(this.storage) {
initialize();
}
initialize() async {
resize(await Flame.util.initialDimensions());
player = Player();
player.x = 0;
player.y = 0;
}
#override
void render(Canvas canvas) {
canvas.save();
player.render(canvas);
canvas.restore();
}
#override
void update(double t) {
timeElapsed += t;
if (timeElapsed > 1) {
timeElapsed = 0;
player.y += t * 300;
player.x += t * 300;
}
}
#override
void resize(Size size) {
screenSize = size;
tileSize = screenSize.width / 9;
}
}
class Player extends SpriteComponent {
Player() : super.square(128.0, 'player.png') {
this.angle = 0.0;
}
}

You don't have to handle the render method yourself, just add your SpriteComponent to the game and it will handle the rendering for you.
class ThisGame extends Game {
final SharedPreferences storage;
Size screenSize;
double tileSize;
Player player;
double spriteSize = 128;
double timeElapsed = 0;
ThisGame(this.storage) {
initialize();
}
initialize() async {
resize(await Flame.util.initialDimensions());
player = Player();
player.x = 0;
player.y = 0;
add(player); // <-- Like this
}
#override
void update(double t) {
super.update(t) // Also don't forget to call super
timeElapsed += t;
if (timeElapsed > 1) {
timeElapsed = 0;
player.y += t * 300;
player.x += t * 300;
}
}
#override
void resize(Size size) {
screenSize = size;
tileSize = screenSize.width / 9;
}
}
class Player extends SpriteComponent {
Player() : super.square(128.0, 'player.png') {
this.angle = 0.0;
}
}
Also, don't forget to call super.update(t) in your update override.

Related

NullPointerException not in my code but in onResume() for LibGDX AndroidInput

This the stack trace:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.epl.game, PID: 18789
java.lang.RuntimeException: Unable to resume activity {com.epl.game/com.epl.game.AndroidLauncher}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.badlogic.gdx.backends.android.AndroidInput.onResume()' on a null object reference
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4205)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4237)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.badlogic.gdx.backends.android.AndroidInput.onResume()' on a null object reference
at com.badlogic.gdx.backends.android.AndroidApplication.onResume(AndroidApplication.java:300)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1453)
at android.app.Activity.performResume(Activity.java:7962)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4195)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4237) 
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52) 
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
at android.os.Handler.dispatchMessage(Handler.java:107) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:49 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
This is my main project
package com.epl.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import org.omg.PortableServer.POAManagerPackage.State;
import java.util.ArrayList;
import java.util.Random;
public class epl extends ApplicationAdapter {
MyTextInputListener listener = new MyTextInputListener();
SpriteBatch batch;
Texture background;
Texture[] man;
State[] gsm;
int batsmanState = 0;
int pause = 0;
float gravity = 0.2f;
float velocity = 0;
int manY = 0;
Rectangle manRectangle;
BitmapFont font1;
BitmapFont font2;
BitmapFont font3;
Texture dizzy;
int score = 0;
int gameState = 0;
int i1 = 0;
int i2 = 0;
State state0;
State state1;
State state2;
State state3;
State state4;
State state5;
Random random;
String humanName;
ArrayList<Integer> coinXs = new ArrayList<>();
ArrayList<Integer> coinYs = new ArrayList<>();
ArrayList<Rectangle> coinRectangles = new ArrayList<>();
Texture coin;
int coinCount;
ArrayList<Integer> bombXs = new ArrayList<>();
ArrayList<Integer> bombYs = new ArrayList<>();
ArrayList<Rectangle> bombRectangles = new ArrayList<>();
Texture bomb;
int bombCount;
PlayServices ply;
#Override
public void create() {
batch = new SpriteBatch();
background = new Texture("bg.png");
man = new Texture[4];
man[0] = new Texture("batsman.jpg");
man[1] = new Texture("batsman.jpg");
man[2] = new Texture("batsman.jpg");
man[3] = new Texture("batsman.jpg");
gsm = new State[6];
gsm[0] = (state0);
gsm[1] = (state1);
gsm[2] = (state2);
gsm[3] = (state3);
gsm[4] = (state4);
gsm[5] = (state5);
manY = Gdx.graphics.getHeight() / 2;
coin = new Texture("ball.png");
bomb = new Texture("stump.jpeg");
random = new Random();
dizzy = new Texture("out.jpeg");
font1 = new BitmapFont();
font1.setColor(Color.RED);
font1.getData().setScale(10);
font2 = new BitmapFont();
font2.setColor(Color.RED);
font2.getData().setScale(10);
font3 = new BitmapFont();
font3.setColor(Color.RED);
font3.getData().setScale(10);
}
public void makeCoin() {
float height = random.nextFloat() * Gdx.graphics.getHeight();
coinYs.add((int) height);
coinXs.add(Gdx.graphics.getWidth());
}
public void makeBomb() {
float height = random.nextFloat() * Gdx.graphics.getHeight();
bombYs.add((int)height);
bombXs.add(Gdx.graphics.getWidth());
}
private String myText;
public class MyTextInputListener implements Input.TextInputListener {
#Override
public void input(String text) {
}
#Override
public void canceled() {
whatIsYourName();
}
public String getText() {
return myText;
}
public void whatIsYourName() {
Gdx.input.getTextInput(listener, "Name : ", "", "eg:Jonathan");
humanName = listener.getText();
gameState = 1;
}
}
public epl(PlayServices ply){
this.ply = ply;
}
#Override
public void render () {
batch.begin();
batch.draw(background, 0, 0, Gdx.graphics.getWidth(),
Gdx.graphics.getHeight());
if (gameState == 1 && state1 == null) {
// GAME IS LIVE
// BOMB
if (bombCount < 250) {
bombCount++;
} else {
bombCount = 0;
makeBomb();
}
bombRectangles.clear();
for (int i = 0; i < bombXs.size(); i++) {
batch.draw(bomb, bombXs.get(i), bombYs.get(i));
bombXs.set(i, bombXs.get(i) - 8);
bombRectangles.add(new Rectangle(bombXs.get(i),
bombYs.get(i),
bomb.getWidth(), bomb.getHeight()));
}
// COINS
if (coinCount < 100) {
coinCount++;
} else {
coinCount = 0;
makeCoin();
}
coinRectangles.clear();
for (int i = 0; i < coinXs.size(); i++) {
batch.draw(coin, coinXs.get(i), coinYs.get(i));
coinXs.set(i, coinXs.get(i) - 4);
coinRectangles.add(new Rectangle(coinXs.get(i),
coinYs.get(i),
coin.getWidth(), coin.getHeight()));
}
if (Gdx.input.justTouched()) {
velocity = -10;
}
if (pause < 8) {
pause++;
} else {
pause = 0;
if (batsmanState < 3) {
batsmanState++;
} else {
batsmanState = 0;
}
}
velocity += gravity;
manY -= velocity;
if (manY <= 0) {
manY = 0;
}
} else if (gameState == 5 && state5 == null) {
//leaderboard
if (Gdx.input.justTouched()){
ply.submitScore(humanName,score);
ply.showScore(humanName);
gameState = 1;
}
}else if (gameState == 3 && state3 == null) {
//name
listener.whatIsYourName();
gameState = 1;
} else if (gameState == 0 && state0 == null) {
// Waiting to start
if (humanName == null){
gameState = 3;
}else{
gameState = 1;
}
} else if (gameState == 4 && state4 == null) {
//final score display
font3.draw(batch, "Score = " + score,100,1400);
if (Gdx.input.justTouched()){
score = 0;
gameState = 1;
}
}else if (gameState == 2 && state2 == null) {
// GAME OVER
if (Gdx.input.justTouched()) {
manY = Gdx.graphics.getHeight() / 2;
velocity = 0;
coinXs.clear();
coinYs.clear();
coinRectangles.clear();
coinCount = 0;
bombXs.clear();
bombYs.clear();
bombRectangles.clear();
bombCount = 0;
i1 = 0;
i2 = 0;
}
}
if (gameState == 2) {
batch.draw(dizzy, Gdx.graphics.getWidth() / 2 -
man[batsmanState].getWidth() / 2, manY);
if (Gdx.input.justTouched()){
gameState = 4;
}
} else {
batch.draw(man[batsmanState], Gdx.graphics.getWidth() / 2 -
man[batsmanState].getWidth() / 2, manY);
}
manRectangle = new Rectangle(Gdx.graphics.getWidth() / 2 -
man[batsmanState].getWidth() / 2, manY,
man[batsmanState].getWidth(), man[batsmanState].getHeight());
for (int i=0; i < coinRectangles.size();i++) {
if (Intersector.overlaps(manRectangle, coinRectangles.get(i))) {
score++;
i1 = random.nextInt((4 -1) + 1);
score = score + i1;
i2 = i1 + 1;
coinRectangles.remove(i);
coinXs.remove(i);
coinYs.remove(i);
break;
}
}
for (int i=0; i < bombRectangles.size();i++) {
if (Intersector.overlaps(manRectangle, bombRectangles.get(i))) {
gameState = 2;
}
}
font1.draw(batch, String.valueOf(score),100,200);
font2.draw(batch, String.valueOf(i2),900,200);
batch.end();
}
#Override
public void dispose () {
batch.dispose();
}
}
This is my android launcher
package com.epl.game;
import android.content.Intent;
import android.os.Bundle;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.google.android.gms.games.Games;
import com.google.example.games.basegameutils.GameHelper;
public class AndroidLauncher extends AndroidApplication implements PlayServices {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameHelper = new GameHelper(this, GameHelper.CLIENT_GAMES);
gameHelper.enableDebugLog(true);
GameHelper.GameHelperListener gameHelperListener = new GameHelper.GameHelperListener() {
#Override
public void onSignInFailed() {
}
#Override
public void onSignInSucceeded() {
}
};
gameHelper.setup(gameHelperListener);
}
String leaderboard = "CgkI7PuNlqsVEAIQAA";
private GameHelper gameHelper;
#Override
protected void onStart() {
super.onStart();
gameHelper.onStart(this); // You will be logged in to google play services as soon as you open app , i,e on start
}
#Override
protected void onStop() {
super.onStop();
gameHelper.onStop();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
gameHelper.onActivityResult(requestCode, resultCode, data);
}
#Override
public boolean signIn() {
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
gameHelper.beginUserInitiatedSignIn();
}
});
} catch (Exception e) {
}
return true;
}
#Override
public void submitScore(String LeaderBoard, int highScore) {
if (isSignedIn()) {
Games.Leaderboards.submitScore(gameHelper.getApiClient(), LeaderBoard, highScore);
} else {
System.out.println(" Not signin Yet ");
}
}
#Override
public void showScore(String leaderboard) {
if (isSignedIn()) {
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(gameHelper.getApiClient(), leaderboard), 1);
} else {
signIn();
}
}
#Override
public boolean isSignedIn() {
return false;
}
And this is my play services interface
package com.epl.game;
public interface PlayServices
{
boolean signIn();
void submitScore(String LeaderBoard, int highScore);
void showScore(String LeaderBoard);
boolean isSignedIn();
}
I am new to libgdx and I am trying to create a game with a leaderboard .
I created this by importing the BaseGameUtils .
Else if you have another way i could create a global leaderboard in my
game please let me know.
It is critical that you call the initialize method in onCreate of your AndroidLauncher class. This is what sets up LibGDX's backends for graphics, sound, and input. Since you did not call initialize, the input class (along with graphics, sound, files, etc.) was not set up and assigned, and so is still null when the resume part of the lifecycle is reached. This leads to the NullPointerException.
In your case, your onCreate method should look something like:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
// customize the configuration here
initialize(new epl(), config);
// Your other setup code...
}
Note, class names in Java should always start with a capital letter. It will make it easier to read and understand your code.

Resize GameObject over time [duplicate]

I made a test game in unity that makes it so when I click on a button, it spawns a cylinder created from a factory class. I'm trying to make it so when I create the cylinder, its height shrinks over the next 20 seconds. Some methods I found are difficult to translate into what I'm doing. If you could lead me to the right direction, I'd very much appreciate it.
Here's my code for the cylinder class
public class Cylinder : Shape
{
public Cylinder()
{
GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
cylinder.transform.position = new Vector3(3, 0, 0);
cylinder.transform.localScale = new Vector3(1.0f, Random.Range(1, 2)-1*Time.deltaTime, 1.0f);
cylinder.GetComponent<MeshRenderer>().material.color = Random.ColorHSV();
Destroy(cylinder, 30.0f);
}
}
This can be done with the Time.deltaTime and Vector3.Lerp in a coroutine function. Similar to Rotate GameObject over time and Move GameObject over time questions. Modified it a little bit to do just this.
bool isScaling = false;
IEnumerator scaleOverTime(Transform objectToScale, Vector3 toScale, float duration)
{
//Make sure there is only one instance of this function running
if (isScaling)
{
yield break; ///exit if this is still running
}
isScaling = true;
float counter = 0;
//Get the current scale of the object to be moved
Vector3 startScaleSize = objectToScale.localScale;
while (counter < duration)
{
counter += Time.deltaTime;
objectToScale.localScale = Vector3.Lerp(startScaleSize, toScale, counter / duration);
yield return null;
}
isScaling = false;
}
USAGE:
Will scale GameObject within 20 seconds:
StartCoroutine(scaleOverTime(cylinder.transform, new Vector3(0, 0, 90), 20f));
Check out Lerp. A general example of how to use it would be something like this:
float t = 0;
Update()
{
t += Time.deltaTime;
cylinder.localScale = new Vector3(1, Mathf.Lerp(2f, 1f, t/3f), 1); // shrink from 2 to 1 over 3 seconds;
}
You will create a new monobehaviour script and add it to your primitive. Then you wil use "Update" method of monobehaviour (or use coroutine) for change object over time.
Monobehaviour must be look like this:
public class ShrinkBehaviour : MonoBehaviour
{
bool isNeedToShrink;
Config currentConfig;
float startTime;
float totalDistance;
public void StartShrink(Config config)
{
startTime = Time.time;
currentConfig = config;
totalDistance = Vector3.Distance(currentConfig.startSize, currentConfig.destinationSize);
isNeedToShrink = true;
transform.localScale = config.startSize;
}
private void Update()
{
if (isNeedToShrink)
{
var nextSize = GetNextSize(currentConfig);
if (Vector3.Distance(nextSize, currentConfig.destinationSize) <= 0.05f)
{
isNeedToShrink = false;
return;
}
transform.localScale = nextSize;
}
}
Vector3 GetNextSize(Config config)
{
float timeCovered = (Time.time - startTime) / config.duration;
var result = Vector3.Lerp(config.startSize, config.destinationSize, timeCovered);
return result;
}
public struct Config
{
public float duration;
public Vector3 startSize;
public Vector3 destinationSize;
}
}
For using this, you must do next:
public Cylinder()
{
GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
var shrink = cylinder.AddComponent<ShrinkBehaviour>();
shrink.StartShrink(new ShrinkBehaviour.Config() { startSize = Vector3.one * 10, destinationSize = Vector3.one * 1, duration = 20f });
cylinder.transform.position = new Vector3(3, 0, 0);
cylinder.GetComponent<MeshRenderer>().material.color = Random.ColorHSV();
Destroy(cylinder, 30.0f);
}
You must remember, monobehaviour-script must be in separate file, and must have name similar to monobehaviour-class name. For example, ShrinkBehaviour.cs;

Progressive lag with each collision iteration libGDX

Hello I am developing a game on my spare time with AIDE and libGDX. Though AIDE has some missing methods of the libGDX API and I had to create some workarounds to compensate for the missing methods.
So my problem is that with every instance of a collision the app becomes more and more laggy. No new textures are being drawn and non go away. Just a poor implementation that is meant to push you out of said collision tile. It runs fine until you have a few collisions. My thought is that it creates a new variable with every collision and stores it into memory causing a leak. But I can't really tell if that is the case. Oh I'd like to add that I don't have access to a computer, just my phone. Here is my movement class
move.java
public class move
{
float posX;
float posY;
float touchedOnScreenX;
float touchedOnScreenY;
float centerOfScreenX;
float centerOfScreenY;
float posXSetter;
float posYSetter;
float Speed = 150;
float mapBoundsWidth;
float mapBoundsHeight;
boolean upperBounds;
boolean lowerBounds;
boolean rightBounds;
boolean leftBounds;
boolean tileCollition;
public move() {
}
public void renderMove(float delta) {
if(Gdx.input.isTouched()) {
screenAndTouchInfo();
checkBoundsBoolean();
checkForCollision();
}
}
//slows game down
private void checkForCollision()
{
if (upperBounds == false)
{
if (lowerBounds == false)
{
if (rightBounds == false)
{
if (leftBounds == false)
{
if (tileCollition == false)
{
movement();
}
else
{
collitionSide();
}
}
else
{
posX++;
}
}
else
{
posX--;
}
}
else
{
posY++;
}
}
else
{
posY --;
}
}
private void movement()
{
posYSetter = posY;
posXSetter = posX;
if (touchedOnScreenX < centerOfScreenX)
{
posX -= Gdx.graphics.getDeltaTime() * Speed;
}
else
{
posX += Gdx.graphics.getDeltaTime() * Speed;
}
if (touchedOnScreenY < centerOfScreenY)
{
posY -= Gdx.graphics.getDeltaTime() * Speed;
}
else
{
posY += Gdx.graphics.getDeltaTime() * Speed;
}
if (touchedOnScreenY < centerOfScreenY + 64 && touchedOnScreenY > centerOfScreenY - 64)
{
posY = posYSetter;
}
if (touchedOnScreenX < centerOfScreenX + 64 && touchedOnScreenX > centerOfScreenX - 64)
{
posX = posXSetter;
}
}
//buggy and slows game down. Can push you into tile if input is the opposite of the side
public void collitionSide() {
if (tileCollition == true){
if (touchedOnScreenX < centerOfScreenX)
{
posX = posX +10;
}
else
{
posX = posX - 10;
}
if (touchedOnScreenY < centerOfScreenY)
{
posY = posY +10;
}
else
{
posY = posY -10;
}
}
}
private void screenAndTouchInfo()
{
touchedOnScreenX = Gdx.input.getX();
touchedOnScreenY = (Gdx.graphics.getHeight() - Gdx.input.getY());
centerOfScreenX = Gdx.graphics.getWidth() / 2;
centerOfScreenY = Gdx.graphics.getHeight() / 2;
}
//slows game down
private void checkBoundsBoolean()
{
if (posX > mapBoundsWidth)
{
rightBounds = true;
}
else {
rightBounds = false;
}
if (posY > mapBoundsHeight)
{
upperBounds = true;
}
else {
upperBounds = false;
}
if (posX < mapBoundsWidth - mapBoundsWidth)
{
leftBounds = true;
}
else {
leftBounds = false;
}
if (posY < mapBoundsHeight - mapBoundsHeight)
{
lowerBounds = true;
}
else {
lowerBounds = false;
}
}
public void setTileCollision(boolean tileCollision) {
this.tileCollition = tileCollision;
}
public float getPosX() {
return posX;
}
public float getPosY() {
return posY;
}
public float getTouchedOnScreenX() {
return touchedOnScreenX;
}
public float getTouchedOnScreenY() {
return touchedOnScreenY;
}
public float getCenterOfScreenX() {
return centerOfScreenX;
}
public float getCenterOfScreenY() {
return centerOfScreenY;
}
public void setMapboundsWidth(float width) {
this.mapBoundsWidth = width;
}
public void setMapboundsHeight(float height) {
this.mapBoundsHeight = height;
}
}
I know that is a lot of code to comb through and I am sorry if it isn't always clear what is going on. I refactored it to where it would be a little easier to understand. Oh if anyone could tell me why AIDE is missing some methods of libGDX that would be nice too.The most notable one would be Cell.setTile(). That I know is in libGDX's API and can be found in their documentation. But when I implement it it throws a unknown method of class Cell error. I have researched on how to use the method as well with no avail. Had to create int[][] map and a for loop to draw map with another Texture[]. It works. Lol. Thank you to whomever even took the time to look at my long winded double question. Hopefully someone can tell me why this lag is created from said collisions.
I recommend moving your collision code into a separate thread. This should improve performance significantly:
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
#Override
public void run() {
// Physics loop goes here
}
});
Make sure to shutdown the executor when disposing your screen.

How to create a Video Wall like in the JavaFX 2.0 demo?

How do you create a video wall like in the JavaFX 2.0 demo here:
https://www.youtube.com/watch?v=UXSmJYFrulY#t=411
For a start it doesn't have to be videos, it can be images as well. All I'd like to have is to place the nodes like they are in the video, i. e. in a curved shape like the insides of a cylinder or a sphere.
Or is the source of that demo available somewhere?
Thank you very much.
I researched and found a very awesome site with the relevant information:
http://paulbourke.net/geometry/transformationprojection/
The relevant part was the Coordinate System Transformation, in particular the equations for converting between cartesian and spherical coordinates.
double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
In my example below y isn't used from the formula, since the image rows are stacked.
Note: By using these formulas in 2 nested for-loops from -Math.PI to Math.PI you can lay out the nodes around a sphere. The difficult part regarding the full sphere was to rotate the nodes towards the center, that one I couldn't figure out.
Since I wasn't familiar with Java3D I also checked out the Building a 3D Sample Application.
In the end I got a video wall, the code is reduced to this:
public class VideoWall extends Application {
Random rand = new Random();
Group root = new Group();
PerspectiveCamera camera;
private static final double CAMERA_INITIAL_DISTANCE = -850;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
Image[] images = new Image[] {
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")
};
public VideoWall(){
}
public static void main(String[] args) {
launch(args);
}
/**
* Create ImageView with random Image.
* #return
*/
private ImageView createImageView() {
Image image = images[ rand.nextInt(images.length)];
ImageView c = new ImageView( image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
#Override
public void start(Stage primaryStage) {
// build camera
camera = new PerspectiveCamera(true);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
// we display any node (imageview, webview, etc)
Node node;
// create a single webview; we only add it once because we don't want to flood youtube
WebView webView = new WebView();
webView.getEngine().load(
"http://www.youtube.com/embed/utUPth77L_o?autoplay=1"
);
webView.setPrefSize(100, 70);
// wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
double ringBeginDeg = -30;
double ringEndDeg = 38;
double r = 1300;
double yOffset = 80; // offset per image row
double yOffsetInitial = 120; // initial y offset from "floor"
int count=0;
for( double angle1=Math.toRadians(ringBeginDeg); angle1 <Math.toRadians(ringEndDeg); angle1+=0.08)
{
double angle2 = Math.PI;
for( int i=-3; i <= 3; i++)
{
double x = r * Math.sin(angle1) * Math.cos(angle2);
// double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
// add 1 webview, the rest imageviews
if( count == 16) {
node = webView;
} else {
node = createImageView();
}
node.setTranslateX(x);
node.setTranslateY(yOffset * i - yOffsetInitial);
node.setTranslateZ(z);
// rotate towards viewer position
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees( -angle1));
node.getTransforms().addAll(rx);
root.getChildren().add( node);
count++;
}
}
Scene scene = new Scene(root, 1600, 900, Color.BLACK);
primaryStage.setScene( scene);
primaryStage.show();
scene.setCamera(camera);
}
}
You can add whatever node you prefer. I added a youtube webview for testing. It plays, but the video doesn't get loaded, so all you see is static noise (the grey tile in the screenshot). So in theory you could make the nodes all webview with youtube videos, but that would mean flooding youtube. Better use some offline videos.
Here's a screenshot:
I also toyed around with the full 3d example and creating a ring. That's how it looked like (with always the same image) from an outer view:
Having the camera in the center you can nicely scroll the ring.
If someone wants to toy around, here's a quick & dirty gist with the navigable ring. Use left/right/middle mouse buttons for navigation.
And if you'd like to toy around with a full sphere, you may use this:
// full sphere
for (double angle1 = -Math.PI; angle1 <= Math.PI; angle1 += 0.15) {
for (double angle2 = -Math.PI; angle2 <= Math.PI; angle2 += 0.15) {
double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
c = createImageView();
c.setTranslateX(x);
c.setTranslateY(y);
c.setTranslateZ(z);
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees(-angle1));
c.getTransforms().addAll(rx);
world.getChildren().add(c);
}
}
Which looks like this:
But as mentioned, I haven't figured out yet how to rotate all tiles so that they look into the center. And they'd need to be equally distributed. But that's just for fun and off-topic.
Since it's part of the video in my question, it was only a matter of keeping a list of parallel transitions to create the "build-up" animation of the tiles. The bottom row has a reflection now.
The extended code:
public class VideoWall extends Application {
Random rand = new Random();
Group root = new Group();
PerspectiveCamera camera;
private static final double CAMERA_INITIAL_DISTANCE = -850;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
Image[] images = new Image[] {
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")
};
List<ParallelTransition> transitionList = new ArrayList<>();
public VideoWall(){
}
public static void main(String[] args) {
launch(args);
}
/**
* Create ImageView with random Image.
* #return
*/
private ImageView createImageView() {
Image image = images[ rand.nextInt(images.length)];
ImageView c = new ImageView( image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
#Override
public void start(Stage primaryStage) {
// build camera
camera = new PerspectiveCamera(true);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
// we display any node (imageview, webview, etc)
Node node;
// wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
double ringBeginDeg = -30;
double ringEndDeg = 38;
double r = 1300;
double yOffset = 80; // offset per image row
double yOffsetInitial = 120; // initial y offset from "floor"
int min = -3;
int max = 3;
for( double angle1=Math.toRadians(ringBeginDeg); angle1 <Math.toRadians(ringEndDeg); angle1+=0.08)
{
double angle2 = Math.PI;
for( int i=min; i <= max; i++)
{
double x = r * Math.sin(angle1) * Math.cos(angle2);
// double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
node = createImageView();
node.setTranslateX(x);
node.setTranslateY(yOffset * i - yOffsetInitial);
node.setTranslateZ(z);
// rotate towards viewer position
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees( -angle1));
node.getTransforms().addAll(rx);
// reflection on bottom row
if( i==max) {
Reflection refl = new Reflection();
refl.setFraction(0.8f);
node.setEffect(refl);
}
// build the wall using a transition
node.setVisible(false);
transitionList.add( createTransition( node));
root.getChildren().add( node);
}
}
Scene scene = new Scene(root, 1600, 900, Color.BLACK);
primaryStage.setScene( scene);
primaryStage.show();
scene.setCamera(camera);
AnimationTimer timer = createAnimation();
timer.start();
}
private AnimationTimer createAnimation() {
Collections.sort(transitionList, new Comparator<ParallelTransition>() {
#Override
public int compare(ParallelTransition arg0, ParallelTransition arg1) {
// bottom right to top left
Point2D ref = new Point2D(1000,1000);
Point2D pt0 = new Point2D( arg0.getNode().getTranslateX(), arg0.getNode().getTranslateY());
Point2D pt1 = new Point2D( arg1.getNode().getTranslateX(), arg1.getNode().getTranslateY());
return Double.compare(ref.distance(pt0), ref.distance(pt1));
// bottom row first
// return -Double.compare( arg0.getNode().getTranslateY(), arg1.getNode().getTranslateY());
}
});
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
//if( (now - last) > 1_000_000_000)
if( (now - last) > 40_000_000)
{
if( transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if( transitionList.size() == 0) {
stop();
}
}
};
return timer;
}
private ParallelTransition createTransition( final Node node) {
Path path = new Path();
path.getElements().add(new MoveToAbs( node, node.getTranslateX() - 1000, node.getTranslateY() - 900));
path.getElements().add(new LineToAbs( node, node.getTranslateX(), node.getTranslateY()));
Duration duration = Duration.millis(1500);
PathTransition pt = new PathTransition( duration, path, node);
RotateTransition rt = new RotateTransition( duration, node);
rt.setByAngle(720);
rt.setAutoReverse(true);
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.setNode(node);
parallelTransition.getChildren().addAll(pt, rt);
return parallelTransition;
}
public static class MoveToAbs extends MoveTo {
public MoveToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
public static class LineToAbs extends LineTo {
public LineToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
}
Well to me it seems to be a matter of creating a Grid, or "Mesh" out of ImageViews.
Then you would Map all the Viewport's to the Image('s) you want to display.
Here for example is a Skybox for 3D implementation using this approach.
Note that this is just a simple Cube.
The image is setup similar to this, though I made it into 6 separate Images.
If you wanted to use video, I recommend you use VLCJ,
They have samples for JavaFX setup Here
For this you would apply the same principals to the WritableImage(s)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
I added a couple things for you to play with ...
/**
* A self initializing First Person Shooter camera
*
* #author Jason Pollastrini aka jdub1581
*/
public class SimpleFPSCamera extends Parent {
public SimpleFPSCamera() {
initialize();
}
private void update() {
updateControls();
}
private void updateControls() {
if (fwd && !back) {
moveForward();
}
if (strafeL) {
strafeLeft();
}
if (strafeR) {
strafeRight();
}
if (back && !fwd) {
moveBack();
}
if (up && !down) {
moveUp();
}
if (down && !up) {
moveDown();
}
}
/*==========================================================================
Initialization
*/
private final Group root = new Group();
private final Affine affine = new Affine();
private final Translate t = new Translate(0, 0, 0);
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS),
rotateY = new Rotate(0, Rotate.Y_AXIS),
rotateZ = new Rotate(0, Rotate.Z_AXIS);
private boolean fwd, strafeL, strafeR, back, up, down, shift;
private double mouseSpeed = 1.0, mouseModifier = 0.1;
private double moveSpeed = 10.0;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
private void initialize() {
getChildren().add(root);
getTransforms().addAll(affine);
initializeCamera();
startUpdateThread();
}
public void loadControlsForSubScene(SubScene scene) {
sceneProperty().addListener(l -> {
if (getScene() != null) {
getScene().addEventHandler(KeyEvent.ANY, ke -> {
if (ke.getEventType() == KeyEvent.KEY_PRESSED) {
switch (ke.getCode()) {
case Q:
up = true;
break;
case E:
down = true;
break;
case W:
fwd = true;
break;
case S:
back = true;
break;
case A:
strafeL = true;
break;
case D:
strafeR = true;
break;
case SHIFT:
shift = true;
moveSpeed = 20;
break;
}
} else if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
switch (ke.getCode()) {
case Q:
up = false;
break;
case E:
down = false;
break;
case W:
fwd = false;
break;
case S:
back = false;
break;
case A:
strafeL = false;
break;
case D:
strafeR = false;
break;
case SHIFT:
moveSpeed = 10;
shift = false;
break;
}
}
ke.consume();
});
}
});
scene.addEventHandler(MouseEvent.ANY, me -> {
if (me.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
} else if (me.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
mouseSpeed = 1.0;
mouseModifier = 0.1;
if (me.isPrimaryButtonDown()) {
if (me.isControlDown()) {
mouseSpeed = 0.1;
}
if (me.isShiftDown()) {
mouseSpeed = 1.0;
}
t.setX(getPosition().getX());
t.setY(getPosition().getY());
t.setZ(getPosition().getZ());
affine.setToIdentity();
rotateY.setAngle(
Utils.clamp(-360, ((rotateY.getAngle() + mouseDeltaX * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 360)
); // horizontal
rotateX.setAngle(
Utils.clamp(-45, ((rotateX.getAngle() - mouseDeltaY * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 35)
); // vertical
affine.prepend(t.createConcatenation(rotateY.createConcatenation(rotateX)));
} else if (me.isSecondaryButtonDown()) {
/*
init zoom?
*/
} else if (me.isMiddleButtonDown()) {
/*
init panning?
*/
}
}
});
scene.addEventHandler(ScrollEvent.ANY, se -> {
if (se.getEventType().equals(ScrollEvent.SCROLL_STARTED)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL_FINISHED)) {
}
});
}
public void loadControlsForScene(Scene scene) {
scene.addEventHandler(KeyEvent.ANY, ke -> {
if (ke.getEventType() == KeyEvent.KEY_PRESSED) {
switch (ke.getCode()) {
case Q:
up = true;
break;
case E:
down = true;
break;
case W:
fwd = true;
break;
case S:
back = true;
break;
case A:
strafeL = true;
break;
case D:
strafeR = true;
break;
case SHIFT:
shift = true;
moveSpeed = 20;
break;
}
} else if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
switch (ke.getCode()) {
case Q:
up = false;
break;
case E:
down = false;
break;
case W:
fwd = false;
break;
case S:
back = false;
break;
case A:
strafeL = false;
break;
case D:
strafeR = false;
break;
case SHIFT:
moveSpeed = 10;
shift = false;
break;
}
}
ke.consume();
});
scene.addEventHandler(MouseEvent.ANY, me -> {
if (me.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
} else if (me.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
mouseSpeed = 1.0;
mouseModifier = 0.1;
if (me.isPrimaryButtonDown()) {
if (me.isControlDown()) {
mouseSpeed = 0.1;
}
if (me.isShiftDown()) {
mouseSpeed = 1.0;
}
t.setX(getPosition().getX());
t.setY(getPosition().getY());
t.setZ(getPosition().getZ());
affine.setToIdentity();
rotateY.setAngle(
Utils.clamp(-360, ((rotateY.getAngle() + mouseDeltaX * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 360)
); // horizontal
rotateX.setAngle(
Utils.clamp(-45, ((rotateX.getAngle() - mouseDeltaY * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 35)
); // vertical
affine.prepend(t.createConcatenation(rotateY.createConcatenation(rotateX)));
} else if (me.isSecondaryButtonDown()) {
/*
init zoom?
*/
} else if (me.isMiddleButtonDown()) {
/*
init panning?
*/
}
}
});
scene.addEventHandler(ScrollEvent.ANY, se -> {
if (se.getEventType().equals(ScrollEvent.SCROLL_STARTED)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL_FINISHED)) {
}
});
}
private void initializeCamera() {
getCamera().setNearClip(0.1);
getCamera().setFarClip(100000);
getCamera().setFieldOfView(42);
getCamera().setVerticalFieldOfView(true);
//getCamera().getTransforms().add(new Rotate(180, Rotate.Z_AXIS));
root.getChildren().add(getCamera());
}
private void startUpdateThread() {
new AnimationTimer() {
#Override
public void handle(long now) {
update();
}
}.start();
}
/*==========================================================================
Movement
*/
private void moveForward() {
affine.setTx(getPosition().getX() + moveSpeed * getN().getX());
affine.setTy(getPosition().getY() + moveSpeed * getN().getY());
affine.setTz(getPosition().getZ() + moveSpeed * getN().getZ());
}
private void strafeLeft() {
affine.setTx(getPosition().getX() + moveSpeed * -getU().getX());
affine.setTy(getPosition().getY() + moveSpeed * -getU().getY());
affine.setTz(getPosition().getZ() + moveSpeed * -getU().getZ());
}
private void strafeRight() {
affine.setTx(getPosition().getX() + moveSpeed * getU().getX());
affine.setTy(getPosition().getY() + moveSpeed * getU().getY());
affine.setTz(getPosition().getZ() + moveSpeed * getU().getZ());
}
private void moveBack() {
affine.setTx(getPosition().getX() + moveSpeed * -getN().getX());
affine.setTy(getPosition().getY() + moveSpeed * -getN().getY());
affine.setTz(getPosition().getZ() + moveSpeed * -getN().getZ());
}
private void moveUp() {
affine.setTx(getPosition().getX() + moveSpeed * -getV().getX());
affine.setTy(getPosition().getY() + moveSpeed * -getV().getY());
affine.setTz(getPosition().getZ() + moveSpeed * -getV().getZ());
}
private void moveDown() {
affine.setTx(getPosition().getX() + moveSpeed * getV().getX());
affine.setTy(getPosition().getY() + moveSpeed * getV().getY());
affine.setTz(getPosition().getZ() + moveSpeed * getV().getZ());
}
/*==========================================================================
Properties
*/
private final ReadOnlyObjectWrapper<PerspectiveCamera> camera = new ReadOnlyObjectWrapper<>(this, "camera", new PerspectiveCamera(true));
public final PerspectiveCamera getCamera() {
return camera.get();
}
public ReadOnlyObjectProperty cameraProperty() {
return camera.getReadOnlyProperty();
}
/*==========================================================================
Callbacks
| R | Up| F | | P|
U |mxx|mxy|mxz| |tx|
V |myx|myy|myz| |ty|
N |mzx|mzy|mzz| |tz|
*/
//Forward / look direction
private final Callback<Transform, Point3D> F = (a) -> {
return new Point3D(a.getMzx(), a.getMzy(), a.getMzz());
};
private final Callback<Transform, Point3D> N = (a) -> {
return new Point3D(a.getMxz(), a.getMyz(), a.getMzz());
};
// up direction
private final Callback<Transform, Point3D> UP = (a) -> {
return new Point3D(a.getMyx(), a.getMyy(), a.getMyz());
};
private final Callback<Transform, Point3D> V = (a) -> {
return new Point3D(a.getMxy(), a.getMyy(), a.getMzy());
};
// right direction
private final Callback<Transform, Point3D> R = (a) -> {
return new Point3D(a.getMxx(), a.getMxy(), a.getMxz());
};
private final Callback<Transform, Point3D> U = (a) -> {
return new Point3D(a.getMxx(), a.getMyx(), a.getMzx());
};
//position
private final Callback<Transform, Point3D> P = (a) -> {
return new Point3D(a.getTx(), a.getTy(), a.getTz());
};
private Point3D getF() {
return F.call(getLocalToSceneTransform());
}
public Point3D getLookDirection() {
return getF();
}
private Point3D getN() {
return N.call(getLocalToSceneTransform());
}
public Point3D getLookNormal() {
return getN();
}
private Point3D getR() {
return R.call(getLocalToSceneTransform());
}
private Point3D getU() {
return U.call(getLocalToSceneTransform());
}
private Point3D getUp() {
return UP.call(getLocalToSceneTransform());
}
private Point3D getV() {
return V.call(getLocalToSceneTransform());
}
public final Point3D getPosition() {
return P.call(getLocalToSceneTransform());
}
}
Your Modified code:
public class VideoWall extends Application {
Random rand = new Random();
Group root = new Group();
PerspectiveCamera camera;
SimpleFPSCamera fpsCam;
private static final double CAMERA_INITIAL_DISTANCE = -10000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 100000.0;
Image[] images = new Image[]{
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")
};
List<ParallelTransition> transitionList = new ArrayList<>();
List<ImageView> imageList = new ArrayList<>();
public VideoWall() {
}
public static void main(String[] args) {
launch(args);
}
/**
* Create ImageView with random Image.
*
* #return
*/
private ImageView createImageView() {
Image image = images[rand.nextInt(images.length)];
ImageView c = new ImageView(image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
private BillboardImage createBillboardImage() {
Image image = images[rand.nextInt(images.length)];
BillboardImage c = new BillboardImage(image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
#Override
public void start(Stage primaryStage) {
// build camera
//camera = new PerspectiveCamera(true);
//camera.setNearClip(CAMERA_NEAR_CLIP);
//camera.setFarClip(CAMERA_FAR_CLIP);
//camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
fpsCam = new SimpleFPSCamera();
// we display any node (imageview, webview, etc)
Node node;
// wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
double ringBeginDeg = -30;
double ringEndDeg = 38;
double r = 1300;
double yOffset = 80; // offset per image row
double yOffsetInitial = 120; // initial y offset from "floor"
int min = -3;
int max = 3;
/*
for (double angle1 = Math.toRadians(ringBeginDeg); angle1 < Math.toRadians(ringEndDeg); angle1 += 0.08) {
double angle2 = Math.PI;
for (int i = min; i <= max; i++) {
double x = r * Math.sin(angle1) * Math.cos(angle2);
// double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
node = createImageView();
node.setTranslateX(x);
node.setTranslateY(yOffset * i - yOffsetInitial);
node.setTranslateZ(z);
// rotate towards viewer position
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees(-angle1));
node.getTransforms().addAll(rx);
// reflection on bottom row
if (i == max) {
Reflection refl = new Reflection();
refl.setFraction(0.8f);
node.setEffect(refl);
}
// build the wall using a transition
node.setVisible(false);
transitionList.add(createTransition(node));
root.getChildren().add(node);
}
}*/
// full sphere
for (double angle1 = -Math.PI; angle1 <= Math.PI; angle1 += 0.48) {
for (double angle2 = -Math.PI; angle2 <= Math.PI; angle2 += 0.48) {
double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
BillboardImage c = createBillboardImage();
c.setTranslateX(x);
c.setTranslateY(y);
c.setTranslateZ(z);
imageList.add(c);
}
}
root.getChildren().add(fpsCam);
root.getChildren().addAll(imageList);
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.BLACK);
scene.setCamera(fpsCam.getCamera());
fpsCam.loadControlsForScene(scene);
primaryStage.setScene(scene);
primaryStage.show();
AnimationTimer timer = createBillboardTimer();
timer.start();
}
private AnimationTimer createBillboardTimer() {
return new AnimationTimer() {
#Override
public void handle(long now) {
if(!imageList.isEmpty()){
imageList.stream().forEach(bbi ->{
((BillboardImage)bbi).updateMatrix(bbi, fpsCam);
});
}
}
};
}
private AnimationTimer createAnimation() {
Collections.sort(transitionList, new Comparator<ParallelTransition>() {
#Override
public int compare(ParallelTransition arg0, ParallelTransition arg1) {
// bottom right to top left
Point2D ref = new Point2D(1000, 1000);
Point2D pt0 = new Point2D(arg0.getNode().getTranslateX(), arg0.getNode().getTranslateY());
Point2D pt1 = new Point2D(arg1.getNode().getTranslateX(), arg1.getNode().getTranslateY());
return Double.compare(ref.distance(pt0), ref.distance(pt1));
// bottom row first
// return -Double.compare( arg0.getNode().getTranslateY(), arg1.getNode().getTranslateY());
}
});
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
//if( (now - last) > 1_000_000_000)
if ((now - last) > 40_000_000) {
if (transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if (transitionList.size() == 0) {
stop();
}
}
};
return timer;
}
private ParallelTransition createTransition(final Node node) {
Path path = new Path();
path.getElements().add(new MoveToAbs(node, node.getTranslateX() - 1000, node.getTranslateY() - 900));
path.getElements().add(new LineToAbs(node, node.getTranslateX(), node.getTranslateY()));
Duration duration = Duration.millis(1500);
PathTransition pt = new PathTransition(duration, path, node);
RotateTransition rt = new RotateTransition(duration, node);
rt.setByAngle(720);
rt.setAutoReverse(true);
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.setNode(node);
parallelTransition.getChildren().addAll(pt, rt);
return parallelTransition;
}
public static class MoveToAbs extends MoveTo {
public MoveToAbs(Node node, double x, double y) {
super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
public static class LineToAbs extends LineTo {
public LineToAbs(Node node, double x, double y) {
super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
/*
*/
public enum BillboardMode {
SPHERICAL,
CYLINDRICAL;
}
private class BillboardImage extends ImageView{
// Add transform to Node that needs to look at target..
public Affine affine = new Affine();
public BillboardImage() {
this.getTransforms().add(affine);
}
public BillboardImage(String url) {
super(url);
this.getTransforms().add(affine);
}
public BillboardImage(Image image) {
super(image);
this.getTransforms().add(affine);
}
// set up to look at camera, can change to any other Node
protected void updateMatrix(Node billBoardNode, Node other) {
Transform self = billBoardNode.getLocalToSceneTransform(),
oth = other.getLocalToSceneTransform();
Bounds b;
double cX, cY, cZ;
if (!(billBoardNode instanceof Shape3D)) {
b = billBoardNode.getBoundsInLocal();
cX = b.getWidth() / 2;
cY = b.getHeight() / 2;
cZ = b.getDepth() / 2;
} else {
cX = self.getTx();
cY = self.getTy();
cZ = self.getTz();
}
Point3D otherPos = Point3D.ZERO.add(oth.getTx(), oth.getTy(), oth.getTz());
Point3D selfPos = new Point3D(cX, cY, cZ);
Point3D up = Point3D.ZERO.add(0, -1, 0),
forward = new Point3D(
(selfPos.getX()) - otherPos.getX(),
(selfPos.getY()) - otherPos.getY(),
(selfPos.getZ()) - otherPos.getZ()
).normalize(),
right = up.crossProduct(forward).normalize();
up = forward.crossProduct(right).normalize();
switch (getBillboardMode()) {
case SPHERICAL:
affine.setMxx(right.getX()); affine.setMxy(up.getX());affine.setMzx(forward.getX());
affine.setMyx(right.getY());affine.setMyy(up.getY()); affine.setMzy(forward.getY());
affine.setMzx(right.getZ());affine.setMzy(up.getZ());affine.setMzz(forward.getZ());
affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz());
affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz());
affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy());
break;
case CYLINDRICAL:
affine.setMxx(right.getX());affine.setMxy(0);affine.setMzx(forward.getX());
affine.setMyx(0);affine.setMyy(1);affine.setMzy(0);
affine.setMzx(right.getZ()); affine.setMzy(0);affine.setMzz(forward.getZ());
affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz());
affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz());
affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy());
break;
}
}
public BillboardMode getBillboardMode() {
return BillboardMode.SPHERICAL;
}
}
}
At least now the Images are billboarded to the Camera..
Feel free to play with my FPSCamera as well ..
Controls are like any standard 1st person shooter
w = forward, s = back,
a = left-strafe, d = right-strafe,
q & e are up and down.

Creating Tabmenu in j2me

Is there any way to create a Tab Menu in j2me?
I found a code but I am unable to understand it
In this code there is Tab Menu created which is in Canvas class and then Tab menu is created which is totally done in Canvas or painted. The only part I found difficult to grasp was the void go() method and then
When I try to draw anything above and below this code using paint method, it doesn't work - what's the problem?
Below is the code
// Tab Menu CANVAS class
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
public class TabMenuCanvas extends Canvas
{
TabMenu menu = null;
public TabMenuCanvas()
{
menu = new TabMenu(
new String[]{"Home", "News", "Community", "Your files", "Credits", "Events", "Blog", "Upload", "Forum Nokia"},
getWidth() - 20
);
}
protected void keyPressed(int key)
{
int gameAction = getGameAction(key);
if(gameAction == Canvas.RIGHT)
{
menu.goRight();
repaint();
}
else if(gameAction == Canvas.LEFT)
{
menu.goLeft();
repaint();
}
}
protected void paint(Graphics g)
{
g.translate(10, 30);
menu.paint(g);
g.translate(- 10, - 30);
}
}
// Tab Menu Class
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
public class TabMenu
{
int background = 0xffffff;
int bgColor = 0xcccccc;
int bgFocusedColor = 0x0000ff;
int foreColor = 0x000000;
int foreFocusedColor = 0xffffff;
int cornerRadius = 6;
int padding = 2;
int margin = 2;
Font font = Font.getDefaultFont();
int scrollStep = 20;
int selectedTab = 0; //selected tab index
int[] tabsWidth = null; //width of single tabs
int[] tabsLeft = null; //left X coordinate of single tabs
int tabHeight = 0; //height of tabs (equal for all tabs)
String[] tabs = null; //tab labels
int menuWidth = 0; //total menu width
int viewportWidth = 0; //visible viewport width
int viewportX = 0; //current viewport X coordinate
public TabMenu(String[] tabs, int width)
{
this.tabs = tabs;
this.viewportWidth = width;
initialize();
}
void initialize()
{
tabHeight = font.getHeight() + cornerRadius + 2 * padding; //[ same for all tabs]
menuWidth = 0;
tabsWidth = new int[tabs.length];
tabsLeft = new int[tabs.length];
for(int i = 0; i < tabsWidth.length; i++)
{
tabsWidth[i] = font.stringWidth(tabs[i]) + 2 * padding + 2 * cornerRadius;
tabsLeft[i] = menuWidth;
menuWidth += tabsWidth[i];
if(i > 0)
{
menuWidth += margin;
}
}
}
public void goRight()
{
go(+1);
}
public void goLeft()
{
go(-1);
}
private void go(int delta)
{
int newTab = Math.max(0, Math.min(tabs.length - 1, selectedTab + delta));
boolean scroll = true;
if(newTab != selectedTab && isTabVisible(newTab))
{
selectedTab = newTab;
if( (delta > 0 && tabsLeft[selectedTab] + tabsWidth[selectedTab] > viewportX + viewportWidth) ||
(delta < 0 && tabsLeft[selectedTab] < viewportX))
{
scroll = true;
}
else
{
scroll = false;
}
}
if(scroll)
{
viewportX = Math.max(0, Math.min(menuWidth - viewportWidth, viewportX + delta * scrollStep));
}
}
private boolean isTabVisible(int tabIndex)
{
return tabsLeft[tabIndex] < viewportX + viewportWidth &&
tabsLeft[tabIndex] + tabsWidth[tabIndex] >= viewportX;
}
public void paint(Graphics g)
{
int currentX = - viewportX;
g.setClip(0, 0, viewportWidth, tabHeight);
g.setColor(background);
g.fillRect(0, 0, viewportWidth, tabHeight);
for(int i = 0; i < tabs.length; i++)
{
g.setColor(i == selectedTab ? bgFocusedColor : bgColor);
g.fillRoundRect(currentX, 0, tabsWidth[i], tabHeight + cornerRadius, 2 * cornerRadius, 2 * cornerRadius);
g.setColor(i == selectedTab ? foreFocusedColor : foreColor);
g.drawString(tabs[i], currentX + cornerRadius + padding, cornerRadius + padding, Graphics.LEFT | Graphics.TOP);
currentX += tabsWidth[i] + margin;
}
}
}
When I try to draw anything above and below this code using paint method, it doesn't work
what of the paint methods you use to draw above and below? Pay attention that there are two methods named that way - first is in TabMenuCanvas, second is in TabMenu (second method is invoked from TabMenuCanvas#repaint).
whatever you would try to draw in TabMenuCanvas#paint will most likely be overwritten by setClip and fillRect when TabMenu#paint is invoked following repaint request
The only place where one can expect to be able to draw something visible seems to be in TabMenu#paint method, inside the clip area that is set there.
You can use GUI Libraries for J2ME,for example Lightweight User Interface Toolkit (LWUIT),Flemil have "tab menu".You can see list of GUI Libraries here.

Resources