JavaFX: Massive collision detection optimization (Quad Tree) - javafx-2

I was working on massive collision detection for my game(more than 1000 sprites is massive for my game), and i was searching to find a way to implement this, then i reached to quad tree:
http://en.wikipedia.org/wiki/Quadtree
Well it's approach to reduce the number of objects that should be check for collision by dividing them to the groups of objects which have more chance to collide.
I found a java version of quad tree here:
http://gamedev.tutsplus.com/tutorials/implementation/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space/
Then i change it and use it for my javafx game. but the performance wasn't really good for huge number of objects, so i made some optimisation on it.

Well i used AnimationTimer for each tree to check for collisions which has improved performance so much. i think Animation Timer use GPU to process because when i run my code CPU usage doesn't go hight(3% to 5% - 1640 sprites). but if i use Thread instead of AnimationTimer it use much more CPU(about 40% to 50% - 1640 sprites).
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.scene.layout.Region;
import javafx.scene.layout.RegionBuilder;
import javafx.scene.paint.Color;
import viwofx.sprit.Sprite;
import viwofx.ui.GameScene;
public class QuadTree
{
private int MAX_OBJECTS = 10;
private int MAX_LEVELS = 5;
private int level;
private ArrayList<Sprite> sprites;
private ArrayList<Sprite> unAllocatedSprites;
private Region bounds;
private QuadTree[] nodes;
private QuadTree parent;
private AnimationTimer detection;
private boolean detecting = false;
private QuadTree getqt()
{
return this;
}
public QuadTree(QuadTree p, int pLevel, Region pBounds)
{
this.parent = p;
level = pLevel;
sprites = new ArrayList<>(0);
unAllocatedSprites = new ArrayList<>(0);
bounds = pBounds;
nodes = new QuadTree[4];
detection = new AnimationTimer()
{
#Override
public void handle(long l)
{
// This for happens when this node has child nodes and there is some object which can not fit whitin the bounds of child nodes
// these object being checked till they can fit inside the bounds of child nodes then they will be added to correspinding child node,
// or object is out of bounds then it will be pushed to the parent node
for (int i = 0; i < unAllocatedSprites.size(); i++)
{
if (!isInside(unAllocatedSprites.get(i)))
{
pushToParent(unAllocatedSprites.get(i));
continue;
}
int index = getIndex(unAllocatedSprites.get(i));
if (index != -1)
{
nodes[index].add(unAllocatedSprites.remove(i));
}
}
for (int i = 0; i < sprites.size(); i++)
{
Sprite ts = sprites.get(i);
if (isInside(ts))
{
int ii = 0;
for (ii = 0; ii < sprites.size(); ii++)
{
Sprite ts2 = sprites.get(ii);
if (ts != ts2)
{
Your collision detection logic
}
}
if (parent != null)
{
for (ii = 0; ii < parent.getUnAllocatedSprites().size(); ii++)
{
Sprite ts2 = parent.getUnAllocatedSprites().get(ii);
if (ts != ts2 && isInside(ts2))
{
Your collision detection logic
}
}
}
}
else
{
pushToParent(ts);
}
}
}
};
}
public int getLevel()
{
return level;
}
public ArrayList<Sprite> getUnAllocatedSprites()
{
return unAllocatedSprites;
}
// Split the node into 4 subnodes
private void split()
{
double subWidth = (bounds.getPrefWidth() / 2);
double subHeight = (bounds.getPrefHeight() / 2);
double x = bounds.getLayoutX();
double y = bounds.getLayoutY();
nodes[0] = new QuadTree(this, level + 1, RegionBuilder.create().layoutX(x).layoutY(y).prefWidth(subWidth).prefHeight(subHeight).build());
nodes[1] = new QuadTree(this, level + 1, RegionBuilder.create().layoutX(x + subWidth).layoutY(y).prefWidth(subWidth).prefHeight(subHeight).build());
nodes[2] = new QuadTree(this, level + 1, RegionBuilder.create().layoutX(x).layoutY(y + subHeight).prefWidth(subWidth).prefHeight(subHeight).build());
nodes[3] = new QuadTree(this, level + 1, RegionBuilder.create().layoutX(x + subWidth).layoutY(y + subHeight).prefWidth(subWidth).prefHeight(subHeight).build());
}
private int getIndex(Sprite s)
{
int index = -1;
double verticalMidpoint = bounds.getLayoutX() + (bounds.getPrefWidth() / 2);
double horizontalMidpoint = bounds.getLayoutY() + (bounds.getPrefHeight() / 2);
double spriteMaxX = (s.getNode().getTranslateX() + s.getWidth());
double spriteMaxY = (s.getNode().getTranslateY() + s.getHeight());
// Object can completely fit within the top quadrants
boolean topQuadrant = (spriteMaxY < horizontalMidpoint);
// Object can completely fit within the bottom quadrants
boolean bottomQuadrant = (s.getNode().getTranslateY() >= horizontalMidpoint);
// Object can completely fit within the left quadrants
if (s.getNode().getTranslateX() >= bounds.getLayoutX() && spriteMaxX < verticalMidpoint)
{
if (topQuadrant)
{
index = 0;
}
else if (bottomQuadrant)
{
index = 2;
}
}
// Object can completely fit within the right quadrants
else if (s.getNode().getTranslateX() >= verticalMidpoint && (s.getNode().getTranslateX() + s.getWidth()) < (bounds.getLayoutX() + bounds.getPrefWidth()))
{
if (topQuadrant)
{
index = 1;
}
else if (bottomQuadrant)
{
index = 3;
}
}
return index;
}
public boolean isInside(Sprite s)
{
double maxX = bounds.getLayoutX() + bounds.getPrefWidth();
double maxY = bounds.getLayoutY() + bounds.getPrefHeight();
// Object can completely fit within the left quadrants
if (s.getNode().getTranslateX() >= bounds.getLayoutX() && (s.getNode().getTranslateX() + s.getWidth()) < maxX && s.getNode().getTranslateY() >= bounds.getLayoutY() && (s.getNode().getTranslateY() + s.getHeight()) < maxY)
{
return true;
}
if (parent != null && parent.getUnAllocatedSprites().contains(s))
{
return true;
}
return false;
}
public void pushToParent(Sprite s)
{
sprites.remove(s);
unAllocatedSprites.remove(s);
if (parent == null)
{
//System.out.println("parent");
if (!unAllocatedSprites.contains(s))
{
unAllocatedSprites.add(s);
}
return;
}
parent.add(s);
if (sprites.size() < 1 && unAllocatedSprites.size() < 1)
{
stopDetection();
}
}
public void add(viwofx.sprit.Sprite sprite)
{
// if sprite is not fit in the bounds of node, it will be pushed to the parent node.
// this is a optimization for when child node push a object to this node and object still is not fit in the bounds this node,
// so it will be pushed to the parent node till object can be fited whitin the node bounds
// this if prevent of out of bounds object to being added to unAllocatedSprites and then being pushed to parent
if (!isInside(sprite))
{
pushToParent(sprite);
return;
}
// if tree has been splited already add sprite to corrosponding child
if (nodes[0] != null)
{
int index = getIndex(sprite);
if (index != -1)
{
nodes[index].add(sprite);
return;
}
else
{
unAllocatedSprites.add(sprite);
return;
}
}
sprites.add(sprite);
if (!detecting)
{
startDetection();
}
if (sprites.size() > MAX_OBJECTS && level < MAX_LEVELS)
{
if (nodes[0] == null)
{
split();
}
int i = 0;
while (i < sprites.size())
{
int index = getIndex(sprites.get(i));
if (index != -1)
{
nodes[index].add(sprites.remove(i));
}
else
{
unAllocatedSprites.add(sprites.remove(i));
}
}
}
}
public List<Sprite> retrieve(List<Sprite> returnObjects, Sprite pRect)
{
int index = getIndex(pRect);
if (index != -1 && nodes[0] != null)
{
nodes[index].retrieve(returnObjects, pRect);
}
returnObjects.addAll(sprites);
return returnObjects;
}
public void startDetection()
{
detecting = true;
detection.start();
}
public void stopDetection()
{
//detecting = false;
//detection.stop();
}
}
I hope this will be helpful for you.

