Merge pull request #64 from Bigpet/sliderrender
Slider rendering to offscreen buffer
This commit is contained in:
commit
9c8a8f24c6
BIN
res/slidergradient.png
Normal file
BIN
res/slidergradient.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
|
@ -374,6 +374,18 @@ public class GameData {
|
|||
health = 100f;
|
||||
healthDisplay = 100f;
|
||||
hitResultCount = new int[HIT_MAX];
|
||||
if(hitResultList != null)
|
||||
{
|
||||
Iterator<OsuHitObjectResult> iter =hitResultList.iterator();
|
||||
while(iter.hasNext())
|
||||
{
|
||||
OsuHitObjectResult hitObj = iter.next();
|
||||
if(hitObj != null && hitObj.curve != null)
|
||||
{
|
||||
hitObj.curve.discardCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
hitResultList = new LinkedBlockingDeque<OsuHitObjectResult>();
|
||||
hitErrorList = new LinkedBlockingDeque<HitErrorInfo>();
|
||||
fullObjectCount = 0;
|
||||
|
@ -939,8 +951,13 @@ public class GameData {
|
|||
}
|
||||
|
||||
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME);
|
||||
} else
|
||||
} else{
|
||||
if (hitResult.curve !=null)
|
||||
{
|
||||
hitResult.curve.discardCache();
|
||||
}
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ public enum GameImage {
|
|||
APPROACHCIRCLE ("approachcircle", "png"),
|
||||
|
||||
// Slider
|
||||
SLIDER_GRADIENT ("slidergradient", "png"),
|
||||
SLIDER_BALL ("sliderb", "sliderb%d", "png"),
|
||||
SLIDER_FOLLOWCIRCLE ("sliderfollowcircle", "png"),
|
||||
REVERSEARROW ("reversearrow", "png"),
|
||||
|
|
|
@ -228,6 +228,7 @@ public class Options {
|
|||
UI.getCursor().reset();
|
||||
}
|
||||
},
|
||||
NEW_SLIDER("Enable New Slider", "Use the new Slider style (requires OpenGL 3.0).",false),
|
||||
DYNAMIC_BACKGROUND ("Enable Dynamic Backgrounds", "The song background will be used as the main menu background.", true),
|
||||
BACKGROUND_DIM ("Background Dim", "Percentage to dim the background image during gameplay.", 50, 0, 100),
|
||||
FORCE_DEFAULT_PLAYFIELD ("Force Default Playfield", "Override the song background with the default playfield background.", false),
|
||||
|
@ -1071,6 +1072,9 @@ public class Options {
|
|||
case "NewCursor":
|
||||
GameOption.NEW_CURSOR.setValue(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "NewSlider":
|
||||
GameOption.NEW_SLIDER.setValue(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "DynamicBackground":
|
||||
GameOption.DYNAMIC_BACKGROUND.setValue(Boolean.parseBoolean(value));
|
||||
break;
|
||||
|
@ -1222,6 +1226,8 @@ public class Options {
|
|||
writer.newLine();
|
||||
writer.write(String.format("NewCursor = %b", isNewCursorEnabled()));
|
||||
writer.newLine();
|
||||
writer.write(String.format("NewSlider = %b", GameOption.NEW_SLIDER.getBooleanValue()));
|
||||
writer.newLine();
|
||||
writer.write(String.format("DynamicBackground = %b", isDynamicBackgroundEnabled()));
|
||||
writer.newLine();
|
||||
writer.write(String.format("LoadVerbose = %b", isLoadVerbose()));
|
||||
|
|
|
@ -170,12 +170,26 @@ public class Slider implements GameObject {
|
|||
float fadeinScale = (timeDiff - game.getApproachTime() + FADE_IN_TIME) / (float) FADE_IN_TIME;
|
||||
float approachScale = 1 + scale * 3;
|
||||
float alpha = Utils.clamp(1 - fadeinScale, 0, 1);
|
||||
boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber();
|
||||
|
||||
float oldAlpha = Utils.COLOR_WHITE_FADE.a;
|
||||
Utils.COLOR_WHITE_FADE.a = color.a = alpha;
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
float[] endPos = curve.pointAt(1);
|
||||
|
||||
// curve
|
||||
curve.draw(color);
|
||||
color.a = alpha;
|
||||
|
||||
// end circle
|
||||
hitCircle.drawCentered(endPos[0], endPos[1], color);
|
||||
hitCircleOverlay.drawCentered(endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
|
||||
|
||||
// start circle
|
||||
hitCircle.drawCentered(x, y, color);
|
||||
if (!overlayAboveNumber) {
|
||||
hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
}
|
||||
|
||||
// ticks
|
||||
if (ticksT != null) {
|
||||
|
@ -185,25 +199,11 @@ public class Slider implements GameObject {
|
|||
tick.drawCentered(c[0], c[1], Utils.COLOR_WHITE_FADE);
|
||||
}
|
||||
}
|
||||
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
|
||||
// end circle
|
||||
float[] endPos = curve.pointAt(1);
|
||||
hitCircle.drawCentered(endPos[0], endPos[1], color);
|
||||
hitCircleOverlay.drawCentered(endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
|
||||
|
||||
// start circle
|
||||
hitCircle.drawCentered(x, y, color);
|
||||
boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber();
|
||||
if (!overlayAboveNumber)
|
||||
hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
if (sliderClickedInitial)
|
||||
; // don't draw current combo number if already clicked
|
||||
else
|
||||
data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
|
||||
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
||||
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
||||
if (overlayAboveNumber)
|
||||
hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
|
||||
|
|
|
@ -18,12 +18,15 @@
|
|||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import itdelatrisu.opsu.render.CurveRenderState;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.Options;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* Representation of a curve.
|
||||
|
@ -34,6 +37,9 @@ public abstract class Curve {
|
|||
/** Points generated along the curve should be spaced this far apart. */
|
||||
protected static float CURVE_POINTS_SEPERATION = 5;
|
||||
|
||||
/** The width and height of the display container this curve gets drawn into */
|
||||
protected static int containerWidth = 0, containerHeight = 0;
|
||||
|
||||
/** The associated HitObject. */
|
||||
protected HitObject hitObject;
|
||||
|
||||
|
@ -42,6 +48,9 @@ public abstract class Curve {
|
|||
|
||||
/** The scaled slider x, y coordinate lists. */
|
||||
protected float[] sliderX, sliderY;
|
||||
|
||||
/** Per-curve render-state used for the new style curve renders*/
|
||||
private CurveRenderState renderState;
|
||||
|
||||
/** Points along the curve (set by inherited classes). */
|
||||
protected Vec2f[] curve;
|
||||
|
@ -57,8 +66,22 @@ public abstract class Curve {
|
|||
this.y = hitObject.getScaledY();
|
||||
this.sliderX = hitObject.getScaledSliderX();
|
||||
this.sliderY = hitObject.getScaledSliderY();
|
||||
this.renderState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width and height of the container that Curves get drawn into
|
||||
* Should be called before any curves are drawn.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public static void init(int width, int height, float circleSize)
|
||||
{
|
||||
containerWidth = width;
|
||||
containerHeight = height;
|
||||
CurveRenderState.init(width, height, circleSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the point on the curve at a value t.
|
||||
* @param t the t value [0, 1]
|
||||
|
@ -71,15 +94,24 @@ public abstract class Curve {
|
|||
* @param color the color filter
|
||||
*/
|
||||
public void draw(Color color) {
|
||||
if (curve == null)
|
||||
if (curve == null) {
|
||||
return;
|
||||
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
hitCircleOverlay.drawCentered(curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
hitCircle.drawCentered(curve[i].x, curve[i].y, color);
|
||||
}
|
||||
if (Options.GameOption.NEW_SLIDER.getBooleanValue()) {
|
||||
if (renderState == null) {
|
||||
renderState = new CurveRenderState(hitObject);
|
||||
}
|
||||
renderState.draw(color, curve);
|
||||
} else {
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
for (int i = 0; i < curve.length; i++) {
|
||||
hitCircleOverlay.drawCentered(curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
||||
}
|
||||
for (int i = 0; i < curve.length; i++) {
|
||||
hitCircle.drawCentered(curve[i].x, curve[i].y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,4 +142,9 @@ public abstract class Curve {
|
|||
protected float lerp(float a, float b, float t) {
|
||||
return a * (1 - t) + b * t;
|
||||
}
|
||||
|
||||
public void discardCache() {
|
||||
if(renderState != null)
|
||||
renderState.discardCache();
|
||||
}
|
||||
}
|
||||
|
|
449
src/itdelatrisu/opsu/render/CurveRenderState.java
Normal file
449
src/itdelatrisu/opsu/render/CurveRenderState.java
Normal file
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package itdelatrisu.opsu.render;
|
||||
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.objects.curves.Vec2f;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* Hold the temporary render state that needs to be restored again after the new
|
||||
* style curves are drawn.
|
||||
*/
|
||||
public class CurveRenderState {
|
||||
|
||||
/** The width and height of the display container this curve gets drawn into */
|
||||
protected static int containerWidth, containerHeight;
|
||||
|
||||
/** cached drawn slider, only used if new style sliders are activated */
|
||||
public Rendertarget fbo;
|
||||
|
||||
/** thickness of the curve */
|
||||
protected static int scale;
|
||||
|
||||
/** The HitObject associated with the curve to be drawn */
|
||||
HitObject hitObject;
|
||||
|
||||
/** Static state that's needed to draw the new style curves */
|
||||
private static final NewCurveStyleState staticState = new NewCurveStyleState();
|
||||
|
||||
/**
|
||||
* Set the width and height of the container that Curves get drawn into
|
||||
* Should be called before any curves are drawn.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public static void init(int width, int height, float circleSize)
|
||||
{
|
||||
containerWidth = width;
|
||||
containerHeight = height;
|
||||
//equivalent to what happens in Slider.init
|
||||
scale = (int) (104 - (circleSize * 8));
|
||||
scale = (int) (scale * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
||||
//scale = scale * 118 / 128; //for curves exaclty as big as the sliderball
|
||||
FrameBufferCache.init(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object to hold the render state that's necessary to draw a curve.
|
||||
* @param hitObject the HitObject that represents this curve, just used as a
|
||||
* unique ID
|
||||
*/
|
||||
public CurveRenderState(HitObject hitObject) {
|
||||
fbo = null;
|
||||
this.hitObject = hitObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a curve to the screen that's tinted with `color`. The first time
|
||||
* this is called this caches the image result of the curve and on subsequent
|
||||
* runs it just draws the cached copy to the screen.
|
||||
* @param color tint of the curve
|
||||
* @param curve the points along the curve to be drawn
|
||||
*/
|
||||
public void draw(Color color, Vec2f[] curve) {
|
||||
float alpha = color.a;
|
||||
//if this curve hasn't been drawn, draw it and cache the result
|
||||
if (fbo == null) {
|
||||
FrameBufferCache cache = FrameBufferCache.getInstance();
|
||||
Rendertarget mapping = cache.get(hitObject);
|
||||
if (mapping == null) {
|
||||
mapping = cache.insert(hitObject);
|
||||
}
|
||||
fbo = mapping;
|
||||
|
||||
int old_fb = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
|
||||
int old_tex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
|
||||
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo.getID());
|
||||
GL11.glViewport(0, 0, fbo.width, fbo.height);
|
||||
GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
Utils.COLOR_WHITE_FADE.a = 1.0f;
|
||||
this.draw_curve(color, curve);
|
||||
color.a = 1f;
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, old_tex);
|
||||
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, old_fb);
|
||||
Utils.COLOR_WHITE_FADE.a = alpha;
|
||||
}
|
||||
|
||||
//draw a fullscreen quad with the texture that contains the curve
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_1D);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.getTextureID());
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glColor4f(1.0f, 1.0f, 1.0f, alpha);
|
||||
GL11.glTexCoord2f(1.0f, 1.0f);
|
||||
GL11.glVertex2i(fbo.width, 0);
|
||||
GL11.glTexCoord2f(0.0f, 1.0f);
|
||||
GL11.glVertex2i(0, 0);
|
||||
GL11.glTexCoord2f(0.0f, 0.0f);
|
||||
GL11.glVertex2i(0, fbo.height);
|
||||
GL11.glTexCoord2f(1.0f, 0.0f);
|
||||
GL11.glVertex2i(fbo.width, fbo.height);
|
||||
GL11.glEnd();
|
||||
}
|
||||
|
||||
public void discardCache() {
|
||||
fbo = null;
|
||||
FrameBufferCache.getInstance().freeMappingFor(hitObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just a structure to hold all the important OpenGL state that needs to be
|
||||
* changed to draw the curve. This is used to backup and restore the state
|
||||
* so that the code outside of this (mainly Slick2D) doesn't break
|
||||
*/
|
||||
private class RenderState {
|
||||
boolean smoothedPoly;
|
||||
boolean blendEnabled;
|
||||
boolean depthEnabled;
|
||||
boolean depthWriteEnabled;
|
||||
boolean texEnabled;
|
||||
int texUnit;
|
||||
int oldProgram;
|
||||
int oldArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup the current state of the relevant OpenGL state and change it to
|
||||
* what's needed to draw the curve
|
||||
* @return
|
||||
*/
|
||||
private RenderState startRender() {
|
||||
RenderState state = new RenderState();
|
||||
state.smoothedPoly = GL11.glGetBoolean(GL11.GL_POLYGON_SMOOTH);
|
||||
state.blendEnabled = GL11.glGetBoolean(GL11.GL_BLEND);
|
||||
state.depthEnabled = GL11.glGetBoolean(GL11.GL_DEPTH_TEST);
|
||||
state.depthWriteEnabled = GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK);
|
||||
state.texEnabled = GL11.glGetBoolean(GL11.GL_TEXTURE_2D);
|
||||
state.texUnit = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
|
||||
state.oldProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
state.oldArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
|
||||
GL11.glDisable(GL11.GL_POLYGON_SMOOTH);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL14.glBlendEquation(GL14.GL_FUNC_ADD);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GL11.glDepthMask(true);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_1D);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_1D, staticState.gradientTexture);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
|
||||
|
||||
GL20.glUseProgram(0);
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the old OpenGL state that's backed up in {@code state}
|
||||
* @param state the old state to restore
|
||||
*/
|
||||
private void endRender(RenderState state) {
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPopMatrix();
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GL11.glPopMatrix();
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL20.glUseProgram(state.oldProgram);
|
||||
GL13.glActiveTexture(state.texUnit);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, state.oldArrayBuffer);
|
||||
if (!state.depthWriteEnabled) {
|
||||
GL11.glDepthMask(false);
|
||||
}
|
||||
if (!state.depthEnabled) {
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
}
|
||||
if (state.texEnabled) {
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
if (state.smoothedPoly) {
|
||||
GL11.glEnable(GL11.GL_POLYGON_SMOOTH);
|
||||
}
|
||||
if (!state.blendEnabled) {
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the actual drawing of the curve into the currently bound framebuffer.
|
||||
* @param color the color of the curve
|
||||
* @param curve the points along the curve
|
||||
*/
|
||||
private void draw_curve(Color color, Vec2f[] curve) {
|
||||
staticState.initGradient();
|
||||
RenderState state = startRender();
|
||||
int vtx_buf;
|
||||
//The size is: floatsize * (position + texture coordinates) * (number of cones) * (vertices in a cone)
|
||||
FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * (2 * curve.length - 1) * (NewCurveStyleState.DIVIDES + 2)).asFloatBuffer();
|
||||
staticState.initShaderProgram();
|
||||
vtx_buf = GL15.glGenBuffers();
|
||||
for (int i = 0; i < curve.length; ++i) {
|
||||
float x = curve[i].x;
|
||||
float y = curve[i].y;
|
||||
//if(i == 0 || i==curve.length-1){
|
||||
fillCone(buff, x, y, NewCurveStyleState.DIVIDES);
|
||||
if (i != 0) {
|
||||
float last_x = curve[i - 1].x;
|
||||
float last_y = curve[i - 1].y;
|
||||
double diff_x = x - last_x;
|
||||
double diff_y = y - last_y;
|
||||
x = (float) (x - diff_x / 2);
|
||||
y = (float) (y - diff_y / 2);
|
||||
fillCone(buff, x, y, NewCurveStyleState.DIVIDES);
|
||||
}
|
||||
}
|
||||
buff.flip();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vtx_buf);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buff, GL15.GL_STATIC_DRAW);
|
||||
GL20.glUseProgram(staticState.program);
|
||||
GL20.glEnableVertexAttribArray(staticState.attribLoc);
|
||||
GL20.glEnableVertexAttribArray(staticState.texCoordLoc);
|
||||
GL20.glUniform1i(staticState.texLoc, 0);
|
||||
GL20.glUniform3f(staticState.colLoc, color.r, color.g, color.b);
|
||||
//stride is 6*4 for the floats (4 bytes) (u,v)(x,y,z,w)
|
||||
//2*4 is for skipping the first 2 floats (u,v)
|
||||
GL20.glVertexAttribPointer(staticState.attribLoc, 4, GL11.GL_FLOAT, false, 6 * 4, 2 * 4);
|
||||
GL20.glVertexAttribPointer(staticState.texCoordLoc, 2, GL11.GL_FLOAT, false, 6 * 4, 0);
|
||||
for (int i = 0; i < curve.length*2-1; ++i) {
|
||||
GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2);
|
||||
}
|
||||
GL20.glDisableVertexAttribArray(staticState.texCoordLoc);
|
||||
GL20.glDisableVertexAttribArray(staticState.attribLoc);
|
||||
GL15.glDeleteBuffers(vtx_buf);
|
||||
endRender(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill {@code buff} with the texture coordinates and positions for a cone
|
||||
* with {@code DIVIDES} ground corners that has its center at the coordinates
|
||||
* {@code (x1,y1)}
|
||||
* @param buff the buffer to be filled
|
||||
* @param x1 x-coordinate of the cone
|
||||
* @param y1 y-coordinate of the cone
|
||||
* @param DIVIDES the base of the cone is a regular polygon with this many sides
|
||||
*/
|
||||
protected void fillCone(FloatBuffer buff, float x1, float y1, final int DIVIDES) {
|
||||
float divx = containerWidth / 2.0f;
|
||||
float divy = containerHeight / 2.0f;
|
||||
float offx = -1.0f;
|
||||
float offy = 1.0f;
|
||||
float x, y;
|
||||
float radius = scale / 2;
|
||||
buff.put(1.0f);
|
||||
buff.put(0.5f);
|
||||
//GL11.glTexCoord2d(1.0, 0.5);
|
||||
x = offx + x1 / divx;
|
||||
y = offy - y1 / divy;
|
||||
buff.put(x);
|
||||
buff.put(y);
|
||||
buff.put(0f);
|
||||
buff.put(1f);
|
||||
//GL11.glVertex4f(x, y, 0.0f, 1.0f);
|
||||
for (int j = 0; j < DIVIDES; ++j) {
|
||||
double phase = j * (float) Math.PI * 2 / DIVIDES;
|
||||
buff.put(0.0f);
|
||||
buff.put(0.5f);
|
||||
//GL11.glTexCoord2d(0.0, 0.5);
|
||||
x = (x1 + radius * (float) Math.sin(phase)) / divx;
|
||||
y = (y1 + radius * (float) Math.cos(phase)) / divy;
|
||||
buff.put((offx + x));
|
||||
buff.put((offy - y));
|
||||
buff.put(1f);
|
||||
buff.put(1f);
|
||||
//GL11.glVertex4f(x + 90 * (float) Math.sin(phase), y + 90 * (float) Math.cos(phase), 1.0f, 1.0f);
|
||||
}
|
||||
buff.put(0.0f);
|
||||
buff.put(0.5f);
|
||||
//GL11.glTexCoord2d(0.0, 0.5);
|
||||
x = (x1 + radius * (float) Math.sin(0.0)) / divx;
|
||||
y = (y1 + radius * (float) Math.cos(0.0)) / divy;
|
||||
buff.put((offx + x));
|
||||
buff.put((offy - y));
|
||||
buff.put(1f);
|
||||
buff.put(1f);
|
||||
//GL11.glVertex4f(x + 90 * (float) Math.sin(0.0), y + 90 * (float) Math.cos(0.0), 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all the necessary state that needs to be tracked to draw curves
|
||||
* in the new style and not re-create the shader each time.
|
||||
*
|
||||
* @author Bigpet {@literal <dravorek (at) gmail.com>}
|
||||
*/
|
||||
private static class NewCurveStyleState {
|
||||
|
||||
/**
|
||||
* used for new style Slider rendering, defines how many vertices the
|
||||
* base of the cone has that is used to draw the curve
|
||||
*/
|
||||
protected static final int DIVIDES = 30;
|
||||
|
||||
/** OpenGL shader program ID used to draw and recolor the curve */
|
||||
protected int program = 0;
|
||||
|
||||
/** OpenGL shader attribute location of the vertex position attribute */
|
||||
protected int attribLoc = 0;
|
||||
|
||||
/** OpenGL shader attribute location of the texture coordinate attribute */
|
||||
protected int texCoordLoc = 0;
|
||||
|
||||
/** OpenGL shader uniform location of the color attribute */
|
||||
protected int colLoc = 0;
|
||||
|
||||
/** OpenGL shader uniform location of the texture sampler attribute */
|
||||
protected int texLoc = 0;
|
||||
|
||||
/** OpenGL texture id for the gradient texture for the curve */
|
||||
protected int gradientTexture = 0;
|
||||
|
||||
/**
|
||||
* Reads the first row of the slider gradient texture and upload it as
|
||||
* a 1D texture to OpenGL if it hasn't already been done
|
||||
*/
|
||||
public void initGradient()
|
||||
{
|
||||
if(gradientTexture == 0)
|
||||
{
|
||||
Image slider = GameImage.SLIDER_GRADIENT.getImage().getScaledCopy(1.0f/GameImage.getUIscale());
|
||||
staticState.gradientTexture = GL11.glGenTextures();
|
||||
ByteBuffer buff = BufferUtils.createByteBuffer(slider.getWidth()*4);
|
||||
for(int i=0 ; i< slider.getWidth(); ++i)
|
||||
{
|
||||
Color col = slider.getColor(i, 0);
|
||||
buff.put((byte)(255*col.r));
|
||||
buff.put((byte)(255*col.g));
|
||||
buff.put((byte)(255*col.b));
|
||||
buff.put((byte)(255*col.a));
|
||||
}
|
||||
buff.flip();
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_1D, gradientTexture);
|
||||
GL11.glTexImage1D(GL11.GL_TEXTURE_1D,0,GL11.GL_RGBA,slider.getWidth(),0,GL11.GL_RGBA,GL11.GL_UNSIGNED_BYTE,buff);
|
||||
GL30.glGenerateMipmap(GL11.GL_TEXTURE_1D);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles and links the shader program for the new style curve objects
|
||||
* if it hasn't already been compiled and linked.
|
||||
*/
|
||||
public void initShaderProgram() {
|
||||
if (program == 0) {
|
||||
program = GL20.glCreateProgram();
|
||||
int vtxShdr = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
|
||||
int frgShdr = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
|
||||
GL20.glShaderSource(vtxShdr, "#version 330\n"
|
||||
+ "\n"
|
||||
+ "layout(location = 0) in vec4 in_position;\n"
|
||||
+ "layout(location = 1) in vec2 in_tex_coord;\n"
|
||||
+ "\n"
|
||||
+ "out vec2 tex_coord;\n"
|
||||
+ "void main()\n"
|
||||
+ "{\n"
|
||||
+ " gl_Position = in_position;\n"
|
||||
+ " tex_coord = in_tex_coord;\n"
|
||||
+ "}");
|
||||
GL20.glCompileShader(vtxShdr);
|
||||
int res = GL20.glGetShaderi(vtxShdr, GL20.GL_COMPILE_STATUS);
|
||||
if (res != GL11.GL_TRUE) {
|
||||
String error = GL20.glGetShaderInfoLog(vtxShdr, 1024);
|
||||
Log.error("Vertex Shader compilation failed", new Exception(error));
|
||||
}
|
||||
GL20.glShaderSource(frgShdr, "#version 330\n"
|
||||
+ "\n"
|
||||
+ "uniform sampler1D tex;\n"
|
||||
+ "uniform vec2 tex_size;\n"
|
||||
+ "uniform vec3 col_tint;\n"
|
||||
+ "\n"
|
||||
+ "in vec2 tex_coord;\n"
|
||||
+ "layout(location = 0) out vec4 out_colour;\n"
|
||||
+ "\n"
|
||||
+ "void main()\n"
|
||||
+ "{\n"
|
||||
+ " vec4 in_color = texture(tex, tex_coord.x);\n"
|
||||
+ " float blend_factor = in_color.r-in_color.b;\n"
|
||||
+ " vec4 new_color = vec4(mix(in_color.xyz,col_tint,blend_factor),in_color.w);\n"
|
||||
+ " out_colour = new_color;\n"
|
||||
+ "}");
|
||||
GL20.glCompileShader(frgShdr);
|
||||
res = GL20.glGetShaderi(frgShdr, GL20.GL_COMPILE_STATUS);
|
||||
if (res != GL11.GL_TRUE) {
|
||||
String error = GL20.glGetShaderInfoLog(frgShdr, 1024);
|
||||
Log.error("Fragment Shader compilation failed", new Exception(error));
|
||||
}
|
||||
GL20.glAttachShader(program, vtxShdr);
|
||||
GL20.glAttachShader(program, frgShdr);
|
||||
GL20.glLinkProgram(program);
|
||||
res = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
|
||||
if (res != GL11.GL_TRUE) {
|
||||
String error = GL20.glGetProgramInfoLog(program, 1024);
|
||||
Log.error("Program linking failed", new Exception(error));
|
||||
}
|
||||
attribLoc = GL20.glGetAttribLocation(program, "in_position");
|
||||
texCoordLoc = GL20.glGetAttribLocation(program, "in_tex_coord");
|
||||
texLoc = GL20.glGetUniformLocation(program, "tex");
|
||||
colLoc = GL20.glGetUniformLocation(program, "col_tint");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
src/itdelatrisu/opsu/render/FrameBufferCache.java
Normal file
131
src/itdelatrisu/opsu/render/FrameBufferCache.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package itdelatrisu.opsu.render;
|
||||
|
||||
import itdelatrisu.opsu.Opsu;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* This is cache for OpenGL FrameBufferObjects. This is currently only used
|
||||
* to draw curve objects of the new slider style. Does currently not integrate
|
||||
* well and requires some manual OpenGL state manipulation to use it.
|
||||
* @author Bigpet {@literal <dravorek (at) gmail.com>}
|
||||
*/
|
||||
public class FrameBufferCache {
|
||||
private static final int INITIAL_CACHE_SIZE = 4;
|
||||
private static FrameBufferCache instance = null;
|
||||
private Map<HitObject, Rendertarget> cacheMap;
|
||||
private ArrayList<Rendertarget> cache;
|
||||
public static int width;
|
||||
public static int height;
|
||||
|
||||
/**
|
||||
* Set the width and height of the framebuffers in this cache.
|
||||
* Should be called before anything is inserted into the map.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public static void init(int width, int height)
|
||||
{
|
||||
FrameBufferCache.width = width;
|
||||
FrameBufferCache.height = height;
|
||||
}
|
||||
|
||||
private FrameBufferCache() {
|
||||
cache = new ArrayList<>();
|
||||
cacheMap = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is a framebuffer object mapped to the {@code HitObject obj}
|
||||
* @param obj
|
||||
* @return true if there is a framebuffer mapped for this HitObject, else false
|
||||
*/
|
||||
public boolean contains(HitObject obj) {
|
||||
return cacheMap.containsKey(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@code Rendertarget} mapped to {@code obj}
|
||||
* @param obj
|
||||
* @return the {@code Rendertarget} if there's one mapped to {@code obj}, otherwise null
|
||||
*/
|
||||
public Rendertarget get(HitObject obj) {
|
||||
return cacheMap.get(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the mapping for {@code obj} to free it up to get used by another {@code HitObject}
|
||||
* @param obj
|
||||
* @return true if there was a mapping for {@code obj} and false if there was no mapping for it.
|
||||
*/
|
||||
public boolean freeMappingFor(HitObject obj) {
|
||||
return cacheMap.remove(obj) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache of all the mappings. This does not actually delete the
|
||||
* cached framebuffers, it merely frees them all up to get mapped anew.
|
||||
*/
|
||||
public void freeMap() {
|
||||
cacheMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mapping from {@code obj} to a framebuffer. If there was already
|
||||
* a mapping from {@code obj} this will associate another framebuffer with it
|
||||
* (thereby freeing up the previously mapped framebuffer).
|
||||
* @param obj
|
||||
* @return the {@code Rendertarget} newly mapped to {@code obj}
|
||||
*/
|
||||
public Rendertarget insert(HitObject obj) {
|
||||
//find first RTTFramebuffer that's not mapped to anything and return it
|
||||
Rendertarget buffer;
|
||||
for (int i = 0; i < cache.size(); ++i) {
|
||||
buffer = cache.get(i);
|
||||
if (!cacheMap.containsValue(buffer)) {
|
||||
cacheMap.put(obj, buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
//no unmapped RTTFramebuffer found, create a new one
|
||||
buffer = Rendertarget.createRTTFramebuffer(width, height);
|
||||
cache.add(buffer);
|
||||
Log.warn("Framebuffer cache was not large enough, possible resource leak.");
|
||||
cacheMap.put(obj, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* There should only ever be one framebuffer cache, this function returns that one framebuffer cache instance.
|
||||
* If there was no instance created already then this function creates it.
|
||||
* @return the instance of FrameBufferCache
|
||||
*/
|
||||
public static FrameBufferCache getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new FrameBufferCache();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
110
src/itdelatrisu/opsu/render/Rendertarget.java
Normal file
110
src/itdelatrisu/opsu/render/Rendertarget.java
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package itdelatrisu.opsu.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
/**
|
||||
*
|
||||
* Represents a rendertarget. For now this maps to an OpenGL FBO via LWJGL
|
||||
*/
|
||||
public class Rendertarget {
|
||||
public final int width;
|
||||
public final int height;
|
||||
private final int fboID;
|
||||
private final int textureID;
|
||||
|
||||
/**
|
||||
* Create a new FBO
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
private Rendertarget(int width, int height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
fboID = GL30.glGenFramebuffers();
|
||||
textureID = GL11.glGenTextures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind this rendertarget as the primary framebuffer.
|
||||
*/
|
||||
public void bind()
|
||||
{
|
||||
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fboID);
|
||||
}
|
||||
|
||||
//use judiciously, try to avoid if possible and consider adding a method to
|
||||
//this class if you find yourself calling this repeatedly
|
||||
public int getID()
|
||||
{
|
||||
return fboID;
|
||||
}
|
||||
|
||||
//try not to use, could be moved into seperate class
|
||||
public int getTextureID()
|
||||
{
|
||||
return textureID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the default framebuffer
|
||||
*/
|
||||
public static void unbind()
|
||||
{
|
||||
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Rendertarget with a Texture that it renders the color buffer in
|
||||
* and a renderbuffer that it renders the depth to.
|
||||
*/
|
||||
public static Rendertarget createRTTFramebuffer(int width, int height)
|
||||
{
|
||||
int old_framebuffer = GL11.glGetInteger(GL30.GL_READ_FRAMEBUFFER_BINDING);
|
||||
int old_texture = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
Rendertarget buffer = new Rendertarget(width,height);
|
||||
buffer.bind();
|
||||
|
||||
int fboTexture = buffer.textureID;
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fboTexture);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, 4, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_INT, (ByteBuffer) null);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
|
||||
int fboDepth = GL30.glGenRenderbuffers();
|
||||
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, fboDepth);
|
||||
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_DEPTH_COMPONENT, width, height);
|
||||
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, fboDepth);
|
||||
|
||||
GL32.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, fboTexture, 0);
|
||||
GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0);
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, old_texture);
|
||||
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, old_framebuffer);
|
||||
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -41,6 +41,8 @@ import itdelatrisu.opsu.objects.DummyObject;
|
|||
import itdelatrisu.opsu.objects.GameObject;
|
||||
import itdelatrisu.opsu.objects.Slider;
|
||||
import itdelatrisu.opsu.objects.Spinner;
|
||||
import itdelatrisu.opsu.objects.curves.Curve;
|
||||
import itdelatrisu.opsu.render.FrameBufferCache;
|
||||
import itdelatrisu.opsu.replay.PlaybackSpeed;
|
||||
import itdelatrisu.opsu.replay.Replay;
|
||||
import itdelatrisu.opsu.replay.ReplayFrame;
|
||||
|
@ -1023,6 +1025,9 @@ public class Game extends BasicGameState {
|
|||
if (beatmap == null || beatmap.objects == null)
|
||||
throw new RuntimeException("Running game with no beatmap loaded.");
|
||||
|
||||
//free all previously cached hitobject to framebuffer mappings if some still exist
|
||||
FrameBufferCache.getInstance().freeMap();
|
||||
|
||||
// grab the mouse (not working for touchscreen)
|
||||
// container.setMouseGrabbed(true);
|
||||
|
||||
|
@ -1388,6 +1393,7 @@ public class Game extends BasicGameState {
|
|||
Circle.init(container, circleSize);
|
||||
Slider.init(container, circleSize, beatmap);
|
||||
Spinner.init(container);
|
||||
Curve.init(container.getWidth(),container.getHeight(),circleSize);
|
||||
|
||||
// approachRate (hit object approach time)
|
||||
if (approachRate < 5)
|
||||
|
|
|
@ -59,7 +59,8 @@ public class OptionsMenu extends BasicGameState {
|
|||
GameOption.SCREENSHOT_FORMAT,
|
||||
GameOption.NEW_CURSOR,
|
||||
GameOption.DYNAMIC_BACKGROUND,
|
||||
GameOption.LOAD_VERBOSE
|
||||
GameOption.LOAD_VERBOSE,
|
||||
GameOption.NEW_SLIDER
|
||||
}),
|
||||
MUSIC ("Music", new GameOption[] {
|
||||
GameOption.MASTER_VOLUME,
|
||||
|
|
Loading…
Reference in New Issue
Block a user