1 /**
2 Copyright: Copyright (c) 2017-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.graphics.animation.spritesheetanimation;
7 
8 import voxelman.math;
9 import voxelman.graphics;
10 
11 struct AnimationFrame
12 {
13 	Sprite sprite;
14 	float timelineStart;
15 	float timelineEnd;
16 }
17 
18 struct SpriteSheetAnimation
19 {
20 	AnimationFrame[] frames;
21 	float totalDuration;
22 }
23 
24 alias SpriteSheetAnimationRef = SpriteSheetAnimation*;
25 
26 
27 import std.json;
28 SpriteSheetAnimationRef loadSpriteSheetAnimation(string filename, TextureAtlas texAtlas)
29 {
30 	// create filenames
31 	import std.path : setExtension;
32 	string imageFilename = setExtension(filename, "png");
33 	string metaFilename = setExtension(filename, "json");
34 
35 	// load texture
36 	import dlib.image.io.png;
37 	auto image = new Bitmap(loadPNG(imageFilename));
38 
39 	// parse frames
40 	import std.file;
41 	string metaFileData = cast(string)read(metaFilename);
42 	auto jsonValue = parseJSON(metaFileData);
43 	auto frames = parseFrames(jsonValue["frames"].array);
44 	float totalDuration = frames[$-1].timelineEnd;
45 
46 	putFramesIntoAtlas(image, frames, texAtlas);
47 
48 	return new SpriteSheetAnimation(frames, totalDuration);
49 }
50 
51 AnimationFrame[] parseFrames(JSONValue[] framesArray)
52 {
53 	AnimationFrame[] frames;
54 	float timelineStart = 0;
55 	float timelineEnd = 0;
56 	foreach(frameJson; framesArray)
57 	{
58 		auto frame = AnimationFrame(
59 			Sprite(irect(
60 				cast(int)frameJson["frame"]["x"].integer,
61 				cast(int)frameJson["frame"]["y"].integer,
62 				cast(int)frameJson["frame"]["w"].integer,
63 				cast(int)frameJson["frame"]["h"].integer)));
64 		auto durationMsecs = cast(int)frameJson["duration"].integer;
65 		frame.timelineStart = timelineStart;
66 		timelineEnd = timelineStart + durationMsecs / 1000.0;
67 		frame.timelineEnd = timelineEnd;
68 		timelineStart = timelineEnd;
69 		frames ~= frame;
70 	}
71 
72 	return frames;
73 }
74 
75 // After this frames refer to positions in atlas
76 void putFramesIntoAtlas(Bitmap sourceBitmap, AnimationFrame[] frames, TextureAtlas texAtlas)
77 {
78 	foreach(ref frame; frames)
79 	{
80 		ivec2 position = texAtlas.insert(sourceBitmap, frame.sprite.atlasRect);
81 		frame.sprite.atlasRect.position = position;
82 	}
83 }
84 
85 enum AnimationStatus
86 {
87 	playing,
88 	paused
89 }
90 
91 struct AnimationInstance
92 {
93 	SpriteSheetAnimationRef sheet;
94 	AnimationStatus status;
95 	double timer = 0;
96 	size_t currentFrame = 0;
97 	vec2 scale = vec2(1, 1);
98 	vec2 origin = vec2(0, 0);
99 	void delegate() onLoop;
100 
101 	void update(double dt)
102 	{
103 		if (status != AnimationStatus.playing) return;
104 
105 		timer = timer + dt;
106 
107 		double loops = floor(timer / sheet.totalDuration);
108 		if (loops != 0)
109 		{
110 			timer = timer - sheet.totalDuration * loops;
111 			if (onLoop) onLoop();
112 		}
113 
114 		currentFrame = seekFrameIndex(timer);
115 	}
116 
117 	Sprite currentFrameSprite()
118 	{
119 		return sheet.frames[currentFrame].sprite;
120 	}
121 
122 	void pause()
123 	{
124 		status = AnimationStatus.paused;
125 	}
126 
127 	void resume()
128 	{
129 		status = AnimationStatus.playing;
130 	}
131 
132 	void gotoFrame(int frame)
133 	{
134 		currentFrame = clamp(frame, 0, sheet.frames.length-1);
135 		timer = sheet.frames[currentFrame].timelineStart;
136 	}
137 
138 	size_t seekFrameIndex(float timer)
139 	{
140 		if (sheet.frames.length < 2) return 0;
141 
142 		int right = cast(int)(sheet.frames.length-1);
143 		int left = 0;
144 		int middle = 0;
145 
146 		while (left <= right)
147 		{
148 			middle = (left + right) / 2;
149 			if (timer > sheet.frames[middle].timelineEnd) left = middle + 1;
150 			else if (timer <= sheet.frames[middle].timelineStart) right = middle - 1;
151 			else return middle;
152 		}
153 
154 		return middle;
155 	}
156 }