Branch data Line data Source code
1 : : #ifdef RUN_TESTS
2 : : # include <gtest/gtest.h>
3 : : # include <set>
4 : : #endif
5 : : /** @file rendering/person.cc
6 : : * @brief Defines PersonTransform(), which renders a person animation on the screen.
7 : : */
8 : :
9 : : #include <cstring>
10 : : #include "clock.hh"
11 : : #include "color.hh"
12 : : #include "cell.hh"
13 : : #include "256color.hh"
14 : :
15 : : /** This is the graphics data that gets rendered. It consists of two four-color 16x16 sprites. */
16 : : static constexpr char persondata[] =
17 : : " ##### "
18 : : " ###### #'''''### "
19 : : " #''''''## #'''''''''# "
20 : : " #'''''''''# ###'.#.### "
21 : : " ###..#.### #..##.#....# "
22 : : " #..##.#....# #..##..#...# "
23 : : " #..##..#...# ##...##### "
24 : : " ##...##### ###.....# "
25 : : " ##.....# ##'''##''### "
26 : : " #''##''# #..''''##''#'# "
27 : : " #''''##''# #..'''######'.#"
28 : : " #''''##### #..####.##.#.#"
29 : : " #...##.## .#########''# "
30 : : " #..'''### #''######'''# "
31 : : " #'''''# #'''# #'''# "
32 : : " ##### ### ### ";
33 : : static constexpr unsigned xcoordinates[2] = { 0, 16 };
34 : : static constexpr unsigned person_width = 16;
35 : : static constexpr unsigned data_width = 32, data_lines = 16;
36 : :
37 : : static double walk_speed = 64.0; // pixels per second (64 is normal)
38 : : static double frame_rate = 6.0; // (6 is normal)
39 : :
40 : 336486 : static double GetStartTime()
41 : : {
42 [ + + + - : 336486 : static double value = GetTime();
+ - ]
43 : 336486 : return value;
44 : : }
45 : :
46 : : /** X-coordinate where the Person is */
47 : 313062 : int PersonBaseX(unsigned window_width)
48 : : {
49 : 313062 : double start = GetStartTime();
50 : 313062 : double time_elapsed = GetTime() - start;
51 [ + - ]: 313062 : unsigned walkway_width = window_width + std::max(person_width, window_width/5) + person_width;
52 : 313062 : return unsigned(time_elapsed * walk_speed) % walkway_width - person_width;
53 : : }
54 : : /** Which frame to draw right now */
55 : 23424 : static int PersonFrame()
56 : : {
57 : 23424 : double start = GetStartTime();
58 : 23424 : double time_elapsed = GetTime() - start;
59 : 23424 : return unsigned(time_elapsed * frame_rate) % 2;
60 : : }
61 : :
62 : : /** ColorSlideCache implements smooth color slides in text mode. */
63 : : class ColorSlideCache
64 : : {
65 : : enum { MaxWidth = 3840 };
66 : : const unsigned char* const colors;
67 : : const unsigned short* const color_positions;
68 : : const unsigned color_length;
69 : : unsigned cached_width;
70 : : unsigned short cache_color[MaxWidth];
71 : : unsigned char cache_char[MaxWidth];
72 : :
73 : : public:
74 : : /** Saves the desired color positions and proportions of the slide for latter use. */
75 : : ColorSlideCache(const unsigned char* c, const unsigned short* p, unsigned l)
76 : : : colors(c), color_positions(p), color_length(l), cached_width(0) {}
77 : :
78 : : /** Configures the slide for a particular rendering width. */
79 : 619584 : void SetWidth(unsigned w)
80 : : {
81 [ + + ]: 619584 : if(w == cached_width) return;
82 : 3 : cached_width = w;
83 : 3 : unsigned char first=0;
84 : 3 : { std::memset(cache_char, 0, sizeof(cache_char));
85 : 3 : std::memset(cache_color, 0, sizeof(cache_color)); }
86 [ + + ]: 403 : for(unsigned x=0; x<w && x<MaxWidth; ++x)
87 : : {
88 : 400 : unsigned short cur_position = (((unsigned long)x) << 16u) / w;
89 [ + + + + ]: 477 : while(first < color_length && color_positions[first] <= cur_position) ++first;
90 : 400 : unsigned long next_position=0;
91 : 400 : unsigned char next_value=0;
92 [ + + ]: 400 : if(first < color_length)
93 : 394 : { next_position = color_positions[first]; next_value = colors[first]; }
94 : : else
95 : 6 : { next_position = 10000ul; next_value = colors[color_length-1]; }
96 : :
97 : 400 : unsigned short prev_position=0;
98 : 400 : unsigned char prev_value =next_value;
99 [ + - ]: 400 : if(first > 0)
100 : 400 : { prev_position = color_positions[first-1]; prev_value = colors[first-1]; }
101 : :
102 : 400 : float position = (cur_position - prev_position) / float(next_position - prev_position );
103 : : //static const unsigned char chars[4] = { 0x20, 0xB0, 0xB1, 0xB2 };
104 : : //unsigned char ch = chars[unsigned(position*4)];
105 : 400 : static const unsigned char chars[2] = { 0,1};
106 : 400 : unsigned char ch = chars[unsigned(position*2)];
107 : : //static const unsigned char chars[2] = { 0x20, 0xDC };
108 : : //unsigned char ch = chars[unsigned(position*2)];
109 : : //unsigned char ch = 'A';
110 : : //if(prev_value == next_value || ch == 0x20) { ch = 0x20; next_value = 0; }
111 [ + + ]: 400 : if(prev_value == next_value || ch == 0) { ch = 0; next_value = 0; }
112 : 400 : cache_char[x] = ch;
113 : 400 : cache_color[x] = prev_value | (next_value << 8u);
114 : : //cache[x] = 0x80008741ul;
115 : : }
116 : : }
117 : : /** Retrieves the color data for the given position on the slide.
118 : : * @param x Coordinate, must be smaller than the width in SetWidth
119 : : * @param c1 Out-param: Background color to render
120 : : * @param c2 Out-param: Foreground color to render, if ch is not blank (0)
121 : : * @param ch Out-param: Character to render (1 = dither pattern, 0 = black)
122 : : */
123 : 619584 : inline void Get(unsigned x, unsigned char& ch, unsigned char& c1, unsigned char& c2) const
124 : : {
125 : 619584 : unsigned short tmp = cache_color[x];
126 : 619584 : ch = cache_char[x];
127 : 619584 : c1 = tmp;
128 : 619584 : c2 = tmp >> 8u;
129 : : }
130 : : };
131 : : static const unsigned char slide1_colors[21] = {6,73,109,248,7,7,7,7,7,248,109,73,6,6,6,36,35,2,2,28,22};
132 : : static const unsigned short slide1_positions[21] = {0u,1401u,3711u,6302u,7072u,8192u,16384u,24576u,32768u,33889u,34659u,37250u,39560u,40960u,49152u,50903u,53634u,55944u,57344u,59937u,63981u};
133 : : static const unsigned char slide2_colors[35] = {248,7,249,250,251,252,188,253,254,255,15,230,229,228,227,11,227,185,186,185,179,143,142,136,100,94,58,239,238,8,236,235,234,233,0};
134 : : static const unsigned short slide2_positions[35] = {0u,440u,1247u,2126u,3006u,3886u,4839u,5938u,6965u,8064u,9750u,12590u,15573u,18029u,19784u,21100u,24890u,27163u,30262u,35051u,35694u,38054u,40431u,41156u,46212u,46523u,50413u,52303u,53249u,54194u,56294u,58815u,61335u,63856u,64696u};
135 : : static ColorSlideCache slide1(slide1_colors, slide1_positions, sizeof(slide1_colors));
136 : : static ColorSlideCache slide2(slide2_colors, slide2_positions, sizeof(slide2_colors));
137 : :
138 : : /* Renders person on the screen. */
139 : 619584 : void PersonTransform(unsigned& bgcolor, unsigned& fgcolor,
140 : : unsigned width, unsigned x, unsigned y,
141 : : unsigned action_type)
142 : : {
143 : : // Action_type:
144 : : // 1 = top of screen (Person, green slide)
145 : : // 2 = bottom of screen (Status, yellow slide)
146 : : // 0 = anything else
147 [ + - ]: 619584 : if(bgcolor != Cell{}.fgcolor)
148 : : {
149 : : // Only transform lines with white (ansi 7) background
150 : 596288 : return;
151 : : }
152 : :
153 : 1239168 : auto GetSlide = [&](ColorSlideCache& slide)
154 : : {
155 : 619584 : slide.SetWidth(width);
156 : 619584 : unsigned char ch,c1,c2; slide.Get(x,ch,c1,c2);
157 : 619584 : unsigned result = c1;
158 [ + + ]: 619584 : if(ch)
159 : : {
160 : 214896 : unsigned pos = (x^y)&1;
161 [ + + ]: 214896 : result = pos ? c2 : c1;
162 : : }
163 : 619584 : return xterm256table[result];
164 : 619584 : };
165 [ + + ]: 619584 : if(action_type <= 1) bgcolor = GetSlide(slide1); // Not bottom of screen
166 [ + + ]: 619584 : if(action_type == 2) bgcolor = GetSlide(slide2); // Is bottom of screen
167 : :
168 [ + + + - ]: 619584 : if(y >= data_lines || action_type != 1) // Stop here if not to render person
169 : : {
170 : : return;
171 : : }
172 : :
173 : : //unsigned scrx = x;
174 : 309760 : unsigned basex = PersonBaseX(width);
175 : : #ifdef CLOCK_BACKWARDS
176 : : basex = width - basex;
177 : : #endif
178 : 309760 : x -= basex;
179 : : // Person outside view?
180 [ + + ]: 309760 : if(x >= person_width)
181 : : {
182 : : return;
183 : : }
184 : : #ifdef CLOCK_BACKWARDS
185 : : x = person_width-1-x;
186 : : #endif
187 : :
188 : 23296 : unsigned frame_number = PersonFrame();
189 : 23296 : unsigned frame_start = xcoordinates[frame_number];
190 : 23296 : char c = persondata[y*data_width + frame_start + x];
191 [ + + + + : 23296 : switch(c)
- ]
192 : : {
193 : 9204 : case ' ': //bgcolor=fgcolor = Mix(bgcolor, fgcolor, 15,1,16); break;
194 : 9204 : fgcolor = Mix(bgcolor, fgcolor, 1,15,16); break;
195 : 2997 : case '.': bgcolor=fgcolor = Mix(bgcolor, 0x000000, 7,1,8); break;
196 : 3675 : case '\'':bgcolor=fgcolor = Mix(bgcolor, 0x000000, 6,2,8); break;
197 : 7420 : case '#': bgcolor=fgcolor = 0x000000; break;
198 : : }
199 : : }
200 : :
201 : : #ifdef RUN_TESTS
202 : 3 : TEST(person, x_coordinate_varies)
203 : : {
204 : 1 : SetTimeFactor(0.);
205 : 1 : std::set<int> coordinates;
206 : 1 : const unsigned width = 640, duration=25, fps=128;
207 : : // Simulate 25 seconds of time, window width 640, at framerate 128fps.
208 : : // We should get every X coordinate covered plus a few outside the screen.
209 [ + + ]: 3201 : for(unsigned frame=0; frame<duration*fps; ++frame)
210 : : {
211 [ + - + - ]: 3200 : coordinates.insert( PersonBaseX(width) );
212 [ + - ]: 3200 : AdvanceTime(1.0 / fps);
213 : : }
214 : 1 : EXPECT_GT(coordinates.size(), std::size_t(width));
215 [ + - ]: 1 : if(!coordinates.empty())
216 : : {
217 : 1 : EXPECT_LT(*coordinates.begin(), 0);
218 : 2 : EXPECT_GT(*coordinates.rbegin(), int(width));
219 : : }
220 : 1 : }
221 : 3 : TEST(person, pose_varies)
222 : : {
223 : 1 : SetTimeFactor(0.);
224 : 1 : std::set<int> frames;
225 : : // Simulate 4 seconds of time, framerate 32fps.
226 : : // We should get both poses of person.
227 [ + + ]: 129 : for(unsigned frame=0; frame<128; ++frame)
228 : : {
229 [ + - + - ]: 128 : frames.insert( PersonFrame() );
230 [ + - ]: 128 : AdvanceTime(1.0 / 32.0);
231 : : }
232 : 2 : EXPECT_EQ(frames.size(), 2u);
233 : 1 : }
234 : : #endif
|