1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-12-30 16:56:47 +00:00
Funkin/source/funkin/graphics/rendering/SustainTrail.hx
2023-01-22 22:25:45 -05:00

234 lines
7.1 KiB
Haxe

package funkin.graphics.rendering;
import flixel.FlxSprite;
import flixel.graphics.FlxGraphic;
import flixel.graphics.tile.FlxDrawTrianglesItem;
import flixel.math.FlxMath;
/**
* This is based heavily on the `FlxStrip` class. It uses `drawTriangles()` to clip a sustain note
* trail at a certain time.
* The whole `FlxGraphic` is used as a texture map. See the `NOTE_hold_assets.fla` file for specifics
* on how it should be constructed.
*
* @author MtH
*/
class SustainTrail extends FlxSprite
{
/**
* Used to determine which note color/direction to draw for the sustain.
*/
public var noteData:Int = 0;
/**
* The zoom level to render the sustain at.
* Defaults to 1.0, increased to 6.0 for pixel notes.
*/
public var zoom(default, set):Float = 1;
/**
* The strumtime of the note, in milliseconds.
*/
public var strumTime:Float = 0; // millis
/**
* The sustain length of the note, in milliseconds.
*/
public var sustainLength(default, set):Float = 0; // millis
/**
* The scroll speed of the note, as a multiplier.
*/
public var scrollSpeed(default, set):Float = 1.0; // stand-in for PlayState scroll speed
/**
* Whether the note was missed.
*/
public var missed:Bool = false; // maybe BlendMode.MULTIPLY if missed somehow, drawTriangles does not support!
/**
* A `Vector` of floats where each pair of numbers is treated as a coordinate location (an x, y pair).
*/
var vertices:DrawData<Float> = new DrawData<Float>();
/**
* A `Vector` of integers or indexes, where every three indexes define a triangle.
*/
var indices:DrawData<Int> = new DrawData<Int>();
/**
* A `Vector` of normalized coordinates used to apply texture mapping.
*/
var uvtData:DrawData<Float> = new DrawData<Float>();
var processedGraphic:FlxGraphic;
/**
* What part of the trail's end actually represents the end of the note.
* This can be used to have a little bit sticking out.
*/
public var endOffset:Float = 0.5; // 0.73 is roughly the bottom of the sprite in the normal graphic!
/**
* At what point the bottom for the trail's end should be clipped off.
* Used in cases where there's an extra bit of the graphic on the bottom to avoid antialiasing issues with overflow.
*/
public var bottomClip:Float = 0.9;
/**
* Normally you would take strumTime:Float, noteData:Int, sustainLength:Float, parentNote:Note (?)
* @param NoteData
* @param SustainLength
* @param FileName
*/
public function new(NoteData:Int, SustainLength:Float, Path:String, ?Alpha:Float = 0.6, ?Pixel:Bool = false)
{
super(0, 0, Path);
// BASIC SETUP
this.sustainLength = SustainLength;
this.noteData = NoteData;
// CALCULATE SIZE
if (Pixel)
{
this.endOffset = bottomClip = 1;
this.antialiasing = false;
this.zoom = 6.0;
}
else
{
this.antialiasing = true;
this.zoom = 1.0;
}
// width = graphic.width / 8 * zoom; // amount of notes * 2
height = sustainHeight(sustainLength, scrollSpeed);
// instead of scrollSpeed, PlayState.SONG.speed
alpha = Alpha; // setting alpha calls updateColorTransform(), which initializes processedGraphic!
updateClipping();
indices = new DrawData<Int>(12, true, [0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4]);
}
/**
* Calculates height of a sustain note for a given length (milliseconds) and scroll speed.
* @param susLength The length of the sustain note in milliseconds.
* @param scroll The current scroll speed.
*/
public static inline function sustainHeight(susLength:Float, scroll:Float)
{
return (susLength * 0.45 * scroll);
}
function set_zoom(z:Float)
{
this.zoom = z;
width = graphic.width / 8 * z;
updateClipping();
return this.zoom;
}
function set_sustainLength(s:Float)
{
height = sustainHeight(s, scrollSpeed);
return sustainLength = s;
}
function set_scrollSpeed(s:Float)
{
height = sustainHeight(sustainLength, s);
return scrollSpeed = s;
}
/**
* Sets up new vertex and UV data to clip the trail.
* If flipY is true, top and bottom bounds swap places.
* @param songTime The time to clip the note at, in milliseconds.
*/
public function updateClipping(songTime:Float = 0):Void
{
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), scrollSpeed), 0, height);
if (clipHeight == 0)
{
visible = false;
return;
}
else
visible = true;
var bottomHeight:Float = graphic.height * zoom * endOffset;
var partHeight:Float = clipHeight - bottomHeight;
// == HOLD == //
// left bound
vertices[6] = vertices[0] = 0.0;
// top bound
vertices[3] = vertices[1] = flipY ? clipHeight : height - clipHeight;
// right bound
vertices[4] = vertices[2] = width;
// bottom bound (also top bound for hold ends)
if (partHeight > 0) vertices[7] = vertices[5] = flipY ? 0.0 + bottomHeight : vertices[1] + partHeight;
else
vertices[7] = vertices[5] = vertices[1];
// same shit with da bounds, just in relation to the texture
uvtData[6] = uvtData[0] = 1 / 4 * (noteData % 4);
// height overflows past image bounds so wraps around, looping the texture
// flipY bounds are not swapped for UV data, so the graphic is actually flipped
// top bound
uvtData[3] = uvtData[1] = (-partHeight) / graphic.height / zoom;
uvtData[4] = uvtData[2] = uvtData[0] + 1 / 8; // 1
// bottom bound
uvtData[7] = uvtData[5] = 0.0;
// == HOLD ENDS == //
// left bound
vertices[14] = vertices[8] = vertices[0];
// top bound
vertices[11] = vertices[9] = vertices[5];
// right bound
vertices[12] = vertices[10] = vertices[2];
// bottom bound, mind the bottomClip because it clips off bottom of graphic!!
vertices[15] = vertices[13] = flipY ? graphic.height * (-bottomClip + endOffset) : height + graphic.height * (bottomClip - endOffset);
uvtData[14] = uvtData[8] = uvtData[2];
if (partHeight > 0) uvtData[11] = uvtData[9] = 0.0;
else
uvtData[11] = uvtData[9] = (bottomHeight - clipHeight) / zoom / graphic.height;
uvtData[12] = uvtData[10] = uvtData[8] + 1 / 8;
// again, clips off bottom !!
uvtData[15] = uvtData[13] = bottomClip;
}
@:access(flixel.FlxCamera)
override public function draw():Void
{
if (alpha == 0 || graphic == null || vertices == null) return;
for (camera in cameras)
{
if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue;
getScreenPosition(_point, camera).subtractPoint(offset);
camera.drawTriangles(processedGraphic, vertices, indices, uvtData, null, _point, blend, true, antialiasing);
}
}
override public function destroy():Void
{
vertices = null;
indices = null;
uvtData = null;
processedGraphic.destroy();
super.destroy();
}
override function updateColorTransform():Void
{
super.updateColorTransform();
if (processedGraphic != null) processedGraphic.destroy();
processedGraphic = FlxGraphic.fromGraphic(graphic, true);
processedGraphic.bitmap.colorTransform(processedGraphic.bitmap.rect, colorTransform);
}
}