How do I write a recursive Quadtree traversal method in java? - java

I understand the concept but I have no idea how to write the code out in java. From my understanding you travel down to check if the next node is a leaf, if not you keep going down, then you go back up and do it for the others. But how do I code this?
Here's my constructor for the quadtree
public class QuadtreeBitmap {
// location
private final int x;
private final int y;
// height and width
private final int size;
// if leaf
private boolean leaf;
// either Colour.BLACK or Colour.WHITE
private Colour colour;
// otherwise
private QuadtreeBitmap northWest;
private QuadtreeBitmap northEast;
private QuadtreeBitmap southWest;
private QuadtreeBitmap southEast;
/**
* Constructs a new quadtree bitmap with height and width equal to the specified size, and
* every pixel initialized to the given colour. The specified size must be a power of 2,
* and must be greater than zero.
*
* #param size the height and width of this quadtree bitmap
* #param colour the colour with which to initialize every pixel in this quadtree bitmap
*/
public QuadtreeBitmap(int size, Colour colour) {
this(0, 0, size, colour);
}
/**
* Constructs a new quadtree bitmap with height and width equal to the specified size, and
* every pixel initialized to white. The specified size must be a power of 2, and must be
* greater than zero.
*
* #param size the height and width of this quadtree bitmap
*/
public QuadtreeBitmap(int size) {
this(0, 0, size, Colour.WHITE);
}
// specifying location only supported internally
private QuadtreeBitmap(int x, int y, int size, Colour colour) {
// only supporting power-of-2 dimensions
if (!powerOfTwo(size)) {
throw new IllegalArgumentException("Size not power of 2.");
}
this.x = x;
this.y = y;
this.size = size;
this.leaf = true;
this.colour = colour;
this.northWest = null;
this.northEast = null;
this.southWest = null;
this.southEast = null;
}
// combining quads to form tree only supported internally, assumes well-positioned
private QuadtreeBitmap(int x, int y, int size, List<QuadtreeBitmap> quads) {
this(x, y, size, Colour.WHITE);
northWest = quads.get(0);
northEast = quads.get(1);
southWest = quads.get(2);
southEast = quads.get(3);
this.leaf = false;
}
// for any basic task which needs to be repeated all four quadrants
private List<QuadtreeBitmap> quadrants() {
return Arrays.asList(northWest, northEast, southWest, southEast);
}
// retrieves the quadrant within which the specified location lies
private QuadtreeBitmap quadrantOf(int x, int y) {
for (QuadtreeBitmap quad : quadrants()) {
if (quad.containsPoint(x, y)) {
return quad;
}
}
return null;
}
public int getSize() {
return size;
}
Essentially I need to implement a whole bunch of methods for an assignment, like count pixels of certain colour, etc but I have no idea on how to get started

Let's simplify. How would you write a traversal on a binary tree?
Well, you'd write a function like
public int CountNodes(){
int leftcount = (this.left==null) ? 0 : this.left.CountNodes();
int rightcount = (this.right==null) ? 0 : this.right.CountNodes();
return 1 + leftcount + rightcount;
}
So you want to implement this with your fixed internal values northEast, northWest, southEast, southWest instead of 'left' and 'right'. Notice that the +1 in the node value accounts for the node itself, making an explicit 'leaf check' unnecessary.
It should be mentioned that you need to be aware of stack size limits in your environment. Foxpro used to have a limited stack height of 32 calls, and recursive calls like this can get deep with very large subject materials.

Related

How to create a tick method that would make sure all blocks are working in accordance with gravity

public class Physics {
private int weight;
private int durability;
private int xPos;
private int yPos;
private int x;
private int y;
private Level level;
int tileWidth = Main.WIDTH / 16;
int tileHeight = Main.HEIGHT / 16;
public Physics(Level level) {
this.level = level;
}
private void timer(int time) {//this is a subroutine that creates a slight pause
long currTime = System.currentTimeMillis();//this is getting the current time in miliseconds
long target = currTime + time;//this gets the current time and sets the target pause time to the current time plus the time you want to pause for in miliseconds
while(System.currentTimeMillis() < target);//this makes the program idle for the target amount of time
}
public void gravity(int weight,int durability, int xPos, int yPos, Tile tile, TileType tileTypes, boolean newblock){//this is the physics subroutine for when you place a block
if(tile.getType() == TileType.SKY) {//if the tile you clicked on is a sky tile
tile.setType(tileTypes, weight, durability);//it sets the new tiles type weight and durability to the ones that were entered dependant on which block the user chose from the pallet
timer(50);//this is the pause time between the block falling one y coordinate down
tile.setType(TileType.SKY, 0, 0);//once the tile is drawn one tile down it changeds the tile above it back to a sky tile
yPos++;//this the increases the y possition by one to foxus on the next block down
gravity(weight,durability, xPos, yPos, level.tiles[xPos][yPos],tileTypes,true);//this then calls the subroutine within itself and this keeps going until it hits the bottom
}
//if (newblock == true) {
if(tile.getType() != TileType.SKY){//once the tile below the tile we're focusing on is not a sky tile this if statement is run
Tile tile1 = level.tiles[xPos][yPos-1];//it sets the tile were focusing on to the tile above the non skytile
tile1.setType(tileTypes, weight, durability);//it then sets this tile to the tile you chose from the pallet
weight = tile1.getWeight();
durability = tile1.getDurability();
System.out.println(weight+"kg, "+durability);
}
//}
//if (newblock == false) {
//}
}
public void blockDestruction(Tile tile) {
}
public void tick(int x, int y) {
Tile tile = level.tiles[x][y];
Tile tile1 = level.tiles[x][y-1];
TileType tilecheck = tile1.getType();
if(tile1.getType() != TileType.SKY && tile1.getType() != TileType.GRASS) {
gravity(tile.getWeight(),tile.getDurability(),x,y,tile,tile.getType(),false);
y = y-1;
/*for (int i = y-1; i>0;i--) {
if (tilecheck != TileType.SKY) {
tile1 = level.tiles[x][i];
tilecheck = tile1.getType();
}else
tile1 = level.tiles[x][i+1];
tile1.setType(TileType.SKY);
}*/
}
}
}
the tick method at the end there is what I'm struggling with what im trying to create is a method that i would be able to put inside of my tick method in my main class which will check all blocks to see if there is a block underneath them and if not then it will apply the gravity method to that block. Sorry for the code being such a mess ive been trying loads of solutions and have gotten completely stuck so it is currently a bit of a mess if anything need explaining then please let me know.
this is what the program looks like

ImageView failing to Resize at runtime

I was looking Cropper and image crooper open source App by Edmodo, it does a pretty good job except instead of Image cropping I want to use it to scale my images, my Logic Was everytime the cropper Window/View changes in size I would change the height and width of my imageview to match the Crop view but everytime I do this, nothing happens, but looking at LogCat it seems my height and width do change except the imageview still looks the same even after changes below is the Class, my changes are commented in the onSizeChanged()
/*
* Copyright 2013, Edmodo, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License.
* You may obtain a copy of the License in the LICENSE file, or 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 com.theartofdev.edmodo.cropper.cropwindow;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import com.theartofdev.edmodo.cropper.CropImageView;
import com.theartofdev.edmodo.cropper.cropwindow.edge.Edge;
import com.theartofdev.edmodo.cropper.cropwindow.handle.Handle;
import com.theartofdev.edmodo.cropper.util.AspectRatioUtil;
import com.theartofdev.edmodo.cropper.util.HandleUtil;
import com.theartofdev.edmodo.cropper.util.PaintUtil;
/**
* A custom View representing the crop window and the shaded background outside the crop window.
*/
public class CropOverlayView extends View {
//region: Fields and Consts
private static final int SNAP_RADIUS_DP = 6;
private static final float DEFAULT_SHOW_GUIDELINES_LIMIT = 100;
// Gets default values from PaintUtil, sets a bunch of values such that the
// corners will draw correctly
private static final float DEFAULT_CORNER_THICKNESS_DP = PaintUtil.getCornerThickness();
private static final float DEFAULT_LINE_THICKNESS_DP = PaintUtil.getLineThickness();
private static final float DEFAULT_CORNER_OFFSET_DP = (DEFAULT_CORNER_THICKNESS_DP / 2) - (DEFAULT_LINE_THICKNESS_DP / 2);
private static final float DEFAULT_CORNER_EXTENSION_DP = DEFAULT_CORNER_THICKNESS_DP / 2
+ DEFAULT_CORNER_OFFSET_DP;
private static final float DEFAULT_CORNER_LENGTH_DP = 20;
private static final int GUIDELINES_ON_TOUCH = 1;
private static final int GUIDELINES_ON = 2;
private static RectF mRectF = new RectF();
/**
* The Paint used to draw the white rectangle around the crop area.
*/
private Paint mBorderPaint;
/**
* The Paint used to draw the guidelines within the crop area when pressed.
*/
private Paint mGuidelinePaint;
/**
* The Paint used to draw the corners of the Border
*/
private Paint mCornerPaint;
/**
* The Paint used to darken the surrounding areas outside the crop area.
*/
private Paint mBackgroundPaint;
/**
* The bounding box around the Bitmap that we are cropping.
*/
private Rect mBitmapRect;
// The radius of the touch zone (in pixels) around a given Handle.
private float mHandleRadius;
// An edge of the crop window will snap to the corresponding edge of a
// specified bounding box when the crop window edge is less than or equal to
// this distance (in pixels) away from the bounding box edge.
private float mSnapRadius;
// Holds the x and y offset between the exact touch location and the exact
// handle location that is activated. There may be an offset because we
// allow for some leeway (specified by mHandleRadius) in activating a
// handle. However, we want to maintain these offset values while the handle
// is being dragged so that the handle doesn't jump.
private Pair<Float, Float> mTouchOffset;
// The Handle that is currently pressed; null if no Handle is pressed.
private Handle mPressedHandle;
// Flag indicating if the crop area should always be a certain aspect ratio
// (indicated by mTargetAspectRatio).
private boolean mFixAspectRatio = CropImageView.DEFAULT_FIXED_ASPECT_RATIO;
// Floats to save the current aspect ratio of the image
private int mAspectRatioX = CropImageView.DEFAULT_ASPECT_RATIO_X;
private int mAspectRatioY = CropImageView.DEFAULT_ASPECT_RATIO_Y;
// The aspect ratio that the crop area should maintain; this variable is
// only used when mMaintainAspectRatio is true.
private float mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
/**
* Instance variables for customizable attributes
*/
private int mGuidelines;
/**
* The shape of the cropping area - rectangle/circular.
*/
private CropImageView.CropShape mCropShape;
// Whether the Crop View has been initialized for the first time
private boolean initializedCropWindow = false;
// Instance variables for the corner values
private float mCornerExtension;
private float mCornerOffset;
private float mCornerLength;
//endregion
public CropOverlayView(Context context) {
super(context);
init(context);
}
public CropOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* Informs the CropOverlayView of the image's position relative to the
* ImageView. This is necessary to call in order to draw the crop window.
*
* #param bitmapRect the image's bounding box
*/
public void setBitmapRect(Rect bitmapRect) {
mBitmapRect = bitmapRect;
initCropWindow(mBitmapRect);
}
/**
* Resets the crop overlay view.
*/
public void resetCropOverlayView() {
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
/**
* The shape of the cropping area - rectangle/circular.
*/
public void setCropShape(CropImageView.CropShape cropShape) {
mCropShape = cropShape;
invalidate();
}
/**
* Sets the guidelines for the CropOverlayView to be either on, off, or to
* show when resizing the application.
*
* #param guidelines Integer that signals whether the guidelines should be
* on, off, or only showing when resizing.
*/
public void setGuidelines(int guidelines) {
if (guidelines < 0 || guidelines > 2)
throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation.");
else {
mGuidelines = guidelines;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
}
/**
* Sets whether the aspect ratio is fixed or not; true fixes the aspect
* ratio, while false allows it to be changed.
*
* #param fixAspectRatio Boolean that signals whether the aspect ratio
* should be maintained.
*/
public void setFixedAspectRatio(boolean fixAspectRatio) {
mFixAspectRatio = fixAspectRatio;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
/**
* Sets the X value of the aspect ratio; is defaulted to 1.
*
* #param aspectRatioX int that specifies the new X value of the aspect
* ratio
*/
public void setAspectRatioX(int aspectRatioX) {
if (aspectRatioX <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioX = aspectRatioX;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
}
/**
* Sets the Y value of the aspect ratio; is defaulted to 1.
*
* #param aspectRatioY int that specifies the new Y value of the aspect
* ratio
*/
public void setAspectRatioY(int aspectRatioY) {
if (aspectRatioY <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioY = aspectRatioY;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
if (initializedCropWindow) {
initCropWindow(mBitmapRect);
invalidate();
}
}
}
/**
* Sets all initial values, but does not call initCropWindow to reset the
* views. Used once at the very start to initialize the attributes.
*
* #param guidelines Integer that signals whether the guidelines should be
* on, off, or only showing when resizing.
* #param fixAspectRatio Boolean that signals whether the aspect ratio
* should be maintained.
* #param aspectRatioX float that specifies the new X value of the aspect
* ratio
* #param aspectRatioY float that specifies the new Y value of the aspect
* ratio
*/
public void setInitialAttributeValues(int guidelines, boolean fixAspectRatio, int aspectRatioX, int aspectRatioY) {
if (guidelines < 0 || guidelines > 2)
throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation.");
else
mGuidelines = guidelines;
mFixAspectRatio = fixAspectRatio;
if (aspectRatioX <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioX = aspectRatioX;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
}
if (aspectRatioY <= 0)
throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0.");
else {
mAspectRatioY = aspectRatioY;
mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;
}
}
//region: Private methods
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Initialize the crop window here because we need the size of the view
// to have been determined.
//my changes to update imageview height and width
CropImageView.mImageView.getLayoutParams().height = h;
CropImageView.mImageView.getLayoutParams().width = w;
CropImageView.mImageView.requestLayout();
CropImageView.mImageView.invalidate();
initCropWindow(mBitmapRect);
// CropImageView.mImageView.invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw translucent background for the cropped area.
drawBackground(canvas, mBitmapRect);
if (showGuidelines()) {
// Determines whether guidelines should be drawn or not
if (mGuidelines == GUIDELINES_ON) {
drawRuleOfThirdsGuidelines(canvas);
} else if (mGuidelines == GUIDELINES_ON_TOUCH) {
// Draw only when resizing
if (mPressedHandle != null)
drawRuleOfThirdsGuidelines(canvas);
}
}
float w = mBorderPaint.getStrokeWidth();
float l = Edge.LEFT.getCoordinate() + w;
float t = Edge.TOP.getCoordinate() + w;
float r = Edge.RIGHT.getCoordinate() - w;
float b = Edge.BOTTOM.getCoordinate() - w;
if (mCropShape == CropImageView.CropShape.RECTANGLE) {
// Draw rectangle crop window border.
canvas.drawRect(l, t, r, b, mBorderPaint);
drawCorners(canvas);
} else {
// Draw circular crop window border
mRectF.set(l, t, r, b);
canvas.drawOval(mRectF, mBorderPaint);
}
}
#Override
public boolean onTouchEvent(#SuppressWarnings("NullableProblems") MotionEvent event) {
// If this View is not enabled, don't allow for touch interactions.
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onActionDown(event.getX(), event.getY());
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
onActionUp();
return true;
case MotionEvent.ACTION_MOVE:
onActionMove(event.getX(), event.getY());
getParent().requestDisallowInterceptTouchEvent(true);
return true;
default:
return false;
}
}
private void init(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
mHandleRadius = HandleUtil.getTargetRadius(context);
mSnapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SNAP_RADIUS_DP,
displayMetrics);
mBorderPaint = PaintUtil.newBorderPaint(context);
mGuidelinePaint = PaintUtil.newGuidelinePaint();
mBackgroundPaint = PaintUtil.newBackgroundPaint(context);
mCornerPaint = PaintUtil.newCornerPaint(context);
// Sets the values for the corner sizes
mCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_CORNER_OFFSET_DP,
displayMetrics);
mCornerExtension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_CORNER_EXTENSION_DP,
displayMetrics);
mCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_CORNER_LENGTH_DP,
displayMetrics);
// Sets guidelines to default until specified otherwise
mGuidelines = CropImageView.DEFAULT_GUIDELINES;
}
/**
* Set the initial crop window size and position. This is dependent on the
* size and position of the image being cropped.
*
* #param bitmapRect the bounding box around the image being cropped
*/
private void initCropWindow(Rect bitmapRect) {
if (bitmapRect.width() == 0 || bitmapRect.height() == 0) {
return;
}
// Tells the attribute functions the crop window has already been
// initialized
if (!initializedCropWindow) {
initializedCropWindow = true;
}
if (mFixAspectRatio
&& (bitmapRect.left != 0 || bitmapRect.right != 0
|| bitmapRect.top != 0 || bitmapRect.bottom != 0)) {
// If the image aspect ratio is wider than the crop aspect ratio,
// then the image height is the determining initial length. Else,
// vice-versa.
if (AspectRatioUtil.calculateAspectRatio(bitmapRect) > mTargetAspectRatio) {
Edge.TOP.setCoordinate(bitmapRect.top);
Edge.BOTTOM.setCoordinate(bitmapRect.bottom);
final float centerX = getWidth() / 2f;
//dirty fix for wrong crop overlay aspect ratio when using fixed aspect ratio
mTargetAspectRatio = (float) mAspectRatioX / mAspectRatioY;
// Limits the aspect ratio to no less than 40 wide or 40 tall
final float cropWidth = Math.max(Edge.MIN_CROP_LENGTH_PX,
AspectRatioUtil.calculateWidth(Edge.TOP.getCoordinate(),
Edge.BOTTOM.getCoordinate(),
mTargetAspectRatio));
// Create new TargetAspectRatio if the original one does not fit
// the screen
if (cropWidth == Edge.MIN_CROP_LENGTH_PX) {
mTargetAspectRatio = (Edge.MIN_CROP_LENGTH_PX) / (Edge.BOTTOM.getCoordinate() - Edge.TOP.getCoordinate());
}
final float halfCropWidth = cropWidth / 2f;
Edge.LEFT.setCoordinate(centerX - halfCropWidth);
Edge.RIGHT.setCoordinate(centerX + halfCropWidth);
} else {
Edge.LEFT.setCoordinate(bitmapRect.left);
Edge.RIGHT.setCoordinate(bitmapRect.right);
final float centerY = getHeight() / 2f;
// Limits the aspect ratio to no less than 40 wide or 40 tall
final float cropHeight = Math.max(Edge.MIN_CROP_LENGTH_PX,
AspectRatioUtil.calculateHeight(Edge.LEFT.getCoordinate(),
Edge.RIGHT.getCoordinate(),
mTargetAspectRatio));
// Create new TargetAspectRatio if the original one does not fit
// the screen
if (cropHeight == Edge.MIN_CROP_LENGTH_PX) {
mTargetAspectRatio = (Edge.RIGHT.getCoordinate() - Edge.LEFT.getCoordinate()) / Edge.MIN_CROP_LENGTH_PX;
}
final float halfCropHeight = cropHeight / 2f;
Edge.TOP.setCoordinate(centerY - halfCropHeight);
Edge.BOTTOM.setCoordinate(centerY + halfCropHeight);
}
} else { // ... do not fix aspect ratio...
// Initialize crop window to have 10% padding w/ respect to image.
final float horizontalPadding = 0.1f * bitmapRect.width();
final float verticalPadding = 0.1f * bitmapRect.height();
Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding);
Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding);
Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding);
Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding);
}
}
/**
* Indicates whether the crop window is small enough that the guidelines
* should be shown. Public because this function is also used to determine
* if the center handle should be focused.
*
* #return boolean Whether the guidelines should be shown or not
*/
public static boolean showGuidelines() {
if ((Math.abs(Edge.LEFT.getCoordinate() - Edge.RIGHT.getCoordinate()) < DEFAULT_SHOW_GUIDELINES_LIMIT)
|| (Math.abs(Edge.TOP.getCoordinate() - Edge.BOTTOM.getCoordinate()) < DEFAULT_SHOW_GUIDELINES_LIMIT)) {
return false;
} else {
return true;
}
}
private void drawRuleOfThirdsGuidelines(Canvas canvas) {
float w = mBorderPaint.getStrokeWidth();
float l = Edge.LEFT.getCoordinate() + w;
float t = Edge.TOP.getCoordinate() + w;
float r = Edge.RIGHT.getCoordinate() - w;
float b = Edge.BOTTOM.getCoordinate() - w;
if (mCropShape == CropImageView.CropShape.OVAL) {
l += 15 * mGuidelinePaint.getStrokeWidth();
t += 15 * mGuidelinePaint.getStrokeWidth();
r -= 15 * mGuidelinePaint.getStrokeWidth();
b -= 15 * mGuidelinePaint.getStrokeWidth();
}
// Draw vertical guidelines.
final float oneThirdCropWidth = Edge.getWidth() / 3;
final float x1 = l + oneThirdCropWidth;
canvas.drawLine(x1, t, x1, b, mGuidelinePaint);
final float x2 = r - oneThirdCropWidth;
canvas.drawLine(x2, t, x2, b, mGuidelinePaint);
// Draw horizontal guidelines.
final float oneThirdCropHeight = Edge.getHeight() / 3;
final float y1 = t + oneThirdCropHeight;
canvas.drawLine(l, y1, r, y1, mGuidelinePaint);
final float y2 = b - oneThirdCropHeight;
canvas.drawLine(l, y2, r, y2, mGuidelinePaint);
}
private void drawBackground(Canvas canvas, Rect bitmapRect) {
final float l = Edge.LEFT.getCoordinate();
final float t = Edge.TOP.getCoordinate();
final float r = Edge.RIGHT.getCoordinate();
final float b = Edge.BOTTOM.getCoordinate();
if (mCropShape == CropImageView.CropShape.RECTANGLE) {
canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, t, mBackgroundPaint);
canvas.drawRect(bitmapRect.left, b, bitmapRect.right, bitmapRect.bottom, mBackgroundPaint);
canvas.drawRect(bitmapRect.left, t, l, b, mBackgroundPaint);
canvas.drawRect(r, t, bitmapRect.right, b, mBackgroundPaint);
} else {
Path circleSelectionPath = new Path();
mRectF.set(l, t, r, b);
circleSelectionPath.addOval(mRectF, Path.Direction.CW);
canvas.clipPath(circleSelectionPath, Region.Op.XOR);
canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, bitmapRect.bottom, mBackgroundPaint);
canvas.restore();
}
}
private void drawCorners(Canvas canvas) {
float w = mBorderPaint.getStrokeWidth();
final float l = Edge.LEFT.getCoordinate() + w;
final float t = Edge.TOP.getCoordinate() + w;
final float r = Edge.RIGHT.getCoordinate() - w;
final float b = Edge.BOTTOM.getCoordinate() - w;
// Top left
canvas.drawLine(l - mCornerOffset, t - mCornerExtension, l - mCornerOffset, t + mCornerLength, mCornerPaint);
canvas.drawLine(l, t - mCornerOffset, l + mCornerLength, t - mCornerOffset, mCornerPaint);
// Top right
canvas.drawLine(r + mCornerOffset, t - mCornerExtension, r + mCornerOffset, t + mCornerLength, mCornerPaint);
canvas.drawLine(r, t - mCornerOffset, r - mCornerLength, t - mCornerOffset, mCornerPaint);
// Bottom left
canvas.drawLine(l - mCornerOffset, b + mCornerExtension, l - mCornerOffset, b - mCornerLength, mCornerPaint);
canvas.drawLine(l, b + mCornerOffset, l + mCornerLength, b + mCornerOffset, mCornerPaint);
// Bottom left
canvas.drawLine(r + mCornerOffset, b + mCornerExtension, r + mCornerOffset, b - mCornerLength, mCornerPaint);
canvas.drawLine(r, b + mCornerOffset, r - mCornerLength, b + mCornerOffset, mCornerPaint);
}
/**
* Handles a {#link android.view.MotionEvent#ACTION_DOWN} event.
*
* #param x the x-coordinate of the down action
* #param y the y-coordinate of the down action
*/
private void onActionDown(float x, float y) {
final float left = Edge.LEFT.getCoordinate();
final float top = Edge.TOP.getCoordinate();
final float right = Edge.RIGHT.getCoordinate();
final float bottom = Edge.BOTTOM.getCoordinate();
mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius);
if (mPressedHandle == null) {
return;
}
// Calculate the offset of the touch point from the precise location
// of the handle. Save these values in a member variable since we want
// to maintain this offset as we drag the handle.
mTouchOffset = HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom);
invalidate();
}
/**
* Handles a {#link android.view.MotionEvent#ACTION_UP} or
* {#link android.view.MotionEvent#ACTION_CANCEL} event.
*/
private void onActionUp() {
if (mPressedHandle == null) {
return;
}
mPressedHandle = null;
invalidate();
}
/**
* Handles a {#link android.view.MotionEvent#ACTION_MOVE} event.
*
* #param x the x-coordinate of the move event
* #param y the y-coordinate of the move event
*/
private void onActionMove(float x, float y) {
if (mPressedHandle == null) {
return;
}
// Adjust the coordinates for the finger position's offset (i.e. the
// distance from the initial touch to the precise handle location).
// We want to maintain the initial touch's distance to the pressed
// handle so that the crop window size does not "jump".
x += mTouchOffset.first;
y += mTouchOffset.second;
// Calculate the new crop window size/position.
if (mFixAspectRatio) {
mPressedHandle.updateCropWindow(x, y, mTargetAspectRatio, mBitmapRect, mSnapRadius);
} else {
mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius);
}
invalidate();
}
//endregion
}
You could just try using Picasso. It does all the scaling for you in the background after you put it into your Gradle build. Picasso

