1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2007 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27
28 // This code is heavily based off Haeleth's O'caml implementation
29 // (which translates binary GAN files to and from an XML
30 // representation), found at rldev/src/rlxml/gan.ml.
31
32 #include <boost/archive/text_oarchive.hpp>
33 #include <boost/archive/text_iarchive.hpp>
34
35 #include "systems/base/gan_graphics_object_data.h"
36
37 #include <boost/serialization/export.hpp>
38 #include <boost/filesystem/fstream.hpp>
39 #include <iostream>
40 #include <string>
41 #include <vector>
42
43 #include "libreallive/defs.h"
44 #include "machine/serialization.h"
45 #include "systems/base/event_system.h"
46 #include "systems/base/graphics_object.h"
47 #include "systems/base/graphics_system.h"
48 #include "systems/base/surface.h"
49 #include "systems/base/system.h"
50 #include "utilities/exception.h"
51 #include "utilities/file.h"
52
53 using libreallive::read_i32;
54 using std::string;
55 using std::ifstream;
56 using std::ostringstream;
57 using std::cerr;
58 using std::endl;
59 using std::vector;
60
61 namespace fs = boost::filesystem;
62
63 // -----------------------------------------------------------------------
64 // GanGraphicsObjectData
65 // -----------------------------------------------------------------------
66
GanGraphicsObjectData(System & system)67 GanGraphicsObjectData::GanGraphicsObjectData(System& system)
68 : system_(system),
69 current_set_(-1),
70 current_frame_(-1),
71 time_at_last_frame_change_(0) {}
72
GanGraphicsObjectData(System & system,const std::string & gan_file,const std::string & img_file)73 GanGraphicsObjectData::GanGraphicsObjectData(System& system,
74 const std::string& gan_file,
75 const std::string& img_file)
76 : system_(system),
77 gan_filename_(gan_file),
78 img_filename_(img_file),
79 current_set_(-1),
80 current_frame_(-1),
81 time_at_last_frame_change_(0) {
82 LoadGANData();
83 }
84
~GanGraphicsObjectData()85 GanGraphicsObjectData::~GanGraphicsObjectData() {}
86
LoadGANData()87 void GanGraphicsObjectData::LoadGANData() {
88 image_ = system_.graphics().GetSurfaceNamed(img_filename_);
89 image_->EnsureUploaded();
90
91 fs::path gan_file_path = system_.FindFile(gan_filename_, GAN_FILETYPES);
92 if (gan_file_path.empty()) {
93 ostringstream oss;
94 oss << "Could not find GAN file \"" << gan_filename_ << "\".";
95 throw rlvm::Exception(oss.str());
96 }
97
98 int file_size = 0;
99 std::unique_ptr<char[]> gan_data;
100 if (LoadFileData(gan_file_path, gan_data, file_size)) {
101 ostringstream oss;
102 oss << "Could not read the contents of \"" << gan_file_path << "\"";
103 throw rlvm::Exception(oss.str());
104 }
105
106 TestFileMagic(gan_filename_, gan_data, file_size);
107 ReadData(gan_filename_, gan_data, file_size);
108 }
109
TestFileMagic(const std::string & file_name,std::unique_ptr<char[]> & gan_data,int file_size)110 void GanGraphicsObjectData::TestFileMagic(const std::string& file_name,
111 std::unique_ptr<char[]>& gan_data,
112 int file_size) {
113 const char* data = gan_data.get();
114 int a = read_i32(data);
115 int b = read_i32(data + 0x04);
116 int c = read_i32(data + 0x08);
117
118 if (a != 10000 || b != 10000 || c != 10100)
119 ThrowBadFormat(file_name, "Incorrect GAN file magic");
120 }
121
ReadData(const std::string & file_name,std::unique_ptr<char[]> & gan_data,int file_size)122 void GanGraphicsObjectData::ReadData(const std::string& file_name,
123 std::unique_ptr<char[]>& gan_data,
124 int file_size) {
125 const char* data = gan_data.get();
126 int file_name_length = read_i32(data + 0xc);
127 string raw_file_name = data + 0x10;
128
129 // Strings should be NULL terminated.
130 data = data + 0x10 + file_name_length - 1;
131 if (*data != 0)
132 ThrowBadFormat(file_name, "Incorrect filename length in GAN header");
133 data++;
134
135 int twenty_thousand = read_i32(data);
136 if (twenty_thousand != 20000)
137 ThrowBadFormat(file_name, "Expected start of GAN data section");
138 data += 4;
139
140 int number_of_sets = read_i32(data);
141 data += 4;
142
143 for (int i = 0; i < number_of_sets; ++i) {
144 int start_of_ganset = read_i32(data);
145 if (start_of_ganset != 0x7530)
146 ThrowBadFormat(file_name, "Expected start of GAN set");
147
148 data += 4;
149 int frame_count = read_i32(data);
150 if (frame_count < 0)
151 ThrowBadFormat(file_name,
152 "Expected animation to contain at least one frame");
153 data += 4;
154
155 vector<Frame> animation_set;
156 for (int j = 0; j < frame_count; ++j)
157 animation_set.push_back(ReadSetFrame(file_name, data));
158 animation_sets.push_back(animation_set);
159 }
160 }
161
ReadSetFrame(const std::string & file_name,const char * & data)162 GanGraphicsObjectData::Frame GanGraphicsObjectData::ReadSetFrame(
163 const std::string& file_name,
164 const char*& data) {
165 GanGraphicsObjectData::Frame frame;
166
167 int tag = read_i32(data);
168 data += 4;
169 while (tag != 999999) {
170 int value = read_i32(data);
171 data += 4;
172
173 switch (tag) {
174 case 30100:
175 frame.pattern = value;
176 break;
177 case 30101:
178 frame.x = value;
179 break;
180 case 30102:
181 frame.y = value;
182 break;
183 case 30103:
184 frame.time = value;
185 break;
186 case 30104:
187 frame.alpha = value;
188 break;
189 case 30105:
190 frame.other = value;
191 break;
192 default: {
193 ostringstream oss;
194 oss << "Unknown GAN frame tag: " << tag;
195 ThrowBadFormat(file_name, oss.str());
196 }
197 }
198
199 tag = read_i32(data);
200 data += 4;
201 }
202
203 return frame;
204 }
205
ThrowBadFormat(const std::string & file_name,const std::string & error)206 void GanGraphicsObjectData::ThrowBadFormat(const std::string& file_name,
207 const std::string& error) {
208 ostringstream oss;
209 oss << "File \"" << file_name
210 << "\" does not appear to be in GAN format: " << error;
211 throw rlvm::Exception(oss.str());
212 }
213
PixelWidth(const GraphicsObject & rendering_properties)214 int GanGraphicsObjectData::PixelWidth(
215 const GraphicsObject& rendering_properties) {
216 if (current_set_ != -1 && current_frame_ != -1) {
217 const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
218 if (frame.pattern != -1) {
219 const Surface::GrpRect& rect = image_->GetPattern(frame.pattern);
220 return int(rendering_properties.GetWidthScaleFactor() *
221 rect.rect.width());
222 }
223 }
224
225 return 0;
226 }
227
PixelHeight(const GraphicsObject & rendering_properties)228 int GanGraphicsObjectData::PixelHeight(
229 const GraphicsObject& rendering_properties) {
230 if (current_set_ != -1 && current_frame_ != -1) {
231 const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
232 if (frame.pattern != -1) {
233 const Surface::GrpRect& rect = image_->GetPattern(frame.pattern);
234 return int(rendering_properties.GetHeightScaleFactor() *
235 rect.rect.height());
236 }
237 }
238
239 return 0;
240 }
241
Clone() const242 GraphicsObjectData* GanGraphicsObjectData::Clone() const {
243 return new GanGraphicsObjectData(*this);
244 }
245
Execute(RLMachine & machine)246 void GanGraphicsObjectData::Execute(RLMachine& machine) {
247 if (is_currently_playing() && current_frame_ >= 0) {
248 unsigned int current_time = system_.event().GetTicks();
249 unsigned int time_since_last_frame_change =
250 current_time - time_at_last_frame_change_;
251
252 const vector<Frame>& current_set = animation_sets.at(current_set_);
253 unsigned int frame_time = (unsigned int)(current_set[current_frame_].time);
254 if (time_since_last_frame_change > frame_time) {
255 current_frame_++;
256 if (size_t(current_frame_) == current_set.size()) {
257 current_frame_--;
258 // endAnimation() can delete this, so it needs to be the last thing
259 // done in this code path...
260 EndAnimation();
261 } else {
262 time_at_last_frame_change_ = current_time;
263 system_.graphics().MarkScreenAsDirty(GUT_DISPLAY_OBJ);
264 }
265 }
266 }
267 }
268
LoopAnimation()269 void GanGraphicsObjectData::LoopAnimation() { current_frame_ = 0; }
270
CurrentSurface(const GraphicsObject & go)271 std::shared_ptr<const Surface> GanGraphicsObjectData::CurrentSurface(
272 const GraphicsObject& go) {
273 if (current_set_ != -1 && current_frame_ != -1) {
274 const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
275
276 if (frame.pattern != -1) {
277 // We are currently rendering an animation AND the current frame says to
278 // render something to the screen.
279 return image_;
280 }
281 }
282
283 return std::shared_ptr<const Surface>();
284 }
285
SrcRect(const GraphicsObject & go)286 Rect GanGraphicsObjectData::SrcRect(const GraphicsObject& go) {
287 const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
288 if (frame.pattern != -1) {
289 return image_->GetPattern(frame.pattern).rect;
290 }
291
292 return Rect();
293 }
294
DstOrigin(const GraphicsObject & go)295 Point GanGraphicsObjectData::DstOrigin(const GraphicsObject& go) {
296 const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
297 return GraphicsObjectData::DstOrigin(go) - Size(frame.x, frame.y);
298 }
299
GetRenderingAlpha(const GraphicsObject & go,const GraphicsObject * parent)300 int GanGraphicsObjectData::GetRenderingAlpha(const GraphicsObject& go,
301 const GraphicsObject* parent) {
302 const Frame& frame = animation_sets.at(current_set_).at(current_frame_);
303 if (frame.pattern != -1) {
304 // Calculate the combination of our frame alpha with the current object
305 // alpha.
306 float parent_alpha = parent ? (parent->GetComputedAlpha() / 255.0f) : 1;
307 return int(((frame.alpha / 255.0f) * (go.GetComputedAlpha() / 255.0f) *
308 parent_alpha) *
309 255);
310 } else {
311 // Should never happen.
312 return go.GetComputedAlpha();
313 }
314 }
315
ObjectInfo(std::ostream & tree)316 void GanGraphicsObjectData::ObjectInfo(std::ostream& tree) {
317 tree << " GAN file: " << gan_filename_ << " (Using image: " << img_filename_
318 << ")" << endl;
319 }
320
PlaySet(int set)321 void GanGraphicsObjectData::PlaySet(int set) {
322 set_is_currently_playing(true);
323 current_set_ = set;
324 current_frame_ = 0;
325 time_at_last_frame_change_ = system_.event().GetTicks();
326 system_.graphics().MarkScreenAsDirty(GUT_DISPLAY_OBJ);
327 }
328
329 template <class Archive>
load(Archive & ar,unsigned int version)330 void GanGraphicsObjectData::load(Archive& ar, unsigned int version) {
331 ar& boost::serialization::base_object<GraphicsObjectData>(*this) &
332 gan_filename_ & img_filename_ & current_set_ & current_frame_ &
333 time_at_last_frame_change_;
334
335 LoadGANData();
336
337 // Saving |time_at_last_frame_change_| as part of the format is obviously a
338 // mistake, but is now baked into the file format. Ask the clock for a more
339 // suitable value.
340 if (time_at_last_frame_change_ != 0) {
341 time_at_last_frame_change_ = system_.event().GetTicks();
342 system_.graphics().MarkScreenAsDirty(GUT_DISPLAY_OBJ);
343 }
344 }
345
346 template <class Archive>
save(Archive & ar,unsigned int version) const347 void GanGraphicsObjectData::save(Archive& ar, unsigned int version) const {
348 ar& boost::serialization::base_object<GraphicsObjectData>(*this) &
349 gan_filename_ & img_filename_ & current_set_ & current_frame_ &
350 time_at_last_frame_change_;
351 }
352
353 // -----------------------------------------------------------------------
354
355 // Explicit instantiations for text archives (since we hide the
356 // implementation)
357
358 template void GanGraphicsObjectData::save<boost::archive::text_oarchive>(
359 boost::archive::text_oarchive& ar,
360 unsigned int version) const;
361
362 template void GanGraphicsObjectData::load<boost::archive::text_iarchive>(
363 boost::archive::text_iarchive& ar,
364 unsigned int version);
365
366 // -----------------------------------------------------------------------
367
368 BOOST_CLASS_EXPORT(GanGraphicsObjectData);
369