mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-09 05:11:52 +09:00
migration wip java 9 modularise
This commit is contained in:
@@ -1,743 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 See AUTHORS file.
|
||||
*
|
||||
* 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 com.badlogic.gdx.backends.lwjgl;
|
||||
|
||||
import com.badlogic.gdx.Application;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Graphics;
|
||||
import com.badlogic.gdx.graphics.Cursor.SystemCursor;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.GL30;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.Pixmap.Format;
|
||||
import com.badlogic.gdx.graphics.glutils.GLVersion;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.GdxRuntimeException;
|
||||
import com.badlogic.gdx.utils.SharedLibraryLoader;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.ContextAttribs;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.PixelFormat;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/** An implementation of the {@link Graphics} interface based on Lwjgl.
|
||||
* @author mzechner */
|
||||
public class LwjglGraphics implements Graphics {
|
||||
|
||||
/** The suppored OpenGL extensions */
|
||||
static Array<String> extensions;
|
||||
static GLVersion glVersion;
|
||||
|
||||
GL20 gl20;
|
||||
GL30 gl30;
|
||||
long frameId = -1;
|
||||
float deltaTime = 0;
|
||||
long frameStart = 0;
|
||||
int frames = 0;
|
||||
int fps;
|
||||
long lastTime = System.nanoTime();
|
||||
Canvas canvas;
|
||||
boolean vsync = false;
|
||||
boolean resize = false;
|
||||
LwjglApplicationConfiguration config;
|
||||
BufferFormat bufferFormat = new BufferFormat(8, 8, 8, 8, 16, 8, 0, false);
|
||||
volatile boolean isContinuous = true;
|
||||
volatile boolean requestRendering = false;
|
||||
boolean softwareMode;
|
||||
boolean usingGL30;
|
||||
|
||||
// deltaTime kalman filter related variables
|
||||
private float kalmanEstimate = 1.0f/60.0f;
|
||||
private float kalmanErrorCovariance = 1.0f;
|
||||
private final float kalmanErrorRate = 0.2f; // 0.2: empirical value
|
||||
private final float kalmanUpdateThreshold = 0.1f;
|
||||
private final float getMagnitudeDifferenceEpsilon = 0.00001f;
|
||||
|
||||
LwjglGraphics (LwjglApplicationConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
LwjglGraphics (Canvas canvas) {
|
||||
this.config = new LwjglApplicationConfiguration();
|
||||
config.width = canvas.getWidth();
|
||||
config.height = canvas.getHeight();
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
LwjglGraphics (Canvas canvas, LwjglApplicationConfiguration config) {
|
||||
this.config = config;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
|
||||
public int getHeight () {
|
||||
if (canvas != null)
|
||||
return Math.max(1, canvas.getHeight());
|
||||
else
|
||||
return (int)(Display.getHeight() * Display.getPixelScaleFactor());
|
||||
}
|
||||
|
||||
public int getWidth () {
|
||||
if (canvas != null)
|
||||
return Math.max(1, canvas.getWidth());
|
||||
else
|
||||
return (int)(Display.getWidth() * Display.getPixelScaleFactor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBackBufferWidth () {
|
||||
return getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBackBufferHeight () {
|
||||
return getHeight();
|
||||
}
|
||||
|
||||
|
||||
public long getFrameId () {
|
||||
return frameId;
|
||||
}
|
||||
|
||||
public float getDeltaTime () {
|
||||
return kalmanEstimate;
|
||||
}
|
||||
|
||||
private void resetDeltaSmoothingHistory() {
|
||||
kalmanEstimate = 1.0f/60.0f;
|
||||
kalmanErrorCovariance = 1.0f;
|
||||
}
|
||||
|
||||
// only for a > 0 && b > 0
|
||||
private float getMagnitudeDifference(float a, float b) {
|
||||
if (a < getMagnitudeDifferenceEpsilon || b < getMagnitudeDifferenceEpsilon) {
|
||||
return (a + b) / getMagnitudeDifferenceEpsilon;
|
||||
}
|
||||
|
||||
if (a > b) {
|
||||
return a / b;
|
||||
}
|
||||
else {
|
||||
return b / a;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateKalmanRenderDelta() {
|
||||
// The problem with this kalman filter is that it assumes most simplistic situation:
|
||||
// 1. the actual delta (measured delta - noise) is constant (that is, not constantly increasing or something)
|
||||
// 2. everything is linear
|
||||
// We may need to implement Extended Kalman Filter but what is Jacobian, I suck at maths.
|
||||
//
|
||||
// Instead, this implementation will reset itself when difference in magnitude between
|
||||
// old and new is greater than set value.
|
||||
//
|
||||
// It's not perfect but it works, much better than averaging.
|
||||
|
||||
if (getMagnitudeDifference(deltaTime, kalmanEstimate) >= 2.0) {
|
||||
resetDeltaSmoothingHistory();
|
||||
}
|
||||
|
||||
// measurement value
|
||||
float observation = deltaTime;
|
||||
|
||||
if (observation <= kalmanUpdateThreshold) {
|
||||
// time update
|
||||
float priorEstimate = kalmanEstimate;
|
||||
float priorError = kalmanErrorCovariance;
|
||||
|
||||
// measurement update
|
||||
float gain = priorError / (priorError + kalmanErrorRate);
|
||||
float newEstimate = priorEstimate + gain * (observation - priorEstimate);
|
||||
float newError = (1.0f - gain) * priorError;
|
||||
|
||||
kalmanEstimate = newEstimate;
|
||||
kalmanErrorCovariance = newError;
|
||||
}
|
||||
}
|
||||
|
||||
public float getRawDeltaTime () {
|
||||
return deltaTime;
|
||||
}
|
||||
|
||||
public GraphicsType getType () {
|
||||
return GraphicsType.LWJGL;
|
||||
}
|
||||
|
||||
public GLVersion getGLVersion () {
|
||||
return glVersion;
|
||||
}
|
||||
|
||||
public boolean isGL20Available () {
|
||||
return gl20 != null;
|
||||
}
|
||||
|
||||
public GL20 getGL20 () {
|
||||
return gl20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGL20 (GL20 gl20) {
|
||||
this.gl20 = gl20;
|
||||
if (gl30 == null) {
|
||||
Gdx.gl = gl20;
|
||||
Gdx.gl20 = gl20;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGL30Available () {
|
||||
return gl30 != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GL30 getGL30 () {
|
||||
return gl30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGL30 (GL30 gl30) {
|
||||
this.gl30 = gl30;
|
||||
if (gl30 != null) {
|
||||
this.gl20 = gl30;
|
||||
|
||||
Gdx.gl = gl20;
|
||||
Gdx.gl20 = gl20;
|
||||
Gdx.gl30 = gl30;
|
||||
}
|
||||
}
|
||||
|
||||
public int getFramesPerSecond () {
|
||||
return fps;
|
||||
}
|
||||
|
||||
void updateTime () {
|
||||
long time = System.nanoTime();
|
||||
deltaTime = (time - lastTime) / 1000000000.0f;
|
||||
lastTime = time;
|
||||
|
||||
if (time - frameStart >= 1000000000) {
|
||||
fps = frames;
|
||||
frames = 0;
|
||||
frameStart = time;
|
||||
}
|
||||
frames++;
|
||||
|
||||
updateKalmanRenderDelta();
|
||||
}
|
||||
|
||||
void setupDisplay () throws LWJGLException {
|
||||
if (config.useHDPI) {
|
||||
System.setProperty("org.lwjgl.opengl.Display.enableHighDPI", "true");
|
||||
}
|
||||
|
||||
if (canvas != null) {
|
||||
Display.setParent(canvas);
|
||||
} else {
|
||||
boolean displayCreated = false;
|
||||
|
||||
if(!config.fullscreen) {
|
||||
displayCreated = setWindowedMode(config.width, config.height);
|
||||
} else {
|
||||
DisplayMode bestMode = null;
|
||||
for(DisplayMode mode: getDisplayModes()) {
|
||||
if(mode.width == config.width && mode.height == config.height) {
|
||||
if(bestMode == null || bestMode.refreshRate < this.getDisplayMode().refreshRate) {
|
||||
bestMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(bestMode == null) {
|
||||
bestMode = this.getDisplayMode();
|
||||
}
|
||||
displayCreated = setFullscreenMode(bestMode);
|
||||
}
|
||||
if (!displayCreated) {
|
||||
if (config.setDisplayModeCallback != null) {
|
||||
config = config.setDisplayModeCallback.onFailure(config);
|
||||
if (config != null) {
|
||||
displayCreated = setWindowedMode(config.width, config.height);
|
||||
}
|
||||
}
|
||||
if (!displayCreated) {
|
||||
throw new GdxRuntimeException("Couldn't set display mode " + config.width + "x" + config.height + ", fullscreen: "
|
||||
+ config.fullscreen);
|
||||
}
|
||||
}
|
||||
if (config.iconPaths.size > 0) {
|
||||
ByteBuffer[] icons = new ByteBuffer[config.iconPaths.size];
|
||||
for (int i = 0, n = config.iconPaths.size; i < n; i++) {
|
||||
Pixmap pixmap = new Pixmap(Gdx.files.getFileHandle(config.iconPaths.get(i), config.iconFileTypes.get(i)));
|
||||
if (pixmap.getFormat() != Format.RGBA8888) {
|
||||
Pixmap rgba = new Pixmap(pixmap.getWidth(), pixmap.getHeight(), Format.RGBA8888);
|
||||
rgba.drawPixmap(pixmap, 0, 0);
|
||||
pixmap.dispose();
|
||||
pixmap = rgba;
|
||||
}
|
||||
icons[i] = ByteBuffer.allocateDirect(pixmap.getPixels().limit());
|
||||
icons[i].put(pixmap.getPixels()).flip();
|
||||
pixmap.dispose();
|
||||
}
|
||||
Display.setIcon(icons);
|
||||
}
|
||||
}
|
||||
Display.setTitle(config.title);
|
||||
Display.setResizable(config.resizable);
|
||||
Display.setInitialBackground(config.initialBackgroundColor.r, config.initialBackgroundColor.g,
|
||||
config.initialBackgroundColor.b);
|
||||
|
||||
Display.setLocation(config.x, config.y);
|
||||
createDisplayPixelFormat(config.useGL30, config.gles30ContextMajorVersion, config.gles30ContextMinorVersion);
|
||||
initiateGL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only needed when setupDisplay() is not called.
|
||||
*/
|
||||
void initiateGL() {
|
||||
extractVersion();
|
||||
extractExtensions();
|
||||
initiateGLInstances();
|
||||
}
|
||||
|
||||
private static void extractVersion () {
|
||||
String versionString = org.lwjgl.opengl.GL11.glGetString(GL11.GL_VERSION);
|
||||
String vendorString = org.lwjgl.opengl.GL11.glGetString(GL11.GL_VENDOR);
|
||||
String rendererString = org.lwjgl.opengl.GL11.glGetString(GL11.GL_RENDERER);
|
||||
glVersion = new GLVersion(Application.ApplicationType.Desktop, versionString, vendorString, rendererString);
|
||||
}
|
||||
|
||||
private static void extractExtensions () {
|
||||
extensions = new Array<String>();
|
||||
if (glVersion.isVersionEqualToOrHigher(3, 2)) {
|
||||
int numExtensions = GL11.glGetInteger(GL30.GL_NUM_EXTENSIONS);
|
||||
for (int i = 0; i < numExtensions; ++i)
|
||||
extensions.add(org.lwjgl.opengl.GL30.glGetStringi(GL20.GL_EXTENSIONS, i));
|
||||
} else {
|
||||
extensions.addAll(org.lwjgl.opengl.GL11.glGetString(GL20.GL_EXTENSIONS).split(" "));
|
||||
}
|
||||
}
|
||||
|
||||
/** @return whether the supported OpenGL (not ES) version is compatible with OpenGL ES 3.x. */
|
||||
private static boolean fullCompatibleWithGLES3 () {
|
||||
// OpenGL ES 3.0 is compatible with OpenGL 4.3 core, see http://en.wikipedia.org/wiki/OpenGL_ES#OpenGL_ES_3.0
|
||||
return glVersion.isVersionEqualToOrHigher(4, 3);
|
||||
}
|
||||
|
||||
/** @return whether the supported OpenGL (not ES) version is compatible with OpenGL ES 2.x. */
|
||||
private static boolean fullCompatibleWithGLES2 () {
|
||||
// OpenGL ES 2.0 is compatible with OpenGL 4.1 core
|
||||
// see https://www.opengl.org/registry/specs/ARB/ES2_compatibility.txt
|
||||
return glVersion.isVersionEqualToOrHigher(4, 1) || extensions.contains("GL_ARB_ES2_compatibility", false);
|
||||
}
|
||||
|
||||
private static boolean supportsFBO () {
|
||||
// FBO is in core since OpenGL 3.0, see https://www.opengl.org/wiki/Framebuffer_Object
|
||||
return glVersion.isVersionEqualToOrHigher(3, 0) || extensions.contains("GL_EXT_framebuffer_object", false)
|
||||
|| extensions.contains("GL_ARB_framebuffer_object", false);
|
||||
}
|
||||
|
||||
private void createDisplayPixelFormat (boolean useGL30, int gles30ContextMajor, int gles30ContextMinor) {
|
||||
try {
|
||||
if (useGL30) {
|
||||
ContextAttribs context = new ContextAttribs(gles30ContextMajor, gles30ContextMinor).withForwardCompatible(false)
|
||||
.withProfileCore(true);
|
||||
try {
|
||||
Display.create(new PixelFormat(config.r + config.g + config.b, config.a, config.depth, config.stencil,
|
||||
config.samples), context);
|
||||
} catch (Exception e) {
|
||||
System.out.println("LwjglGraphics: OpenGL " + gles30ContextMajor + "." + gles30ContextMinor
|
||||
+ "+ core profile (GLES 3.0) not supported.");
|
||||
createDisplayPixelFormat(false, gles30ContextMajor, gles30ContextMinor);
|
||||
return;
|
||||
}
|
||||
System.out.println("LwjglGraphics: created OpenGL " + gles30ContextMajor + "." + gles30ContextMinor
|
||||
+ "+ core profile (GLES 3.0) context. This is experimental!");
|
||||
usingGL30 = true;
|
||||
} else {
|
||||
Display
|
||||
.create(new PixelFormat(config.r + config.g + config.b, config.a, config.depth, config.stencil, config.samples));
|
||||
usingGL30 = false;
|
||||
}
|
||||
bufferFormat = new BufferFormat(config.r, config.g, config.b, config.a, config.depth, config.stencil, config.samples,
|
||||
false);
|
||||
} catch (Exception ex) {
|
||||
Display.destroy();
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
try {
|
||||
Display.create(new PixelFormat(0, 16, 8));
|
||||
if (getDisplayMode().bitsPerPixel == 16) {
|
||||
bufferFormat = new BufferFormat(5, 6, 5, 0, 16, 8, 0, false);
|
||||
}
|
||||
if (getDisplayMode().bitsPerPixel == 24) {
|
||||
bufferFormat = new BufferFormat(8, 8, 8, 0, 16, 8, 0, false);
|
||||
}
|
||||
if (getDisplayMode().bitsPerPixel == 32) {
|
||||
bufferFormat = new BufferFormat(8, 8, 8, 8, 16, 8, 0, false);
|
||||
}
|
||||
} catch (Exception ex2) {
|
||||
Display.destroy();
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
try {
|
||||
Display.create(new PixelFormat());
|
||||
} catch (Exception ex3) {
|
||||
if (!softwareMode && config.allowSoftwareMode) {
|
||||
softwareMode = true;
|
||||
System.setProperty("org.lwjgl.opengl.Display.allowSoftwareOpenGL", "true");
|
||||
createDisplayPixelFormat(useGL30, gles30ContextMajor, gles30ContextMinor);
|
||||
return;
|
||||
}
|
||||
throw new GdxRuntimeException("OpenGL is not supported by the video driver.", ex3);
|
||||
}
|
||||
if (getDisplayMode().bitsPerPixel == 16) {
|
||||
bufferFormat = new BufferFormat(5, 6, 5, 0, 8, 0, 0, false);
|
||||
}
|
||||
if (getDisplayMode().bitsPerPixel == 24) {
|
||||
bufferFormat = new BufferFormat(8, 8, 8, 0, 8, 0, 0, false);
|
||||
}
|
||||
if (getDisplayMode().bitsPerPixel == 32) {
|
||||
bufferFormat = new BufferFormat(8, 8, 8, 8, 8, 0, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void initiateGLInstances () {
|
||||
if (usingGL30) {
|
||||
gl30 = new LwjglGL30();
|
||||
gl20 = gl30;
|
||||
} else {
|
||||
gl20 = new LwjglGL20();
|
||||
}
|
||||
|
||||
if (!glVersion.isVersionEqualToOrHigher(2, 0))
|
||||
throw new GdxRuntimeException("OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: "
|
||||
+ GL11.glGetString(GL11.GL_VERSION) + "\n" + glVersion.getDebugVersionString());
|
||||
|
||||
if (!supportsFBO()) {
|
||||
throw new GdxRuntimeException("OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: "
|
||||
+ GL11.glGetString(GL11.GL_VERSION) + ", FBO extension: false\n" + glVersion.getDebugVersionString());
|
||||
}
|
||||
|
||||
Gdx.gl = gl20;
|
||||
Gdx.gl20 = gl20;
|
||||
Gdx.gl30 = gl30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPpiX () {
|
||||
return Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPpiY () {
|
||||
return Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPpcX () {
|
||||
return (Toolkit.getDefaultToolkit().getScreenResolution() / 2.54f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPpcY () {
|
||||
return (Toolkit.getDefaultToolkit().getScreenResolution() / 2.54f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDensity () {
|
||||
if (config.overrideDensity != -1) return config.overrideDensity / 160f;
|
||||
return (Toolkit.getDefaultToolkit().getScreenResolution() / 160f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsDisplayModeChange () {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Monitor getPrimaryMonitor () {
|
||||
return new LwjglMonitor(0, 0, "Primary Monitor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Monitor getMonitor () {
|
||||
return getPrimaryMonitor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Monitor[] getMonitors () {
|
||||
return new Monitor[] { getPrimaryMonitor() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayMode[] getDisplayModes (Monitor monitor) {
|
||||
return getDisplayModes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayMode getDisplayMode (Monitor monitor) {
|
||||
return getDisplayMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setFullscreenMode (DisplayMode displayMode) {
|
||||
org.lwjgl.opengl.DisplayMode mode = ((LwjglDisplayMode)displayMode).mode;
|
||||
try {
|
||||
if (!mode.isFullscreenCapable()) {
|
||||
Display.setDisplayMode(mode);
|
||||
} else {
|
||||
Display.setDisplayModeAndFullscreen(mode);
|
||||
}
|
||||
float scaleFactor = Display.getPixelScaleFactor();
|
||||
config.width = (int)(mode.getWidth() * scaleFactor);
|
||||
config.height = (int)(mode.getHeight() * scaleFactor);
|
||||
if (Gdx.gl != null) Gdx.gl.glViewport(0, 0, config.width, config.height);
|
||||
resize = true;
|
||||
return true;
|
||||
} catch (LWJGLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Kindly stolen from http://lwjgl.org/wiki/index.php?title=LWJGL_Basics_5_(Fullscreen), not perfect but will do. */
|
||||
@Override
|
||||
public boolean setWindowedMode (int width, int height) {
|
||||
if (getWidth() == width && getHeight() == height && !Display.isFullscreen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
org.lwjgl.opengl.DisplayMode targetDisplayMode = null;
|
||||
boolean fullscreen = false;
|
||||
|
||||
if (fullscreen) {
|
||||
org.lwjgl.opengl.DisplayMode[] modes = Display.getAvailableDisplayModes();
|
||||
int freq = 0;
|
||||
|
||||
for (int i = 0; i < modes.length; i++) {
|
||||
org.lwjgl.opengl.DisplayMode current = modes[i];
|
||||
|
||||
if ((current.getWidth() == width) && (current.getHeight() == height)) {
|
||||
if ((targetDisplayMode == null) || (current.getFrequency() >= freq)) {
|
||||
if ((targetDisplayMode == null) || (current.getBitsPerPixel() > targetDisplayMode.getBitsPerPixel())) {
|
||||
targetDisplayMode = current;
|
||||
freq = targetDisplayMode.getFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
// if we've found a match for bpp and frequence against the
|
||||
// original display mode then it's probably best to go for this one
|
||||
// since it's most likely compatible with the monitor
|
||||
if ((current.getBitsPerPixel() == Display.getDesktopDisplayMode().getBitsPerPixel())
|
||||
&& (current.getFrequency() == Display.getDesktopDisplayMode().getFrequency())) {
|
||||
targetDisplayMode = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
targetDisplayMode = new org.lwjgl.opengl.DisplayMode(width, height);
|
||||
}
|
||||
|
||||
if (targetDisplayMode == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean resizable = !fullscreen && config.resizable;
|
||||
|
||||
Display.setDisplayMode(targetDisplayMode);
|
||||
Display.setFullscreen(fullscreen);
|
||||
// Workaround for bug in LWJGL whereby resizable state is lost on DisplayMode change
|
||||
if (resizable == Display.isResizable()) {
|
||||
Display.setResizable(!resizable);
|
||||
}
|
||||
Display.setResizable(resizable);
|
||||
|
||||
float scaleFactor = Display.getPixelScaleFactor();
|
||||
config.width = (int)(targetDisplayMode.getWidth() * scaleFactor);
|
||||
config.height = (int)(targetDisplayMode.getHeight() * scaleFactor);
|
||||
if (Gdx.gl != null) Gdx.gl.glViewport(0, 0, config.width, config.height);
|
||||
resize = true;
|
||||
return true;
|
||||
} catch (LWJGLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayMode[] getDisplayModes () {
|
||||
try {
|
||||
org.lwjgl.opengl.DisplayMode[] availableDisplayModes = Display.getAvailableDisplayModes();
|
||||
DisplayMode[] modes = new DisplayMode[availableDisplayModes.length];
|
||||
|
||||
int idx = 0;
|
||||
for (org.lwjgl.opengl.DisplayMode mode : availableDisplayModes) {
|
||||
if (mode.isFullscreenCapable()) {
|
||||
modes[idx++] = new LwjglDisplayMode(mode.getWidth(), mode.getHeight(), mode.getFrequency(),
|
||||
mode.getBitsPerPixel(), mode);
|
||||
}
|
||||
}
|
||||
|
||||
return modes;
|
||||
} catch (LWJGLException e) {
|
||||
throw new GdxRuntimeException("Couldn't fetch available display modes", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayMode getDisplayMode () {
|
||||
org.lwjgl.opengl.DisplayMode mode = Display.getDesktopDisplayMode();
|
||||
return new LwjglDisplayMode(mode.getWidth(), mode.getHeight(), mode.getFrequency(), mode.getBitsPerPixel(), mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle (String title) {
|
||||
Display.setTitle(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display must be reconfigured via {@link #setWindowedMode(int, int)} for the changes to take
|
||||
* effect.
|
||||
*/
|
||||
@Override
|
||||
public void setUndecorated (boolean undecorated) {
|
||||
System.setProperty("org.lwjgl.opengl.Window.undecorated", undecorated ? "true" : "false");
|
||||
}
|
||||
|
||||
/**
|
||||
* Display must be reconfigured via {@link #setWindowedMode(int, int)} for the changes to take
|
||||
* effect.
|
||||
*/
|
||||
@Override
|
||||
public void setResizable (boolean resizable) {
|
||||
this.config.resizable = resizable;
|
||||
Display.setResizable(resizable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferFormat getBufferFormat () {
|
||||
return bufferFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVSync (boolean vsync) {
|
||||
this.vsync = vsync;
|
||||
Display.setVSyncEnabled(vsync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsExtension (String extension) {
|
||||
return extensions.contains(extension, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousRendering (boolean isContinuous) {
|
||||
this.isContinuous = isContinuous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContinuousRendering () {
|
||||
return isContinuous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestRendering () {
|
||||
synchronized (this) {
|
||||
requestRendering = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldRender () {
|
||||
synchronized (this) {
|
||||
boolean rq = requestRendering;
|
||||
requestRendering = false;
|
||||
return rq || isContinuous || Display.isDirty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullscreen () {
|
||||
return Display.isFullscreen();
|
||||
}
|
||||
|
||||
public boolean isSoftwareMode () {
|
||||
return softwareMode;
|
||||
}
|
||||
|
||||
/** A callback used by LwjglApplication when trying to create the display */
|
||||
public interface SetDisplayModeCallback {
|
||||
/** If the display creation fails, this method will be called. Suggested usage is to modify the passed configuration to use a
|
||||
* common width and height, and set fullscreen to false.
|
||||
* @return the configuration to be used for a second attempt at creating a display. A null value results in NOT attempting
|
||||
* to create the display a second time */
|
||||
public LwjglApplicationConfiguration onFailure (LwjglApplicationConfiguration initialConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.badlogic.gdx.graphics.Cursor newCursor (Pixmap pixmap, int xHotspot, int yHotspot) {
|
||||
return new LwjglCursor(pixmap, xHotspot, yHotspot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCursor (com.badlogic.gdx.graphics.Cursor cursor) {
|
||||
if (canvas != null && SharedLibraryLoader.isMac) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Mouse.setNativeCursor(((LwjglCursor)cursor).lwjglCursor);
|
||||
} catch (LWJGLException e) {
|
||||
throw new GdxRuntimeException("Could not set cursor image.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSystemCursor (SystemCursor systemCursor) {
|
||||
if (canvas != null && SharedLibraryLoader.isMac) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Mouse.setNativeCursor(null);
|
||||
} catch (LWJGLException e) {
|
||||
throw new GdxRuntimeException("Couldn't set system cursor");
|
||||
}
|
||||
}
|
||||
|
||||
private class LwjglDisplayMode extends DisplayMode {
|
||||
org.lwjgl.opengl.DisplayMode mode;
|
||||
|
||||
public LwjglDisplayMode (int width, int height, int refreshRate, int bitsPerPixel, org.lwjgl.opengl.DisplayMode mode) {
|
||||
super(width, height, refreshRate, bitsPerPixel);
|
||||
this.mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
private class LwjglMonitor extends Monitor {
|
||||
protected LwjglMonitor (int virtualX, int virtualY, String name) {
|
||||
super(virtualX, virtualY, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 See AUTHORS file.
|
||||
*
|
||||
* 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 com.badlogic.gdx.backends.lwjgl.audio;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.badlogic.gdx.Audio;
|
||||
import com.badlogic.gdx.audio.AudioDevice;
|
||||
import com.badlogic.gdx.audio.AudioRecorder;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.GdxRuntimeException;
|
||||
import com.badlogic.gdx.utils.IntArray;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.LongMap;
|
||||
import com.badlogic.gdx.utils.ObjectMap;
|
||||
|
||||
import static org.lwjgl.openal.AL10.*;
|
||||
|
||||
/** @author Nathan Sweet */
|
||||
public class OpenALAudio implements Audio {
|
||||
private final int deviceBufferSize;
|
||||
private final int deviceBufferCount;
|
||||
private IntArray idleSources, allSources;
|
||||
private LongMap<Integer> soundIdToSource;
|
||||
private IntMap<Long> sourceToSoundId;
|
||||
private long nextSoundId = 0;
|
||||
private ObjectMap<String, Class<? extends OpenALSound>> extensionToSoundClass = new ObjectMap();
|
||||
private ObjectMap<String, Class<? extends OpenALMusic>> extensionToMusicClass = new ObjectMap();
|
||||
private OpenALSound[] recentSounds;
|
||||
private int mostRecetSound = -1;
|
||||
|
||||
Array<OpenALMusic> music = new Array(false, 1, OpenALMusic.class);
|
||||
boolean noDevice = false;
|
||||
|
||||
public OpenALAudio () {
|
||||
this(16, 9, 512);
|
||||
}
|
||||
|
||||
public OpenALAudio (int simultaneousSources, int deviceBufferCount, int deviceBufferSize) {
|
||||
this.deviceBufferSize = deviceBufferSize;
|
||||
this.deviceBufferCount = deviceBufferCount;
|
||||
|
||||
registerSound("ogg", Ogg.Sound.class);
|
||||
registerMusic("ogg", Ogg.Music.class);
|
||||
registerSound("wav", Wav.Sound.class);
|
||||
registerMusic("wav", Wav.Music.class);
|
||||
registerSound("mp3", Mp3.Sound.class);
|
||||
registerMusic("mp3", Mp3.Music.class);
|
||||
|
||||
try {
|
||||
AL.create();
|
||||
} catch (LWJGLException ex) {
|
||||
noDevice = true;
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
allSources = new IntArray(false, simultaneousSources);
|
||||
for (int i = 0; i < simultaneousSources; i++) {
|
||||
int sourceID = alGenSources();
|
||||
if (alGetError() != AL_NO_ERROR) break;
|
||||
allSources.add(sourceID);
|
||||
}
|
||||
idleSources = new IntArray(allSources);
|
||||
soundIdToSource = new LongMap<Integer>();
|
||||
sourceToSoundId = new IntMap<Long>();
|
||||
|
||||
FloatBuffer orientation = (FloatBuffer)BufferUtils.createFloatBuffer(6)
|
||||
.put(new float[] {0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f}).flip();
|
||||
alListener(AL_ORIENTATION, orientation);
|
||||
FloatBuffer velocity = (FloatBuffer)BufferUtils.createFloatBuffer(3).put(new float[] {0.0f, 0.0f, 0.0f}).flip();
|
||||
alListener(AL_VELOCITY, velocity);
|
||||
FloatBuffer position = (FloatBuffer)BufferUtils.createFloatBuffer(3).put(new float[] {0.0f, 0.0f, 0.0f}).flip();
|
||||
alListener(AL_POSITION, position);
|
||||
|
||||
recentSounds = new OpenALSound[simultaneousSources];
|
||||
}
|
||||
|
||||
public void registerSound (String extension, Class<? extends OpenALSound> soundClass) {
|
||||
if (extension == null) throw new IllegalArgumentException("extension cannot be null.");
|
||||
if (soundClass == null) throw new IllegalArgumentException("soundClass cannot be null.");
|
||||
extensionToSoundClass.put(extension, soundClass);
|
||||
}
|
||||
|
||||
public void registerMusic (String extension, Class<? extends OpenALMusic> musicClass) {
|
||||
if (extension == null) throw new IllegalArgumentException("extension cannot be null.");
|
||||
if (musicClass == null) throw new IllegalArgumentException("musicClass cannot be null.");
|
||||
extensionToMusicClass.put(extension, musicClass);
|
||||
}
|
||||
|
||||
public OpenALSound newSound (FileHandle file) {
|
||||
if (file == null) throw new IllegalArgumentException("file cannot be null.");
|
||||
Class<? extends OpenALSound> soundClass = extensionToSoundClass.get(file.extension().toLowerCase());
|
||||
if (soundClass == null) throw new GdxRuntimeException("Unknown file extension for sound: " + file);
|
||||
try {
|
||||
return soundClass.getConstructor(new Class[] {OpenALAudio.class, FileHandle.class}).newInstance(this, file);
|
||||
} catch (Exception ex) {
|
||||
throw new GdxRuntimeException("Error creating sound " + soundClass.getName() + " for file: " + file, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public OpenALMusic newMusic (FileHandle file) {
|
||||
if (file == null) throw new IllegalArgumentException("file cannot be null.");
|
||||
Class<? extends OpenALMusic> musicClass = extensionToMusicClass.get(file.extension().toLowerCase());
|
||||
if (musicClass == null) throw new GdxRuntimeException("Unknown file extension for music: " + file);
|
||||
try {
|
||||
return musicClass.getConstructor(new Class[] {OpenALAudio.class, FileHandle.class}).newInstance(this, file);
|
||||
} catch (Exception ex) {
|
||||
throw new GdxRuntimeException("Error creating music " + musicClass.getName() + " for file: " + file, ex);
|
||||
}
|
||||
}
|
||||
|
||||
int obtainSource (boolean isMusic) {
|
||||
if (noDevice) return 0;
|
||||
for (int i = 0, n = idleSources.size; i < n; i++) {
|
||||
int sourceId = idleSources.get(i);
|
||||
int state = alGetSourcei(sourceId, AL_SOURCE_STATE);
|
||||
if (state != AL_PLAYING && state != AL_PAUSED) {
|
||||
if (isMusic) {
|
||||
idleSources.removeIndex(i);
|
||||
} else {
|
||||
if (sourceToSoundId.containsKey(sourceId)) {
|
||||
long soundId = sourceToSoundId.get(sourceId);
|
||||
sourceToSoundId.remove(sourceId);
|
||||
soundIdToSource.remove(soundId);
|
||||
}
|
||||
|
||||
long soundId = nextSoundId++;
|
||||
sourceToSoundId.put(sourceId, soundId);
|
||||
soundIdToSource.put(soundId, sourceId);
|
||||
}
|
||||
alSourceStop(sourceId);
|
||||
alSourcei(sourceId, AL_BUFFER, 0);
|
||||
AL10.alSourcef(sourceId, AL10.AL_GAIN, 1);
|
||||
AL10.alSourcef(sourceId, AL10.AL_PITCH, 1);
|
||||
AL10.alSource3f(sourceId, AL10.AL_POSITION, 0, 0, 1f);
|
||||
return sourceId;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void freeSource (int sourceID) {
|
||||
if (noDevice) return;
|
||||
alSourceStop(sourceID);
|
||||
alSourcei(sourceID, AL_BUFFER, 0);
|
||||
if (sourceToSoundId.containsKey(sourceID)) {
|
||||
long soundId = sourceToSoundId.remove(sourceID);
|
||||
soundIdToSource.remove(soundId);
|
||||
}
|
||||
idleSources.add(sourceID);
|
||||
}
|
||||
|
||||
void freeBuffer (int bufferID) {
|
||||
if (noDevice) return;
|
||||
for (int i = 0, n = idleSources.size; i < n; i++) {
|
||||
int sourceID = idleSources.get(i);
|
||||
if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) {
|
||||
if (sourceToSoundId.containsKey(sourceID)) {
|
||||
long soundId = sourceToSoundId.remove(sourceID);
|
||||
soundIdToSource.remove(soundId);
|
||||
}
|
||||
alSourceStop(sourceID);
|
||||
alSourcei(sourceID, AL_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stopSourcesWithBuffer (int bufferID) {
|
||||
if (noDevice) return;
|
||||
for (int i = 0, n = idleSources.size; i < n; i++) {
|
||||
int sourceID = idleSources.get(i);
|
||||
if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) {
|
||||
if (sourceToSoundId.containsKey(sourceID)) {
|
||||
long soundId = sourceToSoundId.remove(sourceID);
|
||||
soundIdToSource.remove(soundId);
|
||||
}
|
||||
alSourceStop(sourceID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pauseSourcesWithBuffer (int bufferID) {
|
||||
if (noDevice) return;
|
||||
for (int i = 0, n = idleSources.size; i < n; i++) {
|
||||
int sourceID = idleSources.get(i);
|
||||
if (alGetSourcei(sourceID, AL_BUFFER) == bufferID)
|
||||
alSourcePause(sourceID);
|
||||
}
|
||||
}
|
||||
|
||||
void resumeSourcesWithBuffer (int bufferID) {
|
||||
if (noDevice) return;
|
||||
for (int i = 0, n = idleSources.size; i < n; i++) {
|
||||
int sourceID = idleSources.get(i);
|
||||
if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) {
|
||||
if (alGetSourcei(sourceID, AL_SOURCE_STATE) == AL_PAUSED)
|
||||
alSourcePlay(sourceID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update () {
|
||||
if (noDevice) return;
|
||||
for (int i = 0; i < music.size; i++)
|
||||
music.items[i].update();
|
||||
}
|
||||
|
||||
public long getSoundId (int sourceId) {
|
||||
if (!sourceToSoundId.containsKey(sourceId)) return -1;
|
||||
return sourceToSoundId.get(sourceId);
|
||||
}
|
||||
|
||||
public void stopSound (long soundId) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
alSourceStop(sourceId);
|
||||
}
|
||||
|
||||
public void pauseSound (long soundId) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
alSourcePause(sourceId);
|
||||
}
|
||||
|
||||
public void resumeSound (long soundId) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
if (alGetSourcei(sourceId, AL_SOURCE_STATE) == AL_PAUSED)
|
||||
alSourcePlay(sourceId);
|
||||
}
|
||||
|
||||
public void setSoundGain (long soundId, float volume) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
AL10.alSourcef(sourceId, AL10.AL_GAIN, volume);
|
||||
}
|
||||
|
||||
public void setSoundLooping (long soundId, boolean looping) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
alSourcei(sourceId, AL10.AL_LOOPING, looping ? AL10.AL_TRUE : AL10.AL_FALSE);
|
||||
}
|
||||
|
||||
public void setSoundPitch (long soundId, float pitch) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
AL10.alSourcef(sourceId, AL10.AL_PITCH, pitch);
|
||||
}
|
||||
|
||||
public void setSoundPan (long soundId, float pan, float volume) {
|
||||
if (!soundIdToSource.containsKey(soundId)) return;
|
||||
int sourceId = soundIdToSource.get(soundId);
|
||||
|
||||
AL10.alSource3f(sourceId, AL10.AL_POSITION, MathUtils.cos((pan - 1) * MathUtils.PI / 2), 0,
|
||||
MathUtils.sin((pan + 1) * MathUtils.PI / 2));
|
||||
AL10.alSourcef(sourceId, AL10.AL_GAIN, volume);
|
||||
}
|
||||
|
||||
public void dispose () {
|
||||
if (noDevice) return;
|
||||
for (int i = 0, n = allSources.size; i < n; i++) {
|
||||
int sourceID = allSources.get(i);
|
||||
int state = alGetSourcei(sourceID, AL_SOURCE_STATE);
|
||||
if (state != AL_STOPPED) alSourceStop(sourceID);
|
||||
alDeleteSources(sourceID);
|
||||
}
|
||||
|
||||
sourceToSoundId.clear();
|
||||
soundIdToSource.clear();
|
||||
|
||||
AL.destroy();
|
||||
while (AL.isCreated()) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AudioDevice newAudioDevice (int sampleRate, final boolean isMono) {
|
||||
if (noDevice) return new AudioDevice() {
|
||||
@Override
|
||||
public void writeSamples (float[] samples, int offset, int numSamples) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSamples (short[] samples, int offset, int numSamples) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume (float volume) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMono () {
|
||||
return isMono;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLatency () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose () {
|
||||
}
|
||||
};
|
||||
return new OpenALAudioDevice(this, sampleRate, isMono, deviceBufferSize, deviceBufferCount);
|
||||
}
|
||||
|
||||
public AudioRecorder newAudioRecorder (int samplingRate, boolean isMono) {
|
||||
if (noDevice) return new AudioRecorder() {
|
||||
@Override
|
||||
public void read (short[] samples, int offset, int numSamples) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose () {
|
||||
}
|
||||
};
|
||||
return new JavaSoundAudioRecorder(samplingRate, isMono);
|
||||
}
|
||||
|
||||
/** Retains a list of the most recently played sounds and stops the sound played least recently if necessary for a new sound to
|
||||
* play */
|
||||
protected void retain (OpenALSound sound, boolean stop) {
|
||||
// Move the pointer ahead and wrap
|
||||
mostRecetSound++;
|
||||
mostRecetSound %= recentSounds.length;
|
||||
|
||||
if (stop) {
|
||||
// Stop the least recent sound (the one we are about to bump off the buffer)
|
||||
if (recentSounds[mostRecetSound] != null) recentSounds[mostRecetSound].stop();
|
||||
}
|
||||
|
||||
recentSounds[mostRecetSound] = sound;
|
||||
}
|
||||
|
||||
/** Removes the disposed sound from the least recently played list */
|
||||
public void forget (OpenALSound sound) {
|
||||
for (int i = 0; i < recentSounds.length; i++) {
|
||||
if (recentSounds[i] == sound) recentSounds[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,561 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 See AUTHORS file.
|
||||
*
|
||||
* 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 com.badlogic.gdx.graphics;
|
||||
|
||||
import com.badlogic.gdx.utils.NumberUtils;
|
||||
|
||||
/** A color class, holding the r, g, b and alpha component as floats in the range [0,1]. All methods perform clamping on the
|
||||
* internal values after execution.
|
||||
*
|
||||
* @author mzechner */
|
||||
public class Color {
|
||||
public static final Color WHITE = new Color(1, 1, 1,1);
|
||||
public static final Color LIGHT_GRAY = new Color(0xbfbfbfff);
|
||||
public static final Color GRAY = new Color(0x7f7f7fff);
|
||||
public static final Color DARK_GRAY = new Color(0x3f3f3fff);
|
||||
public static final Color BLACK = new Color(0, 0, 0, 1);
|
||||
|
||||
/** Convenience for frequently used <code>WHITE.toFloatBits()</code> */
|
||||
public static final float WHITE_FLOAT_BITS = WHITE.toFloatBits();
|
||||
|
||||
public static final Color CLEAR = new Color(0, 0, 0, 0);
|
||||
|
||||
public static final Color BLUE = new Color(0, 0, 1, 1);
|
||||
public static final Color NAVY = new Color(0, 0, 0.5f, 1);
|
||||
public static final Color ROYAL = new Color(0x4169e1ff);
|
||||
public static final Color SLATE = new Color(0x708090ff);
|
||||
public static final Color SKY = new Color(0x87ceebff);
|
||||
public static final Color CYAN = new Color(0, 1, 1, 1);
|
||||
public static final Color TEAL = new Color(0, 0.5f, 0.5f, 1);
|
||||
|
||||
public static final Color GREEN = new Color(0x00ff00ff);
|
||||
public static final Color CHARTREUSE = new Color(0x7fff00ff);
|
||||
public static final Color LIME = new Color(0x32cd32ff);
|
||||
public static final Color FOREST = new Color(0x228b22ff);
|
||||
public static final Color OLIVE = new Color(0x6b8e23ff);
|
||||
|
||||
public static final Color YELLOW = new Color(0xffff00ff);
|
||||
public static final Color GOLD = new Color(0xffd700ff);
|
||||
public static final Color GOLDENROD = new Color(0xdaa520ff);
|
||||
public static final Color ORANGE = new Color(0xffa500ff);
|
||||
|
||||
public static final Color BROWN = new Color(0x8b4513ff);
|
||||
public static final Color TAN = new Color(0xd2b48cff);
|
||||
public static final Color FIREBRICK = new Color(0xb22222ff);
|
||||
|
||||
public static final Color RED = new Color(0xff0000ff);
|
||||
public static final Color SCARLET = new Color(0xff341cff);
|
||||
public static final Color CORAL = new Color(0xff7f50ff);
|
||||
public static final Color SALMON = new Color(0xfa8072ff);
|
||||
public static final Color PINK = new Color(0xff69b4ff);
|
||||
public static final Color MAGENTA = new Color(1, 0, 1, 1);
|
||||
|
||||
public static final Color PURPLE = new Color(0xa020f0ff);
|
||||
public static final Color VIOLET = new Color(0xee82eeff);
|
||||
public static final Color MAROON = new Color(0xb03060ff);
|
||||
|
||||
/** the red, green, blue and alpha components **/
|
||||
public float r, g, b, a;
|
||||
|
||||
/** Constructs a new Color with all components set to 0. */
|
||||
public Color () {
|
||||
}
|
||||
|
||||
/** @see #rgba8888ToColor(Color, int) */
|
||||
public Color (int rgba8888) {
|
||||
rgba8888ToColor(this, rgba8888);
|
||||
}
|
||||
|
||||
/** Constructor, sets the components of the color
|
||||
*
|
||||
* @param r the red component
|
||||
* @param g the green component
|
||||
* @param b the blue component
|
||||
* @param a the alpha component */
|
||||
public Color (float r, float g, float b, float a) {
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
/** Constructs a new color using the given color
|
||||
*
|
||||
* @param color the color */
|
||||
public Color (Color color) {
|
||||
set(color);
|
||||
}
|
||||
|
||||
/** Sets this color to the given color.
|
||||
*
|
||||
* @param color the Color */
|
||||
public Color set (Color color) {
|
||||
this.r = color.r;
|
||||
this.g = color.g;
|
||||
this.b = color.b;
|
||||
this.a = color.a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Multiplies the this color and the given color
|
||||
*
|
||||
* @param color the color
|
||||
* @return this color. */
|
||||
public Color mul (Color color) {
|
||||
this.r *= color.r;
|
||||
this.g *= color.g;
|
||||
this.b *= color.b;
|
||||
this.a *= color.a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Multiplies all components of this Color with the given value.
|
||||
*
|
||||
* @param value the value
|
||||
* @return this color */
|
||||
public Color mul (float value) {
|
||||
this.r *= value;
|
||||
this.g *= value;
|
||||
this.b *= value;
|
||||
this.a *= value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds the given color to this color.
|
||||
*
|
||||
* @param color the color
|
||||
* @return this color */
|
||||
public Color add (Color color) {
|
||||
this.r += color.r;
|
||||
this.g += color.g;
|
||||
this.b += color.b;
|
||||
this.a += color.a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Subtracts the given color from this color
|
||||
*
|
||||
* @param color the color
|
||||
* @return this color */
|
||||
public Color sub (Color color) {
|
||||
this.r -= color.r;
|
||||
this.g -= color.g;
|
||||
this.b -= color.b;
|
||||
this.a -= color.a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets this Color's component values.
|
||||
*
|
||||
* @param r Red component
|
||||
* @param g Green component
|
||||
* @param b Blue component
|
||||
* @param a Alpha component
|
||||
*
|
||||
* @return this Color for chaining */
|
||||
public Color set (float r, float g, float b, float a) {
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets this color's component values through an integer representation.
|
||||
*
|
||||
* @return this Color for chaining
|
||||
* @see #rgba8888ToColor(Color, int) */
|
||||
public Color set (int rgba) {
|
||||
rgba8888ToColor(this, rgba);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds the given color component values to this Color's values.
|
||||
*
|
||||
* @param r Red component
|
||||
* @param g Green component
|
||||
* @param b Blue component
|
||||
* @param a Alpha component
|
||||
*
|
||||
* @return this Color for chaining */
|
||||
public Color add (float r, float g, float b, float a) {
|
||||
this.r += r;
|
||||
this.g += g;
|
||||
this.b += b;
|
||||
this.a += a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Subtracts the given values from this Color's component values.
|
||||
*
|
||||
* @param r Red component
|
||||
* @param g Green component
|
||||
* @param b Blue component
|
||||
* @param a Alpha component
|
||||
*
|
||||
* @return this Color for chaining */
|
||||
public Color sub (float r, float g, float b, float a) {
|
||||
this.r -= r;
|
||||
this.g -= g;
|
||||
this.b -= b;
|
||||
this.a -= a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Multiplies this Color's color components by the given ones.
|
||||
*
|
||||
* @param r Red component
|
||||
* @param g Green component
|
||||
* @param b Blue component
|
||||
* @param a Alpha component
|
||||
*
|
||||
* @return this Color for chaining */
|
||||
public Color mul (float r, float g, float b, float a) {
|
||||
this.r *= r;
|
||||
this.g *= g;
|
||||
this.b *= b;
|
||||
this.a *= a;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Linearly interpolates between this color and the target color by t which is in the range [0,1]. The result is stored in
|
||||
* this color.
|
||||
* @param target The target color
|
||||
* @param t The interpolation coefficient
|
||||
* @return This color for chaining. */
|
||||
public Color lerp (final Color target, final float t) {
|
||||
this.r += t * (target.r - this.r);
|
||||
this.g += t * (target.g - this.g);
|
||||
this.b += t * (target.b - this.b);
|
||||
this.a += t * (target.a - this.a);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Linearly interpolates between this color and the target color by t which is in the range [0,1]. The result is stored in
|
||||
* this color.
|
||||
* @param r The red component of the target color
|
||||
* @param g The green component of the target color
|
||||
* @param b The blue component of the target color
|
||||
* @param a The alpha component of the target color
|
||||
* @param t The interpolation coefficient
|
||||
* @return This color for chaining. */
|
||||
public Color lerp (final float r, final float g, final float b, final float a, final float t) {
|
||||
this.r += t * (r - this.r);
|
||||
this.g += t * (g - this.g);
|
||||
this.b += t * (b - this.b);
|
||||
this.a += t * (a - this.a);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Multiplies the RGB values by the alpha. */
|
||||
public Color premultiplyAlpha () {
|
||||
r *= a;
|
||||
g *= a;
|
||||
b *= a;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Color color = (Color)o;
|
||||
return toIntBits() == color.toIntBits();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int result = (r != +0.0f ? NumberUtils.floatToIntBits(r) : 0);
|
||||
result = 31 * result + (g != +0.0f ? NumberUtils.floatToIntBits(g) : 0);
|
||||
result = 31 * result + (b != +0.0f ? NumberUtils.floatToIntBits(b) : 0);
|
||||
result = 31 * result + (a != +0.0f ? NumberUtils.floatToIntBits(a) : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Packs the color components into a 32-bit integer with the format ABGR and then converts it to a float. Alpha is compressed
|
||||
* from 0-255 to 0-254 to avoid using float bits in the NaN range (see {@link NumberUtils#intToFloatColor(int)}).
|
||||
* @return the packed color as a 32-bit float */
|
||||
public float toFloatBits () {
|
||||
int color = ((int)(255 * a) << 24) | ((int)(255 * b) << 16) | ((int)(255 * g) << 8) | ((int)(255 * r));
|
||||
return NumberUtils.intToFloatColor(color);
|
||||
}
|
||||
|
||||
/** Packs the color components into a 32-bit integer with the format ABGR.
|
||||
* @return the packed color as a 32-bit int. */
|
||||
public int toIntBits () {
|
||||
return ((int)(255 * a) << 24) | ((int)(255 * b) << 16) | ((int)(255 * g) << 8) | ((int)(255 * r));
|
||||
}
|
||||
|
||||
/** Returns the color encoded as hex string with the format RRGGBBAA. */
|
||||
public String toString () {
|
||||
String value = Integer
|
||||
.toHexString(((int)(255 * r) << 24) | ((int)(255 * g) << 16) | ((int)(255 * b) << 8) | ((int)(255 * a)));
|
||||
while (value.length() < 8)
|
||||
value = "0" + value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new color from a hex string with the format RRGGBBAA.
|
||||
* @see #toString() */
|
||||
public static Color valueOf (String hex) {
|
||||
hex = hex.charAt(0) == '#' ? hex.substring(1) : hex;
|
||||
int r = Integer.valueOf(hex.substring(0, 2), 16);
|
||||
int g = Integer.valueOf(hex.substring(2, 4), 16);
|
||||
int b = Integer.valueOf(hex.substring(4, 6), 16);
|
||||
int a = hex.length() != 8 ? 255 : Integer.valueOf(hex.substring(6, 8), 16);
|
||||
return new Color(r / 255f, g / 255f, b / 255f, a / 255f);
|
||||
}
|
||||
|
||||
/** Packs the color components into a 32-bit integer with the format ABGR and then converts it to a float. Note that no range
|
||||
* checking is performed for higher performance.
|
||||
* @param r the red component, 0 - 255
|
||||
* @param g the green component, 0 - 255
|
||||
* @param b the blue component, 0 - 255
|
||||
* @param a the alpha component, 0 - 255
|
||||
* @return the packed color as a float
|
||||
* @see NumberUtils#intToFloatColor(int) */
|
||||
public static float toFloatBits (int r, int g, int b, int a) {
|
||||
int color = (a << 24) | (b << 16) | (g << 8) | r;
|
||||
float floatColor = NumberUtils.intToFloatColor(color);
|
||||
return floatColor;
|
||||
}
|
||||
|
||||
/** Packs the color components into a 32-bit integer with the format ABGR and then converts it to a float.
|
||||
* @return the packed color as a 32-bit float
|
||||
* @see NumberUtils#intToFloatColor(int) */
|
||||
public static float toFloatBits (float r, float g, float b, float a) {
|
||||
int color = ((int)(255 * a) << 24) | ((int)(255 * b) << 16) | ((int)(255 * g) << 8) | ((int)(255 * r));
|
||||
return NumberUtils.intToFloatColor(color);
|
||||
}
|
||||
|
||||
/** Packs the color components into a 32-bit integer with the format ABGR. Note that no range checking is performed for higher
|
||||
* performance.
|
||||
* @param r the red component, 0 - 255
|
||||
* @param g the green component, 0 - 255
|
||||
* @param b the blue component, 0 - 255
|
||||
* @param a the alpha component, 0 - 255
|
||||
* @return the packed color as a 32-bit int */
|
||||
public static int toIntBits (int r, int g, int b, int a) {
|
||||
return (a << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
|
||||
public static int alpha (float alpha) {
|
||||
return (int)(alpha * 255.0f);
|
||||
}
|
||||
|
||||
public static int luminanceAlpha (float luminance, float alpha) {
|
||||
return ((int)(luminance * 255.0f) << 8) | (int)(alpha * 255);
|
||||
}
|
||||
|
||||
public static int rgb565 (float r, float g, float b) {
|
||||
return ((int)(r * 31) << 11) | ((int)(g * 63) << 5) | (int)(b * 31);
|
||||
}
|
||||
|
||||
public static int rgba4444 (float r, float g, float b, float a) {
|
||||
return ((int)(r * 15) << 12) | ((int)(g * 15) << 8) | ((int)(b * 15) << 4) | (int)(a * 15);
|
||||
}
|
||||
|
||||
public static int rgb888 (float r, float g, float b) {
|
||||
return ((int)(r * 255) << 16) | ((int)(g * 255) << 8) | (int)(b * 255);
|
||||
}
|
||||
|
||||
public static int rgba8888 (float r, float g, float b, float a) {
|
||||
return ((int)(r * 255) << 24) | ((int)(g * 255) << 16) | ((int)(b * 255) << 8) | (int)(a * 255);
|
||||
}
|
||||
|
||||
public static int argb8888 (float a, float r, float g, float b) {
|
||||
return ((int)(a * 255) << 24) | ((int)(r * 255) << 16) | ((int)(g * 255) << 8) | (int)(b * 255);
|
||||
}
|
||||
|
||||
public static int rgb565 (Color color) {
|
||||
return ((int)(color.r * 31) << 11) | ((int)(color.g * 63) << 5) | (int)(color.b * 31);
|
||||
}
|
||||
|
||||
public static int rgba4444 (Color color) {
|
||||
return ((int)(color.r * 15) << 12) | ((int)(color.g * 15) << 8) | ((int)(color.b * 15) << 4) | (int)(color.a * 15);
|
||||
}
|
||||
|
||||
public static int rgb888 (Color color) {
|
||||
return ((int)(color.r * 255) << 16) | ((int)(color.g * 255) << 8) | (int)(color.b * 255);
|
||||
}
|
||||
|
||||
public static int rgba8888 (Color color) {
|
||||
return ((int)(color.r * 255) << 24) | ((int)(color.g * 255) << 16) | ((int)(color.b * 255) << 8) | (int)(color.a * 255);
|
||||
}
|
||||
|
||||
public static int argb8888 (Color color) {
|
||||
return ((int)(color.a * 255) << 24) | ((int)(color.r * 255) << 16) | ((int)(color.g * 255) << 8) | (int)(color.b * 255);
|
||||
}
|
||||
|
||||
/** Sets the Color components using the specified integer value in the format RGB565. This is inverse to the rgb565(r, g, b)
|
||||
* method.
|
||||
*
|
||||
* @param color The Color to be modified.
|
||||
* @param value An integer color value in RGB565 format. */
|
||||
public static void rgb565ToColor (Color color, int value) {
|
||||
color.r = ((value & 0x0000F800) >>> 11) / 31f;
|
||||
color.g = ((value & 0x000007E0) >>> 5) / 63f;
|
||||
color.b = ((value & 0x0000001F) >>> 0) / 31f;
|
||||
}
|
||||
|
||||
/** Sets the Color components using the specified integer value in the format RGBA4444. This is inverse to the rgba4444(r, g,
|
||||
* b, a) method.
|
||||
*
|
||||
* @param color The Color to be modified.
|
||||
* @param value An integer color value in RGBA4444 format. */
|
||||
public static void rgba4444ToColor (Color color, int value) {
|
||||
color.r = ((value & 0x0000f000) >>> 12) / 15f;
|
||||
color.g = ((value & 0x00000f00) >>> 8) / 15f;
|
||||
color.b = ((value & 0x000000f0) >>> 4) / 15f;
|
||||
color.a = ((value & 0x0000000f)) / 15f;
|
||||
}
|
||||
|
||||
/** Sets the Color components using the specified integer value in the format RGB888. This is inverse to the rgb888(r, g, b)
|
||||
* method.
|
||||
*
|
||||
* @param color The Color to be modified.
|
||||
* @param value An integer color value in RGB888 format. */
|
||||
public static void rgb888ToColor (Color color, int value) {
|
||||
color.r = ((value & 0x00ff0000) >>> 16) / 255f;
|
||||
color.g = ((value & 0x0000ff00) >>> 8) / 255f;
|
||||
color.b = ((value & 0x000000ff)) / 255f;
|
||||
}
|
||||
|
||||
/** Sets the Color components using the specified integer value in the format RGBA8888. This is inverse to the rgba8888(r, g,
|
||||
* b, a) method.
|
||||
*
|
||||
* @param color The Color to be modified.
|
||||
* @param value An integer color value in RGBA8888 format. */
|
||||
public static void rgba8888ToColor (Color color, int value) {
|
||||
color.r = ((value & 0xff000000) >>> 24) / 255f;
|
||||
color.g = ((value & 0x00ff0000) >>> 16) / 255f;
|
||||
color.b = ((value & 0x0000ff00) >>> 8) / 255f;
|
||||
color.a = ((value & 0x000000ff)) / 255f;
|
||||
}
|
||||
|
||||
/** Sets the Color components using the specified integer value in the format ARGB8888. This is the inverse to the argb8888(a,
|
||||
* r, g, b) method
|
||||
*
|
||||
* @param color The Color to be modified.
|
||||
* @param value An integer color value in ARGB8888 format. */
|
||||
public static void argb8888ToColor (Color color, int value) {
|
||||
color.a = ((value & 0xff000000) >>> 24) / 255f;
|
||||
color.r = ((value & 0x00ff0000) >>> 16) / 255f;
|
||||
color.g = ((value & 0x0000ff00) >>> 8) / 255f;
|
||||
color.b = ((value & 0x000000ff)) / 255f;
|
||||
}
|
||||
|
||||
/** Sets the Color components using the specified float value in the format ABGB8888.
|
||||
* @param color The Color to be modified. */
|
||||
public static void abgr8888ToColor (Color color, float value) {
|
||||
int c = NumberUtils.floatToIntColor(value);
|
||||
color.a = ((c & 0xff000000) >>> 24) / 255f;
|
||||
color.b = ((c & 0x00ff0000) >>> 16) / 255f;
|
||||
color.g = ((c & 0x0000ff00) >>> 8) / 255f;
|
||||
color.r = ((c & 0x000000ff)) / 255f;
|
||||
}
|
||||
|
||||
/** Sets the RGB Color components using the specified Hue-Saturation-Value. Note that HSV components are voluntary not clamped
|
||||
* to preserve high range color and can range beyond typical values.
|
||||
* @param h The Hue in degree from 0 to 360
|
||||
* @param s The Saturation from 0 to 1
|
||||
* @param v The Value (brightness) from 0 to 1
|
||||
* @return The modified Color for chaining. */
|
||||
public Color fromHsv (float h, float s, float v) {
|
||||
float x = (h / 60f + 6) % 6;
|
||||
int i = (int)x;
|
||||
float f = x - i;
|
||||
float p = v * (1 - s);
|
||||
float q = v * (1 - s * f);
|
||||
float t = v * (1 - s * (1 - f));
|
||||
switch (i) {
|
||||
case 0:
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
default:
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
}
|
||||
|
||||
//return clamp();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets RGB components using the specified Hue-Saturation-Value. This is a convenient method for
|
||||
* {@link #fromHsv(float, float, float)}. This is the inverse of {@link #toHsv(float[])}.
|
||||
* @param hsv The Hue, Saturation and Value components in that order.
|
||||
* @return The modified Color for chaining. */
|
||||
public Color fromHsv (float[] hsv) {
|
||||
return fromHsv(hsv[0], hsv[1], hsv[2]);
|
||||
}
|
||||
|
||||
/** Extract Hue-Saturation-Value. This is the inverse of {@link #fromHsv(float[])}.
|
||||
* @param hsv The HSV array to be modified.
|
||||
* @return HSV components for chaining. */
|
||||
public float[] toHsv (float[] hsv) {
|
||||
float max = Math.max(Math.max(r, g), b);
|
||||
float min = Math.min(Math.min(r, g), b);
|
||||
float range = max - min;
|
||||
if (range == 0) {
|
||||
hsv[0] = 0;
|
||||
} else if (max == r) {
|
||||
hsv[0] = (60 * (g - b) / range + 360) % 360;
|
||||
} else if (max == g) {
|
||||
hsv[0] = 60 * (b - r) / range + 120;
|
||||
} else {
|
||||
hsv[0] = 60 * (r - g) / range + 240;
|
||||
}
|
||||
|
||||
if (max > 0) {
|
||||
hsv[1] = 1 - min / max;
|
||||
} else {
|
||||
hsv[1] = 0;
|
||||
}
|
||||
|
||||
hsv[2] = max;
|
||||
|
||||
return hsv;
|
||||
}
|
||||
|
||||
/** @return a copy of this color */
|
||||
public Color cpy () {
|
||||
return new Color(this);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.badlogic.gdx.graphics;
|
||||
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.utils.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2019-01-07.
|
||||
*/
|
||||
public class PixmapIO2 {
|
||||
|
||||
// REMEMBER: to the GL's perspective, this game's FBOs are always Y-flipped. //
|
||||
|
||||
public static int HEADER_FOOTER_SIZE = 18 + 26;
|
||||
|
||||
public static void writeTGAHappy(FileHandle file, Pixmap pixmap, boolean flipY) throws IOException {
|
||||
OutputStream output = file.write(false);
|
||||
|
||||
try {
|
||||
_writeTGA(output, pixmap, false, flipY);
|
||||
} finally {
|
||||
StreamUtils.closeQuietly(output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeTGA(FileHandle file, Pixmap pixmap, boolean flipY) throws IOException {
|
||||
OutputStream output = file.write(false);
|
||||
|
||||
try {
|
||||
_writeTGA(output, pixmap, true, flipY);
|
||||
} finally {
|
||||
StreamUtils.closeQuietly(output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void _writeTGA(OutputStream out, Pixmap pixmap, boolean verbatim, boolean flipY) throws IOException {
|
||||
byte[] width = toShortLittle(pixmap.getWidth());
|
||||
byte[] height = toShortLittle(pixmap.getHeight());
|
||||
byte[] zero = toShortLittle(0);
|
||||
|
||||
out.write(0); // ID field: empty
|
||||
out.write(0); // no colour map, but should be ignored anyway as it being unmapped RGB
|
||||
out.write(2); // 2 means unmapped RGB
|
||||
out.write(new byte[]{0,0,0,0,0}); // color map spec: empty
|
||||
out.write(zero); // x origin: 0
|
||||
out.write(zero); // y origin: 0
|
||||
out.write(width); // width
|
||||
out.write(height); // height
|
||||
out.write(32); // image pixel size: we're writing 32-bit image (8bpp BGRA)
|
||||
out.write(8); // image descriptor: dunno, Photoshop writes 8 in there
|
||||
|
||||
// write actual image data
|
||||
// since we're following Photoshop's conventional header, we also follows Photoshop's
|
||||
// TGA saving scheme, that is:
|
||||
// 1. BGRA order
|
||||
// 2. Y-Flipped but not X-Flipped
|
||||
|
||||
if (!flipY) {
|
||||
for (int y = pixmap.getHeight() - 1; y >= 0; y--) {
|
||||
for (int x = 0; x < pixmap.getWidth(); x++) {
|
||||
writeTga(x, y, verbatim, pixmap, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int y = 0; y < pixmap.getHeight(); y++) {
|
||||
for (int x = 0; x < pixmap.getWidth(); x++) {
|
||||
writeTga(x, y, verbatim, pixmap, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// write footer
|
||||
// 00 00 00 00 00 00 00 00 TRUEVISION-XFILE 2E 00
|
||||
out.write(new byte[]{0,0,0,0,0,0,0,0});
|
||||
if (verbatim)
|
||||
out.write("TRUEVISION-XFILE".getBytes());
|
||||
else
|
||||
out.write("TerrarumHappyTGA".getBytes());
|
||||
out.write(new byte[]{0x2E,0});
|
||||
|
||||
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
private static byte[] zeroalpha = new byte[]{0,0,0,0};
|
||||
private static void writeTga(int x, int y, boolean verbatim, Pixmap pixmap, OutputStream out) throws IOException {
|
||||
int color = pixmap.getPixel(x, y);
|
||||
|
||||
// if alpha == 0, write special value instead
|
||||
if (verbatim && (color & 0xFF) == 0) {
|
||||
out.write(zeroalpha);
|
||||
}
|
||||
else {
|
||||
out.write(RGBAtoBGRA(color));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] toShortLittle(int i) {
|
||||
return new byte[]{
|
||||
(byte) (i & 0xFF),
|
||||
(byte) ((i >>> 8) & 0xFF)
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] RGBAtoBGRA(int rgba) {
|
||||
return new byte[]{
|
||||
(byte) ((rgba >>> 8) & 0xFF),
|
||||
(byte) ((rgba >>> 16) & 0xFF),
|
||||
(byte) ((rgba >>> 24) & 0xFF),
|
||||
(byte) (rgba & 0xFF)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user