Difficulty morphing a series of images in Java

I'm having a little difficulty doing an assignment. I'll post the class and explain.
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.text.*;
/**
* A class that represents a picture. This class inherits from
* SimplePicture and allows the student to add functionality to
* the Picture class.
*
* Copyright Georgia Institute of Technology 2004-2005
* #author Barbara Ericson ericson#cc.gatech.edu
*/
public class Picture extends SimplePicture
{
///////////////////// constructors //////////////////////////////////
/**
* Constructor that takes no arguments
*/
public Picture ()
{
/* not needed but use it to show students the implicit call to super()
* child constructors always call a parent constructor
*/
super();
}
/**
* Constructor that takes a file name and creates the picture
* #param fileName the name of the file to create the picture from
*/
public Picture(String fileName)
{
// let the parent class handle this fileName
super(fileName);
}
/**
* Constructor that takes the width and height
* #param width the width of the desired picture
* #param height the height of the desired picture
*/
public Picture(int width, int height)
{
// let the parent class handle this width and height
super(width,height);
}
/**
* Constructor that takes a picture and creates a
* copy of that picture
*/
public Picture(Picture copyPicture)
{
// let the parent class do the copy
super(copyPicture);
}
////////////////////// methods ///////////////////////////////////////
/**
* Method to return a string with information about this picture.
* #return a string with information about the picture such as fileName,
* height and width.
*/
public String toString()
{
String output = "Picture, filename " + getFileName() +
" height " + getHeight()
+ " width " + getWidth();
return output;
}
/**
* Modified version of method from page 154 of the textbook for copying pictures
*/
public void copyPictureTo(Picture sourcePicture, int xStart, int yStart)
{
Pixel sourcePixel = null;
Pixel targetPixel = null;
//loop through the columns
try{
for (int sourceX = 0, targetX = xStart;
sourceX < sourcePicture.getWidth();
sourceX++, targetX++)
{
//loop through the rows
for (int sourceY = 0,
targetY = yStart;
sourceY < sourcePicture.getHeight();
sourceY++, targetY++)
{
sourcePixel = sourcePicture.getPixel(sourceX,sourceY);
targetPixel = this.getPixel(targetX,targetY);
targetPixel.setColor(sourcePixel.getColor());
}
}
}catch(IndexOutOfBoundsException ex){System.out.println("Either xStart or yStart is out of bounds");System.exit(0);}
}
//morphStage() method is located here.
public void morphStage(Picture startPicture, Picture endPicture, int numStages, int k)
{
Pixel[] pixelArrayStart = startPicture.getPixels();
Pixel[] pixelArrayEnd = endPicture.getPixels();
Pixel pixelObjEnd = null;
Pixel pixelObjStart = null;
Pixel pixelObjNew = null;
//Colour values for starting picture.
int startingRedValue = 0;
int startingGreenValue = 0;
int startingBlueValue = 0;
//Colour values for ending picture.
int endRedValue = 0;
int endGreenValue = 0;
int endBlueValue = 0;
//Colour values for intermediate pictures.
int redValue = 0;
int greenValue = 0;
int blueValue = 0;
//Loops through each entry int he first array, getting the RGB values
for (int i = 0; i < pixelArrayStart.length; i++)
{
//Loops through each entry in the second array, getting the RGB values
{
for (int j = 0; j < pixelArrayEnd.length; j++)
{
pixelObjEnd = pixelArrayEnd[j];
endRedValue = pixelObjEnd.getRed();
endGreenValue = pixelObjEnd.getGreen();
endBlueValue = pixelObjEnd.getBlue();
pixelObjStart = pixelArrayStart[i];
startingRedValue = pixelObjStart.getRed();
startingGreenValue = pixelObjStart.getGreen();
startingBlueValue = pixelObjStart.getBlue();
redValue = startingRedValue +((endRedValue - startingRedValue)/(numStages + 1))*k;
greenValue = startingGreenValue +((endGreenValue - startingGreenValue)/(numStages + 1))*k;
blueValue = startingBlueValue +((endBlueValue - startingBlueValue)/(numStages + 1))*k;
pixelObjNew.setRed(redValue);
pixelObjNew.setGreen(greenValue);
pixelObjNew.setBlue(blueValue);
}
}
}
}
}
// end of class Picture, put all new methods before this
The idea is to create a series of intermediate pictures using the equation, redValue = startingRedValue +((endRedValue - startingRedValue)/(numStages + 1))*k.
I'm not sure if I'm writing the code properly, but the idea was to create an array of pixels for the first picture, get the RGB values, create an array of pixels for the second picture, get the RGB values, and then create the a new pixel, pixelObjNew by setting each RGB value using the equation.
The code compiles fine, but when I try to run it with two pictures, there's an error. (NB: TestMorphing is the class I wrote for the test)
java.lang.NullPointerException
at Picture.morphStage(Picture.java:149)
at TestMorphing.main(TestMorphing.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at edu.rice.cs.drjava.model.compiler.JavacCompiler.runCommand(JavacCompiler.java:272)
This is an introduction to computer science class, btw.
Much appreciated!
EDIT: Here's the pixel class.
import java.awt.Color;
/**
* Class that references a pixel in a picture. A pixel has an x and y
* location in a picture. A pixel knows how to get and set the red,
* green, blue, and alpha values in the picture. A pixel also knows
* how to get and set the color using a Color object.
*
* Copyright Georgia Institute of Technology 2004
* #author Barb Ericson ericson#cc.gatech.edu
*/
public class Pixel
{
////////////////////////// fields ///////////////////////////////////
/** the digital picture this pixel belongs to */
private DigitalPicture picture;
/** the x location of this pixel in the picture (0,0) is top left */
private int x;
/** the y location of this pixel in the picture (0,0) is top left */
private int y;
////////////////////// constructors /////////////////////////////////
/**
* A constructor that take the x and y location for the pixel and
* the picture the pixel is coming from
* #param picture the picture that the pixel is in
* #param x the x location of the pixel in the picture
* #param y the y location of the pixel in the picture
*/
public Pixel(DigitalPicture picture, int x, int y)
{
// set the picture
this.picture = picture;
// set the x location
this.x = x;
// set the y location
this.y = y;
}
///////////////////////// methods //////////////////////////////
/**
* Method to get the x location of this pixel.
* #return the x location of the pixel in the picture
*/
public int getX() { return x; }
/**
* Method to get the y location of this pixel.
* #return the y location of the pixel in the picture
*/
public int getY() { return y; }
/**
* Method to get the amount of alpha (transparency) at this pixel.
* It will be from 0-255.
* #return the amount of alpha (transparency)
*/
public int getAlpha() {
/* get the value at the location from the picture as a 32 bit int
* with alpha, red, green, blue each taking 8 bits from left to right
*/
int value = picture.getBasicPixel(x,y);
// get the alpha value (starts at 25 so shift right 24)
// then and it with all 1's for the first 8 bits to keep
// end up with from 0 to 255
int alpha = (value >> 24) & 0xff;
return alpha;
}
/**
* Method to get the amount of red at this pixel. It will be
* from 0-255 with 0 being no red and 255 being as much red as
* you can have.
* #return the amount of red from 0 for none to 255 for max
*/
public int getRed() {
/* get the value at the location from the picture as a 32 bit int
* with alpha, red, green, blue each taking 8 bits from left to right
*/
int value = picture.getBasicPixel(x,y);
// get the red value (starts at 17 so shift right 16)
// then and it with all 1's for the first 8 bits to keep
// end up with from 0 to 255
int red = (value >> 16) & 0xff;
return red;
}
/**
* Method to get the red value from a pixel represented as an int
* #param value the color value as an int
* #return the amount of red
*/
public static int getRed(int value)
{
int red = (value >> 16) & 0xff;
return red;
}
/**
* Method to get the amount of green at this pixel. It will be
* from 0-255 with 0 being no green and 255 being as much green as
* you can have.
* #return the amount of green from 0 for none to 255 for max
*/
public int getGreen() {
/* get the value at the location from the picture as a 32 bit int
* with alpha, red, green, blue each taking 8 bits from left to right
*/
int value = picture.getBasicPixel(x,y);
// get the green value (starts at 9 so shift right 8)
int green = (value >> 8) & 0xff;
return green;
}
/**
* Method to get the green value from a pixel represented as an int
* #param value the color value as an int
* #return the amount of green
*/
public static int getGreen(int value)
{
int green = (value >> 8) & 0xff;
return green;
}
/**
* Method to get the amount of blue at this pixel. It will be
* from 0-255 with 0 being no blue and 255 being as much blue as
* you can have.
* #return the amount of blue from 0 for none to 255 for max
*/
public int getBlue() {
/* get the value at the location from the picture as a 32 bit int
* with alpha, red, green, blue each taking 8 bits from left to right
*/
int value = picture.getBasicPixel(x,y);
// get the blue value (starts at 0 so no shift required)
int blue = value & 0xff;
return blue;
}
/**
* Method to get the blue value from a pixel represented as an int
* #param value the color value as an int
* #return the amount of blue
*/
public static int getBlue(int value)
{
int blue = value & 0xff;
return blue;
}
/**
* Method to get a color object that represents the color at this pixel.
* #return a color object that represents the pixel color
*/
public Color getColor()
{
/* get the value at the location from the picture as a 32 bit int
* with alpha, red, green, blue each taking 8 bits from left to right
*/
int value = picture.getBasicPixel(x,y);
// get the red value (starts at 17 so shift right 16)
// then and it with all 1's for the first 8 bits to keep
// end up with from 0 to 255
int red = (value >> 16) & 0xff;
// get the green value (starts at 9 so shift right 8)
int green = (value >> 8) & 0xff;
// get the blue value (starts at 0 so no shift required)
int blue = value & 0xff;
return new Color(red,green,blue);
}
/**
* Method to set the pixel color to the passed in color object.
* #param newColor the new color to use
*/
public void setColor(Color newColor)
{
// set the red, green, and blue values
int red = newColor.getRed();
int green = newColor.getGreen();
int blue = newColor.getBlue();
// update the associated picture
updatePicture(this.getAlpha(),red,green,blue);
}
/**
* Method to update the picture based on the passed color
* values for this pixel
* #param alpha the alpha (transparency) at this pixel
* #param red the red value for the color at this pixel
* #param green the green value for the color at this pixel
* #param blue the blue value for the color at this pixel
*/
public void updatePicture(int alpha, int red, int green, int blue)
{
// create a 32 bit int with alpha, red, green blue from left to right
int value = (alpha << 24) + (red << 16) + (green << 8) + blue;
// update the picture with the int value
picture.setBasicPixel(x,y,value);
}
/**
* Method to correct a color value to be within 0 and 255
* #param the value to use
* #return a value within 0 and 255
*/
private static int correctValue(int value)
{
if (value < 0)
value = 0;
if (value > 255)
value = 255;
return value;
}
/**
* Method to set the red to a new red value
* #param value the new value to use
*/
public void setRed(int value)
{
// set the red value to the corrected value
int red = correctValue(value);
// update the pixel value in the picture
updatePicture(getAlpha(), red, getGreen(), getBlue());
}
/**
* Method to set the green to a new green value
* #param value the value to use
*/
public void setGreen(int value)
{
// set the green value to the corrected value
int green = correctValue(value);
// update the pixel value in the picture
updatePicture(getAlpha(), getRed(), green, getBlue());
}
/**
* Method to set the blue to a new blue value
* #param value the new value to use
*/
public void setBlue(int value)
{
// set the blue value to the corrected value
int blue = correctValue(value);
// update the pixel value in the picture
updatePicture(getAlpha(), getRed(), getGreen(), blue);
}
/**
* Method to set the alpha (transparency) to a new alpha value
* #param value the new value to use
*/
public void setAlpha(int value)
{
// make sure that the alpha is from 0 to 255
int alpha = correctValue(value);
// update the associated picture
updatePicture(alpha, getRed(), getGreen(), getBlue());
}
/**
* Method to get the distance between this pixel's color and the passed color
* #param testColor the color to compare to
* #return the distance between this pixel's color and the passed color
*/
public double colorDistance(Color testColor)
{
double redDistance = this.getRed() - testColor.getRed();
double greenDistance = this.getGreen() - testColor.getGreen();
double blueDistance = this.getBlue() - testColor.getBlue();
double distance = Math.sqrt(redDistance * redDistance +
greenDistance * greenDistance +
blueDistance * blueDistance);
return distance;
}
/**
* Method to compute the color distances between two color objects
* #param color1 a color object
* #param color2 a color object
* #return the distance between the two colors
*/
public static double colorDistance(Color color1,Color color2)
{
double redDistance = color1.getRed() - color2.getRed();
double greenDistance = color1.getGreen() - color2.getGreen();
double blueDistance = color1.getBlue() - color2.getBlue();
double distance = Math.sqrt(redDistance * redDistance +
greenDistance * greenDistance +
blueDistance * blueDistance);
return distance;
}
/**
* Method to get the average of the colors of this pixel
* #return the average of the red, green, and blue values
*/
public double getAverage()
{
double average = (getRed() + getGreen() + getBlue()) / 3.0;
return average;
}
/**
* Method to return a string with information about this pixel
* #return a string with information about this pixel
*/
public String toString()
{
return "Pixel red=" + getRed() + " green=" + getGreen() +
" blue=" + getBlue();
}
}
Your problem seems to stem from your trying to use the Pixel type variable, pixelObjNew, while it is still null. You appear to be using a 3rd party library, likely part of your Ga Tech class code, and our not being privy to this code will necessary limit our ability help, but having said that, the bottom line is that you should first assign a valid instantiated object to this variable prior to trying to use it. This might be as simple as calling,
pixelObjNew = new Pixel();
Or it might be a whole lot more complex. Check your library's API, your class notes, your sample code, to find out what you should do.
Key points for solving NPE:
Finding and inspecting the line that throws the NPE is critical to solving it.
A variable that you are trying to use on that line is null, and your trying to dereference it is causing your problem.
Often it's obvious by looking at the line to see which variable is at fault.
At other times you'll need a debugger or System.out.println(...)` statements (the so-called "poor man's debugger") to help you.
Edit
On looking at your Pixel class, it appears that 1) using a default constructor won't do, and that 2) a Pixel by itself appears to be meaningless, that it only makes sense in context with the Picture from which it is a component. This would also suggest to me that your current approach to morphing might be (accent on the might) be off.
My guess, and please understand that this is a huge guess, is that you might want to create several Picture objects, one for each intermediate stage, and one for the end picture. And then you can extract Pixel arrays from each Picture and adjust them (linearly?). Again this is a silly wild arsed guess. Use this advice with caution.
Also, I think that I would not nest for loops to do my morphing if it is to be a linear morph.
pixelObjNew is initialized as null and that never changes. So pixelObjNew.setRed(redValue); is going to throw an NPE.