Related

Recycle View Inside Of Fragment Not Calling OnViewCreate

So basically, it's as the title says. I'm implementing a recycle view inside a fragment to create a map on one part of the screen. I need two recycle views on the page but only one isn't working. When I run the debugger in Android Studio it never reaches the override methods in the Adapter class. Below is the code, unfortunately it's a lot.
I should also mention
Number of items is caclulated and is not zero
Adapter is added to recycler view
Layout manager is added to recycler view
Why aren't any of the overriden methods being called?
Please let me know if you require further information
FRAGMENT
public class FragmentMap extends Fragment {
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
MapData mapData;
StructureData structures;
public FragmentMap(MapData mapData, StructureData structures)
{
this.mapData = mapData;
this.structures = structures;
}
public FragmentMap() {
// Required empty public constructor
}
public static FragmentMap newInstance(String param1, String param2) {
FragmentMap fragment = new FragmentMap();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_map, container, false);
RecyclerView rv = view.findViewById(R.id.mapRecyclerView);
SelectorAdapter myAdapter = new SelectorAdapter(structures);
MapAdapter mapAdapter = new MapAdapter(mapData);
rv.setAdapter(mapAdapter);
rv.setLayoutManager(new GridLayoutManager(
getActivity(),
MapData.HEIGHT,
GridLayoutManager.HORIZONTAL,
false));
return view;
}
}
ADAPTER
public class MapAdapter extends RecyclerView.Adapter<MapViewHolder>{
MapData mapData;
public MapAdapter(MapData mapData)
{
this.mapData = mapData;
}
#NonNull
#Override
public MapViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.grid_cell,parent,false);
MapViewHolder myViewHolder = new MapViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(#NonNull MapViewHolder holder, int position) {
int row = position % MapData.HEIGHT;
int col = position / MapData.HEIGHT;
MapElement mapElement = mapData.get(row, col);
holder.cellOne.setImageResource(mapElement.getNorthEast());
}
#Override
public int getItemCount() {
Log.d("TAG", "getItemCount: " + MapData.HEIGHT * MapData.WIDTH);
return MapData.HEIGHT * MapData.WIDTH;
}
}
VIEWHOLDER
public class MapViewHolder extends RecyclerView.ViewHolder {
ImageView cellOne, cellTwo, cellThree, cellFour, structure;
ConstraintLayout parent;
public MapViewHolder(#NonNull View itemView) {
super(itemView);
cellOne = itemView.findViewById(R.id.imageOne);
cellTwo = itemView.findViewById(R.id.imageTwo);
cellThree = itemView.findViewById(R.id.imageThree);
cellFour = itemView.findViewById(R.id.imageFour);
parent = itemView.findViewById(R.id.parent);
int size = parent.getMeasuredHeight() / MapData.HEIGHT + 1;
ViewGroup.LayoutParams lp = itemView.getLayoutParams();
lp.width = size;
lp.height = size;
}
}
MAPDATA
public class MapData
{
public static final int WIDTH = 30;
public static final int HEIGHT = 10;
private static final int WATER = R.drawable.ic_water;
private static final int[] GRASS = {R.drawable.ic_grass1, R.drawable.ic_grass2,
R.drawable.ic_grass3, R.drawable.ic_grass4};
private static final Random rng = new Random();
private MapElement[][] grid;
private static MapData instance = null;
public static MapData get()
{
if(instance == null)
{
instance = new MapData(generateGrid());
}
return instance;
}
private static MapElement[][] generateGrid()
{
final int HEIGHT_RANGE = 256;
final int WATER_LEVEL = 112;
final int INLAND_BIAS = 24;
final int AREA_SIZE = 1;
final int SMOOTHING_ITERATIONS = 2;
int[][] heightField = new int[HEIGHT][WIDTH];
for(int i = 0; i < HEIGHT; i++)
{
for(int j = 0; j < WIDTH; j++)
{
heightField[i][j] =
rng.nextInt(HEIGHT_RANGE)
+ INLAND_BIAS * (
Math.min(Math.min(i, j), Math.min(HEIGHT - i - 1, WIDTH - j - 1)) -
Math.min(HEIGHT, WIDTH) / 4);
}
}
int[][] newHf = new int[HEIGHT][WIDTH];
for(int s = 0; s < SMOOTHING_ITERATIONS; s++)
{
for(int i = 0; i < HEIGHT; i++)
{
for(int j = 0; j < WIDTH; j++)
{
int areaSize = 0;
int heightSum = 0;
for(int areaI = Math.max(0, i - AREA_SIZE);
areaI < Math.min(HEIGHT, i + AREA_SIZE + 1);
areaI++)
{
for(int areaJ = Math.max(0, j - AREA_SIZE);
areaJ < Math.min(WIDTH, j + AREA_SIZE + 1);
areaJ++)
{
areaSize++;
heightSum += heightField[areaI][areaJ];
}
}
newHf[i][j] = heightSum / areaSize;
}
}
int[][] tmpHf = heightField;
heightField = newHf;
newHf = tmpHf;
}
MapElement[][] grid = new MapElement[HEIGHT][WIDTH];
for(int i = 0; i < HEIGHT; i++)
{
for(int j = 0; j < WIDTH; j++)
{
MapElement element;
if(heightField[i][j] >= WATER_LEVEL)
{
boolean waterN = (i == 0) || (heightField[i - 1][j] < WATER_LEVEL);
boolean waterE = (j == WIDTH - 1) || (heightField[i][j + 1] < WATER_LEVEL);
boolean waterS = (i == HEIGHT - 1) || (heightField[i + 1][j] < WATER_LEVEL);
boolean waterW = (j == 0) || (heightField[i][j - 1] < WATER_LEVEL);
boolean waterNW = (i == 0) || (j == 0) || (heightField[i - 1][j - 1] < WATER_LEVEL);
boolean waterNE = (i == 0) || (j == WIDTH - 1) || (heightField[i - 1][j + 1] < WATER_LEVEL);
boolean waterSW = (i == HEIGHT - 1) || (j == 0) || (heightField[i + 1][j - 1] < WATER_LEVEL);
boolean waterSE = (i == HEIGHT - 1) || (j == WIDTH - 1) || (heightField[i + 1][j + 1] < WATER_LEVEL);
boolean coast = waterN || waterE || waterS || waterW ||
waterNW || waterNE || waterSW || waterSE;
grid[i][j] = new MapElement(
!coast,
choose(waterN, waterW, waterNW,
R.drawable.ic_coast_north, R.drawable.ic_coast_west,
R.drawable.ic_coast_northwest, R.drawable.ic_coast_northwest_concave),
choose(waterN, waterE, waterNE,
R.drawable.ic_coast_north, R.drawable.ic_coast_east,
R.drawable.ic_coast_northeast, R.drawable.ic_coast_northeast_concave),
choose(waterS, waterW, waterSW,
R.drawable.ic_coast_south, R.drawable.ic_coast_west,
R.drawable.ic_coast_southwest, R.drawable.ic_coast_southwest_concave),
choose(waterS, waterE, waterSE,
R.drawable.ic_coast_south, R.drawable.ic_coast_east,
R.drawable.ic_coast_southeast, R.drawable.ic_coast_southeast_concave),
null);
}
else
{
grid[i][j] = new MapElement(
false, WATER, WATER, WATER, WATER, null);
}
}
}
return grid;
}
private static int choose(boolean nsWater, boolean ewWater, boolean diagWater,
int nsCoastId, int ewCoastId, int convexCoastId, int concaveCoastId)
{
int id;
if(nsWater)
{
if(ewWater)
{
id = convexCoastId;
}
else
{
id = nsCoastId;
}
}
else
{
if(ewWater)
{
id = ewCoastId;
}
else if(diagWater)
{
id = concaveCoastId;
}
else
{
id = GRASS[rng.nextInt(GRASS.length)];
}
}
return id;
}
protected MapData(MapElement[][] grid)
{
this.grid = grid;
}
public void regenerate()
{
this.grid = generateGrid();
}
public MapElement get(int i, int j)
{
return grid[i][j];
}
}
MAP ELEMENT
public class MapElement
{
private final boolean buildable;
private final int terrainNorthWest;
private final int terrainSouthWest;
private final int terrainNorthEast;
private final int terrainSouthEast;
private Structure structure;
public MapElement(boolean buildable, int northWest, int northEast,
int southWest, int southEast, Structure structure)
{
this.buildable = buildable;
this.terrainNorthWest = northWest;
this.terrainNorthEast = northEast;
this.terrainSouthWest = southWest;
this.terrainSouthEast = southEast;
this.structure = structure;
}
public boolean isBuildable()
{
return buildable;
}
public int getNorthWest()
{
return terrainNorthWest;
}
public int getSouthWest()
{
return terrainSouthWest;
}
public int getNorthEast()
{
return terrainNorthEast;
}
public int getSouthEast()
{
return terrainSouthEast;
}
/**
* Retrieves the structure built on this map element.
* #return The structure, or null if one is not present.
*/
public Structure getStructure()
{
return structure;
}
public void setStructure(Structure structure)
{
this.structure = structure;
}
}
You are initializing only one RecyclerView in onCreateView, if you want to have two Recyclerviews you just need to initialize second one in the same way.

