/* 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 . * */ // Object map / Object click-area module // Polygon Hit Test code ( HitTestPoly() ) adapted from code (C) Eric Haines // appearing in Graphics Gems IV, "Point in Polygon Strategies." // p. 24-46, code: p. 34-45 #include "saga/saga.h" #include "saga/gfx.h" #include "saga/console.h" #include "saga/font.h" #include "saga/interface.h" #include "saga/objectmap.h" #include "saga/actor.h" #include "saga/scene.h" #include "saga/isomap.h" #ifdef SAGA_DEBUG #include "saga/render.h" #endif namespace Saga { void HitZone::load(SagaEngine *vm, Common::MemoryReadStreamEndian *readStream, int index, int sceneNumber) { _index = index; _flags = readStream->readByte(); _clickAreas.resize(readStream->readByte()); _rightButtonVerb = readStream->readByte(); readStream->readByte(); // pad _nameIndex = readStream->readUint16(); _scriptNumber = readStream->readUint16(); for (auto &area : _clickAreas) { area.resize(readStream->readUint16LE()); assert(!area.empty()); for (auto &point : area) { point.x = readStream->readSint16(); point.y = readStream->readSint16(); // WORKAROUND: bug #2154: "ITE: Riff ignores command in Ferret merchant center" // Apparently ITE Mac version has bug in game data. Both ObjectMap and ActionMap // for exit area are little taller (y = 123) and thus Riff goes to exit // when clicked on barrel of nails. if (vm->getGameId() == GID_ITE) { if (sceneNumber == 18 && index == 0 && (&area == _clickAreas.begin()) && (&point == area.begin()) && point.y == 123) { point.y = 129; } } } } } bool HitZone::getSpecialPoint(Point &specialPoint) const { for (const auto &area : _clickAreas) { if (area.size() == 1) { specialPoint = area[0]; return true; } } return false; } bool HitZone::hitTest(const Point &testPoint) { const Point *points; uint pointsCount; if (_flags & kHitZoneEnabled) { for (const auto &area : _clickAreas) { pointsCount = area.size(); if (pointsCount < 2) { continue; } points = &area.front(); if (pointsCount == 2) { // Hit-test a box region if ((testPoint.x >= points[0].x) && (testPoint.x <= points[1].x) && (testPoint.y >= points[0].y) && (testPoint.y <= points[1].y)) { return true; } } else { // Hit-test a polygon if (hitTestPoly(points, pointsCount, testPoint)) { return true; } } } } return false; } #ifdef SAGA_DEBUG void HitZone::draw(SagaEngine *vm, int color) { int pointsCount, j; Location location; HitZone::ClickArea tmpPoints; const Point *points; Point specialPoint1; Point specialPoint2; for (const auto &area : _clickAreas) { pointsCount = area.size(); points = &area.front(); if (vm->_scene->getFlags() & kSceneFlagISO) { tmpPoints.resize(pointsCount); for (j = 0; j < pointsCount; j++) { location.u() = points[j].x; location.v() = points[j].y; location.z = 0; vm->_isoMap->tileCoordsToScreenPoint(location, tmpPoints[j]); } points = &tmpPoints.front(); } if (pointsCount == 2) { // 2 points represent a box vm->_gfx->drawFrame(points[0], points[1], color); } else { if (pointsCount > 2) { // Otherwise draw a polyline // Do a full refresh so that the polyline can be shown vm->_render->setFullRefresh(true); vm->_gfx->drawPolyLine(points, pointsCount, color); } } } if (getSpecialPoint(specialPoint1)) { specialPoint2 = specialPoint1; specialPoint1.x--; specialPoint1.y--; specialPoint2.x++; specialPoint2.y++; vm->_gfx->drawFrame(specialPoint1, specialPoint2, color); } } #endif // Loads an object map resource ( objects ( clickareas ( points ) ) ) void ObjectMap::load(const ByteArray &resourceData) { if (!_hitZoneList.empty()) { error("ObjectMap::load _hitZoneList not empty"); } if (resourceData.empty()) { return; } if (resourceData.size() < 4) { error("ObjectMap::load wrong resourceLength"); } ByteArrayReadStreamEndian readS(resourceData, _vm->isBigEndian()); _hitZoneList.resize(readS.readUint16()); int idx = 0; for (auto &hitZone : _hitZoneList) { hitZone.load(_vm, &readS, idx++, _vm->_scene->currentSceneNumber()); } } void ObjectMap::clear() { _hitZoneList.clear(); } #ifdef SAGA_DEBUG void ObjectMap::draw(const Point& testPoint, int color, int color2) { int hitZoneIndex; Common::String txtBuf; Point pickPoint; Point textPoint; Location pickLocation; pickPoint = testPoint; if (_vm->_scene->getFlags() & kSceneFlagISO) { assert(_vm->_actor->_protagonist); pickPoint.y -= _vm->_actor->_protagonist->_location.z; _vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation); pickLocation.toScreenPointUV(pickPoint); } hitZoneIndex = hitTest(pickPoint); for (auto &i : _hitZoneList) { i->draw(_vm, (hitZoneIndex == i->getIndex()) ? color2 : color); } if (hitZoneIndex != -1) { txtBuf = Common::String::format("hitZone %d", hitZoneIndex); textPoint.x = 2; textPoint.y = 2; _vm->_font->textDraw(kKnownFontSmall, txtBuf.c_str(), textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline); } } #endif int ObjectMap::hitTest(const Point& testPoint) { // Loop through all scene objects for (auto &hitZone : _hitZoneList) { if (hitZone.hitTest(testPoint)) { return hitZone.getIndex(); } } return -1; } void ObjectMap::cmdInfo() { _vm->_console->debugPrintf("%d zone(s) loaded.\n\n", _hitZoneList.size()); } } // End of namespace Saga