Java Junit4 testing; protected methods

I'm working to test this chunk of code - it's a class called MazeBuilder. My problem is that most of the methods are protected, so I can't access them in the tests...
So my thought was that the test should just focus on Run(), so it accesses a lot of the other methods. But I'm concerned that it will be impossible to get any sort of cohesive testing done operating from just one method.
Additionally, what is the proper way to test the 2 constructors ( MazeBuilder() and MazeBuilder(boolean deterministic) )? As it stands, I'm just testing that the object formed is not null - i.e. that they're being built at all. Is there a more expansive way of testing a constructor that I'm unaware of?
package falstad;
public class MazeBuilder implements Runnable {
// Given input information:
protected int width, height ; // width and height of maze,
Maze maze; // reference to the maze that is constructed, results are returned by calling maze.newMaze(..)
private int rooms; // requested number of rooms in maze, a room is an area with no walls larger than a single cell
int expectedPartiters; // user given limit for partiters
// Produced output information to create the new maze
// root, cells, dists, startx, starty
protected int startx, starty ; // starting position inside maze for entity to search for exit
// conventional encoding of maze as a 2 dimensional integer array encapsulated in the Cells class
// a single integer entry can hold information on walls, borders/bounds
protected Cells cells; // the internal representation of a maze as a matrix of cells
protected Distance dists ; // distance matrix that stores how far each position is away from the exit positino
// class internal local variables
protected SingleRandom random ; // random number stream, used to make randomized decisions, e.g for direction to go
Thread buildThread; // computations are performed in own separated thread with this.run()
//int colchange; // randomly selected in run method of this thread, used as parameter to Segment class constructor
/**
* Constructor for a randomized maze generation
*/
public MazeBuilder(){
random = SingleRandom.getRandom();
}
/**
* Constructor with option to make maze generation deterministic or random
*/
public MazeBuilder(boolean deterministic){
if (true == deterministic)
{
System.out.println("Project 2: functionality to make maze generation deterministic not implemented yet! Fix this!");
// Control random number generation
// TODO: implement code that makes sure that if MazeBuilder.build is called for same skill level twice, same results
// HINT: check http://download.oracle.com/javase/6/docs/api/java/util/Random.html and file SingleRandom.java
}
random = SingleRandom.getRandom();
}
/**
* Provides the sign of a given integer number
* #param num
* #return -1 if num < 0, 0 if num == 0, 1 if num > 0
*/
static int getSign(int num) {
return (num < 0) ? -1 : (num > 0) ? 1 : 0;
}
/**
* This method generates a maze.
* It computes distances, determines a start and exit position that are as far apart as possible.
*/
protected void generate() {
// generate paths in cells such that there is one strongly connected component
// i.e. between any two cells in the maze there is a path to get from one to the other
// the search algorithm starts at some random point
generatePathways();
final int[] remote = dists.computeDistances(cells) ;
// identify cell with the greatest distance
final int[] pos = dists.getStartPosition();
startx = pos[0] ;
starty = pos[1] ;
// make exit position at true exit in the cells data structure
setExitPosition(remote[0], remote[1]);
}
/**
* This method generates pathways into the maze.
*
*/
protected void generatePathways() {
int[][] origdirs = new int[width][height] ;
int x = random.nextIntWithinInterval(0, width-1) ;
int y = 0;
final int firstx = x ;
final int firsty = y ;
int dir = 0;
int origdir = dir;
cells.setVisitedFlagToZero(x, y);
while (true) {
int dx = Constants.DIRS_X[dir];
int dy = Constants.DIRS_Y[dir];
if (!cells.canGo(x, y, dx, dy)) {
dir = (dir+1) & 3;
if (origdir == dir) {
if (x == firstx && y == firsty)
break;
int odr = origdirs[x][y];
dx = Constants.DIRS_X[odr];
dy = Constants.DIRS_Y[odr];
x -= dx;
y -= dy;
origdir = dir = random.nextIntWithinInterval(0, 3);
}
} else {
cells.deleteWall(x, y, dx, dy);
x += dx;
y += dy;
cells.setVisitedFlagToZero(x, y);
origdirs[x][y] = dir;
origdir = dir = random.nextIntWithinInterval(0, 3);
}
}
}
/**
* Establish valid exit position by breaking down wall to outside area.
* #param remotex
* #param remotey
*/
protected void setExitPosition(int remotex, int remotey) {
int bit = 0;
if (remotex == 0)
bit = Constants.CW_LEFT;
else if (remotex == width-1)
bit = Constants.CW_RIGHT;
else if (remotey == 0)
bit = Constants.CW_TOP;
else if (remotey == height-1)
bit = Constants.CW_BOT;
else
dbg("Generate 1");
cells.setBitToZero(remotex, remotey, bit);
//System.out.println("exit position set to zero: " + remotex + " " + remotey + " " + bit + ":" + cells.hasMaskedBitsFalse(remotex, remotey, bit)
// + ", Corner case: " + ((0 == remotex && 0 == remotey) || (0 == remotex && height-1 == remotey) || (width-1 == remotex && 0 == remotey) || (width-1 == remotex && height-1 == remotey)));
}
static final int MIN_ROOM_DIMENSION = 3 ;
static final int MAX_ROOM_DIMENSION = 8 ;
/**
* Allocates space for a room of random dimensions in the maze.
* The position of the room is chosen randomly. The method is not sophisticated
* such that the attempt may fail even if the maze has ample space to accommodate
* a room of the chosen size.
* #return true if room is successfully placed, false otherwise
*/
private boolean placeRoom() {
// get width and height of random size that are not too large
// if too large return as a failed attempt
final int rw = random.nextIntWithinInterval(MIN_ROOM_DIMENSION, MAX_ROOM_DIMENSION);
if (rw >= width-4)
return false;
final int rh = random.nextIntWithinInterval(MIN_ROOM_DIMENSION, MAX_ROOM_DIMENSION);
if (rh >= height-4)
return false;
// proceed for a given width and height
// obtain a random position (rx,ry) such that room is located on as a rectangle with (rx,ry) and (rxl,ryl) as corner points
// upper bound is chosen such that width and height of room fits maze area.
final int rx = random.nextIntWithinInterval(1, width-rw-1);
final int ry = random.nextIntWithinInterval(1, height-rh-1);
final int rxl = rx+rw-1;
final int ryl = ry+rh-1;
// check all cells in this area if they already belong to a room
// if this is the case, return false for a failed attempt
if (cells.areaOverlapsWithRoom(rx, ry, rxl, ryl))
return false ;
// since the area is available, mark it for this room and remove all walls
// from this on it is clear that we can place the room on the maze
cells.markAreaAsRoom(rw, rh, rx, ry, rxl, ryl);
return true;
}
static void dbg(String str) {
System.out.println("MazeBuilder: "+str);
}
/**
* Fill the given maze object with a newly computed maze according to parameter settings
* #param mz maze to be filled
* #param w width of requested maze
* #param h height of requested maze
* #param roomct number of rooms
* #param pc number of expected partiters
*/
public void build(Maze mz, int w, int h, int roomct, int pc) {
init(mz, w, h, roomct, pc);
buildThread = new Thread(this);
buildThread.start();
}
/**
* Initialize internal attributes, method is called by build() when input parameters are provided
* #param mz maze to be filled
* #param w width of requested maze
* #param h height of requested maze
* #param roomct number of rooms
* #param pc number of expected partiters
*/
private void init(Maze mz, int w, int h, int roomct, int pc) {
// store parameters
maze = mz;
width = w;
height = h;
rooms = roomct;
expectedPartiters = pc;
// initialize data structures
cells = new Cells(w,h) ;
dists = new Distance(w,h) ;
//colchange = random.nextIntWithinInterval(0, 255); // used in the constructor for Segments class Seg
}
static final long SLEEP_INTERVAL = 100 ; //unit is millisecond
/**
* Main method to run construction of a new maze with a MazeBuilder in a thread of its own.
* This method is called internally by the build method when it sets up and starts a new thread for this object.
*/
public void run() {
// try-catch block to recognize if thread is interrupted
try {
// create an initial invalid maze where all walls and borders are up
cells.initialize();
// place rooms in maze
generateRooms();
Thread.sleep(SLEEP_INTERVAL) ; // test if thread has been interrupted, i.e. notified to stop
// put pathways into the maze, determine its starting and end position and calculate distances
generate();
Thread.sleep(SLEEP_INTERVAL) ; // test if thread has been interrupted, i.e. notified to stop
final int colchange = random.nextIntWithinInterval(0, 255); // used in the constructor for Segments class Seg
final BSPBuilder b = new BSPBuilder(maze, dists, cells, width, height, colchange, expectedPartiters) ;
BSPNode root = b.generateBSPNodes();
Thread.sleep(SLEEP_INTERVAL) ; // test if thread has been interrupted, i.e. notified to stop
// dbg("partiters = "+partiters);
// communicate results back to maze object
maze.newMaze(root, cells, dists, startx, starty);
}
catch (InterruptedException ex) {
// necessary to catch exception to avoid escalation
// exception mechanism basically used to exit method in a controlled way
// no need to clean up internal data structures
// dbg("Catching signal to stop") ;
}
}
static final int MAX_TRIES = 250 ;
/**
* Generate all rooms in a given maze where initially all walls are up. Rooms are placed randomly and of random sizes
* such that the maze can turn out to be too small to accommodate the requested number of rooms (class attribute rooms).
* In that case less rooms are produced.
* #return generated number of rooms
*/
private int generateRooms() {
// Rooms are randomly positioned such that it may be impossible to place the all rooms if the maze is too small
// to prevent an infinite loop we limit the number of failed to MAX_TRIES == 250
int tries = 0 ;
int result = 0 ;
while (tries < MAX_TRIES && result <= rooms) {
if (placeRoom())
result++ ;
else
tries++ ;
}
return result ;
}
/**
* Notify the maze builder thread to stop the creation of a maze and to terminate
*/
public void interrupt() {
buildThread.interrupt() ;
}
}
To unit test your protected methods, simply put your test class in the same package as the class you are looking to test (in this case falsted). Just because they are in the same package, it doesn't mean they have to be in the same directory (just a parallel test directory hierarchy).
For example, if you are using maven, your source would be in src/main/java/falsted and your tests would be in src/test/java/falsted. From a maven perspective, these are separate directories and therefore can easily be managed separately, while from a Java perspective, they are the same package (so protected methods are visible).
Test your constructors by probing the state of the object to ensure that all values got their default or initial value.
You can use protected in test method. Recommended way to structure your project is.
In src/main/java
package falstad;
public class MazeBuilder {}
In src/test/java
package falstad;
public class MazeBuilderTest {}