Threaded Terrain Generation 3d Bug Unity

So I have this weird bug in my terrain generation project
I have 2 methods that were supposed to fix offsets for different noise scales between chunks but for some reason when I implimented threading it stopped working in seemingly random areas. I'm 100% sure this worked before threading.
public struct GetMeshData : IJob
{
public ChunkData chunkData;
public NativeArray<float> noiseMap;
public MeshData meshData;
public void Execute()
{
for (int z = 0; z < chunkData.chunkSize + 1; z++)
{
for (int x = 0; x < chunkData.chunkSize + 1; x++)
{
noiseMap[z * (chunkData.chunkSize + 1) + x] = Noise.GetNoiseValue(chunkData, x, z);
}
}
MeshData temporaryMeshData = MeshGeneration.GenerateMesh(chunkData, noiseMap, meshData.triangles, meshData.vertices, meshData.uvs);
meshData = temporaryMeshData;
}
public GetMeshData(ChunkData chunkData, NativeArray<Vector3> vertices, NativeArray<Vector2> uvs, NativeArray<int> triangles, NativeArray<float> noiseMap)
{
this.chunkData = chunkData;
this.meshData = new MeshData(vertices, uvs, triangles);
this.noiseMap = noiseMap;
}
}
this is the job while this is my code to handle it
for (int i = 0; i < meshJob.Count; i++)
{
if (meshJob[i].IsCompleted)
{
meshJob[i].Complete();
TerrainChunk currentChunk = chunks[meshJobData[i].chunkData.chunkPosition.x,
meshJobData[i].chunkData.chunkPosition.y];
PostMeshGeneration(currentChunk, meshJobData[i].meshData);
toBeAdjusted.Add(currentChunk);
meshJobData[i].noiseMap.Dispose();
meshJobData[i].meshData.vertices.Dispose();
meshJobData[i].meshData.triangles.Dispose();
meshJobData[i].meshData.uvs.Dispose();
meshJob.RemoveAt(i);
meshJobData.RemoveAt(i);
}
}
this is what I do after the job is complete:
public void PostMeshGeneration(TerrainChunk chunk, MeshData meshData)
{
Mesh mesh = new Mesh();
mesh.vertices = meshData.vertices.ToArray();
mesh.triangles = meshData.triangles.ToArray();
mesh.uv = meshData.uvs.ToArray();
chunk.GetChunkGameObject().GetComponent<MeshFilter>().mesh = mesh;
ReadjustMeshCollider(chunk);
}
lastly this is trying to fix the scale offsets
if (toBeAdjusted.Count != 0 && meshJob.Count == 0 && meshJobData.Count == 0)
{
while (toBeAdjusted.Count > 0)
{
TerrainChunk currentChunk = toBeAdjusted[0];
Mesh mesh = currentChunk.GetChunkGameObject().GetComponent<MeshFilter>().mesh;
AdjustNoiseScaling(currentChunk);
FixCornerVerticesOffset(currentChunk);
mesh.UploadMeshData(false);
ApplyColorsToChunk(currentChunk, mesh.vertices);
ReadjustMeshCollider(currentChunk);
toBeAdjusted.RemoveAt(0);
}
}
if you need to see more code go to the github link https://github.com/htmhell69/TerrainGenerationUnity

