├── bass.dll ├── icon.ico ├── rsrc.syso ├── bass_fx.dll ├── libbass.so ├── libbass.dylib ├── libbass_fx.so ├── libbass_fx.dylib ├── assets ├── textures │ ├── cursor.png │ ├── hit-0.png │ ├── hit-100.png │ ├── hit-300.png │ ├── hit-50.png │ ├── default-0.png │ ├── default-1.png │ ├── default-2.png │ ├── default-3.png │ ├── default-4.png │ ├── default-5.png │ ├── default-6.png │ ├── default-7.png │ ├── default-8.png │ ├── default-9.png │ ├── hitcircle.png │ ├── presskey.png │ ├── cursor-top.png │ ├── cursortrail.png │ ├── dansercoin.png │ ├── dansercoin16.png │ ├── dansercoin24.png │ ├── dansercoin48.png │ ├── reversearrow.png │ ├── sliderball.png │ ├── spinner-top.png │ ├── approachcircle.png │ ├── hitcircle-full.png │ ├── slidergradient.png │ ├── spinner-bottom.png │ ├── spinner-circle.png │ ├── spinner-clear.png │ ├── spinner-middle.png │ ├── hitcircleoverlay.png │ ├── ranking-a-small.png │ ├── ranking-b-small.png │ ├── ranking-c-small.png │ ├── ranking-d-small.png │ ├── ranking-s-small.png │ ├── ranking-sh-small.png │ ├── ranking-x-small.png │ ├── ranking-xh-small.png │ ├── sliderscorepoint.png │ ├── spinner-background.png │ ├── spinner-approachcircle.png │ └── skin.ini ├── fonts │ ├── Roboto-Black.ttf │ └── Roboto-Bold.ttf ├── sounds │ ├── drum-hitclap.wav │ ├── drum-hitfinish.wav │ ├── drum-hitnormal.wav │ ├── normal-hitclap.wav │ ├── soft-hitclap.wav │ ├── soft-hitfinish.wav │ ├── soft-hitnormal.wav │ ├── drum-hitwhistle.wav │ ├── drum-slidertick.wav │ ├── normal-hitfinish.wav │ ├── normal-hitnormal.wav │ ├── soft-hitwhistle.wav │ ├── soft-slidertick.wav │ ├── normal-hitwhistle.wav │ └── normal-slidertick.wav └── shaders │ ├── fbopass.vsh │ ├── fbopass.fsh │ ├── slider.vsh │ ├── slider.fsh │ ├── sprite.fsh │ ├── combine.fsh │ ├── cursortrail.fsh │ ├── sprite.vsh │ ├── brightfilter.fsh │ ├── cursortrail.vsh │ └── blur.fsh ├── states └── state.go ├── hitjudge ├── hitresult.go ├── key.go ├── objectresult.go ├── error.go ├── totalresult.go └── errorio.go ├── score ├── rank.go ├── ppcalculater.go └── scorecalculater.go ├── bmath ├── curves │ ├── curve.go │ ├── linear.go │ ├── cirarc.go │ ├── catmull.go │ └── bezier.go ├── math.go ├── vector2d.go ├── sliders │ └── sliderAlgo.go └── camera.go ├── dance ├── movers │ ├── mover.go │ ├── linear.go │ ├── axisaligned.go │ ├── halfcircle.go │ ├── bezier.go │ └── angleoffset.go └── schedulers │ ├── scheduler.go │ ├── sliderprocessor.go │ └── generic.go ├── prof └── prof.go ├── replay ├── replayparser.go └── replayreader.go ├── error.err ├── .gitattributes ├── audio ├── system.go ├── sample.go ├── osuaudio.go └── music.go ├── main.go ├── .gitignore ├── wheelmodified.md ├── settings ├── dance.go └── manager.go ├── README.md ├── utils ├── fps.go ├── screenshot.go ├── wintime.go ├── colors.go └── utils.go ├── storyboard ├── enums.go ├── loop.go ├── transformations.go ├── layer.go ├── commands.go └── object.go ├── danser.exe.manifest ├── hitjudgetest └── Genryuu Kaiko.md ├── configui └── varassign.go ├── beatmap ├── objects │ ├── pause.go │ ├── baseobject.go │ ├── timing.go │ ├── util.go │ └── circle.go ├── loader.go └── beatmap.go ├── LICENSE ├── osuconst └── osuconst.go ├── resultcache └── cacheio.go ├── animation ├── glider.go └── easing │ └── equations.go ├── render ├── framebuffer │ └── framebuffer.go ├── fx.go ├── texture │ ├── single.go │ ├── texture.go │ └── atlas.go ├── effects │ ├── bloom.go │ └── blur.go ├── slider.go └── font │ └── font.go ├── ini └── ini.go ├── settings-1080.json ├── settings.json └── database └── manager.go /bass.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/bass.dll -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/icon.ico -------------------------------------------------------------------------------- /rsrc.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/rsrc.syso -------------------------------------------------------------------------------- /bass_fx.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/bass_fx.dll -------------------------------------------------------------------------------- /libbass.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/libbass.so -------------------------------------------------------------------------------- /libbass.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/libbass.dylib -------------------------------------------------------------------------------- /libbass_fx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/libbass_fx.so -------------------------------------------------------------------------------- /libbass_fx.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/libbass_fx.dylib -------------------------------------------------------------------------------- /assets/textures/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/cursor.png -------------------------------------------------------------------------------- /assets/textures/hit-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hit-0.png -------------------------------------------------------------------------------- /assets/textures/hit-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hit-100.png -------------------------------------------------------------------------------- /assets/textures/hit-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hit-300.png -------------------------------------------------------------------------------- /assets/textures/hit-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hit-50.png -------------------------------------------------------------------------------- /assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /assets/textures/default-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-0.png -------------------------------------------------------------------------------- /assets/textures/default-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-1.png -------------------------------------------------------------------------------- /assets/textures/default-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-2.png -------------------------------------------------------------------------------- /assets/textures/default-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-3.png -------------------------------------------------------------------------------- /assets/textures/default-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-4.png -------------------------------------------------------------------------------- /assets/textures/default-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-5.png -------------------------------------------------------------------------------- /assets/textures/default-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-6.png -------------------------------------------------------------------------------- /assets/textures/default-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-7.png -------------------------------------------------------------------------------- /assets/textures/default-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-8.png -------------------------------------------------------------------------------- /assets/textures/default-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/default-9.png -------------------------------------------------------------------------------- /assets/textures/hitcircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hitcircle.png -------------------------------------------------------------------------------- /assets/textures/presskey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/presskey.png -------------------------------------------------------------------------------- /assets/sounds/drum-hitclap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/drum-hitclap.wav -------------------------------------------------------------------------------- /assets/sounds/drum-hitfinish.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/drum-hitfinish.wav -------------------------------------------------------------------------------- /assets/sounds/drum-hitnormal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/drum-hitnormal.wav -------------------------------------------------------------------------------- /assets/sounds/normal-hitclap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/normal-hitclap.wav -------------------------------------------------------------------------------- /assets/sounds/soft-hitclap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/soft-hitclap.wav -------------------------------------------------------------------------------- /assets/sounds/soft-hitfinish.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/soft-hitfinish.wav -------------------------------------------------------------------------------- /assets/sounds/soft-hitnormal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/soft-hitnormal.wav -------------------------------------------------------------------------------- /assets/textures/cursor-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/cursor-top.png -------------------------------------------------------------------------------- /assets/textures/cursortrail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/cursortrail.png -------------------------------------------------------------------------------- /assets/textures/dansercoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/dansercoin.png -------------------------------------------------------------------------------- /assets/textures/dansercoin16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/dansercoin16.png -------------------------------------------------------------------------------- /assets/textures/dansercoin24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/dansercoin24.png -------------------------------------------------------------------------------- /assets/textures/dansercoin48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/dansercoin48.png -------------------------------------------------------------------------------- /assets/textures/reversearrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/reversearrow.png -------------------------------------------------------------------------------- /assets/textures/sliderball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/sliderball.png -------------------------------------------------------------------------------- /assets/textures/spinner-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-top.png -------------------------------------------------------------------------------- /assets/sounds/drum-hitwhistle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/drum-hitwhistle.wav -------------------------------------------------------------------------------- /assets/sounds/drum-slidertick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/drum-slidertick.wav -------------------------------------------------------------------------------- /assets/sounds/normal-hitfinish.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/normal-hitfinish.wav -------------------------------------------------------------------------------- /assets/sounds/normal-hitnormal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/normal-hitnormal.wav -------------------------------------------------------------------------------- /assets/sounds/soft-hitwhistle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/soft-hitwhistle.wav -------------------------------------------------------------------------------- /assets/sounds/soft-slidertick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/soft-slidertick.wav -------------------------------------------------------------------------------- /assets/textures/approachcircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/approachcircle.png -------------------------------------------------------------------------------- /assets/textures/hitcircle-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hitcircle-full.png -------------------------------------------------------------------------------- /assets/textures/slidergradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/slidergradient.png -------------------------------------------------------------------------------- /assets/textures/spinner-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-bottom.png -------------------------------------------------------------------------------- /assets/textures/spinner-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-circle.png -------------------------------------------------------------------------------- /assets/textures/spinner-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-clear.png -------------------------------------------------------------------------------- /assets/textures/spinner-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-middle.png -------------------------------------------------------------------------------- /assets/sounds/normal-hitwhistle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/normal-hitwhistle.wav -------------------------------------------------------------------------------- /assets/sounds/normal-slidertick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/sounds/normal-slidertick.wav -------------------------------------------------------------------------------- /assets/textures/hitcircleoverlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/hitcircleoverlay.png -------------------------------------------------------------------------------- /assets/textures/ranking-a-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-a-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-b-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-b-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-c-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-c-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-d-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-d-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-s-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-s-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-sh-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-sh-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-x-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-x-small.png -------------------------------------------------------------------------------- /assets/textures/ranking-xh-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/ranking-xh-small.png -------------------------------------------------------------------------------- /assets/textures/sliderscorepoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/sliderscorepoint.png -------------------------------------------------------------------------------- /states/state.go: -------------------------------------------------------------------------------- 1 | package states 2 | 3 | type State interface { 4 | Show() 5 | Hide() 6 | Draw(delta float64) 7 | Dispose() 8 | } 9 | -------------------------------------------------------------------------------- /assets/textures/spinner-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-background.png -------------------------------------------------------------------------------- /assets/textures/spinner-approachcircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasupandceacar/osu-vs-player/HEAD/assets/textures/spinner-approachcircle.png -------------------------------------------------------------------------------- /hitjudge/hitresult.go: -------------------------------------------------------------------------------- 1 | package hitjudge 2 | 3 | type HitResult int 4 | 5 | const ( 6 | Hit300 HitResult = 0 7 | Hit100 HitResult = 1 8 | Hit50 HitResult = 2 9 | HitMiss HitResult = 3 10 | ) 11 | -------------------------------------------------------------------------------- /hitjudge/key.go: -------------------------------------------------------------------------------- 1 | package hitjudge 2 | 3 | type Key string 4 | 5 | const ( 6 | Key1 Key = "K1" 7 | Key2 Key = "K2" 8 | Mouse1 Key = "M1" 9 | Mouse2 Key = "M2" 10 | NoKey Key = "NK" 11 | ) 12 | -------------------------------------------------------------------------------- /hitjudge/objectresult.go: -------------------------------------------------------------------------------- 1 | package hitjudge 2 | 3 | import "danser/bmath" 4 | 5 | type ObjectResult struct { 6 | JudgePos bmath.Vector2d 7 | JudgeTime int64 8 | Result HitResult 9 | IsBreak bool 10 | } 11 | -------------------------------------------------------------------------------- /hitjudge/error.go: -------------------------------------------------------------------------------- 1 | package hitjudge 2 | 3 | type Error struct { 4 | ReplayIndex int 5 | ObjectIndex int 6 | Result HitResult 7 | IsBreak bool 8 | MaxComboOffset int 9 | NowComboOffset int 10 | } 11 | -------------------------------------------------------------------------------- /score/rank.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | type Rank int 4 | 5 | const ( 6 | SSH Rank = -2 7 | SH Rank = -1 8 | SS Rank = 0 9 | S Rank = 1 10 | A Rank = 2 11 | B Rank = 3 12 | C Rank = 4 13 | D Rank = 5 14 | ) 15 | -------------------------------------------------------------------------------- /assets/shaders/fbopass.vsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 in_position; 4 | in vec2 in_tex_coord; 5 | 6 | out vec2 tex_coord; 7 | void main() 8 | { 9 | gl_Position = vec4(in_position, 1); 10 | tex_coord = in_tex_coord; 11 | } -------------------------------------------------------------------------------- /bmath/curves/curve.go: -------------------------------------------------------------------------------- 1 | package curves 2 | 3 | import "danser/bmath" 4 | 5 | type Curve interface { 6 | PointAt(t float64) bmath.Vector2d 7 | GetStartAngle() float64 8 | GetEndAngle() float64 9 | GetLength() float64 10 | } 11 | -------------------------------------------------------------------------------- /assets/shaders/fbopass.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2DArray tex; 4 | 5 | in vec2 tex_coord; 6 | out vec4 color; 7 | 8 | void main() 9 | { 10 | vec4 in_color = texture(tex, vec3(tex_coord, 0)); 11 | color = in_color; 12 | } -------------------------------------------------------------------------------- /dance/movers/mover.go: -------------------------------------------------------------------------------- 1 | package movers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | ) 7 | 8 | type MultiPointMover interface { 9 | Reset() 10 | SetObjects(objs []objects.BaseObject) 11 | Update(time int64) bmath.Vector2d 12 | GetEndTime() int64 13 | } 14 | -------------------------------------------------------------------------------- /prof/prof.go: -------------------------------------------------------------------------------- 1 | package prof 2 | 3 | import ( 4 | "os" 5 | "runtime/pprof" 6 | ) 7 | 8 | func ProfStart() { 9 | f, err := os.OpenFile("vsplayer.prof", os.O_RDWR|os.O_CREATE, 0644) 10 | if err != nil { 11 | panic(err) 12 | } 13 | pprof.StartCPUProfile(f) 14 | } 15 | 16 | func ProfEnd() { 17 | pprof.StopCPUProfile() 18 | } 19 | -------------------------------------------------------------------------------- /assets/shaders/slider.vsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 in_position; 4 | in vec3 center; 5 | in vec2 in_tex_coord; 6 | 7 | uniform mat4 proj; 8 | uniform mat4 trans; 9 | 10 | out vec2 tex_coord; 11 | void main() 12 | { 13 | gl_Position = proj * ((trans * vec4(in_position-center, 1))+vec4(center, 0)); 14 | tex_coord = in_tex_coord; 15 | } -------------------------------------------------------------------------------- /assets/shaders/slider.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2DArray tex; 4 | uniform vec4 col_border; 5 | uniform vec4 col_border1; 6 | 7 | in vec2 tex_coord; 8 | out vec4 color; 9 | void main() 10 | { 11 | vec4 in_color = texture(tex, vec3(tex_coord, 0)); 12 | 13 | color = in_color*mix(col_border1, col_border, smoothstep(45.0/512, 60.0/512, tex_coord.x)); 14 | } -------------------------------------------------------------------------------- /hitjudge/totalresult.go: -------------------------------------------------------------------------------- 1 | package hitjudge 2 | 3 | import ( 4 | "danser/score" 5 | "github.com/flesnuk/oppai5" 6 | ) 7 | 8 | type TotalResult struct { 9 | N300 uint16 10 | N100 uint16 11 | N50 uint16 12 | Misses uint16 13 | Combo uint16 14 | Mods uint32 15 | Acc float64 16 | Rank score.Rank 17 | PP oppai.PPv2 18 | UR float64 19 | } 20 | -------------------------------------------------------------------------------- /assets/shaders/sprite.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec4 col_tint; 4 | in vec3 tex_coord; 5 | in float additive; 6 | 7 | uniform sampler2DArray tex; 8 | 9 | out vec4 color; 10 | 11 | void main() 12 | { 13 | vec4 in_color = texture(tex, tex_coord); 14 | color = in_color*col_tint; 15 | color.rgb *= color.a; 16 | if (additive == 1) { 17 | color.a = 0; 18 | } 19 | } -------------------------------------------------------------------------------- /replay/replayparser.go: -------------------------------------------------------------------------------- 1 | package replay 2 | 3 | import ( 4 | "github.com/Mempler/rplpa" 5 | "io/ioutil" 6 | ) 7 | 8 | func ExtractReplay(name string) *rplpa.Replay { 9 | buf, err := ioutil.ReadFile(name) 10 | if err != nil { 11 | panic(err) 12 | } 13 | replay, err := rplpa.ParseReplay(buf) 14 | if err != nil { 15 | panic(err) 16 | } 17 | return replay 18 | } 19 | -------------------------------------------------------------------------------- /assets/shaders/combine.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2DArray tex; 4 | uniform sampler2DArray tex2; 5 | uniform float power; 6 | 7 | in vec2 tex_coord; 8 | out vec4 color; 9 | 10 | void main() 11 | { 12 | vec4 in_color = texture(tex, vec3(tex_coord, 0)); 13 | vec4 in_color2 = texture(tex2, vec3(tex_coord, 0)); 14 | color = in_color + in_color2 * power; 15 | } -------------------------------------------------------------------------------- /assets/shaders/cursortrail.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2DArray tex; 4 | uniform vec4 col_tint; 5 | uniform float points; 6 | 7 | in vec2 tex_coord; 8 | in float index; 9 | 10 | out vec4 color; 11 | 12 | void main() { 13 | vec4 in_color = texture(tex, vec3(tex_coord, 0)); 14 | color = in_color * col_tint * vec4(1, 1, 1, 1-smoothstep(points / 3, points, index)); 15 | } -------------------------------------------------------------------------------- /assets/shaders/sprite.vsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 in_position; 4 | in vec3 in_tex_coord; 5 | in vec4 in_color; 6 | in float in_additive; 7 | 8 | uniform mat4 proj; 9 | 10 | out vec4 col_tint; 11 | out vec3 tex_coord; 12 | out float additive; 13 | void main() 14 | { 15 | gl_Position = proj * vec4(in_position, 1); 16 | tex_coord = in_tex_coord; 17 | col_tint = in_color; 18 | additive = in_additive; 19 | } -------------------------------------------------------------------------------- /bmath/math.go: -------------------------------------------------------------------------------- 1 | package bmath 2 | 3 | import "math" 4 | 5 | func AngleBetween(centre, p1, p2 Vector2d) float64 { 6 | a := centre.Dst(p1) 7 | b := centre.Dst(p2) 8 | c := p1.Dst(p2) 9 | return math.Acos((a*a + b*b - c*c) / (2 * a * b)) 10 | } 11 | 12 | func Xor(v1 bool, v2 bool) bool { 13 | return (v1 && v2) != (v1 || v2) 14 | } 15 | 16 | func Fmod(a float64, b float64) float64 { 17 | return a - float64(int(a/b))*b 18 | } 19 | -------------------------------------------------------------------------------- /error.err: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ReplayIndex": 1, 4 | "ObjectIndex": 497, 5 | "Result": 0, 6 | "IsBreak": false, 7 | "MaxComboOffset": 1, 8 | "NowComboOffset": 1 9 | }, 10 | { 11 | "ReplayIndex": 1, 12 | "ObjectIndex": 1043, 13 | "Result": 0, 14 | "IsBreak": false, 15 | "MaxComboOffset": 1, 16 | "NowComboOffset": 1 17 | } 18 | ] -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /assets/shaders/brightfilter.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2DArray tex; 4 | uniform float threshold; 5 | 6 | in vec2 tex_coord; 7 | out vec4 color; 8 | 9 | void main() 10 | { 11 | vec4 in_color = texture(tex, vec3(tex_coord, 0)); 12 | float brightness = dot(in_color.rgb, vec3(0.2126, 0.7152, 0.0722)); 13 | 14 | if (brightness > threshold) { 15 | color = in_color; 16 | } else { 17 | color = vec4(0.0); 18 | } 19 | } -------------------------------------------------------------------------------- /audio/system.go: -------------------------------------------------------------------------------- 1 | package audio 2 | 3 | /* 4 | #cgo CFLAGS: -I/usr/include -I. 5 | #cgo LDFLAGS: -L${SRCDIR}/../ -L/usr/lib -Wl,-rpath=\$ORIGIN -lbass -lbass_fx 6 | #include "bass.h" 7 | #include "bass_fx.h" 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "log" 13 | ) 14 | 15 | func Init() { 16 | if C.BASS_Init(C.int(-1), C.DWORD(44100), C.DWORD(0), nil, nil) != 0 { 17 | log.Println("BASS Initialized!") 18 | } else { 19 | log.Println("BASS error", int(C.BASS_ErrorGetCode())) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/shaders/cursortrail.vsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 in_position; 4 | in vec3 in_mid; 5 | in vec2 in_tex_coord; 6 | in float in_index; 7 | 8 | uniform mat4 proj; 9 | uniform float scale; 10 | uniform float points; 11 | uniform float endScale; 12 | 13 | out vec2 tex_coord; 14 | out float index; 15 | 16 | void main() { 17 | gl_Position = proj * vec4((in_position - in_mid) * scale * (endScale + (1.0 - endScale) * (points-1-in_index) / points) + in_mid, 1); 18 | tex_coord = in_tex_coord; 19 | index = in_index; 20 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "danser/configui" 5 | "flag" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | stdinLog := flag.Bool("stdinLog", false, "") 12 | noGUI := flag.Bool("noGUI", false, "") 13 | 14 | flag.Parse() 15 | 16 | if !*stdinLog { 17 | // 设置log文件 18 | file, _ := os.OpenFile("vsplayer.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 19 | defer file.Close() 20 | log.SetOutput(file) 21 | log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime) 22 | } 23 | 24 | configui.UImain(*noGUI) 25 | } 26 | -------------------------------------------------------------------------------- /dance/schedulers/scheduler.go: -------------------------------------------------------------------------------- 1 | package schedulers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/render" 7 | ) 8 | 9 | type Scheduler interface { 10 | Init(objects []objects.BaseObject, cursor *render.Cursor) 11 | //Update(time int64) 12 | 13 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 14 | // 添加更多参数 15 | Update(time int64, position bmath.Vector2d) 16 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # ========================= 22 | # Operating System Files 23 | # ========================= 24 | 25 | .idea 26 | *.osr 27 | song/ 28 | screenshots/ 29 | replays*/ 30 | cache/ 31 | *.exe 32 | skin/ 33 | danser.log 34 | vsplayer.log 35 | *.prof 36 | danser.db 37 | -------------------------------------------------------------------------------- /wheelmodified.md: -------------------------------------------------------------------------------- 1 | - 新版pp算法 oppai的更新 2 | [https://github.com/flesnuk/oppai5/pull/2](https://github.com/flesnuk/oppai5/pull/2) 已merge 3 | - oppai算法转盘开头计算的bug 4 | [https://github.com/flesnuk/oppai5/pull/3](https://github.com/flesnuk/oppai5/pull/3) 已merge 5 | - replay 按键读取错误 6 | [https://github.com/Mempler/rplpa/issues/2](https://github.com/Mempler/rplpa/issues/2) 已merge 7 | - 如何让 go walk 窗口大小固定(SetFixedSize) 8 | [https://studygolang.com/topics/1150](https://studygolang.com/topics/1150) 9 | - 2021 年 2 月份,新的 pp 算法修改 10 | [https://github.com/flesnuk/oppai5/pull/6](https://github.com/flesnuk/oppai5/pull/6) 已merge -------------------------------------------------------------------------------- /bmath/curves/linear.go: -------------------------------------------------------------------------------- 1 | package curves 2 | 3 | import math2 "danser/bmath" 4 | 5 | type Linear struct { 6 | point1, point2 math2.Vector2d 7 | } 8 | 9 | func NewLinear(pt1, pt2 math2.Vector2d) Linear { 10 | return Linear{pt1, pt2} 11 | } 12 | 13 | func (ln Linear) PointAt(t float64) math2.Vector2d { 14 | return ln.point2.Sub(ln.point1).Scl(t).Add(ln.point1) 15 | } 16 | 17 | func (ln Linear) GetStartAngle() float64 { 18 | return ln.point1.AngleRV(ln.point2) 19 | } 20 | 21 | func (ln Linear) GetEndAngle() float64 { 22 | return ln.point2.AngleRV(ln.point1) 23 | } 24 | 25 | func (ln Linear) GetLength() float64 { 26 | return ln.point1.Dst(ln.point2) 27 | } 28 | -------------------------------------------------------------------------------- /settings/dance.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | type bezier struct { 4 | Aggressiveness, SliderAggressiveness float64 5 | } 6 | 7 | type flower struct { 8 | UseNewStyle bool 9 | AngleOffset float64 10 | DistanceMult float64 11 | StreamTrigger int64 12 | StreamAngleOffset float64 13 | LongJump int64 14 | LongJumpMult float64 15 | LongJumpOnEqualPos bool 16 | } 17 | 18 | type circular struct { 19 | RadiusMultiplier float64 20 | StreamTrigger int64 21 | } 22 | 23 | type dance struct { 24 | SliderDance bool 25 | TAGSliderDance bool 26 | Bezier *bezier 27 | Flower *flower 28 | HalfCircle *circular 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About this project ## 2 | Learn more in [https://space.bilibili.com/4436914/channel/detail?cid=68983](https://space.bilibili.com/4436914/channel/detail?cid=68983) 3 | 4 | ## Build ## 5 | 6 | - Use ```go get``` to get the missing wheels. 7 | - Look at [wheelmodified.md](https://github.com/wasupandceacar/osu-vs-player/blob/master/wheelmodified.md) to see my code modifications for used wheels. For some modifications I've merged it to original repo, while for others you should learn them and modify the wheels, otherwise NO hope for build success. 8 | - Change the code folder name to ```danser``` —— because I import module using ```danser/xxx```. 9 | - Look at [build.md](https://github.com/wasupandceacar/osu-vs-player/blob/master/build.md) to learn how to build. 10 | - You may encounter rsrc build error, but the built exe will still run. -------------------------------------------------------------------------------- /utils/fps.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "log" 4 | 5 | type FPSCounter struct { 6 | samples []float64 7 | index int 8 | FPS float64 9 | sum float64 10 | log bool 11 | } 12 | 13 | func NewFPSCounter(samples int, log bool) *FPSCounter { 14 | return &FPSCounter{make([]float64, samples), -1, 0, 0.0, log} 15 | } 16 | 17 | func (prof *FPSCounter) PutSample(fps float64) { 18 | prof.index++ 19 | if prof.index >= len(prof.samples) { 20 | prof.index = 0 21 | } 22 | prof.samples[prof.index] = fps 23 | prof.sum += 1.0 / fps 24 | if prof.sum >= 1.0 && prof.log { 25 | log.Println("FPS:", prof.GetFPS()) 26 | prof.sum = 0.0 27 | } 28 | } 29 | 30 | func (prof *FPSCounter) GetFPS() float64 { 31 | sum := 0.0 32 | for _, g := range prof.samples { 33 | sum += g 34 | } 35 | return sum / float64(len(prof.samples)) 36 | } 37 | -------------------------------------------------------------------------------- /dance/schedulers/sliderprocessor.go: -------------------------------------------------------------------------------- 1 | package schedulers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "sort" 6 | ) 7 | 8 | func objectPreProcess(hitobject objects.BaseObject, sliderDance bool) ([]objects.BaseObject, bool) { 9 | if s1, ok1 := hitobject.(*objects.Slider); ok1 && sliderDance { 10 | return s1.GetAsDummyCircles(), true 11 | } 12 | return nil, false 13 | } 14 | 15 | func PreprocessQueue(index int, queue []objects.BaseObject, sliderDance bool) []objects.BaseObject { 16 | if arr, ok := objectPreProcess(queue[index], sliderDance); ok { 17 | if index < len(queue)-1 { 18 | queue1 := append(queue[:index], append(arr, queue[index+1:]...)...) 19 | sort.Slice(queue1, func(i, j int) bool { return queue1[i].GetBasicData().StartTime < queue1[j].GetBasicData().StartTime }) 20 | return queue1 21 | } else { 22 | return append(queue[:index], arr...) 23 | } 24 | } 25 | return queue 26 | } 27 | -------------------------------------------------------------------------------- /storyboard/enums.go: -------------------------------------------------------------------------------- 1 | package storyboard 2 | 3 | import "danser/bmath" 4 | 5 | var Origin = map[string]bmath.Vector2d{ 6 | "0": bmath.NewVec2d(-1, -1), 7 | "TopLeft": bmath.NewVec2d(-1, -1), 8 | 9 | "1": bmath.NewVec2d(0, 0), 10 | "Centre": bmath.NewVec2d(0, 0), 11 | 12 | "2": bmath.NewVec2d(-1, 0), 13 | "CentreLeft": bmath.NewVec2d(-1, 0), 14 | 15 | "3": bmath.NewVec2d(1, -1), 16 | "TopRight": bmath.NewVec2d(1, -1), 17 | 18 | "4": bmath.NewVec2d(0, 1), 19 | "BottomCentre": bmath.NewVec2d(0, 1), 20 | 21 | "5": bmath.NewVec2d(0, -1), 22 | "TopCentre": bmath.NewVec2d(0, -1), 23 | 24 | "7": bmath.NewVec2d(1, 0), 25 | "CentreRight": bmath.NewVec2d(1, 0), 26 | 27 | "8": bmath.NewVec2d(-1, 1), 28 | "BottomLeft": bmath.NewVec2d(-1, 1), 29 | 30 | "9": bmath.NewVec2d(1, 1), 31 | "BottomRight": bmath.NewVec2d(1, 1), 32 | } 33 | -------------------------------------------------------------------------------- /danser.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | PerMonitorV2 12 | 13 | 14 | true 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /hitjudgetest/Genryuu Kaiko.md: -------------------------------------------------------------------------------- 1 | | player | 真实(100,50,miss) | Tail+头 | Tail+中 | Tail+尾 | 无Tail+头 | 无Tail+中 | 无Tail+尾 | 2 | | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | 3 | | Mathi | 5,0,0 | 4,0,0 | 5,0,0 | 5,0,0 | 3,0,0 | 4,0,0 | 4,0,0 4 | | rustbell | 9,3,2 | 10,3,2 | 9,3,2 | 9,3,2 | 7,3,2 | 7,3,2 | 8,3,2 5 | | idke | 24,1,3 | 27,1,3 | 27,1,3 | 27,1,3 | 20,1,3 | 22,1,3 | 22,1,3 6 | | firebat92 | 9,0,1 | 6,0,1 | 7,0,1 | 7,0,1 | 6,0,1 | 7,0,1 | 7,0,1 7 | | HappyStick | 21,0,6 | 18,0,7 | 18,0,7 | 18,0,7 | 18,0,7 | 18,0,7 | 18,0,7 8 | | BeasttrollMC | 26,0,7 | 23,0,8 | 24,0,8 | 24,0,8 | 22,0,8 | 23,0,8 | 23,0,8 9 | | Toy | 8,2,4 | 4,2,7 | 5,2,7 | 5,2,7 | 4,2,7 | 5,2,7 | 5,2,7 10 | | ThePooN | 25,0,17 | 23,0,19 | 23,0,19 | 23,0,19 | 22,0,19 | 22,0,19 | 22,0,19 11 | | WubWoofWolf | 18,0,4 | 15,0,5 | 13,0,5 | 13,0,5 | 12,0,5 | 11,0,5 | 11,0,5 12 | | _index | 4,0,4 | 3,0,2 | 2,0,2 | 2,0,2 | 2,0,2 | 2,0,2 | 2,0,2 13 | 14 | 结论:暂时使用判定Tail+中?这TM不是完全判不准么,我佛了#呲牙 -------------------------------------------------------------------------------- /configui/varassign.go: -------------------------------------------------------------------------------- 1 | package configui 2 | 3 | import ( 4 | "github.com/lxn/walk" 5 | "strconv" 6 | ) 7 | 8 | func assign(ivarpt interface{}, ivar interface{}, component interface{}) { 9 | switch ivar.(type) { 10 | case bool: 11 | *ivarpt.(*bool) = component.(*walk.CheckBox).Checked() 12 | break 13 | case string: 14 | *ivarpt.(*string) = component.(*walk.LineEdit).Text() 15 | break 16 | case int: 17 | intvar, err := strconv.Atoi(component.(*walk.LineEdit).Text()) 18 | if err != nil { 19 | panic(err) 20 | } 21 | *ivarpt.(*int) = intvar 22 | break 23 | case int64: 24 | intvar, err := strconv.Atoi(component.(*walk.LineEdit).Text()) 25 | if err != nil { 26 | panic(err) 27 | } 28 | *ivarpt.(*int64) = int64(intvar) 29 | break 30 | case float64: 31 | float64var, err := strconv.ParseFloat(component.(*walk.LineEdit).Text(), 64) 32 | if err != nil { 33 | panic(err) 34 | } 35 | *ivarpt.(*float64) = float64var 36 | break 37 | } 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /beatmap/objects/pause.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "danser/bmath" 5 | . "danser/osuconst" 6 | "strconv" 7 | ) 8 | 9 | type Pause struct { 10 | objData *basicData 11 | } 12 | 13 | func NewPause(data []string) *Pause { 14 | pause := &Pause{} 15 | pause.objData = &basicData{} 16 | pause.objData.StartTime, _ = strconv.ParseInt(data[1], 10, 64) 17 | pause.objData.EndTime, _ = strconv.ParseInt(data[2], 10, 64) 18 | pause.objData.StartPos = bmath.NewVec2d(PLAYFIELD_WIDTH/2, PLAYFIELD_HEIGHT/2) 19 | pause.objData.EndPos = pause.objData.StartPos 20 | pause.objData.Number = -1 21 | return pause 22 | } 23 | 24 | func (self Pause) GetBasicData() *basicData { 25 | return self.objData 26 | } 27 | 28 | func (self *Pause) SetDifficulty(preempt, fadeIn float64) { 29 | 30 | } 31 | 32 | func (self *Pause) Update(time int64) bool { 33 | return time >= self.objData.EndTime 34 | } 35 | 36 | func (self *Pause) GetPosition() bmath.Vector2d { 37 | return self.objData.StartPos 38 | } 39 | 40 | func (self *Pause) GetObjectNumber() int64 { 41 | return -1 42 | } 43 | -------------------------------------------------------------------------------- /storyboard/loop.go: -------------------------------------------------------------------------------- 1 | package storyboard 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type Loop struct { 8 | start, end, repeats int64 9 | transformations *Transformations 10 | } 11 | 12 | func NewLoop(data []string, object Object) *Loop { 13 | loop := &Loop{transformations: NewTransformations(object)} 14 | loop.start, _ = strconv.ParseInt(data[1], 10, 64) 15 | loop.repeats, _ = strconv.ParseInt(data[2], 10, 64) 16 | return loop 17 | } 18 | 19 | func (loop *Loop) Add(command *Command) { 20 | loop.transformations.Add(command) 21 | loop.end = loop.start + loop.transformations.startTime + loop.repeats*(loop.transformations.endTime-loop.transformations.startTime) 22 | } 23 | 24 | func (loop *Loop) Update(time int64) { 25 | sTime := int64(0) 26 | if time-loop.start > loop.transformations.endTime { 27 | sTime = loop.transformations.startTime 28 | } 29 | 30 | local := (time - loop.start - sTime) % (loop.transformations.endTime - sTime) 31 | if time >= loop.end { 32 | local = loop.transformations.endTime - sTime 33 | } 34 | 35 | loop.transformations.Update(sTime + local) 36 | } 37 | -------------------------------------------------------------------------------- /hitjudge/errorio.go: -------------------------------------------------------------------------------- 1 | package hitjudge 2 | 3 | import ( 4 | "danser/settings" 5 | "encoding/json" 6 | "io/ioutil" 7 | ) 8 | 9 | func SaveError(errors []Error) { 10 | oerr := ioutil.WriteFile(settings.VSplayer.ErrorFix.ErrorFixFile, []byte(getErrorCache(errors)), 0666) 11 | if oerr != nil { 12 | panic(oerr) 13 | } 14 | } 15 | 16 | func ReadError() []Error { 17 | oread, _ := ioutil.ReadFile(settings.VSplayer.ErrorFix.ErrorFixFile) 18 | return setErrorCache(oread) 19 | } 20 | 21 | func getErrorCache(errors []Error) string { 22 | data, err := json.MarshalIndent(errors, "", " ") 23 | if err != nil { 24 | panic(err) 25 | } 26 | return string(data) 27 | } 28 | 29 | func setErrorCache(r []byte) []Error { 30 | var errors []Error 31 | if err := json.Unmarshal(r, &errors); err != nil { 32 | panic(err) 33 | } 34 | return errors 35 | } 36 | 37 | // 过滤Error 38 | func FilterError(replayindex int, errors []Error) []Error { 39 | reerror := []Error{} 40 | for _, err := range errors { 41 | if err.ReplayIndex == replayindex { 42 | reerror = append(reerror, err) 43 | } 44 | } 45 | return reerror 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sebastian Krajewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /utils/screenshot.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/go-gl/gl/v3.3-core/gl" 5 | "github.com/go-gl/glfw/v3.2/glfw" 6 | "image" 7 | "image/png" 8 | "os" 9 | "runtime" 10 | "strconv" 11 | "time" 12 | "unsafe" 13 | ) 14 | 15 | func MakeScreenshot(win glfw.Window) { 16 | w, h := win.GetFramebufferSize() 17 | buff := make([]uint8, w*h*4) 18 | gl.PixelStorei(gl.PACK_ALIGNMENT, int32(1)) 19 | gl.ReadPixels(0, 0, int32(w), int32(h), gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&buff[0])) 20 | 21 | go func() { 22 | img := image.NewNRGBA(image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{w, h}}) 23 | buff1 := make([]uint8, w*h*4) 24 | for i := h - 1; i >= 0; i-- { 25 | for j := 0; j < w*4; j++ { 26 | if (j+1)%4 == 0 { 27 | buff1[(h-1-i)*w*4+j] = 0xFF 28 | } else { 29 | buff1[(h-1-i)*w*4+j] = buff[i*w*4+j] 30 | } 31 | } 32 | } 33 | runtime.KeepAlive(buff) 34 | img.Pix = buff1 35 | os.Mkdir("screenshots", 0644) 36 | f, _ := os.OpenFile("screenshots/"+strconv.FormatInt(time.Now().UnixNano(), 10)+".png", os.O_WRONLY|os.O_CREATE, 0644) 37 | defer f.Close() 38 | png.Encode(f, img) 39 | 40 | }() 41 | } 42 | -------------------------------------------------------------------------------- /assets/shaders/blur.fsh: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | #define INVSQ2PI 0.398942 4 | 5 | uniform sampler2DArray tex; 6 | 7 | uniform vec2 kernelSize; 8 | uniform vec2 direction; 9 | uniform vec2 sigma; 10 | uniform vec2 size; 11 | 12 | in vec2 tex_coord; 13 | 14 | out vec4 color; 15 | 16 | float gauss(float x, float sigma) { 17 | return INVSQ2PI * exp(-0.5 * x * x / (sigma * sigma)) / sigma; 18 | } 19 | 20 | void main() { 21 | float tSigma = length(direction*sigma); 22 | 23 | float gs = gauss(0, tSigma); 24 | 25 | vec4 inc = texture(tex, vec3(tex_coord, 0)); 26 | 27 | color = inc*gs; 28 | 29 | float totalGauss = gs; 30 | 31 | int kSize = int(length(kernelSize*direction)); 32 | 33 | for (int i = 2; i < 200; i+=2) { 34 | float fac = float(i) - 0.5; 35 | 36 | gs = gauss(i, tSigma)*2.0; 37 | totalGauss += 2.0*gs; 38 | 39 | vec2 mv = fac * direction / size; 40 | 41 | color += texture(tex, vec3(tex_coord + mv, 0)) * gs; 42 | color += texture(tex, vec3(tex_coord - mv, 0)) * gs; 43 | 44 | if (i >= kSize) { 45 | break; 46 | } 47 | 48 | } 49 | 50 | color /= totalGauss; 51 | } -------------------------------------------------------------------------------- /utils/wintime.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | ) 7 | 8 | /* 9 | #ifdef _WIN32 10 | #include 11 | 12 | int started = 0; 13 | double PCFreq = 0.0; 14 | __int64 CounterStart = 0; 15 | 16 | void startCounter() { 17 | LARGE_INTEGER li; 18 | QueryPerformanceFrequency(&li); 19 | 20 | PCFreq = (double)(li.QuadPart) / 1000000000.0; 21 | 22 | QueryPerformanceCounter(&li); 23 | CounterStart = li.QuadPart; 24 | } 25 | 26 | double getTime() { 27 | if (!started) { 28 | startCounter(); 29 | started = 1; 30 | } 31 | LARGE_INTEGER li; 32 | QueryPerformanceCounter(&li); 33 | return (double)(li.QuadPart-CounterStart)/PCFreq; 34 | } 35 | 36 | void resetTime() { 37 | started = 0; 38 | PCFreq = 0.0; 39 | CounterStart = 0; 40 | } 41 | 42 | long long getNanoTime() { 43 | return (long long)(getTime()); 44 | } 45 | #else 46 | long long getNanoTime() { 47 | return 0; 48 | } 49 | #endif 50 | */ 51 | import "C" 52 | 53 | func GetNanoTime() int64 { 54 | if runtime.GOOS == "windows" { 55 | return int64(C.getNanoTime()) 56 | } 57 | return time.Now().UnixNano() 58 | } 59 | 60 | func ResetTime() { 61 | if runtime.GOOS == "windows" { 62 | C.resetTime() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /dance/movers/linear.go: -------------------------------------------------------------------------------- 1 | package movers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/bmath/curves" 7 | "math" 8 | ) 9 | 10 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // 直线移动,auto 12 | 13 | type LinearMover struct { 14 | bz curves.Bezier 15 | beginTime, endTime int64 16 | } 17 | 18 | func NewLinearMover() MultiPointMover { 19 | return &LinearMover{} 20 | } 21 | 22 | func (bm *LinearMover) Reset() { 23 | 24 | } 25 | 26 | func (bm *LinearMover) SetObjects(objs []objects.BaseObject) { 27 | end, start := objs[0], objs[1] 28 | endPos := end.GetBasicData().EndPos 29 | endTime := end.GetBasicData().EndTime 30 | startPos := start.GetBasicData().StartPos 31 | startTime := start.GetBasicData().StartTime 32 | 33 | bm.bz = curves.NewBezier([]bmath.Vector2d{endPos, startPos}) 34 | bm.endTime = endTime 35 | bm.beginTime = startTime 36 | } 37 | 38 | func (bm LinearMover) Update(time int64) bmath.Vector2d { 39 | t := float64(time-bm.endTime) / float64(bm.beginTime-bm.endTime) 40 | t = math.Max(0.0, math.Min(1.0, t)) 41 | return bm.bz.NPointAt(math.Sin(t * math.Pi / 2)) 42 | } 43 | 44 | func (bm *LinearMover) GetEndTime() int64 { 45 | return bm.beginTime 46 | } 47 | -------------------------------------------------------------------------------- /score/ppcalculater.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import ( 4 | "danser/settings" 5 | "github.com/flesnuk/oppai5" 6 | "math" 7 | "os" 8 | ) 9 | 10 | // 部分载入map 11 | func LoadMapbyNum(filename string, objnum int) *oppai.Map { 12 | f, _ := os.Open(filename) 13 | return oppai.ParsebyNum(f, objnum) 14 | } 15 | 16 | // 部分载入map,并计算难度信息 17 | func CalculateDiffbyNum(filename string, objnum int, mods uint32) oppai.PP { 18 | beatmap := LoadMapbyNum(filename, objnum) 19 | return oppai.PPInfo(beatmap, &oppai.Parameters{ 20 | Combo: uint16(beatmap.MaxCombo), 21 | Mods: mods, 22 | N300: uint16(objnum), 23 | N100: 0, 24 | N50: 0, 25 | Misses: 0, 26 | }) 27 | } 28 | 29 | // 计算每帧实时数值(PP、UR) 30 | func CalculateRealtimeValue(firstvalue float64, secondvalue float64, firsttime int64, secondtime int64, nowtime float64) (realvalue float64) { 31 | deltavalue := secondvalue - firstvalue 32 | deltatime := math.Min(float64(secondtime-firsttime), settings.VSplayer.PlayerInfoUI.RealTimePPGap) 33 | realvalue = firstvalue + deltavalue*math.Max(math.Min(math.Min(nowtime-float64(firsttime+settings.VSplayer.PlayerFieldUI.HitFadeTime), settings.VSplayer.PlayerInfoUI.RealTimePPGap)/deltatime, 1), 0) 34 | if math.IsNaN(realvalue) { 35 | realvalue = 0.0 36 | } 37 | return realvalue 38 | } 39 | -------------------------------------------------------------------------------- /replay/replayreader.go: -------------------------------------------------------------------------------- 1 | package replay 2 | 3 | import ( 4 | "danser/settings" 5 | "io/ioutil" 6 | "log" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func GetOsrFiles() (files []string, err error) { 12 | dir, err := ioutil.ReadDir(settings.VSplayer.ReplayandCache.ReplayDir) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | if settings.VSplayer.PlayerInfo.SpecifiedPlayers { 18 | specifiedplayers := strings.Split(settings.VSplayer.PlayerInfo.SpecifiedLine, ",") 19 | specifiedplayerindex := []int{} 20 | for _, player := range specifiedplayers { 21 | pl, _ := strconv.Atoi(player) 22 | if pl <= 0 { 23 | log.Panic("指定player的字符串有误,请重新检查设定") 24 | } else { 25 | specifiedplayerindex = append(specifiedplayerindex, pl) 26 | } 27 | } 28 | for i, fi := range dir { 29 | ok := strings.HasSuffix(fi.Name(), ".osr") 30 | if ok { 31 | for _, pl := range specifiedplayerindex { 32 | if i+1 == pl { 33 | files = append(files, settings.VSplayer.ReplayandCache.ReplayDir+fi.Name()) 34 | } 35 | } 36 | } 37 | } 38 | } else { 39 | for _, fi := range dir { 40 | ok := strings.HasSuffix(fi.Name(), ".osr") 41 | if ok { 42 | files = append(files, settings.VSplayer.ReplayandCache.ReplayDir+fi.Name()) 43 | } 44 | } 45 | } 46 | return files, nil 47 | } 48 | -------------------------------------------------------------------------------- /audio/sample.go: -------------------------------------------------------------------------------- 1 | package audio 2 | 3 | /* 4 | #include "bass.h" 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "danser/settings" 10 | "os" 11 | "unsafe" 12 | ) 13 | 14 | type Sample struct { 15 | channel C.DWORD 16 | } 17 | 18 | func NewSample(path string) *Sample { 19 | f, err := os.Open(path) 20 | 21 | if os.IsNotExist(err) { 22 | return nil 23 | } 24 | f.Close() 25 | 26 | player := &Sample{} 27 | han := C.BASS_SampleLoad(0, unsafe.Pointer(C.CString(path)), 0, 0, 32, 0) 28 | player.channel = han 29 | return player 30 | } 31 | 32 | func (wv *Sample) Play() { 33 | channel := C.BASS_SampleGetChannel(C.DWORD(wv.channel), 0) 34 | C.BASS_ChannelSetAttribute(channel, C.BASS_ATTRIB_VOL, C.float(settings.Audio.GeneralVolume*settings.Audio.SampleVolume)) 35 | C.BASS_ChannelPlay(channel, 1) 36 | } 37 | 38 | func (wv *Sample) PlayV(volume float64) { 39 | channel := C.BASS_SampleGetChannel(C.DWORD(wv.channel), 0) 40 | C.BASS_ChannelSetAttribute(channel, C.BASS_ATTRIB_VOL, C.float(volume)) 41 | C.BASS_ChannelPlay(channel, 1) 42 | } 43 | 44 | func (wv *Sample) PlayRV(volume float64) { 45 | channel := C.BASS_SampleGetChannel(C.DWORD(wv.channel), 0) 46 | C.BASS_ChannelSetAttribute(channel, C.BASS_ATTRIB_VOL, C.float(settings.Audio.GeneralVolume*settings.Audio.SampleVolume*volume)) 47 | C.BASS_ChannelPlay(channel, 1) 48 | } 49 | -------------------------------------------------------------------------------- /dance/movers/axisaligned.go: -------------------------------------------------------------------------------- 1 | package movers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/bmath/sliders" 7 | "math" 8 | ) 9 | 10 | type AxisMover struct { 11 | bz sliders.SliderAlgo 12 | beginTime, endTime int64 13 | } 14 | 15 | func NewAxisMover() MultiPointMover { 16 | return &AxisMover{} 17 | } 18 | 19 | func (bm *AxisMover) Reset() { 20 | 21 | } 22 | 23 | func (bm *AxisMover) SetObjects(objs []objects.BaseObject) { 24 | end, start := objs[0], objs[1] 25 | endPos := end.GetBasicData().EndPos 26 | endTime := end.GetBasicData().EndTime 27 | startPos := start.GetBasicData().StartPos 28 | startTime := start.GetBasicData().StartTime 29 | 30 | var midP bmath.Vector2d 31 | 32 | if math.Abs(startPos.Sub(endPos).X) < math.Abs(startPos.Sub(endPos).X) { 33 | midP = bmath.NewVec2d(endPos.X, startPos.Y) 34 | } else { 35 | midP = bmath.NewVec2d(startPos.X, endPos.Y) 36 | } 37 | 38 | bm.bz = sliders.NewSliderAlgo("L", []bmath.Vector2d{endPos, midP, startPos}, endPos.Dst(midP)+midP.Dst(startPos)) 39 | bm.endTime = endTime 40 | bm.beginTime = startTime 41 | } 42 | 43 | func (bm AxisMover) Update(time int64) bmath.Vector2d { 44 | t := float64(time-bm.endTime) / float64(bm.beginTime-bm.endTime) 45 | tr := math.Max(0.0, math.Min(1.0, math.Sin(t*math.Pi/2))) 46 | return bm.bz.PointAt(tr) 47 | } 48 | 49 | func (bm *AxisMover) GetEndTime() int64 { 50 | return bm.beginTime 51 | } 52 | -------------------------------------------------------------------------------- /beatmap/loader.go: -------------------------------------------------------------------------------- 1 | package beatmap 2 | 3 | import ( 4 | "danser/settings" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | func LoadBeatmaps() []*BeatMap { 13 | searchDir := settings.General.OsuSongsDir 14 | 15 | log.Println("Loading beatmaps...") 16 | 17 | var candidates []string 18 | var beatmaps []*BeatMap 19 | 20 | _, err := os.Open(searchDir) 21 | if os.IsNotExist(err) { 22 | log.Println(searchDir + " does not exist!") 23 | return beatmaps 24 | } 25 | 26 | filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error { 27 | if strings.HasSuffix(f.Name(), ".osu") { 28 | candidates = append(candidates, path) 29 | } 30 | return nil 31 | }) 32 | 33 | channel := make(chan string, 20) 34 | channelB := make(chan *BeatMap, len(candidates)) 35 | var wg sync.WaitGroup 36 | 37 | for i := 0; i < 20; i++ { 38 | wg.Add(1) 39 | go func() { 40 | defer wg.Done() 41 | for { 42 | path, ok := <-channel 43 | if !ok { 44 | break 45 | } 46 | if f, err := os.Open(path); err == nil { 47 | if bMap := ParseBeatMap(f); bMap != nil { 48 | channelB <- bMap 49 | } 50 | f.Close() 51 | } 52 | } 53 | }() 54 | } 55 | 56 | for _, path := range candidates { 57 | channel <- path 58 | } 59 | 60 | close(channel) 61 | wg.Wait() 62 | 63 | for len(channelB) > 0 { 64 | beatmap := <-channelB 65 | beatmaps = append(beatmaps, beatmap) 66 | } 67 | return beatmaps 68 | } 69 | -------------------------------------------------------------------------------- /osuconst/osuconst.go: -------------------------------------------------------------------------------- 1 | package osuconst 2 | 3 | // osu判定区域相对大小 4 | const PLAYFIELD_WIDTH = 512.0 5 | const PLAYFIELD_HEIGHT = 384.0 6 | 7 | // 原始皮肤基准大小 8 | const DEFAULT_SKIN_SIZE = 667.0 9 | 10 | // 各个object的判定大小倍数 11 | const CIRCLE_JUDGE_SCALE = 1.0 12 | const TICK_JUDGE_SCALE = 2.4 13 | 14 | // 初始acc、pp、ur 15 | const DEFAULT_ACC = 100.0 16 | const DEFAULT_PP = 0.0 17 | const DEFAULT_UR = 0.0 18 | 19 | // MOD参数 20 | const MOD_NF = 1 21 | const MOD_EZ = 2 22 | const MOD_TD = 4 23 | const MOD_HD = 8 24 | const MOD_HR = 16 25 | const MOD_SD = 32 26 | const MOD_DT = 64 27 | const MOD_HT = 256 28 | const MOD_NC = 512 29 | const MOD_FL = 1024 30 | const MOD_SO = 4096 31 | const MOD_PF = 16384 32 | 33 | // OD参数 34 | const OD_300_BASE = 79 35 | const OD_100_BASE = 139 36 | const OD_50_BASE = 199 37 | const OD_MISS_BASE = 399 38 | 39 | const OD_300_MULT = 6 40 | const OD_100_MULT = 8 41 | const OD_50_MULT = 10 42 | 43 | const OD_PRECISION_FIX = 0.5 44 | 45 | const OD_HR_HENSE = 1.4 46 | const OD_EZ_HENSE = 0.5 47 | 48 | const OD_MAX = 10.0 49 | 50 | // CS 参数 51 | const CS_HR_HENSE = 1.3 52 | const CS_EZ_HENSE = 0.5 53 | 54 | const CS_MAX = 10.0 55 | 56 | const NO_USE_CS_OFFSET = -1.0 57 | 58 | // AR参数 59 | const AR_HR_HENSE = 1.4 60 | const AR_EZ_HENSE = 0.5 61 | 62 | const AR_MAX = 10.0 63 | 64 | // HD参数 65 | const FADE_IN_DURATION_MULTIPLIER = 0.4 66 | const FADE_OUT_DURATION_MULTIPLIER = 0.3 67 | 68 | // stackleniency参数 69 | const BASE_STACK_OFFSET = -6.4 70 | 71 | // replay时间结束默认时间 72 | const REPLAY_END_TIME = -12345 73 | -------------------------------------------------------------------------------- /dance/movers/halfcircle.go: -------------------------------------------------------------------------------- 1 | package movers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/bmath/curves" 7 | "danser/settings" 8 | "math" 9 | ) 10 | 11 | type HalfCircleMover struct { 12 | ca curves.Curve 13 | startTime, endTime int64 14 | invert float64 15 | } 16 | 17 | func NewHalfCircleMover() MultiPointMover { 18 | return &HalfCircleMover{invert: -1} 19 | } 20 | 21 | func (bm *HalfCircleMover) Reset() { 22 | bm.invert = -1 23 | } 24 | 25 | func (bm *HalfCircleMover) SetObjects(objs []objects.BaseObject) { 26 | end := objs[0] 27 | start := objs[1] 28 | 29 | endPos := end.GetBasicData().EndPos 30 | startPos := start.GetBasicData().StartPos 31 | bm.endTime = end.GetBasicData().EndTime 32 | bm.startTime = start.GetBasicData().StartTime 33 | 34 | if settings.Dance.HalfCircle.StreamTrigger < 0 || (bm.startTime-bm.endTime) < settings.Dance.HalfCircle.StreamTrigger { 35 | bm.invert = -1 * bm.invert 36 | } 37 | 38 | if endPos == startPos { 39 | bm.ca = curves.NewLinear(endPos, startPos) 40 | return 41 | } 42 | 43 | point := endPos.Mid(startPos) 44 | p := point.Sub(endPos).Rotate(bm.invert * math.Pi / 2).Scl(settings.Dance.HalfCircle.RadiusMultiplier).Add(point) 45 | bm.ca = curves.NewCirArc(endPos, p, startPos) 46 | } 47 | 48 | func (bm *HalfCircleMover) Update(time int64) bmath.Vector2d { 49 | return bm.ca.PointAt(float64(time-bm.endTime) / float64(bm.startTime-bm.endTime)) 50 | } 51 | 52 | func (bm *HalfCircleMover) GetEndTime() int64 { 53 | return bm.startTime 54 | } 55 | -------------------------------------------------------------------------------- /score/scorecalculater.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import . "danser/osuconst" 4 | 5 | func CalculateAccuracy(hits []int64) float64 { 6 | sum := int64(0) 7 | for _, value := range hits { 8 | sum += value 9 | } 10 | return 100 * float64(sum) / float64(300*len(hits)) 11 | } 12 | 13 | func CalculateRank(hits []int64, mods uint32) Rank { 14 | countall := len(hits) 15 | count300 := 0 16 | count100 := 0 17 | count50 := 0 18 | countmiss := 0 19 | for _, value := range hits { 20 | switch value { 21 | case 300: 22 | count300 += 1 23 | break 24 | case 100: 25 | count100 += 1 26 | break 27 | case 50: 28 | count50 += 1 29 | break 30 | case 0: 31 | countmiss += 1 32 | break 33 | } 34 | } 35 | if count300 == countall { 36 | if IsSilver(mods) { 37 | // SSH 38 | return SSH 39 | } else { 40 | // SS 41 | return SS 42 | } 43 | } else if ((float64(count300) / float64(countall)) > 0.9) && ((float64(count50) / float64(countall)) < 0.01) && (countmiss == 0) { 44 | if IsSilver(mods) { 45 | // SH 46 | return SH 47 | } else { 48 | // S 49 | return S 50 | } 51 | } else if ((float64(count300) / float64(countall)) > 0.9) || (((float64(count300) / float64(countall)) > 0.8) && (countmiss == 0)) { 52 | // A 53 | return A 54 | } else if ((float64(count300) / float64(countall)) > 0.8) || (((float64(count300) / float64(countall)) > 0.7) && (countmiss == 0)) { 55 | // B 56 | return B 57 | } else if (float64(count300) / float64(countall)) > 0.6 { 58 | // C 59 | return C 60 | } else { 61 | // D 62 | return D 63 | } 64 | } 65 | 66 | func IsSilver(mods uint32) bool { 67 | return (mods&MOD_HD > 0) || (mods&MOD_FL > 0) 68 | } 69 | -------------------------------------------------------------------------------- /resultcache/cacheio.go: -------------------------------------------------------------------------------- 1 | package resultcache 2 | 3 | import ( 4 | "danser/build" 5 | "danser/hitjudge" 6 | "danser/settings" 7 | "encoding/json" 8 | "github.com/Mempler/rplpa" 9 | "io/ioutil" 10 | "log" 11 | ) 12 | 13 | type Cache struct { 14 | ObjectResults []hitjudge.ObjectResult 15 | TotalResults []hitjudge.TotalResult 16 | Version string 17 | } 18 | 19 | func CacheResult(objectResults []hitjudge.ObjectResult, totalResults []hitjudge.TotalResult, rep *rplpa.Replay) { 20 | err := ioutil.WriteFile(settings.VSplayer.ReplayandCache.CacheDir+rep.ReplayMD5+".oac", marshalCache(Cache{ObjectResults: objectResults, TotalResults: totalResults, Version: build.CACHE_VERSION}), 0666) 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | func GetResult(rep *rplpa.Replay) ([]hitjudge.ObjectResult, []hitjudge.TotalResult, bool) { 27 | bytes, err := ioutil.ReadFile(settings.VSplayer.ReplayandCache.CacheDir + rep.ReplayMD5 + ".oac") 28 | if err != nil { 29 | log.Printf("Could not find ot could not access cache file for %v's replay, MD5 = %v", rep.Username, rep.ReplayMD5) 30 | return nil, nil, false 31 | } 32 | cache := unmarshalCache(bytes) 33 | if cache.Version != build.CACHE_VERSION { 34 | log.Printf("Detected unmatched CACHE_VERSION in %v.oac (%v), overwriting...", rep.ReplayMD5, rep.Username) 35 | return nil, nil, false 36 | } 37 | return cache.ObjectResults, cache.TotalResults, true 38 | } 39 | 40 | func marshalCache(cache Cache) []byte { 41 | data, err := json.Marshal(cache) 42 | if err != nil { 43 | panic(err) 44 | } 45 | return data 46 | } 47 | 48 | func unmarshalCache(r []byte) Cache { 49 | var cache Cache 50 | if err := json.Unmarshal(r, &cache); err != nil { 51 | panic(err) 52 | } 53 | return cache 54 | } 55 | -------------------------------------------------------------------------------- /animation/glider.go: -------------------------------------------------------------------------------- 1 | package animation 2 | 3 | import ( 4 | "danser/animation/easing" 5 | ) 6 | 7 | type event struct { 8 | startTime, endTime, targetValue float64 9 | hasStartValue bool 10 | startValue float64 11 | } 12 | 13 | type Glider struct { 14 | eventqueue []event 15 | time, value, startValue float64 16 | current event 17 | easing func(float64) float64 18 | } 19 | 20 | func NewGlider(value float64) *Glider { 21 | return &Glider{value: value, startValue: value, current: event{-1, 0, value, false, 0}, easing: easing.Linear} 22 | } 23 | 24 | func (glider *Glider) SetEasing(easing func(float64) float64) { 25 | glider.easing = easing 26 | } 27 | 28 | func (glider *Glider) AddEvent(startTime, endTime, targetValue float64) { 29 | glider.eventqueue = append(glider.eventqueue, event{startTime, endTime, targetValue, false, 0}) 30 | } 31 | 32 | func (glider *Glider) AddEventS(startTime, endTime, startValue, targetValue float64) { 33 | glider.eventqueue = append(glider.eventqueue, event{startTime, endTime, targetValue, true, startValue}) 34 | } 35 | 36 | func (glider *Glider) Update(time float64) { 37 | glider.time = time 38 | if len(glider.eventqueue) > 0 { 39 | if e := glider.eventqueue[0]; e.startTime <= time { 40 | glider.current = e 41 | glider.eventqueue = glider.eventqueue[1:] 42 | } 43 | } 44 | 45 | if time <= glider.current.endTime { 46 | e := glider.current 47 | t := (time - e.startTime) / (e.endTime - e.startTime) 48 | glider.value = glider.startValue + glider.easing(t)*(e.targetValue-glider.startValue) 49 | } else { 50 | glider.value = glider.current.targetValue 51 | glider.startValue = glider.value 52 | } 53 | } 54 | 55 | func (glider *Glider) UpdateD(delta float64) { 56 | glider.Update(glider.time + delta) 57 | } 58 | func (glider *Glider) SetValue(value float64) { 59 | glider.value = value 60 | glider.current.targetValue = value 61 | glider.startValue = value 62 | } 63 | 64 | func (glider *Glider) GetValue() float64 { 65 | return glider.value 66 | } 67 | -------------------------------------------------------------------------------- /utils/colors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/lucasb-eyer/go-colorful" 6 | ) 7 | 8 | func GetColors(baseHue, hueShift float64, times int, alpha float64) []mgl32.Vec4 { 9 | return GetColorsSV(baseHue, hueShift, times, 1, 1, alpha) 10 | } 11 | 12 | func GetColor(H, S, V, alpha float64) mgl32.Vec4 { 13 | color := colorful.Hsv(H, S, V) 14 | return mgl32.Vec4{float32(color.R), float32(color.G), float32(color.B), float32(alpha)} 15 | } 16 | 17 | func GetColorsSV(baseHue, hueShift float64, times int, S, V, alpha float64) []mgl32.Vec4 { 18 | colors := make([]mgl32.Vec4, times) 19 | 20 | for baseHue < 0.0 { 21 | baseHue += 360.0 22 | } 23 | 24 | for baseHue >= 360.0 { 25 | baseHue -= 360.0 26 | } 27 | 28 | for i := 0; i < times; i++ { 29 | hue := baseHue + float64(i)*hueShift 30 | 31 | for hue < 0.0 { 32 | hue += 360.0 33 | } 34 | 35 | for hue >= 360.0 { 36 | hue -= 360.0 37 | } 38 | 39 | colors[i] = GetColor(hue, S, V, alpha) 40 | } 41 | 42 | return colors 43 | } 44 | 45 | func GetColorsSVT(baseHue, hueShift, tagShift float64, times, tag int, S, V, alpha float64) []mgl32.Vec4 { 46 | colors := make([]mgl32.Vec4, 0) 47 | 48 | for baseHue < 0.0 { 49 | baseHue += 360.0 50 | } 51 | 52 | for baseHue >= 360.0 { 53 | baseHue -= 360.0 54 | } 55 | 56 | for i := 0; i < times; i++ { 57 | hue := baseHue + float64(i)*hueShift 58 | 59 | for hue < 0.0 { 60 | hue += 360.0 61 | } 62 | 63 | for hue >= 360.0 { 64 | hue -= 360.0 65 | } 66 | 67 | colors = append(colors, GetColorsSV(hue, tagShift, tag, S, V, alpha)...) 68 | } 69 | 70 | return colors 71 | } 72 | 73 | func GetColorShifted(color mgl32.Vec4, hueOffset float64) mgl32.Vec4 { 74 | tohsv := colorful.Color{float64(color[0]), float64(color[1]), float64(color[2])} 75 | h, s, v := tohsv.Hsv() 76 | h += hueOffset 77 | 78 | for h < 0 { 79 | h += 360.0 80 | } 81 | 82 | for h > 360.0 { 83 | h -= 360.0 84 | } 85 | 86 | col2 := colorful.Hsv(h, s, v) 87 | return mgl32.Vec4{float32(col2.R), float32(col2.G), float32(col2.B), color.W()} 88 | } 89 | -------------------------------------------------------------------------------- /render/framebuffer/framebuffer.go: -------------------------------------------------------------------------------- 1 | package framebuffer 2 | 3 | import ( 4 | "runtime" 5 | 6 | "danser/render/texture" 7 | "github.com/faiface/mainthread" 8 | "github.com/go-gl/gl/v3.3-core/gl" 9 | ) 10 | 11 | // Framebuffer is a fixed resolution texture that you can draw on. 12 | type Framebuffer struct { 13 | obj uint32 14 | last int32 15 | tex *texture.TextureSingle 16 | } 17 | 18 | // NewFrame creates a new fully transparent Framebuffer with given dimensions in pixels. 19 | func NewFrame(width, height int, smooth, depth bool) *Framebuffer { 20 | f := new(Framebuffer) 21 | 22 | f.tex = texture.NewTextureSingle(width, height, 0) 23 | 24 | gl.GenFramebuffers(1, &f.obj) 25 | 26 | f.Begin() 27 | f.tex.Bind(0) 28 | gl.FramebufferTextureLayerARB(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, f.tex.GetID(), 0, 0) 29 | 30 | if depth { 31 | var depthRenderBuffer uint32 32 | gl.GenRenderbuffers(1, &depthRenderBuffer) 33 | gl.BindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer) 34 | gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT, int32(width), int32(height)) 35 | gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderBuffer) 36 | } 37 | 38 | f.End() 39 | 40 | runtime.SetFinalizer(f, (*Framebuffer).delete) 41 | 42 | return f 43 | } 44 | 45 | func (f *Framebuffer) delete() { 46 | mainthread.CallNonBlock(func() { 47 | gl.DeleteFramebuffers(1, &f.obj) 48 | }) 49 | } 50 | 51 | // ID returns the OpenGL framebuffer ID of this Framebuffer. 52 | func (f *Framebuffer) ID() uint32 { 53 | return f.obj 54 | } 55 | 56 | // Begin binds the Framebuffer. All draw operations will target this Framebuffer until End is called. 57 | func (f *Framebuffer) Begin() { 58 | gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &f.last) 59 | gl.BindFramebuffer(gl.FRAMEBUFFER, f.obj) 60 | } 61 | 62 | // End unbinds the Framebuffer. All draw operations will go to whatever was bound before this Framebuffer. 63 | func (f *Framebuffer) End() { 64 | gl.BindFramebuffer(gl.FRAMEBUFFER, uint32(f.last)) 65 | } 66 | 67 | // Texture returns the Framebuffer's underlying Texture that the Framebuffer draws on. 68 | func (f *Framebuffer) Texture() texture.Texture { 69 | return f.tex 70 | } 71 | -------------------------------------------------------------------------------- /render/fx.go: -------------------------------------------------------------------------------- 1 | package render 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl32" 5 | "github.com/wieku/glhf" 6 | ) 7 | 8 | var fxshader *glhf.Shader = nil 9 | 10 | func setupFx() { 11 | fxVertexFormat := glhf.AttrFormat{ 12 | {Name: "in_position", Type: glhf.Vec3}, 13 | } 14 | 15 | fxUniformFormat := glhf.AttrFormat{ 16 | {Name: "in_color", Type: glhf.Vec4}, 17 | {Name: "transform", Type: glhf.Mat4}, 18 | } 19 | var err error 20 | fxshader, err = glhf.NewShader(fxVertexFormat, fxUniformFormat, fxvertex, fxfragment) 21 | 22 | if err != nil { 23 | panic("Fx: " + err.Error()) 24 | } 25 | 26 | } 27 | 28 | type FxBatch struct { 29 | color mgl32.Vec4 30 | transform mgl32.Mat4 31 | } 32 | 33 | func NewFxBatch() *FxBatch { 34 | if fxshader == nil { 35 | setupFx() 36 | } 37 | return &FxBatch{mgl32.Vec4{1, 1, 1, 1}, mgl32.Ident4()} 38 | } 39 | 40 | func (batch *FxBatch) Begin() { 41 | fxshader.Begin() 42 | fxshader.SetUniformAttr(0, batch.color) 43 | fxshader.SetUniformAttr(1, batch.transform) 44 | } 45 | 46 | func (batch *FxBatch) CreateVao(length int) *glhf.VertexSlice { 47 | return glhf.MakeVertexSlice(fxshader, length, length) 48 | } 49 | 50 | func (batch *FxBatch) SetColor(r, g, b, a float64) { 51 | batch.color = mgl32.Vec4{float32(r), float32(g), float32(b), float32(a)} 52 | fxshader.SetUniformAttr(0, batch.color) 53 | } 54 | 55 | func (batch *FxBatch) SetColorM(color mgl32.Vec4) { 56 | batch.color = color 57 | fxshader.SetUniformAttr(0, batch.color) 58 | } 59 | 60 | func (batch *FxBatch) ResetTransform() { 61 | batch.transform = mgl32.Ident4() 62 | fxshader.SetUniformAttr(1, batch.transform) 63 | } 64 | 65 | func (batch *FxBatch) End() { 66 | fxshader.End() 67 | } 68 | 69 | func (batch *FxBatch) SetTransform(dz mgl32.Mat4) { 70 | batch.transform = dz 71 | fxshader.SetUniformAttr(1, dz) 72 | } 73 | 74 | const fxvertex = ` 75 | #version 330 76 | 77 | in vec3 in_position; 78 | 79 | uniform mat4 transform; 80 | 81 | void main() 82 | { 83 | gl_Position = transform * vec4(in_position, 1); 84 | } 85 | ` 86 | 87 | const fxfragment = ` 88 | #version 330 89 | 90 | uniform vec4 in_color; 91 | out vec4 color; 92 | 93 | void main() 94 | { 95 | color = in_color; 96 | } 97 | ` 98 | -------------------------------------------------------------------------------- /render/texture/single.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/faiface/mainthread" 5 | "github.com/go-gl/gl/v3.3-core/gl" 6 | "image" 7 | "runtime" 8 | ) 9 | 10 | type TextureSingle struct { 11 | store *textureStore 12 | defRegion TextureRegion 13 | } 14 | 15 | func NewTextureSingle(width, height, mipmaps int) *TextureSingle { 16 | texture := new(TextureSingle) 17 | texture.store = newStore(1, width, height, mipmaps) 18 | texture.defRegion = TextureRegion{texture, 0, 1, 0, 1, int32(width), int32(height), 0} 19 | 20 | runtime.SetFinalizer(texture, (*TextureSingle).Dispose) 21 | 22 | return texture 23 | } 24 | 25 | func LoadTextureSingle(img *image.NRGBA, mipmaps int) *TextureSingle { 26 | texture := NewTextureSingle(img.Bounds().Dx(), img.Bounds().Dy(), mipmaps) 27 | texture.SetData(0, 0, img.Bounds().Dx(), img.Bounds().Dy(), img.Pix) 28 | return texture 29 | } 30 | 31 | func (texture *TextureSingle) SetData(x, y, width, height int, data []uint8) { 32 | if len(data) != width*height*4 { 33 | panic("Wrong number of pixels given!") 34 | } 35 | 36 | gl.TexSubImage3D(gl.TEXTURE_2D_ARRAY, 0, int32(x), int32(y), 0, int32(width), int32(height), 1, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(data)) 37 | if texture.store.mipmaps > 1 { 38 | gl.GenerateMipmap(gl.TEXTURE_2D_ARRAY) 39 | } 40 | } 41 | 42 | func (texture *TextureSingle) GetID() uint32 { 43 | return texture.store.id 44 | } 45 | 46 | func (texture *TextureSingle) GetWidth() int32 { 47 | return texture.store.width 48 | } 49 | 50 | func (texture *TextureSingle) GetHeight() int32 { 51 | return texture.store.height 52 | } 53 | 54 | func (texture *TextureSingle) GetRegion() TextureRegion { 55 | return texture.defRegion 56 | } 57 | 58 | func (texture *TextureSingle) GetLayers() int32 { 59 | return 1 60 | } 61 | 62 | func (texture *TextureSingle) SetFiltering(min, mag Filter) { 63 | texture.store.SetFiltering(min, mag) 64 | } 65 | 66 | func (texture *TextureSingle) Bind(loc uint) { 67 | texture.store.Bind(loc) 68 | } 69 | 70 | func (texture *TextureSingle) GetLocation() uint { 71 | return texture.store.binding 72 | } 73 | 74 | func (texture *TextureSingle) Dispose() { 75 | mainthread.CallNonBlock(func() { 76 | texture.store.Dispose() 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /bmath/curves/cirarc.go: -------------------------------------------------------------------------------- 1 | package curves 2 | 3 | import ( 4 | math2 "danser/bmath" 5 | "math" 6 | ) 7 | 8 | type CirArc struct { 9 | pt1, pt2, pt3 math2.Vector2d 10 | centre math2.Vector2d 11 | startAngle, totalAngle, r, dir float64 12 | Unstable bool 13 | } 14 | 15 | func NewCirArc(pt1, pt2, pt3 math2.Vector2d) CirArc { 16 | arc := &CirArc{pt1: pt1, pt2: pt2, pt3: pt3} 17 | 18 | aSq := pt2.DstSq(pt3) 19 | bSq := pt1.DstSq(pt3) 20 | cSq := pt1.DstSq(pt2) 21 | 22 | if math.Abs(aSq) < 0.001 || math.Abs(bSq) < 0.001 || math.Abs(cSq) < 0.001 { 23 | arc.Unstable = true 24 | } 25 | 26 | s := aSq * (bSq + cSq - aSq) 27 | t := bSq * (aSq + cSq - bSq) 28 | u := cSq * (aSq + bSq - cSq) 29 | 30 | sum := s + t + u 31 | 32 | if math.Abs(sum) < 0.001 { 33 | arc.Unstable = true 34 | } 35 | 36 | centre := pt1.Scl(s).Add(pt2.Scl(t)).Add(pt3.Scl(u)).Scl(1 / sum) 37 | 38 | dA := pt1.Sub(centre) 39 | dC := pt3.Sub(centre) 40 | 41 | r := dA.Len() 42 | 43 | start := math.Atan2(dA.Y, dA.X) 44 | end := math.Atan2(dC.Y, dC.X) 45 | 46 | for end < start { 47 | end += 2 * math.Pi 48 | } 49 | 50 | dir := 1 51 | totalAngle := end - start 52 | 53 | aToC := pt3.Sub(pt1) 54 | aToC = math2.NewVec2d(aToC.Y, -aToC.X) 55 | if aToC.Dot(pt2.Sub(pt1)) < 0 { 56 | dir = -dir 57 | totalAngle = 2*math.Pi - totalAngle 58 | } 59 | 60 | arc.totalAngle = totalAngle 61 | arc.dir = float64(dir) 62 | arc.startAngle = start 63 | arc.centre = centre 64 | arc.r = r 65 | 66 | return *arc 67 | } 68 | 69 | func (ln CirArc) PointAt(t float64) math2.Vector2d { 70 | return math2.NewVec2dRad(ln.startAngle+ln.dir*t*ln.totalAngle, ln.r).Add(ln.centre) 71 | } 72 | 73 | func (ln CirArc) GetLength() float64 { 74 | return ln.r * ln.totalAngle 75 | } 76 | 77 | func (ln CirArc) GetStartAngle() float64 { 78 | return ln.pt1.AngleRV(ln.PointAt(1.0 / ln.GetLength())) 79 | } 80 | 81 | func (ln CirArc) GetEndAngle() float64 { 82 | return ln.pt3.AngleRV(ln.PointAt((ln.GetLength() - 1.0) / ln.GetLength())) 83 | } 84 | 85 | func (ln CirArc) GetPoints(num int) []math2.Vector2d { 86 | t0 := 1 / float64(num-1) 87 | 88 | points := make([]math2.Vector2d, num) 89 | t := 0.0 90 | for i := 0; i < num; i += 1 { 91 | points[i] = ln.PointAt(t) 92 | t += t0 93 | } 94 | 95 | return points 96 | } 97 | -------------------------------------------------------------------------------- /storyboard/transformations.go: -------------------------------------------------------------------------------- 1 | package storyboard 2 | 3 | import ( 4 | "math" 5 | "sort" 6 | ) 7 | 8 | type Transformations struct { 9 | object Object 10 | commands []*Command 11 | queue []*Command 12 | processed []*Command 13 | startTime, endTime, lastTime int64 14 | } 15 | 16 | func NewTransformations(obj Object) *Transformations { 17 | return &Transformations{object: obj, startTime: math.MaxInt64, endTime: math.MinInt64} 18 | } 19 | 20 | func (trans *Transformations) Add(command *Command) { 21 | 22 | if command.command != "P" { 23 | exists := false 24 | 25 | for _, e := range trans.queue { 26 | if e.command == command.command && e.start < command.start { 27 | exists = true 28 | break 29 | } 30 | } 31 | 32 | if !exists { 33 | if trans.object != nil { 34 | command.Init(trans.object) 35 | } 36 | } 37 | } 38 | 39 | trans.commands = append(trans.commands, command) 40 | trans.queue = append(trans.queue, command) 41 | 42 | if command.start < trans.startTime { 43 | trans.startTime = command.start 44 | } 45 | 46 | if command.end > trans.endTime { 47 | trans.endTime = command.end 48 | } 49 | } 50 | 51 | func (trans *Transformations) Finalize() { 52 | sort.Slice(trans.queue, func(i, j int) bool { 53 | return trans.queue[i].start < trans.queue[j].start 54 | }) 55 | 56 | sort.Slice(trans.commands, func(i, j int) bool { 57 | return trans.commands[i].start < trans.commands[j].start 58 | }) 59 | } 60 | 61 | func (trans *Transformations) Update(time int64) { 62 | 63 | if time < trans.lastTime { 64 | trans.queue = make([]*Command, len(trans.commands)) 65 | copy(trans.queue, trans.commands) 66 | trans.processed = make([]*Command, 0) 67 | } 68 | 69 | for i := 0; i < len(trans.queue); i++ { 70 | c := trans.queue[i] 71 | if c.start <= time { 72 | trans.processed = append(trans.processed, c) 73 | trans.queue = append(trans.queue[:i], trans.queue[i+1:]...) 74 | i-- 75 | } 76 | } 77 | 78 | for i := 0; i < len(trans.processed); i++ { 79 | c := trans.processed[i] 80 | c.Update(time) 81 | if trans.object != nil { 82 | c.Apply(trans.object) 83 | } 84 | 85 | if time > c.end { 86 | trans.processed = append(trans.processed[:i], trans.processed[i+1:]...) 87 | i-- 88 | } 89 | } 90 | 91 | trans.lastTime = time 92 | } 93 | -------------------------------------------------------------------------------- /bmath/curves/catmull.go: -------------------------------------------------------------------------------- 1 | package curves 2 | 3 | import ( 4 | "danser/bmath" 5 | math2 "danser/bmath" 6 | "math" 7 | ) 8 | 9 | type Catmull struct { 10 | points []math2.Vector2d 11 | ApproxLength float64 12 | } 13 | 14 | func NewCatmull(points []math2.Vector2d) Catmull { 15 | 16 | if len(points) != 4 { 17 | panic("4 points are needed to create centripetal catmull rom") 18 | } 19 | 20 | cm := &Catmull{points: points} 21 | 22 | pointLength := points[1].Dst(points[2]) 23 | 24 | pointLength = math.Ceil(pointLength) 25 | 26 | for i := 1; i <= int(pointLength); i++ { 27 | cm.ApproxLength += cm.NPointAt(float64(i) / pointLength).Dst(cm.NPointAt(float64(i-1) / pointLength)) 28 | } 29 | 30 | return *cm 31 | } 32 | 33 | func (cm Catmull) NPointAt(t float64) math2.Vector2d { 34 | return findPoint(cm.points[0], cm.points[1], cm.points[2], cm.points[3], t) 35 | } 36 | 37 | func findPoint(vec1, vec2, vec3, vec4 bmath.Vector2d, t float64) bmath.Vector2d { 38 | t2 := t * t 39 | t3 := t * t2 40 | 41 | return bmath.NewVec2d(0.5*(2*vec2.X+(-vec1.X+vec3.X)*t+(2*vec1.X-5*vec2.X+4*vec3.X-vec4.X)*t2+(-vec1.X+3*vec2.X-3*vec3.X+vec4.X)*t3), 42 | 0.5*(2*vec2.Y+(-vec1.Y+vec3.Y)*t+(2*vec1.Y-5*vec2.Y+4*vec3.Y-vec4.Y)*t2+(-vec1.Y+3*vec2.Y-3*vec3.Y+vec4.Y)*t3)) 43 | } 44 | 45 | //It's not a neat solution, but it works 46 | //This calculates point on catmull curve with constant velocity 47 | func (cm Catmull) PointAt(t float64) math2.Vector2d { 48 | desiredWidth := cm.ApproxLength * t 49 | width := 0.0 50 | pos := cm.points[1] 51 | c := 0.0 52 | for width < desiredWidth { 53 | pt := cm.NPointAt(c) 54 | width += pt.Dst(pos) 55 | if width > desiredWidth { 56 | return pos 57 | } 58 | pos = pt 59 | c += 1.0 / float64(cm.ApproxLength*2-1) 60 | } 61 | 62 | return pos 63 | } 64 | 65 | func (cm Catmull) GetLength() float64 { 66 | return cm.ApproxLength 67 | } 68 | 69 | func (cm Catmull) GetStartAngle() float64 { 70 | return cm.points[0].AngleRV(cm.NPointAt(1.0 / cm.ApproxLength)) 71 | } 72 | 73 | func (cm Catmull) GetEndAngle() float64 { 74 | return cm.points[len(cm.points)-1].AngleRV(cm.NPointAt((cm.ApproxLength - 1) / cm.ApproxLength)) 75 | } 76 | 77 | func (ln Catmull) GetPoints(num int) []math2.Vector2d { 78 | t0 := 1 / float64(num-1) 79 | 80 | points := make([]math2.Vector2d, num) 81 | t := 0.0 82 | for i := 0; i < num; i += 1 { 83 | points[i] = ln.PointAt(t) 84 | t += t0 85 | } 86 | 87 | return points 88 | } 89 | -------------------------------------------------------------------------------- /storyboard/layer.go: -------------------------------------------------------------------------------- 1 | package storyboard 2 | 3 | import ( 4 | "danser/render" 5 | "sort" 6 | "sync" 7 | ) 8 | 9 | type StoryboardLayer struct { 10 | spriteQueue []Object 11 | spriteProcessed []Object 12 | drawArray []Object 13 | visibleObjects int 14 | allSprites int 15 | mutex *sync.Mutex 16 | } 17 | 18 | func NewStoryboardLayer() *StoryboardLayer { 19 | return &StoryboardLayer{mutex: &sync.Mutex{}} 20 | } 21 | 22 | func (layer *StoryboardLayer) Add(object Object) { 23 | layer.spriteQueue = append(layer.spriteQueue, object) 24 | } 25 | 26 | func (layer *StoryboardLayer) FinishLoading() { 27 | sort.Slice(layer.spriteQueue, func(i, j int) bool { 28 | return layer.spriteQueue[i].GetStartTime() < layer.spriteQueue[j].GetStartTime() 29 | }) 30 | layer.allSprites = len(layer.spriteQueue) 31 | layer.drawArray = make([]Object, len(layer.spriteQueue)) 32 | } 33 | 34 | func (layer *StoryboardLayer) Update(time int64) { 35 | toRemove := 0 36 | 37 | for i := 0; i < len(layer.spriteQueue); i++ { 38 | c := layer.spriteQueue[i] 39 | if c.GetStartTime() > time { 40 | break 41 | } 42 | 43 | toRemove++ 44 | } 45 | 46 | if toRemove > 0 { 47 | layer.spriteProcessed = append(layer.spriteProcessed, layer.spriteQueue[:toRemove]...) 48 | layer.spriteQueue = layer.spriteQueue[toRemove:] 49 | sort.Slice(layer.spriteProcessed, func(i, j int) bool { 50 | return layer.spriteProcessed[i].GetZIndex() < layer.spriteProcessed[j].GetZIndex() 51 | }) 52 | } 53 | 54 | layer.mutex.Lock() 55 | 56 | for i := 0; i < len(layer.spriteProcessed); i++ { 57 | c := layer.spriteProcessed[i] 58 | c.Update(time) 59 | 60 | if time >= c.GetEndTime() { 61 | layer.spriteProcessed = append(layer.spriteProcessed[:i], layer.spriteProcessed[i+1:]...) 62 | i-- 63 | } 64 | } 65 | 66 | layer.visibleObjects = len(layer.spriteProcessed) 67 | copy(layer.drawArray, layer.spriteProcessed) 68 | 69 | layer.mutex.Unlock() 70 | } 71 | 72 | func (layer *StoryboardLayer) GetLoad() (sum float64) { 73 | for i := 0; i < layer.visibleObjects; i++ { 74 | if layer.drawArray[i] != nil { 75 | sum += layer.drawArray[i].GetLoad() 76 | } 77 | } 78 | return 79 | } 80 | 81 | func (layer *StoryboardLayer) Draw(time int64, batch *render.SpriteBatch) { 82 | layer.mutex.Lock() 83 | for i := 0; i < layer.visibleObjects; i++ { 84 | if layer.drawArray[i] != nil { 85 | layer.drawArray[i].Draw(time, batch) 86 | } 87 | } 88 | 89 | layer.mutex.Unlock() 90 | } 91 | -------------------------------------------------------------------------------- /render/texture/texture.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/go-gl/gl/v3.3-core/gl" 5 | ) 6 | 7 | type Filter int32 8 | 9 | var Filtering = struct { 10 | Nearest, 11 | Linear, 12 | MipMap, 13 | MipMapNearestNearest, 14 | MipMapLinearNearest, 15 | MipMapNearestLinear, 16 | MipMapLinearLinear Filter 17 | }{gl.NEAREST, gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR, gl.NEAREST_MIPMAP_NEAREST, gl.LINEAR_MIPMAP_NEAREST, gl.NEAREST_MIPMAP_LINEAR, gl.LINEAR_MIPMAP_LINEAR} 18 | 19 | type Texture interface { 20 | GetID() uint32 21 | GetWidth() int32 22 | GetHeight() int32 23 | GetRegion() TextureRegion 24 | GetLayers() int32 25 | SetFiltering(min, mag Filter) 26 | Bind(loc uint) 27 | GetLocation() uint 28 | Dispose() 29 | } 30 | 31 | type TextureRegion struct { 32 | Texture Texture 33 | U1, U2, V1, V2 float32 34 | Width, Height int32 35 | Layer int32 36 | } 37 | 38 | type textureStore struct { 39 | id uint32 40 | binding uint 41 | layers, width, height, mipmaps int32 42 | } 43 | 44 | func newStore(layerNum, width, height, mipmaps int) *textureStore { 45 | store := new(textureStore) 46 | gl.GenTextures(1, &store.id) 47 | 48 | store.layers = int32(layerNum) 49 | store.width = int32(width) 50 | store.height = int32(height) 51 | 52 | if mipmaps < 1 { 53 | mipmaps = 1 54 | } 55 | store.mipmaps = int32(mipmaps) 56 | 57 | store.Bind(0) 58 | gl.TexStorage3D(gl.TEXTURE_2D_ARRAY, store.mipmaps, gl.RGBA8, store.width, store.height, store.layers) 59 | gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_BASE_LEVEL, 0) 60 | gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAX_LEVEL, store.mipmaps-1) 61 | gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 62 | gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 63 | 64 | if mipmaps > 1 { 65 | store.SetFiltering(Filtering.MipMap, Filtering.Linear) 66 | } else { 67 | store.SetFiltering(Filtering.Linear, Filtering.Linear) 68 | } 69 | 70 | return store 71 | } 72 | 73 | func (store *textureStore) Bind(loc uint) { 74 | store.binding = loc 75 | gl.ActiveTexture(gl.TEXTURE0 + uint32(loc)) 76 | gl.BindTexture(gl.TEXTURE_2D_ARRAY, store.id) 77 | } 78 | 79 | func (store *textureStore) SetFiltering(min, mag Filter) { 80 | gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, int32(min)) 81 | gl.TexParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, int32(mag)) 82 | } 83 | 84 | func (store *textureStore) Dispose() { 85 | gl.DeleteTextures(1, &store.id) 86 | } 87 | -------------------------------------------------------------------------------- /beatmap/objects/baseobject.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | om "danser/bmath" 5 | . "danser/osuconst" 6 | "danser/render" 7 | "danser/settings" 8 | "github.com/go-gl/mathgl/mgl32" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type BaseObject interface { 14 | GetBasicData() *basicData 15 | Update(time int64) bool 16 | GetPosition() om.Vector2d 17 | SetDifficulty(preempt, fadeIn float64) 18 | } 19 | 20 | type Renderable interface { 21 | Draw(time int64, preempt float64, fadeIn float64, color mgl32.Vec4, batch *render.SpriteBatch) bool 22 | DrawApproach(time int64, preempt float64, fadeIn float64, color mgl32.Vec4, batch *render.SpriteBatch) 23 | GetObjectNumber() int64 24 | } 25 | 26 | type basicData struct { 27 | StartPos, EndPos om.Vector2d 28 | StartTime, EndTime int64 29 | StackOffset om.Vector2d 30 | StackIndex int64 31 | // 一个combo内的object序号 32 | Number int64 33 | // 总的obejct序号 34 | ObjectNumber int64 35 | SliderPoint bool 36 | 37 | // 物件的判定时间 38 | // note:开始时间 39 | // 滑条:结束时间减滑条尾偏移 40 | // 转盘:结束时间 41 | JudgeTime int64 42 | 43 | sampleSet int 44 | additionSet int 45 | customIndex int 46 | customVolume float64 47 | } 48 | 49 | func commonParse(data []string, number int64) *basicData { 50 | x, _ := strconv.ParseFloat(data[0], 64) 51 | y, _ := strconv.ParseFloat(data[1], 64) 52 | if settings.VSplayer.Mods.EnableHR { 53 | y = PLAYFIELD_HEIGHT - y 54 | } 55 | time, _ := strconv.ParseInt(data[2], 10, 64) 56 | return &basicData{StartPos: om.NewVec2d(x, y), StartTime: time, Number: number} 57 | } 58 | 59 | func commonParsebyPath(data []string, number int64, isHR bool) *basicData { 60 | x, _ := strconv.ParseFloat(data[0], 64) 61 | y, _ := strconv.ParseFloat(data[1], 64) 62 | if isHR { 63 | y = PLAYFIELD_HEIGHT - y 64 | } 65 | time, _ := strconv.ParseInt(data[2], 10, 64) 66 | return &basicData{StartPos: om.NewVec2d(x, y), StartTime: time, Number: number} 67 | } 68 | 69 | func (bData *basicData) parseExtras(data []string, extraIndex int) { 70 | if extraIndex < len(data) { 71 | extras := strings.Split(data[extraIndex], ":") 72 | sampleSet, _ := strconv.ParseInt(extras[0], 10, 64) 73 | additionSet, _ := strconv.ParseInt(extras[1], 10, 64) 74 | index, _ := strconv.ParseInt(extras[2], 10, 64) 75 | if len(extras) > 3 { 76 | volume, _ := strconv.ParseInt(extras[3], 10, 64) 77 | bData.customVolume = float64(volume) / 100.0 78 | } else { 79 | bData.customVolume = 0 80 | } 81 | 82 | bData.sampleSet = int(sampleSet) 83 | bData.additionSet = int(additionSet) 84 | bData.customIndex = int(index) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /beatmap/objects/timing.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "log" 5 | "math" 6 | ) 7 | 8 | type TimingPoint struct { 9 | Time int64 10 | BaseBpm, Bpm float64 11 | SampleSet int 12 | SampleIndex int 13 | SampleVolume float64 14 | } 15 | 16 | func (t TimingPoint) GetRatio() float64 { 17 | return t.Bpm / t.BaseBpm 18 | } 19 | 20 | type Timings struct { 21 | Points []TimingPoint 22 | queue []TimingPoint 23 | SliderMult float64 24 | Current TimingPoint 25 | fullBPM, partBPM float64 26 | BaseSet int 27 | LastSet int 28 | TickRate float64 29 | } 30 | 31 | func NewTimings() *Timings { 32 | return &Timings{BaseSet: 1, LastSet: 1} 33 | } 34 | 35 | func (tim *Timings) AddPoint(time int64, bpm float64, sampleset, sampleindex int, samplevolume float64) { 36 | point := TimingPoint{Time: time, Bpm: bpm, SampleSet: sampleset, SampleIndex: sampleindex, SampleVolume: samplevolume} 37 | if point.Bpm > 0 { 38 | tim.fullBPM = point.Bpm 39 | } else { 40 | point.Bpm = tim.fullBPM / math.Max(0.1, -100.0/point.Bpm) 41 | } 42 | point.BaseBpm = tim.fullBPM 43 | tim.Points = append(tim.Points, point) 44 | tim.queue = append(tim.queue, point) 45 | } 46 | 47 | func (tim *Timings) Update(time int64) { 48 | if len(tim.queue) > 0 { 49 | p := tim.queue[0] 50 | if p.Time <= time { 51 | tim.queue = tim.queue[1:] 52 | tim.partBPM = p.Bpm 53 | tim.Current = p 54 | } 55 | } 56 | } 57 | 58 | func clamp(a int, min int, max int) int { 59 | if a > max { 60 | return max 61 | } 62 | if a < min { 63 | return min 64 | } 65 | return a 66 | } 67 | 68 | func clampF(a float64, min float64, max float64) float64 { 69 | if a > max { 70 | return max 71 | } 72 | if a < min { 73 | return min 74 | } 75 | return a 76 | } 77 | 78 | func (tim *Timings) GetPoint(time int64) TimingPoint { 79 | for i, pt := range tim.Points { 80 | if time < pt.Time { 81 | return tim.Points[clamp(i-1, 0, len(tim.Points)-1)] 82 | } 83 | } 84 | return tim.Points[len(tim.Points)-1] 85 | } 86 | 87 | func (tim Timings) GetSliderTimeS(time int64, pixelLength float64) int64 { 88 | return int64(tim.GetPoint(time).Bpm * pixelLength / (100.0 * tim.SliderMult)) 89 | } 90 | 91 | func (tim Timings) GetSliderTime(pixelLength float64) int64 { 92 | return int64(tim.partBPM * pixelLength / (100.0 * tim.SliderMult)) 93 | } 94 | 95 | func (tim Timings) GetSliderTimeP(point TimingPoint, pixelLength float64) int64 { 96 | return int64((point.Bpm * pixelLength / (100.0 * tim.SliderMult))) 97 | } 98 | 99 | func (tim *Timings) Reset() { 100 | tim.queue = make([]TimingPoint, len(tim.Points)) 101 | copy(tim.queue, tim.Points) 102 | tim.Current = tim.queue[0] 103 | } 104 | 105 | func (tim *Timings) Log() { 106 | log.Println(len(tim.Points)) 107 | } 108 | -------------------------------------------------------------------------------- /bmath/curves/bezier.go: -------------------------------------------------------------------------------- 1 | package curves 2 | 3 | import ( 4 | math2 "danser/bmath" 5 | "math" 6 | ) 7 | 8 | type Bezier struct { 9 | points []math2.Vector2d 10 | ApproxLength float64 11 | lengthCalculated bool 12 | } 13 | 14 | func NewBezier(points []math2.Vector2d) Bezier { 15 | bz := &Bezier{points: points} 16 | 17 | pointLength := 0.0 18 | for i := 1; i < len(points); i++ { 19 | pointLength += points[i].Dst(points[i-1]) 20 | } 21 | 22 | pointLength = math.Ceil(pointLength) 23 | 24 | for i := 1; i <= int(pointLength); i++ { 25 | bz.ApproxLength += bz.NPointAt(float64(i) / pointLength).Dst(bz.NPointAt(float64(i-1) / pointLength)) 26 | } 27 | 28 | return *bz 29 | } 30 | 31 | func (bz Bezier) NPointAt(t float64) math2.Vector2d { 32 | x := 0.0 33 | y := 0.0 34 | n := len(bz.points) - 1 35 | for i := 0; i <= n; i++ { 36 | b := bernstein(int64(i), int64(n), t) 37 | x += bz.points[i].X * b 38 | y += bz.points[i].Y * b 39 | } 40 | return math2.NewVec2d(x, y) 41 | } 42 | 43 | //It's not a neat solution, but it works 44 | //This calculates point on bezier with constant velocity 45 | func (bz Bezier) PointAt(t float64) math2.Vector2d { 46 | desiredWidth := bz.ApproxLength * t 47 | width := 0.0 48 | pos := bz.points[0] 49 | c := 0.0 50 | for width < desiredWidth { 51 | pt := bz.NPointAt(c) 52 | width += pt.Dst(pos) 53 | if width > desiredWidth { 54 | return pos 55 | } 56 | pos = pt 57 | c += 1.0 / float64(bz.ApproxLength*2-1) 58 | } 59 | 60 | return pos 61 | } 62 | 63 | func (bz Bezier) GetLength() float64 { 64 | return bz.ApproxLength 65 | } 66 | 67 | func (bz Bezier) GetStartAngle() float64 { 68 | return bz.points[0].AngleRV(bz.NPointAt(1.0 / bz.ApproxLength)) 69 | } 70 | 71 | func (bz Bezier) GetEndAngle() float64 { 72 | return bz.points[len(bz.points)-1].AngleRV(bz.NPointAt((bz.ApproxLength - 1) / bz.ApproxLength)) 73 | } 74 | 75 | func min(a, b int64) int64 { 76 | if a < b { 77 | return a 78 | } 79 | return b 80 | } 81 | 82 | func BinomialCoefficient(n, k int64) int64 { 83 | if k < 0 || k > n { 84 | return 0 85 | } 86 | if k == 0 || k == n { 87 | return 1 88 | } 89 | k = min(k, n-k) 90 | var c int64 = 1 91 | var i int64 = 0 92 | for ; i < k; i++ { 93 | c = c * (n - i) / (i + 1) 94 | } 95 | 96 | return c 97 | } 98 | 99 | func bernstein(i, n int64, t float64) float64 { 100 | return float64(BinomialCoefficient(n, i)) * math.Pow(t, float64(i)) * math.Pow(1.0-t, float64(n-i)) 101 | } 102 | 103 | func calcLength() { 104 | 105 | } 106 | 107 | func (ln Bezier) GetPoints(num int) []math2.Vector2d { 108 | t0 := 1 / float64(num-1) 109 | 110 | points := make([]math2.Vector2d, num) 111 | t := 0.0 112 | for i := 0; i < num; i += 1 { 113 | points[i] = ln.PointAt(t) 114 | t += t0 115 | } 116 | 117 | return points 118 | } 119 | -------------------------------------------------------------------------------- /dance/schedulers/generic.go: -------------------------------------------------------------------------------- 1 | package schedulers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/render" 7 | ) 8 | 9 | //type GenericScheduler struct { 10 | // cursor *render.Cursor 11 | // queue []objects.BaseObject 12 | // mover movers.MultiPointMover 13 | //} 14 | // 15 | //func NewGenericScheduler(mover func() movers.MultiPointMover) Scheduler { 16 | // return &GenericScheduler{mover: mover()} 17 | //} 18 | // 19 | //func (sched *GenericScheduler) Init(objs []objects.BaseObject, cursor *render.Cursor) { 20 | // sched.cursor = cursor 21 | // sched.queue = objs 22 | // sched.mover.Reset() 23 | // sched.queue = PreprocessQueue(0, sched.queue, settings.Dance.SliderDance) 24 | // ///////////////////////////////////////////////////////////////////////////////////////////////////// 25 | // // 初始位置 (100, 100) 26 | // sched.mover.SetObjects([]objects.BaseObject{objects.DummyCircle(bmath.NewVec2d(100, 100), 0), sched.queue[0]}) 27 | // ///////////////////////////////////////////////////////////////////////////////////////////////////// 28 | //} 29 | // 30 | //func (sched *GenericScheduler) Update(time int64) { 31 | // if len(sched.queue) > 0 { 32 | // move := true 33 | // for i := 0; i < len(sched.queue); i++ { 34 | // g := sched.queue[i] 35 | // if g.GetBasicData().StartTime > time { 36 | // break 37 | // } 38 | // 39 | // move = false 40 | // 41 | // if time >= g.GetBasicData().StartTime && time <= g.GetBasicData().EndTime { 42 | // 43 | // sched.cursor.SetPos(g.GetPosition()) 44 | // } else if time > g.GetBasicData().EndTime { 45 | // if i < len(sched.queue)-1 { 46 | // sched.queue = append(sched.queue[:i], sched.queue[i+1:]...) 47 | // } else if i < len(sched.queue) { 48 | // sched.queue = sched.queue[:i] 49 | // } 50 | // i-- 51 | // 52 | // if len(sched.queue) > 0 { 53 | // sched.queue = PreprocessQueue(i+1, sched.queue, settings.Dance.SliderDance) 54 | // sched.mover.SetObjects([]objects.BaseObject{g, sched.queue[i+1]}) 55 | // } 56 | // 57 | // move = true 58 | // } 59 | // } 60 | // 61 | // if move && sched.mover.GetEndTime() >= time { 62 | // sched.cursor.SetPos(sched.mover.Update(time)) 63 | // } 64 | // 65 | // } 66 | // //log.Println(time, sched.cursor.Position) 67 | //} 68 | 69 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 70 | // 修改Scheduler 71 | type ReplayScheduler struct { 72 | cursor *render.Cursor 73 | } 74 | 75 | func NewReplayScheduler() Scheduler { 76 | return &ReplayScheduler{} 77 | } 78 | 79 | func (sched *ReplayScheduler) Init(objs []objects.BaseObject, cursor *render.Cursor) { 80 | sched.cursor = cursor 81 | } 82 | 83 | func (sched *ReplayScheduler) Update(time int64, position bmath.Vector2d) { 84 | sched.cursor.SetPos(position) 85 | } 86 | 87 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 88 | -------------------------------------------------------------------------------- /ini/ini.go: -------------------------------------------------------------------------------- 1 | package ini 2 | 3 | /** 4 | * 截入配置文件并读取其中内容 5 | * @Auther QiuXiangCheng 6 | * @Date 2018/05/08 7 | * 8 | * 与INI配置文件风格一样 根据顺序读取文件和每一行 如果在行首出现了;号,则认为是配置文件的注释 9 | * 当INI不规范时 如[mysql] 的注释被写为[mysql 则会返回错误 10 | * 本包的配置文件是严格区分大小写的 需要禁止区分大小写 将在后期加入或自行加入 11 | */ 12 | 13 | import ( 14 | "bufio" 15 | "errors" 16 | "net/url" 17 | "os" 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | // 配置文件 23 | type Config struct { 24 | conf map[string]url.Values 25 | } 26 | 27 | // 将指定的配置以字符串返回 28 | func (c *Config) String(tag string) string { 29 | spl := strings.Split(tag, ".") 30 | key := strings.Join(spl[1:], "_") 31 | if len(spl) < 2 || spl[1] == "" { 32 | return "" 33 | } 34 | 35 | return c.conf[spl[0]].Get(key) 36 | } 37 | 38 | // 返回一个Int类型的配置值 39 | func (c *Config) Int(tag string) (int, error) { 40 | return strconv.Atoi(c.String(tag)) 41 | } 42 | 43 | // 返回一个int64配置值 44 | func (c *Config) Int64(tag string) (int64, error) { 45 | return strconv.ParseInt(c.String(tag), 10, 64) 46 | } 47 | 48 | // 返回一个float64配置值 49 | func (c *Config) Float64(tag string) (float64, error) { 50 | return strconv.ParseFloat(c.String(tag), 64) 51 | } 52 | 53 | // 初始化一个文件配置句柄 54 | func NewFileConf(filePath string) (*Config, error) { 55 | 56 | cf := &Config{ 57 | conf: make(map[string]url.Values, 10), 58 | } 59 | 60 | f, err := NewFileReader(filePath) 61 | if err != nil { 62 | return nil, errors.New("Error:can not read file \"" + filePath + "\"") 63 | } 64 | defer f.Close() 65 | 66 | tag := "" 67 | buf := bufio.NewReader(f) 68 | replacer := strings.NewReplacer(" ", "") 69 | 70 | for { 71 | lstr, err := buf.ReadString('\n') 72 | if err != nil && err != errors.New("EOF") { 73 | break 74 | } 75 | 76 | if lstr == "" { 77 | break 78 | } 79 | 80 | lstr = strings.TrimSpace(lstr) 81 | if lstr == "" { 82 | continue 83 | } 84 | 85 | if idx := strings.Index(lstr, "["); idx != -1 { 86 | if lstr[len(lstr)-1:] != "]" { 87 | return nil, errors.New("Error:field to parse this symbol style:\"" + lstr + "\"") 88 | } 89 | tag = lstr[1 : len(lstr)-1] 90 | cf.conf[tag] = url.Values{} 91 | } else { 92 | lstr = replacer.Replace(lstr) 93 | // 修改分隔符为 : 94 | spl := strings.Split(lstr, ":") 95 | 96 | // 增加注释符 // 97 | if lstr[0:1] == ";" || lstr[0:2] == "//" { 98 | continue 99 | } 100 | 101 | if len(spl) < 2 { 102 | return nil, errors.New("error:" + lstr) 103 | } 104 | cf.conf[tag].Set(strings.Replace(spl[0], ".", "_", -1), spl[1]) 105 | } 106 | } 107 | 108 | return cf, nil 109 | } 110 | 111 | // 打开一个文件句柄 112 | func NewFileReader(filePath string) (*os.File, error) { 113 | if !PathExists(filePath) { 114 | return nil, errors.New("Error:File not exists:" + filePath) 115 | } 116 | 117 | f, err := os.Open(filePath) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return f, nil 123 | } 124 | 125 | // 检查文件或文件夹是否存在 126 | func PathExists(path string) bool { 127 | _, err := os.Stat(path) 128 | if err == nil { 129 | return true 130 | } 131 | 132 | if os.IsNotExist(err) { 133 | return false 134 | } 135 | 136 | return false 137 | } 138 | -------------------------------------------------------------------------------- /bmath/vector2d.go: -------------------------------------------------------------------------------- 1 | package bmath 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-gl/mathgl/mgl32" 6 | "math" 7 | ) 8 | 9 | type Vector2d struct { 10 | X, Y float64 11 | } 12 | 13 | func NewVec2d(x, y float64) Vector2d { 14 | return Vector2d{x, y} 15 | } 16 | 17 | func NewVec2dP(x, y float64) *Vector2d { 18 | return &Vector2d{x, y} 19 | } 20 | 21 | func NewVec2dRad(rad, length float64) Vector2d { 22 | return Vector2d{math.Cos(rad) * length, math.Sin(rad) * length} 23 | } 24 | 25 | func (v Vector2d) X32() float32 { 26 | return float32(v.X) 27 | } 28 | 29 | func (v Vector2d) Y32() float32 { 30 | return float32(v.Y) 31 | } 32 | 33 | func (v Vector2d) AsVec3() mgl32.Vec3 { 34 | return mgl32.Vec3{float32(v.X), float32(v.Y), 0} 35 | } 36 | 37 | func (v Vector2d) AsVec4() mgl32.Vec4 { 38 | return mgl32.Vec4{float32(v.X), float32(v.Y), 0, 1} 39 | } 40 | 41 | func (v *Vector2d) Set(x, y float64) { 42 | v.X = x 43 | v.Y = y 44 | } 45 | 46 | func (v *Vector2d) SetRad(rad, length float64) { 47 | v.X = math.Cos(rad) * length 48 | v.Y = math.Sin(rad) * length 49 | } 50 | 51 | func (v Vector2d) printOut() { 52 | fmt.Println("[", v.X, ":", v.Y, "]") 53 | } 54 | 55 | func (v Vector2d) Add(v1 Vector2d) Vector2d { 56 | return Vector2d{v.X + v1.X, v.Y + v1.Y} 57 | } 58 | 59 | func (v Vector2d) AddS(x, y float64) Vector2d { 60 | return Vector2d{v.X + x, v.Y + y} 61 | } 62 | 63 | func (v Vector2d) Sub(v1 Vector2d) Vector2d { 64 | return Vector2d{v.X - v1.X, v.Y - v1.Y} 65 | } 66 | 67 | func (v Vector2d) Mult(v1 Vector2d) Vector2d { 68 | return Vector2d{v.X * v1.X, v.Y * v1.Y} 69 | } 70 | 71 | func (v Vector2d) Mid(v1 Vector2d) Vector2d { 72 | return Vector2d{(v.X + v1.X) / 2, (v.Y + v1.Y) / 2} 73 | } 74 | 75 | func (v Vector2d) Dot(v1 Vector2d) float64 { 76 | return v.X*v1.X + v.Y*v1.Y 77 | } 78 | 79 | func (v Vector2d) Dst(v1 Vector2d) float64 { 80 | return math.Sqrt(math.Pow(v1.X-v.X, 2) + math.Pow(v1.Y-v.Y, 2)) 81 | } 82 | 83 | func (v Vector2d) DstSq(v1 Vector2d) float64 { 84 | return math.Pow(v1.X-v.X, 2) + math.Pow(v1.Y-v.Y, 2) 85 | } 86 | 87 | func (v Vector2d) Angle() float64 { 88 | return v.AngleR() * 180 / math.Pi 89 | } 90 | 91 | func (v Vector2d) AngleR() float64 { 92 | return math.Atan2(v.Y, v.X) 93 | } 94 | 95 | func (v Vector2d) Nor() Vector2d { 96 | len := v.Len() 97 | return Vector2d{v.X / len, v.Y / len} 98 | } 99 | 100 | func (v Vector2d) AngleRV(v1 Vector2d) float64 { 101 | return math.Atan2(v.Y-v1.Y, v.X-v1.X) 102 | } 103 | 104 | func (v Vector2d) Rotate(rad float64) Vector2d { 105 | cos := math.Cos(rad) 106 | sin := math.Sin(rad) 107 | return Vector2d{v.X*cos - v.Y*sin, v.X*sin + v.Y*cos} 108 | } 109 | 110 | func (v Vector2d) Len() float64 { 111 | return math.Sqrt(v.X*v.X + v.Y*v.Y) 112 | } 113 | 114 | func (v Vector2d) Scl(mag float64) Vector2d { 115 | return Vector2d{v.X * mag, v.Y * mag} 116 | } 117 | 118 | func (v Vector2d) Abs() Vector2d { 119 | return NewVec2d(math.Abs(v.X), math.Abs(v.Y)) 120 | } 121 | 122 | func (v Vector2d) Copy() Vector2d { 123 | return Vector2d{v.X, v.Y} 124 | } 125 | 126 | func GetX(v Vector2d) float64 { 127 | return v.X 128 | } 129 | 130 | func GetY(v Vector2d) float64 { 131 | return v.Y 132 | } 133 | -------------------------------------------------------------------------------- /dance/movers/bezier.go: -------------------------------------------------------------------------------- 1 | package movers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/bmath/curves" 7 | . "danser/osuconst" 8 | "danser/settings" 9 | "math" 10 | ) 11 | 12 | type BezierMover struct { 13 | pt bmath.Vector2d 14 | bz curves.Bezier 15 | beginTime, endTime int64 16 | previousSpeed float64 17 | invert float64 18 | } 19 | 20 | func NewBezierMover() MultiPointMover { 21 | bm := &BezierMover{invert: 1} 22 | bm.pt = bmath.NewVec2d(PLAYFIELD_WIDTH/2, PLAYFIELD_HEIGHT/2) 23 | bm.previousSpeed = -1 24 | return bm 25 | } 26 | 27 | func (bm *BezierMover) Reset() { 28 | bm.pt = bmath.NewVec2d(PLAYFIELD_WIDTH/2, PLAYFIELD_HEIGHT/2) 29 | bm.invert = 1 30 | bm.previousSpeed = -1 31 | } 32 | 33 | func (bm *BezierMover) SetObjects(objs []objects.BaseObject) { 34 | end := objs[0] 35 | start := objs[1] 36 | endPos := end.GetBasicData().EndPos 37 | endTime := end.GetBasicData().EndTime 38 | startPos := start.GetBasicData().StartPos 39 | startTime := start.GetBasicData().StartTime 40 | 41 | dst := endPos.Dst(startPos) 42 | 43 | if bm.previousSpeed < 0 { 44 | bm.previousSpeed = dst / float64(startTime-endTime) 45 | } 46 | 47 | s1, ok1 := end.(*objects.Slider) 48 | s2, ok2 := start.(*objects.Slider) 49 | 50 | var points []bmath.Vector2d 51 | 52 | genScale := bm.previousSpeed 53 | 54 | aggressiveness := settings.Dance.Bezier.Aggressiveness 55 | sliderAggressiveness := settings.Dance.Bezier.SliderAggressiveness 56 | 57 | if endPos == startPos { 58 | points = []bmath.Vector2d{endPos, startPos} 59 | } else if ok1 && ok2 { 60 | endAngle := s1.GetEndAngle() 61 | startAngle := s2.GetStartAngle() 62 | bm.pt = bmath.NewVec2dRad(endAngle, s1.GetPointAt(endTime-10).Dst(endPos)*aggressiveness*sliderAggressiveness/10).Add(endPos) 63 | pt2 := bmath.NewVec2dRad(startAngle, s2.GetPointAt(startTime+10).Dst(startPos)*aggressiveness*sliderAggressiveness/10).Add(startPos) 64 | points = []bmath.Vector2d{endPos, bm.pt, pt2, startPos} 65 | } else if ok1 { 66 | endAngle := s1.GetEndAngle() 67 | pt1 := bmath.NewVec2dRad(endAngle, s1.GetPointAt(endTime-10).Dst(endPos)*aggressiveness*sliderAggressiveness/10).Add(endPos) 68 | bm.pt = bmath.NewVec2dRad(startPos.AngleRV(bm.pt), genScale*aggressiveness).Add(startPos) 69 | points = []bmath.Vector2d{endPos, pt1, bm.pt, startPos} 70 | } else if ok2 { 71 | startAngle := s2.GetStartAngle() 72 | bm.pt = bmath.NewVec2dRad(endPos.AngleRV(bm.pt), genScale*aggressiveness).Add(endPos) 73 | pt1 := bmath.NewVec2dRad(startAngle, s2.GetPointAt(startTime+10).Dst(startPos)*aggressiveness*sliderAggressiveness/10).Add(startPos) 74 | points = []bmath.Vector2d{endPos, bm.pt, pt1, startPos} 75 | } else { 76 | angle := endPos.AngleRV(bm.pt) 77 | if math.IsNaN(angle) { 78 | angle = 0 79 | } 80 | bm.pt = bmath.NewVec2dRad(angle, bm.previousSpeed*aggressiveness).Add(endPos) 81 | 82 | points = []bmath.Vector2d{endPos, bm.pt, startPos} 83 | } 84 | 85 | bm.bz = curves.NewBezier(points) 86 | 87 | bm.endTime = endTime 88 | bm.beginTime = startTime 89 | bm.previousSpeed = (dst + 1.0) / float64(startTime-endTime) 90 | } 91 | 92 | func (bm *BezierMover) Update(time int64) bmath.Vector2d { 93 | return bm.bz.NPointAt(float64(time-bm.endTime) / float64(bm.beginTime-bm.endTime)) 94 | } 95 | 96 | func (bm *BezierMover) GetEndTime() int64 { 97 | return bm.beginTime 98 | } 99 | -------------------------------------------------------------------------------- /beatmap/beatmap.go: -------------------------------------------------------------------------------- 1 | package beatmap 2 | 3 | import ( 4 | "danser/audio" 5 | "danser/beatmap/objects" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type BeatMap struct { 12 | Artist, ArtistUnicode, Name, NameUnicode, Difficulty, Creator, Source, Tags string 13 | // 加入OD 14 | SliderMultiplier, StackLeniency, CircleSize, AR, ARms, FadeIn, OD, OD300, OD100, OD50, ODMiss float64 15 | Dir, File, Audio, Bg, MD5, PausesText, TimingPoints string 16 | 17 | LastModified, TimeAdded, PlayCount, LastPlayed, PreviewTime int64 18 | 19 | Timings *objects.Timings 20 | HitObjects []objects.BaseObject 21 | Pauses []objects.BaseObject 22 | Queue []objects.BaseObject 23 | } 24 | 25 | func NewBeatMap() *BeatMap { 26 | return &BeatMap{Timings: objects.NewTimings(), AR: 8.0, StackLeniency: 0.7} 27 | } 28 | 29 | func (b *BeatMap) Reset() { 30 | b.Queue = make([]objects.BaseObject, len(b.HitObjects)) 31 | copy(b.Queue, b.HitObjects) 32 | b.Timings.Reset() 33 | for _, o := range b.HitObjects { 34 | o.SetDifficulty(b.ARms, b.FadeIn) 35 | } 36 | } 37 | 38 | func (b *BeatMap) Update(time int64) { 39 | b.Timings.Update(time) 40 | if len(b.Queue) > 0 { 41 | for i := 0; i < len(b.Queue); i++ { 42 | g := b.Queue[i] 43 | if g.GetBasicData().StartTime > time { 44 | break 45 | } 46 | 47 | if isDone := g.Update(time); isDone { 48 | if i < len(b.Queue)-1 { 49 | b.Queue = append(b.Queue[:i], b.Queue[i+1:]...) 50 | } else if i < len(b.Queue) { 51 | b.Queue = b.Queue[:i] 52 | } 53 | i-- 54 | } 55 | } 56 | } 57 | 58 | } 59 | func (beatMap *BeatMap) GetObjectsCopy() []objects.BaseObject { 60 | queue := make([]objects.BaseObject, len(beatMap.HitObjects)) 61 | copy(queue, beatMap.HitObjects) 62 | return queue 63 | } 64 | 65 | func (beatMap *BeatMap) LoadTimingPoints() { 66 | 67 | points := strings.Split(beatMap.TimingPoints, "|") 68 | 69 | if len(points) == 1 && points[0] == "" { 70 | return 71 | } 72 | 73 | for _, point := range points { 74 | line := strings.Split(point, ",") 75 | time, _ := strconv.ParseInt(line[0], 10, 64) 76 | bpm, _ := strconv.ParseFloat(line[1], 64) 77 | if len(line) > 3 { 78 | sampleset, _ := strconv.ParseInt(line[3], 10, 64) 79 | sampleindex, _ := strconv.ParseInt(line[4], 10, 64) 80 | 81 | samplevolume := int64(100) 82 | 83 | if len(line) > 5 { 84 | samplevolume, _ = strconv.ParseInt(line[5], 10, 64) 85 | } 86 | 87 | beatMap.Timings.LastSet = int(sampleset) 88 | beatMap.Timings.AddPoint(time, bpm, int(sampleset), int(sampleindex), float64(samplevolume)/100) 89 | } else { 90 | beatMap.Timings.AddPoint(time, bpm, beatMap.Timings.LastSet, 1, 1) 91 | } 92 | } 93 | } 94 | 95 | func (beatMap *BeatMap) LoadCustomSamples() { 96 | audio.LoadBeatmapSamples(beatMap.Dir) 97 | } 98 | 99 | func (beatMap *BeatMap) LoadPauses() { 100 | points := strings.Split(beatMap.PausesText, ",") 101 | 102 | if len(points) < 2 { 103 | return 104 | } 105 | 106 | for i := 0; i < len(points); i += 2 { 107 | line := []string{"2", points[i], points[i+1]} 108 | beatMap.Pauses = append(beatMap.Pauses, objects.NewPause(line)) 109 | } 110 | } 111 | 112 | func (beatMap *BeatMap) UpdatePlayStats() { 113 | beatMap.PlayCount += 1 114 | beatMap.LastPlayed = time.Now().UnixNano() / 1000000 115 | } 116 | -------------------------------------------------------------------------------- /settings/manager.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | var fileStorage *fileformat 10 | var fileName string 11 | 12 | func initDefaults() { 13 | Version = SETTINGSVERSION 14 | General = &general{os.Getenv("localappdata") + string(os.PathSeparator) + "osu!" + string(os.PathSeparator) + "Songs" + string(os.PathSeparator)} 15 | VSplayer = &vsplayer{ 16 | &playerinfo{ 17 | 1, 18 | false, 19 | "", 20 | "", 21 | }, 22 | &playerinfoUI{ 23 | 6, 24 | 14, 25 | 696, 26 | false, 27 | false, 28 | true, 29 | 1000, 30 | false, 31 | false, 32 | false, 33 | 1.3, 34 | 0.6, 35 | }, 36 | &recordinfoUI{ 37 | "who are you", 38 | "1970.01.01", 39 | 1019, 40 | 35, 41 | 18, 42 | 0.5, 43 | }, 44 | &diffinfoUI{ 45 | true, 46 | 1049, 47 | 678, 48 | 18, 49 | 0.8, 50 | }, 51 | &playerfieldUI{ 52 | 200, 53 | 25, 54 | 13, 55 | false, 56 | }, 57 | &mapinfo{ 58 | "title", 59 | "difficulty", 60 | }, 61 | &mods{ 62 | false, 63 | false, 64 | false, 65 | false, 66 | false, 67 | }, 68 | &knockout{ 69 | true, 70 | false, 71 | 2000, 72 | 36, 73 | 1.5, 74 | }, 75 | &replayandcache{ 76 | "replays\\", 77 | "cache\\", 78 | true, 79 | false, 80 | }, 81 | &errorfix{ 82 | false, 83 | "error.err", 84 | }, 85 | &skin{ 86 | false, 87 | "skin\\", 88 | 0, 89 | }, 90 | } 91 | Graphics = &graphics{1920, 1080, 1280, 720, true, false, 1000, 16} 92 | Audio = &audio{0.5, 0.5, 0.5, 0, false, false} 93 | Beat = &beat{1.2} 94 | Cursor = &cursor{&color{true, 8, &hsv{0, 1.0, 1.0}, false, 0, false, 0, 0}, true, -36, true, true, -36.0, false, 18, true, true, false, 0.4, 0.5, 2000, 1, 0.4, 0.9} 95 | Objects = &objects{5, 0.3, true, true, &color{true, 8, &hsv{0, 1.0, 1.0}, false, 0, true, 100.0, 0}, -1, 1.2, true, 30, 50, true, true, true, true, true, 0.0, false, &color{false, 8, &hsv{0, 0.0, 1.0}, false, 0, true, 100.0, 0}, true, 18, true} 96 | Playfield = &playfield{5, 2, 5, 0, 0.95, 0.95, true, 0, 0.6, 0.6, 0, 1, 0, true, 1, true, true, 0.8, 1.1, 0, false, 2, true, true, 0.3, &bloom{0.0, 0.6, 0.7}} 97 | Dance = &dance{Bezier: &bezier{60, 3}, Flower: &flower{true, 90, 0.666, 130, 90, -1, 0.7, false}, HalfCircle: &circular{1, 130}} 98 | fileStorage = &fileformat{&Version, General, VSplayer, Graphics, Audio, Beat, Cursor, Objects, Playfield, Dance} 99 | } 100 | 101 | func LoadSettings(version int) bool { 102 | initDefaults() 103 | fileName = "settings" 104 | 105 | if version > 0 { 106 | fileName += "-" + strconv.FormatInt(int64(version), 10) 107 | } 108 | fileName += ".json" 109 | 110 | file, err := os.Open(fileName) 111 | defer file.Close() 112 | if os.IsNotExist(err) { 113 | saveSettings(fileName) 114 | return true 115 | } else if err != nil { 116 | panic(err) 117 | } else { 118 | load(file) 119 | saveSettings(fileName) //this is done to save additions from the current format 120 | } 121 | 122 | return false 123 | } 124 | 125 | func load(file *os.File) { 126 | decoder := json.NewDecoder(file) 127 | decoder.Decode(fileStorage) 128 | } 129 | 130 | func Save() { 131 | saveSettings(fileName) 132 | } 133 | 134 | func saveSettings(path string) { 135 | file, err := os.Create(path) 136 | defer file.Close() 137 | 138 | if err != nil && !os.IsExist(err) { 139 | panic(err) 140 | } 141 | 142 | Version = SETTINGSVERSION 143 | encoder := json.NewEncoder(file) 144 | encoder.SetIndent("", "\t") 145 | encoder.Encode(fileStorage) 146 | } 147 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "danser/render/texture" 5 | _ "golang.org/x/image/bmp" 6 | "image" 7 | "image/draw" 8 | _ "image/jpeg" 9 | _ "image/png" 10 | "log" 11 | "os" 12 | "sort" 13 | ) 14 | 15 | func LoadImage(path string) (*image.NRGBA, error) { 16 | file, err := os.Open(path) 17 | log.Println("Loading texture: ", path) 18 | if err != nil { 19 | log.Println("er1") 20 | return nil, err 21 | } 22 | img, _, err := image.Decode(file) 23 | if err != nil { 24 | log.Println("er2") 25 | return nil, err 26 | } 27 | bounds := img.Bounds() 28 | nrgba := image.NewNRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy())) 29 | draw.Draw(nrgba, nrgba.Bounds(), img, bounds.Min, draw.Src) 30 | return nrgba, nil 31 | } 32 | 33 | /*func LoadTexture(path string) (*texture.Texture, error) { 34 | img, err := LoadImage(path) 35 | if err == nil { 36 | tex := glhf.NewTexture( 37 | img.Bounds().Dx(), 38 | img.Bounds().Dy(), 39 | 4, 40 | true, 41 | img.Pix, 42 | ) 43 | 44 | tex.Begin() 45 | tex.SetWrap(glhf.CLAMP_TO_EDGE) 46 | tex.End() 47 | 48 | return tex, nil 49 | } 50 | return nil, err 51 | }*/ 52 | 53 | func LoadTexture(path string) (*texture.TextureSingle, error) { 54 | img, err := LoadImage(path) 55 | if err == nil { 56 | tex := texture.LoadTextureSingle(img, 4) 57 | 58 | return tex, nil 59 | } 60 | return nil, err 61 | } 62 | 63 | func LoadTextureToAtlas(atlas *texture.TextureAtlas, path string) (*texture.TextureRegion, error) { 64 | img, err := LoadImage(path) 65 | if err == nil { 66 | return atlas.AddTexture(path, img.Bounds().Dx(), img.Bounds().Dy(), img.Pix), nil 67 | } 68 | log.Println(err) 69 | return nil, err 70 | } 71 | 72 | /*func LoadTextureU(path string) (*glhf.Texture, error) { 73 | img, err := LoadImage(path) 74 | if err == nil { 75 | tex := glhf.NewTexture( 76 | img.Bounds().Dx(), 77 | img.Bounds().Dy(), 78 | 0, 79 | true, 80 | img.Pix, 81 | ) 82 | 83 | tex.Begin() 84 | tex.SetWrap(glhf.CLAMP_TO_EDGE) 85 | tex.End() 86 | 87 | return tex, nil 88 | } 89 | return nil, err 90 | }*/ 91 | 92 | func PathExists(path string) (bool, error) { 93 | _, err := os.Stat(path) 94 | if err == nil { 95 | return true, nil 96 | } 97 | if os.IsNotExist(err) { 98 | return false, nil 99 | } 100 | return false, err 101 | } 102 | 103 | // 对数组排序,获得其序号(正序) 104 | func SortRankLowToHigh(array []float64) (rank []int) { 105 | var sortarray []float64 106 | sortarray = make([]float64, len(array)) 107 | copy(sortarray, array) 108 | sort.Float64s(sortarray) 109 | rank = make([]int, len(array)) 110 | for i, ar := range array { 111 | rank[i] = firstindexof(sortarray, ar) + 1 112 | } 113 | return rank 114 | } 115 | 116 | // 对数组排序,获得其序号(反序) 117 | func SortRankHighToLow(array []float64) (rank []int) { 118 | var sortarray []float64 119 | sortarray = make([]float64, len(array)) 120 | copy(sortarray, array) 121 | sort.Float64s(sortarray) 122 | rank = make([]int, len(array)) 123 | for i, ar := range array { 124 | rank[i] = len(array) - lastindexof(sortarray, ar) 125 | } 126 | return rank 127 | } 128 | 129 | // 查找第一个指定元素返回的下标 130 | func firstindexof(array []float64, ar float64) int { 131 | error := 0.1 132 | for i, a := range array { 133 | if (ar >= a-error) && (ar <= a+error) { 134 | return i 135 | } 136 | } 137 | return -1 138 | } 139 | 140 | // 查找最后一个指定元素返回的下标 141 | func lastindexof(array []float64, ar float64) int { 142 | error := 0.1 143 | for i := len(array) - 1; i >= 0; i-- { 144 | a := array[i] 145 | if (ar >= a-error) && (ar <= a+error) { 146 | return i 147 | } 148 | } 149 | return -1 150 | } 151 | -------------------------------------------------------------------------------- /render/effects/bloom.go: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | import ( 4 | "danser/render/framebuffer" 5 | "github.com/go-gl/gl/v3.3-core/gl" 6 | "github.com/wieku/glhf" 7 | "io/ioutil" 8 | ) 9 | 10 | type BloomEffect struct { 11 | colFilter *glhf.Shader 12 | combineShader *glhf.Shader 13 | fbo *framebuffer.Framebuffer 14 | 15 | blurEffect *BlurEffect 16 | 17 | blur, threshold, power float64 18 | fboSlice *glhf.VertexSlice 19 | } 20 | 21 | func NewBloomEffect(width, height int) *BloomEffect { 22 | effect := &BloomEffect{} 23 | vertexFormat := glhf.AttrFormat{ 24 | {Name: "in_position", Type: glhf.Vec3}, 25 | {Name: "in_tex_coord", Type: glhf.Vec2}, 26 | } 27 | 28 | uniformFormat := glhf.AttrFormat{ 29 | {Name: "tex", Type: glhf.Int}, 30 | {Name: "threshold", Type: glhf.Float}, 31 | } 32 | 33 | var err error 34 | vert, _ := ioutil.ReadFile("assets/shaders/fbopass.vsh") 35 | frag, _ := ioutil.ReadFile("assets/shaders/brightfilter.fsh") 36 | effect.colFilter, err = glhf.NewShader(vertexFormat, uniformFormat, string(vert), string(frag)) 37 | 38 | if err != nil { 39 | panic("BloomFilter: " + err.Error()) 40 | } 41 | 42 | uniformFormat = glhf.AttrFormat{ 43 | {Name: "tex", Type: glhf.Int}, 44 | {Name: "tex2", Type: glhf.Int}, 45 | {Name: "power", Type: glhf.Float}, 46 | } 47 | frag, _ = ioutil.ReadFile("assets/shaders/combine.fsh") 48 | effect.combineShader, err = glhf.NewShader(vertexFormat, uniformFormat, string(vert), string(frag)) 49 | 50 | if err != nil { 51 | panic("BloomCombine: " + err.Error()) 52 | } 53 | 54 | effect.fboSlice = glhf.MakeVertexSlice(effect.colFilter, 6, 6) 55 | effect.fboSlice.Begin() 56 | effect.fboSlice.SetVertexData([]float32{ 57 | -1, -1, 0, 0, 0, 58 | 1, -1, 0, 1, 0, 59 | -1, 1, 0, 0, 1, 60 | 1, -1, 0, 1, 0, 61 | 1, 1, 0, 1, 1, 62 | -1, 1, 0, 0, 1, 63 | }) 64 | effect.fboSlice.End() 65 | 66 | effect.fbo = framebuffer.NewFrame(width, height, true, false) 67 | 68 | effect.threshold = 0.7 69 | effect.blur = 0.3 70 | effect.power = 1.2 71 | 72 | effect.blurEffect = NewBlurEffect(width, height) 73 | effect.blurEffect.SetBlur(effect.blur, effect.blur) 74 | return effect 75 | } 76 | 77 | func (effect *BloomEffect) SetThreshold(threshold float64) { 78 | effect.threshold = threshold 79 | } 80 | 81 | func (effect *BloomEffect) SetBlur(blur float64) { 82 | effect.blur = blur 83 | effect.blurEffect.SetBlur(blur, blur) 84 | } 85 | 86 | func (effect *BloomEffect) SetPower(power float64) { 87 | effect.power = power 88 | } 89 | 90 | func (effect *BloomEffect) Begin() { 91 | effect.fbo.Begin() 92 | glhf.Clear(0, 0, 0, 0) 93 | } 94 | 95 | func (effect *BloomEffect) EndAndRender() { 96 | effect.fbo.End() 97 | 98 | effect.blurEffect.Begin() 99 | glhf.Clear(0, 0, 0, 0) 100 | 101 | effect.colFilter.Begin() 102 | effect.colFilter.SetUniformAttr(0, int32(0)) 103 | effect.colFilter.SetUniformAttr(1, float32(effect.threshold)) 104 | 105 | effect.fbo.Texture().Bind(0) 106 | 107 | effect.fboSlice.Begin() 108 | effect.fboSlice.Draw() 109 | effect.fboSlice.End() 110 | 111 | effect.colFilter.End() 112 | 113 | gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 114 | 115 | texture := effect.blurEffect.EndAndProcess() 116 | 117 | effect.combineShader.Begin() 118 | effect.combineShader.SetUniformAttr(0, int32(0)) 119 | effect.combineShader.SetUniformAttr(1, int32(1)) 120 | effect.combineShader.SetUniformAttr(2, float32(effect.power)) 121 | 122 | effect.fbo.Texture().Bind(0) 123 | 124 | texture.Bind(1) 125 | 126 | effect.fboSlice.Begin() 127 | effect.fboSlice.Draw() 128 | effect.fboSlice.End() 129 | 130 | effect.combineShader.End() 131 | 132 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 133 | } 134 | -------------------------------------------------------------------------------- /audio/osuaudio.go: -------------------------------------------------------------------------------- 1 | package audio 2 | 3 | import ( 4 | "danser/settings" 5 | "os" 6 | "path/filepath" 7 | "strconv" 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | var Samples [3][5]*Sample 13 | var MapSamples [3][5]map[int]*Sample 14 | 15 | var sets = map[string]int{ 16 | "normal": 1, 17 | "soft": 2, 18 | "drum": 3, 19 | } 20 | 21 | var hitsounds = map[string]int{ 22 | "hitnormal": 1, 23 | "hitwhistle": 2, 24 | "hitfinish": 3, 25 | "hitclap": 4, 26 | "slidertick": 5, 27 | } 28 | 29 | func LoadSamples() { 30 | Samples[0][0] = NewSample("assets/sounds/normal-hitnormal.wav") 31 | Samples[0][1] = NewSample("assets/sounds/normal-hitwhistle.wav") 32 | Samples[0][2] = NewSample("assets/sounds/normal-hitfinish.wav") 33 | Samples[0][3] = NewSample("assets/sounds/normal-hitclap.wav") 34 | Samples[0][4] = NewSample("assets/sounds/normal-slidertick.wav") 35 | 36 | Samples[1][0] = NewSample("assets/sounds/soft-hitnormal.wav") 37 | Samples[1][1] = NewSample("assets/sounds/soft-hitwhistle.wav") 38 | Samples[1][2] = NewSample("assets/sounds/soft-hitfinish.wav") 39 | Samples[1][3] = NewSample("assets/sounds/soft-hitclap.wav") 40 | Samples[1][4] = NewSample("assets/sounds/soft-slidertick.wav") 41 | 42 | Samples[2][0] = NewSample("assets/sounds/drum-hitnormal.wav") 43 | Samples[2][1] = NewSample("assets/sounds/drum-hitwhistle.wav") 44 | Samples[2][2] = NewSample("assets/sounds/drum-hitfinish.wav") 45 | Samples[2][3] = NewSample("assets/sounds/drum-hitclap.wav") 46 | Samples[2][4] = NewSample("assets/sounds/drum-slidertick.wav") 47 | } 48 | 49 | func PlaySample(sampleSet, additionSet, hitsound, index int, volume float64) { 50 | playSample(sampleSet, 0, index, volume) 51 | 52 | if additionSet == 0 { 53 | additionSet = sampleSet 54 | } 55 | 56 | if hitsound&2 > 0 { 57 | playSample(additionSet, 1, index, volume) 58 | } 59 | if hitsound&4 > 0 { 60 | playSample(additionSet, 2, index, volume) 61 | } 62 | if hitsound&8 > 0 { 63 | playSample(additionSet, 3, index, volume) 64 | } 65 | } 66 | 67 | func playSample(sampleSet int, hitsoundIndex, index int, volume float64) { 68 | if settings.Audio.IgnoreBeatmapSampleVolume { 69 | volume = 1.0 70 | } 71 | 72 | if sample := MapSamples[sampleSet-1][hitsoundIndex][index]; sample != nil { 73 | sample.PlayRV(volume) 74 | } else { 75 | Samples[sampleSet-1][hitsoundIndex].PlayRV(volume) 76 | } 77 | } 78 | 79 | func PlaySliderTick(sampleSet, index int, volume float64) { 80 | playSample(sampleSet, 4, index, volume) 81 | } 82 | 83 | func LoadBeatmapSamples(dir string) { 84 | splitBeforeDigit := func(name string) []string { 85 | for i, r := range name { 86 | if unicode.IsDigit(r) { 87 | return []string{name[:i], name[i:]} 88 | } 89 | } 90 | return []string{name} 91 | } 92 | 93 | fullPath := settings.General.OsuSongsDir + string(os.PathSeparator) + dir 94 | 95 | filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error { 96 | if !strings.HasSuffix(info.Name(), ".wav") { 97 | return nil 98 | } 99 | 100 | rawName := strings.TrimSuffix(info.Name(), ".wav") 101 | 102 | if separated := strings.Split(rawName, "-"); len(separated) == 2 { 103 | 104 | setID := sets[separated[0]] 105 | 106 | if setID == 0 { 107 | return nil 108 | } 109 | 110 | subSeparated := splitBeforeDigit(separated[1]) 111 | 112 | hitSoundIndex := 1 113 | 114 | if len(subSeparated) > 1 { 115 | index, err := strconv.ParseInt(subSeparated[1], 10, 32) 116 | 117 | if err != nil { 118 | return nil 119 | } 120 | 121 | hitSoundIndex = int(index) 122 | } 123 | 124 | hitSoundID := hitsounds[subSeparated[0]] 125 | 126 | if hitSoundID == 0 { 127 | return nil 128 | } 129 | 130 | if MapSamples[setID-1][hitSoundID-1] == nil { 131 | MapSamples[setID-1][hitSoundID-1] = make(map[int]*Sample) 132 | } 133 | 134 | MapSamples[setID-1][hitSoundID-1][hitSoundIndex] = NewSample(path) 135 | 136 | } 137 | 138 | return nil 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /render/effects/blur.go: -------------------------------------------------------------------------------- 1 | package effects 2 | 3 | import ( 4 | "danser/render/framebuffer" 5 | "danser/render/texture" 6 | "danser/settings" 7 | "github.com/go-gl/gl/v3.3-core/gl" 8 | "github.com/go-gl/mathgl/mgl32" 9 | "github.com/wieku/glhf" 10 | "io/ioutil" 11 | "math" 12 | ) 13 | 14 | type BlurEffect struct { 15 | blurShader *glhf.Shader 16 | fbo1 *framebuffer.Framebuffer 17 | fbo2 *framebuffer.Framebuffer 18 | kernelSize mgl32.Vec2 19 | sigma mgl32.Vec2 20 | size mgl32.Vec2 21 | fboSlice *glhf.VertexSlice 22 | } 23 | 24 | func NewBlurEffect(width, height int) *BlurEffect { 25 | effect := &BlurEffect{} 26 | vertexFormat := glhf.AttrFormat{ 27 | {Name: "in_position", Type: glhf.Vec3}, 28 | {Name: "in_tex_coord", Type: glhf.Vec2}, 29 | } 30 | 31 | uniformFormat := glhf.AttrFormat{ 32 | {Name: "tex", Type: glhf.Int}, 33 | {Name: "kernelSize", Type: glhf.Vec2}, 34 | {Name: "direction", Type: glhf.Vec2}, 35 | {Name: "sigma", Type: glhf.Vec2}, 36 | {Name: "size", Type: glhf.Vec2}, 37 | } 38 | 39 | var err error 40 | vert, _ := ioutil.ReadFile("assets/shaders/fbopass.vsh") 41 | frag, _ := ioutil.ReadFile("assets/shaders/blur.fsh") 42 | effect.blurShader, err = glhf.NewShader(vertexFormat, uniformFormat, string(vert), string(frag)) 43 | 44 | if err != nil { 45 | panic("Blur: " + err.Error()) 46 | } 47 | 48 | effect.fboSlice = glhf.MakeVertexSlice(effect.blurShader, 6, 6) 49 | effect.fboSlice.Begin() 50 | effect.fboSlice.SetVertexData([]float32{ 51 | -1, -1, 0, 0, 0, 52 | 1, -1, 0, 1, 0, 53 | -1, 1, 0, 0, 1, 54 | 1, -1, 0, 1, 0, 55 | 1, 1, 0, 1, 1, 56 | -1, 1, 0, 0, 1, 57 | }) 58 | effect.fboSlice.End() 59 | 60 | effect.fbo1 = framebuffer.NewFrame(width, height, true, false) 61 | effect.fbo2 = framebuffer.NewFrame(width, height, true, false) 62 | effect.SetBlur(0, 0) 63 | effect.size = mgl32.Vec2{float32(width), float32(height)} 64 | return effect 65 | } 66 | 67 | func (effect *BlurEffect) SetBlur(blurX, blurY float64) { 68 | sigmaX, sigmaY := float32(blurX)*25, float32(blurY)*25 69 | kX := kernelSize(sigmaX) 70 | if kX == 0 { 71 | sigmaX = 1.0 72 | } 73 | kY := kernelSize(sigmaY) 74 | if kY == 0 { 75 | sigmaY = 1.0 76 | } 77 | effect.kernelSize = mgl32.Vec2{float32(kX), float32(kY)} 78 | effect.sigma = mgl32.Vec2{sigmaX, sigmaY} 79 | } 80 | 81 | func gauss(x int, sigma float32) float32 { 82 | factor := float32(0.398942) 83 | return factor * float32(math.Exp(-0.5*float64(x*x)/float64(sigma*sigma))) / sigma 84 | } 85 | 86 | func kernelSize(sigma float32) int { 87 | if sigma == 0 { 88 | return 0 89 | } 90 | baseG := gauss(0, sigma) * 0.1 91 | max := 200 92 | 93 | for i := 1; i <= max; i++ { 94 | if gauss(i, sigma) < baseG { 95 | return i - 1 96 | } 97 | } 98 | return max 99 | } 100 | 101 | func (effect *BlurEffect) Begin() { 102 | effect.fbo1.Begin() 103 | glhf.Clear(0, 0, 0, 1) 104 | gl.Viewport(0, 0, int32(effect.fbo1.Texture().GetWidth()), int32(effect.fbo1.Texture().GetHeight())) 105 | } 106 | 107 | func (effect *BlurEffect) EndAndProcess() texture.Texture { 108 | effect.fbo1.End() 109 | 110 | effect.blurShader.Begin() 111 | effect.blurShader.SetUniformAttr(0, int32(0)) 112 | effect.blurShader.SetUniformAttr(1, effect.kernelSize) 113 | effect.blurShader.SetUniformAttr(2, mgl32.Vec2{1, 0}) 114 | effect.blurShader.SetUniformAttr(3, effect.sigma) 115 | effect.blurShader.SetUniformAttr(4, effect.size) 116 | 117 | effect.fboSlice.Begin() 118 | 119 | effect.fbo2.Begin() 120 | glhf.Clear(0, 0, 0, 0) 121 | 122 | effect.fbo1.Texture().Bind(0) 123 | 124 | effect.fboSlice.Draw() 125 | 126 | effect.fbo2.End() 127 | 128 | effect.fbo1.Begin() 129 | glhf.Clear(0, 0, 0, 0) 130 | 131 | effect.fbo2.Texture().Bind(0) 132 | 133 | effect.blurShader.SetUniformAttr(2, mgl32.Vec2{0, 1}) 134 | effect.fboSlice.Draw() 135 | 136 | effect.fbo1.End() 137 | 138 | effect.fboSlice.End() 139 | effect.blurShader.End() 140 | gl.Viewport(0, 0, int32(settings.Graphics.GetWidth()), int32(settings.Graphics.GetHeight())) 141 | return effect.fbo1.Texture() 142 | } 143 | -------------------------------------------------------------------------------- /beatmap/objects/util.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "danser/bmath" 5 | "danser/render" 6 | "strconv" 7 | ) 8 | 9 | func GetObject(data []string, number int64) (BaseObject, int64) { 10 | objType, _ := strconv.ParseInt(data[3], 10, 64) 11 | objType = objType % 16 12 | newnumber := number 13 | if (objType & CIRCLE) > 0 { 14 | if objType == NEWCIRCLE { 15 | // 新的combo 16 | newnumber = 1 17 | } else { 18 | // 继续combo 19 | newnumber += 1 20 | } 21 | return NewCircle(data, newnumber), newnumber 22 | } else if (objType & SLIDER) > 0 { 23 | if objType == NEWSLIDER { 24 | // 新的combo 25 | newnumber = 1 26 | } else { 27 | // 继续combo 28 | newnumber += 1 29 | } 30 | sl := NewSlider(data, newnumber) 31 | if sl == nil { 32 | return nil, newnumber 33 | } else { 34 | return sl, newnumber 35 | } 36 | } else if (objType & SPINNNER) > 0 { 37 | if objType == NEWSPINNNER { 38 | // 新的combo 39 | newnumber = 1 40 | } else { 41 | // 继续combo 42 | newnumber += 1 43 | } 44 | return NewSpinner(data, newnumber), newnumber 45 | } 46 | return nil, newnumber 47 | } 48 | 49 | func GetObjectbyPath(data []string, number int64, isHR bool) (BaseObject, int64) { 50 | objType, _ := strconv.ParseInt(data[3], 10, 64) 51 | objType = objType % 16 52 | newnumber := number 53 | if (objType & CIRCLE) > 0 { 54 | if objType == NEWCIRCLE { 55 | // 新的combo 56 | newnumber = 1 57 | } else { 58 | // 继续combo 59 | newnumber += 1 60 | } 61 | return NewCirclebyPath(data, newnumber, isHR), newnumber 62 | } else if (objType & SLIDER) > 0 { 63 | if objType == NEWSLIDER { 64 | // 新的combo 65 | newnumber = 1 66 | } else { 67 | // 继续combo 68 | newnumber += 1 69 | } 70 | sl := NewSliderbyPath(data, newnumber, isHR) 71 | if sl == nil { 72 | return nil, newnumber 73 | } else { 74 | return sl, newnumber 75 | } 76 | } else if (objType & SPINNNER) > 0 { 77 | if objType == NEWSPINNNER { 78 | // 新的combo 79 | newnumber = 1 80 | } else { 81 | // 继续combo 82 | newnumber += 1 83 | } 84 | return NewSpinner(data, newnumber), newnumber 85 | } 86 | return nil, newnumber 87 | } 88 | 89 | // 绘制圈内数字 90 | func DrawHitCircleNumber(number int64, position bmath.Vector2d, batch *render.SpriteBatch) { 91 | switch number { 92 | case 0: 93 | batch.DrawUnitN(*render.Circle0, position) 94 | break 95 | case 1: 96 | batch.DrawUnitN(*render.Circle1, position) 97 | break 98 | case 2: 99 | batch.DrawUnitN(*render.Circle2, position) 100 | break 101 | case 3: 102 | batch.DrawUnitN(*render.Circle3, position) 103 | break 104 | case 4: 105 | batch.DrawUnitN(*render.Circle4, position) 106 | break 107 | case 5: 108 | batch.DrawUnitN(*render.Circle5, position) 109 | break 110 | case 6: 111 | batch.DrawUnitN(*render.Circle6, position) 112 | break 113 | case 7: 114 | batch.DrawUnitN(*render.Circle7, position) 115 | break 116 | case 8: 117 | batch.DrawUnitN(*render.Circle8, position) 118 | break 119 | case 9: 120 | batch.DrawUnitN(*render.Circle9, position) 121 | break 122 | } 123 | } 124 | 125 | // 获取圈内数字宽度 126 | func GetHitCircleNumberWidth(number int64) int32 { 127 | switch number { 128 | case 0: 129 | return render.Circle0.Width 130 | case 1: 131 | return render.Circle1.Width 132 | case 2: 133 | return render.Circle2.Width 134 | case 3: 135 | return render.Circle3.Width 136 | case 4: 137 | return render.Circle4.Width 138 | case 5: 139 | return render.Circle5.Width 140 | case 6: 141 | return render.Circle6.Width 142 | case 7: 143 | return render.Circle7.Width 144 | case 8: 145 | return render.Circle8.Width 146 | case 9: 147 | return render.Circle9.Width 148 | } 149 | return 0 150 | } 151 | 152 | // 限制范围 153 | func Clamp(value float64, min float64, max float64) float64 { 154 | if value < min { 155 | return min 156 | } else if value > max { 157 | return max 158 | } else { 159 | return value 160 | } 161 | } 162 | 163 | const ( 164 | CIRCLE int64 = 1 165 | SLIDER int64 = 2 166 | SPINNNER int64 = 8 167 | NEWCIRCLE int64 = 5 168 | NEWSLIDER int64 = 6 169 | NEWSPINNNER int64 = 12 170 | ) 171 | -------------------------------------------------------------------------------- /bmath/sliders/sliderAlgo.go: -------------------------------------------------------------------------------- 1 | package sliders 2 | 3 | import ( 4 | "danser/bmath" 5 | m2 "danser/bmath" 6 | "danser/bmath/curves" 7 | . "danser/osuconst" 8 | "math" 9 | ) 10 | 11 | type SliderAlgo struct { 12 | curves []curves.Curve 13 | sections []float64 14 | length float64 15 | scale float64 16 | typ string 17 | startPos bmath.Vector2d 18 | endPos bmath.Vector2d 19 | } 20 | 21 | func NewSliderAlgo(typ string, points []m2.Vector2d, desiredLength float64) SliderAlgo { 22 | // 另外保存开始点和结束点 23 | startPos := points[0] 24 | endPos := points[len(points)-1] 25 | 26 | var curveList []curves.Curve 27 | 28 | var length = 0.0 29 | if len(points) < 3 { 30 | typ = "L" 31 | } 32 | switch typ { 33 | case "P": 34 | c := curves.NewCirArc(points[0], points[1], points[2]) 35 | if !c.Unstable { 36 | curveList = append(curveList, c) 37 | length += c.GetLength() 38 | break 39 | } 40 | fallthrough 41 | case "L": 42 | for i := 1; i < len(points); i++ { 43 | c := curves.NewLinear(points[i-1], points[i]) 44 | curveList = append(curveList, c) 45 | length += c.GetLength() 46 | } 47 | break 48 | case "B": 49 | lastIndex := 0 50 | for i, p := range points { 51 | if (i == len(points)-1 && p != points[i-1]) || (i < len(points)-1 && points[i+1] == p) { 52 | pts := points[lastIndex : i+1] 53 | var c curves.Curve 54 | if len(pts) > 2 { 55 | c = curves.NewBezier(pts) 56 | } else if len(pts) == 1 { 57 | c = curves.NewLinear(pts[0], pts[0]) 58 | } else { 59 | c = curves.NewLinear(pts[0], pts[1]) 60 | } 61 | 62 | curveList = append(curveList, c) 63 | length += c.GetLength() 64 | lastIndex = i + 1 65 | } 66 | } 67 | break 68 | case "C": 69 | 70 | if points[0] != points[1] { 71 | points = append([]bmath.Vector2d{points[0]}, points...) 72 | } 73 | 74 | if points[len(points)-1] != points[len(points)-2] { 75 | points = append(points, points[len(points)-1]) 76 | } 77 | 78 | for i := 0; i < len(points)-3; i++ { 79 | c := curves.NewCatmull(points[i : i+4]) 80 | curveList = append(curveList, c) 81 | length += c.GetLength() 82 | } 83 | break 84 | } 85 | 86 | scale := -1.0 87 | 88 | if length > desiredLength { 89 | scale = desiredLength / length 90 | } else if desiredLength > length { 91 | last := curveList[len(curveList)-1] 92 | p2 := last.PointAt(1) 93 | p3 := bmath.NewVec2dRad(last.GetEndAngle()+math.Pi, desiredLength-length).Add(p2) 94 | c := curves.NewLinear(p2, p3) 95 | curveList = append(curveList, c) 96 | length += c.GetLength() 97 | } 98 | 99 | sections := make([]float64, len(curveList)+1) 100 | sections[0] = 0.0 101 | prev := 0.0 102 | if len(curveList) > 1 { 103 | for i := 0; i < len(curveList); i++ { 104 | prev += curveList[i].GetLength() / length 105 | sections[i+1] = prev 106 | } 107 | } 108 | 109 | if typ == "P" { 110 | 111 | } 112 | 113 | return SliderAlgo{curveList, sections, length, scale, typ, startPos, endPos} 114 | } 115 | 116 | func (sa *SliderAlgo) PointAt(t float64) m2.Vector2d { 117 | if sa.scale > -0.5 { 118 | t *= sa.scale 119 | } 120 | if len(sa.curves) == 1 { 121 | return sa.curves[0].PointAt(t) 122 | } else { 123 | t = sa.sections[len(sa.sections)-1] * t 124 | for i := 1; i < len(sa.sections); i++ { 125 | if t <= sa.sections[i] || i == len(sa.sections)-1 { 126 | prc := (t - sa.sections[i-1]) / (sa.sections[i] - sa.sections[i-1]) 127 | return sa.curves[i-1].PointAt(prc) 128 | } 129 | } 130 | } 131 | return m2.NewVec2d(PLAYFIELD_WIDTH/2, PLAYFIELD_HEIGHT/2) 132 | } 133 | 134 | func (sa *SliderAlgo) PointAtTail(t float64) m2.Vector2d { 135 | if len(sa.curves) == 1 { 136 | return sa.curves[0].PointAt(t) 137 | } else { 138 | t = sa.sections[len(sa.sections)-1] * t 139 | for i := 1; i < len(sa.sections); i++ { 140 | if t <= sa.sections[i] || i == len(sa.sections)-1 { 141 | prc := (t - sa.sections[i-1]) / (sa.sections[i] - sa.sections[i-1]) 142 | return sa.curves[i-1].PointAt(prc) 143 | } 144 | } 145 | } 146 | return m2.NewVec2d(PLAYFIELD_WIDTH/2, PLAYFIELD_HEIGHT/2) 147 | } 148 | 149 | func (sa *SliderAlgo) GetLength() float64 { 150 | if sa.scale > -0.5 { 151 | return sa.length * sa.scale 152 | } 153 | return sa.length 154 | } 155 | 156 | func (sa *SliderAlgo) GetPointsLen() int { 157 | return len(sa.curves) 158 | } 159 | -------------------------------------------------------------------------------- /assets/textures/skin.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | Name: AngeLMegumin's private skin Strato's remake 3 | Author: tarora 4 | Version: 2.2 5 | 6 | 7 | 8 | 9 | SliderBallFlip: 1 10 | CursorRotate: 0 11 | //CursorTrailRotate: 0 12 | CursorExpand: 1 13 | CursorCentre: 1 14 | SliderBallFrames: 6 15 | SpinnerFadePlayfield: 1 16 | ComboBurstRandom: 0 17 | HitCircleOverlayAboveNumer: 75 18 | AnimationFramerate: 7 19 | //CustomComboBurstSounds: 30,60,100,150,200,250,300 20 | //SliderStyle: 1 21 | 22 | [Colours] 23 | Combo1: 255,0,0 24 | Combo2: 255,120,0 25 | Combo3: 255,255,255 26 | 27 | InputOverlayText: 255,255,255 28 | SliderBorder: 179, 109, 255 29 | SliderTrackOverride: 0,0,0 30 | //SpinnerApproachCircle: 77,139,217 31 | SongSelectActiveText: 255,182,193 32 | //SongSelectInactiveText: 255,255,255 33 | //StarBreakAdditive: 0,0,0 34 | MenuGlow: 0,78,155 35 | 36 | [Fonts] 37 | HitCirclePrefix: default 38 | HitCircleOverlap: 50 39 | 40 | ScorePrefix: score 41 | ScoreOverlap: -5 42 | 43 | 44 | [Mania] 45 | Keys: 1 46 | //Mania skin config 47 | ColumnStart: 275 48 | HitPosition: 390 49 | UpsideDown: 0 50 | JudgementLine: 1 51 | ScorePosition: 230 52 | ComboPosition: 160 53 | LightFramePerSecond: 60 54 | ColumnWidth: 64 55 | //Colours 56 | //images 57 | //Keys 58 | [Mania] 59 | Keys: 2 60 | //Mania skin config 61 | ColumnStart: 275 62 | HitPosition: 390 63 | UpsideDown: 0 64 | JudgementLine: 1 65 | ScorePosition: 230 66 | ComboPosition: 160 67 | LightFramePerSecond: 60 68 | ColumnWidth: 30,30 69 | //Colours 70 | //images 71 | //Keys 72 | [Mania] 73 | Keys: 3 74 | //Mania skin config 75 | ColumnStart: 275 76 | HitPosition: 390 77 | UpsideDown: 0 78 | JudgementLine: 1 79 | ScorePosition: 230 80 | ComboPosition: 160 81 | LightFramePerSecond: 60 82 | ColumnWidth: 30,30,30 83 | //Colours 84 | //images 85 | //Keys 86 | [Mania] 87 | Keys: 4 88 | //Mania skin config 89 | ColumnStart: 345 90 | HitPosition: 390 91 | UpsideDown: 0 92 | JudgementLine: 1 93 | ScorePosition: 230 94 | ComboPosition: 160 95 | LightFramePerSecond: 24 96 | ColumnWidth: 50,50,50,50 97 | //Colours 98 | Colour1: 0,0,0,230 99 | Colour2: 0,0,0,230 100 | Colour3: 0,0,0,230 101 | Colour4: 0,0,0,230 102 | ColourHold: 255,255,255,255 103 | //Keys 104 | ColumnLineWidth: 0,0,0,0,0 105 | 106 | //Скин кукизи в 2к16 xDddDDdDdDDDD 107 | [Mania] 108 | Keys: 5 109 | //Mania skin config 110 | ColumnStart: 320 111 | HitPosition: 390 112 | UpsideDown: 0 113 | JudgementLine: 1 114 | ScorePosition: 230 115 | ComboPosition: 160 116 | LightFramePerSecond: 24 117 | ColumnWidth: 45,45,45,45,45 118 | //Colours 119 | Colour1: 0,0,0,255 120 | Colour2: 0,0,0,255 121 | Colour3: 0,0,0,255 122 | Colour4: 0,0,0,255 123 | Colour5: 0,0,0,255 124 | ColourHold: 255,255,255,255 125 | //Keys 126 | ColumnLineWidth: 0,0,0,0,0,0 127 | [Mania] 128 | Keys: 6 129 | //Mania skin config 130 | ColumnStart: 275 131 | HitPosition: 390 132 | UpsideDown: 0 133 | JudgementLine: 1 134 | ScorePosition: 230 135 | ComboPosition: 160 136 | LightFramePerSecond: 60 137 | ColumnWidth: 44,44,44,44,44,44 138 | //Colours 139 | Colour1: 0,0,0,255 140 | Colour2: 0,0,0,255 141 | Colour3: 0,0,0,255 142 | Colour4: 0,0,0,255 143 | Colour5: 0,0,0,255 144 | Colour6: 0,0,0,255 145 | ColourHold: 255,255,255,255 146 | //Keys 147 | ColumnLineWidth: 0,0,0,0,0,0,0 148 | [Mania] 149 | Keys: 7 150 | //Mania skin config 151 | ColumnStart: 275 152 | HitPosition: 390 153 | UpsideDown: 0 154 | JudgementLine: 0 155 | ScorePosition: 285 156 | ComboPosition: 170 157 | LightFramePerSecond: 24 158 | ColumnWidth: 44,44,44,44,44,44,44 159 | //Colours 160 | Colour1: 0,0,0,230 161 | Colour2: 0,0,0,230 162 | Colour3: 0,0,0,230 163 | Colour4: 0,0,0,230 164 | Colour5: 0,0,0,230 165 | Colour6: 0,0,0,230 166 | Colour7: 0,0,0,230 167 | ColourHold: 255,255,255,255 168 | ColourBarline: 255,255,255,150 169 | //images 170 | //Keys 171 | ColumnLineWidth: 0,0,0,0,0,0,0,0 172 | [Mania] 173 | Keys: 8 174 | //Mania skin config 175 | ColumnStart: 170 176 | HitPosition: 390 177 | UpsideDown: 0 178 | JudgementLine: 1 179 | ScorePosition: 260 180 | ComboPosition: 111 181 | LightFramePerSecond: 60 182 | ColumnWidth: 64,64,64,64,64,64,64,64 183 | //Colours 184 | Colour1: 0,0,0,255 185 | Colour2: 0,0,0,255 186 | Colour3: 0,0,0,255 187 | Colour4: 0,0,0,255 188 | Colour5: 0,0,0,255 189 | Colour6: 0,0,0,255 190 | Colour7: 0,0,0,255 191 | Colour8: 0,0,0,255 192 | //Keys 193 | ColumnLineWidth: 0,0,0,0,0,0,0,0,0 194 | [Mania] 195 | Keys: 9 196 | //Mania skin config 197 | ColumnStart: 275 198 | HitPosition: 390 199 | UpsideDown: 0 200 | JudgementLine: 1 201 | ScorePosition: 325 202 | ComboPosition: 111 203 | LightFramePerSecond: 60 204 | ColumnWidth: 30,30,30,30,30,30,30,30,30 205 | //Colours 206 | //images 207 | //Keys 208 | -------------------------------------------------------------------------------- /audio/music.go: -------------------------------------------------------------------------------- 1 | package audio 2 | 3 | /* 4 | #include "bass.h" 5 | #include "bass_fx.h" 6 | 7 | extern void musicCallback(DWORD); 8 | 9 | static inline void SyncFunc(HSYNC handle, DWORD channel, DWORD data, void *user) { 10 | musicCallback(channel); 11 | } 12 | 13 | static inline void setSync(HCHANNEL channel) { 14 | BASS_ChannelSetSync(channel, BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, SyncFunc, 0); 15 | } 16 | */ 17 | import "C" 18 | import ( 19 | "unsafe" 20 | //"log" 21 | "danser/settings" 22 | "math" 23 | ) 24 | 25 | const ( 26 | MUSIC_STOPPED = 0 27 | MUSIC_PLAYING = 1 28 | MUSIC_STALLED = 2 29 | MUSIC_PAUSED = 3 30 | ) 31 | 32 | type Callback func() 33 | 34 | var callbacks = make(map[C.DWORD][]Callback) 35 | 36 | //export musicCallback 37 | func musicCallback(channel C.DWORD) { 38 | for _, f := range callbacks[channel] { 39 | f() 40 | } 41 | } 42 | 43 | func registerEndCallback(channel C.DWORD, f func()) { 44 | arr := callbacks[channel] 45 | arr = append(arr, Callback(f)) 46 | callbacks[channel] = arr 47 | C.setSync(channel) 48 | } 49 | 50 | func unregisterEndCallback(channel C.DWORD, f func()) { 51 | arr := callbacks[channel] 52 | var cb Callback = f 53 | for i, v := range arr { 54 | if &v == &cb { 55 | arr = append(arr[:i], arr[i+1:]...) 56 | break 57 | } 58 | } 59 | callbacks[channel] = arr 60 | } 61 | 62 | type Music struct { 63 | channel C.DWORD 64 | fft []float32 65 | beat float64 66 | peak float64 67 | } 68 | 69 | func NewMusic(path string) *Music { 70 | player := new(Music) 71 | channel := C.BASS_StreamCreateFile(0, unsafe.Pointer(C.CString(path)), 0, 0, C.BASS_ASYNCFILE|C.BASS_STREAM_DECODE) 72 | player.channel = C.BASS_FX_TempoCreate(channel, C.BASS_FX_FREESOURCE) 73 | player.fft = make([]float32, 512) 74 | return player 75 | } 76 | 77 | func (wv *Music) Play() { 78 | wv.SetVolume(settings.Audio.GeneralVolume * settings.Audio.MusicVolume) 79 | C.BASS_ChannelPlay(C.DWORD(wv.channel), 1) 80 | } 81 | 82 | func (wv *Music) PlayV(volume float64) { 83 | wv.SetVolume(volume) 84 | C.BASS_ChannelPlay(C.DWORD(wv.channel), 0) 85 | } 86 | 87 | func (wv *Music) RegisterCallback(f func()) { 88 | registerEndCallback(wv.channel, f) 89 | } 90 | 91 | func (wv *Music) UnregisterCallback(f func()) { 92 | unregisterEndCallback(wv.channel, f) 93 | } 94 | 95 | func (wv *Music) Pause() { 96 | C.BASS_ChannelPause(C.DWORD(wv.channel)) 97 | } 98 | 99 | func (wv *Music) Resume() { 100 | C.BASS_ChannelPlay(C.DWORD(wv.channel), 0) 101 | } 102 | 103 | func (wv *Music) Stop() { 104 | C.BASS_ChannelStop(C.DWORD(wv.channel)) 105 | } 106 | 107 | func (wv *Music) SetVolume(vol float64) { 108 | C.BASS_ChannelSetAttribute(C.DWORD(wv.channel), C.BASS_ATTRIB_VOL, C.float(vol)) 109 | } 110 | 111 | func (wv *Music) SetVolumeRelative(vol float64) { 112 | C.BASS_ChannelSetAttribute(C.DWORD(wv.channel), C.BASS_ATTRIB_VOL, C.float(settings.Audio.GeneralVolume*settings.Audio.MusicVolume*vol)) 113 | } 114 | 115 | func (wv *Music) GetLength() float64 { 116 | return float64(C.BASS_ChannelBytes2Seconds(wv.channel, C.BASS_ChannelGetLength(wv.channel, C.BASS_POS_BYTE))) 117 | } 118 | 119 | func (wv *Music) SetPosition(pos float64) { 120 | C.BASS_ChannelSetPosition(wv.channel, C.BASS_ChannelSeconds2Bytes(wv.channel, C.double(pos)), C.BASS_POS_BYTE) 121 | } 122 | 123 | func (wv *Music) GetPosition() float64 { 124 | return float64(C.BASS_ChannelBytes2Seconds(wv.channel, C.BASS_ChannelGetPosition(wv.channel, C.BASS_POS_BYTE))) 125 | } 126 | 127 | func (wv *Music) SetTempo(tempo float64) { 128 | C.BASS_ChannelSetAttribute(C.DWORD(wv.channel), C.BASS_ATTRIB_TEMPO, C.float((tempo-1.0)*100)) 129 | } 130 | 131 | func (wv *Music) SetPitch(tempo float64) { 132 | C.BASS_ChannelSetAttribute(C.DWORD(wv.channel), C.BASS_ATTRIB_TEMPO_PITCH, C.float((tempo-1.0)*12)) 133 | } 134 | 135 | func (wv *Music) GetState() int { 136 | return int(C.BASS_ChannelIsActive(wv.channel)) 137 | } 138 | 139 | func (wv *Music) Update() { 140 | C.BASS_ChannelGetData(wv.channel, unsafe.Pointer(&wv.fft[0]), C.BASS_DATA_FFT1024) 141 | toPeak := -1.0 142 | beatAv := 0.0 143 | for i, g := range wv.fft { 144 | h := math.Abs(float64(g)) 145 | if toPeak < h { 146 | toPeak = h 147 | } 148 | if i > 0 && i < 5 { 149 | beatAv = math.Max(beatAv, float64(g)) 150 | } 151 | //toAv += math.Abs(float64(g)) 152 | } 153 | //beatAv /= 5.0 154 | //toAv /= 512 155 | wv.beat = beatAv 156 | wv.peak = toPeak 157 | } 158 | 159 | func (wv *Music) GetFFT() []float32 { 160 | return wv.fft 161 | } 162 | 163 | func (wv *Music) GetPeak() float64 { 164 | return wv.peak 165 | } 166 | 167 | func (wv *Music) GetBeat() float64 { 168 | return wv.beat 169 | } 170 | -------------------------------------------------------------------------------- /dance/movers/angleoffset.go: -------------------------------------------------------------------------------- 1 | package movers 2 | 3 | import ( 4 | "danser/beatmap/objects" 5 | "danser/bmath" 6 | "danser/bmath/curves" 7 | "danser/settings" 8 | "math" 9 | ) 10 | 11 | type AngleOffsetMover struct { 12 | lastAngle float64 13 | lastPoint bmath.Vector2d 14 | bz curves.Bezier 15 | startTime, endTime int64 16 | invert float64 17 | } 18 | 19 | func NewAngleOffsetMover() MultiPointMover { 20 | return &AngleOffsetMover{lastAngle: 0, invert: 1} 21 | } 22 | 23 | func (bm *AngleOffsetMover) Reset() { 24 | bm.lastAngle = 0 25 | bm.invert = 1 26 | bm.lastPoint = bmath.NewVec2d(0, 0) 27 | } 28 | 29 | func (bm *AngleOffsetMover) SetObjects(objs []objects.BaseObject) { 30 | end := objs[0] 31 | start := objs[1] 32 | 33 | endPos := end.GetBasicData().EndPos 34 | endTime := end.GetBasicData().EndTime 35 | startPos := start.GetBasicData().StartPos 36 | startTime := start.GetBasicData().StartTime 37 | 38 | distance := endPos.Dst(startPos) 39 | 40 | s1, ok1 := end.(*objects.Slider) 41 | s2, ok2 := start.(*objects.Slider) 42 | 43 | var points []bmath.Vector2d 44 | 45 | scaledDistance := distance * settings.Dance.Flower.DistanceMult 46 | newAngle := settings.Dance.Flower.AngleOffset * math.Pi / 180.0 47 | 48 | if end.GetBasicData().StartTime > 0 && settings.Dance.Flower.LongJump >= 0 && (startTime-endTime) > settings.Dance.Flower.LongJump { 49 | scaledDistance = float64(startTime-endTime) * settings.Dance.Flower.LongJumpMult 50 | } 51 | 52 | if endPos == startPos { 53 | if settings.Dance.Flower.LongJumpOnEqualPos { 54 | scaledDistance = float64(startTime-endTime) * settings.Dance.Flower.LongJumpMult 55 | bm.lastAngle += math.Pi 56 | 57 | pt1 := bmath.NewVec2dRad(bm.lastAngle, scaledDistance).Add(endPos) 58 | 59 | if ok1 { 60 | pt1 = bmath.NewVec2dRad(s1.GetEndAngle(), scaledDistance).Add(endPos) 61 | } 62 | 63 | if !ok2 { 64 | angle := bm.lastAngle - newAngle*bm.invert 65 | pt2 := bmath.NewVec2dRad(angle, scaledDistance).Add(startPos) 66 | bm.lastAngle = angle 67 | points = []bmath.Vector2d{endPos, pt1, pt2, startPos} 68 | } else { 69 | pt2 := bmath.NewVec2dRad(s2.GetStartAngle(), scaledDistance).Add(startPos) 70 | points = []bmath.Vector2d{endPos, pt1, pt2, startPos} 71 | } 72 | 73 | } else { 74 | points = []bmath.Vector2d{endPos, startPos} 75 | } 76 | } else if ok1 && ok2 { 77 | bm.invert = -1 * bm.invert 78 | 79 | pt1 := bmath.NewVec2dRad(s1.GetEndAngle(), scaledDistance).Add(endPos) 80 | pt2 := bmath.NewVec2dRad(s2.GetStartAngle(), scaledDistance).Add(startPos) 81 | 82 | points = []bmath.Vector2d{endPos, pt1, pt2, startPos} 83 | } else if ok1 { 84 | bm.invert = -1 * bm.invert 85 | bm.lastAngle = endPos.AngleRV(startPos) - newAngle*bm.invert 86 | 87 | pt1 := bmath.NewVec2dRad(s1.GetEndAngle(), scaledDistance).Add(endPos) 88 | pt2 := bmath.NewVec2dRad(bm.lastAngle, scaledDistance).Add(startPos) 89 | 90 | points = []bmath.Vector2d{endPos, pt1, pt2, startPos} 91 | } else if ok2 { 92 | bm.lastAngle += math.Pi 93 | 94 | pt1 := bmath.NewVec2dRad(bm.lastAngle, scaledDistance).Add(endPos) 95 | pt2 := bmath.NewVec2dRad(s2.GetStartAngle(), scaledDistance).Add(startPos) 96 | 97 | points = []bmath.Vector2d{endPos, pt1, pt2, startPos} 98 | } else { 99 | if settings.Dance.Flower.UseNewStyle { 100 | if bmath.AngleBetween(endPos, bm.lastPoint, startPos) >= settings.Dance.Flower.AngleOffset*math.Pi/180.0 { 101 | bm.invert = -1 * bm.invert 102 | newAngle = settings.Dance.Flower.StreamAngleOffset * math.Pi / 180.0 103 | } 104 | } else if startTime-endTime < settings.Dance.Flower.StreamTrigger { 105 | newAngle = settings.Dance.Flower.StreamAngleOffset * math.Pi / 180.0 106 | } 107 | 108 | angle := endPos.AngleRV(startPos) - newAngle*bm.invert 109 | 110 | pt1 := bmath.NewVec2dRad(bm.lastAngle+math.Pi, scaledDistance).Add(endPos) 111 | pt2 := bmath.NewVec2dRad(angle, scaledDistance).Add(startPos) 112 | 113 | bm.lastAngle = angle 114 | 115 | if !settings.Dance.Flower.UseNewStyle && startTime-endTime < settings.Dance.Flower.StreamTrigger && !(start.GetBasicData().SliderPoint && end.GetBasicData().SliderPoint) { 116 | bm.invert = -1 * bm.invert 117 | } 118 | 119 | points = []bmath.Vector2d{endPos, pt1, pt2, startPos} 120 | } 121 | 122 | bm.bz = curves.NewBezier(points) 123 | bm.endTime = endTime 124 | bm.startTime = startTime 125 | bm.lastPoint = endPos 126 | } 127 | 128 | func (bm *AngleOffsetMover) Update(time int64) bmath.Vector2d { 129 | t := float64(time-bm.endTime) / float64(bm.startTime-bm.endTime) 130 | t = math.Max(0.0, math.Min(1.0, t)) 131 | return bm.bz.NPointAt(t) 132 | } 133 | 134 | func (bm *AngleOffsetMover) GetEndTime() int64 { 135 | return bm.startTime 136 | } 137 | -------------------------------------------------------------------------------- /storyboard/commands.go: -------------------------------------------------------------------------------- 1 | package storyboard 2 | 3 | import ( 4 | "danser/animation/easing" 5 | "danser/bmath" 6 | "github.com/go-gl/mathgl/mgl32" 7 | "log" 8 | "math" 9 | "strconv" 10 | ) 11 | 12 | type Command struct { 13 | start, end int64 14 | command string 15 | easing func(float64) float64 16 | val []float64 17 | numSections int64 18 | sectionTime int64 19 | sections [][]float64 20 | custom string 21 | constant bool 22 | } 23 | 24 | func NewCommand(data []string) *Command { 25 | command := &Command{} 26 | command.command = data[0] 27 | 28 | easingID, err := strconv.ParseInt(data[1], 10, 32) 29 | 30 | if err != nil { 31 | log.Println(err) 32 | } 33 | 34 | command.easing = easing.GetEasing(easingID) 35 | 36 | command.start, err = strconv.ParseInt(data[2], 10, 64) 37 | 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | if data[3] == "" { 43 | command.end = command.start 44 | } else { 45 | command.end, err = strconv.ParseInt(data[3], 10, 64) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | } 51 | 52 | if command.end < command.start { 53 | command.end = command.start 54 | } 55 | 56 | arguments := 0 57 | 58 | switch command.command { 59 | case "F", "R", "S", "MX", "MY": 60 | arguments = 1 61 | break 62 | case "M", "V": 63 | arguments = 2 64 | break 65 | case "C": 66 | arguments = 3 67 | break 68 | } 69 | 70 | parameters := data[4:] 71 | 72 | if arguments == 0 { 73 | command.custom = parameters[0] 74 | command.val = make([]float64, 1) 75 | return command 76 | } 77 | 78 | numSections := len(parameters) / arguments 79 | command.numSections = int64(numSections) - 1 80 | command.sectionTime = command.end - command.start 81 | 82 | if command.numSections > 1 { 83 | command.end = command.start + command.sectionTime*command.numSections 84 | } 85 | 86 | command.sections = make([][]float64, numSections) 87 | command.val = make([]float64, arguments) 88 | 89 | for i := 0; i < numSections; i++ { 90 | command.sections[i] = make([]float64, arguments) 91 | for j := 0; j < arguments; j++ { 92 | var err error 93 | command.sections[i][j], err = strconv.ParseFloat(parameters[arguments*i+j], 64) 94 | 95 | if command.command == "C" { 96 | command.sections[i][j] /= 255 97 | } 98 | 99 | if err != nil { 100 | log.Println(err) 101 | } 102 | } 103 | } 104 | 105 | copy(command.val, command.sections[0]) 106 | 107 | if numSections == 1 { 108 | command.constant = true 109 | } 110 | 111 | return command 112 | } 113 | 114 | func (command *Command) Update(time int64) { 115 | 116 | if command.command == "P" { 117 | if command.start != command.end { 118 | if time >= command.start && time <= command.end { 119 | command.val[0] = 1 120 | } else { 121 | command.val[0] = 0 122 | } 123 | } else { 124 | command.val[0] = 1 125 | } 126 | 127 | return 128 | } 129 | 130 | if command.constant { 131 | copy(command.val, command.sections[0]) 132 | } else { 133 | 134 | section := int64(0) 135 | 136 | if command.sectionTime > 0 { 137 | section = (time - command.start) / command.sectionTime 138 | } 139 | 140 | if section >= command.numSections { 141 | section = command.numSections - 1 142 | } 143 | 144 | t := command.easing(math.Min(math.Max(float64((time-command.start)-section*command.sectionTime)/float64(command.sectionTime), 0), 1)) 145 | 146 | for i := range command.val { 147 | command.val[i] = command.sections[section][i] + t*(command.sections[section+1][i]-command.sections[section][i]) 148 | } 149 | } 150 | } 151 | 152 | func (command *Command) Apply(obj Object) { 153 | switch command.command { 154 | case "F": 155 | obj.SetAlpha(command.val[0]) 156 | break 157 | case "R": 158 | obj.SetRotation(command.val[0]) 159 | break 160 | case "S": 161 | obj.SetScale(bmath.NewVec2d(command.val[0], command.val[0])) 162 | break 163 | case "V": 164 | obj.SetScale(bmath.NewVec2d(command.val[0], command.val[1])) 165 | break 166 | case "M": 167 | obj.SetPosition(bmath.NewVec2d(command.val[0], command.val[1])) 168 | break 169 | case "MX": 170 | obj.SetPosition(bmath.NewVec2d(command.val[0], obj.GetPosition().Y)) 171 | break 172 | case "MY": 173 | obj.SetPosition(bmath.NewVec2d(obj.GetPosition().X, command.val[0])) 174 | break 175 | case "C": 176 | obj.SetColor(mgl32.Vec3{float32(command.val[0]), float32(command.val[1]), float32(command.val[2])}) 177 | break 178 | case "P": 179 | switch command.custom { 180 | case "H": 181 | obj.SetHFlip(command.val[0] == 1) 182 | break 183 | case "V": 184 | obj.SetVFlip(command.val[0] == 1) 185 | break 186 | case "A": 187 | obj.SetAdditive(command.val[0] == 1) 188 | break 189 | } 190 | break 191 | } 192 | } 193 | 194 | func (command *Command) Init(obj Object) { 195 | 196 | if command.command == "P" { 197 | return 198 | } 199 | 200 | copy(command.val, command.sections[0]) 201 | command.Apply(obj) 202 | } 203 | 204 | //TODO: TRIGGER command 205 | -------------------------------------------------------------------------------- /render/slider.go: -------------------------------------------------------------------------------- 1 | package render 2 | 3 | import ( 4 | "danser/bmath" 5 | "danser/render/framebuffer" 6 | "danser/settings" 7 | "danser/utils" 8 | "github.com/go-gl/gl/v3.3-core/gl" 9 | "github.com/go-gl/mathgl/mgl32" 10 | "github.com/wieku/glhf" 11 | _ "image/png" 12 | "io/ioutil" 13 | "log" 14 | "math" 15 | ) 16 | 17 | var sliderShader *glhf.Shader = nil 18 | var fboShader *glhf.Shader 19 | var fboSlice *glhf.VertexSlice 20 | var sliderVertexFormat glhf.AttrFormat 21 | var cam mgl32.Mat4 22 | var fbo *framebuffer.Framebuffer 23 | var fboUnit int32 24 | var CS float64 25 | 26 | func SetupSlider() { 27 | 28 | sliderVertexFormat = glhf.AttrFormat{ 29 | {Name: "in_position", Type: glhf.Vec3}, 30 | {Name: "center", Type: glhf.Vec3}, 31 | {Name: "in_tex_coord", Type: glhf.Vec2}, 32 | } 33 | var err error 34 | 35 | svert, _ := ioutil.ReadFile("assets/shaders/slider.vsh") 36 | sfrag, _ := ioutil.ReadFile("assets/shaders/slider.fsh") 37 | sliderShader, err = glhf.NewShader(sliderVertexFormat, glhf.AttrFormat{{Name: "col_border", Type: glhf.Vec4}, {Name: "tex", Type: glhf.Int}, {Name: "proj", Type: glhf.Mat4}, {Name: "trans", Type: glhf.Mat4}, {Name: "col_border1", Type: glhf.Vec4}}, string(svert), string(sfrag)) 38 | if err != nil { 39 | log.Println(err) 40 | } 41 | 42 | fvert, _ := ioutil.ReadFile("assets/shaders/fbopass.vsh") 43 | ffrag, _ := ioutil.ReadFile("assets/shaders/fbopass.fsh") 44 | fboShader, err = glhf.NewShader(glhf.AttrFormat{ 45 | {Name: "in_position", Type: glhf.Vec3}, 46 | {Name: "in_tex_coord", Type: glhf.Vec2}, 47 | }, glhf.AttrFormat{{Name: "tex", Type: glhf.Int}}, string(fvert), string(ffrag)) 48 | 49 | if err != nil { 50 | log.Println("FboPass: " + err.Error()) 51 | } 52 | 53 | fbo = framebuffer.NewFrame(int(settings.Graphics.GetWidth()), int(settings.Graphics.GetHeight()), true, true) 54 | fbo.Texture().Bind(29) 55 | fboUnit = 29 56 | 57 | fboSlice = glhf.MakeVertexSlice(fboShader, 6, 6) 58 | fboSlice.Begin() 59 | fboSlice.SetVertexData([]float32{ 60 | -1, -1, 0, 0, 0, 61 | 1, -1, 0, 1, 0, 62 | -1, 1, 0, 0, 1, 63 | 1, -1, 0, 1, 0, 64 | 1, 1, 0, 1, 1, 65 | -1, 1, 0, 0, 1, 66 | }) 67 | fboSlice.End() 68 | 69 | } 70 | 71 | type SliderRenderer struct{} 72 | 73 | func NewSliderRenderer() *SliderRenderer { 74 | return &SliderRenderer{} 75 | } 76 | 77 | func (sr *SliderRenderer) Begin() { 78 | 79 | gl.Disable(gl.BLEND) 80 | gl.Enable(gl.DEPTH_TEST) 81 | gl.DepthMask(true) 82 | gl.DepthFunc(gl.LESS) 83 | 84 | fbo.Begin() 85 | 86 | gl.ClearColor(0.0, 0.0, 0.0, 0.0) 87 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 88 | 89 | sliderShader.Begin() 90 | 91 | gl.ActiveTexture(gl.TEXTURE0) 92 | SliderGradient.Bind(0) 93 | sliderShader.SetUniformAttr(1, int32(0)) 94 | sliderShader.SetUniformAttr(2, cam) 95 | } 96 | 97 | func (sr *SliderRenderer) SetColor(color mgl32.Vec4, prev mgl32.Vec4) { 98 | sliderShader.SetUniformAttr(0, color) 99 | if settings.Objects.EnableCustomSliderBorderGradientOffset { 100 | sliderShader.SetUniformAttr(4, utils.GetColorShifted(color, settings.Objects.SliderBorderGradientOffset)) 101 | } else { 102 | sliderShader.SetUniformAttr(4, prev) 103 | } 104 | } 105 | 106 | func (sr *SliderRenderer) SetScale(scale float64) { 107 | sliderShader.SetUniformAttr(3, mgl32.Scale3D(float32(scale), float32(scale), 1)) 108 | } 109 | 110 | func (sr *SliderRenderer) EndAndRender() { 111 | 112 | sliderShader.End() 113 | fbo.End() 114 | gl.Disable(gl.DEPTH_TEST) 115 | gl.DepthMask(false) 116 | gl.Enable(gl.BLEND) 117 | 118 | gl.BlendEquation(gl.FUNC_ADD) 119 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 120 | 121 | fboShader.Begin() 122 | fboShader.SetUniformAttr(0, int32(fboUnit)) 123 | fboSlice.BeginDraw() 124 | fboSlice.Draw() 125 | fboSlice.EndDraw() 126 | fboShader.End() 127 | } 128 | 129 | func (self *SliderRenderer) SetCamera(camera mgl32.Mat4) { 130 | cam = camera 131 | sliderShader.SetUniformAttr(2, cam) 132 | } 133 | 134 | func (self *SliderRenderer) GetShape(curve []bmath.Vector2d) ([]float32, int) { 135 | return createMesh(curve), int(settings.Objects.SliderLOD) 136 | } 137 | 138 | func (self *SliderRenderer) UploadMesh(mesh []float32) *glhf.VertexSlice { 139 | slice := glhf.MakeVertexSlice(sliderShader, len(mesh)/8, len(mesh)/8) 140 | slice.Begin() 141 | slice.SetVertexData(mesh) 142 | slice.End() 143 | return slice 144 | } 145 | 146 | func createMesh(curve []bmath.Vector2d) []float32 { 147 | vertices := make([]float32, 8*3*int(settings.Objects.SliderLOD)*len(curve)) 148 | num := 0 149 | iter := 0 150 | for _, v := range curve { 151 | tab := createCircle(v.X, v.Y, 64*CS, int(settings.Objects.SliderLOD)) 152 | for j := range tab { 153 | if j >= 2 { 154 | p1, p2, p3 := tab[j-1], tab[j], tab[0] 155 | set(vertices, iter, float32(p1.X), float32(p1.Y), 1.0, float32(p3.X), float32(p3.Y), 0.0, 0.0, 0.0, float32(p2.X), float32(p2.Y), 1.0, float32(p3.X), float32(p3.Y), 0.0, 0.0, 0.0, float32(p3.X), float32(p3.Y), 0.0, float32(p3.X), float32(p3.Y), 0.0, 1.0, 0.0) 156 | iter += 24 157 | } 158 | } 159 | num += len(tab) 160 | } 161 | 162 | return vertices 163 | } 164 | 165 | func set(array []float32, index int, data ...float32) { 166 | copy(array[index:index+len(data)], data) 167 | } 168 | 169 | func createCircle(x, y, radius float64, segments int) []bmath.Vector2d { 170 | points := make([]bmath.Vector2d, segments+2) 171 | points[0] = bmath.NewVec2d(x, y) 172 | 173 | for i := 0; i < segments; i++ { 174 | points[i+1] = bmath.NewVec2dRad(float64(i)/float64(segments)*2*math.Pi, radius).AddS(x, y) 175 | } 176 | 177 | points[segments+1] = points[1] 178 | return points 179 | } 180 | -------------------------------------------------------------------------------- /render/font/font.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "danser/bmath" 5 | "danser/render" 6 | "danser/render/texture" 7 | "danser/settings" 8 | "github.com/go-gl/gl/v3.3-core/gl" 9 | "github.com/golang/freetype" 10 | "github.com/golang/freetype/truetype" 11 | font2 "golang.org/x/image/font" 12 | "golang.org/x/image/math/fixed" 13 | "image" 14 | "image/draw" 15 | "io" 16 | "io/ioutil" 17 | "log" 18 | ) 19 | 20 | var fonts map[string]*Font 21 | 22 | func init() { 23 | fonts = make(map[string]*Font) 24 | } 25 | 26 | func GetFont(name string) *Font { 27 | return fonts[name] 28 | } 29 | 30 | type glyphData struct { 31 | region texture.TextureRegion 32 | advance, bearingX, bearingY float64 33 | } 34 | 35 | type Font struct { 36 | face font2.Face 37 | atlas *texture.TextureAtlas 38 | glyphs []*glyphData 39 | min, max rune 40 | initialSize float64 41 | } 42 | 43 | func LoadFont(reader io.Reader, loc uint) *Font { 44 | data, err := ioutil.ReadAll(reader) 45 | 46 | if err != nil { 47 | panic("Error reading font: " + err.Error()) 48 | } 49 | 50 | ttf, err := truetype.Parse(data) 51 | 52 | if err != nil { 53 | panic("Error reading font: " + err.Error()) 54 | } 55 | 56 | font := new(Font) 57 | font.min = rune(32) 58 | font.max = rune(127) 59 | font.initialSize = 64.0 60 | font.glyphs = make([]*glyphData, font.max-font.min+1) 61 | 62 | font.atlas = texture.NewTextureAtlas(4096, 4) 63 | 64 | font.atlas.Bind(loc) 65 | 66 | fc := truetype.NewFace(ttf, &truetype.Options{Size: font.initialSize, DPI: 72, Hinting: font2.HintingFull}) 67 | font.face = fc 68 | context := freetype.NewContext() 69 | context.SetFont(ttf) 70 | context.SetFontSize(font.initialSize) 71 | context.SetDPI(72) 72 | context.SetHinting(font2.HintingFull) 73 | 74 | for i := font.min; i <= font.max; i++ { 75 | 76 | gBnd, gAdv, ok := fc.GlyphBounds(i) 77 | if ok != true { 78 | continue 79 | } 80 | 81 | gh := int32((gBnd.Max.Y - gBnd.Min.Y) >> 6) 82 | gw := int32((gBnd.Max.X - gBnd.Min.X) >> 6) 83 | 84 | //if glyph has no dimensions set to a max value 85 | if gw == 0 || gh == 0 { 86 | gBnd = ttf.Bounds(fixed.Int26_6(20)) 87 | gw = int32((gBnd.Max.X - gBnd.Min.X) >> 6) 88 | gh = int32((gBnd.Max.Y - gBnd.Min.Y) >> 6) 89 | 90 | //above can sometimes yield 0 for font smaller than 48pt, 1 is minimum 91 | if gw == 0 || gh == 0 { 92 | gw = 1 93 | gh = 1 94 | } 95 | } 96 | 97 | //The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y. 98 | gAscent := int(-gBnd.Min.Y) >> 6 99 | //gdescent := int(gBnd.Max.Y) >> 6 100 | 101 | fg, bg := image.White, image.Transparent 102 | rect := image.Rect(0, 0, int(gw), int(gh)) 103 | rgba := image.NewNRGBA(rect) 104 | draw.Draw(rgba, rgba.Bounds(), bg, image.Point{}, draw.Src) 105 | 106 | context.SetClip(rect) 107 | context.SetDst(rgba) 108 | context.SetSrc(fg) 109 | 110 | px := 0 - (int(gBnd.Min.X) >> 6) 111 | py := gAscent 112 | pt := freetype.Pt(px, py) 113 | 114 | // Draw the text from mask to image 115 | _, err = context.DrawString(string(i), pt) 116 | if err != nil { 117 | log.Println(err) 118 | continue 119 | } 120 | 121 | //res := font.toSDF(rgba) 122 | 123 | newPix := make([]uint8, len(rgba.Pix)) 124 | height := rgba.Bounds().Dy() 125 | 126 | for i := 0; i < height; i++ { 127 | copy(newPix[i*rgba.Stride:(i+1)*rgba.Stride], rgba.Pix[(height-1-i)*rgba.Stride:(height-i)*rgba.Stride]) 128 | } 129 | 130 | region := font.atlas.AddTexture(string(i), rgba.Bounds().Dx(), rgba.Bounds().Dy(), newPix) 131 | 132 | //set w,h and adv, bearing V and bearing H in char 133 | advance := float64(gAdv) / 64 134 | bearingV := float64(gBnd.Max.Y) / 64 135 | bearingH := float64(gBnd.Min.X) / 64 136 | font.glyphs[i-font.min] = &glyphData{*region, advance, bearingH, bearingV} 137 | } 138 | 139 | log.Println(ttf.Name(truetype.NameIDFontFullName), "loaded!") 140 | fonts[ttf.Name(truetype.NameIDFontFullName)] = font 141 | return font 142 | } 143 | 144 | func (font *Font) Draw(renderer *render.SpriteBatch, x, y float64, size float64, text string) { 145 | font.DrawAndGetLastPosition(renderer, x, y, size, text) 146 | } 147 | 148 | func (font *Font) DrawAndGetLastPosition(renderer *render.SpriteBatch, x, y float64, size float64, text string) float64 { 149 | xpad := x 150 | scale := size / font.initialSize 151 | 152 | for i, c := range text { 153 | if c-font.min < 0 || c-font.min > font.max || int(c) > 127 { 154 | //log.Println("Warning! A non-ASCII or unprintable character is presented in text! Skipping") 155 | continue 156 | } 157 | char := font.glyphs[c-font.min] 158 | if char == nil { 159 | continue 160 | } 161 | 162 | kerning := 0.0 163 | 164 | if i > 0 { 165 | kerning = float64(font.face.Kern(rune(text[i-1]), c)) / 64 166 | } 167 | 168 | renderer.SetScale(scale, scale) 169 | renderer.SetTranslation(bmath.NewVec2d(xpad+(char.bearingX-kerning+float64(char.region.Width)/2)*scale, y+(float64(char.region.Height)/2-char.bearingY)*scale)) 170 | renderer.DrawTexture(char.region) 171 | xpad += scale * (char.advance - kerning) 172 | 173 | } 174 | return xpad 175 | } 176 | 177 | type Word struct { 178 | X float64 179 | Size float64 180 | Text string 181 | } 182 | 183 | func (font *Font) DrawAll(renderer *render.SpriteBatch, words []Word) { 184 | // 清除之前的文字,否则会重叠 185 | gl.ClearColor(0, 0, 0, 1) 186 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 187 | //获得初始Y坐标 188 | currentY := float64(settings.Graphics.GetHeight() - 40) 189 | for _, word := range words { 190 | font.Draw(renderer, word.X, currentY, word.Size, word.Text) 191 | // 扩大1.7倍size的间距 192 | currentY -= word.Size * 1.7 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /settings-1080.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "v1", 3 | "General": { 4 | "OsuSongsDir": "song\\" 5 | }, 6 | "VSplayer": { 7 | "PlayerInfo": { 8 | "Players": 50, 9 | "SpecifiedPlayers": false, 10 | "SpecifiedLine": "2" 11 | }, 12 | "PlayerInfoUI": { 13 | "BaseSize": 9, 14 | "BaseX": 18, 15 | "BaseY": 1037, 16 | "ShowMouse1": true, 17 | "ShowMouse2": true, 18 | "ShowRealTimePP": true, 19 | "RealTimePPGap": 800, 20 | "ShowRealTimeUR": false, 21 | "ShowPPAndURRank": false, 22 | "Rank1Highlight": true, 23 | "HighlightMult": 1.3, 24 | "LineGapMult": 0 25 | }, 26 | "RecordInfoUI": { 27 | "Recorder": "wasupandceacar", 28 | "RecordTime": "2019.2.21", 29 | "RecordBaseX": 1609, 30 | "RecordBaseY": 40, 31 | "RecordBaseSize": 22, 32 | "RecordAlpha": 0.4 33 | }, 34 | "DiffInfoUI": { 35 | "ShowDiffInfo": true, 36 | "DiffBaseX": 1649, 37 | "DiffBaseY": 1035, 38 | "DiffBaseSize": 22, 39 | "DiffAlpha": 0.8 40 | }, 41 | "PlayerFieldUI": { 42 | "HitFadeTime": 200, 43 | "CursorColorNum": 13, 44 | "CursorColorSkipNum": 13, 45 | "ShowHitCircleNumber": true 46 | }, 47 | "MapInfo": { 48 | "Title": "Exit This Earth's Atomosphere", 49 | "Difficulty": "Evolution" 50 | }, 51 | "Mods": { 52 | "EnableDT": false, 53 | "EnableHT": false, 54 | "EnableEZ": false, 55 | "EnableHR": false, 56 | "EnableHD": false 57 | }, 58 | "Knockout": { 59 | "EnableKnockout": true, 60 | "ShowTrueMiss": false, 61 | "PlayerFadeTime": 1500, 62 | "SameTimeOffset": 22, 63 | "MissMult": 1 64 | }, 65 | "ReplayandCache": { 66 | "ReplayDir": "replays\\", 67 | "CacheDir": "cache\\", 68 | "SaveResultCache": true, 69 | "ReadResultCache": true, 70 | "ReplayDebug": false 71 | }, 72 | "ErrorFix": { 73 | "EnableErrorFix": true, 74 | "ErrorFixFile": "error.err" 75 | }, 76 | "Skin": { 77 | "EnableSkin": true, 78 | "SkinDir": "skin\\lain1\\", 79 | "NumberOffset": 0 80 | } 81 | }, 82 | "Graphics": { 83 | "Width": 1920, 84 | "Height": 1080, 85 | "WindowWidth": 1920, 86 | "WindowHeight": 1080, 87 | "Fullscreen": false, 88 | "VSync": false, 89 | "FPSCap": 1000, 90 | "MSAA": 16 91 | }, 92 | "Audio": { 93 | "GeneralVolume": 0.5, 94 | "MusicVolume": 0.5, 95 | "SampleVolume": 0.5, 96 | "Offset": 0, 97 | "IgnoreBeatmapSamples": false, 98 | "IgnoreBeatmapSampleVolume": false 99 | }, 100 | "Beat": { 101 | "BeatScale": 1.2 102 | }, 103 | "Cursor": { 104 | "Colors": { 105 | "EnableRainbow": false, 106 | "RainbowSpeed": 8, 107 | "BaseColor": { 108 | "Hue": 0, 109 | "Saturation": 1, 110 | "Value": 1 111 | }, 112 | "EnableCustomHueOffset": false, 113 | "HueOffset": 0, 114 | "FlashToTheBeat": false, 115 | "FlashAmplitude": 0 116 | }, 117 | "EnableCustomTagColorOffset": true, 118 | "TagColorOffset": -36, 119 | "EnableTrailGlow": true, 120 | "EnableCustomTrailGlowOffset": true, 121 | "TrailGlowOffset": -36, 122 | "ScaleToCS": false, 123 | "CursorSize": 8.5, 124 | "ScaleToTheBeat": false, 125 | "ShowCursorsOnBreaks": true, 126 | "BounceOnEdges": false, 127 | "TrailEndScale": 0.4, 128 | "TrailDensity": 1, 129 | "TrailMaxLength": 2000, 130 | "TrailRemoveSpeed": 1, 131 | "GlowEndScale": 0.4, 132 | "InnerLengthMult": 0.9 133 | }, 134 | "Objects": { 135 | "MandalaTexturesTrigger": 5, 136 | "MandalaTexturesAlpha": 0.3, 137 | "ForceSliderBallTexture": true, 138 | "DrawApproachCircles": true, 139 | "Colors": { 140 | "EnableRainbow": false, 141 | "RainbowSpeed": 8, 142 | "BaseColor": { 143 | "Hue": 0, 144 | "Saturation": 1, 145 | "Value": 1 146 | }, 147 | "EnableCustomHueOffset": false, 148 | "HueOffset": 0, 149 | "FlashToTheBeat": false, 150 | "FlashAmplitude": 100 151 | }, 152 | "ObjectsSize": -1, 153 | "CSMult": 1, 154 | "ScaleToTheBeat": false, 155 | "SliderLOD": 30, 156 | "SliderPathLOD": 50, 157 | "SliderSnakeIn": true, 158 | "SliderSnakeOut": true, 159 | "SliderMerge": true, 160 | "DrawFollowPoints": true, 161 | "WhiteFollowPoints": true, 162 | "FollowPointColorOffset": 0, 163 | "EnableCustomSliderBorderColor": false, 164 | "CustomSliderBorderColor": { 165 | "EnableRainbow": false, 166 | "RainbowSpeed": 8, 167 | "BaseColor": { 168 | "Hue": 0, 169 | "Saturation": 0, 170 | "Value": 1 171 | }, 172 | "EnableCustomHueOffset": false, 173 | "HueOffset": 0, 174 | "FlashToTheBeat": false, 175 | "FlashAmplitude": 100 176 | }, 177 | "EnableCustomSliderBorderGradientOffset": true, 178 | "SliderBorderGradientOffset": 18, 179 | "StackEnabled": true 180 | }, 181 | "Playfield": { 182 | "LeadInTime": 5, 183 | "LeadInHold": 2, 184 | "FadeOutTime": 20, 185 | "BackgroundInDim": 0, 186 | "BackgroundDim": 0.7, 187 | "BackgroundDimBreaks": 0.1, 188 | "BlurEnable": true, 189 | "BackgroundInBlur": 0, 190 | "BackgroundBlur": 0.6, 191 | "BackgroundBlurBreaks": 0.6, 192 | "SpectrumInDim": 0, 193 | "SpectrumDim": 1, 194 | "SpectrumDimBreaks": 0, 195 | "StoryboardEnabled": true, 196 | "Scale": 1, 197 | "FlashToTheBeat": true, 198 | "UnblurToTheBeat": true, 199 | "UnblurFill": 0.8, 200 | "KiaiFactor": 1.1, 201 | "BaseRotation": 0, 202 | "RotationEnabled": false, 203 | "RotationSpeed": 2, 204 | "BloomEnabled": true, 205 | "BloomToTheBeat": false, 206 | "BloomBeatAddition": 0.3, 207 | "Bloom": { 208 | "Threshold": 0, 209 | "Blur": 0.6, 210 | "Power": 0.7 211 | } 212 | }, 213 | "Dance": { 214 | "SliderDance": false, 215 | "TAGSliderDance": false, 216 | "Bezier": { 217 | "Aggressiveness": 60, 218 | "SliderAggressiveness": 3 219 | }, 220 | "Flower": { 221 | "UseNewStyle": true, 222 | "AngleOffset": 90, 223 | "DistanceMult": 0.666, 224 | "StreamTrigger": 130, 225 | "StreamAngleOffset": 90, 226 | "LongJump": -1, 227 | "LongJumpMult": 0.7, 228 | "LongJumpOnEqualPos": false 229 | }, 230 | "HalfCircle": { 231 | "RadiusMultiplier": 1, 232 | "StreamTrigger": 130 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "v1", 3 | "General": { 4 | "OsuSongsDir": "song\\" 5 | }, 6 | "VSplayer": { 7 | "PlayerInfo": { 8 | "Players": 1, 9 | "SpecifiedPlayers": false, 10 | "SpecifiedLine": "6", 11 | "SpecifiedColor": "WhiteCat:255,255,255|YakumoYukari:0,0,0" 12 | }, 13 | "PlayerInfoUI": { 14 | "BaseSize": 6, 15 | "BaseX": 14, 16 | "BaseY": 696, 17 | "ShowMouse1": false, 18 | "ShowMouse2": false, 19 | "ShowRealTimePP": true, 20 | "RealTimePPGap": 800, 21 | "ShowRealTimeUR": true, 22 | "ShowPPAndURRank": false, 23 | "Rank1Highlight": false, 24 | "HighlightMult": 1.3, 25 | "LineGapMult": 0 26 | }, 27 | "RecordInfoUI": { 28 | "Recorder": "wasupandceacar", 29 | "RecordTime": "2019.2.21", 30 | "RecordBaseX": 1019, 31 | "RecordBaseY": 35, 32 | "RecordBaseSize": 18, 33 | "RecordAlpha": 0.4 34 | }, 35 | "DiffInfoUI": { 36 | "ShowDiffInfo": false, 37 | "DiffBaseX": 1049, 38 | "DiffBaseY": 678, 39 | "DiffBaseSize": 18, 40 | "DiffAlpha": 0.8 41 | }, 42 | "PlayerFieldUI": { 43 | "HitFadeTime": 200, 44 | "CursorColorNum": 25, 45 | "CursorColorSkipNum": 13, 46 | "ShowHitCircleNumber": true 47 | }, 48 | "MapInfo": { 49 | "Title": "Graces of Heaven", 50 | "Difficulty": "Debug" 51 | }, 52 | "Mods": { 53 | "EnableDT": false, 54 | "EnableHT": false, 55 | "EnableEZ": false, 56 | "EnableHR": false, 57 | "EnableHD": false 58 | }, 59 | "Knockout": { 60 | "EnableKnockout": true, 61 | "ShowTrueMiss": false, 62 | "PlayerFadeTime": 1500, 63 | "SameTimeOffset": 12, 64 | "MissMult": 0.8 65 | }, 66 | "ReplayandCache": { 67 | "ReplayDir": "replays\\", 68 | "CacheDir": "cache\\", 69 | "UseCacheSystem": true, 70 | "ReplayDebug": true 71 | }, 72 | "ErrorFix": { 73 | "EnableErrorFix": false, 74 | "ErrorFixFile": "error.err" 75 | }, 76 | "Skin": { 77 | "EnableSkin": true, 78 | "SkinDir": "skin\\rafis1\\", 79 | "NumberOffset": 30 80 | } 81 | }, 82 | "Graphics": { 83 | "Width": 1920, 84 | "Height": 1080, 85 | "WindowWidth": 1280, 86 | "WindowHeight": 720, 87 | "Fullscreen": false, 88 | "VSync": false, 89 | "FPSCap": 1000, 90 | "MSAA": 16 91 | }, 92 | "Audio": { 93 | "GeneralVolume": 0.5, 94 | "MusicVolume": 0.5, 95 | "SampleVolume": 0.5, 96 | "Offset": 0, 97 | "IgnoreBeatmapSamples": false, 98 | "IgnoreBeatmapSampleVolume": false 99 | }, 100 | "Beat": { 101 | "BeatScale": 1.2 102 | }, 103 | "Cursor": { 104 | "Colors": { 105 | "EnableRainbow": false, 106 | "RainbowSpeed": 8, 107 | "BaseColor": { 108 | "Hue": 0, 109 | "Saturation": 1, 110 | "Value": 1 111 | }, 112 | "EnableCustomHueOffset": false, 113 | "HueOffset": 0, 114 | "FlashToTheBeat": false, 115 | "FlashAmplitude": 0 116 | }, 117 | "EnableCustomTagColorOffset": true, 118 | "TagColorOffset": -36, 119 | "EnableTrailGlow": true, 120 | "EnableCustomTrailGlowOffset": true, 121 | "TrailGlowOffset": -36, 122 | "ScaleToCS": false, 123 | "CursorSize": 8, 124 | "ScaleToTheBeat": false, 125 | "ShowCursorsOnBreaks": true, 126 | "BounceOnEdges": false, 127 | "TrailEndScale": 0.4, 128 | "TrailDensity": 1, 129 | "TrailMaxLength": 2000, 130 | "TrailRemoveSpeed": 1, 131 | "GlowEndScale": 0.4, 132 | "InnerLengthMult": 0.9 133 | }, 134 | "Objects": { 135 | "MandalaTexturesTrigger": 5, 136 | "MandalaTexturesAlpha": 0.3, 137 | "ForceSliderBallTexture": true, 138 | "DrawApproachCircles": true, 139 | "Colors": { 140 | "EnableRainbow": false, 141 | "RainbowSpeed": 8, 142 | "BaseColor": { 143 | "Hue": 0, 144 | "Saturation": 1, 145 | "Value": 1 146 | }, 147 | "EnableCustomHueOffset": false, 148 | "HueOffset": 0, 149 | "FlashToTheBeat": false, 150 | "FlashAmplitude": 100 151 | }, 152 | "ObjectsSize": -1, 153 | "CSMult": 1, 154 | "ScaleToTheBeat": false, 155 | "SliderLOD": 30, 156 | "SliderPathLOD": 50, 157 | "SliderSnakeIn": true, 158 | "SliderSnakeOut": true, 159 | "SliderMerge": false, 160 | "DrawFollowPoints": true, 161 | "WhiteFollowPoints": true, 162 | "FollowPointColorOffset": 0, 163 | "EnableCustomSliderBorderColor": false, 164 | "CustomSliderBorderColor": { 165 | "EnableRainbow": false, 166 | "RainbowSpeed": 8, 167 | "BaseColor": { 168 | "Hue": 0, 169 | "Saturation": 0, 170 | "Value": 1 171 | }, 172 | "EnableCustomHueOffset": false, 173 | "HueOffset": 0, 174 | "FlashToTheBeat": false, 175 | "FlashAmplitude": 100 176 | }, 177 | "EnableCustomSliderBorderGradientOffset": true, 178 | "SliderBorderGradientOffset": 18, 179 | "StackEnabled": true 180 | }, 181 | "Playfield": { 182 | "LeadInTime": 5, 183 | "LeadInHold": 2, 184 | "FadeOutTime": 20, 185 | "BackgroundInDim": 0, 186 | "BackgroundDim": 0.7, 187 | "BackgroundDimBreaks": 0.1, 188 | "BlurEnable": true, 189 | "BackgroundInBlur": 0, 190 | "BackgroundBlur": 0, 191 | "BackgroundBlurBreaks": 0, 192 | "SpectrumInDim": 0, 193 | "SpectrumDim": 1, 194 | "SpectrumDimBreaks": 0, 195 | "StoryboardEnabled": false, 196 | "Scale": 1, 197 | "FlashToTheBeat": true, 198 | "UnblurToTheBeat": true, 199 | "UnblurFill": 0.8, 200 | "KiaiFactor": 1.1, 201 | "BaseRotation": 0, 202 | "RotationEnabled": false, 203 | "RotationSpeed": 2, 204 | "BloomEnabled": true, 205 | "BloomToTheBeat": false, 206 | "BloomBeatAddition": 0.3, 207 | "Bloom": { 208 | "Threshold": 0, 209 | "Blur": 0.6, 210 | "Power": 0.7 211 | } 212 | }, 213 | "Dance": { 214 | "SliderDance": false, 215 | "TAGSliderDance": false, 216 | "Bezier": { 217 | "Aggressiveness": 60, 218 | "SliderAggressiveness": 3 219 | }, 220 | "Flower": { 221 | "UseNewStyle": true, 222 | "AngleOffset": 90, 223 | "DistanceMult": 0.666, 224 | "StreamTrigger": 130, 225 | "StreamAngleOffset": 90, 226 | "LongJump": -1, 227 | "LongJumpMult": 0.7, 228 | "LongJumpOnEqualPos": false 229 | }, 230 | "HalfCircle": { 231 | "RadiusMultiplier": 1, 232 | "StreamTrigger": 130 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /bmath/camera.go: -------------------------------------------------------------------------------- 1 | package bmath 2 | 3 | import ( 4 | . "danser/osuconst" 5 | "github.com/go-gl/mathgl/mgl32" 6 | ) 7 | 8 | type Rectangle struct { 9 | MinX, MinY, MaxX, MaxY float64 10 | } 11 | 12 | type Camera struct { 13 | screenRect Rectangle 14 | projection mgl32.Mat4 15 | view mgl32.Mat4 16 | projectionView mgl32.Mat4 17 | invProjectionView mgl32.Mat4 18 | 19 | viewDirty bool 20 | origin Vector2d 21 | position Vector2d 22 | rotation float64 23 | scale Vector2d 24 | 25 | rebuildCache bool 26 | cache []mgl32.Mat4 27 | } 28 | 29 | func NewCamera() *Camera { 30 | return &Camera{scale: NewVec2d(1, 1)} 31 | } 32 | 33 | func (camera *Camera) SetViewport(width, height int, yDown bool) { 34 | camera.screenRect.MinX = -float64(width) / 2 35 | camera.screenRect.MaxX = float64(width) / 2 36 | 37 | if yDown { 38 | camera.screenRect.MinY = float64(height) / 2 39 | camera.screenRect.MaxY = -float64(height) / 2 40 | } else { 41 | camera.screenRect.MinY = -float64(height) / 2 42 | camera.screenRect.MaxY = float64(height) / 2 43 | } 44 | 45 | if yDown { 46 | camera.projection = mgl32.Ortho(float32(camera.screenRect.MinX), float32(camera.screenRect.MaxX), float32(camera.screenRect.MinY), float32(camera.screenRect.MaxY), 1, -1) 47 | } else { 48 | camera.projection = mgl32.Ortho(float32(camera.screenRect.MinX), float32(camera.screenRect.MaxX), float32(camera.screenRect.MinY), float32(camera.screenRect.MaxY), -1, 1) 49 | } 50 | 51 | camera.rebuildCache = true 52 | camera.viewDirty = true 53 | } 54 | 55 | func (camera *Camera) SetOsuViewport(width, height int) { 56 | osuAspect := PLAYFIELD_WIDTH / PLAYFIELD_HEIGHT 57 | screenAspect := float64(width) / float64(height) 58 | 59 | if screenAspect > osuAspect { 60 | sh := (PLAYFIELD_HEIGHT - PLAYFIELD_HEIGHT*900.0/1080.0) / 2 61 | sw := (PLAYFIELD_WIDTH*screenAspect*900.0/1080.0 - PLAYFIELD_WIDTH) / 2 62 | camera.screenRect.MinX = -sw 63 | camera.screenRect.MaxX = PLAYFIELD_WIDTH + sw 64 | 65 | camera.screenRect.MinY = PLAYFIELD_HEIGHT + sh 66 | camera.screenRect.MaxY = -sh 67 | } 68 | 69 | camera.projection = mgl32.Ortho(float32(camera.screenRect.MinX), float32(camera.screenRect.MaxX), float32(camera.screenRect.MinY), float32(camera.screenRect.MaxY), 1, -1) 70 | camera.rebuildCache = true 71 | camera.viewDirty = true 72 | } 73 | 74 | func (camera *Camera) SetViewportF(x, y, width, height int) { 75 | camera.screenRect.MinX = float64(x) 76 | camera.screenRect.MaxX = float64(width) 77 | camera.screenRect.MinY = float64(y) 78 | camera.screenRect.MaxY = float64(height) 79 | 80 | camera.projection = mgl32.Ortho(float32(camera.screenRect.MinX), float32(camera.screenRect.MaxX), float32(camera.screenRect.MinY), float32(camera.screenRect.MaxY), 1, -1) 81 | camera.rebuildCache = true 82 | camera.viewDirty = true 83 | } 84 | 85 | func (camera *Camera) calculateView() { 86 | camera.view = mgl32.Translate3D(camera.position.X32(), camera.position.Y32(), 0).Mul4(mgl32.HomogRotate3DZ(float32(camera.rotation))).Mul4(mgl32.Scale3D(camera.scale.X32(), camera.scale.Y32(), 1)).Mul4(mgl32.Translate3D(camera.origin.X32(), camera.origin.Y32(), 0)) 87 | } 88 | 89 | func (camera *Camera) SetPosition(pos Vector2d) { 90 | camera.position = pos 91 | camera.viewDirty = true 92 | } 93 | 94 | func (camera *Camera) SetOrigin(pos Vector2d) { 95 | camera.origin = pos.Scl(-1) 96 | camera.viewDirty = true 97 | } 98 | 99 | func (camera *Camera) SetScale(scale Vector2d) { 100 | camera.scale = scale 101 | camera.viewDirty = true 102 | } 103 | 104 | func (camera *Camera) SetRotation(rad float64) { 105 | camera.rotation = rad 106 | camera.viewDirty = true 107 | } 108 | 109 | func (camera *Camera) Rotate(rad float64) { 110 | camera.rotation += rad 111 | camera.viewDirty = true 112 | } 113 | 114 | func (camera *Camera) Translate(pos Vector2d) { 115 | camera.position = camera.position.Add(pos) 116 | camera.viewDirty = true 117 | } 118 | 119 | func (camera *Camera) Scale(scale Vector2d) { 120 | camera.scale = camera.scale.Mult(scale) 121 | camera.viewDirty = true 122 | } 123 | 124 | func (camera *Camera) Update() { 125 | if camera.viewDirty { 126 | camera.calculateView() 127 | camera.projectionView = camera.projection.Mul4(camera.view) 128 | camera.invProjectionView = camera.projectionView.Inv() 129 | camera.rebuildCache = true 130 | camera.viewDirty = false 131 | } 132 | } 133 | 134 | func (camera *Camera) GenRotated(rotations int, rotOffset float64) []mgl32.Mat4 { 135 | 136 | if len(camera.cache) != rotations || camera.rebuildCache { 137 | if len(camera.cache) != rotations { 138 | camera.cache = make([]mgl32.Mat4, rotations) 139 | } 140 | 141 | for i := 0; i < rotations; i++ { 142 | camera.cache[i] = camera.projection.Mul4(mgl32.HomogRotate3DZ(float32(i) * float32(rotOffset))).Mul4(camera.view) 143 | } 144 | camera.rebuildCache = false 145 | } 146 | 147 | return camera.cache 148 | } 149 | 150 | func (camera *Camera) GenRotatedX(rotations int, rotOffset float64) []mgl32.Mat4 { 151 | 152 | if len(camera.cache) != rotations || camera.rebuildCache { 153 | if len(camera.cache) != rotations { 154 | camera.cache = make([]mgl32.Mat4, rotations) 155 | } 156 | 157 | for i := 0; i < rotations; i++ { 158 | camera.cache[i] = camera.projection.Mul4(mgl32.HomogRotate3DX(float32(i) * float32(rotOffset))).Mul4(camera.view) 159 | } 160 | camera.rebuildCache = false 161 | } 162 | 163 | return camera.cache 164 | } 165 | 166 | func (camera Camera) GetProjectionView() mgl32.Mat4 { 167 | return camera.projectionView 168 | } 169 | 170 | func (camera Camera) Unproject(screenPos Vector2d) Vector2d { 171 | res := camera.invProjectionView.Mul4x1(mgl32.Vec4{float32((screenPos.X + camera.screenRect.MinX) / camera.screenRect.MaxX), -float32((screenPos.Y + camera.screenRect.MaxY) / camera.screenRect.MinY), 0.0, 1.0}) 172 | return NewVec2d(float64(res[0]), float64(res[1])) 173 | } 174 | 175 | func (camera Camera) GetWorldRect() Rectangle { 176 | res := camera.invProjectionView.Mul4x1(mgl32.Vec4{-1.0, 1.0, 0.0, 1.0}) 177 | var rectangle Rectangle 178 | rectangle.MinX = float64(res[0]) 179 | rectangle.MinY = float64(res[1]) 180 | res = camera.invProjectionView.Mul4x1(mgl32.Vec4{1.0, -1.0, 0.0, 1.0}) 181 | rectangle.MaxX = float64(res[0]) 182 | rectangle.MaxY = float64(res[1]) 183 | if rectangle.MinY > rectangle.MaxY { 184 | a := rectangle.MinY 185 | rectangle.MinY, rectangle.MaxY = rectangle.MaxY, a 186 | } 187 | return rectangle 188 | } 189 | -------------------------------------------------------------------------------- /animation/easing/equations.go: -------------------------------------------------------------------------------- 1 | package easing 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | var easings = []func(float64) float64{ 8 | Linear, 9 | OutQuad, 10 | InQuad, 11 | InQuad, 12 | OutQuad, 13 | InOutQuad, 14 | InCubic, 15 | OutCubic, 16 | InOutCubic, 17 | InQuart, 18 | OutQuart, 19 | InOutQuart, 20 | InQuint, 21 | OutQuint, 22 | InOutQuint, 23 | InSine, 24 | OutSine, 25 | InOutSine, 26 | InExpo, 27 | OutExpo, 28 | InOutExpo, 29 | InCirc, 30 | OutCirc, 31 | InOutCirc, 32 | InElastic, 33 | OutElastic, 34 | OutHalfElastic, 35 | OutQuartElastic, 36 | InOutElastic, 37 | InBack, 38 | OutBack, 39 | InOutBack, 40 | InBounce, 41 | OutBounce, 42 | InOutBounce, 43 | } 44 | 45 | func GetEasing(easingID int64) func(float64) float64 { 46 | if easingID < 0 || easingID >= int64(len(easings)) { 47 | easingID = 0 48 | } 49 | return easings[easingID] 50 | } 51 | 52 | /* ======================== 53 | Using equations from: https://github.com/fogleman/ease/blob/master/ease.go 54 | ========================*/ 55 | 56 | func Linear(t float64) float64 { 57 | return t 58 | } 59 | 60 | func InQuad(t float64) float64 { 61 | return t * t 62 | } 63 | 64 | func OutQuad(t float64) float64 { 65 | return -t * (t - 2) 66 | } 67 | 68 | func InOutQuad(t float64) float64 { 69 | if t < 0.5 { 70 | return 2 * t * t 71 | } else { 72 | t = 2*t - 1 73 | return -0.5 * (t*(t-2) - 1) 74 | } 75 | } 76 | 77 | func InCubic(t float64) float64 { 78 | return t * t * t 79 | } 80 | 81 | func OutCubic(t float64) float64 { 82 | t -= 1 83 | return t*t*t + 1 84 | } 85 | 86 | func InOutCubic(t float64) float64 { 87 | t *= 2 88 | if t < 1 { 89 | return 0.5 * t * t * t 90 | } else { 91 | t -= 2 92 | return 0.5 * (t*t*t + 2) 93 | } 94 | } 95 | 96 | func InQuart(t float64) float64 { 97 | return t * t * t * t 98 | } 99 | 100 | func OutQuart(t float64) float64 { 101 | t -= 1 102 | return -(t*t*t*t - 1) 103 | } 104 | 105 | func InOutQuart(t float64) float64 { 106 | t *= 2 107 | if t < 1 { 108 | return 0.5 * t * t * t * t 109 | } else { 110 | t -= 2 111 | return -0.5 * (t*t*t*t - 2) 112 | } 113 | } 114 | 115 | func InQuint(t float64) float64 { 116 | return t * t * t * t * t 117 | } 118 | 119 | func OutQuint(t float64) float64 { 120 | t -= 1 121 | return t*t*t*t*t + 1 122 | } 123 | 124 | func InOutQuint(t float64) float64 { 125 | t *= 2 126 | if t < 1 { 127 | return 0.5 * t * t * t * t * t 128 | } else { 129 | t -= 2 130 | return 0.5 * (t*t*t*t*t + 2) 131 | } 132 | } 133 | 134 | func InSine(t float64) float64 { 135 | return -1*math.Cos(t*math.Pi/2) + 1 136 | } 137 | 138 | func OutSine(t float64) float64 { 139 | return math.Sin(t * math.Pi / 2) 140 | } 141 | 142 | func InOutSine(t float64) float64 { 143 | return -0.5 * (math.Cos(math.Pi*t) - 1) 144 | } 145 | 146 | func InExpo(t float64) float64 { 147 | if t == 0 { 148 | return 0 149 | } else { 150 | return math.Pow(2, 10*(t-1)) 151 | } 152 | } 153 | 154 | func OutExpo(t float64) float64 { 155 | if t == 1 { 156 | return 1 157 | } else { 158 | return 1 - math.Pow(2, -10*t) 159 | } 160 | } 161 | 162 | func InOutExpo(t float64) float64 { 163 | if t == 0 { 164 | return 0 165 | } else if t == 1 { 166 | return 1 167 | } else { 168 | if t < 0.5 { 169 | return 0.5 * math.Pow(2, (20*t)-10) 170 | } else { 171 | return 1 - 0.5*math.Pow(2, (-20*t)+10) 172 | } 173 | } 174 | } 175 | 176 | func InCirc(t float64) float64 { 177 | return -1 * (math.Sqrt(1-t*t) - 1) 178 | } 179 | 180 | func OutCirc(t float64) float64 { 181 | t -= 1 182 | return math.Sqrt(1 - (t * t)) 183 | } 184 | 185 | func InOutCirc(t float64) float64 { 186 | t *= 2 187 | if t < 1 { 188 | return -0.5 * (math.Sqrt(1-t*t) - 1) 189 | } else { 190 | t = t - 2 191 | return 0.5 * (math.Sqrt(1-t*t) + 1) 192 | } 193 | } 194 | 195 | func InElastic(t float64) float64 { 196 | return InElasticFunction(0.5)(t) 197 | } 198 | 199 | func OutElastic(t float64) float64 { 200 | return OutElasticFunction(0.5, 1)(t) 201 | } 202 | 203 | func OutHalfElastic(t float64) float64 { 204 | return OutElasticFunction(0.5, 0.5)(t) 205 | } 206 | 207 | func OutQuartElastic(t float64) float64 { 208 | return OutElasticFunction(0.5, 0.25)(t) 209 | } 210 | 211 | func InOutElastic(t float64) float64 { 212 | return InOutElasticFunction(0.5)(t) 213 | } 214 | 215 | func InElasticFunction(period float64) func(float64) float64 { 216 | p := period 217 | return func(t float64) float64 { 218 | t -= 1 219 | return -1 * (math.Pow(2, 10*t) * math.Sin((t-p/4)*(2*math.Pi)/p)) 220 | } 221 | } 222 | 223 | func OutElasticFunction(period, mod float64) func(float64) float64 { 224 | p := period 225 | return func(t float64) float64 { 226 | return math.Pow(2, -10*t)*math.Sin((mod*t-p/4)*(2*math.Pi/p)) + 1 227 | } 228 | } 229 | 230 | func InOutElasticFunction(period float64) func(float64) float64 { 231 | p := period 232 | return func(t float64) float64 { 233 | t *= 2 234 | if t < 1 { 235 | t -= 1 236 | return -0.5 * (math.Pow(2, 10*t) * math.Sin((t-p/4)*2*math.Pi/p)) 237 | } else { 238 | t -= 1 239 | return math.Pow(2, -10*t)*math.Sin((t-p/4)*2*math.Pi/p)*0.5 + 1 240 | } 241 | } 242 | } 243 | 244 | func InBack(t float64) float64 { 245 | s := 1.70158 246 | return t * t * ((s+1)*t - s) 247 | } 248 | 249 | func OutBack(t float64) float64 { 250 | s := 1.70158 251 | t -= 1 252 | return t*t*((s+1)*t+s) + 1 253 | } 254 | 255 | func InOutBack(t float64) float64 { 256 | s := 1.70158 257 | t *= 2 258 | if t < 1 { 259 | s *= 1.525 260 | return 0.5 * (t * t * ((s+1)*t - s)) 261 | } else { 262 | t -= 2 263 | s *= 1.525 264 | return 0.5 * (t*t*((s+1)*t+s) + 2) 265 | } 266 | } 267 | 268 | func InBounce(t float64) float64 { 269 | return 1 - OutBounce(1-t) 270 | } 271 | 272 | func OutBounce(t float64) float64 { 273 | if t < 4/11.0 { 274 | return (121 * t * t) / 16.0 275 | } else if t < 8/11.0 { 276 | return (363 / 40.0 * t * t) - (99 / 10.0 * t) + 17/5.0 277 | } else if t < 9/10.0 { 278 | return (4356 / 361.0 * t * t) - (35442 / 1805.0 * t) + 16061/1805.0 279 | } else { 280 | return (54 / 5.0 * t * t) - (513 / 25.0 * t) + 268/25.0 281 | } 282 | } 283 | 284 | func InOutBounce(t float64) float64 { 285 | if t < 0.5 { 286 | return InBounce(2*t) * 0.5 287 | } else { 288 | return OutBounce(2*t-1)*0.5 + 0.5 289 | } 290 | } 291 | 292 | func InSquare(t float64) float64 { 293 | if t < 1 { 294 | return 0 295 | } else { 296 | return 1 297 | } 298 | } 299 | 300 | func OutSquare(t float64) float64 { 301 | if t > 0 { 302 | return 1 303 | } else { 304 | return 0 305 | } 306 | } 307 | 308 | func InOutSquare(t float64) float64 { 309 | if t < 0.5 { 310 | return 0 311 | } else { 312 | return 1 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /render/texture/atlas.go: -------------------------------------------------------------------------------- 1 | package texture 2 | 3 | import ( 4 | "github.com/faiface/mainthread" 5 | "github.com/go-gl/gl/v3.3-core/gl" 6 | "log" 7 | "runtime" 8 | ) 9 | 10 | type rectangle struct { 11 | x, y, width, height int 12 | } 13 | 14 | func (rect rectangle) area() int { 15 | return rect.width * rect.height 16 | } 17 | 18 | type TextureAtlas struct { 19 | store *textureStore 20 | defRegion TextureRegion 21 | min, mag Filter 22 | padding int 23 | subTextures map[string]*TextureRegion 24 | emptySpaces map[int32][]rectangle 25 | } 26 | 27 | func NewTextureAtlas(size, mipmaps int) *TextureAtlas { 28 | texture := new(TextureAtlas) 29 | texture.subTextures = make(map[string]*TextureRegion) 30 | texture.emptySpaces = make(map[int32][]rectangle) 31 | texture.emptySpaces[0] = []rectangle{{0, 0, size, size}} 32 | 33 | var siz int32 34 | gl.GetIntegerv(gl.MAX_TEXTURE_SIZE, &siz) 35 | if int(siz) < size { 36 | log.Printf("WARNING: GPU supports only %dx%d textures\n", siz, siz) 37 | size = int(siz) 38 | } 39 | 40 | texture.store = newStore(1, size, size, mipmaps) 41 | texture.defRegion = TextureRegion{texture, 0, 1, 0, 1, int32(size), int32(size), 0} 42 | texture.padding = 1 << uint(texture.store.mipmaps) 43 | 44 | runtime.SetFinalizer(texture, (*TextureAtlas).Dispose) 45 | 46 | return texture 47 | } 48 | 49 | func (texture *TextureAtlas) AddTexture(name string, width, height int, data []uint8) *TextureRegion { 50 | texture.Bind(texture.store.binding) 51 | if len(data) != width*height*4 { 52 | panic("Wrong number of pixels given!") 53 | } 54 | 55 | if int(texture.GetWidth()) < width || int(texture.GetHeight()) < height { 56 | log.Panicf("Texture is too big! Atlas size: %dx%d, texture size: %dx%d", texture.GetWidth(), texture.GetHeight(), width, height) 57 | } 58 | 59 | imBounds := rectangle{0, 0, width + texture.padding, height + texture.padding} 60 | for layer := int32(0); layer < texture.store.layers; layer++ { 61 | j := -1 62 | smallest := rectangle{0, 0, int(texture.store.width), int(texture.store.height)} 63 | 64 | for i := range texture.emptySpaces[layer] { 65 | space := texture.emptySpaces[layer][i] 66 | if imBounds.width <= space.width && imBounds.height <= space.height { 67 | if space.area() <= smallest.area() { 68 | j = i 69 | smallest = space 70 | } 71 | } 72 | } 73 | 74 | if j == -1 { 75 | if layer == texture.store.layers-1 { 76 | texture.newLayer() 77 | } 78 | continue 79 | } else { 80 | dw := smallest.width - imBounds.width 81 | dh := smallest.height - imBounds.height 82 | 83 | var rect1 rectangle 84 | var rect2 rectangle 85 | 86 | if dh > dw { 87 | rect1 = rectangle{smallest.x + imBounds.width, smallest.y, smallest.width - imBounds.width, imBounds.height} 88 | rect2 = rectangle{smallest.x, smallest.y + imBounds.height, smallest.width, smallest.height - imBounds.height} 89 | } else { 90 | rect1 = rectangle{smallest.x + imBounds.width, smallest.y, smallest.width - imBounds.width, smallest.height} 91 | rect2 = rectangle{smallest.x, smallest.y + imBounds.height, imBounds.width, smallest.height - imBounds.height} 92 | } 93 | 94 | texture.emptySpaces[layer] = append(texture.emptySpaces[layer][:j], texture.emptySpaces[layer][j+1:]...) 95 | texture.emptySpaces[layer] = append(texture.emptySpaces[layer], rect1, rect2) 96 | 97 | gl.TexSubImage3D(gl.TEXTURE_2D_ARRAY, 0, int32(smallest.x), int32(smallest.y), int32(layer), int32(width), int32(height), 1, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(data)) 98 | gl.GenerateMipmap(gl.TEXTURE_2D_ARRAY) 99 | 100 | region := TextureRegion{Texture: texture, Width: int32(width), Height: int32(height), Layer: int32(layer)} 101 | region.U1 = (float32(smallest.x) + 0.5) / float32(texture.store.width) 102 | region.V1 = (float32(smallest.y) + 0.5) / float32(texture.store.height) 103 | region.U2 = region.U1 + float32(width-1)/float32(texture.store.width) 104 | region.V2 = region.V1 + float32(height-1)/float32(texture.store.height) 105 | texture.subTextures[name] = ®ion 106 | return ®ion 107 | } 108 | } 109 | 110 | return nil 111 | } 112 | 113 | func (texture *TextureAtlas) GetTexture(name string) *TextureRegion { 114 | return texture.subTextures[name] 115 | } 116 | 117 | func (texture *TextureAtlas) newLayer() { 118 | texture.emptySpaces[texture.store.layers] = []rectangle{{0, 0, int(texture.store.width), int(texture.store.height)}} 119 | 120 | layers := texture.store.layers + 1 121 | 122 | var fbo uint32 123 | gl.GenFramebuffers(1, &fbo) 124 | gl.BindFramebuffer(gl.READ_FRAMEBUFFER, fbo) 125 | 126 | dstStore := newStore(int(layers), int(texture.store.width), int(texture.store.height), int(texture.store.mipmaps)) 127 | dstStore.SetFiltering(texture.min, texture.mag) 128 | dstStore.Bind(texture.store.binding) 129 | 130 | for layer := int32(0); layer < layers-1; layer++ { 131 | for level := int32(0); level < texture.store.mipmaps; level++ { 132 | gl.FramebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.store.id, level, layer) 133 | 134 | div := int32(1 << uint(level)) 135 | gl.CopyTexSubImage3D(gl.TEXTURE_2D_ARRAY, level, 0, 0, layer, 0, 0, texture.store.width/div, texture.store.height/div) 136 | } 137 | } 138 | 139 | gl.DeleteFramebuffers(1, &fbo) 140 | texture.store.Dispose() 141 | 142 | texture.store = dstStore 143 | } 144 | 145 | func (texture *TextureAtlas) SetData(x, y, width, height, layer int, data []uint8) { 146 | if len(data) != width*height*4 { 147 | panic("Wrong number of pixels given!") 148 | } 149 | 150 | gl.TexSubImage3D(gl.TEXTURE_2D_ARRAY, 0, int32(x), int32(y), int32(layer), int32(width), int32(height), 1, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(data)) 151 | if texture.store.mipmaps > 1 { 152 | gl.GenerateMipmap(gl.TEXTURE_2D_ARRAY) 153 | } 154 | } 155 | 156 | func (texture *TextureAtlas) GetID() uint32 { 157 | return texture.store.id 158 | } 159 | 160 | func (texture *TextureAtlas) GetWidth() int32 { 161 | return texture.store.width 162 | } 163 | 164 | func (texture *TextureAtlas) GetHeight() int32 { 165 | return texture.store.height 166 | } 167 | 168 | func (texture *TextureAtlas) GetRegion() TextureRegion { 169 | return texture.defRegion 170 | } 171 | 172 | func (texture *TextureAtlas) GetLayers() int32 { 173 | return texture.store.layers 174 | } 175 | 176 | func (texture *TextureAtlas) SetFiltering(min, mag Filter) { 177 | texture.min, texture.mag = min, mag 178 | texture.store.SetFiltering(min, mag) 179 | } 180 | 181 | func (texture *TextureAtlas) Bind(loc uint) { 182 | texture.store.Bind(loc) 183 | } 184 | 185 | func (texture *TextureAtlas) GetLocation() uint { 186 | return texture.store.binding 187 | } 188 | 189 | func (texture *TextureAtlas) Dispose() { 190 | mainthread.CallNonBlock(func() { 191 | texture.store.Dispose() 192 | }) 193 | } 194 | -------------------------------------------------------------------------------- /database/manager.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "danser/settings" 5 | "database/sql" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "crypto/md5" 12 | "danser/beatmap" 13 | "encoding/hex" 14 | _ "github.com/mattn/go-sqlite3" 15 | "io" 16 | "strconv" 17 | "time" 18 | ) 19 | 20 | var dbFile *sql.DB 21 | 22 | const databaseVersion = 20180814 23 | 24 | func Init() { 25 | var err error 26 | dbFile, err = sql.Open("sqlite3", "danser.db") 27 | 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | _, err = dbFile.Exec(`CREATE TABLE IF NOT EXISTS beatmaps (dir TEXT, file TEXT, lastModified INTEGER, title TEXT, titleUnicode TEXT, artist TEXT, artistUnicode TEXT, creator TEXT, version TEXT, source TEXT, tags TEXT, cs REAL, ar REAL, sliderMultiplier REAL, sliderTickRate REAL, audioFile TEXT, previewTime INTEGER, sampleSet INTEGER, stackLeniency REAL, mode INTEGER, bg TEXT, pauses TEXT, timingPoints TEXT, md5 TEXT, dateAdded INTEGER, playCount INTEGER, lastPlayed INTEGER); 33 | CREATE INDEX IF NOT EXISTS idx ON beatmaps (dir, file); 34 | CREATE TABLE IF NOT EXISTS info (key TEXT NOT NULL UNIQUE, value TEXT);`) 35 | 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | _, err = dbFile.Exec("REPLACE INTO info (key, value) VALUES ('version', ?)", strconv.FormatInt(databaseVersion, 10)) 41 | if err != nil { 42 | log.Println(err) 43 | } 44 | } 45 | 46 | func LoadBeatmaps() []*beatmap.BeatMap { 47 | log.Println("Checking database...") 48 | 49 | searchDir := settings.General.OsuSongsDir 50 | 51 | _, err := os.Open(searchDir) 52 | if os.IsNotExist(err) { 53 | log.Println(searchDir + " does not exist!") 54 | return nil 55 | } 56 | 57 | mod := getLastModified() 58 | 59 | newBeatmaps := make([]*beatmap.BeatMap, 0) 60 | cachedBeatmaps := make([]*beatmap.BeatMap, 0) 61 | 62 | filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error { 63 | if strings.HasSuffix(f.Name(), ".osu") { 64 | cachedTime := mod[filepath.Base(filepath.Dir(path))+"/"+f.Name()] 65 | if cachedTime != f.ModTime().UnixNano()/1000000 { 66 | removeBeatmap(filepath.Base(filepath.Dir(path)), f.Name()) 67 | 68 | file, err := os.Open(path) 69 | 70 | if err == nil { 71 | defer file.Close() 72 | 73 | if bMap := beatmap.ParseBeatMap(file); bMap != nil { 74 | bMap.Dir = filepath.Base(filepath.Dir(path)) 75 | bMap.File = f.Name() 76 | bMap.LastModified = f.ModTime().UnixNano() / 1000000 77 | bMap.TimeAdded = time.Now().UnixNano() / 1000000 78 | log.Println("Importing:", bMap.File) 79 | 80 | hash := md5.New() 81 | if _, err := io.Copy(hash, file); err == nil { 82 | bMap.MD5 = hex.EncodeToString(hash.Sum(nil)) 83 | newBeatmaps = append(newBeatmaps, bMap) 84 | } 85 | } 86 | } 87 | } else { 88 | bMap := beatmap.NewBeatMap() 89 | bMap.Dir = filepath.Base(filepath.Dir(path)) 90 | bMap.File = f.Name() 91 | cachedBeatmaps = append(cachedBeatmaps, bMap) 92 | } 93 | } 94 | return nil 95 | }) 96 | 97 | log.Println("Imported", len(newBeatmaps), "new beatmaps.") 98 | 99 | updateBeatmaps(newBeatmaps) 100 | 101 | log.Println("Found", len(cachedBeatmaps), "cached beatmaps. Loading...") 102 | 103 | loadBeatmaps(cachedBeatmaps) 104 | 105 | allMaps := append(newBeatmaps, cachedBeatmaps...) 106 | 107 | log.Println("Loaded", len(allMaps), "total.") 108 | 109 | return allMaps 110 | } 111 | 112 | func UpdatePlayStats(beatmap *beatmap.BeatMap) { 113 | _, err := dbFile.Exec("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?", beatmap.PlayCount, beatmap.LastPlayed, beatmap.Dir, beatmap.File) 114 | if err != nil { 115 | log.Println(err) 116 | } 117 | } 118 | 119 | func removeBeatmap(dir, file string) { 120 | dbFile.Exec("DELETE FROM beatmaps WHERE dir = ? AND file = ?", dir, file) 121 | } 122 | 123 | func loadBeatmaps(bMaps []*beatmap.BeatMap) { 124 | 125 | beatmaps := make(map[string]int) 126 | 127 | for i, bMap := range bMaps { 128 | beatmaps[bMap.Dir+"/"+bMap.File] = i + 1 129 | } 130 | 131 | res, _ := dbFile.Query("SELECT * FROM beatmaps") 132 | 133 | for res.Next() { 134 | beatmap := beatmap.NewBeatMap() 135 | var mode int 136 | res.Scan( 137 | &beatmap.Dir, 138 | &beatmap.File, 139 | &beatmap.LastModified, 140 | &beatmap.Name, 141 | &beatmap.NameUnicode, 142 | &beatmap.Artist, 143 | &beatmap.ArtistUnicode, 144 | &beatmap.Creator, 145 | &beatmap.Difficulty, 146 | &beatmap.Source, 147 | &beatmap.Tags, 148 | &beatmap.CircleSize, 149 | &beatmap.AR, 150 | &beatmap.Timings.SliderMult, 151 | &beatmap.Timings.TickRate, 152 | &beatmap.Audio, 153 | &beatmap.PreviewTime, 154 | &beatmap.Timings.BaseSet, 155 | &beatmap.StackLeniency, 156 | &mode, 157 | &beatmap.Bg, 158 | &beatmap.PausesText, 159 | &beatmap.TimingPoints, 160 | &beatmap.MD5, 161 | &beatmap.TimeAdded, 162 | &beatmap.PlayCount, 163 | &beatmap.LastPlayed) 164 | 165 | if beatmap.Name+beatmap.Artist+beatmap.Creator == "" || beatmap.TimingPoints == "" { 166 | log.Println("Corrupted cached beatmap found. Removing from database:", beatmap.File) 167 | removeBeatmap(beatmap.Dir, beatmap.File) 168 | continue 169 | } 170 | 171 | key := beatmap.Dir + "/" + beatmap.File 172 | 173 | if beatmaps[key] > 0 { 174 | bMaps[beatmaps[key]-1] = beatmap 175 | beatmap.LoadPauses() 176 | beatmap.LoadTimingPoints() 177 | } 178 | 179 | } 180 | 181 | } 182 | 183 | func updateBeatmaps(bMaps []*beatmap.BeatMap) { 184 | tx, err := dbFile.Begin() 185 | 186 | if err == nil { 187 | var st *sql.Stmt 188 | st, err = tx.Prepare("INSERT INTO beatmaps VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") 189 | 190 | if err == nil { 191 | for _, bMap := range bMaps { 192 | _, err1 := st.Exec(bMap.Dir, 193 | bMap.File, 194 | bMap.LastModified, 195 | bMap.Name, 196 | bMap.NameUnicode, 197 | bMap.Artist, 198 | bMap.ArtistUnicode, 199 | bMap.Creator, 200 | bMap.Difficulty, 201 | bMap.Source, 202 | bMap.Tags, 203 | bMap.CircleSize, 204 | bMap.AR, 205 | bMap.SliderMultiplier, 206 | bMap.Timings.TickRate, 207 | bMap.Audio, 208 | bMap.PreviewTime, 209 | bMap.Timings.BaseSet, 210 | bMap.StackLeniency, 211 | 0, 212 | bMap.Bg, 213 | bMap.PausesText, 214 | bMap.TimingPoints, 215 | bMap.MD5, 216 | bMap.TimeAdded, 217 | bMap.PlayCount, 218 | bMap.LastPlayed) 219 | 220 | if err1 != nil { 221 | log.Println(err1) 222 | } 223 | } 224 | } 225 | 226 | st.Close() 227 | tx.Commit() 228 | } 229 | 230 | if err != nil { 231 | log.Println(err) 232 | } 233 | 234 | } 235 | 236 | func getLastModified() map[string]int64 { 237 | res, _ := dbFile.Query("SELECT dir, file, lastModified FROM beatmaps") 238 | 239 | mod := make(map[string]int64) 240 | 241 | for res.Next() { 242 | var dir string 243 | var file string 244 | var lastModified int64 245 | 246 | res.Scan(&dir, &file, &lastModified) 247 | mod[dir+"/"+file] = lastModified 248 | } 249 | 250 | return mod 251 | } 252 | -------------------------------------------------------------------------------- /beatmap/objects/circle.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "danser/audio" 5 | "danser/bmath" 6 | . "danser/osuconst" 7 | "danser/render" 8 | "danser/settings" 9 | "github.com/go-gl/mathgl/mgl32" 10 | "math" 11 | "strconv" 12 | ) 13 | 14 | type Circle struct { 15 | objData *basicData 16 | sample int 17 | Timings *Timings 18 | } 19 | 20 | func NewCircle(data []string, number int64) *Circle { 21 | circle := &Circle{} 22 | circle.objData = commonParse(data, number) 23 | f, _ := strconv.ParseInt(data[4], 10, 64) 24 | circle.sample = int(f) 25 | circle.objData.EndTime = circle.objData.StartTime 26 | circle.objData.EndPos = circle.objData.StartPos 27 | circle.objData.parseExtras(data, 5) 28 | return circle 29 | } 30 | 31 | func NewCirclebyPath(data []string, number int64, isHR bool) *Circle { 32 | circle := &Circle{} 33 | circle.objData = commonParsebyPath(data, number, isHR) 34 | f, _ := strconv.ParseInt(data[4], 10, 64) 35 | circle.sample = int(f) 36 | circle.objData.EndTime = circle.objData.StartTime 37 | circle.objData.EndPos = circle.objData.StartPos 38 | circle.objData.parseExtras(data, 5) 39 | return circle 40 | } 41 | 42 | func DummyCircle(pos bmath.Vector2d, time int64) *Circle { 43 | return DummyCircleInherit(pos, time, false) 44 | } 45 | 46 | func DummyCircleInherit(pos bmath.Vector2d, time int64, inherit bool) *Circle { 47 | circle := &Circle{objData: &basicData{}} 48 | circle.objData.StartPos = pos 49 | circle.objData.EndPos = pos 50 | circle.objData.StartTime = time 51 | circle.objData.EndTime = time 52 | circle.objData.EndPos = circle.objData.StartPos 53 | circle.objData.SliderPoint = inherit 54 | return circle 55 | } 56 | 57 | func (self Circle) GetBasicData() *basicData { 58 | return self.objData 59 | } 60 | 61 | func (self *Circle) Update(time int64) bool { 62 | 63 | index := self.objData.customIndex 64 | 65 | if index == 0 { 66 | index = self.Timings.Current.SampleIndex 67 | } 68 | 69 | if self.objData.sampleSet == 0 { 70 | audio.PlaySample(self.Timings.Current.SampleSet, self.objData.additionSet, self.sample, index, self.Timings.Current.SampleVolume) 71 | } else { 72 | audio.PlaySample(self.objData.sampleSet, self.objData.additionSet, self.sample, index, self.Timings.Current.SampleVolume) 73 | } 74 | 75 | return true 76 | } 77 | 78 | func (self *Circle) SetTiming(timings *Timings) { 79 | self.Timings = timings 80 | self.objData.JudgeTime = self.objData.StartTime 81 | } 82 | 83 | func (self *Circle) GetPosition() bmath.Vector2d { 84 | return self.objData.StartPos 85 | } 86 | 87 | func (self *Circle) Draw(time int64, preempt float64, fadeIn float64, color mgl32.Vec4, batch *render.SpriteBatch) bool { 88 | alpha := 1.0 89 | fadeInStart := float64(self.objData.StartTime) - preempt 90 | fadeInEnd := math.Min(float64(self.objData.StartTime), fadeInStart+fadeIn) 91 | 92 | if settings.VSplayer.Mods.EnableHD { 93 | hiddenFadeInStart := float64(self.objData.StartTime) - preempt 94 | hiddenFadeInEnd := hiddenFadeInStart + preempt*FADE_IN_DURATION_MULTIPLIER 95 | 96 | hiddenFadeOutStart := hiddenFadeInEnd 97 | hiddenFadeOutEnd := hiddenFadeOutStart + preempt*FADE_OUT_DURATION_MULTIPLIER 98 | if float64(time) < hiddenFadeInEnd && float64(time) >= hiddenFadeInStart { 99 | alpha = Clamp(1.0-(hiddenFadeInEnd-float64(time))/(hiddenFadeInEnd-hiddenFadeInStart), 0.0, 1.0) 100 | } else if float64(time) >= hiddenFadeOutStart { 101 | alpha = Clamp((hiddenFadeOutEnd-float64(time))/(hiddenFadeOutEnd-hiddenFadeOutStart), 0.0, 1.0) 102 | } else { 103 | alpha = float64(color[3]) 104 | } 105 | } else { 106 | if time < self.objData.StartTime && float64(time) >= fadeInStart { 107 | alpha = Clamp(1.0-(fadeInEnd-float64(time))/fadeIn, 0.0, 1.0) 108 | } else if time >= self.objData.StartTime { 109 | alpha = Clamp(1.0-float64(time-self.objData.StartTime)/(preempt/2), 0.0, 1.0) 110 | } else { 111 | alpha = float64(color[3]) 112 | } 113 | } 114 | 115 | batch.SetTranslation(self.objData.StartPos) 116 | 117 | if time >= self.objData.StartTime { 118 | batch.SetSubScale(1+(1.0-alpha)*0.5, 1+(1.0-alpha)*0.5) 119 | } 120 | 121 | if settings.DIVIDES >= settings.Objects.MandalaTexturesTrigger { 122 | alpha *= settings.Objects.MandalaTexturesAlpha 123 | } 124 | 125 | batch.SetColor(float64(color[0]), float64(color[1]), float64(color[2]), alpha) 126 | if settings.DIVIDES >= settings.Objects.MandalaTexturesTrigger { 127 | batch.DrawUnit(*render.CircleFull) 128 | } else { 129 | batch.DrawUnit(*render.Circle) 130 | } 131 | 132 | if settings.VSplayer.PlayerFieldUI.ShowHitCircleNumber { 133 | // 绘制圈内数字 134 | widthratio := float64(render.Circle0.Width) / float64(render.Circle.Width) 135 | heightratio := float64(render.Circle0.Height) / float64(render.Circle.Height) 136 | batch.SetNumberScale(widthratio, heightratio) 137 | batch.SetColor(1, 1, 1, alpha) 138 | 139 | if self.objData.Number < 10 { 140 | // 编号一位数 141 | DrawHitCircleNumber(self.objData.Number, self.objData.StartPos, batch) 142 | } else { 143 | // 只考虑编号两位数的情况 144 | 145 | // 计算十位数和个位数 146 | tenDigit := self.objData.Number / 10 147 | unitDigit := self.objData.Number % 10 148 | 149 | // 计算十位数和个位数的位置 150 | screenratio := PLAYFIELD_HEIGHT / 600 151 | baseX := self.objData.StartPos.X 152 | baseY := self.objData.StartPos.Y 153 | tenDigitWidth := int64(GetHitCircleNumberWidth(tenDigit)) 154 | unitDigitWidth := int64(GetHitCircleNumberWidth(unitDigit)) 155 | tenBaseX := baseX + float64(render.HitCircleOverlap-tenDigitWidth)/2*screenratio 156 | unitBaseX := baseX - float64(render.HitCircleOverlap-unitDigitWidth)/2*screenratio 157 | 158 | DrawHitCircleNumber(tenDigit, bmath.Vector2d{tenBaseX, baseY}, batch) 159 | DrawHitCircleNumber(unitDigit, bmath.Vector2d{unitBaseX, baseY}, batch) 160 | } 161 | } 162 | 163 | if settings.DIVIDES < settings.Objects.MandalaTexturesTrigger { 164 | batch.SetColor(1, 1, 1, alpha) 165 | batch.DrawUnit(*render.CircleOverlay) 166 | } 167 | 168 | batch.SetSubScale(1, 1) 169 | 170 | if time >= self.objData.StartTime+int64(preempt/2) { 171 | return true 172 | } 173 | return false 174 | } 175 | 176 | func (self *Circle) SetDifficulty(preempt, fadeIn float64) { 177 | 178 | } 179 | 180 | func (self *Circle) DrawApproach(time int64, preempt float64, fadeIn float64, color mgl32.Vec4, batch *render.SpriteBatch) { 181 | alpha := 1.0 182 | arr := float64(self.objData.StartTime-time) / preempt 183 | 184 | approachCircleFadeInStart := float64(self.objData.StartTime) - preempt 185 | approachCircleFadeInEnd := math.Min(float64(self.objData.StartTime), approachCircleFadeInStart+2*fadeIn) 186 | 187 | if time < self.objData.StartTime && float64(time) >= approachCircleFadeInStart { 188 | alpha = Clamp(1.0-(approachCircleFadeInEnd-float64(time))/fadeIn, 0.0, 1.0) 189 | } else if time >= self.objData.StartTime { 190 | alpha = Clamp(1.0-float64(time-self.objData.StartTime)/(preempt/2), 0.0, 1.0) 191 | } else { 192 | alpha = float64(color[3]) 193 | } 194 | 195 | batch.SetTranslation(self.objData.StartPos) 196 | 197 | if settings.Objects.DrawApproachCircles && time <= self.objData.StartTime { 198 | batch.SetColor(float64(color[0]), float64(color[1]), float64(color[2]), alpha) 199 | batch.SetSubScale(1.0+arr*2, 1.0+arr*2) 200 | batch.DrawUnitFix(*render.ApproachCircle, float64(128*render.ApproachCircle2x), float64(128*render.ApproachCircle2x)) 201 | } 202 | 203 | batch.SetSubScale(1, 1) 204 | } 205 | 206 | func (self *Circle) GetObjectNumber() int64 { 207 | return self.objData.ObjectNumber 208 | } 209 | -------------------------------------------------------------------------------- /storyboard/object.go: -------------------------------------------------------------------------------- 1 | package storyboard 2 | 3 | import ( 4 | "danser/bmath" 5 | "danser/render" 6 | "danser/render/texture" 7 | "github.com/go-gl/mathgl/mgl32" 8 | "math" 9 | "strings" 10 | "unicode" 11 | ) 12 | 13 | const ( 14 | storyboardArea = 640.0 * 480.0 15 | maxLoad = 1.3328125 //480*480*(16/9)/(640*480) 16 | ) 17 | 18 | type color struct { 19 | R, G, B, A float64 20 | } 21 | 22 | type Object interface { 23 | Update(time int64) 24 | Draw(time int64, batch *render.SpriteBatch) 25 | GetLoad() float64 26 | GetStartTime() int64 27 | GetEndTime() int64 28 | GetZIndex() int64 29 | 30 | GetPosition() bmath.Vector2d 31 | SetPosition(vec bmath.Vector2d) 32 | 33 | GetScale() bmath.Vector2d 34 | SetScale(vec bmath.Vector2d) 35 | 36 | GetRotation() float64 37 | SetRotation(rad float64) 38 | 39 | GetColor() mgl32.Vec3 40 | SetColor(color mgl32.Vec3) 41 | 42 | GetAlpha() float64 43 | SetAlpha(alpha float64) 44 | 45 | SetHFlip(on bool) 46 | SetVFlip(on bool) 47 | 48 | SetAdditive(on bool) 49 | } 50 | 51 | type Sprite struct { 52 | texture []*texture.TextureRegion 53 | frameDelay float64 54 | loopForever bool 55 | currentFrame int 56 | transform *Transformations 57 | loopQueue []*Loop 58 | loopProcessed []*Loop 59 | startTime, endTime, zIndex int64 60 | position bmath.Vector2d 61 | origin bmath.Vector2d 62 | scale bmath.Vector2d 63 | flip bmath.Vector2d 64 | rotation float64 65 | color color 66 | dirty bool 67 | additive bool 68 | firstupdate bool 69 | } 70 | 71 | func cutWhites(text string) (string, int) { 72 | for i, c := range text { 73 | if unicode.IsLetter(c) || unicode.IsNumber(c) { 74 | return text[i:], i 75 | } 76 | } 77 | 78 | return text, 0 79 | } 80 | 81 | func NewSprite(texture []*texture.TextureRegion, frameDelay float64, loopForever bool, zIndex int64, position bmath.Vector2d, origin bmath.Vector2d, subCommands []string) *Sprite { 82 | sprite := &Sprite{texture: texture, frameDelay: frameDelay, loopForever: loopForever, zIndex: zIndex, position: position, origin: origin, scale: bmath.NewVec2d(1, 1), flip: bmath.NewVec2d(1, 1), color: color{1, 1, 1, 1}} 83 | sprite.transform = NewTransformations(sprite) 84 | 85 | var currentLoop *Loop = nil 86 | loopDepth := -1 87 | 88 | for _, subCommand := range subCommands { 89 | command := strings.Split(subCommand, ",") 90 | var removed int 91 | command[0], removed = cutWhites(command[0]) 92 | 93 | if command[0] == "T" { 94 | continue 95 | } 96 | 97 | if removed == 1 { 98 | if currentLoop != nil { 99 | sprite.loopQueue = append(sprite.loopQueue, currentLoop) 100 | loopDepth = -1 101 | } 102 | if command[0] != "L" { 103 | sprite.transform.Add(NewCommand(command)) 104 | } 105 | } 106 | 107 | if command[0] == "L" { 108 | 109 | currentLoop = NewLoop(command, sprite) 110 | 111 | loopDepth = removed + 1 112 | 113 | } else if removed == loopDepth { 114 | currentLoop.Add(NewCommand(command)) 115 | } 116 | } 117 | 118 | if currentLoop != nil { 119 | sprite.loopQueue = append(sprite.loopQueue, currentLoop) 120 | loopDepth = -1 121 | } 122 | 123 | sprite.transform.Finalize() 124 | 125 | sprite.startTime = sprite.transform.startTime 126 | sprite.endTime = sprite.transform.endTime 127 | 128 | for _, loop := range sprite.loopQueue { 129 | if loop.start < sprite.startTime { 130 | sprite.startTime = loop.start 131 | } 132 | 133 | if loop.end > sprite.endTime { 134 | sprite.endTime = loop.end 135 | } 136 | } 137 | 138 | return sprite 139 | } 140 | 141 | func (sprite *Sprite) Update(time int64) { 142 | sprite.currentFrame = 0 143 | 144 | if len(sprite.texture) > 1 { 145 | frame := int(math.Floor(float64(time-sprite.startTime) / sprite.frameDelay)) 146 | if !sprite.loopForever { 147 | if frame >= len(sprite.texture) { 148 | frame = len(sprite.texture) - 1 149 | } 150 | sprite.currentFrame = frame 151 | } else { 152 | sprite.currentFrame = frame % len(sprite.texture) 153 | } 154 | } 155 | 156 | sprite.transform.Update(time) 157 | 158 | for i := 0; i < len(sprite.loopQueue); i++ { 159 | c := sprite.loopQueue[i] 160 | if c.start <= time { 161 | sprite.loopProcessed = append(sprite.loopProcessed, c) 162 | sprite.loopQueue = append(sprite.loopQueue[:i], sprite.loopQueue[i+1:]...) 163 | i-- 164 | } 165 | } 166 | 167 | for i := 0; i < len(sprite.loopProcessed); i++ { 168 | c := sprite.loopProcessed[i] 169 | c.Update(time) 170 | 171 | if time > c.end { 172 | sprite.loopProcessed = append(sprite.loopProcessed[:i], sprite.loopProcessed[i+1:]...) 173 | i-- 174 | } 175 | } 176 | 177 | sprite.firstupdate = true 178 | } 179 | 180 | func (sprite *Sprite) Draw(time int64, batch *render.SpriteBatch) { 181 | if !sprite.firstupdate || sprite.color.A < 0.01 { 182 | return 183 | } 184 | 185 | alpha := sprite.color.A 186 | if alpha > 1.001 { 187 | alpha -= math.Ceil(sprite.color.A) - 1 188 | } 189 | batch.DrawStObject(sprite.position, sprite.origin, sprite.scale.Abs(), sprite.flip, sprite.rotation, mgl32.Vec4{float32(sprite.color.R), float32(sprite.color.G), float32(sprite.color.B), float32(alpha)}, sprite.additive, *sprite.texture[sprite.currentFrame], true) 190 | } 191 | 192 | func (sprite *Sprite) GetPosition() bmath.Vector2d { 193 | return sprite.position 194 | } 195 | 196 | func (sprite *Sprite) SetPosition(vec bmath.Vector2d) { 197 | sprite.position = vec 198 | sprite.dirty = true 199 | } 200 | 201 | func (sprite *Sprite) GetScale() bmath.Vector2d { 202 | return sprite.scale 203 | } 204 | 205 | func (sprite *Sprite) SetScale(vec bmath.Vector2d) { 206 | sprite.scale = vec 207 | sprite.dirty = true 208 | } 209 | 210 | func (sprite *Sprite) GetRotation() float64 { 211 | return sprite.rotation 212 | } 213 | 214 | func (sprite *Sprite) SetRotation(rad float64) { 215 | sprite.rotation = rad 216 | sprite.dirty = true 217 | } 218 | 219 | func (sprite *Sprite) GetColor() mgl32.Vec3 { 220 | return mgl32.Vec3{float32(sprite.color.R), float32(sprite.color.G), float32(sprite.color.B)} 221 | } 222 | 223 | func (sprite *Sprite) SetColor(color mgl32.Vec3) { 224 | sprite.color.R, sprite.color.G, sprite.color.B = float64(color[0]), float64(color[1]), float64(color[2]) 225 | sprite.dirty = true 226 | } 227 | 228 | func (sprite *Sprite) GetAlpha() float64 { 229 | return sprite.color.A 230 | } 231 | 232 | func (sprite *Sprite) SetAlpha(alpha float64) { 233 | sprite.color.A = alpha 234 | sprite.dirty = true 235 | } 236 | 237 | func (sprite *Sprite) SetHFlip(on bool) { 238 | j := 1.0 239 | if on { 240 | j = -1 241 | } 242 | sprite.flip.X = j 243 | sprite.dirty = true 244 | } 245 | 246 | func (sprite *Sprite) SetVFlip(on bool) { 247 | j := 1.0 248 | if on { 249 | j = -1 250 | } 251 | sprite.flip.Y = j 252 | sprite.dirty = true 253 | } 254 | 255 | func (sprite *Sprite) SetAdditive(on bool) { 256 | sprite.additive = on 257 | sprite.dirty = true 258 | } 259 | 260 | func (sprite *Sprite) GetStartTime() int64 { 261 | return sprite.startTime 262 | } 263 | 264 | func (sprite *Sprite) GetEndTime() int64 { 265 | return sprite.endTime 266 | } 267 | 268 | func (sprite *Sprite) GetZIndex() int64 { 269 | return sprite.zIndex 270 | } 271 | 272 | func (sprite *Sprite) GetLoad() float64 { 273 | if sprite.color.A >= 0.01 { 274 | return math.Min((float64(sprite.texture[0].Width)*sprite.scale.X*float64(sprite.texture[0].Height)*sprite.scale.Y)/storyboardArea, maxLoad) 275 | } 276 | return 0 277 | } 278 | --------------------------------------------------------------------------------