Texture Buffers and glMultiDrawElements

Backstory:
I'm trying to draw as many squares the the screen as possible using a single draw call. I'm using a custom glsl vertex shader that is specialized for 2D drawing, and that is supposed to be pulling position data for the vertices of the squares from a samplerBuffer. Since I don't need to worry about rotating or scaling the squares all I should need to do is load the position data into a buffer, bind a texture to that buffer, and then use the sampler to get each vertex's position in the shader. In order to get an index into the texture I store each elements index as the z-component of the vertices.
Everything seems to work really well for a thousand or so squares, but after that I start to get weird blinking. It sort of seems like it's not drawing all of the squares every draw step, or possibly not using all of the positions so that many of the squares are overlapping.
The weird thing is, that if I use drawElements instead of drawElementsMulti, the blinking goes away (but of course then all the squares are drawn as one single object, which I don't want)
One question I have is if my position data is limited to the max texture size, or the max texture buffer size. And if I am limited to the much smaller max texture size, how do I get around it? There's got to be a reason all of that texture buffer space is there, but I obviously don't get how to properly use it.
I'm also thinking maybe glMultiDrawElements is doing something I'm not accounting for with the sampler somehow. Idk, I'm really lost at this point, and yet..it works perfectly for smaller numbers of squares, so I must be doing something right.
[EDIT] Code had changed to reflect suggestions below (and for readability), but the problem persists.
Ok, so here's some code. First the vertex shader:
uniform mat3 projection;
attribute vec3 vertex;
uniform samplerBuffer positionSampler;
attribute vec4 vertex_color;
varying vec4 color;
float positionFetch(int index)
{
// I've tried texelFetch here as well, same effect
float value = texelFetchBuffer(positionSampler, index).r;
return value;
}
void main(void)
{
color = vec4(1, 1, 1, 1);
// use the z-component of the vertex to look up the position of this instance in the texture
vec3 real_position = vec3(vertex.x + positionFetch(int(vertex.z)*2), vertex.y + positionFetch(int(vertex.z)*2+1), 1);
gl_Position = vec4(projection * real_position, 1);
}
And now my GLRenderer, sorry there is so much code, I just really want to make sure there's enough info here to get an answer. This has really been driving me nuts, and examples for java seem to be hard to come by (maybe this code will help someone else on their quest):
public class GLRenderer extends GLCanvas implements GLEventListener, WindowListener
{
private static final long serialVersionUID = -8513201172428486833L;
private static final int bytesPerFloat = Float.SIZE / Byte.SIZE;
private static final int bytesPerShort = Short.SIZE / Byte.SIZE;
public float viewWidth, viewHeight;
public float screenWidth, screenHeight;
private FPSAnimator animator;
private boolean didInit = false;
JFrame the_frame;
SquareGeometry geometry;
// Thought power of 2 might be required, doesn't seem to make a difference
private static final int NUM_THINGS = 2*2*2*2*2*2*2*2*2*2*2*2*2*2;
float[] position = new float[NUM_THINGS*2];
// Shader attributes
private int shaderProgram, projectionAttribute, vertexAttribute, positionAttribute;
public static void main(String[] args)
{
new GLRenderer();
}
public GLRenderer()
{
// setup OpenGL Version 2
super(new GLCapabilities(GLProfile.get(GLProfile.GL2)));
addGLEventListener(this);
setSize(1800, 1000);
the_frame = new JFrame("Hello World");
the_frame.getContentPane().add(this);
the_frame.setSize(the_frame.getContentPane().getPreferredSize());
the_frame.setVisible(true);
the_frame.addWindowListener(this);
animator = new FPSAnimator(this, 60);
animator.start();
}
// Called by the drivers when the gl context is first made available
public void init(GLAutoDrawable d)
{
final GL2 gl = d.getGL().getGL2();
IntBuffer asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_BUFFER_SIZE, asd);
System.out.println(asd.get(0));
asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_SIZE, asd);
System.out.println(asd.get(0));
shaderProgram = ShaderLoader.compileProgram(gl, "default");
gl.glLinkProgram(shaderProgram);
_getShaderAttributes(gl);
gl.glUseProgram(shaderProgram);
_checkGLCapabilities(gl);
_initGLSettings(gl);
// Calculate batch of vertex data from dirt geometry
geometry = new SquareGeometry(.1f);
geometry.buildGeometry(viewWidth, viewHeight);
geometry.finalizeGeometry(NUM_THINGS);
geometry.vertexBufferID = _generateBufferID(gl);
_loadVertexBuffer(gl, geometry);
geometry.indexBufferID = _generateBufferID(gl);
_loadIndexBuffer(gl, geometry);
geometry.positionBufferID = _generateBufferID(gl);
// initialize buffer object
int size = NUM_THINGS * 2 * bytesPerFloat;
System.out.println(size);
IntBuffer bla = IntBuffer.allocate(1);
gl.glGenTextures(1, bla);
geometry.positionTextureID = bla.get(0);
gl.glUniform1i(positionAttribute, 0);
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glBindTexture(GL2.GL_TEXTURE_BUFFER, geometry.positionTextureID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
gl.glBufferData(GL2.GL_TEXTURE_BUFFER, size, null, GL2.GL_DYNAMIC_DRAW);
gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
}
private void _initGLSettings(GL2 gl)
{
gl.glClearColor(0f, 0f, 0f, 1f);
}
private void _loadIndexBuffer(GL2 gl, SquareGeometry geometry)
{
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, bytesPerShort*NUM_THINGS*geometry.getNumPoints(), geometry.indexBuffer, GL2.GL_STATIC_DRAW);
}
private void _loadVertexBuffer(GL2 gl, SquareGeometry geometry)
{
int numBytes = geometry.getNumPoints() * 3 * bytesPerFloat * NUM_THINGS;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
gl.glBufferData(GL2.GL_ARRAY_BUFFER, numBytes, geometry.vertexBuffer, GL2.GL_STATIC_DRAW);
gl.glEnableVertexAttribArray(vertexAttribute);
gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
}
private int _generateBufferID(GL2 gl)
{
IntBuffer bufferIDBuffer = IntBuffer.allocate(1);
gl.glGenBuffers(1, bufferIDBuffer);
return bufferIDBuffer.get(0);
}
private void _checkGLCapabilities(GL2 gl)
{
// TODO: Respond to this information in a meaningful way.
boolean VBOsupported = gl.isFunctionAvailable("glGenBuffersARB") && gl.isFunctionAvailable("glBindBufferARB")
&& gl.isFunctionAvailable("glBufferDataARB") && gl.isFunctionAvailable("glDeleteBuffersARB");
System.out.println("VBO Supported: " + VBOsupported);
}
private void _getShaderAttributes(GL2 gl)
{
vertexAttribute = gl.glGetAttribLocation(shaderProgram, "vertex");
projectionAttribute = gl.glGetUniformLocation(shaderProgram, "projection");
positionAttribute = gl.glGetUniformLocation(shaderProgram, "positionSampler");
}
// Called by me on the first resize call, useful for things that can't be initialized until the screen size is known
public void viewInit(GL2 gl)
{
for(int i = 0; i < NUM_THINGS; i++)
{
position[i*2] = (float) (Math.random()*viewWidth);
position[i*2+1] = (float) (Math.random()*viewHeight);
}
gl.glUniformMatrix3fv(projectionAttribute, 1, false, Matrix.projection3f, 0);
// Load position data into a texture buffer
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
ByteBuffer textureBuffer = gl.glMapBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_WRITE_ONLY);
FloatBuffer textureFloatBuffer = textureBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
for(int i = 0; i < position.length; i++)
{
textureFloatBuffer.put(position[i]);
}
gl.glUnmapBuffer(GL2.GL_TEXTURE_BUFFER);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, 0);
}
public void display(GLAutoDrawable d)
{
if (!didInit || geometry.vertexBufferID == 0)
{
return;
}
//long startDrawTime = System.currentTimeMillis();
final GL2 gl = d.getGL().getGL2();
gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
// If we were drawing any other buffers here we'd need to set this every time
// but instead we just leave them bound after initialization, saves a little render time
// No combination of these seems to fix the problem
//gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
//gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
//gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
//gl.glActiveTexture(GL2.GL_TEXTURE0);
//gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
_render(gl, geometry);
// Also tried these
//gl.glFlush();
//gl.glFinish();
}
public void _render(GL2 gl, SquareGeometry geometry)
{
gl.glMultiDrawElements(geometry.drawMode, geometry.countBuffer, GL2.GL_UNSIGNED_SHORT, geometry.offsetBuffer, NUM_THINGS);
// This one works, but isn't what I want
//gl.glDrawElements(GL2.GL_LINE_LOOP, count, GL2.GL_UNSIGNED_SHORT, 0);
}
public void reshape(GLAutoDrawable d, int x, int y, int width, int height)
{
final GL2 gl = d.getGL().getGL2();
gl.glViewport(0, 0, width, height);
float ratio = (float) height / width;
screenWidth = width;
screenHeight = height;
viewWidth = 100;
viewHeight = viewWidth * ratio;
Matrix.ortho3f(0, viewWidth, 0, viewHeight);
if (!didInit)
{
viewInit(gl);
didInit = true;
}
else
{
// respond to view size changing
}
}
}
The final bit is the SquareGeometry class which holds all the bufferIDs and vertex data, but also is responsible for filling the vertex buffer correctly so that each vertex's z component can function as an index into the position texture:
public class SquareGeometry
{
public float[] vertices = null;
ShortBuffer indexBuffer;
IntBuffer countBuffer;
PointerBuffer offsetBuffer;
FloatBuffer vertexBuffer;
public int vertexBufferID = 0;
public int indexBufferID = 0;
public int positionBufferID = 0;
public int positionTextureID = 0;
public int drawMode;
protected float width = 0;
protected float height = 0;
public SquareGeometry(float size)
{
width = size;
height = size;
}
public void buildGeometry(float viewWidth, float viewHeight)
{
vertices = new float[4 * 2];
vertices[0] = -width/2;
vertices[1] = -height/2;
vertices[2] = -width/2;
vertices[3] = height/2;
vertices[4] = width/2;
vertices[5] = height/2;
vertices[6] = width/2;
vertices[7] = -height/2;
drawMode = GL2.GL_POLYGON;
}
public void finalizeGeometry(int numInstances)
{
if(vertices == null) return;
int num_vertices = this.getNumPoints();
int total_num_vertices = numInstances * num_vertices;
// initialize vertex Buffer (# of coordinate values * 4 bytes per float)
ByteBuffer vbb = ByteBuffer.allocateDirect(total_num_vertices * 3 * Float.SIZE);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
for(int i = 0; i < numInstances; i++)
{
for(int v = 0; v < num_vertices; v++)
{
int vertex_index = v * 2;
vertexBuffer.put(vertices[vertex_index]);
vertexBuffer.put(vertices[vertex_index+1]);
vertexBuffer.put(i);
}
}
vertexBuffer.rewind();
// Create the indices
vbb = ByteBuffer.allocateDirect(total_num_vertices * Short.SIZE);
vbb.order(ByteOrder.nativeOrder());
indexBuffer = vbb.asShortBuffer();
for(int i = 0; i < total_num_vertices; i++)
{
indexBuffer.put((short) (i));
}
indexBuffer.rewind();
// Create the counts
vbb = ByteBuffer.allocateDirect(numInstances * Integer.SIZE);
vbb.order(ByteOrder.nativeOrder());
countBuffer = vbb.asIntBuffer();
for(int i = 0; i < numInstances; i++)
{
countBuffer.put(num_vertices);
}
countBuffer.rewind();
// create the offsets
offsetBuffer = PointerBuffer.allocateDirect(numInstances);
for(int i = 0; i < numInstances; i++)
{
offsetBuffer.put(num_vertices*i*2);
}
offsetBuffer.rewind();
}
public int getNumPoints()
{
return vertices.length/2;
}
}
Ok first things first, you are not setting gl_Color in the shader maybe that can be the issue here and you only lucky with small numbers. It is a varying, but do you also have fragment shader that picks up the value?
At no point do you ensure that NUM_THINGS*2 < GL_MAX_TEXTURE_SIZE. I don't know how FloatBuffer.put reacts; being Java probably / hopefully an exception.
Also you bind the positionBufferID buffer, then unbind it but never rebind it.
You create positionTextureID but never put any data there. This also what you put into the sampler positionSampler and try to access.
Yea well lots of issues but my gut tells me the last one may be the real issue here.
Alright, I've got it solved, though I'm still really not clear on what the original problem was. I fixed it by simplifying the drawing to use drawArrays instead of drawElements or multiDrawElements. I'm really not sure why I thought I needed them, as I really don't in this case. I'm pretty sure I was messing up a few things with the indexes and offsets.
Furthermore, as far as the proper way to bind the texture buffer, neither the code I have above, nor example found at the link I posted in a comment are correct at all.
If anyone is interested in the correct way to use the texture buffer like this I just did a pretty extensive write-up on it here http://zebadiah.me/?p=44. Thanks all for the help.

Categories

Resources