How to have different color brushes using multi touch?

I am trying to make it so each finger on the screen has a different color as it paints its path. I am using pointers to create the path and toyed with assigning the pointer IDs a different color per number but no result. In the code below I am trying to make the first finger blue then when another finger begins drawing it would turn red. Currently the code makes all the paint blue but when 3 fingers are on the screen it all changes red. Any help is appreciated thank you
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(idColor == 1)
mFingerPaint.setColor(Color.BLUE);
if(idColor == 2)
mFingerPaint.setColor(Color.RED);
for (Path completedPath : mCompletedPaths) {
canvas.drawPath(completedPath, mFingerPaint);
}
for (Path fingerPath : mFingerPaths) {
if (fingerPath != null) {
canvas.drawPath(fingerPath, mFingerPaint);
}
}
}
public boolean onTouchEvent(MotionEvent event) {
int pointerCount = event.getPointerCount();
int cappedPointerCount = pointerCount > MAX_FINGERS ? MAX_FINGERS : pointerCount;
// get pointer index from the event object
int actionIndex = event.getActionIndex();
// get masked (not specific to a pointer) action
int action = event.getActionMasked();
// get pointer ID
int id = event.getPointerId(actionIndex);
idColor = id;
if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && id < MAX_FINGERS)
{
mFingerPaths[id] = new Path();
mFingerPaths[id].moveTo(event.getX(actionIndex), event.getY(actionIndex));
}
else if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP) && id < MAX_FINGERS)
{
mFingerPaths[id].setLastPoint(event.getX(actionIndex), event.getY(actionIndex));
mCompletedPaths.add(mFingerPaths[id]);
mFingerPaths[id].computeBounds(mPathBounds, true);
invalidate((int) mPathBounds.left, (int) mPathBounds.top, (int) mPathBounds.right, (int) mPathBounds.bottom);
mFingerPaths[id] = null;
}
for(int i = 0; i < cappedPointerCount; i++) {
if(mFingerPaths[i] != null)
{
int index = event.findPointerIndex(i);
mFingerPaths[i].lineTo(event.getX(index), event.getY(index));
mFingerPaths[i].computeBounds(mPathBounds, true);
invalidate((int) mPathBounds.left, (int) mPathBounds.top, (int) mPathBounds.right, (int) mPathBounds.bottom);
}
}
return true;
}
}

