diff --git a/assets/data/editors/layouts/stage/characterEditScreen.xml b/assets/data/editors/layouts/stage/characterEditScreen.xml index e9140123..2658f4ac 100644 --- a/assets/data/editors/layouts/stage/characterEditScreen.xml +++ b/assets/data/editors/layouts/stage/characterEditScreen.xml @@ -52,7 +52,7 @@ - +
diff --git a/libs.xml b/libs.xml index df46404b..e4247aca 100644 --- a/libs.xml +++ b/libs.xml @@ -25,6 +25,7 @@ + diff --git a/project.xml b/project.xml index 09502ba7..c3aabcfb 100644 --- a/project.xml +++ b/project.xml @@ -198,6 +198,7 @@ + diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 58048dbc..fe96e7fb 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -181,7 +181,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I public function copyAtlasValues() { @:privateAccess { - animateAtlas.cameras = cameras; + animateAtlas.cameras = cameras; // investigate if we can use _cameras animateAtlas.scrollFactor = scrollFactor; animateAtlas.scale = scale; animateAtlas.offset = offset; diff --git a/source/funkin/backend/system/FakeCamera.hx b/source/funkin/backend/system/FakeCamera.hx index 83d66eee..b350f4a5 100644 --- a/source/funkin/backend/system/FakeCamera.hx +++ b/source/funkin/backend/system/FakeCamera.hx @@ -40,7 +40,7 @@ class FakeCamera extends FlxCamera { class FakeCallCamera extends FakeCamera { public static final instance = new FakeCallCamera(); - public dynamic function onDraw(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader, smooth:Bool = false, ?shader:FlxShader) { + public dynamic function onDraw(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) { } override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) { diff --git a/source/funkin/backend/utils/MatrixUtil.hx b/source/funkin/backend/utils/MatrixUtil.hx index 97e2d7a9..68cc7921 100644 --- a/source/funkin/backend/utils/MatrixUtil.hx +++ b/source/funkin/backend/utils/MatrixUtil.hx @@ -25,6 +25,8 @@ interface IPrePostDraw { @:access(flixel.FlxCamera) @:access(flixel.FlxSprite) +@:access(flixel.math.FlxMatrix) +@:access(openfl.geom.Matrix) final class MatrixUtil { public static function getMatrixPosition(sprite:FlxSprite, points:OneOfTwo>, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array { @@ -42,46 +44,149 @@ final class MatrixUtil { nc.scroll.set(camera.scroll.x, camera.scroll.y); nc.pixelPerfectRender = camera.pixelPerfectRender; + var points:Array = cast points; + if(isAnimateAtlas) { - var nc = FakeCallCamera.instance; + var cnc = FakeCallCamera.instance; var sprite = funkinSprite; - var oldOnDraw = nc.onDraw; - nc.onDraw = function(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader, smooth:Bool = false, ?shader:FlxShader) { - // pixels is null - + var oldOnDraw = cnc.onDraw; + + // TODO: fix rotation + // TODO: fix skew + + var boundsTopLeft = new FlxPoint(Math.POSITIVE_INFINITY, Math.POSITIVE_INFINITY); + var boundsTopRight = new FlxPoint(Math.NEGATIVE_INFINITY, Math.POSITIVE_INFINITY); + var boundsBottomLeft = new FlxPoint(Math.POSITIVE_INFINITY, Math.NEGATIVE_INFINITY); + var boundsBottomRight = new FlxPoint(Math.NEGATIVE_INFINITY, Math.NEGATIVE_INFINITY); + + cnc.onDraw = function(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) { + // cnc is already perfect rn + // so we need to manually process the limb + var points = [ + // corners + FlxPoint.get(0, 0), + FlxPoint.get(1, 0), + FlxPoint.get(0, 1), + FlxPoint.get(1, 1) + ]; + + rawTransformPoints(points, matrix, frame.sourceSize.x, frame.sourceSize.y); + + // sort the points, incase like they were transformed with a scale -1 matrix + + var topLeft:FlxPoint = points[0]; + var topRight:FlxPoint = points[1]; + var bottomLeft:FlxPoint = points[2]; + var bottomRight:FlxPoint = points[3]; + + if (points[0].y > points[1].y) { + topLeft = points[1]; + topRight = points[0]; + } + if (points[2].y > points[3].y) { + bottomLeft = points[3]; + bottomRight = points[2]; + } + + if (topLeft.x > topRight.x) { + var temp = topLeft; + topLeft = topRight; + topRight = temp; + } + + if (bottomLeft.x > bottomRight.x) { + var temp = bottomLeft; + bottomLeft = bottomRight; + bottomRight = temp; + } + + // points should now contain the corners of the limb + + trace("Corners of limb: " + points); + + // set the max bounds of each corner of the limb + if(topLeft.x < boundsTopLeft.x) boundsTopLeft.x = topLeft.x; + if(topLeft.y < boundsTopLeft.y) boundsTopLeft.y = topLeft.y; + if(topRight.x > boundsTopRight.x) boundsTopRight.x = topRight.x; + if(topRight.y < boundsTopRight.y) boundsTopRight.y = topRight.y; + if(bottomLeft.x < boundsBottomLeft.x) boundsBottomLeft.x = bottomLeft.x; + if(bottomLeft.y > boundsBottomLeft.y) boundsBottomLeft.y = bottomLeft.y; + if(bottomRight.x > boundsBottomRight.x) boundsBottomRight.x = bottomRight.x; + if(bottomRight.y > boundsBottomRight.y) boundsBottomRight.y = bottomRight.y; + + // recycle the points + for(point in points) point.put(); } - var cameras = sprite.animateAtlas._cameras; - sprite.animateAtlas._cameras = [nc]; - if(sprite is IPrePostDraw) { - var postDraw = cast(sprite, IPrePostDraw); - postDraw.preDraw(); - @:privateAccess sprite.draw(); - postDraw.postDraw(); - } else { - @:privateAccess sprite.draw(); + var cameras = sprite._cameras; + var oldVisible = cnc.visible; + cnc.visible = true; + sprite._cameras = [cnc]; + sprite.draw(); + sprite._cameras = cameras; + cnc.visible = oldVisible; + cnc.onDraw = oldOnDraw; + + // fake transform + + //trace(""); + //trace("topLeft: " + boundsTopLeft); + //trace("topRight: " + boundsTopRight); + //trace("bottomLeft: " + boundsBottomLeft); + //trace("bottomRight: " + boundsBottomRight); + //trace("Before transform"); + //trace(points); + + for(point in points) { + //var x = matrix.__transformX(point.x * _width, point.y * _height); + //var y = matrix.__transformY(point.x * _width, point.y * _height); + // apply using boundsTopLeft, boundsTopRight, boundsBottomLeft, boundsBottomRight + // matrix doesnt work for this + var x = FlxMath.lerp( + FlxMath.lerp(boundsTopLeft.x, boundsTopRight.x, point.x), + FlxMath.lerp(boundsBottomLeft.x, boundsBottomRight.x, point.x), + point.y + ); + var y = FlxMath.lerp( + FlxMath.lerp(boundsTopLeft.y, boundsTopRight.y, point.x), + FlxMath.lerp(boundsBottomLeft.y, boundsBottomRight.y, point.x), + point.y + ); + + //trace("(" + point.x + ", " + point.y + ") -> (" + x + ", " + y + ")"); + + // reset to ingame coords + x += camera.scroll.x; + y += camera.scroll.y; + + if(isFunkinSprite) { + var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor); + x += camera.width / 2 * ratio; + y += camera.height / 2 * ratio; + } + point.set(x, y); } - sprite.animateAtlas._cameras = cameras; - nc.onDraw = oldOnDraw; + + //trace("After transform"); + //trace(points); } else { if(sprite is IPrePostDraw) { var postDraw = cast(sprite, IPrePostDraw); postDraw.preDraw(); - @:privateAccess sprite.drawComplex(nc); + sprite.drawComplex(nc); postDraw.postDraw(); } else { - @:privateAccess sprite.drawComplex(nc); + sprite.drawComplex(nc); } + transformPoints(sprite, points, sprite._matrix, camera, _width, _height); } - var points:Array = cast points; - transformPoints(sprite, points, sprite._matrix, camera, _width, _height); return points; } /** * Warning: modifies the points in the array **/ - public static function transformPoints(sprite:FlxSprite, points:Array, matrix:FlxMatrix, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array { + public static function transformPoints(sprite:FlxSprite, points:Array, matrix:FlxMatrix, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1, doCameraTransform:Bool = true):Array { var isFunkinSprite = sprite is FunkinSprite; var funkinSprite:FunkinSprite = null; if(isFunkinSprite) funkinSprite = cast sprite; @@ -90,17 +195,29 @@ final class MatrixUtil { var x = matrix.__transformX(point.x * _width, point.y * _height); var y = matrix.__transformY(point.x * _width, point.y * _height); - // reset to ingame coords - x += camera.scroll.x; - y += camera.scroll.y; + if(doCameraTransform) { + // reset to ingame coords + x += camera.scroll.x; + y += camera.scroll.y; - if(isFunkinSprite) { - var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor); - x += camera.width / 2 * ratio; - y += camera.height / 2 * ratio; + if(isFunkinSprite) { + var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor); + x += camera.width / 2 * ratio; + y += camera.height / 2 * ratio; + } } point.set(x, y); } return points; } + + private static function rawTransformPoints(points:Array, matrix:FlxMatrix, _width:Float = 1, _height:Float = 1):Array { + for(point in points) { + var x = matrix.__transformX(point.x * _width, point.y * _height); + var y = matrix.__transformY(point.x * _width, point.y * _height); + + point.set(x, y); + } + return points; + } } \ No newline at end of file diff --git a/source/funkin/editors/UIDebugState.hx b/source/funkin/editors/UIDebugState.hx index 679b2f6f..e7270da6 100644 --- a/source/funkin/editors/UIDebugState.hx +++ b/source/funkin/editors/UIDebugState.hx @@ -4,6 +4,7 @@ import funkin.editors.ui.old.OldUISliceSpriteTri; import funkin.editors.ui.UITopMenu.UITopMenuButton; import flixel.tweens.FlxTween; import funkin.editors.ui.*; +import funkin.game.Character; class UIDebugState extends UIState { public var topMenuSpr:UITopMenu; @@ -21,6 +22,10 @@ class UIDebugState extends UIState { bg.scrollFactor.set(); add(bg); + var gf = new Character(0, 0, "gf"); + gf.dance(); + add(gf); + add(topMenuSpr = new UITopMenu([ { label: "File", diff --git a/source/funkin/editors/character/CharacterAnimButton.hx b/source/funkin/editors/character/CharacterAnimButton.hx index 79eeb1e4..82fc04f7 100644 --- a/source/funkin/editors/character/CharacterAnimButton.hx +++ b/source/funkin/editors/character/CharacterAnimButton.hx @@ -268,6 +268,14 @@ class CharacterAnimButton extends UIButton { flxAnimation.prefix = newAnim; refreshFlxAnimationFrames(flxAnimation, animData); + + if (valid) { + parent.buildAnimDisplay(anim, data); + animationDisplayBG.alpha = 1; + } else { + parent.removeAnimDisplay(anim); + animationDisplayBG.alpha = 0.4; + } } if (parent.character.getAnimName() == anim) @@ -356,14 +364,6 @@ class CharacterAnimButton extends UIButton { trace('ERROR REFRESHING FLXANIMATION FRAMES: $e'); invalidate(); } - - if (valid) { - parent.buildAnimDisplay(anim, flxAnimation); - animationDisplayBG.alpha = 1; - } else { - parent.removeAnimDisplay(anim); - animationDisplayBG.alpha = 0.4; - } } public inline function refreshSymbolKeyFrames(symbol:FlxSymbolAnimation, animData:AnimData) @:privateAccess { @@ -466,6 +466,14 @@ class CharacterAnimButton extends UIButton { @:noCompletion function __refreshAnimation() @:privateAccess { if (parent.character.animateAtlas != null) refreshSymbolKeyFrames(__getAnimationSymbol(), data); else refreshFlxAnimationFrames(__getFlxAnimation(), data); + + if (valid) { + parent.buildAnimDisplay(anim, data); + animationDisplayBG.alpha = 1; + } else { + parent.removeAnimDisplay(anim); + animationDisplayBG.alpha = 0.4; + } } public inline function checkValid() diff --git a/source/funkin/editors/character/CharacterAnimsWindow.hx b/source/funkin/editors/character/CharacterAnimsWindow.hx index a3e67ea8..9910c0eb 100644 --- a/source/funkin/editors/character/CharacterAnimsWindow.hx +++ b/source/funkin/editors/character/CharacterAnimsWindow.hx @@ -58,28 +58,26 @@ class CharacterAnimsWindow extends UIButtonList { if (character.animateAtlas == null) { var anim:FlxAnimation = character.animation._animations[anim.name]; if (anim == null || anim.frames.length <= 0) return; - + var frameIndex:Int = anim.frames.getDefault([0])[0]; var frame:FlxFrame = displayWindowSprite.frames.frames[frameIndex]; - + var frameRect:Rectangle = new Rectangle(frame.offset.x, frame.offset.y, frame.sourceSize.x, frame.sourceSize.y); var animBounds:Rectangle = displayWindowGraphic != null ? displayWindowGraphic.bitmap.bounds(frameRect) : frameRect; - + displayAnimsFramesList.set(name, {frame: anim.frames.getDefault([0])[0], scale: 104/animBounds.height, animBounds: animBounds}); } else { - var ogFrame:Int = character.animateAtlas.anim.curFrame; - var oldTick:Float = character.animateAtlas.anim._tick; - var oldPlaying:Bool = character.animateAtlas.anim.isPlaying; + character.storeAtlasState(); + /* character.animateAtlas.anim.play(anim.name, true, false, 0); character.animateAtlas.anim.stop(); var animBounds:Rectangle = MatrixUtil.getBounds(character).copyToFlash(); displayAnimsFramesList.set(name, {frame: anim.anim, scale: 104/animBounds.height, animBounds: animBounds}); + */ - character.animateAtlas.anim.play(atlasPlayingAnim, true, false, ogFrame); - character.animateAtlas.anim._tick = oldTick; - character.animateAtlas.anim.isPlaying = oldPlaying; + character.restoreAtlasState(); } } @@ -90,11 +88,11 @@ class CharacterAnimsWindow extends UIButtonList { if (buttons.members.length <= 1) return; if (character.getAnimName() == button.anim) @:privateAccess CharacterEditor.instance._animation_down(null); - + character.removeAnimation(button.anim); if (character.animOffsets.exists(button.anim)) character.animOffsets.remove(button.anim); if (character.animDatas.exists(button.anim)) character.animDatas.remove(button.anim); - + remove(button); button.destroy(); } @@ -128,8 +126,7 @@ class CharacterAnimsWindow extends UIButtonList { if (newButton.valid) { XMLUtil.addAnimToSprite(character, animData); - if (character.animation._animations[animData.name] != null) - buildAnimDisplay(animData.name, character.animation._animations[animData.name]); + buildAnimDisplay(animData.name, animData); } } diff --git a/source/funkin/editors/character/CharacterGhost.hx b/source/funkin/editors/character/CharacterGhost.hx index d1aabfee..2cf8bb3e 100644 --- a/source/funkin/editors/character/CharacterGhost.hx +++ b/source/funkin/editors/character/CharacterGhost.hx @@ -20,10 +20,7 @@ class CharacterGhost extends Character { colorTransform.__identity(); if (animateAtlas != null) { - var ogFrame:Int = animateAtlas.anim.curFrame; - var oldTick:Float = animateAtlas.anim._tick; - var oldPlaying:Bool = animateAtlas.anim.isPlaying; - + storeAtlasState(); for (anim in ghosts) { animateAtlas.anim.play(anim, true, false, 0); setAnimOffset(anim); @@ -32,11 +29,7 @@ class CharacterGhost extends Character { super.draw(); } - if (ghosts.length > 0) { - animateAtlas.anim.play(atlasPlayingAnim, true, false, ogFrame); - animateAtlas.anim._tick = oldTick; - animateAtlas.anim.isPlaying = oldPlaying; - } + if (ghosts.length > 0) restoreAtlasState(); } else { for (anim in ghosts) @:privateAccess { @@ -75,8 +68,9 @@ class CharacterGhost extends Character { offset.set(globalOffset.x * (isPlayer != playerOffsets ? 1 : -1), -globalOffset.y); } - var atlasState:AtlasState = null; - public function storeAtlasState():AtlasState { + // This gets annoying lmao -lunar + private var atlasState:AtlasState = null; + public function storeAtlasState():AtlasState @:privateAccess { return atlasState = { oldAnim: atlasPlayingAnim, oldFrame: animateAtlas.anim.curFrame, @@ -84,4 +78,12 @@ class CharacterGhost extends Character { oldPlaying: animateAtlas.anim.isPlaying, }; } + + public function restoreAtlasState(state:AtlasState = null) @:privateAccess { + if (state == null) state = atlasState; + + animateAtlas.anim.play(state.oldAnim, true, false, state.oldFrame); + animateAtlas.anim._tick = state.oldTick; + animateAtlas.anim.isPlaying = state.oldPlaying; + } } \ No newline at end of file diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx index 042711fa..62950a01 100644 --- a/source/funkin/editors/stage/StageEditor.hx +++ b/source/funkin/editors/stage/StageEditor.hx @@ -19,6 +19,7 @@ import openfl.ui.Mouse; using funkin.backend.utils.MatrixUtil; +@:access(flixel.FlxSprite) class StageEditor extends UIState { static var __stage:String; public var stage:Stage; @@ -491,9 +492,11 @@ class StageEditor extends UIState { var sprites = getRealSprites(); for (i in 0...sprites.length) { var sprite = sprites[sprites.length - i - 1]; - if(sprite.animateAtlas != null) continue; + //if(sprite.animateAtlas != null) continue; calcSpriteBounds(sprite); + //trace("Sprite: " + sprite); + //trace("Sprite bounds: " + sprite.extra.get(exID("bounds"))); if (cast(sprite.extra.get(exID("bounds")), FlxRect).containsPoint(point)) { selectSprite(sprite); break; } @@ -923,10 +926,8 @@ class StageEditor extends UIState { super.draw(); for(sprite in selection) { - if(sprite is FunkinSprite) { - @:privateAccess if(sprite._frame == null) continue; + if(sprite is FunkinSprite) drawGuides(cast sprite); - } } } @@ -934,7 +935,10 @@ class StageEditor extends UIState { var circleColor:FlxColor = 0xFF99a8f2; var hollowColor:FlxColor = 0xffb2beff; - function drawGuides(sprite:FlxSprite) { + function drawGuides(sprite:FunkinSprite) { + //if(sprite._frame == null) return; + if(sprite.offset == null) return; // destroyed + var corners = calcSpriteBounds(sprite); var funkinSprite = sprite is FunkinSprite ? cast(sprite, FunkinSprite) : null; @@ -991,7 +995,6 @@ class StageEditor extends UIState { DrawUtil.dot.animation.play("hollow"); buttonBoxes.push(corner); } - } if(funkinSprite == null) { @@ -1037,6 +1040,9 @@ class StageEditor extends UIState { FlxPoint.get(0.5, -0.5), // angle FlxPoint.get(1, 0) // rotate corner ], sprite.camera, sprite.frameWidth, sprite.frameHeight); + //if(sprite.animateAtlas != null) { + // trace("Corners: " + corners); + //} @:privateAccess corners[corners.length - 1].add( dotCheckSize * 0.35 * sprite._cosAngle + dotCheckSize * 0.35 * sprite._sinAngle, -dotCheckSize * 0.35 * sprite._cosAngle + dotCheckSize * 0.35 * sprite._sinAngle