/*
* Copyright 2016 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.opengl;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBHalfFloatPixel;
import org.lwjgl.opengl.ARBTextureFloat;
import org.lwjgl.opengl.EXTPackedDepthStencil;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.lwjgl.opengl.EXTFramebufferObject.*;
import static org.lwjgl.opengl.GL11.glGenTextures;
import org.terasology.assets.ResourceUrn;
/**
* FBO - Frame Buffer Object
*
* FBOs wrap OpenGL's FrameBuffer functionality for the needs of the rendering portion of the engine.
*
* In OpenGL a FrameBuffer is an entity that can have a number of attachments, i.e. textures storing per-pixel color data.
* By binding FrameBuffers and their attachments, shaders can read from or write to them. For example the final image
* presented on screen is a composite of a number of visual layers stored in the attachments of different FrameBuffers.
* Shaders read from these attachments, process the per-pixel data and eventually produce the image seen on screen.
*
* This class simplifies the creation of FrameBuffers with specific attachments (see the create() method), the binding
* and unbinding of both the FrameBuffer as a whole or its attachments, and the FrameBuffer's proper disposal.
*/
public final class FBO {
private static final boolean DEFAULT_COLOR_MASK = true;
private static final boolean DEFAULT_NORMAL_MASK = true;
private static final boolean DEFAULT_LIGHT_BUFFER_MASK = true;
private static final Logger logger = LoggerFactory.getLogger(FBO.class);
// TODO: make accessors for these
public int fboId;
public int colorBufferTextureId;
public int depthStencilTextureId;
public int depthStencilRboId;
public int normalsBufferTextureId;
public int lightBufferTextureId;
private final Dimensions dimensions;
private boolean writeToColorBuffer;
private boolean writeToNormalsBuffer;
private boolean writeToLightBuffer;
private Status status;
public enum Type {
DEFAULT, // 32 bit color buffer
HDR, // 64 bit color buffer
NO_COLOR // no color buffer
}
public enum Status {
COMPLETE, // usable FBO
INCOMPLETE, // creation failed the OpenGL completeness check
DISPOSED, // no longer known to the GPU - can occur at creation time. See getStatus().
UNEXPECTED // creation failed in an unexpected way
}
// private constructor: the only way to generate an instance of this class
// should be through the static create() method.
private FBO(int width, int height) {
dimensions = new Dimensions(width, height);
writeToColorBuffer = DEFAULT_COLOR_MASK;
writeToNormalsBuffer = DEFAULT_NORMAL_MASK;
writeToLightBuffer = DEFAULT_LIGHT_BUFFER_MASK;
}
/**
* Binds the FrameBuffer tracked by this FBO. The result of subsequent OpenGL draw calls will be stored
* in the FrameBuffer's attachments until a different FrameBuffer is bound.
*/
public void bind() {
// Originally the code contained a check to prevent the currently bound FrameBuffer from being re-bound.
// By my understanding current OpenGL implementations are smart enough to prevent it on their own. If
// necessary, it'd be easy to add a class variable tracking the currently bound FrameBuffer and the
// associated checks.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
}
/**
* Once an FBO is bound, opengl commands will act on it, i.e. by drawing on it.
* Meanwhile shaders might output not just colors but additional per-pixel data. This method establishes on which
* of an FBOs attachments, subsequent opengl commands and shaders will draw on.
*
* @param renderToColorBuffer If True the color buffer is set as drawable. If false subsequent commands and shaders won't be able to draw on it.
* @param renderToNormalsBuffer If True the normal buffer is set as drawable. If false subsequent commands and shaders won't be able to draw on it.
* @param renderToLightBuffer If True the light buffer is set as drawable. If false subsequent commands and shaders won't be able to draw on it.
*/
public void setRenderBufferMask(boolean renderToColorBuffer, boolean renderToNormalsBuffer, boolean renderToLightBuffer) {
if (this.writeToColorBuffer == renderToColorBuffer && this.writeToNormalsBuffer == renderToNormalsBuffer && this.writeToLightBuffer == renderToLightBuffer) {
return;
}
this.writeToColorBuffer = renderToColorBuffer;
this.writeToNormalsBuffer = renderToNormalsBuffer;
this.writeToLightBuffer = renderToLightBuffer;
int attachmentId = 0;
IntBuffer bufferIds = BufferUtils.createIntBuffer(3);
// TODO: change GL_COLOR_ATTACHMENT0_EXT + attachmentId into something like COLOR_BUFFER_ATTACHMENT,
// TODO: in turn set within the class or method
if (colorBufferTextureId != 0) {
if (this.writeToColorBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
}
attachmentId++;
}
if (normalsBufferTextureId != 0) {
if (this.writeToNormalsBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
}
attachmentId++;
}
if (lightBufferTextureId != 0 && this.writeToLightBuffer) { // compacted if block because Jenkins was complaining about it.
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
}
bufferIds.flip();
GL20.glDrawBuffers(bufferIds);
}
/**
* "Unbinding" a FrameBuffer can be more easily thought as binding the application's display,
* i.e. the whole screen or an individual window. The result of subsequent OpenGL draw calls will
* therefore be sent to the display until a different FrameBuffer is bound.
*/
public void unbind() {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
/**
* Binds the color attachment to the currently active texture unit.
* Once a texture is bound it can be sampled by shaders.
*/
public void bindTexture() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, colorBufferTextureId);
}
/**
* Binds the depth attachment to the currently active texture unit.
* Once a texture is bound it can be sampled by shaders.
*/
public void bindDepthTexture() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, depthStencilTextureId);
}
/**
* Binds the normals attachment to the currently active texture unit.
* Once a texture is bound it can be sampled by shaders.
*/
public void bindNormalsTexture() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, normalsBufferTextureId);
}
/**
* Binds the light buffer attachment to the currently active texture unit.
* Once a texture is bound it can be sampled by shaders.
*/
public void bindLightBufferTexture() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, lightBufferTextureId);
}
/**
* Unbinds the texture attached to the currently active texture unit.
* Quirk: this also works if the texture to be unbound is -not- an attachment
* of the calling instance's FrameBuffer.
*/
public static void unbindTexture() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
/**
* Attaches the calling instance's depth attachments to the target FBO.
* Notice that the depth attachments remain attached to the calling instance too.
*
* @param target The FBO to attach the depth attachments to.
*/
public void attachDepthBufferTo(FBO target) {
target.bind();
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthStencilRboId);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, depthStencilTextureId, 0);
target.unbind();
}
/**
* Properly disposes of the underlying FrameBuffer and its attachments,
* effectively freeing memory on the graphic adapter.
*/
public void dispose() {
glDeleteFramebuffersEXT(fboId);
glDeleteRenderbuffersEXT(depthStencilRboId);
GL11.glDeleteTextures(normalsBufferTextureId);
GL11.glDeleteTextures(depthStencilTextureId);
GL11.glDeleteTextures(colorBufferTextureId);
status = Status.DISPOSED;
}
/**
* @return Returns the (int) width of the FrameBuffer, in pixels.
*/
public int width() {
return this.dimensions.width;
}
/**
* @return Returns the (int) height of the FrameBuffer, in pixels.
*/
public int height() {
return this.dimensions.height;
}
/**
* @return Returns the width and height of the FrameBuffer, as a Dimensions object.
*/
public Dimensions dimensions() {
return dimensions;
}
/**
* Retrieves the status of the FBO.
*
* A usable FBO is one with a COMPLETE status.
*
* If the status is INCOMPLETE something went wrong during the allocation process on the GPU. Causes
* can range from mismatched dimensions to missing attachments, among others. The precise error code
* can be obtained browsing the log. Using an FrameBuffer that is not COMPLETE is an error and at this
* stage it is probably unrecoverable. No exceptions are thrown however and it is up to the calling code
* to decide how to react to an it.
*
* An FBO will have a DISPOSED status if the dispose() method has been called on it, which means the
* underlying FrameBuffer is no longer available to the GPU. The FBO is also automatically
* disposed if it is of Type.NO_COLOR and the internal call to glCheckFramebufferStatusEXT()
* returns GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT. This occurs on some graphic cards and the
* resulting FBO should not be used.
*
* An UNEXPECTED status cover all other (unknown) cases and the resulting FBO is probably as dysfunctional
* as an INCOMPLETE or a DISPOSED one.
*
* @return Status.COMPLETE, Status.INCOMPLETE, Status.DISPOSED or Status.UNEXPECTED
*/
public Status getStatus() {
return status;
}
private void setStatus(Status newStatus) {
this.status = newStatus;
}
/**
* Creates an FBO, allocating the underlying FrameBuffer and the desired attachments on the GPU.
*
* Check FBO create(String title, Dimensions dimensions, Type type ...) for more.
* @param config A FBOConfig object that stores information used for creating FBO.
* @return The resuting FBO object wrapping a FrameBuffer and its attachments. Use getStatus() before use to verify completeness.
*/
public static FBO create(FBOConfig config) {
return FBO.create(config.getName(),
config.getDimensions(),
config.getType(),
config.hasDepthBuffer(),
config.hasNormalBuffer(),
config.hasLightBuffer(),
config.hasStencilBuffer());
}
/**
* Creates an FBO, allocating the underlying FrameBuffer and the desired attachments on the GPU.
*
* Also checks the resulting FBO for completeness and logs errors and their error codes as necessary.
* Callers must check the returned FBO's status (see getStatus()). Only FBO with a Status.COMPLETE should be used.
*
* In what follows, the GL constants between parenthesis represent the (internal format, data type, filtering type) of a buffer.
*
* An FBO of Type.DEFAULT will have a 32 bit color buffer attached to it. (GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL_LINEAR)
* An FBO of Type.HDR will have a 64 bit color buffer attached to it. (GL_RGBA, GL_HALF_FLOAT_ARB, GL_LINEAR)
* An FBO of Type.NO_COLOR will have -no- color buffer attached to it.
*
* If the creation process is successful (Status.COMPLETE) GPU memory has been allocated for the FrameBuffer and
* its attachments. However, the content of the attachments is undefined.
*
* @param urn An identification string. It is currently used only to log creation errors and is not stored in the FBO.
* @param dimensions A Dimensions object wrapping width and height of the FBO.
* @param type Can be Type.DEFAULT, Type.HDR or Type.NO_COLOR
* @param useDepthBuffer If true the FBO will have a 24 bit depth buffer attached to it. (GL_DEPTH_COMPONENT24, GL_UNSIGNED_INT, GL_NEAREST)
* @param useNormalBuffer If true the FBO will have a 32 bit normals buffer attached to it. (GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
* @param useLightBuffer If true the FBO will have 32/64 bit light buffer attached to it, depending if Type is DEFAULT/HDR.
* (GL_RGBA/GL_RGBA16F_ARB, GL_UNSIGNED_BYTE/GL_HALF_FLOAT_ARB, GL_LINEAR)
* @param useStencilBuffer If true the depth buffer will also have an 8 bit Stencil buffer associated with it.
* (GL_DEPTH24_STENCIL8_EXT, GL_UNSIGNED_INT_24_8_EXT, GL_NEAREST)
* @return The resuting FBO object wrapping a FrameBuffer and its attachments. Use getStatus() before use to verify completeness.
*/
public static FBO create(ResourceUrn urn, Dimensions dimensions, Type type,
boolean useDepthBuffer, boolean useNormalBuffer, boolean useLightBuffer, boolean useStencilBuffer) {
FBO fbo = new FBO(dimensions.width, dimensions.height);
// Create the FBO on the GPU
fbo.fboId = glGenFramebuffersEXT();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo.fboId);
if (type != Type.NO_COLOR) {
createColorBuffer(fbo, dimensions, type);
}
if (useNormalBuffer) {
createNormalsBuffer(fbo, dimensions);
}
if (useLightBuffer) {
createLightBuffer(fbo, dimensions, type);
}
if (useDepthBuffer) {
createDepthBuffer(fbo, dimensions, useStencilBuffer);
}
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
IntBuffer bufferIds = BufferUtils.createIntBuffer(3);
if (type != Type.NO_COLOR) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT);
}
if (useNormalBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT1_EXT);
}
if (useLightBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT2_EXT);
}
bufferIds.flip();
if (bufferIds.limit() == 0) {
GL11.glReadBuffer(GL11.GL_NONE);
GL20.glDrawBuffers(GL11.GL_NONE);
} else {
GL20.glDrawBuffers(bufferIds);
}
verifyCompleteness(urn, type, fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return fbo;
}
private static void verifyCompleteness(ResourceUrn urn, Type type, FBO fbo) {
int checkFB = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
switch (checkFB) {
case GL_FRAMEBUFFER_COMPLETE_EXT:
fbo.setStatus(Status.COMPLETE);
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT exception");
fbo.setStatus(Status.INCOMPLETE);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT exception");
fbo.setStatus(Status.INCOMPLETE);
break;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT exception");
fbo.setStatus(Status.INCOMPLETE);
break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT exception");
fbo.setStatus(Status.INCOMPLETE);
break;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT exception");
fbo.setStatus(Status.INCOMPLETE);
break;
case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_UNSUPPORTED_EXT exception");
fbo.setStatus(Status.INCOMPLETE);
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
logger.error("FrameBuffer: " + urn
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT exception");
/*
* On some graphics cards, FBO.Type.NO_COLOR can cause a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT.
* Code using NO_COLOR FBOs should check for this and -not use- the FBO if its status is DISPOSED
*/
if (type == Type.NO_COLOR) {
logger.error("FrameBuffer: " + urn
+ ", ...but the FBO.Type was NO_COLOR, ignoring this error and continuing without this FBO.");
fbo.dispose();
} else {
fbo.setStatus(Status.INCOMPLETE);
}
break;
default:
logger.error("FBO '" + urn + "' generated an unexpected reply from glCheckFramebufferStatusEXT: " + checkFB);
fbo.setStatus(Status.UNEXPECTED);
break;
}
}
/**
* Returns the content of the color buffer from GPU memory as a ByteBuffer.
* @return a ByteBuffer or null
*/
public ByteBuffer getColorBufferRawData() {
ByteBuffer buffer = BufferUtils.createByteBuffer(this.width() * this.height() * 4);
this.bindTexture();
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
FBO.unbindTexture();
return buffer;
}
private static void createColorBuffer(FBO fbo, Dimensions dimensions, Type type) {
fbo.colorBufferTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.colorBufferTextureId);
setTextureParameters(GL11.GL_LINEAR);
if (type == Type.HDR) {
allocateTexture(dimensions, GL11.GL_RGBA, GL11.GL_RGBA, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB);
} else {
allocateTexture(dimensions, GL11.GL_RGBA, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, fbo.colorBufferTextureId, 0);
}
private static void createNormalsBuffer(FBO fbo, Dimensions dimensions) {
fbo.normalsBufferTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.normalsBufferTextureId);
setTextureParameters(GL11.GL_LINEAR);
allocateTexture(dimensions, GL11.GL_RGBA, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL11.GL_TEXTURE_2D, fbo.normalsBufferTextureId, 0);
}
private static void createLightBuffer(FBO fbo, Dimensions dimensions, Type type) {
fbo.lightBufferTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.lightBufferTextureId);
setTextureParameters(GL11.GL_LINEAR);
if (type == Type.HDR) {
allocateTexture(dimensions, ARBTextureFloat.GL_RGBA16F_ARB, GL11.GL_RGBA, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB);
} else {
allocateTexture(dimensions, GL11.GL_RGBA, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT, GL11.GL_TEXTURE_2D, fbo.lightBufferTextureId, 0);
}
private static void createDepthBuffer(FBO fbo, Dimensions dimensions, boolean useStencilBuffer) {
fbo.depthStencilTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.depthStencilTextureId);
setTextureParameters(GL11.GL_NEAREST);
if (!useStencilBuffer) {
allocateTexture(dimensions, GL14.GL_DEPTH_COMPONENT24, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT);
} else {
allocateTexture(dimensions,
EXTPackedDepthStencil.GL_DEPTH24_STENCIL8_EXT,
EXTPackedDepthStencil.GL_DEPTH_STENCIL_EXT,
EXTPackedDepthStencil.GL_UNSIGNED_INT_24_8_EXT);
}
fbo.depthStencilRboId = glGenRenderbuffersEXT();
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo.depthStencilRboId);
if (!useStencilBuffer) {
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL14.GL_DEPTH_COMPONENT24, fbo.dimensions.width, fbo.dimensions.height);
} else {
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, EXTPackedDepthStencil.GL_DEPTH24_STENCIL8_EXT, fbo.dimensions.width, fbo.dimensions.height);
}
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo.depthStencilRboId);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, fbo.depthStencilTextureId, 0);
if (useStencilBuffer) {
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, fbo.depthStencilTextureId, 0);
}
}
private static void setTextureParameters(float filterType) {
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filterType);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filterType);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
}
private static void allocateTexture(Dimensions dimensions, int internalFormat, int dataFormat, int dataType) {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, dimensions.width, dimensions.height, 0, dataFormat, dataType, (ByteBuffer) null);
}
/**
* Support class wrapping width and height of FBOs. Also provides some ad-hoc methods to make code more readable.
*/
public static class Dimensions {
private int width;
private int height;
/**
* Standard Constructor - returns a Dimensions object.
*
* @param width An integer, representing the width of the FBO in pixels.
* @param height An integer, representing the height of the FBO in pixels.
*/
public Dimensions(int width, int height) {
this.width = width;
this.height = height;
}
/**
* Copy constructor: construct a Dimensions instance with the dimensions of another.
*
* @param dimensions a Dimensions instance
*/
public Dimensions(Dimensions dimensions) {
this(dimensions.width(), dimensions.height());
}
/**
* Returns a new Dimensions object whose width and height have been divided by the divisor.
* I.e. new Dimensions(20,10).dividedBy(2) returns a Dimensions(10,5) object.
* @param divisor An integer.
* @return a new Dimensions object.
*/
public Dimensions dividedBy(int divisor) {
return new Dimensions(width / divisor, height / divisor);
}
public Dimensions multiplyBy(float multiplier) {
int w = (int) (width * multiplier);
int h = (int) (height * multiplier);
return new Dimensions(w, h);
}
/**
* Multiplies (in place) both width and height of this Dimensions object by multiplier.
* @param multiplier A float representing a multiplication factor.
*/
public void multiplySelfBy(float multiplier) {
width *= multiplier;
height *= multiplier;
}
/**
* Returns true if the other instance of this class is null or has different width/height.
* Similar to the more standard equals(), doesn't bother with checking if -other- is an instance
* of Dimensions. It also makes for more readable code, i.e.:
*
* newDimensions.areDifferentFrom(oldDimensions)
*
* @param other A Dimensions object
* @return True if the two objects are different as defined above.
*/
public boolean areDifferentFrom(Dimensions other) {
return other == null || this.width != other.width || this.height != other.height;
}
/**
* Identical in behaviour to areDifferentFrom(Dimensions other),
* in some situation can be more semantically appropriate, i.e.:
*
* newResolution.isDifferentFrom(oldResolution);
*
* @param other A Dimensions object.
* @return True if the two objects are different as defined in the javadoc for areDifferentFrom(other).
*/
public boolean isDifferentFrom(Dimensions other) {
return areDifferentFrom(other);
}
/**
* Returns the width.
* @return An integer representing the width stored in the Dimensions instance.
*/
public int width() {
return this.width;
}
/**
* Returns the height.
* @return An integer representing the height stored in the Dimensions instance.
*/
public int height() {
return this.height;
}
}
}