diff --git a/res/sh_Cursor.vertex b/res/sh_Cursor.vertex new file mode 100644 index 00000000..de9ed955 --- /dev/null +++ b/res/sh_Cursor.vertex @@ -0,0 +1,15 @@ +attribute vec4 m_Position; +attribute vec2 m_TexCoord; +attribute float m_Time; + +varying vec4 v_Color; +varying vec2 v_TexCoord; + +uniform float g_FadeClock; + +void main(void) +{ + gl_Position = m_Position; + v_Color = vec4(1.0, 0.0, 0.0, 1.0); + v_TexCoord = m_TexCoord; +} \ No newline at end of file diff --git a/res/sh_Texture.frag b/res/sh_Texture.frag new file mode 100644 index 00000000..06102e68 --- /dev/null +++ b/res/sh_Texture.frag @@ -0,0 +1,13 @@ +#ifdef GL_ES + precision mediump float; +#endif + +varying vec4 v_Color; +varying vec2 v_TexCoord; + +uniform sampler2D m_Sampler; + +void main(void) +{ + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);//v_Color * texture2D(m_Sampler, v_TexCoord); +} \ No newline at end of file diff --git a/src/com/osufx/sunpy/PropertyCollection.java b/src/com/osufx/sunpy/PropertyCollection.java new file mode 100644 index 00000000..7a76bf6b --- /dev/null +++ b/src/com/osufx/sunpy/PropertyCollection.java @@ -0,0 +1,92 @@ +package com.osufx.sunpy; + +import java.util.HashMap; + +public class PropertyCollection { + private HashMap properties = new HashMap<>(); + private Shader owner; + + private boolean changed = false; + + public PropertyCollection(Shader owner) { + this.owner = owner; + } + + public void Clear() { + this.changed = true; + this.properties.clear(); + } + + public void Add(ShaderProperty prop) { + this.properties.put(prop.Name, prop); + } + + public void Replace(ShaderProperty prop) { + this.properties.replace(prop.Name, prop); + } + + public void Replace(String key, Object value) { + if (!this.properties.containsKey(key)) + return; + + ShaderProperty prop = this.properties.get(key); + + if (value.getClass() != prop.Type){ + System.out.println(String.format("%s variable %s does not take value type %s", this.owner.name, key, value.getClass())); + return; + } + + if (prop.Value != null && prop.Value == value) + return; + + prop.Value = value; + } + + public void Remove(String key) { + this.properties.remove(key); + } + + public void Set() { + if (!this.changed) + return; + + for (ShaderProperty prop : this.properties.values()) { + prop.Set(); + } + + this.changed = false; + } + + public ShaderProperty get(String propName) { + if (!this.properties.containsKey(propName)) + return null; + return this.properties.get(propName); + } + + public void set(String propName, Object value) { + if (!this.owner.loaded) + return; + + if (!this.properties.containsKey(propName)) { + System.out.println(String.format("Property %s does not exist!", propName)); + return; + } + + ShaderProperty prop = this.properties.get(propName); + + if (value.getClass() != prop.Type){ + System.out.println(String.format("%s variable %s does not take value type %s", this.owner.name, propName, value.getClass())); + return; + } + + if (prop.Value != null && prop.Value == value) + return; + + prop.Value = value; + + if (this.owner.started) + prop.Set(); + else + this.changed = true; + } +} diff --git a/src/com/osufx/sunpy/Shader.java b/src/com/osufx/sunpy/Shader.java new file mode 100644 index 00000000..6b2af720 --- /dev/null +++ b/src/com/osufx/sunpy/Shader.java @@ -0,0 +1,204 @@ +package com.osufx.sunpy; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; +import org.newdawn.slick.util.Log; +import org.newdawn.slick.util.ResourceLoader; + +import java.io.InputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.*; + +public class Shader { + public String name; + public int programId = -1; + public boolean loaded = false; + public boolean started = false; + + public PropertyCollection Properties; + + public static HashMap globalProperties = new HashMap<>(); + public static List loadedShaders = new ArrayList<>(); + public static int currentShader = 0; + + private HashMap attribLoc = new HashMap<>(); + private static final String[] attribs = new String[] { + "m_Position", + "m_Color", + "m_TexCoord", + "m_Time", + "m_Direction" + }; + + private HashMap shaderParts = new HashMap<>(); + private int previousShader; + + public Shader(String vertexShader, String fragmentShader) { + String vertexString; + String fragmentString; + + try { + InputStream vertexStream = ResourceLoader.getResourceAsStream(vertexShader); + vertexString = convertStreamToString(vertexStream); + + InputStream fragmentStream = ResourceLoader.getResourceAsStream(fragmentShader); + fragmentString = convertStreamToString(fragmentStream); + } catch (Exception e){ + e.printStackTrace(); + return; + } + + this.name = String.format("%s/%s", vertexShader, fragmentShader); + this.Properties = new PropertyCollection(this); + + this.programId = GL20.glCreateProgram(); + + this.shaderParts.put(GL20.glCreateShader(GL20.GL_VERTEX_SHADER), vertexString); + this.shaderParts.put(GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER), fragmentString); + this.CompileParts(); + this.Compile(); + } + + public Shader(String shader, final int shaderType) { + if (shaderType != GL20.GL_FRAGMENT_SHADER && shaderType != GL20.GL_VERTEX_SHADER){ + System.out.println("Invalid shader type"); + return; + } + + String shaderString; + + try { + InputStream shaderStream = ResourceLoader.getResourceAsStream(shader); + shaderString = convertStreamToString(shaderStream); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + this.name = String.format("%s/%s", + shaderType == GL20.GL_FRAGMENT_SHADER ? "Fragment" : "Vertex", + shader); + this.Properties = new PropertyCollection(this); + + this.programId = GL20.glCreateProgram(); + + this.shaderParts.put(GL20.glCreateShader(shaderType), shaderString); + this.CompileParts(); + this.Compile(); + } + + public void CompileParts() { + for (Map.Entry entry : this.shaderParts.entrySet()) { + int handle = entry.getKey(); + String source = entry.getValue(); + + GL20.glShaderSource(handle, source); + GL20.glCompileShader(handle); + + int res = GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS); + if (res != GL11.GL_TRUE) { + String error = GL20.glGetShaderInfoLog(handle, 1024); + Log.error("Shader compilation failed.", new Exception(error)); + } + } + } + + public void Compile() { + this.Properties.Clear(); + + for (int handler : this.shaderParts.keySet()) { + GL20.glAttachShader(this.programId, handler); + } + + GL20.glLinkProgram(this.programId); + + int res = GL20.glGetProgrami(this.programId, GL20.GL_LINK_STATUS); + if (res != GL11.GL_TRUE) { + String error = GL20.glGetProgramInfoLog(this.programId, 1024); + Log.error("Program linking failed.", new Exception(error)); + } + + for (int handler : this.shaderParts.keySet()) { + GL20.glDeleteShader(handler); + } + + for (int i = 0; i < attribs.length; i++) { + int attrib = GL20.glGetAttribLocation(this.programId, attribs[i]); + this.attribLoc.put(attribs[i], attrib); + //GL20.glBindAttribLocation(this.programId, i, attribLoc[i]); + } + + /*for (int handler : this.shaderParts.keySet()) { + GL20.glDetachShader(this.programId, handler); + }*/ + + this.loaded = res == GL11.GL_TRUE; + + if (this.loaded) { + int uniCount = GL20.glGetProgrami(this.programId, GL20.GL_ACTIVE_UNIFORMS); + + for (int i = 0; i < uniCount; i++) { + IntBuffer length = BufferUtils.createIntBuffer(1); + IntBuffer size = BufferUtils.createIntBuffer(1); + IntBuffer type = BufferUtils.createIntBuffer(1); + ByteBuffer name = ByteBuffer.allocateDirect(64); + + GL20.glGetActiveUniform(this.programId, i, length, size, type, name); + + + byte[] buf = new byte[length.get(0)]; + + name.get(buf, 0, buf.length); + String propName = new String(buf); + int propLocation = GL20.glGetUniformLocation(programId, propName); + + UniformType propUniformType = UniformType.map.get(type.get(0)); + Class propType = ShaderProperty.ConvertType(propUniformType.name()); + + System.out.println(String.format("propName:%s PropUniformType:%s PropType:%s", propName, propUniformType, propType)); + + this.Properties.Add(new ShaderProperty( + propName, + propLocation, + propType, + propUniformType + )); + } + + for (Map.Entry entry : globalProperties.entrySet()) { + this.Properties.Replace(entry.getKey(), entry.getValue()); + } + + loadedShaders.add(this); + } + } + + public void Begin(){ + if (this.started || !this.loaded) + return; + + GL20.glUseProgram(this.programId); + this.previousShader = currentShader; + currentShader = this.programId; + + this.started = true; + } + + public void End() { + if (!this.started) + return; + + GL20.glUseProgram(this.previousShader); + currentShader = this.previousShader; + + this.started = false; + } + + static String convertStreamToString(InputStream is) { + Scanner s = new Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } +} diff --git a/src/com/osufx/sunpy/ShaderProperty.java b/src/com/osufx/sunpy/ShaderProperty.java new file mode 100644 index 00000000..7924ce9f --- /dev/null +++ b/src/com/osufx/sunpy/ShaderProperty.java @@ -0,0 +1,133 @@ +package com.osufx.sunpy; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL20; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +public class ShaderProperty { + public String Name; + public int Location; + + public Class Type; + public UniformType ActiveUniformType; + + public Object Value; + + public ShaderProperty(String Name, int Location, Class Type, UniformType ActiveUniformType) { + this.Name = Name; + this.Location = Location; + this.Type = Type; + this.ActiveUniformType = ActiveUniformType; + } + + public void Set() { + if (Value == null) + return; + + switch (this.ActiveUniformType) { + case Bool: + GL20.glUniform1(this.Location, IntBuffer.wrap( new int[]{ (boolean)Value ? 1 : 0 } )); + break; + case Int: + GL20.glUniform1(this.Location, IntBuffer.wrap( new int[]{ (int)Value } )); + break; + case Float: + GL20.glUniform1(this.Location, FloatBuffer.wrap( new float[]{ (float)Value } )); + break; + case BoolVec2: + GL20.glUniform2(this.Location, IntBuffer.wrap( boolArrayToIntArray( (boolean[])Value ) )); + break; + case IntVec2: + GL20.glUniform2(this.Location, IntBuffer.wrap( (int[])Value )); + break; + case FloatVec2: + GL20.glUniform2(this.Location, FloatBuffer.wrap( (float[])Value )); + break; + case FloatMat2: + GL20.glUniformMatrix2(this.Location, false, FloatBuffer.wrap( (float[])Value )); + break; + case BoolVec3: + GL20.glUniform3(this.Location, IntBuffer.wrap( boolArrayToIntArray( (boolean[])Value ) )); + break; + case IntVec3: + GL20.glUniform3(this.Location, IntBuffer.wrap( (int[])Value )); + break; + case FloatVec3: + GL20.glUniform3(this.Location, FloatBuffer.wrap( (float[])Value )); + break; + case FloatMat3: + GL20.glUniformMatrix3(this.Location, false, FloatBuffer.wrap( (float[])Value )); + break; + case BoolVec4: + GL20.glUniform4(this.Location, IntBuffer.wrap( boolArrayToIntArray( (boolean[])Value ) )); + break; + case IntVec4: + GL20.glUniform4(this.Location, IntBuffer.wrap( (int[])Value )); + break; + case FloatVec4: + GL20.glUniform4(this.Location, FloatBuffer.wrap( (float[])Value )); + break; + case FloatMat4: + GL20.glUniformMatrix4(this.Location, false, FloatBuffer.wrap( (float[])Value )); + break; + case Sampler2D: + GL20.glUniform1(this.Location, BufferUtils.createIntBuffer(1).put((int) Value)); + break; + } + } + + public static Class ConvertType(String type) { + switch (type) { + default: + //throw new Exception(String.format("Uniform type %s is not supported.", type)); + System.out.println(String.format("Uniform type %s is not supported.", type)); + return null; + case "Bool": + return Boolean.class; + case "Int": + return Integer.class; + case "Float": + return Float.class; + case "BoolVec2": + return boolean[].class; + case "IntVec2": + return int[].class; + case "FloatVec2": + return float[].class; + case "BoolVec3": + return boolean[].class; + case "IntVec3": + return int[].class; + case "FloatVec3": + return float[].class; + case "BoolVec4": + return boolean[].class; + case "IntVec4": + return int[].class; + case "FloatVec4": + return float[].class; + case "FloatMat2": + return float[].class; + case "FloatMat3": + return float[].class; + case "FloatMat4": + return float[].class; + case "Sampler2D": + case "SamplerCube": + return Integer.class; + } + } + + /*public static Type ConvertType(int type) { + (UniformType)24; + }*/ + + public static int[] boolArrayToIntArray(boolean[] array) { + int[] value = new int[array.length]; + for (int i = 0; i < array.length; i++) + value[i] = array[i] ? 1 : 0; + return value; + } +} diff --git a/src/com/osufx/sunpy/UniformType.java b/src/com/osufx/sunpy/UniformType.java new file mode 100644 index 00000000..03c26520 --- /dev/null +++ b/src/com/osufx/sunpy/UniformType.java @@ -0,0 +1,39 @@ +package com.osufx.sunpy; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; + +import java.util.HashMap; + +public enum UniformType { + Int(GL11.GL_INT), + Float(GL11.GL_FLOAT), + FloatVec2(GL20.GL_FLOAT_VEC2), + FloatVec3(GL20.GL_FLOAT_VEC3), + FloatVec4(GL20.GL_FLOAT_VEC4), + IntVec2(GL20.GL_INT_VEC2), + IntVec3(GL20.GL_INT_VEC3), + IntVec4(GL20.GL_INT_VEC4), + Bool(GL20.GL_BOOL), + BoolVec2(GL20.GL_BOOL_VEC2), + BoolVec3(GL20.GL_BOOL_VEC3), + BoolVec4(GL20.GL_BOOL_VEC4), + FloatMat2(GL20.GL_FLOAT_MAT2), + FloatMat3(GL20.GL_FLOAT_MAT3), + FloatMat4(GL20.GL_FLOAT_MAT4), + Sampler2D(GL20.GL_SAMPLER_2D), + SamplerCube(GL20.GL_SAMPLER_CUBE); + + private final int glIndex; + + public static HashMap map = new HashMap<>(); + static { + for (UniformType v : UniformType.values()) { + map.put(v.glIndex, v); + } + } + + UniformType(int glIndex) { + this.glIndex = glIndex; + } +} diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java index 87548940..fc1f58ab 100644 --- a/src/itdelatrisu/opsu/render/CurveRenderState.java +++ b/src/itdelatrisu/opsu/render/CurveRenderState.java @@ -17,6 +17,7 @@ */ package itdelatrisu.opsu.render; +import com.osufx.sunpy.Shader; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; @@ -248,7 +249,7 @@ public class CurveRenderState { 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); + GL20.glUseProgram(Shader.currentShader); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 54613d63..f783fbcf 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -307,7 +307,7 @@ public class Game extends ComplexOpsuState { MUSICBAR_HOVER = new Color(12, 9, 10, 0.35f), MUSICBAR_FILL = new Color(255, 255, 255, 0.75f); - private final Cursor mirrorCursor; + private Cursor mirrorCursor; private final MoveStoryboard moveStoryboardOverlay; private final StoryboardOverlay storyboardOverlay; private final OptionsOverlay optionsOverlay; @@ -318,7 +318,7 @@ public class Game extends ComplexOpsuState { public Game() { super(); - mirrorCursor = new Cursor(true); + //mirrorCursor = new Cursor(true); this.moveStoryboardOverlay = new MoveStoryboard(); this.optionsOverlay = new OptionsOverlay(OptionGroups.storyboardOptions); this.storyboardOverlay = new StoryboardOverlay(moveStoryboardOverlay, optionsOverlay, this); @@ -1501,6 +1501,7 @@ public class Game extends ComplexOpsuState { super.enter(); + /* File replaydir = new File("d:/Users/Robin/games/osu/osr-stuff-master/xi/"); if (!replaydir.exists()) { bubNotifs.sendf(Colors.BUB_RED, "replay folder '%s' does not exist", replaydir.getAbsolutePath()); @@ -1577,7 +1578,7 @@ public class Game extends ComplexOpsuState { replayidx++; } - displayContainer.drawCursor = false; + displayContainer.drawCursor = false;*/ isInGame = true; if (!skippedToCheckpoint) { diff --git a/src/itdelatrisu/opsu/ui/Cursor.java b/src/itdelatrisu/opsu/ui/Cursor.java index 44e8e30f..ad40cd7d 100644 --- a/src/itdelatrisu/opsu/ui/Cursor.java +++ b/src/itdelatrisu/opsu/ui/Cursor.java @@ -18,18 +18,34 @@ package itdelatrisu.opsu.ui; +import com.osufx.sunpy.Shader; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.render.Rendertarget; import itdelatrisu.opsu.ui.animations.AnimationEquation; import java.awt.Point; +import java.nio.IntBuffer; +import java.util.Iterator; import java.util.LinkedList; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.EXTFramebufferObject; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL14; import org.newdawn.slick.*; +import org.newdawn.slick.opengl.Texture; +import org.newdawn.slick.opengl.TextureImpl; + import yugecin.opsudance.Dancer; +import yugecin.opsudance.ReplayPlayback; import yugecin.opsudance.skinning.SkinService; import static yugecin.opsudance.options.Options.*; +import static itdelatrisu.opsu.GameImage.CURSOR; +import static itdelatrisu.opsu.GameImage.CURSOR_MIDDLE; +import static itdelatrisu.opsu.GameImage.CURSOR_TRAIL; import static yugecin.opsudance.core.InstanceContainer.*; /** @@ -55,8 +71,7 @@ public class Cursor { /** The time it takes for the cursor to scale, in milliseconds. */ private static final float CURSOR_SCALE_TIME = 125; - /** Stores all previous cursor locations to display a trail. */ - private LinkedList trail = new LinkedList<>(); + private TrailList trail = new TrailList(); private boolean newStyle; @@ -69,6 +84,10 @@ public class Cursor { private boolean isMirrored; private Color filter; + + private Rendertarget fbo; + + private Shader cursorTrailShader; public Cursor() { this(false); @@ -77,6 +96,9 @@ public class Cursor { public Cursor(boolean isMirrored) { resetLocations(0, 0); this.isMirrored = isMirrored; + this.lastPosition = new Point(width2, 0); + this.filter = new Color(255,255,255); + this.cursorTrailShader = new Shader("sh_Cursor.vertex", "sh_Texture.frag"); } public Cursor(Color filter) { @@ -89,6 +111,10 @@ public class Cursor { * @param mousePressed whether or not the mouse button is pressed */ public void draw(boolean mousePressed) { + if (fbo == null) { + this.fbo = Rendertarget.createRTTFramebuffer(width, height); + } + /* if (OPTION_DISABLE_CURSOR.state) { return; } @@ -168,6 +194,78 @@ public class Cursor { if (hasMiddle) { cursorMiddle.drawCentered(lastPosition.x, lastPosition.y, OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL.state ? Color.white : filter); } + */ + final Image img = CURSOR_TRAIL.getImage(); + final Texture txt = img.getTexture(); + + final float trailw2 = img.getWidth() * OPTION_CURSOR_SIZE.val / 100f / 2f; + final float trailh2 = img.getHeight() * OPTION_CURSOR_SIZE.val / 100f / 2f; + float txtw = txt.getWidth(); + float txth = txt.getHeight(); + + /* + // stuff copied from CurveRenderState and stuff, I don't know what I'm doing + int oldFb = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT); + int oldTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); + IntBuffer oldViewport = BufferUtils.createIntBuffer(16); + GL11.glGetInteger(GL11.GL_VIEWPORT, oldViewport); + EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo.getID()); + GL11.glViewport(0, 0, fbo.width, fbo.height); + GL11.glClearColor(0f, 0f, 0f, 0f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); +*/ + //txt.bind(); + Color.white.bind(); + TextureImpl.unbind(); + cursorTrailShader.Begin(); + GL11.glBegin(GL11.GL_QUADS); + + //GL11.glBindTexture(GL11.GL_TEXTURE_2D, ); + //cursorTrailShader.Properties.set("m_Sampler", txt.getTextureID()); + //cursorTrailShader.Properties.set("g_FadeClock", 0.4f); + float alpha = 0f; + float alphaIncrease = .4f / trail.size; + for (Trailpart p : trail) { + //alpha += alphaIncrease; + GL11.glColor4f(filter.r, filter.g, filter.b, alpha); + //GL11.glTexCoord2f(0f, 0f); + GL11.glVertex3f(p.x - trailw2, p.y - trailh2, 0f); + //GL11.glTexCoord2f(txtw, 0); + GL11.glVertex3f(p.x + trailw2, p.y - trailh2, 0f); + //GL11.glTexCoord2f(txtw, txth); + GL11.glVertex3f(p.x + trailw2, p.y + trailh2, 0f); + //GL11.glTexCoord2f(0f, txth); + GL11.glVertex3f(p.x - trailw2, p.y + trailh2, 0f); + break; + } + //GL11.glEnd(); + GL11.glEnd(); + cursorTrailShader.End(); + + /* + GL11.glBindTexture(GL11.GL_TEXTURE_2D, oldTex); + EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFb); + GL11.glViewport(oldViewport.get(0), oldViewport.get(1), oldViewport.get(2), oldViewport.get(3)); + + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + //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(1f, 1f, 1f, 1f); + GL11.glTexCoord2f(1f, 1f); + GL11.glVertex2i(fbo.width, 0); + GL11.glTexCoord2f(0f, 1f); + GL11.glVertex2i(0, 0); + GL11.glTexCoord2f(0f, 0f); + GL11.glVertex2i(0, fbo.height); + GL11.glTexCoord2f(1f, 0f); + GL11.glVertex2i(fbo.width, fbo.height); + GL11.glEnd(); + GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + */ + //CURSOR.getScaledImage(OPTION_CURSOR_SIZE.val / 100f).drawCentered(lastPosition.x, lastPosition.y, filter); + //CURSOR_MIDDLE.getScaledImage(OPTION_CURSOR_SIZE.val / 100f).drawCentered(lastPosition.x, lastPosition.y, filter); } /** @@ -176,34 +274,22 @@ public class Cursor { * @param mouseY y coordinate to set position to */ public void setCursorPosition(int delta, int mouseX, int mouseY) { - // TODO: use an image buffer - int removeCount = 0; - float FPSmod = Math.max(1000 / Math.max(delta, 1), 1) / 30f; // TODO - if (newStyle) { - // new style: add all points between cursor movements - if ((lastPosition.x == 0 && lastPosition.y == 0) || !addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY)) { - trail.add(new Point(mouseX, mouseY)); - } - lastPosition.move(mouseX, mouseY); + nowtime = System.currentTimeMillis(); - removeCount = (int) (trail.size() / (6 * FPSmod)) + 1; - } else { - // old style: sample one point at a time - trail.add(new Point(mouseX, mouseY)); + addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY); + lastPosition.move(mouseX, mouseY); - int max = (int) (10 * FPSmod); - if (trail.size() > max) - removeCount = trail.size() - max; + int removecount = 0; + TrailNode newfirst = trail.first; + while (newfirst != null && newfirst.value.time < nowtime - 175) { + newfirst = newfirst.next; + removecount++; } - - int cursortraillength = OPTION_DANCE_CURSOR_TRAIL_OVERRIDE.val; - if (cursortraillength > 20) { - removeCount = trail.size() - cursortraillength; + trail.first = newfirst; + if (newfirst == null) { + trail.last = null; } - - // remove points from the lists - for (int i = 0; i < removeCount && !trail.isEmpty(); i++) - trail.remove(); + trail.size -= removecount; } /** @@ -211,8 +297,8 @@ public class Cursor { * @author http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#Java */ private boolean addCursorPoints(int x1, int y1, int x2, int y2) { + int size = trail.size; // delta of exact value and rounded value of the dependent variable - boolean added = false; int d = 0; int dy = Math.abs(y2 - y1); int dx = Math.abs(x2 - x1); @@ -222,16 +308,11 @@ public class Cursor { int ix = x1 < x2 ? 1 : -1; // increment direction int iy = y1 < y2 ? 1 : -1; - int k = 5; // sample size if (dy <= dx) { - for (int i = 0; ; i++) { - if (i == k) { - trail.add(new Point(x1, y1)); - added = true; - i = 0; - } + for (;;) { if (x1 == x2) break; + trail.add(new Trailpart(x1, y1)); x1 += ix; d += dy2; if (d > dx) { @@ -240,14 +321,10 @@ public class Cursor { } } } else { - for (int i = 0; ; i++) { - if (i == k) { - trail.add(new Point(x1, y1)); - added = true; - i = 0; - } + for (;;) { if (y1 == y2) break; + trail.add(new Trailpart(x1, y1)); y1 += iy; d += dx2; if (d > dy) { @@ -256,7 +333,64 @@ public class Cursor { } } } - return added; + return trail.size != size; + } + + private static class TrailList implements Iterable + { + TrailNode first; + TrailNode last; + int size; + + public void add(Trailpart t) + { + if (last == null) { + last = first = new TrailNode(t); + } else { + TrailNode n = new TrailNode(t); + last.next = n; + last = n; + } + size++; + } + + @Override + public Iterator iterator() + { + return new Iterator() { + TrailNode node = first; + @Override + public boolean hasNext() { + return node != null; + } + + @Override + public Trailpart next() { + Trailpart v = node.value; + node = node.next; + return v; + } + }; + } + } + + private static class TrailNode + { + TrailNode next; + Trailpart value; + TrailNode(Trailpart t) { + value = t; + } + } + + private static class Trailpart { + int x, y; + long time; + Trailpart(int x, int y) { + this.x = x; + this.y = y; + this.time = nowtime; + } } /** @@ -289,11 +423,7 @@ public class Cursor { * Resets all cursor location data. */ public void resetLocations(int mouseX, int mouseY) { - trail.clear(); - lastPosition = new Point(mouseX, mouseY); - for (int i = 0; i < 50; i++) { - trail.add(new Point(lastPosition)); - } + } /** diff --git a/src/yugecin/opsudance/core/DisplayContainer.java b/src/yugecin/opsudance/core/DisplayContainer.java index e08f6c46..ecc7cf79 100644 --- a/src/yugecin/opsudance/core/DisplayContainer.java +++ b/src/yugecin/opsudance/core/DisplayContainer.java @@ -92,7 +92,7 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener { private long exitconfirmation; - public final Cursor cursor; + public Cursor cursor; public boolean drawCursor; private final List resolutionChangedListeners; @@ -106,8 +106,6 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener { public DisplayContainer() { this.resolutionChangedListeners = new ArrayList<>(); - this.cursor = new Cursor(); - drawCursor = true; skinservice.addSkinChangedListener(this); @@ -271,6 +269,8 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener { glVersion = GL11.glGetString(GL11.GL_VERSION); glVendor = GL11.glGetString(GL11.GL_VENDOR); GLHelper.hideNativeCursor(); + this.cursor = new Cursor(); + drawCursor = true; } // TODO: move this elsewhere diff --git a/src/yugecin/opsudance/core/Entrypoint.java b/src/yugecin/opsudance/core/Entrypoint.java index 440ff72b..7eed2d88 100644 --- a/src/yugecin/opsudance/core/Entrypoint.java +++ b/src/yugecin/opsudance/core/Entrypoint.java @@ -35,6 +35,7 @@ public class Entrypoint { try { InstanceContainer.kickstart(); } catch (Exception e) { + e.printStackTrace(); JOptionPane.showMessageDialog(null, e.getMessage(), "Cannot start " + PROJECT_NAME, JOptionPane.ERROR_MESSAGE); // TODO replace with errorhandler }