Initial commit
This commit is contained in:
377
engines/stark/resources/image.cpp
Normal file
377
engines/stark/resources/image.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/stark/resources/image.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "image/png.h"
|
||||
|
||||
#include "engines/stark/debug.h"
|
||||
#include "engines/stark/formats/xrc.h"
|
||||
#include "engines/stark/gfx/driver.h"
|
||||
#include "engines/stark/resources/location.h"
|
||||
#include "engines/stark/services/archiveloader.h"
|
||||
#include "engines/stark/services/settings.h"
|
||||
#include "engines/stark/services/services.h"
|
||||
#include "engines/stark/visual/effects/bubbles.h"
|
||||
#include "engines/stark/visual/effects/fireflies.h"
|
||||
#include "engines/stark/visual/effects/fish.h"
|
||||
#include "engines/stark/visual/image.h"
|
||||
#include "engines/stark/visual/text.h"
|
||||
|
||||
#include "math/line2d.h"
|
||||
#include "math/vector2d.h"
|
||||
|
||||
namespace Stark {
|
||||
namespace Resources {
|
||||
|
||||
Object *Image::construct(Object *parent, byte subType, uint16 index, const Common::String &name) {
|
||||
switch (subType) {
|
||||
case kImageSub2:
|
||||
case kImageSub3:
|
||||
return new ImageStill(parent, subType, index, name);
|
||||
case kImageText:
|
||||
return new ImageText(parent, subType, index, name);
|
||||
default:
|
||||
error("Unknown image subtype %d", subType);
|
||||
}
|
||||
}
|
||||
|
||||
Image::~Image() {
|
||||
delete _visual;
|
||||
}
|
||||
|
||||
Image::Image(Object *parent, byte subType, uint16 index, const Common::String &name) :
|
||||
Object(parent, subType, index, name),
|
||||
_transparent(false),
|
||||
_transparentColor(0),
|
||||
_field_44_ADF(0),
|
||||
_field_48_ADF(30),
|
||||
_visual(nullptr) {
|
||||
_type = TYPE;
|
||||
}
|
||||
|
||||
void Image::readData(Formats::XRCReadStream *stream) {
|
||||
_filename = stream->readString();
|
||||
_hotspot = stream->readPoint();
|
||||
_transparent = stream->readBool();
|
||||
_transparentColor = stream->readUint32LE();
|
||||
|
||||
uint32 polygonCount = stream->readUint32LE();
|
||||
for (uint32 i = 0; i < polygonCount; i++) {
|
||||
Polygon polygon;
|
||||
|
||||
uint32 pointCount = stream->readUint32LE();
|
||||
for (uint32 j = 0; j < pointCount; j++) {
|
||||
polygon.push_back(stream->readPoint());
|
||||
}
|
||||
|
||||
_polygons.push_back(polygon);
|
||||
}
|
||||
|
||||
_archiveName = stream->getArchiveName();
|
||||
}
|
||||
|
||||
Visual *Image::getVisual() {
|
||||
initVisual();
|
||||
return _visual;
|
||||
}
|
||||
|
||||
void Image::printData() {
|
||||
debug("filename: %s", _filename.toString().c_str());
|
||||
debug("hotspot: x %d, y %d", _hotspot.x, _hotspot.y);
|
||||
debug("transparent: %d", _transparent);
|
||||
debug("transparentColor: %d", _transparentColor);
|
||||
debug("field_44: %d", _field_44_ADF);
|
||||
debug("field_48: %d", _field_48_ADF);
|
||||
|
||||
for (uint32 i = 0; i < _polygons.size(); i++) {
|
||||
Common::String description;
|
||||
for (uint32 j = 0; j < _polygons[i].size(); j++) {
|
||||
description += Common::String::format("(x %d, y %d) ", _polygons[i][j].x, _polygons[i][j].y);
|
||||
}
|
||||
debug("polygon %d: %s", i, description.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int Image::indexForPoint(const Common::Point &point) const {
|
||||
int index = -1;
|
||||
for (uint32 i = 0; i < _polygons.size(); i++) {
|
||||
if (isPointInPolygon(_polygons[i], point)) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
bool Image::isPointInPolygon(const Polygon &polygon, const Common::Point &point) const {
|
||||
if (polygon.size() <= 1) {
|
||||
return false; // Empty polygon
|
||||
}
|
||||
|
||||
// A ray cast from the point
|
||||
Math::Segment2d testLine(Math::Vector2d(point.x, point.y), Math::Vector2d(-100, -100));
|
||||
|
||||
// Special case the line created between the last point and the first
|
||||
Math::Vector2d prevPoint = Math::Vector2d(polygon.back().x, polygon.back().y);
|
||||
|
||||
// Count the intersections of the ray with the polygon's edges
|
||||
int intersectCount = 0;
|
||||
for (uint32 j = 0; j < polygon.size(); j++) {
|
||||
Math::Vector2d curPoint = Math::Vector2d(polygon[j].x, polygon[j].y);
|
||||
|
||||
if (Math::Segment2d(prevPoint, curPoint).intersectsSegment(testLine, nullptr)) {
|
||||
intersectCount++;
|
||||
}
|
||||
|
||||
prevPoint = curPoint;
|
||||
}
|
||||
|
||||
// If the ray crosses the polygon an odd number of times, the point is inside the polygon
|
||||
return intersectCount % 2 != 0;
|
||||
}
|
||||
|
||||
Common::Point Image::getHotspotPosition(uint index) const {
|
||||
if (index >= _polygons.size()) {
|
||||
return Common::Point(-1, -1);
|
||||
}
|
||||
|
||||
Polygon polygon = _polygons[index];
|
||||
|
||||
// Return the top-middle point as the hotspot
|
||||
int right = polygon[0].x, top = polygon[0].y;
|
||||
|
||||
for (uint i = 1; i < polygon.size(); ++i) {
|
||||
right += polygon[i].x;
|
||||
if (polygon[i].y < top) {
|
||||
top = polygon[i].y;
|
||||
}
|
||||
}
|
||||
|
||||
right /= polygon.size();
|
||||
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
}
|
||||
|
||||
return Common::Point(right, top);
|
||||
}
|
||||
|
||||
ImageStill::~ImageStill() {
|
||||
}
|
||||
|
||||
ImageStill::ImageStill(Object *parent, byte subType, uint16 index, const Common::String &name) :
|
||||
Image(parent, subType, index, name),
|
||||
_noName(false) {
|
||||
}
|
||||
|
||||
void ImageStill::readData(Formats::XRCReadStream *stream) {
|
||||
Image::readData(stream);
|
||||
|
||||
if (stream->isDataLeft()) {
|
||||
_field_44_ADF = stream->readUint32LE();
|
||||
_field_44_ADF /= 33;
|
||||
}
|
||||
|
||||
if (stream->isDataLeft()) {
|
||||
_field_48_ADF = stream->readUint32LE();
|
||||
}
|
||||
|
||||
_noName = _filename == "noname" || _filename == "noname.xmg";
|
||||
}
|
||||
|
||||
void ImageStill::onPostRead() {
|
||||
initVisual();
|
||||
}
|
||||
|
||||
void ImageStill::initVisual() {
|
||||
if (_visual) {
|
||||
return; // The visual is already there
|
||||
}
|
||||
|
||||
if (_noName) {
|
||||
return; // No file to load
|
||||
}
|
||||
|
||||
Common::ReadStream *xmgStream = StarkArchiveLoader->getFile(_filename, _archiveName);
|
||||
|
||||
VisualImageXMG *visual = new VisualImageXMG(StarkGfx);
|
||||
|
||||
if (StarkSettings->isAssetsModEnabled() && StarkGfx->supportsModdedAssets() && loadPNGOverride(visual)) {
|
||||
visual->readOriginalSize(xmgStream);
|
||||
} else {
|
||||
visual->load(xmgStream);
|
||||
}
|
||||
|
||||
visual->setHotSpot(_hotspot);
|
||||
|
||||
_visual = visual;
|
||||
|
||||
delete xmgStream;
|
||||
}
|
||||
|
||||
bool ImageStill::loadPNGOverride(VisualImageXMG *visual) const {
|
||||
if (!_filename.baseName().hasSuffixIgnoreCase(".xmg")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::String pngFilename = _filename.baseName();
|
||||
pngFilename = Common::String(pngFilename.c_str(), pngFilename.size() - 4) + ".png";
|
||||
Common::Path pngFilePath = _filename.getParent().appendComponent(pngFilename);
|
||||
pngFilePath = StarkArchiveLoader->getExternalFilePath(pngFilePath, _archiveName);
|
||||
|
||||
debugC(kDebugModding, "Attempting to load %s", pngFilePath.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
Common::SeekableReadStream *pngStream = SearchMan.createReadStreamForMember(pngFilePath);
|
||||
if (!pngStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!visual->loadPNG(pngStream)) {
|
||||
warning("Failed to load %s. It is not a valid PNG file.", pngFilePath.toString(Common::Path::kNativeSeparator).c_str());
|
||||
delete pngStream;
|
||||
return false;
|
||||
}
|
||||
|
||||
debugC(kDebugModding, "Loaded %s", pngFilePath.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
delete pngStream;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageStill::printData() {
|
||||
Image::printData();
|
||||
}
|
||||
|
||||
ImageText::ImageText(Object *parent, byte subType, uint16 index, const Common::String &name) :
|
||||
Image(parent, subType, index, name),
|
||||
_color(Gfx::Color(0, 0, 0)),
|
||||
_font(0) {
|
||||
}
|
||||
|
||||
ImageText::~ImageText() {
|
||||
}
|
||||
|
||||
void ImageText::readData(Formats::XRCReadStream *stream) {
|
||||
Image::readData(stream);
|
||||
|
||||
_size = stream->readPoint();
|
||||
_text = stream->readString();
|
||||
_color.r = stream->readByte();
|
||||
_color.g = stream->readByte();
|
||||
_color.b = stream->readByte();
|
||||
_color.a = 0xFF; stream->readByte();
|
||||
_font = stream->readUint32LE();
|
||||
|
||||
// WORKAROUND: Give more space to text in the Archives' computer
|
||||
// So there are no line breaks in the French version of the game
|
||||
// when using a scaled font.
|
||||
Location *location = findParent<Location>();
|
||||
if (_name == "MAIN" && location && location->getName() == "Archive Database") {
|
||||
_size.x = 80;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageText::initVisual() {
|
||||
if (_visual) {
|
||||
return; // The visual is already there
|
||||
}
|
||||
|
||||
if (_text.hasPrefix("GFX_Bubbles")) {
|
||||
VisualEffectBubbles *bubbles = new VisualEffectBubbles(StarkGfx, _size);
|
||||
bubbles->setParams(_text);
|
||||
_visual = bubbles;
|
||||
} else if (_text.hasPrefix("GFX_FireFlies")) {
|
||||
VisualEffectFireFlies *fireFlies = new VisualEffectFireFlies(StarkGfx, _size);
|
||||
fireFlies->setParams(_text);
|
||||
_visual = fireFlies;
|
||||
} else if (_text.hasPrefix("GFX_Fish")) {
|
||||
VisualEffectFish *fish = new VisualEffectFish(StarkGfx, _size);
|
||||
fish->setParams(_text);
|
||||
_visual = fish;
|
||||
} else if (_text.hasPrefix("GFX_")) {
|
||||
error("Unknown effect '%s'", _text.c_str());
|
||||
} else {
|
||||
VisualText *text = new VisualText(StarkGfx);
|
||||
text->setText(_text);
|
||||
text->setColor(_color);
|
||||
text->setTargetWidth(_size.x);
|
||||
text->setTargetHeight(_size.y);
|
||||
text->setFont(FontProvider::kCustomFont, _font);
|
||||
|
||||
// WORKAROUND: Move the "White Cardinal" hotspot in the Archives'
|
||||
// computer so it matches the text. Fixes the hotspot being hard to
|
||||
// use with scaled fonts in the Spanish version of the game.
|
||||
if (_name == "The Church" && _polygons.size() == 2) {
|
||||
fixWhiteCardinalHotspot(text);
|
||||
}
|
||||
|
||||
_visual = text;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageText::fixWhiteCardinalHotspot(VisualText *text) {
|
||||
Common::Rect textRect = text->getRect();
|
||||
|
||||
Polygon &hotspotPoly = _polygons.back();
|
||||
if (hotspotPoly.size() != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point &topLeft = hotspotPoly[0];
|
||||
Common::Point &topRight = hotspotPoly[1];
|
||||
Common::Point &bottomRight = hotspotPoly[2];
|
||||
Common::Point &bottomLeft = hotspotPoly[3];
|
||||
|
||||
int16 hotspotHeight = bottomLeft.y - topLeft.y;
|
||||
if (hotspotHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bottomLeft .y = textRect.bottom;
|
||||
bottomRight.y = textRect.bottom;
|
||||
topLeft .y = textRect.bottom - hotspotHeight;
|
||||
topRight .y = textRect.bottom - hotspotHeight;
|
||||
}
|
||||
|
||||
void ImageText::resetVisual() {
|
||||
if (!_visual) {
|
||||
return;
|
||||
}
|
||||
|
||||
VisualText *text = _visual->get<VisualText>();
|
||||
if (text) {
|
||||
text->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageText::printData() {
|
||||
Image::printData();
|
||||
|
||||
debug("size: x %d, y %d", _size.x, _size.y);
|
||||
debug("text: %s", _text.c_str());
|
||||
debug("color: (%d, %d, %d, %d)", _color.r, _color.g, _color.b, _color.a);
|
||||
debug("font: %d", _font);
|
||||
}
|
||||
|
||||
} // End of namespace Resources
|
||||
} // End of namespace Stark
|
||||
Reference in New Issue
Block a user