scanner input that can stop loop anytime?

I'm making a clock and I want a specific input to be able to stop it.
Any ideas?
Here's my code:
public static void main(String[] args) {
//...
Scanner input = new Scanner(System.in);
//...
int s = 0;
int m = 0;
int h = 0;
clock: // label to break a outer most loop
for(int i = 0; i<2; i++) {
on = Boolean.parseBoolean(onValue[i]);
while (on = true) {
for (i=1; i<=60; i++) {
s++;
if(input2check.compareTo("") == 0) {
on = Boolean.parseBoolean(onValue[1]);
break clock;
}
if (s == 60) {
s = 0;
}
System.out.println(s + " sec" );
}
}
}
}
First idea: do the Scanner in a separate thread
As you've noticed, Scanner.nextLine blocks until the user has input something and pressed Enter. We can ignore this problem if our clock does not run in the same thread as the Scanner loop. Let's use a daemon thread for reading user input.
I've picked the word "off" as stopping input from the user.
import java.util.Scanner;
public class Clock1 {
private static volatile boolean on = true;
public static void main(String[] args) throws InterruptedException {
int s = 0, m = 0, h = 0;
Thread t = new Thread(() -> {
try (Scanner scan = new Scanner(System.in)) {
while (on) {
String input = scan.nextLine();
if (input == null)
continue;
if ("off".equalsIgnoreCase(input.trim())) {
on = false;
}
}
}
});
t.setName("console");
t.setDaemon(true);
t.start();
clock: for (int i = 0; i < 2; i++) { //Why this loop ?
long prevTime = System.nanoTime();
long diff;
while (on) {
if (++s % 60 == 0) {
s = 0;
if (++m % 60 == 0) {
m = 0;
++h;
}
}
do {
diff = System.nanoTime() - prevTime;
Thread.sleep(Math.min(1000 - diff / 1_000_000L, 100));
} while (on && diff < 1_000_000_000L);
if (!on)
break clock;
prevTime = System.nanoTime();
System.out.println(String.format("%02d:%02d:%02d", h, m, s));
}
}
System.out.println("Clock stopped");
}
}
You need to be aware that timing a clock with Thread.sleep is inaccurate, though. But it may be enough for your problem...
I tried to compensate for this using several checks for System.nanoTime per second, but accurate timing would require a more complex algorithm. It also allows for about immediate reaction to user input.
Second idea: use a BufferedReader rather than a Scanner
Using a BufferedReader allows us to avoid blocking execution while we check if the buffer is ready, and then only read user input when they've pressed Enter, thus bypassing the problem with Scanner.nextLine.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class Clock2 {
private static volatile boolean on = true;
public static void main(String[] args) throws InterruptedException, IOException {
int s = 0, m = 0, h = 0;
try (BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
clock: for (int i = 0; i < 2; i++) { // Why this loop ?
long prevTime = System.nanoTime();
long diff;
while (on) {
if (++s % 60 == 0) {
s = 0;
if (++m % 60 == 0) {
m = 0;
++h;
}
}
do {
diff = System.nanoTime() - prevTime;
Thread.sleep(Math.min(1000 - diff / 1_000_000L, 100));
String line;
while (on && rdr.ready() && (line = rdr.readLine()) != null) {
if (line.trim().equalsIgnoreCase("off"))
on = false;
}
} while (on && diff < 1_000_000_000L);
if (!on)
break clock;
prevTime = System.nanoTime();
System.out.println(String.format("%02d:%02d:%02d", h, m, s));
}
}
}
System.out.println("Clock stopped");
}
}
Third idea: use a ScheduledExecutor
This time, the clock is timed by a ScheduledExecutor that executes the increment loop once per second in a separate thread as the main program.
Accumulated values had to be stored in a class, so I also implemented AutoCloseable with class Clock3 to ease shutting down the executor when the clock stops.
User input is read in a loop in the main thread using a Scanner until we hit the word "off".
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Clock3 implements AutoCloseable {
private static volatile boolean on = true;
private ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
private int s = 0, m = 0, h = 0;
public static void main(String[] args) throws Exception {
try (Clock3 clock = new Clock3()) {
try (Scanner scan = new Scanner(System.in)) {
while (on) {
String input = scan.nextLine();
if (input == null)
continue;
if ("off".equalsIgnoreCase(input.trim())) {
on = false;
}
}
}
}
System.out.println("Clock stopped");
}
public Clock3() {
exec.scheduleAtFixedRate(() -> {
if (++s % 60 == 0) {
s = 0;
if (++m % 60 == 0) {
m = 0;
++h;
}
}
if (on)
System.out.println(String.format("%02d:%02d:%02d", h, m, s));
}, 0, 1, TimeUnit.SECONDS);
}
#Override
public void close() throws Exception {
exec.shutdownNow();
}
}
I don't know how accurate this scheduling is guaranteed to be. From what I can read from documentation for ScheduledExecutorService.scheduleAtFixedRate:
If any execution of this task takes longer than its period, then subsequent executions may start late.
Which will never be the case with this small increment algorithm.
There are probably better ways of scheduling that however, but that was not the point of your question...

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.

Resources