Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

3
engines/stark/POTFILES Normal file
View File

@@ -0,0 +1,3 @@
engines/stark/detection.cpp
engines/stark/metaengine.cpp
engines/stark/stark.cpp

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine stark "The Longest Journey" yes "" "" "16bit 3d highres freetype2 vorbis bink" "tinygl"

774
engines/stark/console.cpp Normal file
View File

@@ -0,0 +1,774 @@
/* 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/console.h"
#include "engines/stark/formats/xarc.h"
#include "engines/stark/resources/object.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/level.h"
#include "engines/stark/resources/location.h"
#include "engines/stark/resources/knowledge.h"
#include "engines/stark/resources/root.h"
#include "engines/stark/resources/script.h"
#include "engines/stark/resources/knowledgeset.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/textureset.h"
#include "engines/stark/services/archiveloader.h"
#include "engines/stark/services/dialogplayer.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/resourceprovider.h"
#include "engines/stark/services/userinterface.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/staticprovider.h"
#include "engines/stark/tools/decompiler.h"
#include "common/file.h"
namespace Stark {
Console::Console() :
GUI::Debugger() {
registerCmd("dumpArchive", WRAP_METHOD(Console, Cmd_DumpArchive));
registerCmd("dumpRoot", WRAP_METHOD(Console, Cmd_DumpRoot));
registerCmd("dumpStatic", WRAP_METHOD(Console, Cmd_DumpStatic));
registerCmd("dumpGlobal", WRAP_METHOD(Console, Cmd_DumpGlobal));
registerCmd("dumpLevel", WRAP_METHOD(Console, Cmd_DumpLevel));
registerCmd("dumpKnowledge", WRAP_METHOD(Console, Cmd_DumpKnowledge));
registerCmd("dumpLocation", WRAP_METHOD(Console, Cmd_DumpLocation));
registerCmd("listScripts", WRAP_METHOD(Console, Cmd_ListScripts));
registerCmd("enableScript", WRAP_METHOD(Console, Cmd_EnableScript));
registerCmd("forceScript", WRAP_METHOD(Console, Cmd_ForceScript));
registerCmd("decompileScript", WRAP_METHOD(Console, Cmd_DecompileScript));
registerCmd("testDecompiler", WRAP_METHOD(Console, Cmd_TestDecompiler));
registerCmd("listAnimations", WRAP_METHOD(Console, Cmd_ListAnimations));
registerCmd("forceAnimation", WRAP_METHOD(Console, Cmd_ForceAnimation));
registerCmd("listInventoryItems", WRAP_METHOD(Console, Cmd_ListInventoryItems));
registerCmd("listLocations", WRAP_METHOD(Console, Cmd_ListLocations));
registerCmd("location", WRAP_METHOD(Console, Cmd_Location));
registerCmd("chapter", WRAP_METHOD(Console, Cmd_Chapter));
registerCmd("changeLocation", WRAP_METHOD(Console, Cmd_ChangeLocation));
registerCmd("changeChapter", WRAP_METHOD(Console, Cmd_ChangeChapter));
registerCmd("changeKnowledge", WRAP_METHOD(Console, Cmd_ChangeKnowledge));
registerCmd("enableInventoryItem", WRAP_METHOD(Console, Cmd_EnableInventoryItem));
registerCmd("extractAllTextures", WRAP_METHOD(Console, Cmd_ExtractAllTextures));
}
Console::~Console() {
}
bool Console::Cmd_DumpArchive(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Extract all the files from a game archive\n");
debugPrintf("The destination folder, named 'dump', is in the location ScummVM was launched from\n");
debugPrintf("Usage :\n");
debugPrintf("dumpArchive [path to archive]\n");
return true;
}
Formats::XARCArchive xarc;
if (!xarc.open(argv[1])) {
debugPrintf("Can't open archive with name '%s'\n", argv[1]);
return true;
}
Common::ArchiveMemberList members;
xarc.listMembers(members);
for (Common::ArchiveMemberList::const_iterator it = members.begin(); it != members.end(); it++) {
Common::Path fileName(Common::String::format("dump/%s", it->get()->getName().c_str()));
// Open the output file
Common::DumpFile outFile;
if (!outFile.open(fileName, true)) {
debugPrintf("Unable to open file '%s' for writing\n", fileName.toString().c_str());
return true;
}
// Copy the archive content to the output file using a temporary buffer
Common::SeekableReadStream *inStream = it->get()->createReadStream();
uint8 *buf = new uint8[inStream->size()];
inStream->read(buf, inStream->size());
outFile.write(buf, inStream->size());
delete[] buf;
delete inStream;
outFile.close();
debugPrintf("Extracted '%s'\n", it->get()->getName().c_str());
}
return true;
}
bool Console::Cmd_DumpRoot(int argc, const char **argv) {
Resources::Root *root = StarkGlobal->getRoot();
if (root) {
root->print();
} else {
debugPrintf("The global root has not been loaded\n");
}
return true;
}
bool Console::Cmd_DumpGlobal(int argc, const char **argv) {
Resources::Level *level = StarkGlobal->getLevel();
if (level) {
level->print();
} else {
debugPrintf("The global level has not been loaded\n");
}
return true;
}
bool Console::Cmd_DumpStatic(int argc, const char **argv) {
// Static resources are initialized in the beginning of the running
StarkStaticProvider->getLevel()->print();
return true;
}
bool Console::Cmd_DumpLevel(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (current) {
current->getLevel()->print();
} else {
debugPrintf("Game levels have not been loaded\n");
}
return true;
}
bool Console::Cmd_DumpKnowledge(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
Resources::Level *level = current->getLevel();
Resources::Location *location = current->getLocation();
Common::Array<Resources::Knowledge *> knowledge = level->listChildrenRecursive<Resources::Knowledge>();
knowledge.insert_at(knowledge.size(), location->listChildrenRecursive<Resources::Knowledge>());
Common::Array<Resources::Knowledge *>::iterator it;
for (it = knowledge.begin(); it != knowledge.end(); ++it) {
(*it)->print();
}
return true;
}
bool Console::Cmd_ChangeKnowledge(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
uint index = 0;
char type = 0;
if (argc >= 4) {
index = atoi(argv[1]);
type = argv[2][0];
if (type == 'b' || type == 'i') {
Resources::Level *level = current->getLevel();
Resources::Location *location = current->getLocation();
Common::Array<Resources::Knowledge *> knowledgeArr = level->listChildrenRecursive<Resources::Knowledge>();
knowledgeArr.insert_at(knowledgeArr.size(), location->listChildrenRecursive<Resources::Knowledge>());
if (index < knowledgeArr.size() ) {
Resources::Knowledge *knowledge = knowledgeArr[index];
if (type == 'b') {
knowledge->setBooleanValue(atoi(argv[3]));
} else if (type == 'i') {
knowledge->setIntegerValue(atoi(argv[3]));
}
return true;
} else {
debugPrintf("Invalid index %d, only %d indices available\n", index, knowledgeArr.size());
}
} else {
debugPrintf("Invalid type: %c, only b and i are available\n", type);
}
} else if (argc > 1 ) {
debugPrintf("Too few args\n");
}
debugPrintf("Change the value of some knowledge. Use dumpKnowledge to get an id\n");
debugPrintf("Usage :\n");
debugPrintf("changeKnowledge [id] [type] [value]\n");
debugPrintf("available types: b(inary), i(nteger)\n");
return true;
}
Common::Array<Resources::Script *> Console::listAllLocationScripts() const {
Common::Array<Resources::Script *> scripts;
Resources::Level *level = StarkGlobal->getCurrent()->getLevel();
Resources::Location *location = StarkGlobal->getCurrent()->getLocation();
scripts.push_back(level->listChildrenRecursive<Resources::Script>());
scripts.push_back(location->listChildrenRecursive<Resources::Script>());
return scripts;
}
bool Console::Cmd_ListScripts(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
Common::Array<Resources::Script *> scripts = listAllLocationScripts();
for (uint i = 0; i < scripts.size(); i++) {
Resources::Script *script = scripts[i];
debugPrintf("%d: %s - enabled: %d", i, script->getName().c_str(), script->isEnabled());
// Print which resource is causing the script to wait
if (script->isSuspended()) {
Resources::Object *suspending = script->getSuspendingResource();
if (suspending) {
debugPrintf(", waiting for: %s (%s)", suspending->getName().c_str(), suspending->getType().getName());
} else {
debugPrintf(", paused");
}
}
debugPrintf("\n");
}
return true;
}
bool Console::Cmd_EnableScript(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
uint index = 0;
if (argc >= 2) {
index = atoi(argv[1]);
bool value = true;
if (argc >= 3) {
value = atoi(argv[2]);
}
Common::Array<Resources::Script *> scripts = listAllLocationScripts();
if (index < scripts.size() ) {
Resources::Script *script = scripts[index];
script->enable(value);
return true;
} else {
debugPrintf("Invalid index %d, only %d indices available\n", index, scripts.size());
}
}
debugPrintf("Enable or disable a script. Use listScripts to get an id\n");
debugPrintf("Usage :\n");
debugPrintf("enableScript [id] (value)\n");
return true;
}
bool Console::Cmd_ForceScript(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
uint index = 0;
if (argc >= 2) {
index = atoi(argv[1]);
Common::Array<Resources::Script *> scripts = listAllLocationScripts();
if (index < scripts.size() ) {
Resources::Script *script = scripts[index];
script->enable(true);
script->goToNextCommand(); // Skip the begin command to avoid checks
script->execute(Resources::Script::kCallModePlayerAction);
return true;
} else {
debugPrintf("Invalid index %d, only %d indices available\n", index, scripts.size());
}
}
debugPrintf("Force the execution of a script. Use listScripts to get an id\n");
debugPrintf("Usage :\n");
debugPrintf("forceScript [id]\n");
return true;
}
bool Console::Cmd_DecompileScript(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
if (argc >= 2) {
uint index = atoi(argv[1]);
Common::Array<Resources::Script *> scripts = listAllLocationScripts();
if (index < scripts.size()) {
Resources::Script *script = scripts[index];
Tools::Decompiler *decompiler = new Tools::Decompiler(script);
if (decompiler->getError() != "") {
debugPrintf("Decompilation failure: %s\n", decompiler->getError().c_str());
}
debug("Script %d - %s:", index, script->getName().c_str());
decompiler->printDecompiled();
delete decompiler;
return true;
} else {
debugPrintf("Invalid index %d, only %d indices available\n", index, scripts.size());
}
}
debugPrintf("Decompile a script. Use listScripts to get an id\n");
debugPrintf("Usage :\n");
debugPrintf("decompileScript [id]\n");
return true;
}
class ArchiveVisitor {
public:
virtual ~ArchiveVisitor() {}
virtual void acceptLevelArchive(Resources::Level *level) = 0;
virtual void acceptLocationArchive(Resources::Location *location) = 0;
};
void Console::walkAllArchives(ArchiveVisitor *visitor) {
ArchiveLoader *archiveLoader = new ArchiveLoader();
// Temporarily replace the global archive loader with our instance
ArchiveLoader *gameArchiveLoader = StarkArchiveLoader;
StarkArchiveLoader = archiveLoader;
archiveLoader->load("x.xarc");
Resources::Root *root = archiveLoader->useRoot<Resources::Root>("x.xarc");
// Find all the levels
Common::Array<Resources::Level *> levels = root->listChildren<Resources::Level>();
// Loop over the levels
for (uint i = 0; i < levels.size(); i++) {
Resources::Level *level = levels[i];
Common::Path levelArchive = archiveLoader->buildArchiveName(level);
debug("%s - %s", levelArchive.toString(Common::Path::kNativeSeparator).c_str(), level->getName().c_str());
// Load the detailed level archive
archiveLoader->load(levelArchive);
level = archiveLoader->useRoot<Resources::Level>(levelArchive);
// Visit the level archive
visitor->acceptLevelArchive(level);
Common::Array<Resources::Location *> locations = level->listChildren<Resources::Location>();
// Loop over the locations
for (uint j = 0; j < locations.size(); j++) {
Resources::Location *location = locations[j];
Common::Path locationArchive = archiveLoader->buildArchiveName(level, location);
debug("%s - %s", locationArchive.toString(Common::Path::kNativeSeparator).c_str(), location->getName().c_str());
// Load the detailed location archive
archiveLoader->load(locationArchive);
location = archiveLoader->useRoot<Resources::Location>(locationArchive);
// Visit the location archive
visitor->acceptLocationArchive(location);
archiveLoader->returnRoot(locationArchive);
archiveLoader->unloadUnused();
}
archiveLoader->returnRoot(levelArchive);
archiveLoader->unloadUnused();
}
// Restore the global archive loader
StarkArchiveLoader = gameArchiveLoader;
delete archiveLoader;
}
class DecompilingArchiveVisitor : public ArchiveVisitor {
public:
DecompilingArchiveVisitor() :
_totalScripts(0),
_okScripts(0) {}
void acceptLevelArchive(Resources::Level *level) override {
decompileScriptChildren(level);
}
void acceptLocationArchive(Resources::Location *location) override {
decompileScriptChildren(location);
}
int getTotalScripts() const { return _totalScripts; }
int getOKScripts() const { return _okScripts; }
private:
int _totalScripts;
int _okScripts;
void decompileScriptChildren(Resources::Object *resource) {
Common::Array<Resources::Script *> scripts = resource->listChildrenRecursive<Resources::Script>();
for (uint i = 0; i < scripts.size(); i++) {
Resources::Script *script = scripts[i];
Tools::Decompiler decompiler(script);
_totalScripts++;
Common::String result;
if (decompiler.getError() == "") {
result = "OK";
_okScripts++;
} else {
result = decompiler.getError();
}
debug("%d - %s: %s", script->getIndex(), script->getName().c_str(), result.c_str());
}
}
};
bool Console::Cmd_TestDecompiler(int argc, const char **argv) {
DecompilingArchiveVisitor visitor;
walkAllArchives(&visitor);
debugPrintf("Successfully decompiled %d scripts out of %d\n", visitor.getOKScripts(), visitor.getTotalScripts());
return true;
}
class TextureExtractingArchiveVisitor : public ArchiveVisitor {
public:
void acceptLevelArchive(Resources::Level *level) override {
decompileScriptChildren(level);
}
void acceptLocationArchive(Resources::Location *location) override {
decompileScriptChildren(location);
}
private:
void decompileScriptChildren(Resources::Object *resource) {
Common::Array<Resources::TextureSet *> textureSets = resource->listChildrenRecursive<Resources::TextureSet>();
for (uint i = 0; i < textureSets.size(); i++) {
Resources::TextureSet *textureSet = textureSets[i];
textureSet->extractArchive();
}
}
};
bool Console::Cmd_ExtractAllTextures(int argc, const char **argv) {
TextureExtractingArchiveVisitor visitor;
walkAllArchives(&visitor);
return true;
}
Common::Array<Resources::Anim *> Console::listAllLocationAnimations() const {
Common::Array<Resources::Anim *> animations;
Resources::Level *level = StarkGlobal->getCurrent()->getLevel();
Resources::Location *location = StarkGlobal->getCurrent()->getLocation();
animations.push_back(level->listChildrenRecursive<Resources::Anim>());
animations.push_back(location->listChildrenRecursive<Resources::Anim>());
return animations;
}
bool Console::Cmd_ListAnimations(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("This command is only available in game.\n");
return true;
}
Common::Array<Resources::Anim *> animations = listAllLocationAnimations();
for (uint i = 0; i < animations.size(); i++) {
Resources::Anim *anim = animations[i];
Resources::Item *item = anim->findParent<Resources::Item>();
debugPrintf("%d: %s - %s - in use: %d\n", i, item->getName().c_str(), anim->getName().c_str(), anim->isInUse());
}
return true;
}
bool Console::Cmd_ForceAnimation(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("This command is only available in game.\n");
return true;
}
if (argc < 2) {
debugPrintf("Force the execution of an animation. Use listAnimations to get an id\n");
debugPrintf("Usage :\n");
debugPrintf("forceAnimation [id]\n");
return true;
}
uint index = atoi(argv[1]);
Common::Array<Resources::Anim *> animations = listAllLocationAnimations();
if (index >= animations.size() ) {
debugPrintf("Invalid animation %d\n", index);
return true;
}
Resources::Anim *anim = animations[index];
Resources::Item *item = anim->findParent<Resources::Item>();
Resources::ItemVisual *sceneItem = item->getSceneInstance();
if (!sceneItem->isEnabled()) {
sceneItem->setEnabled(true);
}
sceneItem->playActionAnim(anim);
return false;
}
bool Console::Cmd_DumpLocation(int argc, const char **argv) {
if (StarkStaticProvider->isStaticLocation()) {
StarkStaticProvider->getLocation()->print();
return true;
}
Current *current = StarkGlobal->getCurrent();
if (current) {
current->getLocation()->print();
} else {
debugPrintf("Locations have not been loaded\n");
}
return true;
}
bool Console::Cmd_ListInventoryItems(int argc, const char **argv) {
Resources::KnowledgeSet *inventory = StarkGlobal->getInventory();
if (!inventory) {
debugPrintf("The inventory has not been loaded\n");
return true;
}
Common::Array<Resources::Item*> inventoryItems = inventory->listChildren<Resources::Item>(Resources::Item::kItemInventory);
Common::Array<Resources::Item*>::iterator it = inventoryItems.begin();
for (int i = 0; it != inventoryItems.end(); ++it, i++) {
debugPrintf("Item %d: %s%s\n", i, (*it)->getName().c_str(), (*it)->isEnabled() ? " (enabled)" : "");
}
return true;
}
bool Console::Cmd_EnableInventoryItem(int argc, const char **argv) {
Resources::KnowledgeSet *inventory = StarkGlobal->getInventory();
if (!inventory) {
debugPrintf("The inventory has not been loaded\n");
return true;
}
if (argc != 2) {
debugPrintf("Enable a specific inventory item. Use listInventoryItems to get an id\n");
debugPrintf("Usage :\n");
debugPrintf("enableInventoryItem [id]\n");
return true;
}
uint num = atoi(argv[1]);
Common::Array<Resources::Item*> inventoryItems = inventory->listChildren<Resources::Item>(Resources::Item::kItemInventory);
if (num < inventoryItems.size()) {
inventoryItems[num]->setEnabled(true);
} else {
debugPrintf("Invalid index %d, only %d indices available\n", num, inventoryItems.size());
}
return true;
}
bool Console::Cmd_ListLocations(int argc, const char **argv) {
ArchiveLoader *archiveLoader = new ArchiveLoader();
// Temporarily replace the global archive loader with our instance
ArchiveLoader *gameArchiveLoader = StarkArchiveLoader;
StarkArchiveLoader = archiveLoader;
archiveLoader->load("x.xarc");
Resources::Root *root = archiveLoader->useRoot<Resources::Root>("x.xarc");
// Find all the levels
Common::Array<Resources::Level *> levels = root->listChildren<Resources::Level>();
// Loop over the levels
for (uint i = 0; i < levels.size(); i++) {
Resources::Level *level = levels[i];
Common::Path levelArchive = archiveLoader->buildArchiveName(level);
debugPrintf("%s - %s\n", levelArchive.toString(Common::Path::kNativeSeparator).c_str(), level->getName().c_str());
// Load the detailed level archive
archiveLoader->load(levelArchive);
level = archiveLoader->useRoot<Resources::Level>(levelArchive);
Common::Array<Resources::Location *> locations = level->listChildren<Resources::Location>();
// Loop over the locations
for (uint j = 0; j < locations.size(); j++) {
Resources::Location *location = locations[j];
Common::Path roomArchive = archiveLoader->buildArchiveName(level, location);
debugPrintf("%s - %s\n", roomArchive.toString(Common::Path::kNativeSeparator).c_str(), location->getName().c_str());
}
archiveLoader->returnRoot(levelArchive);
archiveLoader->unloadUnused();
}
// Restore the global archive loader
StarkArchiveLoader = gameArchiveLoader;
delete archiveLoader;
return true;
}
bool Console::Cmd_ChangeLocation(int argc, const char **argv) {
if (argc >= 3) {
// Assert indices
Common::Path xarcFileName(Common::String::format("%s/%s/%s.xarc", argv[1], argv[2], argv[2]));
if (!Common::File::exists(xarcFileName)) {
debugPrintf("Invalid location %s %s. Use listLocations to get correct indices\n", argv[1], argv[2]);
return true;
}
uint levelIndex = strtol(argv[1] , nullptr, 16);
uint locationIndex = strtol(argv[2] , nullptr, 16);
StarkUserInterface->changeScreen(Screen::kScreenGame);
if (!StarkGlobal->getRoot()) {
StarkResourceProvider->initGlobal();
}
StarkResourceProvider->requestLocationChange(levelIndex, locationIndex);
return false;
} else if (argc > 1) {
debugPrintf("Too few args\n");
}
debugPrintf("Change the current location. Use listLocations to get indices\n");
debugPrintf("Usage :\n");
debugPrintf("changeLocation [level] [location]\n");
return true;
}
bool Console::Cmd_ChangeChapter(int argc, const char **argv) {
if (!StarkGlobal->getLevel()) {
debugPrintf("The global level has not been loaded\n");
return true;
}
if (argc != 2) {
debugPrintf("Change the current chapter\n");
debugPrintf("Usage :\n");
debugPrintf("changeChapter [value]\n");
return true;
}
char *endPtr = nullptr;
long value = strtol(argv[1], &endPtr, 10);
if (*endPtr == '\0' && value >= 0 && value <= INT_MAX)
StarkGlobal->setCurrentChapter((int32) value);
else
debugPrintf("Invalid chapter\n");
return true;
}
bool Console::Cmd_Location(int argc, const char **argv) {
Current *current = StarkGlobal->getCurrent();
if (!current) {
debugPrintf("Game levels have not been loaded\n");
return true;
}
if (argc != 1) {
debugPrintf("Display the current location\n");
debugPrintf("Usage :\n");
debugPrintf("location\n");
return true;
}
debugPrintf("location: %02x %02x\n", current->getLevel()->getIndex(), current->getLocation()->getIndex());
return true;
}
bool Console::Cmd_Chapter(int argc, const char **argv) {
if (!StarkGlobal->getLevel()) {
debugPrintf("The global level has not been loaded\n");
return true;
}
if (argc != 1) {
debugPrintf("Display the current chapter\n");
debugPrintf("Usage :\n");
debugPrintf("chapter\n");
return true;
}
int32 value = StarkGlobal->getCurrentChapter();
debugPrintf("chapter: %d\n", value);
return true;
}
} // End of namespace Stark

75
engines/stark/console.h Normal file
View File

@@ -0,0 +1,75 @@
/* 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/>.
*
*/
#ifndef CONSOLE_H_
#define CONSOLE_H_
#include "gui/debugger.h"
namespace Stark {
namespace Resources {
class Anim;
class Object;
class Script;
}
class ArchiveVisitor;
class Console : public GUI::Debugger {
public:
Console();
virtual ~Console();
private:
bool Cmd_DumpArchive(int argc, const char **argv);
bool Cmd_DumpRoot(int argc, const char **argv);
bool Cmd_DumpStatic(int argc, const char **argv);
bool Cmd_DumpGlobal(int argc, const char **argv);
bool Cmd_DumpKnowledge(int argc, const char **argv);
bool Cmd_DumpLevel(int argc, const char **argv);
bool Cmd_DumpLocation(int argc, const char **argv);
bool Cmd_ForceScript(int argc, const char **argv);
bool Cmd_DecompileScript(int argc, const char **argv);
bool Cmd_TestDecompiler(int argc, const char** argv);
bool Cmd_ListInventoryItems(int argc, const char **argv);
bool Cmd_EnableInventoryItem(int argc, const char **argv);
bool Cmd_ListLocations(int argc, const char** argv);
bool Cmd_ListScripts(int argc, const char** argv);
bool Cmd_EnableScript(int argc, const char** argv);
bool Cmd_ListAnimations(int argc, const char **argv);
bool Cmd_ForceAnimation(int argc, const char **argv);
bool Cmd_Location(int argc, const char **argv);
bool Cmd_Chapter(int argc, const char **argv);
bool Cmd_ChangeLocation(int argc, const char **argv);
bool Cmd_ChangeChapter(int argc, const char **argv);
bool Cmd_ChangeKnowledge(int argc, const char **argv);
bool Cmd_ExtractAllTextures(int argc, const char **argv);
Common::Array<Resources::Anim *> listAllLocationAnimations() const;
Common::Array<Resources::Script *> listAllLocationScripts() const;
void walkAllArchives(ArchiveVisitor *visitor);
};
} // End of namespace Stark
#endif // CONSOLE_H_

5
engines/stark/credits.pl Normal file
View File

@@ -0,0 +1,5 @@
begin_section("Stark");
add_person("Bastien Bouclet", "bgK", "");
add_person("Einar Johan T. S&oslash;m&aring;en", "somaen", "");
add_person("Liu Zhaosong", "Douglas", "");
end_section();

34
engines/stark/debug.h Normal file
View File

@@ -0,0 +1,34 @@
/* 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/>.
*
*/
#ifndef STARK_DEBUG_H
#define STARK_DEBUG_H
enum {
kDebugArchive = 1,
kDebugXMG,
kDebugXRC,
kDebugModding,
kDebugAnimation,
kDebugUnknown,
};
#endif // STARK_DEBUG_H

441
engines/stark/detection.cpp Normal file
View File

@@ -0,0 +1,441 @@
/* 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/advancedDetector.h"
#include "common/translation.h"
#include "stark/detection.h"
#include "stark/debug.h"
namespace Stark {
static const PlainGameDescriptor starkGames[] = {
{ "tlj", "The Longest Journey" },
{ nullptr, nullptr }
};
static const DebugChannelDef debugFlagList[] = {
{kDebugArchive, "Archive", "Debug the archive loading"},
{kDebugXMG, "XMG", "Debug the loading of XMG images"},
{kDebugXRC, "XRC", "Debug the loading of XRC resource trees"},
{kDebugModding, "Modding", "Debug the loading of modded assets"},
{kDebugAnimation, "Animation", "Debug the animation changes"},
{kDebugUnknown, "Unknown", "Debug unknown values on the data"},
DEBUG_CHANNEL_END
};
static const ADGameDescription gameDescriptions[] = {
// The Longest Journey
// English Steam (game.exe missing valid 147.bmp resource for dialog boxes background)
{
"tlj", "Steam",
AD_ENTRY3s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "5b5a1f1dd2297d9ce0d3d12216d5d2c5", 485,
"game.exe", "2a68bd64e71635c74a5c6bb172ec1cb1", 95744),
Common::EN_ANY,
Common::kPlatformWindows,
GF_MISSING_EXE_RESOURCES|ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// English DVD
{
"tlj", "DVD",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "5b5a1f1dd2297d9ce0d3d12216d5d2c5", 485),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// GOG edition
{
"tlj", "GOG.com",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "5b5a1f1dd2297d9ce0d3d12216d5d2c5", 485),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// English Old Demo
{
"tlj", "Old Demo",
AD_ENTRY2s("x.xarc", "97abc1bb9239dee4c208e533f3c97e1c", 98,
"chapters.ini", "5b5a1f1dd2297d9ce0d3d12216d5d2c5", 485),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO_NONE
},
// The Longest Journey
// English v1.61 Demo
{
"tlj", "v1.61 Demo",
AD_ENTRY2s("x.xarc", "61093bcd499b386ed5c0345c52f48909", 98,
"chapters.ini", "5b5a1f1dd2297d9ce0d3d12216d5d2c5", 485),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO_NONE
},
// The Longest Journey
// Czech 4CD
// Bugreport #11914
{
"tlj", "4 CD build 142",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "547f0b9c04c00d330b60eed6e8d24732", 484),
Common::CS_CZE,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// French Demo
{
"tlj", "Demo",
AD_ENTRY2s("x.xarc", "97abc1bb9239dee4c208e533f3c97e1c", 98,
"chapters.ini", "e54f6370dca06496069790840409cf95", 506),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO_NONE
},
// The Longest Journey
// Norwegian Demo
{
"tlj", "Demo",
AD_ENTRY2s("x.xarc", "97abc1bb9239dee4c208e533f3c97e1c", 98,
"chapters.ini", "f358f604abd1aa1476ed05d6d271ac70", 473),
Common::NB_NOR,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO_NONE
},
// The Longest Journey
// Norwegian 4 CD version - supplied by L0ngcat
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "f358f604abd1aa1476ed05d6d271ac70", 473),
Common::NB_NOR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Norwegian DLC-edition (DVD?)
{
"tlj", "DVD",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "f358f604abd1aa1476ed05d6d271ac70", 473),
Common::NB_NOR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// German DVD version supplied by Vorph on the forums
{
"tlj", "DVD",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "e4611d143a87b263d8d7a54edc7e7cd7", 515),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// German 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "e4611d143a87b263d8d7a54edc7e7cd7", 515),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Italian DVD version
{
"tlj", "DVD",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "9a81ea4e6f5b84511dd4e56d04a64e2e", 498),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Italian 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "9a81ea4e6f5b84511dd4e56d04a64e2e", 498),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Dutch 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "c8dadd9a3b41640734d6213e89cd5635", 508),
Common::NL_NLD,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Spanish 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "3640df6d536b186bff228337284d9631", 525),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// French 2CD
{
"tlj", "2 CD",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "e54f6370dca06496069790840409cf95", 506),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// French 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "e54f6370dca06496069790840409cf95", 506),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Swedish Demo
{
"tlj", "Demo",
AD_ENTRY2s("x.xarc", "97abc1bb9239dee4c208e533f3c97e1c", 98,
"chapters.ini", "f6a2007300209492b7b90b4c0467832d", 462),
Common::SV_SWE,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO_NONE
},
// The Longest Journey
// Swedish 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "f6a2007300209492b7b90b4c0467832d", 462),
Common::SV_SWE,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Swedish DVD Nordic Special Edition - supplied by L0ngcat
{
"tlj", "DVD",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "f6a2007300209492b7b90b4c0467832d", 462),
Common::SV_SWE,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Polish 4CD
{
"tlj", "4 CD",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "6abc5c38e6e31face4b675355b117620", 499),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Polish Demo.
// Provided by Faalargon, Bugreport #11883 (#1440 in Residualvm)
// Folder structure is completely different. Unsupported for now
{
"tlj", MetaEngineDetection::GAME_NOT_IMPLEMENTED, // Reason for being unsupported
AD_ENTRY2s("x.xarc", "6c6c388f757adcc49e7f33b0b2cccf96", 2904,
"chapters.ini", "6ee43a176a5eb94153c2d813261c3226", 252),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_DEMO | ADGF_UNSUPPORTED,
GUIO_NONE
},
// The Longest Journey
// Russian 2CD by 1C
{
"tlj", "2 CD/Fargus",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "740b97b94e97ed11f064f5fa125ebee1", 486),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Russian 2CD by 7Wolf
{
"tlj", "2 CD/7Wolf",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "8e08025c89575d2573c2edf0daa1cb34", 406),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO_NONE
},
// The Longest Journey
// Russian by Triada
{
"tlj", "Triada",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "17510546145f6f574702094dca436a8d", 409),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUIO_NONE
},
// The Longest Journey
// Hungarian fan-made
{
"tlj", "Fanmade",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "790b51a88b5493bff5168a77738e0e84", 451),
Common::HU_HUN,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// Hungarian fan-made applied to German DVD version
// Trac report #14384
{
"tlj", "Fanmade",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "790b51a88b5493bff5168a77738e0e84", 451),
Common::HU_HUN,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
// The Longest Journey
// iOS - v1.0.7 - provided by Aeryl on Discord
{
"tlj", "Remastered",
AD_ENTRY2s("x.xarc", "de8327850d7bba90b690b141eaa23f61", 3032,
"chapters.ini", "da19240d49f714a27da2054caadc0057", 500),
Common::EN_ANY,
Common::kPlatformIOS,
ADGF_UNSTABLE,
GUIO_NONE
},
// The Longest Journey
// Hebrew fan-made
{
"tlj", "Fanmade",
AD_ENTRY2s("x.xarc", "a0559457126caadab0cadac02d35f26f", 3032,
"chapters.ini", "53c9b6087044be0ba05f8d7b39c8ae48", 367),
Common::HE_ISR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO_NONE
},
AD_TABLE_END_MARKER
};
class StarkMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
public:
StarkMetaEngineDetection() : AdvancedMetaEngineDetection(gameDescriptions, starkGames) {
_guiOptions = GUIO4(GUIO_NOMIDI, GAMEOPTION_ASSETS_MOD, GAMEOPTION_LINEAR_FILTERING, GAMEOPTION_FONT_ANTIALIASING);
}
const char *getEngineName() const override {
return "Stark";
}
const char *getName() const override {
return "stark";
}
const char *getOriginalCopyright() const override {
return "(C) Funcom";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
} // End of namespace Stark
REGISTER_PLUGIN_STATIC(STARK_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Stark::StarkMetaEngineDetection);

37
engines/stark/detection.h Normal file
View File

@@ -0,0 +1,37 @@
/* 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/>.
*
*/
#ifndef STARK_DETECTION_H
#define STARK_DETECTION_H
namespace Stark {
enum GameFlags {
GF_MISSING_EXE_RESOURCES = 1 << 0
};
#define GAMEOPTION_ASSETS_MOD GUIO_GAMEOPTIONS1
#define GAMEOPTION_LINEAR_FILTERING GUIO_GAMEOPTIONS2
#define GAMEOPTION_FONT_ANTIALIASING GUIO_GAMEOPTIONS3
} // End of namespace Stark
#endif // STARK_DETECTION_H

View File

@@ -0,0 +1,124 @@
/* 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/formats/biff.h"
#include "engines/stark/services/archiveloader.h"
namespace Stark {
namespace Formats {
BiffArchive::BiffArchive(ArchiveReadStream *stream, ObjectBuilder objectBuilder) :
_objectBuilder(objectBuilder) {
read(stream);
}
BiffArchive::~BiffArchive() {
for (uint i = 0; i < _rootObjects.size(); i++) {
delete _rootObjects[i];
}
}
void BiffArchive::read(ArchiveReadStream *stream) {
uint32 id = stream->readUint32BE();
if (id != MKTAG('B', 'I', 'F', 'F')) {
error("Wrong magic while reading biff archive");
}
_version = stream->readUint32LE();
/*uint32 u1 = */stream->readUint32LE();
/*uint32 u2 = */stream->readUint32LE();
uint32 len = stream->readUint32LE();
for (uint32 i = 0; i < len; i++) {
BiffObject *object = readObject(stream, nullptr);
_rootObjects.push_back(object);
}
}
BiffObject *BiffArchive::readObject(ArchiveReadStream *stream, BiffObject *parent) {
uint32 marker = stream->readUint32LE();
if (marker != 0xf0f0f0f0) {
error("Wrong magic while reading biff archive");
}
uint32 type = stream->readUint32LE();
BiffObject *object = _objectBuilder(type);
if (!object) {
error("Unimplemented BIFF object type %x", type);
}
object->_parent = parent;
object->_u3 = stream->readUint32LE();
uint32 size = stream->readUint32LE();
if (_version >= 2) {
object->_version = stream->readUint32LE();
}
object->readData(stream, size);
marker = stream->readUint32LE();
if (marker != 0x0f0f0f0f) {
error("Wrong magic while reading biff archive");
}
uint32 len = stream->readUint32LE();
for (uint32 i = 0; i < len; ++i) {
BiffObject *child = readObject(stream, object);
object->addChild(child);
}
return object;
}
Common::Array<BiffObject *> BiffArchive::listObjects() {
return _rootObjects;
}
BiffObject::BiffObject() :
_u3(0),
_version(0),
_parent(nullptr),
_type(0) {
}
uint32 BiffObject::getType() const {
return _type;
}
void BiffObject::addChild(BiffObject *child) {
_children.push_back(child);
}
BiffObject::~BiffObject() {
// Delete the children objects
Common::Array<BiffObject *>::iterator i = _children.begin();
while (i != _children.end()) {
delete *i;
i++;
}
}
} // End of namespace Formats
} // End of namespace Stark

View File

@@ -0,0 +1,135 @@
/* 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/>.
*
*/
#ifndef STARK_FORMATS_BIFF_H
#define STARK_FORMATS_BIFF_H
#include "common/array.h"
#include "common/scummsys.h"
namespace Stark {
class ArchiveReadStream;
namespace Formats {
class BiffObject;
/**
* A tree-style container for BiffObjects
*
* Users of this class must provide a factory method for the BiffObject subclasses
* contained in the archive. This class can only read the archive's structure
* and not specific object types.
*/
class BiffArchive {
public:
typedef BiffObject *(*ObjectBuilder)(uint32 type);
BiffArchive(ArchiveReadStream *stream, ObjectBuilder objectBuilder);
~BiffArchive();
/** List the objects at the root level of the archive */
Common::Array<BiffObject *> listObjects();
/** List objects recursively matching the template parameter type */
template<class T>
Common::Array<T *> listObjectsRecursive();
private:
void read(ArchiveReadStream *stream);
BiffObject *readObject(ArchiveReadStream *stream, BiffObject *parent);
ObjectBuilder _objectBuilder;
uint32 _version;
Common::Array<BiffObject *> _rootObjects;
};
/**
* An object which can be read from a BiffArchive
*
* Each object has a list of children objects, resulting in a tree structure
*/
class BiffObject {
public:
BiffObject();
virtual ~BiffObject();
/**
* Used to read the object data from the stream
*/
virtual void readData(ArchiveReadStream *stream, uint32 dataLength) = 0;
/** Get the object type */
uint32 getType() const;
/** List children recursively matching the template parameter type */
template<class T>
Common::Array<T *> listChildrenRecursive();
/** Add an object to the children list */
void addChild(BiffObject *child);
protected:
uint32 _type;
uint32 _u3;
uint32 _version;
BiffObject *_parent;
Common::Array<BiffObject *> _children;
friend class BiffArchive;
};
template<class T>
Common::Array<T *> BiffArchive::listObjectsRecursive() {
Common::Array<BiffObject *> objects = listObjects();
Common::Array<T *> array;
for (uint i = 0; i < objects.size(); i++) {
array.push_back(objects[i]->listChildrenRecursive<T>());
}
return array;
}
template<class T>
Common::Array<T *> BiffObject::listChildrenRecursive() {
Common::Array<T *> list;
for (uint i = 0; i < _children.size(); i++) {
if (_children[i]->getType() == T::TYPE) {
// Found a matching child
list.push_back(static_cast<T *>(_children[i]));
}
// Look for matching resources in the child's children
list.push_back(_children[i]->listChildrenRecursive<T>());
}
return list;
}
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_FORMATS_BIFF_H

View File

@@ -0,0 +1,443 @@
/* 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/formats/biffmesh.h"
#include "engines/stark/formats/biff.h"
#include "engines/stark/services/archiveloader.h"
#include "common/hashmap.h"
namespace Stark {
namespace Formats {
enum BiffMeshObjectType {
kMeshObjectSceneData = 0x5a4aa94,
kMeshObjectBase = 0x5a4aa89,
kMeshObjectTri = 0x5a4aa8d,
kMeshObjectMaterial = 0x5a4aa8e
};
class MeshObjectSceneData : public BiffObject {
public:
static const uint32 TYPE = kMeshObjectSceneData;
MeshObjectSceneData() :
BiffObject(),
_animStart(0),
_animEnd(0) {
_type = TYPE;
}
// BiffObject API
void readData(ArchiveReadStream *stream, uint32 dataLength) override {
_animStart = stream->readUint32LE();
_animEnd = stream->readUint32LE();
}
private:
uint32 _animStart;
uint32 _animEnd;
};
class MeshObjectBase : public BiffObject {
public:
static const uint32 TYPE = kMeshObjectBase;
MeshObjectBase() :
BiffObject() {
_type = TYPE;
}
// BiffObject API
void readData(ArchiveReadStream *stream, uint32 dataLength) override {
_name = stream->readString16();
}
private:
Common::String _name;
};
class MeshObjectTri : public BiffObject {
public:
static const uint32 TYPE = kMeshObjectTri;
MeshObjectTri() :
BiffObject(),
_hasPhysics(false) {
_type = TYPE;
}
struct KeyFrame {
uint32 time;
Math::Quaternion essentialRotation;
float determinant;
Math::Quaternion stretchRotation;
Math::Vector3d scale;
Math::Vector3d translation;
};
struct Vertex {
Common::String animName1;
Common::String animName2;
float animInfluence1;
float animInfluence2;
Math::Vector3d position;
};
struct RawFace {
uint32 vertexIndex[3];
uint32 normalIndex[3];
uint32 textureVertexIndex[3];
uint32 materialId;
uint32 smoothingGroup;
};
// BiffObject API
void readData(ArchiveReadStream *stream, uint32 dataLength) override {
_name = stream->readString16();
uint32 keyFrameCount = stream->readUint32LE();
for (uint i = 0; i < keyFrameCount; i++) {
KeyFrame keyFrame;
keyFrame.time = stream->readUint32LE();
keyFrame.essentialRotation = stream->readQuaternion();
keyFrame.determinant = stream->readFloatLE();
keyFrame.stretchRotation = stream->readQuaternion();
keyFrame.scale = stream->readVector3();
keyFrame.translation = stream->readVector3();
_keyFrames.push_back(keyFrame);
}
if (_version >= 2) {
uint32 uvKeyFrameCount = stream->readUint32LE();
assert(uvKeyFrameCount == 0); // Reading the uv keyframes is not implemented
uint32 attributeCount = stream->readUint32LE();
assert(attributeCount == 0); // Reading the attributes is not implemented
}
uint32 vertexCount = stream->readUint32LE();
for (uint i = 0; i < vertexCount; i++) {
Vertex vertex;
vertex.animName1 = stream->readString16();
vertex.animName2 = stream->readString16();
vertex.animInfluence1 = stream->readFloatLE();
vertex.animInfluence2 = stream->readFloatLE();
vertex.position = stream->readVector3();
_rawVertices.push_back(vertex);
}
uint32 normalCount = stream->readUint32LE();
for (uint i = 0; i < normalCount; i++) {
_rawNormals.push_back(stream->readVector3());
}
uint32 textureVertexCount = stream->readUint32LE();
for (uint i = 0; i < textureVertexCount; i++) {
_rawTexturePositions.push_back(stream->readVector3());
}
uint32 faceCount = stream->readUint32LE();
for (uint i = 0; i < faceCount; i++) {
RawFace face;
face.vertexIndex[0] = stream->readUint32LE();
face.vertexIndex[1] = stream->readUint32LE();
face.vertexIndex[2] = stream->readUint32LE();
face.normalIndex[0] = stream->readUint32LE();
face.normalIndex[1] = stream->readUint32LE();
face.normalIndex[2] = stream->readUint32LE();
face.textureVertexIndex[0] = stream->readUint32LE();
face.textureVertexIndex[1] = stream->readUint32LE();
face.textureVertexIndex[2] = stream->readUint32LE();
face.materialId = stream->readUint32LE();
face.smoothingGroup = stream->readUint32LE();
_rawFaces.push_back(face);
}
_hasPhysics = stream->readByte();
}
void reindex() {
// The raw data loaded from the BIFF archive needs to be split into faces of identical material.
// Reserve enough faces in our faces array
for (uint i = 0; i < _rawFaces.size(); i++) {
if (_rawFaces[i].materialId >= _faces.size()) {
_faces.resize(_rawFaces[i].materialId + 1);
}
_faces[_rawFaces[i].materialId].materialId = _rawFaces[i].materialId;
}
// The raw data loaded from the BIFF archive is multi-indexed, which is not simple to use to draw.
// Here, we reindex the data so that each vertex owns all of its related attributes, hence requiring
// a single index list.
Common::HashMap<VertexKey, uint32, VertexKey::Hash, VertexKey::EqualTo> vertexIndexMap;
for (uint i = 0; i < _rawFaces.size(); i++) {
for (uint j = 0; j < 3; j++) {
VertexKey vertexKey(_rawFaces[i].vertexIndex[j], _rawFaces[i].normalIndex[j], _rawFaces[i].textureVertexIndex[j]);
if (!vertexIndexMap.contains(vertexKey)) {
BiffMesh::Vertex vertex;
vertex.position = _rawVertices[_rawFaces[i].vertexIndex[j]].position;
vertex.normal = _rawNormals[_rawFaces[i].normalIndex[j]];
vertex.texturePosition = _rawTexturePositions[_rawFaces[i].textureVertexIndex[j]];
_vertices.push_back(vertex);
vertexIndexMap.setVal(vertexKey, _vertices.size() - 1);
}
uint32 vertexIndex = vertexIndexMap.getVal(vertexKey);
// Add the index to a face according to its material
_faces[_rawFaces[i].materialId].vertexIndices.push_back(vertexIndex);
}
}
// Clear the raw data
_rawVertices.clear();
_rawNormals.clear();
_rawTexturePositions.clear();
_rawFaces.clear();
}
Math::Matrix4 getTransform(uint keyframeIndex) const {
const KeyFrame &keyframe = _keyFrames[keyframeIndex];
Math::Matrix4 translation;
translation.setPosition(keyframe.translation);
Math::Matrix4 essentialRotation = keyframe.essentialRotation.toMatrix();
Math::Matrix4 determinant;
determinant.setValue(0, 0, keyframe.determinant);
determinant.setValue(1, 1, keyframe.determinant);
determinant.setValue(2, 2, keyframe.determinant);
Math::Matrix4 stretchRotation = keyframe.stretchRotation.toMatrix();
Math::Matrix4 stretchRotationTransposed = stretchRotation;
stretchRotationTransposed.transpose();
Math::Matrix4 scale;
scale.setValue(0, 0, keyframe.scale.x());
scale.setValue(1, 1, keyframe.scale.y());
scale.setValue(2, 2, keyframe.scale.z());
return translation * essentialRotation * determinant * stretchRotationTransposed * scale * stretchRotation;
}
const Common::Array<BiffMesh::Vertex> &getVertices() const {
return _vertices;
}
const Common::Array<Face> &getFaces() const {
return _faces;
}
private:
struct VertexKey {
uint32 _vertexIndex;
uint32 _normalIndex;
uint32 _textureVertexIndex;
VertexKey(uint32 vertexIndex, uint32 normalIndex, uint32 textureVertexIndex) {
_vertexIndex = vertexIndex;
_normalIndex = normalIndex;
_textureVertexIndex = textureVertexIndex;
}
struct Hash {
uint operator() (const VertexKey &x) const {
return x._vertexIndex + x._normalIndex + x._textureVertexIndex;
}
};
struct EqualTo {
bool operator() (const VertexKey &x, const VertexKey &y) const {
return x._vertexIndex == y._vertexIndex &&
x._normalIndex == y._normalIndex &&
x._textureVertexIndex == y._textureVertexIndex;
}
};
};
Common::String _name;
Common::Array<KeyFrame> _keyFrames;
Common::Array<Vertex> _rawVertices;
Common::Array<RawFace> _rawFaces;
Common::Array<Math::Vector3d> _rawNormals;
Common::Array<Math::Vector3d> _rawTexturePositions;
Common::Array<BiffMesh::Vertex> _vertices;
Common::Array<Face> _faces;
bool _hasPhysics;
};
class MeshObjectMaterial : public BiffObject {
public:
static const uint32 TYPE = kMeshObjectMaterial;
MeshObjectMaterial() :
BiffObject(),
_shading(0),
_shininess(0),
_opacity(1),
_doubleSided(false),
_textureTiling(0),
_alphaTiling(0),
_environementTiling(0),
_isColorKey(false),
_colorKey(0) {
_type = TYPE;
}
// BiffObject API
void readData(ArchiveReadStream *stream, uint32 dataLength) override {
_name = stream->readString16();
_texture = stream->readString16();
_alpha = stream->readString16();
_environment = stream->readString16();
_shading = stream->readUint32LE();
_ambiant = stream->readVector3();
_diffuse = stream->readVector3();
_specular = stream->readVector3();
_shininess = stream->readFloatLE();
_opacity = stream->readFloatLE();
_doubleSided = stream->readByte();
_textureTiling = stream->readUint32LE();
_alphaTiling = stream->readUint32LE();
_environementTiling = stream->readUint32LE();
_isColorKey = stream->readByte();
_colorKey = stream->readUint32LE();
uint32 attributeCount = stream->readUint32LE();
assert(attributeCount == 0); // Reading the attributes is not implemented
}
Material toMaterial() const {
Material material;
material.name = _name;
material.texture = _texture;
material.r = _diffuse.x();
material.g = _diffuse.y();
material.b = _diffuse.z();
material.doubleSided = _doubleSided;
return material;
}
private:
Common::String _name;
Common::String _texture;
Common::String _alpha;
Common::String _environment;
uint32 _shading;
Math::Vector3d _ambiant;
Math::Vector3d _diffuse;
Math::Vector3d _specular;
float _shininess;
float _opacity;
bool _doubleSided;
uint32 _textureTiling;
uint32 _alphaTiling;
uint32 _environementTiling;
bool _isColorKey;
uint32 _colorKey;
};
BiffMesh *BiffMeshReader::read(ArchiveReadStream *stream) {
BiffArchive archive = BiffArchive(stream, &biffObjectBuilder);
Common::Array<MeshObjectTri *> tris = archive.listObjectsRecursive<MeshObjectTri>();
Common::Array<MeshObjectMaterial *> materialObjects = archive.listObjectsRecursive<MeshObjectMaterial>();
if (tris.size() != 1) {
error("Unexpected tri count in BIFF archive: '%d'", tris.size());
}
tris[0]->reindex();
Common::Array<Material> materials;
for (uint i = 0; i < materialObjects.size(); i++) {
materials.push_back(materialObjects[i]->toMaterial());
}
BiffMesh *mesh = new BiffMesh(tris[0]->getVertices(), tris[0]->getFaces(), materials);
mesh->setTransform(tris[0]->getTransform(0));
return mesh;
}
BiffObject *BiffMeshReader::biffObjectBuilder(uint32 type) {
switch (type) {
case kMeshObjectSceneData:
return new MeshObjectSceneData();
case kMeshObjectBase:
return new MeshObjectBase();
case kMeshObjectTri:
return new MeshObjectTri();
case kMeshObjectMaterial:
return new MeshObjectMaterial();
default:
return nullptr;
}
}
BiffMesh::BiffMesh(const Common::Array<Vertex> &vertices, const Common::Array<Face> &faces,
const Common::Array<Material> &materials) :
_vertices(vertices),
_faces(faces),
_materials(materials) {
}
const Common::Array<BiffMesh::Vertex> &BiffMesh::getVertices() const {
return _vertices;
}
const Common::Array<Face> &BiffMesh::getFaces() const {
return _faces;
}
const Common::Array<Material> &BiffMesh::getMaterials() const {
return _materials;
}
void BiffMesh::setTransform(const Math::Matrix4 &transform) {
_transform = transform;
}
Math::Matrix4 BiffMesh::getTransform() const {
return _transform;
}
} // End of namespace Formats
} // End of namespace Stark

View File

@@ -0,0 +1,87 @@
/* 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/>.
*
*/
#ifndef STARK_FORMATS_BIFF_MESH_H
#define STARK_FORMATS_BIFF_MESH_H
#include "engines/stark/model/model.h"
#include "common/array.h"
#include "math/matrix4.h"
#include "math/vector3d.h"
namespace Stark {
class ArchiveReadStream;
namespace Formats {
class BiffObject;
class BiffMesh;
/**
* A mesh reader
*
* Reads a mesh out of a BIFF archive
*/
class BiffMeshReader {
public:
/** Read a mesh from a BIFF archive stream */
static BiffMesh *read(ArchiveReadStream *stream);
private:
static BiffObject *biffObjectBuilder(uint32 type);
};
/**
* A mesh read out of a BIFF archive
*/
class BiffMesh {
public:
struct Vertex {
Math::Vector3d position;
Math::Vector3d normal;
Math::Vector3d texturePosition;
};
BiffMesh(const Common::Array<Vertex> &vertices, const Common::Array<Face> &faces, const Common::Array<Material> &materials);
const Common::Array<Vertex> &getVertices() const;
const Common::Array<Face> &getFaces() const;
const Common::Array<Material> &getMaterials() const;
Math::Matrix4 getTransform() const;
void setTransform(const Math::Matrix4 &transform);
private:
Common::Array<Vertex> _vertices;
Common::Array<Face> _faces;
Common::Array<Material> _materials;
Math::Matrix4 _transform;
};
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_FORMATS_BIFF_MESH_H

View File

@@ -0,0 +1,190 @@
/* 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/formats/dds.h"
#include "common/textconsole.h"
namespace Stark {
namespace Formats {
// Based on xoreos' DDS code
static const uint32 kDDSID = MKTAG('D', 'D', 'S', ' ');
static const uint32 kHeaderFlagsHasMipMaps = 0x00020000;
static const uint32 kPixelFlagsHasAlpha = 0x00000001;
static const uint32 kPixelFlagsHasFourCC = 0x00000004;
static const uint32 kPixelFlagsIsIndexed = 0x00000020;
static const uint32 kPixelFlagsIsRGB = 0x00000040;
DDS::~DDS() {
for (uint i = 0; i < _mipmaps.size(); i++) {
_mipmaps[i].free();
}
}
bool DDS::load(Common::SeekableReadStream &dds, const Common::String &name) {
assert(_mipmaps.empty());
_name = name;
if (!readHeader(dds)) {
return false;
}
return readData(dds);
}
const DDS::MipMaps &DDS::getMipMaps() const {
return _mipmaps;
}
bool DDS::readHeader(Common::SeekableReadStream &dds) {
// We found the FourCC of a standard DDS
uint32 magic = dds.readUint32BE();
if (magic != kDDSID) {
warning("Invalid DDS magic number: %d for %s", magic, _name.c_str());
return false;
}
// All DDS header should be 124 bytes (+ 4 for the FourCC)
uint32 headerSize = dds.readUint32LE();
if (headerSize != 124) {
warning("Invalid DDS header size: %d for %s", headerSize, _name.c_str());
return false;
}
// DDS features
uint32 flags = dds.readUint32LE();
// Image dimensions
uint32 height = dds.readUint32LE();
uint32 width = dds.readUint32LE();
if ((width >= 0x8000) || (height >= 0x8000)) {
warning("Unsupported DDS image dimensions (%ux%u) for %s", width, height, _name.c_str());
return false;
}
dds.skip(4 + 4); // Pitch + Depth
//uint32 pitchOrLineSize = dds.readUint32LE();
//uint32 depth = dds.readUint32LE();
uint32 mipMapCount = dds.readUint32LE();
// DDS doesn't provide any mip maps, only one full-size image
if ((flags & kHeaderFlagsHasMipMaps) == 0) {
mipMapCount = 1;
}
dds.skip(44); // Reserved
// Read the pixel data format
DDSPixelFormat format;
format.size = dds.readUint32LE();
format.flags = dds.readUint32LE();
format.fourCC = dds.readUint32BE();
format.bitCount = dds.readUint32LE();
format.rBitMask = dds.readUint32LE();
format.gBitMask = dds.readUint32LE();
format.bBitMask = dds.readUint32LE();
format.aBitMask = dds.readUint32LE();
// Detect which specific format it describes
if (!detectFormat(format)) {
return false;
}
dds.skip(16 + 4); // DDCAPS2 + Reserved
_mipmaps.resize(mipMapCount);
for (uint32 i = 0; i < mipMapCount; i++) {
_mipmaps[i].create(width, height, _format);
width >>= 1;
height >>= 1;
}
return true;
}
bool DDS::readData(Common::SeekableReadStream &dds) {
for (uint i = 0; i < _mipmaps.size(); i++) {
Graphics::Surface &mipmap = _mipmaps[i];
uint32 size = mipmap.pitch * mipmap.h;
uint32 readSize = dds.read(mipmap.getPixels(), size);
if (readSize != size) {
warning("Inconsistent read size in DDS file: %d, expected %d for %s level %d",
readSize, size, _name.c_str(), i);
return false;
}
}
return true;
}
bool DDS::detectFormat(const DDSPixelFormat &format) {
if (format.flags & kPixelFlagsHasFourCC) {
warning("Unsupported DDS feature: FourCC pixel format %d for %s", format.fourCC, _name.c_str());
return false;
}
if (format.flags & kPixelFlagsIsIndexed) {
warning("Unsupported DDS feature: Indexed %d-bits pixel format for %s", format.bitCount, _name.c_str());
return false;
}
if (!(format.flags & kPixelFlagsIsRGB)) {
warning("Only RGB DDS files are supported for %s", _name.c_str());
return false;
}
if (format.bitCount != 24 && format.bitCount != 32) {
warning("Only 24-bits and 32-bits DDS files are supported for %s", _name.c_str());
return false;
}
if ((format.flags & kPixelFlagsHasAlpha) &&
(format.bitCount == 32) &&
(format.rBitMask == 0x00FF0000) && (format.gBitMask == 0x0000FF00) &&
(format.bBitMask == 0x000000FF) && (format.aBitMask == 0xFF000000)) {
_format = Graphics::PixelFormat::createFormatBGRA32();
return true;
} else if (!(format.flags & kPixelFlagsHasAlpha) &&
(format.bitCount == 24) &&
(format.rBitMask == 0x00FF0000) && (format.gBitMask == 0x0000FF00) &&
(format.bBitMask == 0x000000FF)) {
_format = Graphics::PixelFormat::createFormatBGR24();
return true;
} else {
warning("Unsupported pixel format (%X, %X, %d, %X, %X, %X, %X) for %s",
format.flags, format.fourCC, format.bitCount,
format.rBitMask, format.gBitMask, format.bBitMask, format.aBitMask,
_name.c_str());
return false;
}
}
} // End of namespace Formats
} // End of namespace Stark

100
engines/stark/formats/dds.h Normal file
View File

@@ -0,0 +1,100 @@
/* 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/>.
*
*/
#ifndef STARK_FORMATS_DDS_H
#define STARK_FORMATS_DDS_H
#include "common/array.h"
#include "common/stream.h"
#include "graphics/surface.h"
namespace Stark {
namespace Formats {
// Based on xoreos' DDS code
/**
* DDS texture
*
* Only a very small subset of DDS features are supported. Especially,
* compressed formats are not supported. This class is meant to
* load a single DDS file per instance.
*/
class DDS {
public:
~DDS();
typedef Common::Array<Graphics::Surface> MipMaps;
/** Load a DDS texture from a stream */
bool load(Common::SeekableReadStream &dds, const Common::String &name);
/**
* Retrieve the mip map levels for a loaded texture
*
* The first mipmap is the full size image. Each further
* mipmap divides by two the with and the height of the
* previous one.
*/
const MipMaps &getMipMaps() const;
private:
/** The specific pixel format of the included image data. */
struct DDSPixelFormat {
/** The size of the image data in bytes */
uint32 size;
/** Features of the image data */
uint32 flags;
/** The FourCC to detect the format by */
uint32 fourCC;
/** Number of bits per pixel */
uint32 bitCount;
/** Bit mask for the red color component */
uint32 rBitMask;
/** Bit mask for the green color component */
uint32 gBitMask;
/** Bit mask for the blue color component */
uint32 bBitMask;
/** Bit mask for the alpha component */
uint32 aBitMask;
};
bool readHeader(Common::SeekableReadStream &dds);
bool readData(Common::SeekableReadStream &dds);
bool detectFormat(const DDSPixelFormat &format);
MipMaps _mipmaps;
Graphics::PixelFormat _format;
Common::String _name;
};
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_FORMATS_DDS_H

View File

@@ -0,0 +1,154 @@
/* 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/formats/iss.h"
#include "audio/decoders/adpcm_intern.h"
#include "audio/decoders/raw.h"
#include "common/substream.h"
namespace Stark {
namespace Formats {
/**
* ADPCM decoder for the .iss files
*/
class ISSADPCMStream : public Audio::Ima_ADPCMStream {
public:
ISSADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
: Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
protected:
int readBuffer(int16 *buffer, const int numSamples) override {
// Similar to MS IMA, but without the four-bytes-per-channel requirement
int samples;
assert(numSamples % 2 == 0);
for (samples = 0; samples < numSamples && !endOfData(); samples += 2) {
if (_blockPos[0] == _blockAlign) {
// read block header
for (byte i = 0; i < _channels; i++) {
_status.ima_ch[i].last = _stream->readSint16LE();
_status.ima_ch[i].stepIndex = _stream->readSint16LE();
}
_blockPos[0] = 4 * _channels;
}
byte data = _stream->readByte();
buffer[samples + (isStereo() ? 1 : 0)] = decodeIMA(data & 0x0f, isStereo() ? 1 : 0);
buffer[samples + (isStereo() ? 0 : 1)] = decodeIMA((data >> 4) & 0x0f);
_blockPos[0]++;
}
return samples;
}
};
static void skipString(Common::SeekableReadStream *stream) {
// Skip until the next space. Note that this will read past \0
// characters as well. That's not a bug.
byte ch;
while ((ch = stream->readByte()) != 0x20)
;
}
static Common::String readString(Common::SeekableReadStream *stream) {
Common::String ret = "";
byte ch;
while ((ch = stream->readByte()) != 0x20)
ret += ch;
return ret;
}
Audio::RewindableAudioStream *makeISSStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
Common::String codec;
uint16 blockSize, channels, freq = 44100;
uint32 size;
byte flags;
codec = readString(stream);
if (codec.equals("IMA_ADPCM_Sound")) {
codec = readString(stream);
blockSize = (uint16)strtol(codec.c_str(), 0, 10);
skipString(stream);
// name ?
skipString(stream);
// ?
codec = readString(stream);
channels = (uint16)strtol(codec.c_str(), 0, 10) + 1;
skipString(stream);
// ?
codec = readString(stream);
int val = strtol(codec.c_str(), 0, 10);
if (val)
freq /= val;
skipString(stream);
skipString(stream);
codec = readString(stream);
size = (uint32)strtol(codec.c_str(), 0, 10);
return new ISSADPCMStream(stream, DisposeAfterUse::YES, size, freq, channels, blockSize);
} else if (codec.equals("Sound")) {
skipString(stream);
// name ?
codec = readString(stream);
// sample count ?
codec = readString(stream);
channels = (uint16)strtol(codec.c_str(), 0, 10) + 1;
skipString(stream);
// ?
codec = readString(stream);
int val = strtol(codec.c_str(), 0, 10);
if (val)
freq /= val;
skipString(stream);
skipString(stream);
flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
if (channels == 2)
flags |= Audio::FLAG_STEREO;
return Audio::makeRawStream(new Common::SeekableSubReadStream(stream, stream->pos(), stream->size(), DisposeAfterUse::YES), freq, flags, DisposeAfterUse::YES);
} else {
error("Unknown ISS codec '%s'", codec.c_str());
}
}
} // End of namespace Formats
} // End of namespace Stark

View File

@@ -0,0 +1,45 @@
/* 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/>.
*
*/
#ifndef STARK_ISS_H
#define STARK_ISS_H
#include "common/stream.h"
#include "audio/audiostream.h"
namespace Stark {
namespace Formats {
/**
* Create a new RewindableAudioStream from the ISS data in the given stream.
* ISS is the file format used by the 4 CD version of the game.
*
* @param stream the SeekableReadStream from which to read the ISS data
* @param disposeAfterUse whether to delete the stream after use
* @return a new RewindableAudioStream, or NULL, if an error occurred
*/
Audio::RewindableAudioStream *makeISSStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_ISS_H

View File

@@ -0,0 +1,155 @@
/* 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/formats/tm.h"
#include "engines/stark/gfx/driver.h"
#include "engines/stark/gfx/texture.h"
#include "engines/stark/formats/biff.h"
#include "engines/stark/services/archiveloader.h"
#include "engines/stark/services/services.h"
namespace Stark {
namespace Formats {
class TextureGroup : public BiffObject {
public:
static const uint32 TYPE = kTextureSetGroup;
TextureGroup() :
BiffObject(),
_palette(nullptr) {
_type = TYPE;
}
~TextureGroup() override {
delete[] _palette;
}
const byte *getPalette() {
return _palette;
}
// BiffObject API
void readData(ArchiveReadStream *stream, uint32 dataLength) override {
int entries = stream->readUint32LE();
_palette = new byte[entries * 3];
byte *ptr = _palette;
for (int i = 0; i < entries; ++i) {
*ptr++ = (byte) stream->readUint16LE();
*ptr++ = (byte) stream->readUint16LE();
*ptr++ = (byte) stream->readUint16LE();
}
}
private:
byte *_palette;
};
Texture::Texture() :
BiffObject(),
_texture(nullptr),
_u(0) {
_type = TYPE;
}
Texture::~Texture() {
_surface.free();
delete _texture;
}
void Texture::readData(ArchiveReadStream *stream, uint32 dataLength) {
TextureGroup *textureGroup = static_cast<TextureGroup *>(_parent);
_name = stream->readString16();
_u = stream->readByte();
uint32 w = stream->readUint32LE();
uint32 h = stream->readUint32LE();
uint32 levels = stream->readUint32LE();
_texture = StarkGfx->createTexture();
_texture->setLevelCount(levels);
for (uint32 i = 0; i < levels; ++i) {
// Read the pixel data to a surface
Graphics::Surface level;
Graphics::Surface *surface = i == 0 ? &_surface : &level;
surface->create(w, h, Graphics::PixelFormat::createFormatCLUT8());
stream->read(surface->getPixels(), surface->w * surface->h);
// Add the mipmap level to the texture
_texture->addLevel(i, surface, textureGroup->getPalette());
level.free();
w /= 2;
h /= 2;
}
}
Gfx::Texture *Texture::acquireTexturePointer() {
Gfx::Texture *texture = _texture;
_texture = nullptr;
return texture;
}
Graphics::Surface *Texture::getSurface() const {
TextureGroup *textureGroup = static_cast<TextureGroup *>(_parent);
return _surface.convertTo(Gfx::Driver::getRGBAPixelFormat(), textureGroup->getPalette());
}
Gfx::TextureSet *TextureSetReader::read(ArchiveReadStream *stream) {
BiffArchive archive = BiffArchive(stream, &biffObjectBuilder);
Common::Array<Texture *> textures = archive.listObjectsRecursive<Texture>();
Gfx::TextureSet *textureSet = new Gfx::TextureSet();
for (uint i = 0; i < textures.size(); i++) {
textureSet->addTexture(textures[i]->getName(), textures[i]->acquireTexturePointer());
}
return textureSet;
}
BiffArchive *TextureSetReader::readArchive(ArchiveReadStream *stream) {
return new BiffArchive(stream, &biffObjectBuilder);
}
BiffObject *TextureSetReader::biffObjectBuilder(uint32 type) {
switch (type) {
case kTextureSetGroup:
return new TextureGroup();
case kTextureSetTexture:
return new Texture();
default:
return nullptr;
}
}
} // End of namespace Formats
} // End of namespace Stark

107
engines/stark/formats/tm.h Normal file
View File

@@ -0,0 +1,107 @@
/* 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/>.
*
*/
#ifndef STARK_FORMATS_TM_H
#define STARK_FORMATS_TM_H
#include "engines/stark/formats/biff.h"
#include "common/str.h"
#include "graphics/surface.h"
namespace Stark {
class ArchiveReadStream;
namespace Gfx {
class TextureSet;
class Texture;
}
namespace Formats {
/**
* A texture set loader able to read '.tm' files
*/
class TextureSetReader {
public:
/**
* Load a texture set from the provided stream.
*
* The caller is responsible for freeing the texture set.
*/
static Gfx::TextureSet *read(ArchiveReadStream *stream);
/** Read the texture set archive from the provided stream */
static BiffArchive *readArchive(ArchiveReadStream *stream);
private:
static BiffObject *biffObjectBuilder(uint32 type);
};
enum TextureSetType {
kTextureSetGroup = 0x02faf082,
kTextureSetTexture = 0x02faf080
};
/**
* A texture contained in a '.tm' texture set archive
*
* Textures have mipmaps.
*/
class Texture : public BiffObject {
public:
static const uint32 TYPE = kTextureSetTexture;
Texture();
~Texture() override;
Common::String getName() const {
return _name;
}
/**
* Return a pointer to a texture ready for rendering
*
* The caller takes ownership of the texture.
* This method can only be called successfully once
* per texture. Subsequent calls return a null pointer.
*/
Gfx::Texture *acquireTexturePointer();
/** Return a RGBA copy of the pixel data */
Graphics::Surface *getSurface() const;
// BiffObject API
void readData(ArchiveReadStream *stream, uint32 dataLength) override;
private:
Common::String _name;
Gfx::Texture *_texture;
Graphics::Surface _surface;
byte _u;
};
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_FORMATS_TM_H

View File

@@ -0,0 +1,229 @@
/* 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/>.
*
*/
// Based on the Xentax Wiki documentation:
// https://web.archive.org/web/20090621212912/http://wiki.xentax.com/index.php?title=The_Longest_Journey_XARC
#include "engines/stark/formats/xarc.h"
#include "engines/stark/debug.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/substream.h"
namespace Stark {
namespace Formats {
// ARCHIVE MEMBER
class XARCMember : public Common::ArchiveMember {
public:
XARCMember(const XARCArchive *xarc, Common::ReadStream &stream, uint32 offset);
Common::SeekableReadStream *createReadStream() const override;
Common::SeekableReadStream *createReadStreamForAltStream(Common::AltStreamType altStreamType) const override;
Common::String getName() const override { return _name.baseName(); }
Common::Path getPathInArchive() const override { return _name; }
Common::String getFileName() const override { return _name.baseName(); }
uint32 getLength() const { return _length; }
uint32 getOffset() const { return _offset; }
private:
const XARCArchive *_xarc;
Common::Path _name;
uint32 _offset;
uint32 _length;
// Helper function
Common::String readString(Common::ReadStream &stream);
};
XARCMember::XARCMember(const XARCArchive *xarc, Common::ReadStream &stream, uint32 offset) {
_xarc = xarc;
// Read the information about this archive member
_name = Common::Path(readString(stream));
_offset = offset;
_length = stream.readUint32LE();
debugC(20, kDebugArchive, "Stark::XARC Member: \"%s\" starts at offset=%d and has length=%d", _name.toString().c_str(), _offset, _length);
// Unknown value. English: 0, others: 1
uint32 unknown = stream.readUint32LE();
debugC(kDebugUnknown, "Stark::XARC Member: \"%s\" has unknown=%d", _name.toString().c_str(), unknown);
if (unknown != 0 && unknown != 1) {
warning("Stark::XARC Member: \"%s\" has unknown=%d with unknown meaning", _name.toString().c_str(), unknown);
}
}
Common::SeekableReadStream *XARCMember::createReadStream() const {
return _xarc->createReadStreamForMember(this);
}
Common::SeekableReadStream *XARCMember::createReadStreamForAltStream(Common::AltStreamType altStreamType) const {
return nullptr;
}
Common::String XARCMember::readString(Common::ReadStream &stream) {
Common::String str;
// Read until we find a 0
char c = 1;
while (c != 0) {
c = stream.readByte();
if (stream.eos()) {
c = 0;
}
if (c != 0) {
str += c;
}
}
return str;
}
// ARCHIVE
bool XARCArchive::open(const Common::Path &filename) {
Common::File stream;
if (!stream.open(filename)) {
return false;
}
_filename = filename;
// Unknown: always 1? version?
uint32 unknown = stream.readUint32LE();
debugC(kDebugUnknown, "Stark::XARC: \"%s\" has unknown=%d", _filename.toString(Common::Path::kNativeSeparator).c_str(), unknown);
if (unknown != 1) {
warning("Stark::XARC: \"%s\" has unknown=%d with unknown meaning", _filename.toString(Common::Path::kNativeSeparator).c_str(), unknown);
}
// Read the number of contained files
uint32 numFiles = stream.readUint32LE();
debugC(20, kDebugArchive, "Stark::XARC: \"%s\" contains %d files", _filename.toString(Common::Path::kNativeSeparator).c_str(), numFiles);
// Read the offset to the contents of the first file
uint32 offset = stream.readUint32LE();
debugC(20, kDebugArchive, "Stark::XARC: \"%s\"'s first file has offset=%d", _filename.toString(Common::Path::kNativeSeparator).c_str(), offset);
for (uint32 i = 0; i < numFiles; i++) {
XARCMember *member = new XARCMember(this, stream, offset);
_members.push_back(Common::ArchiveMemberPtr(member));
// Set the offset to the next member
offset += member->getLength();
}
return true;
}
Common::Path XARCArchive::getFilename() const {
return _filename;
}
bool XARCArchive::hasFile(const Common::Path &path) const {
Common::String name = path.toString();
for (Common::ArchiveMemberList::const_iterator it = _members.begin(); it != _members.end(); ++it) {
if ((*it)->getName() == name) {
// Found it
return true;
}
}
// Not found
return false;
}
int XARCArchive::listMatchingMembers(Common::ArchiveMemberList &list, const Common::Path &pattern, bool matchPathComponents) const {
Common::String patternString = pattern.toString();
int matches = 0;
for (Common::ArchiveMemberList::const_iterator it = _members.begin(); it != _members.end(); ++it) {
if ((*it)->getName().matchString(patternString)) {
// This file matches, add it
list.push_back(*it);
matches++;
}
}
return matches;
}
int XARCArchive::listMembers(Common::ArchiveMemberList &list) const {
int files = 0;
for (Common::ArchiveMemberList::const_iterator it = _members.begin(); it != _members.end(); ++it) {
// Add all the members to the list
list.push_back(*it);
files++;
}
return files;
}
const Common::ArchiveMemberPtr XARCArchive::getMember(const Common::Path &path) const {
Common::String name = path.toString();
for (Common::ArchiveMemberList::const_iterator it = _members.begin(); it != _members.end(); ++it) {
if ((*it)->getName() == name) {
// Found it
return *it;
}
}
// Not found, return an empty ptr
return Common::ArchiveMemberPtr();
}
Common::SeekableReadStream *XARCArchive::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
for (Common::ArchiveMemberList::const_iterator it = _members.begin(); it != _members.end(); ++it) {
if ((*it)->getName() == name) {
// Found it
return createReadStreamForMember((const XARCMember *)it->get());
}
}
// Not found
return 0;
}
Common::SeekableReadStream *XARCArchive::createReadStreamForMember(const XARCMember *member) const {
// Open the xarc file
Common::File *f = new Common::File;
if (!f)
return NULL;
if (!f->open(_filename)) {
delete f;
return NULL;
}
// Return the substream that contains the archive member
uint32 offset = member->getOffset();
uint32 length = member->getLength();
return new Common::SeekableSubReadStream(f, offset, offset + length, DisposeAfterUse::YES);
// Different approach: keep the archive open and read full resources to memory
//f.seek(member->getOffset());
//return f.readStream(member->getLength());
}
} // End of namespace Formats
} // End of namespace Stark

View File

@@ -0,0 +1,55 @@
/* 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/>.
*
*/
#ifndef STARK_ARCHIVE_H
#define STARK_ARCHIVE_H
#include "common/archive.h"
#include "common/stream.h"
namespace Stark {
namespace Formats {
class XARCMember;
class XARCArchive : public Common::Archive {
public:
bool open(const Common::Path &filename);
Common::Path getFilename() const;
// Archive API
bool hasFile(const Common::Path &path) const;
int listMatchingMembers(Common::ArchiveMemberList &list, const Common::Path &pattern, bool matchPathComponents = false) const;
int listMembers(Common::ArchiveMemberList &list) const;
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const;
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const;
Common::SeekableReadStream *createReadStreamForMember(const XARCMember *member) const;
private:
Common::Path _filename;
Common::ArchiveMemberList _members;
};
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_ARCHIVE_H

View File

@@ -0,0 +1,248 @@
/* 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/formats/xmg.h"
#include "engines/stark/debug.h"
#include "engines/stark/gfx/driver.h"
#include "graphics/conversion.h"
#include "graphics/pixelformat.h"
#include "graphics/surface.h"
#include "common/stream.h"
#include "common/textconsole.h"
namespace Stark {
namespace Formats {
XMGDecoder::XMGDecoder(Common::ReadStream *stream) :
_width(0),
_height(0),
_currX(0),
_currY(0),
_stream(stream),
_transColor(0) {
}
Graphics::Surface *XMGDecoder::decode(Common::ReadStream *stream) {
XMGDecoder dec(stream);
dec.readHeader();
return dec.decodeImage();
}
void XMGDecoder::readSize(Common::ReadStream *stream, uint &width, uint &height) {
XMGDecoder dec(stream);
dec.readHeader();
width = dec._width;
height = dec._height;
}
void XMGDecoder::readHeader() {
// Read the file version
uint32 version = _stream->readUint32LE();
if (version != 3) {
error("Stark::XMG: File version unknown: %d", version);
}
// Read the transparency color (RGBA)
_transColor = _stream->readUint32LE();
// Read the image size
_width = _stream->readUint32LE();
_height = _stream->readUint32LE();
debugC(10, kDebugXMG, "Stark::XMG: Version=%d, TransparencyColor=0x%08x, size=%dx%d", version, _transColor, _width, _height);
// Read the scan length
uint32 scanLen = _stream->readUint32LE();
if (scanLen != 3 * _width) {
error("Stark::XMG: The scan length (%d) doesn't match the width bytes (%d)", scanLen, 3 * _width);
}
// Unknown
uint32 unknown2 = _stream->readUint32LE();
debugC(kDebugUnknown, "Stark::XMG: unknown2 = %08x = %d", unknown2, unknown2);
uint32 unknown3 = _stream->readUint32LE();
debugC(kDebugUnknown, "Stark::XMG: unknown3 = %08x = %d", unknown3, unknown3);
}
Graphics::Surface *XMGDecoder::decodeImage() {
// Create the destination surface
Graphics::Surface *surface = new Graphics::Surface();
surface->create(_width, _height, Gfx::Driver::getRGBAPixelFormat());
_currX = 0, _currY = 0;
while (!_stream->eos()) {
if (_currX >= _width) {
assert(_currX == _width);
_currX = 0;
_currY += 2;
if (_currY >= _height)
break;
}
// Read the number and mode of the tiles
byte op = _stream->readByte();
uint16 count;
if ((op & 0xC0) != 0xC0) {
count = op & 0x3F;
} else {
count = ((op & 0xF) << 8) + _stream->readByte();
op <<= 2;
}
op &= 0xC0;
// Process the current serie
for (int i = 0; i < count; i++) {
Block block = decodeBlock(op);
drawBlock(block, surface);
}
}
return surface;
}
XMGDecoder::Block XMGDecoder::decodeBlock(byte op) {
Block block;
switch (op) {
case 0x00:
// YCrCb
block = processYCrCb();
break;
case 0x40:
// Trans
block = processTrans();
break;
case 0x80:
// RGB
block = processRGB();
break;
default:
error("Unsupported color mode '%d'", op);
}
return block;
}
void XMGDecoder::drawBlock(const Block &block, Graphics::Surface *surface) {
uint32 *pixels = (uint32 *)surface->getBasePtr(_currX, _currY);
bool drawTwoColumns = _currX + 1 < _width;
bool drawTwoLines = _currY + 1 < _height;
pixels[0] = TO_LE_32(block.a1);
if (drawTwoColumns) {
pixels[1] = TO_LE_32(block.a2);
}
if (drawTwoLines) {
pixels[_width + 0] = TO_LE_32(block.b1);
}
if (drawTwoColumns && drawTwoLines) {
pixels[_width + 1] = TO_LE_32(block.b2);
}
_currX += drawTwoColumns ? 2 : 1;
}
XMGDecoder::Block XMGDecoder::processYCrCb() {
byte y0, y1, y2, y3;
byte cr, cb;
y0 = _stream->readByte();
y1 = _stream->readByte();
y2 = _stream->readByte();
y3 = _stream->readByte();
cr = _stream->readByte();
cb = _stream->readByte();
byte r, g, b;
Block block;
Graphics::YUV2RGB(y0, cb, cr, r, g, b);
block.a1 = (255u << 24) + (b << 16) + (g << 8) + r;
Graphics::YUV2RGB(y1, cb, cr, r, g, b);
block.a2 = (255u << 24) + (b << 16) + (g << 8) + r;
Graphics::YUV2RGB(y2, cb, cr, r, g, b);
block.b1 = (255u << 24) + (b << 16) + (g << 8) + r;
Graphics::YUV2RGB(y3, cb, cr, r, g, b);
block.b2 = (255u << 24) + (b << 16) + (g << 8) + r;
return block;
}
XMGDecoder::Block XMGDecoder::processTrans() {
Block block;
block.a1 = 0;
block.a2 = 0;
block.b1 = 0;
block.b2 = 0;
return block;
}
XMGDecoder::Block XMGDecoder::processRGB() {
Block block;
uint32 color;
color = _stream->readUint16LE();
color += _stream->readByte() << 16;
if (color != _transColor)
color += 255u << 24;
else
color = 0;
block.a1 = color;
color = _stream->readUint16LE();
color += _stream->readByte() << 16;
if (color != _transColor)
color += 255u << 24;
else
color = 0;
block.a2 = color;
color = _stream->readUint16LE();
color += _stream->readByte() << 16;
if (color != _transColor)
color += 255u << 24;
else
color = 0;
block.b1 = color;
color = _stream->readUint16LE();
color += _stream->readByte() << 16;
if (color != _transColor)
color += 255u << 24;
else
color = 0;
block.b2 = color;
return block;
}
} // End of namespace Formats
} // End of namespace Stark

View File

@@ -0,0 +1,78 @@
/* 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/>.
*
*/
#ifndef STARK_XMG_H
#define STARK_XMG_H
#include "common/stream.h"
namespace Graphics {
struct Surface;
}
namespace Stark {
namespace Formats {
/**
* XMG (still image) decoder
*/
class XMGDecoder {
public:
static Graphics::Surface *decode(Common::ReadStream *stream);
static void readSize(Common::ReadStream *stream, uint &width, uint &height);
private:
explicit XMGDecoder(Common::ReadStream *stream);
struct Block {
uint32 a1, a2;
uint32 b1, b2;
};
void readHeader();
Graphics::Surface *decodeImage();
Block decodeBlock(byte op);
void drawBlock(const Block &block, Graphics::Surface *surface);
Block processYCrCb();
Block processTrans();
Block processRGB();
uint32 _width;
uint32 _height;
uint32 _currX;
uint32 _currY;
Common::ReadStream *_stream;
/**
* The transparency color in the RGB and transparency blocks.
* In the output surface, the transparent color is black with zero
* alpha. So the images are effectively pre-multiplied alpha.
*/
uint32 _transColor;
};
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_XMG_H

View File

@@ -0,0 +1,333 @@
/* 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/formats/xrc.h"
#include "engines/stark/formats/xarc.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/animhierarchy.h"
#include "engines/stark/resources/animscript.h"
#include "engines/stark/resources/animsoundtrigger.h"
#include "engines/stark/resources/bonesmesh.h"
#include "engines/stark/resources/bookmark.h"
#include "engines/stark/resources/camera.h"
#include "engines/stark/resources/container.h"
#include "engines/stark/resources/command.h"
#include "engines/stark/resources/dialog.h"
#include "engines/stark/resources/direction.h"
#include "engines/stark/resources/fmv.h"
#include "engines/stark/resources/image.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/floor.h"
#include "engines/stark/resources/floorface.h"
#include "engines/stark/resources/floorfield.h"
#include "engines/stark/resources/knowledge.h"
#include "engines/stark/resources/knowledgeset.h"
#include "engines/stark/resources/layer.h"
#include "engines/stark/resources/level.h"
#include "engines/stark/resources/light.h"
#include "engines/stark/resources/lipsync.h"
#include "engines/stark/resources/location.h"
#include "engines/stark/resources/path.h"
#include "engines/stark/resources/pattable.h"
#include "engines/stark/resources/root.h"
#include "engines/stark/resources/script.h"
#include "engines/stark/resources/scroll.h"
#include "engines/stark/resources/speech.h"
#include "engines/stark/resources/sound.h"
#include "engines/stark/resources/string.h"
#include "engines/stark/resources/textureset.h"
#include "engines/stark/resourcereference.h"
namespace Stark {
namespace Formats {
XRCReadStream::XRCReadStream(const Common::Path &archiveName,
Common::SeekableReadStream *parentStream, DisposeAfterUse::Flag disposeParentStream) :
SeekableSubReadStream(parentStream, 0, parentStream->size(), disposeParentStream),
_archiveName(archiveName) {
}
XRCReadStream::~XRCReadStream() {
}
Common::String XRCReadStream::readString() {
// Read the string length
uint16 length = readUint16LE();
// Read the string
char *data = new char[length];
read(data, length);
Common::String string(data, length);
delete[] data;
return string;
}
Resources::Type XRCReadStream::readResourceType() {
byte rawType;
rawType = readByte();
return Resources::Type((Resources::Type::ResourceType) (rawType));
}
ResourceReference XRCReadStream::readResourceReference() {
ResourceReference reference;
reference.loadFromStream(this);
return reference;
}
Math::Vector3d XRCReadStream::readVector3() {
Math::Vector3d v;
v.readFromStream(this);
return v;
}
Common::Rect XRCReadStream::readRect() {
Common::Rect r;
r.left = readSint32LE();
r.top = readSint32LE();
r.right = readSint32LE();
r.bottom = readSint32LE();
return r;
}
Common::Point XRCReadStream::readPoint() {
uint32 x = readUint32LE();
uint32 y = readUint32LE();
return Common::Point(x, y);
}
bool XRCReadStream::readBool() {
uint32 b = readUint32LE();
return b != 0;
}
bool XRCReadStream::isDataLeft() {
return pos() < size();
}
Common::Path XRCReadStream::getArchiveName() const {
return _archiveName;
}
Resources::Object *XRCReader::importTree(XARCArchive *archive) {
// Find the XRC file
Common::ArchiveMemberList members;
archive->listMatchingMembers(members, "*.xrc");
if (members.size() == 0) {
error("No resource tree in archive '%s'", archive->getFilename().toString(Common::Path::kNativeSeparator).c_str());
}
if (members.size() > 1) {
error("Too many resource scripts in archive '%s'", archive->getFilename().toString(Common::Path::kNativeSeparator).c_str());
}
// Open the XRC file
Common::SeekableReadStream *stream = archive->createReadStreamForMember(members.front()->getPathInArchive());
XRCReadStream *xrcStream = new XRCReadStream(archive->getFilename(), stream);
// Import the resource tree
Resources::Object *root = importResource(xrcStream, nullptr);
delete xrcStream;
return root;
}
Resources::Object *XRCReader::importResource(XRCReadStream *stream, Resources::Object *parent) {
Resources::Object *resource = createResource(stream, parent);
importResourceData(stream, resource);
importResourceChildren(stream, resource);
// Resource lifecycle update
resource->onPostRead();
return resource;
}
Resources::Object *XRCReader::createResource(XRCReadStream *stream, Resources::Object *parent) {
// Read the resource type and subtype
Resources::Type type = stream->readResourceType();
byte subType = stream->readByte();
// Read the resource properties
uint16 index = stream->readUint16LE();
Common::String name = stream->readString();
// Create a new resource
Resources::Object *resource;
switch (type.get()) {
case Resources::Type::kRoot:
resource = new Resources::Root(parent, subType, index, name);
break;
case Resources::Type::kLevel:
resource = new Resources::Level(parent, subType, index, name);
break;
case Resources::Type::kLocation:
resource = new Resources::Location(parent, subType, index, name);
break;
case Resources::Type::kLayer:
resource = Resources::Layer::construct(parent, subType, index, name);
break;
case Resources::Type::kCamera:
resource = new Resources::Camera(parent, subType, index, name);
break;
case Resources::Type::kFloor:
resource = new Resources::Floor(parent, subType, index, name);
break;
case Resources::Type::kFloorFace:
resource = new Resources::FloorFace(parent, subType, index, name);
break;
case Resources::Type::kItem:
resource = Resources::Item::construct(parent, subType, index, name);
break;
case Resources::Type::kScript:
resource = new Resources::Script(parent, subType, index, name);
break;
case Resources::Type::kAnimHierarchy:
resource = new Resources::AnimHierarchy(parent, subType, index, name);
break;
case Resources::Type::kAnim:
resource = Resources::Anim::construct(parent, subType, index, name);
break;
case Resources::Type::kDirection:
resource = new Resources::Direction(parent, subType, index, name);
break;
case Resources::Type::kImage:
resource = Resources::Image::construct(parent, subType, index, name);
break;
case Resources::Type::kAnimScript:
resource = new Resources::AnimScript(parent, subType, index, name);
break;
case Resources::Type::kAnimScriptItem:
resource = new Resources::AnimScriptItem(parent, subType, index, name);
break;
case Resources::Type::kSoundItem:
resource = new Resources::Sound(parent, subType, index, name);
break;
case Resources::Type::kPath:
resource = Resources::Path::construct(parent, subType, index, name);
break;
case Resources::Type::kFloorField:
resource = new Resources::FloorField(parent, subType, index, name);
break;
case Resources::Type::kBookmark:
resource = new Resources::Bookmark(parent, subType, index, name);
break;
case Resources::Type::kKnowledgeSet:
resource = new Resources::KnowledgeSet(parent, subType, index, name);
break;
case Resources::Type::kKnowledge:
resource = new Resources::Knowledge(parent, subType, index, name);
break;
case Resources::Type::kCommand:
resource = new Resources::Command(parent, subType, index, name);
break;
case Resources::Type::kPATTable:
resource = new Resources::PATTable(parent, subType, index, name);
break;
case Resources::Type::kContainer:
resource = new Resources::Container(parent, subType, index, name);
break;
case Resources::Type::kDialog:
resource = new Resources::Dialog(parent, subType, index, name);
break;
case Resources::Type::kSpeech:
resource = new Resources::Speech(parent, subType, index, name);
break;
case Resources::Type::kLight:
resource = new Resources::Light(parent, subType, index, name);
break;
case Resources::Type::kBonesMesh:
resource = new Resources::BonesMesh(parent, subType, index, name);
break;
case Resources::Type::kScroll:
resource = new Resources::Scroll(parent, subType, index, name);
break;
case Resources::Type::kFMV:
resource = new Resources::FMV(parent, subType, index, name);
break;
case Resources::Type::kLipSync:
resource = new Resources::LipSync(parent, subType, index, name);
break;
case Resources::Type::kAnimSoundTrigger:
resource = new Resources::AnimSoundTrigger(parent, subType, index, name);
break;
case Resources::Type::kString:
resource = new Resources::String(parent, subType, index, name);
break;
case Resources::Type::kTextureSet:
resource = new Resources::TextureSet(parent, subType, index, name);
break;
default:
resource = new Resources::UnimplementedResource(parent, type, subType, index, name);
break;
}
return resource;
}
void XRCReader::importResourceData(XRCReadStream *stream, Resources::Object *resource) {
// Read the data length
uint32 dataLength = stream->readUint32LE();
// Read the resource type specific data using a memory stream
if (dataLength > 0) {
XRCReadStream *xrcDataStream = new XRCReadStream(stream->getArchiveName(), stream->readStream(dataLength));
resource->readData(xrcDataStream);
if (xrcDataStream->isDataLeft()) {
warning("Not all XRC data was read. Type %s, subtype %d, name %s",
resource->getType().getName(), resource->getSubType(), resource->getName().c_str());
}
if (xrcDataStream->eos()) {
warning("Too much XRC data was read. Type %s, subtype %d, name %s",
resource->getType().getName(), resource->getSubType(), resource->getName().c_str());
}
delete xrcDataStream;
}
}
void XRCReader::importResourceChildren(XRCReadStream *stream, Resources::Object *resource) {
// Get the number of children
uint16 numChildren = stream->readUint16LE();
// Read more unknown data
uint16 unknown3 = stream->readUint16LE();
if (unknown3 != 0) {
warning("Stark::XRCReader: \"%s\" has unknown3=0x%04X with unknown meaning", resource->getName().c_str(), unknown3);
}
// Read the children resources
for (int i = 0; i < numChildren; i++) {
Resources::Object *child = importResource(stream, resource);
// Add child to parent
resource->addChild(child);
}
}
} // End of namespace Formats
} // End of namespace Stark

View File

@@ -0,0 +1,86 @@
/* 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/>.
*
*/
#ifndef STARK_XRC_READER_H
#define STARK_XRC_READER_H
#include "common/array.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/substream.h"
#include "common/types.h"
#include "math/vector3d.h"
#include "math/vector4d.h"
#include "engines/stark/resources/object.h"
#include "engines/stark/resourcereference.h"
namespace Stark {
namespace Formats {
class XARCArchive;
/**
* A read stream with helper functions to read usual XRC data types
*/
class XRCReadStream : public Common::SeekableSubReadStream {
public:
XRCReadStream(const Common::Path &archiveName, Common::SeekableReadStream *parentStream, DisposeAfterUse::Flag disposeParentStream = DisposeAfterUse::YES);
virtual ~XRCReadStream();
/** Obtain the file name of the archive containing the XRC tree */
Common::Path getArchiveName() const;
Common::String readString();
Resources::Type readResourceType();
ResourceReference readResourceReference();
Math::Vector3d readVector3();
Common::Rect readRect();
Common::Point readPoint();
bool readBool();
bool isDataLeft();
private:
Common::Path _archiveName;
};
/**
* An XRC stream parser, used to build resource trees.
*/
class XRCReader {
public:
/**
* Build a resource tree from a stream
*/
static Resources::Object *importTree(XARCArchive *archive);
protected:
static Resources::Object *importResource(XRCReadStream *stream, Resources::Object *parent);
static Resources::Object *createResource(XRCReadStream *stream, Resources::Object *parent);
static void importResourceChildren(XRCReadStream *stream, Resources::Object *resource);
static void importResourceData(XRCReadStream* stream, Resources::Object* resource);
};
} // End of namespace Formats
} // End of namespace Stark
#endif // STARK_XRC_READER_H

View File

@@ -0,0 +1,74 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_BITMAP_H
#define STARK_GFX_BITMAP_H
#include "common/hash-str.h"
namespace Graphics {
struct PixelFormat;
struct Surface;
}
namespace Stark {
namespace Gfx {
/**
* An abstract bitmap
*/
class Bitmap {
public:
Bitmap() : _width(0), _height(0) {}
virtual ~Bitmap() {}
enum SamplingFilter {
kNearest,
kLinear
};
/** Make the texture active */
virtual void bind() const = 0;
/** Define or update the texture pixel data */
virtual void update(const Graphics::Surface *surface, const byte *palette = nullptr) = 0;
/** Set the filter used when sampling the texture */
virtual void setSamplingFilter(SamplingFilter filter) = 0;
/** Get the most ideal pixel format for uploading to a texture */
virtual Graphics::PixelFormat getBestPixelFormat() const = 0;
/** Get the texture width */
uint32 width() const { return _width; }
/** Get the texture height */
uint32 height() const { return _height; }
protected:
uint32 _width;
uint32 _height;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_BITMAP_H

50
engines/stark/gfx/color.h Normal file
View File

@@ -0,0 +1,50 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_COLOR_H
#define STARK_GFX_COLOR_H
#include "common/scummsys.h"
namespace Stark {
namespace Gfx {
struct Color {
uint8 r;
uint8 g;
uint8 b;
uint8 a;
Color(uint8 red, uint8 green, uint8 blue, uint8 alpha = 0xFF) :
r(red), g(green), b(blue), a(alpha) {}
bool operator==(const Color &color) const {
return r == color.r &&
g == color.g &&
b == color.b &&
a == color.a;
}
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_COLOR_H

View File

@@ -0,0 +1,168 @@
/* 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/gfx/driver.h"
#include "engines/stark/gfx/opengl.h"
#include "engines/stark/gfx/opengls.h"
#include "engines/stark/gfx/tinygl.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/settings.h"
#include "common/config-manager.h"
#include "graphics/renderer.h"
#include "graphics/surface.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/context.h"
#endif
#include "gui/error.h"
#include "engines/util.h"
namespace Stark {
namespace Gfx {
Driver *Driver::create() {
Common::String rendererConfig = ConfMan.get("renderer");
Graphics::RendererType desiredRendererType = Graphics::Renderer::parseTypeCode(rendererConfig);
Graphics::RendererType matchingRendererType = Graphics::Renderer::getBestMatchingAvailableType(desiredRendererType,
#if defined(USE_OPENGL_GAME)
Graphics::kRendererTypeOpenGL |
#endif
#if defined(USE_OPENGL_SHADERS)
Graphics::kRendererTypeOpenGLShaders |
#endif
#if defined(USE_TINYGL)
Graphics::kRendererTypeTinyGL |
#endif
0);
bool softRenderer = matchingRendererType == Graphics::kRendererTypeTinyGL;
if (!softRenderer) {
initGraphics3d(kOriginalWidth, kOriginalHeight);
} else {
initGraphics(kOriginalWidth, kOriginalHeight, nullptr);
}
#if defined(USE_OPENGL_SHADERS)
if (matchingRendererType == Graphics::kRendererTypeOpenGLShaders) {
return new OpenGLSDriver();
}
#endif
#if defined(USE_OPENGL_GAME)
if (matchingRendererType == Graphics::kRendererTypeOpenGL) {
return new OpenGLDriver();
}
#endif
#if defined(USE_TINYGL)
if (matchingRendererType == Graphics::kRendererTypeTinyGL) {
return new TinyGLDriver();
}
#endif
/* We should never end up here, getBestMatchingRendererType would have failed before */
error("Unable to create a renderer");
}
const Graphics::PixelFormat Driver::getRGBAPixelFormat() {
return Graphics::PixelFormat::createFormatRGBA32();
}
bool Driver::computeScreenViewport() {
int32 screenWidth = g_system->getWidth();
int32 screenHeight = g_system->getHeight();
Common::Rect viewport;
if (g_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection)) {
// Aspect ratio correction
int32 viewportWidth = MIN<int32>(screenWidth, screenHeight * kOriginalWidth / kOriginalHeight);
int32 viewportHeight = MIN<int32>(screenHeight, screenWidth * kOriginalHeight / kOriginalWidth);
viewport = Common::Rect(viewportWidth, viewportHeight);
// Pillarboxing
viewport.translate((screenWidth - viewportWidth) / 2,
(screenHeight - viewportHeight) / 2);
} else {
// Aspect ratio correction disabled, just stretch
viewport = Common::Rect(screenWidth, screenHeight);
}
if (viewport == _screenViewport) {
return false;
}
_screenViewport = viewport;
return true;
}
Common::Rect Driver::gameViewport() const {
Common::Rect game = Common::Rect(_screenViewport.width(), _screenViewport.height() * kGameViewportHeight / kOriginalHeight);
game.translate(_screenViewport.left, _screenViewport.top + _screenViewport.height() * kTopBorderHeight / kOriginalHeight);
return game;
}
Common::Point Driver::convertCoordinateCurrentToOriginal(const Common::Point &point) const {
// Most of the engine expects 640x480 coordinates
Common::Point scaledPosition = point;
scaledPosition.x -= _screenViewport.left;
scaledPosition.y -= _screenViewport.top;
scaledPosition.x = CLIP<int16>(scaledPosition.x, 0, _screenViewport.width());
scaledPosition.y = CLIP<int16>(scaledPosition.y, 0, _screenViewport.height());
scaledPosition.x *= kOriginalWidth / (float)_screenViewport.width();
scaledPosition.y *= kOriginalHeight / (float)_screenViewport.height();
return scaledPosition;
}
uint Driver::scaleWidthOriginalToCurrent(uint width) const {
return _screenViewport.width() * width / kOriginalWidth;
}
uint Driver::scaleHeightOriginalToCurrent(uint height) const {
return _screenViewport.height() * height / kOriginalHeight;
}
uint Driver::scaleWidthCurrentToOriginal(uint width) const {
return kOriginalWidth * width / _screenViewport.width();
}
uint Driver::scaleHeightCurrentToOriginal(uint height) const {
return kOriginalHeight * height / _screenViewport.height();
}
void Driver::flipVertical(Graphics::Surface *s) {
for (int y = 0; y < s->h / 2; ++y) {
// Flip the lines
byte *line1P = (byte *)s->getBasePtr(0, y);
byte *line2P = (byte *)s->getBasePtr(0, s->h - y - 1);
for (int x = 0; x < s->pitch; ++x)
SWAP(line1P[x], line2P[x]);
}
}
bool Driver::isPosInScreenBounds(const Common::Point &point) const {
return _screenViewport.contains(point);
}
} // End of namespace Gfx
} // End of namespace Stark

165
engines/stark/gfx/driver.h Normal file
View File

@@ -0,0 +1,165 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_DRIVER_H
#define STARK_GFX_DRIVER_H
#include "common/rect.h"
#include "graphics/pixelformat.h"
namespace Graphics {
struct Surface;
}
namespace Stark {
class VisualActor;
class VisualProp;
namespace Gfx {
class SurfaceRenderer;
class FadeRenderer;
class Bitmap;
class Texture;
class Driver {
public:
static Driver *create();
virtual ~Driver() {}
virtual void init() = 0;
bool computeScreenViewport();
virtual void setScreenViewport(bool noScaling) = 0; // deprecated
virtual void setViewport(const Common::Rect &rect) = 0;
/** Get the screen viewport in actual resolution */
Common::Rect getScreenViewport() { return _screenViewport; }
Common::Rect gameViewport() const;
virtual void clearScreen() = 0;
virtual void flipBuffer() = 0;
/**
* Create a new texture for 3D
*
* The caller is responsible for freeing it.
*
*/
virtual Texture *createTexture() = 0;
/**
* Create a new bitmap for 2D
*
* The caller is responsible for freeing it.
*
*/
virtual Bitmap *createBitmap(const Graphics::Surface *surface = nullptr, const byte *palette = nullptr) = 0;
/**
* Create a new actor renderer
*
* The caller is responsible for freeing it.
*/
virtual VisualActor *createActorRenderer() = 0;
/**
* Create a new prop renderer
*
* The caller is responsible for freeing it.
*/
virtual VisualProp *createPropRenderer() = 0;
/**
* Create a new surface renderer
*
* The caller is responsible for freeing it.
*/
virtual SurfaceRenderer *createSurfaceRenderer() = 0;
/**
* Create a new fade renderer
*
* The caller is responsible for freeing it.
*/
virtual FadeRenderer *createFadeRenderer() = 0;
/** Checks if a screenpoint coord is within window bounds */
bool isPosInScreenBounds(const Common::Point &point) const;
/** Convert a coordinate from current to original resolution */
Common::Point convertCoordinateCurrentToOriginal(const Common::Point &point) const;
/** Scale a width value from original resolution to current resolution */
uint scaleWidthOriginalToCurrent(uint width) const;
/** Scale a height value from original resolution to current resolution */
uint scaleHeightOriginalToCurrent(uint height) const;
/** Scale a width value from current resolution to original resolution */
uint scaleWidthCurrentToOriginal(uint width) const;
/** Scale a height value from current resolution to original resolution */
uint scaleHeightCurrentToOriginal(uint width) const;
/**
* Textures are expected to be in the RGBA byte order
*
* That is to say bitmaps sent to OpenGL need to have the following layout:
* R G B A R G B A, ...
*
* This method can be used to retrieve what that means in terms
* of pixel format according to the current platform's endianness.
*/
static const Graphics::PixelFormat getRGBAPixelFormat();
/** Grab a screenshot of the currently active viewport as defined by setViewport */
virtual Graphics::Surface *getViewportScreenshot() const = 0;
virtual void set3DMode() = 0;
virtual bool computeLightsEnabled() = 0;
virtual bool supportsModdedAssets() const { return true; }
static const int32 kOriginalWidth = 640;
static const int32 kOriginalHeight = 480;
static const int32 kTopBorderHeight = 36;
static const int32 kGameViewportHeight = 365;
static const int32 kBottomBorderHeight = 79;
static const int32 kGameViewportWidth = 640;
protected:
static void flipVertical(Graphics::Surface *s);
Common::Rect _screenViewport;
bool _computeLights;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_DRIVER_H

View File

@@ -0,0 +1,45 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_FADE_RENDERER_H
#define STARK_GFX_FADE_RENDERER_H
namespace Stark {
namespace Gfx {
/**
* A renderer to draw fade screen to the current viewport
*/
class FadeRenderer {
public:
virtual ~FadeRenderer() { }
/**
* Draw the fade screen at the provided fade level
*/
virtual void render(float fadeLevel) = 0;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_FADE_RENDERER_H

View File

@@ -0,0 +1,287 @@
/* 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/gfx/opengl.h"
#include "common/system.h"
#include "math/matrix4.h"
#if defined(USE_OPENGL_GAME)
#include "engines/stark/gfx/openglactor.h"
#include "engines/stark/gfx/openglbitmap.h"
#include "engines/stark/gfx/openglprop.h"
#include "engines/stark/gfx/openglsurface.h"
#include "engines/stark/gfx/openglfade.h"
#include "engines/stark/gfx/opengltexture.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#include "graphics/surface.h"
namespace Stark {
namespace Gfx {
OpenGLDriver::OpenGLDriver() {
_computeLights = true;
}
OpenGLDriver::~OpenGLDriver() {
}
void OpenGLDriver::init() {
computeScreenViewport();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_LIGHTING);
}
void OpenGLDriver::setScreenViewport(bool noScaling) {
if (noScaling) {
_viewport = Common::Rect(g_system->getWidth(), g_system->getHeight());
_unscaledViewport = _viewport;
} else {
_viewport = _screenViewport;
_unscaledViewport = Common::Rect(kOriginalWidth, kOriginalHeight);
}
glViewport(_viewport.left, _viewport.top, _viewport.width(), _viewport.height());
}
void OpenGLDriver::setViewport(const Common::Rect &rect) {
_viewport = Common::Rect(
_screenViewport.width() * rect.width() / kOriginalWidth,
_screenViewport.height() * rect.height() / kOriginalHeight
);
_viewport.translate(
_screenViewport.left + _screenViewport.width() * rect.left / kOriginalWidth,
_screenViewport.top + _screenViewport.height() * rect.top / kOriginalHeight
);
_unscaledViewport = rect;
glViewport(_viewport.left, g_system->getHeight() - _viewport.bottom, _viewport.width(), _viewport.height());
}
void OpenGLDriver::clearScreen() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
void OpenGLDriver::flipBuffer() {
g_system->updateScreen();
}
void OpenGLDriver::setupLights(const LightEntryArray &lights) {
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
Math::Matrix4 viewMatrix = StarkScene->getViewMatrix();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
for (uint i = 0; i < lights.size(); i++) {
const LightEntry *l = lights[i];
GLfloat ambientColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
GLfloat lightColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
GLfloat lightPos[] = { 0.0f, 0.0f, 0.0f, 1.0f };
GLfloat lightDir[] = { 0.0f, 0.0f, -1.0f };
GLfloat cutoff = 180.0f;
GLfloat spotExp = 0.0f;
GLfloat c_attenuation = 1.0f;
GLfloat l_attenuation = 0.0f;
GLfloat q_attenuation = 0.0f;
Math::Vector4d worldPosition;
worldPosition.x() = l->position.x();
worldPosition.y() = l->position.y();
worldPosition.z() = l->position.z();
worldPosition.w() = 1.0;
Math::Vector4d eyePosition = viewMatrix * worldPosition;
Math::Vector3d worldDirection = l->direction;
Math::Vector3d eyeDirection = viewMatrix.getRotation() * worldDirection;
eyeDirection.normalize();
switch (l->type) {
case LightEntry::kPoint:
lightColor[0] = (GLfloat)l->color.x();
lightColor[1] = (GLfloat)l->color.y();
lightColor[2] = (GLfloat)l->color.z();
lightPos[0] = (GLfloat)eyePosition.x();
lightPos[1] = (GLfloat)eyePosition.y();
lightPos[2] = (GLfloat)eyePosition.z();
break;
case LightEntry::kDirectional:
lightColor[0] = (GLfloat)l->color.x();
lightColor[1] = (GLfloat)l->color.y();
lightColor[2] = (GLfloat)l->color.z();
lightPos[0] = (GLfloat)-eyeDirection.x();
lightPos[1] = (GLfloat)-eyeDirection.y();
lightPos[2] = (GLfloat)-eyeDirection.z();
lightPos[3] = 0;
break;
case LightEntry::kSpot:
lightColor[0] = (GLfloat)l->color.x();
lightColor[1] = (GLfloat)l->color.y();
lightColor[2] = (GLfloat)l->color.z();
lightPos[0] = (GLfloat)eyePosition.x();
lightPos[1] = (GLfloat)eyePosition.y();
lightPos[2] = (GLfloat)eyePosition.z();
lightDir[0] = (GLfloat)eyeDirection.x();
lightDir[1] = (GLfloat)eyeDirection.y();
lightDir[2] = (GLfloat)eyeDirection.z();
cutoff = (l->outerConeAngle.getDegrees() + l->innerConeAngle.getDegrees()) / 2.26f;
break;
case LightEntry::kAmbient:
lightColor[0] = (GLfloat)l->color.x();
lightColor[1] = (GLfloat)l->color.y();
lightColor[2] = (GLfloat)l->color.z();
break;
default:
break;
}
glLightfv(GL_LIGHT0 + i, GL_AMBIENT, ambientColor);
glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, lightColor);
glLightfv(GL_LIGHT0 + i, GL_POSITION, lightPos);
glLightfv(GL_LIGHT0 + i, GL_SPOT_DIRECTION, lightDir);
glLightf(GL_LIGHT0 + i, GL_SPOT_EXPONENT, spotExp);
glLightf(GL_LIGHT0 + i, GL_SPOT_CUTOFF, cutoff);
glLightf(GL_LIGHT0 + i, GL_CONSTANT_ATTENUATION, c_attenuation);
glLightf(GL_LIGHT0 + i, GL_LINEAR_ATTENUATION, l_attenuation);
glLightf(GL_LIGHT0 + i, GL_QUADRATIC_ATTENUATION, q_attenuation);
glEnable(GL_LIGHT0 + i);
}
for (uint i = lights.size() - 1; i < maxLights; i++) {
// Make sure unused lights are disabled
glDisable(GL_LIGHT0 + i + 1);
}
}
Texture *OpenGLDriver::createTexture() {
return new OpenGlTexture();
}
Bitmap *OpenGLDriver::createBitmap(const Graphics::Surface *surface, const byte *palette) {
OpenGlBitmap *bitmap = new OpenGlBitmap();
if (surface) {
bitmap->update(surface, palette);
}
return bitmap;
}
VisualActor *OpenGLDriver::createActorRenderer() {
return new OpenGLActorRenderer(this);
}
VisualProp *OpenGLDriver::createPropRenderer() {
return new OpenGLPropRenderer(this);
}
SurfaceRenderer *OpenGLDriver::createSurfaceRenderer() {
return new OpenGLSurfaceRenderer(this);
}
FadeRenderer *OpenGLDriver::createFadeRenderer() {
return new OpenGLFadeRenderer(this);
}
void OpenGLDriver::start2DMode() {
// Enable alpha blending
glEnable(GL_BLEND);
//glBlendEquation(GL_FUNC_ADD); // It's the default
// This blend mode prevents color fringes due to filtering.
// It requires the textures to have their color values pre-multiplied
// with their alpha value. This is the "Premultiplied Alpha" technique.
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
if (!_computeLights)
glDisable(GL_LIGHTING);
}
void OpenGLDriver::end2DMode() {
// Disable alpha blending
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
}
void OpenGLDriver::set3DMode() {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Blending and stencil test are only used in rendering shadows
// They are manually enabled and disabled there
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glStencilFunc(GL_EQUAL, 0, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
if (!_computeLights)
glEnable(GL_LIGHTING);
}
bool OpenGLDriver::computeLightsEnabled() {
return _computeLights;
}
Common::Rect OpenGLDriver::getViewport() const {
return _viewport;
}
Common::Rect OpenGLDriver::getUnscaledViewport() const {
return _unscaledViewport;
}
Graphics::Surface *OpenGLDriver::getViewportScreenshot() const {
Graphics::Surface *s = new Graphics::Surface();
s->create(_viewport.width(), _viewport.height(), getRGBAPixelFormat());
glReadPixels(_viewport.left, g_system->getHeight() - _viewport.bottom, _viewport.width(), _viewport.height(),
GL_RGBA, GL_UNSIGNED_BYTE, s->getPixels());
flipVertical(s);
return s;
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)

View File

@@ -0,0 +1,79 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_H
#define STARK_GFX_OPENGL_H
#include "common/system.h"
#include "math/vector3d.h"
#if defined(USE_OPENGL_GAME)
#include "engines/stark/gfx/driver.h"
#include "engines/stark/gfx/renderentry.h"
#include "graphics/opengl/system_headers.h"
namespace Stark {
namespace Gfx {
class OpenGLDriver : public Driver {
public:
OpenGLDriver();
~OpenGLDriver();
void init() override;
void setScreenViewport(bool noScaling) override;
void setViewport(const Common::Rect &rect) override;
void clearScreen() override;
void flipBuffer() override;
Texture *createTexture() override;
Bitmap *createBitmap(const Graphics::Surface *surface = nullptr, const byte *palette = nullptr) override;
VisualActor *createActorRenderer() override;
VisualProp *createPropRenderer() override;
SurfaceRenderer *createSurfaceRenderer() override;
FadeRenderer *createFadeRenderer() override;
void start2DMode();
void end2DMode();
void set3DMode() override;
bool computeLightsEnabled() override;
Common::Rect getViewport() const;
Common::Rect getUnscaledViewport() const;
void setupLights(const LightEntryArray &lights);
Graphics::Surface *getViewportScreenshot() const override;
private:
Common::Rect _viewport;
Common::Rect _unscaledViewport;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)
#endif // STARK_GFX_OPENGL_H

View File

@@ -0,0 +1,489 @@
/* 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/gfx/openglactor.h"
#include "engines/stark/model/model.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/settings.h"
#include "engines/stark/gfx/texture.h"
#include "math/vector2d.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
OpenGLActorRenderer::OpenGLActorRenderer(OpenGLDriver *gfx) :
VisualActor(),
_gfx(gfx),
_faceVBO(nullptr) {
}
OpenGLActorRenderer::~OpenGLActorRenderer() {
clearVertices();
}
void OpenGLActorRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
if (_modelIsDirty) {
clearVertices();
uploadVertices();
_modelIsDirty = false;
}
// TODO: Move updates outside of the rendering code
_animHandler->animate(_time);
_model->updateBoundingBox();
bool drawShadow = false;
if (_castsShadow &&
StarkScene->shouldRenderShadows() &&
StarkSettings->getBoolSetting(Settings::kShadow)) {
drawShadow = true;
}
Math::Vector3d lightDirection;
_gfx->set3DMode();
if (!_gfx->computeLightsEnabled())
_gfx->setupLights(lights);
Math::Matrix4 model = getModelMatrix(position, direction);
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // OpenGL expects matrices transposed
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(modelViewMatrix.getData());
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // OpenGL expects matrices transposed
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projectionMatrix.getData());
Math::Matrix4 normalMatrix;
if (_gfx->computeLightsEnabled()) {
projectionMatrix.transpose();
modelViewMatrix.transpose();
normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
}
Math::Matrix4 mvp;
if (drawShadow) {
mvp = view * model;
mvp.transpose();
Math::Matrix4 modelInverse = model;
modelInverse.inverse();
lightDirection = getShadowLightDirection(lights, position, modelInverse.getRotation());
}
Common::Array<Face *> faces = _model->getFaces();
Common::Array<Material *> mats = _model->getMaterials();
const Common::Array<BoneNode *> &bones = _model->getBones();
if (!_gfx->computeLightsEnabled()) {
glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
const Material *material = mats[(*face)->materialId];
Math::Vector3d color;
const Gfx::Texture *tex = resolveTexture(material);
if (tex) {
tex->bind();
glEnable(GL_TEXTURE_2D);
} else {
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}
auto vertexIndices = _faceEBO[*face];
auto numVertexIndices = (*face)->vertexIndices.size();
for (uint32 i = 0; i < numVertexIndices; i++) {
if (tex) {
if (_gfx->computeLightsEnabled())
color = Math::Vector3d(1.0f, 1.0f, 1.0f);
else
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
} else {
if (_gfx->computeLightsEnabled())
color = Math::Vector3d(material->r, material->g, material->b);
else
glColor4f(material->r, material->g, material->b, 1.0f);
}
uint32 index = vertexIndices[i];
auto vertex = _faceVBO[index];
uint32 bone1 = vertex.bone1;
uint32 bone2 = vertex.bone2;
Math::Vector3d position1 = Math::Vector3d(vertex.pos1x, vertex.pos1y, vertex.pos1z);
Math::Vector3d position2 = Math::Vector3d(vertex.pos2x, vertex.pos2y, vertex.pos2z);
Math::Vector3d bone1Position = Math::Vector3d(bones[bone1]->_animPos.x(),
bones[bone1]->_animPos.y(),
bones[bone1]->_animPos.z());
Math::Vector3d bone2Position = Math::Vector3d(bones[bone2]->_animPos.x(),
bones[bone2]->_animPos.y(),
bones[bone2]->_animPos.z());
Math::Quaternion bone1Rotation = Math::Quaternion(bones[bone1]->_animRot.x(),
bones[bone1]->_animRot.y(),
bones[bone1]->_animRot.z(),
bones[bone1]->_animRot.w());
Math::Quaternion bone2Rotation = Math::Quaternion(bones[bone2]->_animRot.x(),
bones[bone2]->_animRot.y(),
bones[bone2]->_animRot.z(),
bones[bone2]->_animRot.w());
float boneWeight = vertex.boneWeight;
Math::Vector3d normal = Math::Vector3d(vertex.normalx, vertex.normaly, vertex.normalz);
// Compute the vertex position in eye-space
bone1Rotation.transform(position1);
position1 += bone1Position;
bone2Rotation.transform(position2);
position2 += bone2Position;
Math::Vector3d modelPosition = Math::Vector3d::interpolate(position2, position1, boneWeight);
vertex.x = modelPosition.x();
vertex.y = modelPosition.y();
vertex.z = modelPosition.z();
Math::Vector4d modelEyePosition;
if (_gfx->computeLightsEnabled()) {
modelEyePosition = modelViewMatrix * Math::Vector4d(modelPosition.x(),
modelPosition.y(),
modelPosition.z(),
1.0);
}
// Compute the vertex normal in eye-space
Math::Vector3d n1 = normal;
bone1Rotation.transform(n1);
Math::Vector3d n2 = normal;
bone2Rotation.transform(n2);
Math::Vector3d modelNormal = Math::Vector3d(Math::Vector3d::interpolate(n2, n1, boneWeight)).getNormalized();
vertex.nx = modelNormal.x();
vertex.ny = modelNormal.y();
vertex.nz = modelNormal.z();
Math::Vector3d modelEyeNormal;
if (_gfx->computeLightsEnabled()) {
modelEyeNormal = normalMatrix.getRotation() * modelNormal;
modelEyeNormal.normalize();
}
if (drawShadow) {
Math::Vector3d shadowPosition = modelPosition + lightDirection * (-modelPosition.y() / lightDirection.y());
vertex.sx = shadowPosition.x();
vertex.sy = 0.0f;
vertex.sz = shadowPosition.z();
}
if (_gfx->computeLightsEnabled()) {
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
Math::Vector3d lightColor = ambient->color;
for (uint li = 0; li < lights.size() - 1; li++) {
const LightEntry *l = lights[li + 1];
switch (l->type) {
case LightEntry::kPoint: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
vertexToLight.normalize();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, vertexToLight));
lightColor += l->color * attn * incidence;
break;
}
case LightEntry::kDirectional: {
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, -l->eyeDirection));
lightColor += (l->color * incidence);
break;
}
case LightEntry::kSpot: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
vertexToLight.normalize();
float incidence = MAX(0.0f, modelEyeNormal.dotProduct(vertexToLight));
float cosAngle = MAX(0.0f, vertexToLight.dotProduct(-l->eyeDirection));
float cone = CLIP((cosAngle - l->innerConeAngle.getCosine()) / MAX(0.001f, l->outerConeAngle.getCosine() - l->innerConeAngle.getCosine()), 0.0f, 1.0f);
lightColor += l->color * attn * incidence * cone;
break;
}
default:
break;
}
}
lightColor.x() = CLIP(lightColor.x(), 0.0f, 1.0f);
lightColor.y() = CLIP(lightColor.y(), 0.0f, 1.0f);
lightColor.z() = CLIP(lightColor.z(), 0.0f, 1.0f);
color = color * lightColor;
vertex.r = color.x();
vertex.g = color.y();
vertex.b = color.z();
vertex.a = 1.0f; /* needed for compatibility with OpenGL ES 1.x */
}
_faceVBO[index] = vertex;
}
glEnableClientState(GL_VERTEX_ARRAY);
if (_gfx->computeLightsEnabled())
glEnableClientState(GL_COLOR_ARRAY);
if (tex)
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].x);
if (tex)
glTexCoordPointer(2, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].texS);
glNormalPointer(GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].nx);
if (_gfx->computeLightsEnabled())
glColorPointer(4, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].r);
glDrawElements(GL_TRIANGLES, numVertexIndices, GL_UNSIGNED_INT, vertexIndices);
glDisableClientState(GL_VERTEX_ARRAY);
if (_gfx->computeLightsEnabled())
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
}
if (!_gfx->computeLightsEnabled())
glDisable(GL_COLOR_MATERIAL);
if (drawShadow) {
glEnable(GL_BLEND);
glEnable(GL_STENCIL_TEST);
glDisable(GL_TEXTURE_2D);
if (!_gfx->computeLightsEnabled())
glDisable(GL_LIGHTING);
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].sx);
glDrawElements(GL_TRIANGLES, (*face)->vertexIndices.size(), GL_UNSIGNED_INT, _faceEBO[*face]);
glDisableClientState(GL_VERTEX_ARRAY);
}
if (!_gfx->computeLightsEnabled())
glEnable(GL_LIGHTING);
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
}
}
void OpenGLActorRenderer::clearVertices() {
delete[] _faceVBO;
_faceVBO = nullptr;
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
delete[] it->_value;
}
_faceEBO.clear();
}
void OpenGLActorRenderer::uploadVertices() {
_faceVBO = createModelVBO(_model);
Common::Array<Face *> faces = _model->getFaces();
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
_faceEBO[*face] = createFaceEBO(*face);
}
}
ActorVertex *OpenGLActorRenderer::createModelVBO(const Model *model) {
const Common::Array<VertNode *> &modelVertices = model->getVertices();
auto vertices = new ActorVertex[modelVertices.size()];
// Build a vertex array
int i = 0;
for (Common::Array<VertNode *>::const_iterator tri = modelVertices.begin(); tri != modelVertices.end(); ++tri, i++) {
vertices[i].pos1x = (*tri)->_pos1.x();
vertices[i].pos1y = (*tri)->_pos1.y();
vertices[i].pos1z = (*tri)->_pos1.z();
vertices[i].pos2x = (*tri)->_pos2.x();
vertices[i].pos2y = (*tri)->_pos2.y();
vertices[i].pos2z = (*tri)->_pos2.z();
vertices[i].bone1 = (*tri)->_bone1;
vertices[i].bone2 = (*tri)->_bone2;
vertices[i].boneWeight = (*tri)->_boneWeight;
vertices[i].normalx = (*tri)->_normal.x();
vertices[i].normaly = (*tri)->_normal.y();
vertices[i].normalz = (*tri)->_normal.z();
vertices[i].texS = -(*tri)->_texS;
vertices[i].texT = (*tri)->_texT;
}
return vertices;
}
uint32 *OpenGLActorRenderer::createFaceEBO(const Face *face) {
auto indices = new uint32[face->vertexIndices.size()];
for (uint32 index = 0; index < face->vertexIndices.size(); index++) {
indices[index] = face->vertexIndices[index];
}
return indices;
}
Math::Vector3d OpenGLActorRenderer::getShadowLightDirection(const LightEntryArray &lights, const Math::Vector3d &actorPosition,
Math::Matrix3 worldToModelRot) {
Math::Vector3d sumDirection;
bool hasLight = false;
// Compute the contribution from each lights
// The ambient light is skipped intentionally
for (uint i = 1; i < lights.size(); ++i) {
LightEntry *light = lights[i];
bool contributes = false;
Math::Vector3d lightDirection;
switch (light->type) {
case LightEntry::kPoint:
contributes = getPointLightContribution(light, actorPosition, lightDirection);
break;
case LightEntry::kDirectional:
contributes = getDirectionalLightContribution(light, lightDirection);
break;
case LightEntry::kSpot:
contributes = getSpotLightContribution(light, actorPosition, lightDirection);
break;
case LightEntry::kAmbient:
default:
break;
}
if (contributes) {
sumDirection += lightDirection;
hasLight = true;
}
}
if (hasLight) {
// Clip the horizontal length
Math::Vector2d horizontalProjection(sumDirection.x(), sumDirection.y());
float shadowLength = MIN(horizontalProjection.getMagnitude(), StarkScene->getMaxShadowLength());
horizontalProjection.normalize();
horizontalProjection *= shadowLength;
sumDirection.x() = horizontalProjection.getX();
sumDirection.y() = horizontalProjection.getY();
sumDirection.z() = -1;
} else {
// Cast from above by default
sumDirection.x() = 0;
sumDirection.y() = 0;
sumDirection.z() = -1;
}
//Transform the direction to the model space and pass to the shader
return worldToModelRot * sumDirection;
}
bool OpenGLActorRenderer::getPointLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction, float weight) {
float distance = light->position.getDistanceTo(actorPosition);
if (distance > light->falloffFar) {
return false;
}
float factor;
if (distance > light->falloffNear) {
if (light->falloffFar - light->falloffNear > 1) {
factor = 1 - (distance - light->falloffNear) / (light->falloffFar - light->falloffNear);
} else {
factor = 0;
}
} else {
factor = 1;
}
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
if (factor <= 0 || brightness <= 0) {
return false;
}
direction = actorPosition - light->position;
direction.normalize();
direction *= factor * brightness * weight;
return true;
}
bool OpenGLActorRenderer::getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction) {
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
if (brightness <= 0) {
return false;
}
direction = light->direction;
direction.normalize();
direction *= brightness;
return true;
}
bool OpenGLActorRenderer::getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction) {
Math::Vector3d lightToActor = actorPosition - light->position;
lightToActor.normalize();
float cosAngle = MAX(0.0f, lightToActor.dotProduct(light->direction));
float cone = (cosAngle - light->innerConeAngle.getCosine()) /
MAX(0.001f, light->outerConeAngle.getCosine() - light->innerConeAngle.getCosine());
cone = CLIP(cone, 0.0f, 1.0f);
if (cone <= 0) {
return false;
}
return getPointLightContribution(light, actorPosition, direction, cone);
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)

View File

@@ -0,0 +1,106 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_ACTOR_H
#define STARK_GFX_OPENGL_ACTOR_H
#include "engines/stark/gfx/renderentry.h"
#include "engines/stark/visual/actor.h"
#include "engines/stark/gfx/opengl.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
class OpenGLDriver;
struct _ActorVertex {
float pos1x;
float pos1y;
float pos1z;
float pos2x;
float pos2y;
float pos2z;
uint32 bone1;
uint32 bone2;
float boneWeight;
float normalx;
float normaly;
float normalz;
float texS;
float texT;
float x;
float y;
float z;
float nx;
float ny;
float nz;
float sx;
float sy;
float sz;
float r;
float g;
float b;
float a;
};
typedef _ActorVertex ActorVertex;
class OpenGLActorRenderer : public VisualActor {
public:
OpenGLActorRenderer(OpenGLDriver *gfx);
virtual ~OpenGLActorRenderer();
void render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<Face *, uint32 *> FaceBufferMap;
OpenGLDriver *_gfx;
ActorVertex *_faceVBO;
FaceBufferMap _faceEBO;
void clearVertices();
void uploadVertices();
ActorVertex *createModelVBO(const Model *model);
uint32 *createFaceEBO(const Face *face);
void setLightArrayUniform(const LightEntryArray &lights);
Math::Vector3d getShadowLightDirection(const LightEntryArray &lights, const Math::Vector3d &actorPosition, Math::Matrix3 worldToModelRot);
bool getPointLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction, float weight = 1.0f);
bool getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction);
bool getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition, Math::Vector3d &direction);
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)
#endif // STARK_GFX_OPENGL_ACTOR_H

View File

@@ -0,0 +1,122 @@
/* 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/gfx/openglbitmap.h"
#include "engines/stark/gfx/driver.h"
#include "graphics/surface.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/context.h"
namespace Stark {
namespace Gfx {
OpenGlBitmap::OpenGlBitmap() :
Bitmap(),
_id(0) {
glGenTextures(1, &_id);
bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
OpenGlBitmap::~OpenGlBitmap() {
glDeleteTextures(1, &_id);
}
void OpenGlBitmap::bind() const {
glBindTexture(GL_TEXTURE_2D, _id);
}
void OpenGlBitmap::update(const Graphics::Surface *surface, const byte *palette) {
bind();
const Graphics::Surface *rgbaSurface = surface;
if (surface->format != Driver::getRGBAPixelFormat()) {
// Convert the surface to texture format
rgbaSurface = surface->convertTo(Driver::getRGBAPixelFormat(), palette);
}
_width = rgbaSurface->w;
_height = rgbaSurface->h;
GLfloat s, t;
if (!OpenGLContext.NPOTSupported) {
uint32 texWidth = Common::nextHigher2(rgbaSurface->w);
uint32 texHeight = Common::nextHigher2(rgbaSurface->h);
s = (GLfloat)_width / texWidth;
t = (GLfloat)_height / texHeight;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rgbaSurface->w, rgbaSurface->h,
GL_RGBA, GL_UNSIGNED_BYTE, rgbaSurface->getPixels());
} else {
s = t = 1.f;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rgbaSurface->w, rgbaSurface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgbaSurface->getPixels());
}
_texCoords[0*2+0] = 0.0f;
_texCoords[0*2+1] = 0.0f;
_texCoords[1*2+0] = s;
_texCoords[1*2+1] = 0.0f;
_texCoords[2*2+0] = 0.0f;
_texCoords[2*2+1] = t;
_texCoords[3*2+0] = s;
_texCoords[3*2+1] = t;
if (rgbaSurface != surface) {
const_cast<Graphics::Surface *>(rgbaSurface)->free();
delete rgbaSurface;
}
}
void OpenGlBitmap::setSamplingFilter(Bitmap::SamplingFilter filter) {
switch (filter) {
case kNearest:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
break;
case kLinear:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
break;
default:
warning("Unhandled sampling filter %d", filter);
}
}
Graphics::PixelFormat OpenGlBitmap::getBestPixelFormat() const {
return Driver::getRGBAPixelFormat();
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_BITMAP_H
#define STARK_GFX_OPENGL_BITMAP_H
#include "engines/stark/gfx/bitmap.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
namespace Stark {
namespace Gfx {
/**
* An OpenGL texture wrapper for 2D elements
*/
class OpenGlBitmap : public Bitmap {
public:
OpenGlBitmap();
virtual ~OpenGlBitmap();
// Bitmap API
void bind() const override;
void update(const Graphics::Surface *surface, const byte *palette = nullptr) override;
void setSamplingFilter(SamplingFilter filter) override;
Graphics::PixelFormat getBestPixelFormat() const override;
const GLfloat *getTexCoords() const { return _texCoords; }
protected:
GLuint _id;
GLfloat _texCoords[2*4];
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#endif // STARK_GFX_OPENGL_BITMAP_H

View File

@@ -0,0 +1,81 @@
/* 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/gfx/openglfade.h"
#include "engines/stark/gfx/opengl.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
static const GLfloat fadeVertices[] = {
// X Y
-1.0f, 1.0f,
1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
};
OpenGLFadeRenderer::OpenGLFadeRenderer(OpenGLDriver *gfx) :
FadeRenderer(),
_gfx(gfx) {
}
OpenGLFadeRenderer::~OpenGLFadeRenderer() {
}
void OpenGLFadeRenderer::render(float fadeLevel) {
_gfx->start2DMode();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(0.0f, 0.0f, 0.0f, 1.0f - fadeLevel);
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), &fadeVertices[0]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
_gfx->end2DMode();
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)

View File

@@ -0,0 +1,56 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_FADE_H
#define STARK_GFX_OPENGL_FADE_H
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_GAME)
#include "engines/stark/gfx/faderenderer.h"
namespace Stark {
namespace Gfx {
class OpenGLDriver;
/**
* A programmable pipeline OpenGL fade screen renderer
*/
class OpenGLFadeRenderer : public FadeRenderer {
public:
OpenGLFadeRenderer(OpenGLDriver *gfx);
~OpenGLFadeRenderer();
// FadeRenderer API
void render(float fadeLevel);
private:
OpenGLDriver *_gfx;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)
#endif // STARK_GFX_OPENGL_FADE_H

View File

@@ -0,0 +1,267 @@
/* 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/gfx/openglprop.h"
#include "engines/stark/gfx/texture.h"
#include "engines/stark/formats/biffmesh.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
OpenGLPropRenderer::OpenGLPropRenderer(OpenGLDriver *gfx) :
VisualProp(),
_gfx(gfx),
_faceVBO(nullptr),
_modelIsDirty(true) {
}
OpenGLPropRenderer::~OpenGLPropRenderer() {
clearVertices();
}
void OpenGLPropRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
if (_modelIsDirty) {
clearVertices();
uploadVertices();
_modelIsDirty = false;
}
_gfx->set3DMode();
if (!_gfx->computeLightsEnabled())
_gfx->setupLights(lights);
Math::Matrix4 model = getModelMatrix(position, direction);
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // OpenGL expects matrices transposed
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(modelViewMatrix.getData());
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // OpenGL expects matrices transposed
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projectionMatrix.getData());
Math::Matrix4 normalMatrix;
if (_gfx->computeLightsEnabled()) {
projectionMatrix.transpose();
modelViewMatrix.transpose();
normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
}
const Common::Array<Face> &faces = _model->getFaces();
const Common::Array<Material> &materials = _model->getMaterials();
if (!_gfx->computeLightsEnabled())
glEnable(GL_COLOR_MATERIAL);
for (Common::Array<Face>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
const Material &material = materials[face->materialId];
Math::Vector3d color;
const Gfx::Texture *tex = _texture->getTexture(material.texture);
if (tex) {
tex->bind();
glEnable(GL_TEXTURE_2D);
} else {
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}
auto vertexIndices = _faceEBO[face];
auto numVertexIndices = (face)->vertexIndices.size();
if (!_gfx->computeLightsEnabled()) {
if (material.doubleSided)
glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
else
glColorMaterial(GL_FRONT, GL_DIFFUSE);
}
for (uint32 i = 0; i < numVertexIndices; i++) {
uint32 index = vertexIndices[i];
auto vertex = _faceVBO[index];
if (tex) {
if (_gfx->computeLightsEnabled())
color = Math::Vector3d(1.0f, 1.0f, 1.0f);
else
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
if (material.doubleSided) {
vertex.texS = vertex.stexS;
vertex.texT = 1.0f - vertex.stexT;
} else {
vertex.texS = 1.0f - vertex.stexS;
vertex.texT = 1.0f - vertex.stexT;
}
} else {
if (_gfx->computeLightsEnabled())
color = Math::Vector3d(material.r, material.g, material.b);
else
glColor4f(material.r, material.g, material.b, 1.0f);
}
if (_gfx->computeLightsEnabled()) {
Math::Vector4d modelEyePosition = modelViewMatrix * Math::Vector4d(vertex.x, vertex.y, vertex.z, 1.0);
Math::Vector3d modelEyeNormal = normalMatrix.getRotation() * Math::Vector3d(vertex.nx, vertex.ny, vertex.nz);
modelEyeNormal.normalize();
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
Math::Vector3d lightColor = ambient->color;
for (uint li = 0; li < lights.size() - 1; li++) {
const LightEntry *l = lights[li + 1];
switch (l->type) {
case LightEntry::kPoint: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
vertexToLight.normalize();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, vertexToLight));
lightColor += l->color * attn * incidence;
break;
}
case LightEntry::kDirectional: {
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, -l->eyeDirection));
lightColor += (l->color * incidence);
break;
}
case LightEntry::kSpot: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
vertexToLight.normalize();
float incidence = MAX(0.0f, modelEyeNormal.dotProduct(vertexToLight));
float cosAngle = MAX(0.0f, vertexToLight.dotProduct(-l->eyeDirection));
float cone = CLIP((cosAngle - l->innerConeAngle.getCosine()) / MAX(0.001f, l->outerConeAngle.getCosine() - l->innerConeAngle.getCosine()), 0.0f, 1.0f);
lightColor += l->color * attn * incidence * cone;
break;
}
default:
break;
}
}
lightColor.x() = CLIP(lightColor.x(), 0.0f, 1.0f);
lightColor.y() = CLIP(lightColor.y(), 0.0f, 1.0f);
lightColor.z() = CLIP(lightColor.z(), 0.0f, 1.0f);
color = color * lightColor;
vertex.r = color.x();
vertex.g = color.y();
vertex.b = color.z();
vertex.a = 1.0f; /* needed for compatibility with OpenGL ES 1.x */
}
_faceVBO[index] = vertex;
}
glEnableClientState(GL_VERTEX_ARRAY);
if (_gfx->computeLightsEnabled())
glEnableClientState(GL_COLOR_ARRAY);
if (tex)
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(PropVertex), &_faceVBO[0].x);
if (tex)
glTexCoordPointer(2, GL_FLOAT, sizeof(PropVertex), &_faceVBO[0].texS);
glNormalPointer(GL_FLOAT, sizeof(PropVertex), &_faceVBO[0].nx);
if (_gfx->computeLightsEnabled())
glColorPointer(4, GL_FLOAT, sizeof(PropVertex), &_faceVBO[0].r);
glDrawElements(GL_TRIANGLES, face->vertexIndices.size(), GL_UNSIGNED_INT, vertexIndices);
glDisableClientState(GL_VERTEX_ARRAY);
if (_gfx->computeLightsEnabled())
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
}
if (!_gfx->computeLightsEnabled())
glDisable(GL_COLOR_MATERIAL);
}
void OpenGLPropRenderer::clearVertices() {
delete[] _faceVBO;
_faceVBO = nullptr;
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
delete[] it->_value;
}
_faceEBO.clear();
}
void OpenGLPropRenderer::uploadVertices() {
_faceVBO = createFaceVBO();
const Common::Array<Face> &faces = _model->getFaces();
for (Common::Array<Face>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
_faceEBO[face] = createFaceEBO(face);
}
}
PropVertex *OpenGLPropRenderer::createFaceVBO() {
const Common::Array<Formats::BiffMesh::Vertex> &modelVertices = _model->getVertices();
auto vertices = new PropVertex[modelVertices.size()];
// Build a vertex array
for (uint32 i = 0; i < modelVertices.size(); i++) {
vertices[i].x = modelVertices[i].position.x();
vertices[i].y = modelVertices[i].position.y();
vertices[i].z = modelVertices[i].position.z();
vertices[i].nx = modelVertices[i].normal.x();
vertices[i].ny = modelVertices[i].normal.y();
vertices[i].nz = modelVertices[i].normal.z();
vertices[i].stexS = modelVertices[i].texturePosition.x();
vertices[i].stexT = modelVertices[i].texturePosition.y();
}
return vertices;
}
uint32 *OpenGLPropRenderer::createFaceEBO(const Face *face) {
auto indices = new uint32[face->vertexIndices.size()];
for (uint32 index = 0; index < face->vertexIndices.size(); index++) {
indices[index] = face->vertexIndices[index];
}
return indices;
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)

View File

@@ -0,0 +1,87 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_RENDERED_H
#define STARK_GFX_OPENGL_RENDERED_H
#include "engines/stark/model/model.h"
#include "engines/stark/visual/prop.h"
#include "engines/stark/gfx/opengl.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
class Driver;
struct _PropVertex {
float x;
float y;
float z;
float nx;
float ny;
float nz;
float stexS;
float stexT;
float texS;
float texT;
float r;
float g;
float b;
float a;
};
typedef _PropVertex PropVertex;
class OpenGLPropRenderer : public VisualProp {
public:
explicit OpenGLPropRenderer(OpenGLDriver *gfx);
~OpenGLPropRenderer() override;
void render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<const Face *, uint32 *> FaceBufferMap;
OpenGLDriver *_gfx;
bool _modelIsDirty;
PropVertex *_faceVBO;
FaceBufferMap _faceEBO;
void clearVertices();
void uploadVertices();
PropVertex *createFaceVBO();
uint32 *createFaceEBO(const Face *face);
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_OPENGL_S_RENDERED_H
#endif // defined(USE_OPENGL_GAME)

View File

@@ -0,0 +1,250 @@
/* 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/gfx/opengls.h"
#include "common/system.h"
#include "math/matrix4.h"
#if defined(USE_OPENGL_SHADERS)
#include "engines/stark/gfx/openglsactor.h"
#include "engines/stark/gfx/openglbitmap.h"
#include "engines/stark/gfx/openglsprop.h"
#include "engines/stark/gfx/openglssurface.h"
#include "engines/stark/gfx/openglsfade.h"
#include "engines/stark/gfx/opengltexture.h"
#include "graphics/surface.h"
#include "graphics/opengl/shader.h"
namespace Stark {
namespace Gfx {
static const GLfloat surfaceVertices[] = {
// XS YT
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat fadeVertices[] = {
// XS YT
-1.0f, 1.0f,
1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
};
OpenGLSDriver::OpenGLSDriver() :
_surfaceShader(nullptr),
_surfaceFillShader(nullptr),
_actorShader(nullptr),
_fadeShader(nullptr),
_shadowShader(nullptr),
_surfaceVBO(0),
_fadeVBO(0) {
}
OpenGLSDriver::~OpenGLSDriver() {
OpenGL::Shader::freeBuffer(_surfaceVBO);
OpenGL::Shader::freeBuffer(_fadeVBO);
delete _surfaceFillShader;
delete _surfaceShader;
delete _actorShader;
delete _fadeShader;
delete _shadowShader;
}
void OpenGLSDriver::init() {
computeScreenViewport();
static const char* attributes[] = { "position", "texcoord", nullptr };
_surfaceShader = OpenGL::Shader::fromFiles("stark_surface", attributes);
_surfaceVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(surfaceVertices), surfaceVertices);
_surfaceShader->enableVertexAttribute("position", _surfaceVBO, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(float), 0);
_surfaceShader->enableVertexAttribute("texcoord", _surfaceVBO, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(float), 0);
static const char* fillAttributes[] = { "position", nullptr };
_surfaceFillShader = OpenGL::Shader::fromFiles("stark_surface_fill", fillAttributes);
_surfaceFillShader->enableVertexAttribute("position", _surfaceVBO, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(float), 0);
static const char* actorAttributes[] = { "position1", "position2", "bone1", "bone2", "boneWeight", "normal", "texcoord", nullptr };
_actorShader = OpenGL::Shader::fromFiles("stark_actor", actorAttributes);
static const char* shadowAttributes[] = { "position1", "position2", "bone1", "bone2", "boneWeight", nullptr };
_shadowShader = OpenGL::Shader::fromFiles("stark_shadow", shadowAttributes);
static const char* fadeAttributes[] = { "position", nullptr };
_fadeShader = OpenGL::Shader::fromFiles("stark_fade", fadeAttributes);
_fadeVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(fadeVertices), fadeVertices);
_fadeShader->enableVertexAttribute("position", _fadeVBO, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(float), 0);
}
void OpenGLSDriver::setScreenViewport(bool noScaling) {
if (noScaling) {
_viewport = Common::Rect(g_system->getWidth(), g_system->getHeight());
_unscaledViewport = _viewport;
} else {
_viewport = _screenViewport;
_unscaledViewport = Common::Rect(kOriginalWidth, kOriginalHeight);
}
glViewport(_viewport.left, _viewport.top, _viewport.width(), _viewport.height());
}
void OpenGLSDriver::setViewport(const Common::Rect &rect) {
_viewport = Common::Rect(
_screenViewport.width() * rect.width() / kOriginalWidth,
_screenViewport.height() * rect.height() / kOriginalHeight
);
_viewport.translate(
_screenViewport.left + _screenViewport.width() * rect.left / kOriginalWidth,
_screenViewport.top + _screenViewport.height() * rect.top / kOriginalHeight
);
_unscaledViewport = rect;
glViewport(_viewport.left, g_system->getHeight() - _viewport.bottom, _viewport.width(), _viewport.height());
}
void OpenGLSDriver::clearScreen() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
void OpenGLSDriver::flipBuffer() {
g_system->updateScreen();
}
Texture *OpenGLSDriver::createTexture() {
return new OpenGlTexture();
}
Bitmap *OpenGLSDriver::createBitmap(const Graphics::Surface *surface, const byte *palette) {
OpenGlBitmap *bitmap = new OpenGlBitmap();
if (surface) {
bitmap->update(surface, palette);
}
return bitmap;
}
VisualActor *OpenGLSDriver::createActorRenderer() {
return new OpenGLSActorRenderer(this);
}
VisualProp *OpenGLSDriver::createPropRenderer() {
return new OpenGLSPropRenderer(this);
}
SurfaceRenderer *OpenGLSDriver::createSurfaceRenderer() {
return new OpenGLSSurfaceRenderer(this);
}
FadeRenderer *OpenGLSDriver::createFadeRenderer() {
return new OpenGLSFadeRenderer(this);
}
void OpenGLSDriver::start2DMode() {
// Enable alpha blending
glEnable(GL_BLEND);
//glBlendEquation(GL_FUNC_ADD); // It's the default
// This blend mode prevents color fringes due to filtering.
// It requires the textures to have their color values pre-multiplied
// with their alpha value. This is the "Premultiplied Alpha" technique.
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
}
void OpenGLSDriver::end2DMode() {
// Disable alpha blending
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
}
void OpenGLSDriver::set3DMode() {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Blending and stencil test are only used in rendering shadows
// They are manually enabled and disabled there
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glStencilFunc(GL_EQUAL, 0, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
}
bool OpenGLSDriver::computeLightsEnabled() {
return false;
}
Common::Rect OpenGLSDriver::getViewport() const {
return _viewport;
}
Common::Rect OpenGLSDriver::getUnscaledViewport() const {
return _unscaledViewport;
}
OpenGL::Shader *OpenGLSDriver::createActorShaderInstance() {
return _actorShader->clone();
}
OpenGL::Shader *OpenGLSDriver::createSurfaceShaderInstance() {
return _surfaceShader->clone();
}
OpenGL::Shader *OpenGLSDriver::createSurfaceFillShaderInstance() {
return _surfaceFillShader->clone();
}
OpenGL::Shader *OpenGLSDriver::createFadeShaderInstance() {
return _fadeShader->clone();
}
OpenGL::Shader *OpenGLSDriver::createShadowShaderInstance() {
return _shadowShader->clone();
}
Graphics::Surface *OpenGLSDriver::getViewportScreenshot() const {
Graphics::Surface *s = new Graphics::Surface();
s->create(_viewport.width(), _viewport.height(), getRGBAPixelFormat());
glReadPixels(_viewport.left, g_system->getHeight() - _viewport.bottom, _viewport.width(), _viewport.height(),
GL_RGBA, GL_UNSIGNED_BYTE, s->getPixels());
flipVertical(s);
return s;
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,94 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGLS_H
#define STARK_GFX_OPENGLS_H
#include "common/system.h"
#if defined(USE_OPENGL_SHADERS)
#include "engines/stark/gfx/driver.h"
#include "graphics/opengl/system_headers.h"
namespace OpenGL {
class Shader;
}
namespace Stark {
namespace Gfx {
class OpenGLSDriver : public Driver {
public:
OpenGLSDriver();
~OpenGLSDriver();
void init() override;
void setScreenViewport(bool noScaling) override;
void setViewport(const Common::Rect &rect) override;
void clearScreen() override;
void flipBuffer() override;
Texture *createTexture() override;
Bitmap *createBitmap(const Graphics::Surface *surface = nullptr, const byte *palette = nullptr) override;
VisualActor *createActorRenderer() override;
VisualProp *createPropRenderer() override;
SurfaceRenderer *createSurfaceRenderer() override;
FadeRenderer *createFadeRenderer() override;
OpenGL::Shader *createActorShaderInstance();
OpenGL::Shader *createSurfaceShaderInstance();
OpenGL::Shader *createSurfaceFillShaderInstance();
OpenGL::Shader *createFadeShaderInstance();
OpenGL::Shader *createShadowShaderInstance();
void start2DMode();
void end2DMode();
void set3DMode() override;
bool computeLightsEnabled() override;
Common::Rect getViewport() const;
Common::Rect getUnscaledViewport() const;
Graphics::Surface *getViewportScreenshot() const override;
private:
Common::Rect _viewport;
Common::Rect _unscaledViewport;
OpenGL::Shader *_surfaceShader;
OpenGL::Shader *_surfaceFillShader;
OpenGL::Shader *_actorShader;
OpenGL::Shader *_fadeShader;
OpenGL::Shader *_shadowShader;
GLuint _surfaceVBO;
GLuint _fadeVBO;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)
#endif // STARK_GFX_OPENGLS_H

View File

@@ -0,0 +1,431 @@
/* 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/gfx/openglsactor.h"
#include "engines/stark/model/model.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/settings.h"
#include "engines/stark/gfx/opengls.h"
#include "engines/stark/gfx/texture.h"
#if defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/shader.h"
namespace Stark {
namespace Gfx {
OpenGLSActorRenderer::OpenGLSActorRenderer(OpenGLSDriver *gfx) :
VisualActor(),
_gfx(gfx),
_faceVBO(0) {
_shader = _gfx->createActorShaderInstance();
_shadowShader = _gfx->createShadowShaderInstance();
}
OpenGLSActorRenderer::~OpenGLSActorRenderer() {
clearVertices();
delete _shader;
delete _shadowShader;
}
void OpenGLSActorRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
if (_modelIsDirty) {
// Update the OpenGL Buffer Objects if required
clearVertices();
uploadVertices();
_modelIsDirty = false;
}
// TODO: Move updates outside of the rendering code
_animHandler->animate(_time);
_model->updateBoundingBox();
_gfx->set3DMode();
Math::Matrix4 model = getModelMatrix(position, direction);
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // OpenGL expects matrices transposed
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // OpenGL expects matrices transposed
Math::Matrix4 normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
_shader->enableVertexAttribute("position1", _faceVBO, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 0);
_shader->enableVertexAttribute("position2", _faceVBO, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 12);
_shader->enableVertexAttribute("bone1", _faceVBO, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 24);
_shader->enableVertexAttribute("bone2", _faceVBO, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 28);
_shader->enableVertexAttribute("boneWeight", _faceVBO, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 32);
_shader->enableVertexAttribute("normal", _faceVBO, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 36);
_shader->enableVertexAttribute("texcoord", _faceVBO, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 48);
_shader->use(true);
_shader->setUniform("modelViewMatrix", modelViewMatrix);
_shader->setUniform("projectionMatrix", projectionMatrix);
_shader->setUniform("normalMatrix", normalMatrix.getRotation());
setBoneRotationArrayUniform(_shader, "boneRotation");
setBonePositionArrayUniform(_shader, "bonePosition");
setLightArrayUniform(lights);
Common::Array<Face *> faces = _model->getFaces();
Common::Array<Material *> mats = _model->getMaterials();
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
// For each face draw its vertices from the VBO, indexed by the EBO
const Material *material = mats[(*face)->materialId];
const Gfx::Texture *tex = resolveTexture(material);
if (tex) {
tex->bind();
} else {
glBindTexture(GL_TEXTURE_2D, 0);
}
_shader->setUniform("textured", tex != nullptr);
_shader->setUniform("color", Math::Vector3d(material->r, material->g, material->b));
GLuint ebo = _faceEBO[*face];
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glDrawElements(GL_TRIANGLES, (*face)->vertexIndices.size(), GL_UNSIGNED_INT, 0);
}
_shader->unbind();
if (_castsShadow &&
StarkScene->shouldRenderShadows() &&
StarkSettings->getBoolSetting(Settings::kShadow)) {
glEnable(GL_BLEND);
glEnable(GL_STENCIL_TEST);
_shadowShader->enableVertexAttribute("position1", _faceVBO, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 0);
_shadowShader->enableVertexAttribute("position2", _faceVBO, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 12);
_shadowShader->enableVertexAttribute("bone1", _faceVBO, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 24);
_shadowShader->enableVertexAttribute("bone2", _faceVBO, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 28);
_shadowShader->enableVertexAttribute("boneWeight", _faceVBO, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 32);
_shadowShader->use(true);
Math::Matrix4 mvp = projection * view * model;
mvp.transpose();
_shadowShader->setUniform("mvp", mvp);
setBoneRotationArrayUniform(_shadowShader, "boneRotation");
setBonePositionArrayUniform(_shadowShader, "bonePosition");
Math::Matrix4 modelInverse = model;
modelInverse.inverse();
setShadowUniform(lights, position, modelInverse.getRotation());
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
GLuint ebo = _faceEBO[*face];
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glDrawElements(GL_TRIANGLES, (*face)->vertexIndices.size(), GL_UNSIGNED_INT, 0);
}
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
_shadowShader->unbind();
}
}
void OpenGLSActorRenderer::clearVertices() {
OpenGL::Shader::freeBuffer(_faceVBO); // Zero names are silently ignored
_faceVBO = 0;
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
OpenGL::Shader::freeBuffer(it->_value);
}
_faceEBO.clear();
}
void OpenGLSActorRenderer::uploadVertices() {
_faceVBO = createModelVBO(_model);
Common::Array<Face *> faces = _model->getFaces();
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
_faceEBO[*face] = createFaceEBO(*face);
}
}
GLuint OpenGLSActorRenderer::createModelVBO(const Model *model) {
const Common::Array<VertNode *> &modelVertices = model->getVertices();
float *vertices = new float[14 * modelVertices.size()];
float *vertPtr = vertices;
// Build a vertex array
for (Common::Array<VertNode *>::const_iterator tri = modelVertices.begin(); tri != modelVertices.end(); ++tri) {
*vertPtr++ = (*tri)->_pos1.x();
*vertPtr++ = (*tri)->_pos1.y();
*vertPtr++ = (*tri)->_pos1.z();
*vertPtr++ = (*tri)->_pos2.x();
*vertPtr++ = (*tri)->_pos2.y();
*vertPtr++ = (*tri)->_pos2.z();
*vertPtr++ = (*tri)->_bone1;
*vertPtr++ = (*tri)->_bone2;
*vertPtr++ = (*tri)->_boneWeight;
*vertPtr++ = (*tri)->_normal.x();
*vertPtr++ = (*tri)->_normal.y();
*vertPtr++ = (*tri)->_normal.z();
*vertPtr++ = -(*tri)->_texS;
*vertPtr++ = (*tri)->_texT;
}
GLuint vbo = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(float) * 14 * modelVertices.size(), vertices);
delete[] vertices;
return vbo;
}
GLuint OpenGLSActorRenderer::createFaceEBO(const Face *face) {
return OpenGL::Shader::createBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32) * face->vertexIndices.size(), &face->vertexIndices[0]);
}
void OpenGLSActorRenderer::setBonePositionArrayUniform(OpenGL::Shader *shader, const char *uniform) {
const Common::Array<BoneNode *> &bones = _model->getBones();
GLint pos = shader->getUniformLocation(uniform);
if (pos == -1) {
error("No uniform named '%s'", uniform);
}
float *positions = new float[3 * bones.size()];
float *positionsPtr = positions;
for (uint i = 0; i < bones.size(); i++) {
*positionsPtr++ = bones[i]->_animPos.x();
*positionsPtr++ = bones[i]->_animPos.y();
*positionsPtr++ = bones[i]->_animPos.z();
}
glUniform3fv(pos, bones.size(), positions);
delete[] positions;
}
void OpenGLSActorRenderer::setBoneRotationArrayUniform(OpenGL::Shader *shader, const char *uniform) {
const Common::Array<BoneNode *> &bones = _model->getBones();
GLint rot = shader->getUniformLocation(uniform);
if (rot == -1) {
error("No uniform named '%s'", uniform);
}
float *rotations = new float[4 * bones.size()];
float *rotationsPtr = rotations;
for (uint i = 0; i < bones.size(); i++) {
*rotationsPtr++ = bones[i]->_animRot.x();
*rotationsPtr++ = bones[i]->_animRot.y();
*rotationsPtr++ = bones[i]->_animRot.z();
*rotationsPtr++ = bones[i]->_animRot.w();
}
glUniform4fv(rot, bones.size(), rotations);
delete[] rotations;
}
void OpenGLSActorRenderer::setLightArrayUniform(const LightEntryArray &lights) {
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
_shader->setUniform("ambientColor", ambient->color);
Math::Matrix4 viewMatrix = StarkScene->getViewMatrix();
Math::Matrix3 viewMatrixRot = viewMatrix.getRotation();
for (uint i = 0; i < lights.size() - 1; i++) {
const LightEntry *l = lights[i + 1];
Math::Vector4d worldPosition;
worldPosition.x() = l->position.x();
worldPosition.y() = l->position.y();
worldPosition.z() = l->position.z();
worldPosition.w() = 1.0;
Math::Vector4d eyePosition = viewMatrix * worldPosition;
// The light type is stored in the w coordinate of the position to save an uniform slot
eyePosition.w() = l->type;
Math::Vector3d worldDirection = l->direction;
Math::Vector3d eyeDirection = viewMatrixRot * worldDirection;
eyeDirection.normalize();
_shader->setUniform(Common::String::format("lights[%d].position", i).c_str(), eyePosition);
_shader->setUniform(Common::String::format("lights[%d].direction", i).c_str(), eyeDirection);
_shader->setUniform(Common::String::format("lights[%d].color", i).c_str(), l->color);
Math::Vector4d params;
params.x() = l->falloffNear;
params.y() = l->falloffFar;
params.z() = l->innerConeAngle.getCosine();
params.w() = l->outerConeAngle.getCosine();
_shader->setUniform(Common::String::format("lights[%d].params", i).c_str(), params);
}
for (uint i = lights.size() - 1; i < maxLights; i++) {
// Make sure unused lights are disabled
_shader->setUniform(Common::String::format("lights[%d].position", i).c_str(), Math::Vector4d());
}
}
void OpenGLSActorRenderer::setShadowUniform(const LightEntryArray &lights, const Math::Vector3d &actorPosition,
Math::Matrix3 worldToModelRot) {
Math::Vector3d sumDirection;
bool hasLight = false;
// Compute the contribution from each lights
// The ambient light is skipped intentionally
for (uint i = 1; i < lights.size(); ++i) {
LightEntry *light = lights[i];
bool contributes = false;
Math::Vector3d lightDirection;
switch (light->type) {
case LightEntry::kPoint:
contributes = getPointLightContribution(light, actorPosition, lightDirection);
break;
case LightEntry::kDirectional:
contributes = getDirectionalLightContribution(light, lightDirection);
break;
case LightEntry::kSpot:
contributes = getSpotLightContribution(light, actorPosition, lightDirection);
break;
case LightEntry::kAmbient:
default:
break;
}
if (contributes) {
sumDirection += lightDirection;
hasLight = true;
}
}
if (hasLight) {
// Clip the horizontal length
Math::Vector2d horizontalProjection(sumDirection.x(), sumDirection.y());
float shadowLength = MIN(horizontalProjection.getMagnitude(), StarkScene->getMaxShadowLength());
horizontalProjection.normalize();
horizontalProjection *= shadowLength;
sumDirection.x() = horizontalProjection.getX();
sumDirection.y() = horizontalProjection.getY();
sumDirection.z() = -1;
} else {
// Cast from above by default
sumDirection.x() = 0;
sumDirection.y() = 0;
sumDirection.z() = -1;
}
//Transform the direction to the model space and pass to the shader
sumDirection = worldToModelRot * sumDirection;
_shadowShader->setUniform("lightDirection", sumDirection);
}
bool OpenGLSActorRenderer::getPointLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction, float weight) {
float distance = light->position.getDistanceTo(actorPosition);
if (distance > light->falloffFar) {
return false;
}
float factor;
if (distance > light->falloffNear) {
if (light->falloffFar - light->falloffNear > 1) {
factor = 1 - (distance - light->falloffNear) / (light->falloffFar - light->falloffNear);
} else {
factor = 0;
}
} else {
factor = 1;
}
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
if (factor <= 0 || brightness <= 0) {
return false;
}
direction = actorPosition - light->position;
direction.normalize();
direction *= factor * brightness * weight;
return true;
}
bool OpenGLSActorRenderer::getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction) {
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
if (brightness <= 0) {
return false;
}
direction = light->direction;
direction.normalize();
direction *= brightness;
return true;
}
bool OpenGLSActorRenderer::getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction) {
Math::Vector3d lightToActor = actorPosition - light->position;
lightToActor.normalize();
float cosAngle = MAX(0.0f, lightToActor.dotProduct(light->direction));
float cone = (cosAngle - light->innerConeAngle.getCosine()) /
MAX(0.001f, light->outerConeAngle.getCosine() - light->innerConeAngle.getCosine());
cone = CLIP(cone, 0.0f, 1.0f);
if (cone <= 0) {
return false;
}
return getPointLightContribution(light, actorPosition, direction, cone);
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,81 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_S_ACTOR_H
#define STARK_GFX_OPENGL_S_ACTOR_H
#include "engines/stark/gfx/renderentry.h"
#include "engines/stark/visual/actor.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_SHADERS)
namespace OpenGL {
class Shader;
}
namespace Stark {
namespace Gfx {
class OpenGLSDriver;
class OpenGLSActorRenderer : public VisualActor {
public:
OpenGLSActorRenderer(OpenGLSDriver *gfx);
virtual ~OpenGLSActorRenderer();
void render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<Face *, GLuint> FaceBufferMap;
OpenGLSDriver *_gfx;
OpenGL::Shader *_shader, *_shadowShader;
GLuint _faceVBO;
FaceBufferMap _faceEBO;
void clearVertices();
void uploadVertices();
GLuint createModelVBO(const Model *model);
GLuint createFaceEBO(const Face *face);
void setBonePositionArrayUniform(OpenGL::Shader *shader, const char *uniform);
void setBoneRotationArrayUniform(OpenGL::Shader *shader, const char *uniform);
void setLightArrayUniform(const LightEntryArray &lights);
void setShadowUniform(const LightEntryArray &lights, const Math::Vector3d &actorPosition, Math::Matrix3 worldToModelRot);
bool getPointLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction, float weight = 1.0f);
bool getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction);
bool getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition, Math::Vector3d &direction);
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)
#endif // STARK_GFX_OPENGL_S_ACTOR_H

View File

@@ -0,0 +1,57 @@
/* 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/gfx/openglsfade.h"
#include "engines/stark/gfx/opengls.h"
#if defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/shader.h"
namespace Stark {
namespace Gfx {
OpenGLSFadeRenderer::OpenGLSFadeRenderer(OpenGLSDriver *gfx) :
FadeRenderer(),
_gfx(gfx) {
_shader = _gfx->createFadeShaderInstance();
}
OpenGLSFadeRenderer::~OpenGLSFadeRenderer() {
delete _shader;
}
void OpenGLSFadeRenderer::render(float fadeLevel) {
_gfx->start2DMode();
_shader->use();
_shader->setUniform1f("alphaLevel", 1.0 - fadeLevel);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
_shader->unbind();
_gfx->end2DMode();
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,61 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_S_FADE_H
#define STARK_GFX_OPENGL_S_FADE_H
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_SHADERS)
#include "engines/stark/gfx/faderenderer.h"
namespace OpenGL {
class Shader;
}
namespace Stark {
namespace Gfx {
class OpenGLSDriver;
/**
* A programmable pipeline OpenGL fade screen renderer
*/
class OpenGLSFadeRenderer : public FadeRenderer {
public:
OpenGLSFadeRenderer(OpenGLSDriver *gfx);
~OpenGLSFadeRenderer();
// FadeRenderer API
void render(float fadeLevel);
private:
OpenGLSDriver *_gfx;
OpenGL::Shader *_shader;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)
#endif // STARK_GFX_OPENGL_S_FADE_H

View File

@@ -0,0 +1,193 @@
/* 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/gfx/openglsprop.h"
#include "engines/stark/gfx/driver.h"
#include "engines/stark/gfx/texture.h"
#include "engines/stark/formats/biffmesh.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#if defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/shader.h"
namespace Stark {
namespace Gfx {
OpenGLSPropRenderer::OpenGLSPropRenderer(Driver *gfx) :
VisualProp(),
_gfx(gfx),
_faceVBO(0),
_modelIsDirty(true) {
static const char* attributes[] = { "position", "normal", "texcoord", nullptr };
_shader = OpenGL::Shader::fromFiles("stark_prop", attributes);
}
OpenGLSPropRenderer::~OpenGLSPropRenderer() {
clearVertices();
delete _shader;
}
void OpenGLSPropRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
if (_modelIsDirty) {
// Update the OpenGL Buffer Objects if required
clearVertices();
uploadVertices();
_modelIsDirty = false;
}
_gfx->set3DMode();
Math::Matrix4 model = getModelMatrix(position, direction);
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // OpenGL expects matrices transposed
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // OpenGL expects matrices transposed
Math::Matrix4 normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
_shader->enableVertexAttribute("position", _faceVBO, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), 0);
_shader->enableVertexAttribute("normal", _faceVBO, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), 12);
_shader->enableVertexAttribute("texcoord", _faceVBO, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), 24);
_shader->use(true);
_shader->setUniform("modelViewMatrix", modelViewMatrix);
_shader->setUniform("projectionMatrix", projectionMatrix);
_shader->setUniform("normalMatrix", normalMatrix.getRotation());
setLightArrayUniform(lights);
const Common::Array<Face> &faces = _model->getFaces();
const Common::Array<Material> &materials = _model->getMaterials();
for (Common::Array<Face>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
const Material &material = materials[face->materialId];
// For each face draw its vertices from the VBO, indexed by the EBO
const Gfx::Texture *tex = _texture->getTexture(material.texture);
if (tex) {
tex->bind();
} else {
glBindTexture(GL_TEXTURE_2D, 0);
}
_shader->setUniform("textured", tex != nullptr);
_shader->setUniform("color", Math::Vector3d(material.r, material.g, material.b));
_shader->setUniform("doubleSided", material.doubleSided ? 1 : 0);
GLuint ebo = _faceEBO[face];
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glDrawElements(GL_TRIANGLES, face->vertexIndices.size(), GL_UNSIGNED_INT, 0);
}
_shader->unbind();
}
void OpenGLSPropRenderer::clearVertices() {
OpenGL::Shader::freeBuffer(_faceVBO);
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
OpenGL::Shader::freeBuffer(it->_value);
}
_faceEBO.clear();
}
void OpenGLSPropRenderer::uploadVertices() {
_faceVBO = createFaceVBO();
const Common::Array<Face> &faces = _model->getFaces();
for (Common::Array<Face>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
_faceEBO[face] = createFaceEBO(face);
}
}
GLuint OpenGLSPropRenderer::createFaceVBO() {
const Common::Array<Formats::BiffMesh::Vertex> &vertices = _model->getVertices();
return OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(float) * 9 * vertices.size(), &vertices.front());
}
GLuint OpenGLSPropRenderer::createFaceEBO(const Face *face) {
return OpenGL::Shader::createBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32) * face->vertexIndices.size(), &face->vertexIndices.front());
}
void OpenGLSPropRenderer::setLightArrayUniform(const LightEntryArray &lights) {
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
_shader->setUniform("ambientColor", ambient->color);
Math::Matrix4 viewMatrix = StarkScene->getViewMatrix();
Math::Matrix3 viewMatrixRot = viewMatrix.getRotation();
for (uint i = 0; i < lights.size() - 1; i++) {
const LightEntry *l = lights[i + 1];
Math::Vector4d worldPosition;
worldPosition.x() = l->position.x();
worldPosition.y() = l->position.y();
worldPosition.z() = l->position.z();
worldPosition.w() = 1.0;
Math::Vector4d eyePosition = viewMatrix * worldPosition;
// The light type is stored in the w coordinate of the position to save an uniform slot
eyePosition.w() = l->type;
Math::Vector3d worldDirection = l->direction;
Math::Vector3d eyeDirection = viewMatrixRot * worldDirection;
eyeDirection.normalize();
_shader->setUniform(Common::String::format("lights[%d].position", i).c_str(), eyePosition);
_shader->setUniform(Common::String::format("lights[%d].direction", i).c_str(), eyeDirection);
_shader->setUniform(Common::String::format("lights[%d].color", i).c_str(), l->color);
Math::Vector4d params;
params.x() = l->falloffNear;
params.y() = l->falloffFar;
params.z() = l->innerConeAngle.getCosine();
params.w() = l->outerConeAngle.getCosine();
_shader->setUniform(Common::String::format("lights[%d].params", i).c_str(), params);
}
for (uint i = lights.size() - 1; i < maxLights; i++) {
// Make sure unused lights are disabled
_shader->setUniform(Common::String::format("lights[%d].position", i).c_str(), Math::Vector4d());
}
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,76 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_S_RENDERED_H
#define STARK_GFX_OPENGL_S_RENDERED_H
#include "engines/stark/model/model.h"
#include "engines/stark/visual/prop.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_SHADERS)
namespace OpenGL {
class Shader;
}
namespace Stark {
namespace Gfx {
class Driver;
class OpenGLSPropRenderer : public VisualProp {
public:
explicit OpenGLSPropRenderer(Driver *gfx);
~OpenGLSPropRenderer() override;
void render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<const Face *, GLuint> FaceBufferMap;
Driver *_gfx;
OpenGL::Shader *_shader;
bool _modelIsDirty;
GLuint _faceVBO;
FaceBufferMap _faceEBO;
void clearVertices();
void uploadVertices();
GLuint createFaceVBO();
GLuint createFaceEBO(const Face *face);
void setLightArrayUniform(const LightEntryArray &lights);
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_OPENGL_S_RENDERED_H
#endif // defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,113 @@
/* 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/gfx/openglssurface.h"
#include "engines/stark/gfx/opengls.h"
#include "engines/stark/gfx/bitmap.h"
#include "engines/stark/gfx/color.h"
#if defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/shader.h"
namespace Stark {
namespace Gfx {
OpenGLSSurfaceRenderer::OpenGLSSurfaceRenderer(OpenGLSDriver *gfx) :
SurfaceRenderer(),
_gfx(gfx) {
_shader = _gfx->createSurfaceShaderInstance();
_shaderFill = _gfx->createSurfaceFillShaderInstance();
}
OpenGLSSurfaceRenderer::~OpenGLSSurfaceRenderer() {
delete _shaderFill;
delete _shader;
}
void OpenGLSSurfaceRenderer::render(const Bitmap *bitmap, const Common::Point &dest) {
render(bitmap, dest, bitmap->width(), bitmap->height());
}
void OpenGLSSurfaceRenderer::render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) {
// Destination rectangle with given width and height
_gfx->start2DMode();
_shader->use();
_shader->setUniform1f("fadeLevel", _fadeLevel);
_shader->setUniform("snapToGrid", _snapToGrid ? 1 : 0);
_shader->setUniform("verOffsetXY", normalizeOriginalCoordinates(dest.x, dest.y));
if (_noScalingOverride) {
_shader->setUniform("verSizeWH", normalizeCurrentCoordinates(width, height));
} else {
_shader->setUniform("verSizeWH", normalizeOriginalCoordinates(width, height));
}
Common::Rect nativeViewport = _gfx->getViewport();
_shader->setUniform("viewport", Math::Vector2d(nativeViewport.width(), nativeViewport.height()));
bitmap->bind();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
_shader->unbind();
_gfx->end2DMode();
}
void OpenGLSSurfaceRenderer::fill(const Color &color, const Common::Point &dest, uint width, uint height) {
// Destination rectangle with given width and height
_gfx->start2DMode();
_shaderFill->use();
_shaderFill->setUniform1f("fadeLevel", _fadeLevel);
_shaderFill->setUniform("snapToGrid", _snapToGrid ? 1 : 0);
_shaderFill->setUniform("verOffsetXY", normalizeOriginalCoordinates(dest.x, dest.y));
if (_noScalingOverride) {
_shaderFill->setUniform("verSizeWH", normalizeCurrentCoordinates(width, height));
} else {
_shaderFill->setUniform("verSizeWH", normalizeOriginalCoordinates(width, height));
}
Common::Rect nativeViewport = _gfx->getViewport();
_shaderFill->setUniform("viewport", Math::Vector2d(nativeViewport.width(), nativeViewport.height()));
_shaderFill->setUniform("color", Math::Vector4d(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
_shaderFill->unbind();
_gfx->end2DMode();
}
Math::Vector2d OpenGLSSurfaceRenderer::normalizeOriginalCoordinates(int x, int y) const {
Common::Rect viewport = _gfx->getUnscaledViewport();
return Math::Vector2d(x / (float)viewport.width(), y / (float)viewport.height());
}
Math::Vector2d OpenGLSSurfaceRenderer::normalizeCurrentCoordinates(int x, int y) const {
Common::Rect viewport = _gfx->getViewport();
return Math::Vector2d(x / (float)viewport.width(), y / (float)viewport.height());
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // if defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_S_SURFACE_H
#define STARK_GFX_OPENGL_S_SURFACE_H
#include "engines/stark/gfx/surfacerenderer.h"
#include "math/vector2d.h"
#if defined(USE_OPENGL_SHADERS)
namespace OpenGL {
class Shader;
}
namespace Stark {
namespace Gfx {
class OpenGLSDriver;
class Bitmap;
/**
* A programmable pipeline OpenGL surface renderer
*/
class OpenGLSSurfaceRenderer : public SurfaceRenderer {
public:
OpenGLSSurfaceRenderer(OpenGLSDriver *gfx);
virtual ~OpenGLSSurfaceRenderer();
// SurfaceRenderer API
void render(const Bitmap *bitmap, const Common::Point &dest) override;
void render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) override;
void fill(const Color &color, const Common::Point &dest, uint width, uint height) override;
private:
Math::Vector2d normalizeOriginalCoordinates(int x, int y) const;
Math::Vector2d normalizeCurrentCoordinates(int x, int y) const;
OpenGLSDriver *_gfx;
OpenGL::Shader *_shader;
OpenGL::Shader *_shaderFill;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_SHADERS)
#endif // STARK_GFX_OPENGL_S_SURFACE_H

View File

@@ -0,0 +1,166 @@
/* 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/gfx/openglsurface.h"
#include "engines/stark/gfx/openglbitmap.h"
#include "engines/stark/gfx/color.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
OpenGLSurfaceRenderer::OpenGLSurfaceRenderer(OpenGLDriver *gfx) :
SurfaceRenderer(),
_gfx(gfx) {
}
OpenGLSurfaceRenderer::~OpenGLSurfaceRenderer() {
}
void OpenGLSurfaceRenderer::render(const Bitmap *bitmap, const Common::Point &dest) {
render(bitmap, dest, bitmap->width(), bitmap->height());
}
void OpenGLSurfaceRenderer::render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) {
// Destination rectangle with given width and height
_gfx->start2DMode();
SurfaceVertex vertices[4] = {};
convertToVertices(vertices, dest, width, height);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glEnable(GL_TEXTURE_2D);
glDisableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &vertices[0].x);
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(float), ((const OpenGlBitmap *)bitmap)->getTexCoords());
glColor4f(1.0f - _fadeLevel, 1.0f - _fadeLevel, 1.0f - _fadeLevel, 1.0f);
bitmap->bind();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
_gfx->end2DMode();
}
void OpenGLSurfaceRenderer::fill(const Color &color, const Common::Point &dest, uint width, uint height) {
_gfx->start2DMode();
SurfaceVertex vertices[4] = {};
convertToVertices(vertices, dest, width, height);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &vertices[0].x);
glColor4f((color.r / 255.0f) - _fadeLevel, (color.g / 255.0f) - _fadeLevel, (color.b / 255.0f) - _fadeLevel, color.a / 255.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
_gfx->end2DMode();
}
void OpenGLSurfaceRenderer::convertToVertices(SurfaceVertex *vertices, const Common::Point &dest, uint width, uint height) const {
const Math::Vector2d surfaceVertices[] = {
// X Y
{ 0.0f, 0.0f },
{ 1.0f, 0.0f },
{ 0.0f, 1.0f },
{ 1.0f, 1.0f },
};
Math::Vector2d verSizeWH;
if (_noScalingOverride) {
verSizeWH = normalizeCurrentCoordinates(width, height);
} else {
verSizeWH = normalizeOriginalCoordinates(width, height);
}
auto verOffsetXY = normalizeOriginalCoordinates(dest.x, dest.y);
auto nativeViewport = _gfx->getViewport();
auto viewport = Math::Vector2d(nativeViewport.width(), nativeViewport.height());
for (int32 v = 0; v < 4; v++) {
Math::Vector2d pos = verOffsetXY + (surfaceVertices[v] * verSizeWH);
if (_snapToGrid) {
// Align vertex coordinates to the native pixel grid
// This ensures text does not get garbled by nearest neighbors scaling
pos.setX(floor(pos.getX() * viewport.getX() + 0.5) / viewport.getX());
pos.setY(floor(pos.getY() * viewport.getY() + 0.5) / viewport.getY());
}
// position coords
vertices[v].x = pos.getX() * 2.0 - 1.0;
vertices[v].y = -1.0 * (pos.getY() * 2.0 - 1.0);
}
}
Math::Vector2d OpenGLSurfaceRenderer::normalizeOriginalCoordinates(int x, int y) const {
Common::Rect viewport = _gfx->getUnscaledViewport();
return Math::Vector2d(x / (float)viewport.width(), y / (float)viewport.height());
}
Math::Vector2d OpenGLSurfaceRenderer::normalizeCurrentCoordinates(int x, int y) const {
Common::Rect viewport = _gfx->getViewport();
return Math::Vector2d(x / (float)viewport.width(), y / (float)viewport.height());
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // if defined(USE_OPENGL_GAME)

View File

@@ -0,0 +1,69 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_SURFACE_H
#define STARK_GFX_OPENGL_SURFACE_H
#include "engines/stark/gfx/surfacerenderer.h"
#include "engines/stark/gfx/opengl.h"
#include "math/vector2d.h"
#if defined(USE_OPENGL_GAME)
namespace Stark {
namespace Gfx {
class OpenGLDriver;
class Bitmap;
/**
* A programmable pipeline OpenGL surface renderer
*/
class OpenGLSurfaceRenderer : public SurfaceRenderer {
public:
OpenGLSurfaceRenderer(OpenGLDriver *gfx);
virtual ~OpenGLSurfaceRenderer();
// SurfaceRenderer API
void render(const Bitmap *bitmap, const Common::Point &dest) override;
void render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) override;
void fill(const Color &color, const Common::Point &dest, uint width, uint height) override;
private:
struct SurfaceVertex {
float x;
float y;
};
Math::Vector2d normalizeOriginalCoordinates(int x, int y) const;
Math::Vector2d normalizeCurrentCoordinates(int x, int y) const;
void convertToVertices(SurfaceVertex *vertices, const Common::Point &dest, uint width, uint height) const;
OpenGLDriver *_gfx;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME)
#endif // STARK_GFX_OPENGL_SURFACE_H

View File

@@ -0,0 +1,107 @@
/* 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/gfx/opengltexture.h"
#include "engines/stark/gfx/driver.h"
#include "graphics/surface.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "graphics/opengl/context.h"
namespace Stark {
namespace Gfx {
OpenGlTexture::OpenGlTexture() :
Texture(),
_id(0),
_levelCount(0) {
glGenTextures(1, &_id);
bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
OpenGlTexture::~OpenGlTexture() {
glDeleteTextures(1, &_id);
}
void OpenGlTexture::bind() const {
glBindTexture(GL_TEXTURE_2D, _id);
}
void OpenGlTexture::updateLevel(uint32 level, const Graphics::Surface *surface, const byte *palette) {
const Graphics::Surface *rgbaSurface = surface;
if (surface->format != Driver::getRGBAPixelFormat()) {
// Convert the surface to texture format
rgbaSurface = surface->convertTo(Driver::getRGBAPixelFormat(), palette);
}
// Stark textures (not bitmaps!) are always POT
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, rgbaSurface->w, rgbaSurface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgbaSurface->getPixels());
if (rgbaSurface != surface) {
const_cast<Graphics::Surface *>(rgbaSurface)->free();
delete rgbaSurface;
}
}
void OpenGlTexture::setLevelCount(uint32 count) {
_levelCount = count;
if (count >= 1) {
// GLES1 and GLES2 do not allow setting the max provided mipmap level.
// It expects all the levels to be provided, which is not the case in TLJ.
// FIXME: Enable mipmapping on GLES without this extension
if (OpenGLContext.textureMaxLevelSupported) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, count - 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
// TODO: Provide a fallback if this isn't available.
if (OpenGLContext.textureMirrorRepeatSupported) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
}
}
}
void OpenGlTexture::addLevel(uint32 level, const Graphics::Surface *surface, const byte *palette) {
assert(level < _levelCount);
if (level == 0 || OpenGLContext.textureMaxLevelSupported) {
updateLevel(level, surface, palette);
}
}
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)

View File

@@ -0,0 +1,59 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_OPENGL_TEXTURE_H
#define STARK_GFX_OPENGL_TEXTURE_H
#include "engines/stark/gfx/texture.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
namespace Stark {
namespace Gfx {
/**
* An OpenGL texture wrapper
*/
class OpenGlTexture : public Texture {
public:
OpenGlTexture();
virtual ~OpenGlTexture();
// Texture API
void bind() const override;
void setLevelCount(uint32 count) override;
void addLevel(uint32 level, const Graphics::Surface *surface, const byte *palette = nullptr) override;
protected:
void updateLevel(uint32 level, const Graphics::Surface *surface, const byte *palette = nullptr);
GLuint _id;
uint32 _levelCount;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#endif // STARK_GFX_OPENGL_TEXTURE_H

View File

@@ -0,0 +1,234 @@
/* 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/gfx/renderentry.h"
#include "engines/stark/gfx/driver.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/visual/actor.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/prop.h"
#include "engines/stark/visual/smacker.h"
#include "engines/stark/visual/text.h"
#include "engines/stark/visual/visual.h"
namespace Stark {
namespace Gfx {
RenderEntry::RenderEntry(Resources::ItemVisual *owner, const Common::String &name) :
_visual(nullptr),
_name(name),
_owner(owner),
_direction3D(0.0),
_sortKey(0.0),
_clickable(true) {
}
void RenderEntry::render(const LightEntryArray &lights) {
if (!_visual) {
// warning("No visual for render entry '%s'", _name.c_str());
return;
}
VisualImageXMG *imageXMG = _visual->get<VisualImageXMG>();
if (imageXMG) {
imageXMG->render(_position, true);
}
VisualActor *actor = _visual->get<VisualActor>();
if (actor) {
actor->render(_position3D, _direction3D, lights);
}
VisualProp *prop = _visual->get<VisualProp>();
if (prop) {
prop->render(_position3D, _direction3D, lights);
}
VisualSmacker *smacker = _visual->get<VisualSmacker>();
if (smacker) {
smacker->render(_position);
}
VisualText *text = _visual->get<VisualText>();
if (text) {
text->render(_position);
}
VisualEffectBubbles *bubbles = _visual->get<VisualEffectBubbles>();
if (bubbles) {
bubbles->render(_position);
}
VisualEffectFireFlies *fireflies = _visual->get<VisualEffectFireFlies>();
if (fireflies) {
fireflies->render(_position);
}
VisualEffectFish *fish = _visual->get<VisualEffectFish>();
if (fish) {
fish->render(_position);
}
}
void RenderEntry::setVisual(Visual *visual) {
_visual = visual;
}
void RenderEntry::setPosition(const Common::Point &position) {
_position = position;
}
void RenderEntry::setPosition3D(const Math::Vector3d &position, float direction) {
_position3D = position;
_direction3D = direction;
}
void RenderEntry::setSortKey(float sortKey) {
_sortKey = sortKey;
}
void RenderEntry::setClickable(bool clickable) {
_clickable = clickable;
}
bool RenderEntry::compare(const RenderEntry *x, const RenderEntry *y) {
if (x->_sortKey != y->_sortKey) {
return x->_sortKey < y->_sortKey;
} else if (x->_owner && y->_owner) {
// The original used a stable sort. Common::sort is not.
// This should ensure the items remain in the same order if they have the same sort key
return x->_owner->getIndex() < y->_owner->getIndex();
} else {
return (x->_owner < y->_owner);
}
}
bool RenderEntry::containsPoint(const Common::Point &position, Common::Point &relativePosition, const Common::Rect &cursorRect) const {
if (!_visual || !_clickable) {
return false;
}
VisualImageXMG *image = _visual->get<VisualImageXMG>();
if (image) {
Common::Rect imageRect = Common::Rect(image->getWidth(), image->getHeight());
imageRect.translate(_position.x, _position.y);
imageRect.translate(-image->getHotspot().x, -image->getHotspot().y);
relativePosition.x = position.x - imageRect.left;
relativePosition.y = position.y - imageRect.top;
if (imageRect.contains(position) && image->isPointSolid(relativePosition)) {
return true;
}
if (imageRect.width() < 32 && imageRect.height() < 32
&& !cursorRect.isEmpty() && cursorRect.intersects(imageRect)) {
// If the item in the scene is way smaller than the cursor,
// use the whole cursor as a hit rectangle.
relativePosition.x = 1 - image->getHotspot().x;
relativePosition.y = 1 - image->getHotspot().y;
return true;
}
}
VisualSmacker *smacker = _visual->get<VisualSmacker>();
if (smacker) {
Common::Point smackerPosition = smacker->getPosition();
smackerPosition -= _position;
Common::Rect smackerRect = Common::Rect(smacker->getWidth(), smacker->getHeight());
smackerRect.translate(smackerPosition.x, smackerPosition.y);
relativePosition.x = position.x - smackerRect.left;
relativePosition.y = position.y - smackerRect.top;
if (smackerRect.contains(position) && smacker->isPointSolid(relativePosition)) {
return true;
}
}
VisualText *text = _visual->get<VisualText>();
if (text) {
Common::Rect textRect = text->getRect();
textRect.translate(_position.x, _position.y);
relativePosition.x = position.x - textRect.left;
relativePosition.y = position.y - textRect.top;
if (textRect.contains(position)) {
return true;
}
}
return false;
}
bool RenderEntry::intersectRay(const Math::Ray &ray) const {
if (!_visual || !_clickable) {
return false;
}
VisualActor *actor = _visual->get<VisualActor>();
if (actor) {
return actor->intersectRay(ray, _position3D, _direction3D);
}
VisualProp *prop = _visual->get<VisualProp>();
if (prop) {
return prop->intersectRay(ray, _position3D, _direction3D);
}
return false;
}
VisualImageXMG *RenderEntry::getImage() const {
if (!_visual) {
return nullptr;
}
return _visual->get<VisualImageXMG>();
}
VisualText *RenderEntry::getText() const {
if (!_visual) {
return nullptr;
}
return _visual->get<VisualText>();
}
Common::Rect RenderEntry::getBoundingRect() const {
if (!_visual) {
return Common::Rect();
}
VisualActor *actor = _visual->get<VisualActor>();
if (actor) {
return actor->getBoundingRect(_position3D, _direction3D);
}
warning("RenderEntry::getBoundingRect is not implemented for '%s'", _name.c_str());
return Common::Rect();
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,134 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_RENDER_ENTRY_H
#define STARK_GFX_RENDER_ENTRY_H
#include "common/array.h"
#include "common/rect.h"
#include "common/str.h"
#include "math/ray.h"
#include "math/vector3d.h"
namespace Stark {
class Visual;
class VisualImageXMG;
class VisualText;
namespace Resources {
class ItemVisual;
}
namespace Gfx {
struct LightEntry {
enum Type {
kAmbient = 0,
kPoint = 1,
kDirectional = 2,
kSpot = 4
};
Type type;
Math::Vector3d color;
Math::Vector3d position;
Math::Vector3d direction;
Math::Angle innerConeAngle;
Math::Angle outerConeAngle;
float falloffNear;
float falloffFar;
Math::Vector4d worldPosition;
Math::Vector4d eyePosition;
Math::Vector3d eyeDirection;
};
typedef Common::Array<LightEntry *> LightEntryArray;
class RenderEntry {
public:
RenderEntry(Resources::ItemVisual *owner, const Common::String &name);
virtual ~RenderEntry() {}
void render(const LightEntryArray &lights = LightEntryArray());
void setVisual(Visual *visual);
void setPosition(const Common::Point &position);
void setPosition3D(const Math::Vector3d &position, float direction);
void setSortKey(float sortKey);
void setClickable(bool clickable);
/** Gets the position */
Common::Point getPosition() const { return _position; }
/** Gets the owner-object */
Resources::ItemVisual *getOwner() const { return _owner; }
/** Gets the entry's name */
const Common::String &getName() const { return _name; }
/** Obtain the underlying image visual, if any */
VisualImageXMG *getImage() const;
/** Obtain the underlying text visual, if any */
VisualText *getText() const;
/**
* Mouse picking test for 2D items
*
* @param position game window coordinates to test
* @param relativePosition successful hit item relative coordinates
* @param cursorRect cursor rectangle to be used to test small world items
* @return successful hit
*/
bool containsPoint(const Common::Point &position, Common::Point &relativePosition, const Common::Rect &cursorRect) const;
/** Mouse picking test for 3D items */
bool intersectRay(const Math::Ray &ray) const;
/** Compare two render entries by their sort keys */
static bool compare(const RenderEntry *x, const RenderEntry *y);
/**
* Compute the 2D screen space bounding rect for the item,
* in original game view coordinates.
*/
Common::Rect getBoundingRect() const;
protected:
Common::String _name;
Resources::ItemVisual *_owner;
Visual *_visual;
Common::Point _position;
Math::Vector3d _position3D;
float _direction3D;
float _sortKey;
bool _clickable;
};
typedef Common::Array<RenderEntry *> RenderEntryArray;
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_RENDER_ENTRY_H

View File

@@ -0,0 +1,49 @@
/* 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/gfx/surfacerenderer.h"
namespace Stark {
namespace Gfx {
SurfaceRenderer::SurfaceRenderer() :
_noScalingOverride(false),
_fadeLevel(0),
_snapToGrid(false) {
}
SurfaceRenderer::~SurfaceRenderer() {
}
void SurfaceRenderer::setNoScalingOverride(bool noScalingOverride) {
_noScalingOverride = noScalingOverride;
}
void SurfaceRenderer::setFadeLevel(float fadeLevel) {
_fadeLevel = fadeLevel;
}
void SurfaceRenderer::setSnapToGrid(bool snapToGrid) {
_snapToGrid = snapToGrid;
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,85 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_SURFACE_RENDERER_H
#define STARK_GFX_SURFACE_RENDERER_H
#include "common/rect.h"
namespace Stark {
namespace Gfx {
class Bitmap;
struct Color;
/**
* A renderer to draw textures as two dimensional surfaces to the current viewport
*/
class SurfaceRenderer {
public:
SurfaceRenderer();
virtual ~SurfaceRenderer();
/**
* Draw a 2D surface from the specified bitmap
*/
virtual void render(const Bitmap *bitmap, const Common::Point &dest) = 0;
/**
* Draw a 2D surface from the specified bitmap with given width and height
*/
virtual void render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) = 0;
/**
* Draw a filled 2D rectangle using the specified color
*/
virtual void fill(const Color &color, const Common::Point &dest, uint width, uint height) = 0;
/**
* When this is set to true, the texture size is expected to be in current
* coordinates, and is to be drawn without scaling.
*
* This setting does not affect the destination point coordinates
*/
void setNoScalingOverride(bool noScalingOverride);
/**
* The fade level is added to the color value of each pixel
*
* It is a value between -1 and 1
*/
void setFadeLevel(float fadeLevel);
/**
* Align vertex coordinates to the native pixel grid
*/
void setSnapToGrid(bool snapToGrid);
protected:
bool _noScalingOverride;
float _fadeLevel;
bool _snapToGrid;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_SURFACE_RENDERER_H

View File

@@ -0,0 +1,55 @@
/* 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/gfx/texture.h"
#include "graphics/surface.h"
namespace Stark {
namespace Gfx {
TextureSet::TextureSet() {
}
TextureSet::~TextureSet() {
for (TextureMap::iterator it = _texMap.begin(); it != _texMap.end(); ++it) {
delete it->_value;
}
}
void TextureSet::addTexture(const Common::String &name, Texture *texture) {
if (_texMap.contains(name)) {
error("A texture with the name '%s' already exists in the set.", name.c_str());
}
_texMap.setVal(name, texture);
}
const Texture *TextureSet::getTexture(const Common::String &name) const {
TextureMap::const_iterator it = _texMap.find(name);
if (it != _texMap.end())
return it->_value;
return nullptr;
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,85 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TEXTURE_H
#define STARK_GFX_TEXTURE_H
#include "common/hash-str.h"
namespace Graphics {
struct Surface;
}
namespace Stark {
namespace Gfx {
/**
* An abstract texture
*/
class Texture {
public:
Texture() {}
virtual ~Texture() {}
/** Make the texture active */
virtual void bind() const = 0;
/**
* Define the total number of levels of details
*
* Must be called before adding levels
*/
virtual void setLevelCount(uint32 count) = 0;
/**
* Add a detail level to the texture
*/
virtual void addLevel(uint32 level, const Graphics::Surface *surface, const byte *palette = nullptr) = 0;
};
/**
* A collection of textures referenced by their names
*/
class TextureSet {
public:
TextureSet();
~TextureSet();
/**
* Add a texture to the set
*/
void addTexture(const Common::String &name, Texture *texture);
/**
* Retrieve a texture from the set
*/
const Texture *getTexture(const Common::String &name) const;
private:
typedef Common::HashMap<Common::String, Texture *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> TextureMap;
TextureMap _texMap;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TEXTURE_H

View File

@@ -0,0 +1,188 @@
/* 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 "common/system.h"
#include "common/config-manager.h"
#include "math/matrix4.h"
#include "engines/stark/gfx/tinygl.h"
#include "engines/stark/gfx/tinyglactor.h"
#include "engines/stark/gfx/tinyglbitmap.h"
#include "engines/stark/gfx/tinyglprop.h"
#include "engines/stark/gfx/tinyglsurface.h"
#include "engines/stark/gfx/tinyglfade.h"
#include "engines/stark/gfx/tinygltexture.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#include "graphics/surface.h"
namespace Stark {
namespace Gfx {
TinyGLDriver::TinyGLDriver() {
}
TinyGLDriver::~TinyGLDriver() {
TinyGL::destroyContext();
}
void TinyGLDriver::init() {
computeScreenViewport();
TinyGL::createContext(kOriginalWidth, kOriginalHeight, g_system->getScreenFormat(), 512, true, ConfMan.getBool("dirtyrects"));
tglMatrixMode(TGL_PROJECTION);
tglLoadIdentity();
tglMatrixMode(TGL_MODELVIEW);
tglLoadIdentity();
tglDisable(TGL_LIGHTING);
}
void TinyGLDriver::setScreenViewport(bool noScaling) {
if (noScaling) {
_viewport = Common::Rect(g_system->getWidth(), g_system->getHeight());
_unscaledViewport = _viewport;
} else {
_viewport = _screenViewport;
_unscaledViewport = Common::Rect(kOriginalWidth, kOriginalHeight);
}
tglViewport(_viewport.left, _viewport.top, _viewport.width(), _viewport.height());
}
void TinyGLDriver::setViewport(const Common::Rect &rect) {
_viewport = Common::Rect(_screenViewport.width() * rect.width() / kOriginalWidth,
_screenViewport.height() * rect.height() / kOriginalHeight);
_viewport.translate(_screenViewport.left + _screenViewport.width() * rect.left / kOriginalWidth,
_screenViewport.top + _screenViewport.height() * rect.top / kOriginalHeight);
_unscaledViewport = rect;
tglViewport(_viewport.left, g_system->getHeight() - _viewport.bottom, _viewport.width(), _viewport.height());
}
void TinyGLDriver::clearScreen() {
tglClear(TGL_COLOR_BUFFER_BIT | TGL_DEPTH_BUFFER_BIT | TGL_STENCIL_BUFFER_BIT);
}
void TinyGLDriver::flipBuffer() {
Common::List<Common::Rect> dirtyAreas;
TinyGL::presentBuffer(dirtyAreas);
Graphics::Surface glBuffer;
TinyGL::getSurfaceRef(glBuffer);
if (!dirtyAreas.empty()) {
for (Common::List<Common::Rect>::iterator itRect = dirtyAreas.begin(); itRect != dirtyAreas.end(); ++itRect) {
g_system->copyRectToScreen(glBuffer.getBasePtr((*itRect).left, (*itRect).top), glBuffer.pitch,
(*itRect).left, (*itRect).top, (*itRect).width(), (*itRect).height());
}
}
g_system->updateScreen();
}
Texture *TinyGLDriver::createTexture() {
return new TinyGlTexture();
}
Bitmap *TinyGLDriver::createBitmap(const Graphics::Surface *surface, const byte *palette) {
TinyGlBitmap *texture = new TinyGlBitmap();
if (surface) {
texture->update(surface, palette);
}
return texture;
}
VisualActor *TinyGLDriver::createActorRenderer() {
return new TinyGLActorRenderer(this);
}
VisualProp *TinyGLDriver::createPropRenderer() {
return new TinyGLPropRenderer(this);
}
SurfaceRenderer *TinyGLDriver::createSurfaceRenderer() {
return new TinyGLSurfaceRenderer(this);
}
FadeRenderer *TinyGLDriver::createFadeRenderer() {
return new TinyGLFadeRenderer(this);
}
void TinyGLDriver::start2DMode() {
// This blend mode prevents color fringes due to filtering.
// It requires the textures to have their color values pre-multiplied
// with their alpha value. This is the "Premultiplied Alpha" technique.
tglBlendFunc(TGL_ONE, TGL_ONE_MINUS_SRC_ALPHA);
tglEnable(TGL_BLEND);
tglDisable(TGL_DEPTH_TEST);
tglDepthMask(TGL_FALSE);
}
void TinyGLDriver::end2DMode() {
tglDisable(TGL_BLEND);
tglEnable(TGL_DEPTH_TEST);
tglDepthMask(TGL_TRUE);
}
void TinyGLDriver::set3DMode() {
tglEnable(TGL_DEPTH_TEST);
tglDepthFunc(TGL_LESS);
// Stencil test are only used in rendering shadows
// They are manually enabled and disabled there
tglStencilFunc(TGL_EQUAL, 0, 0xFF);
tglStencilOp(TGL_KEEP, TGL_KEEP, TGL_INCR);
}
bool TinyGLDriver::computeLightsEnabled() {
return false;
}
Common::Rect TinyGLDriver::getViewport() const {
return _viewport;
}
Common::Rect TinyGLDriver::getUnscaledViewport() const {
return _unscaledViewport;
}
Graphics::Surface *TinyGLDriver::getViewportScreenshot() const {
Graphics::Surface *tmp = TinyGL::copyFromFrameBuffer(getRGBAPixelFormat());
Graphics::Surface *s = new Graphics::Surface();
s->create(_viewport.width(), _viewport.height(), getRGBAPixelFormat());
byte *src = (byte *)tmp->getPixels();
s->copyRectToSurface(src + tmp->pitch * _viewport.top + _viewport.left * tmp->format.bytesPerPixel,
tmp->pitch, 0, 0, _viewport.width(), _viewport.height());
tmp->free();
delete tmp;
return s;
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,78 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_H
#define STARK_GFX_TINYGL_H
#include "common/system.h"
#include "math/vector3d.h"
#include "engines/stark/gfx/driver.h"
#include "engines/stark/gfx/renderentry.h"
#include "graphics/tinygl/tinygl.h"
namespace Stark {
namespace Gfx {
class TinyGLDriver : public Driver {
public:
TinyGLDriver();
~TinyGLDriver();
void init() override;
void setScreenViewport(bool noScaling) override;
void setViewport(const Common::Rect &rect) override;
void clearScreen() override;
void flipBuffer() override;
Texture *createTexture() override;
Bitmap *createBitmap(const Graphics::Surface *surface = nullptr, const byte *palette = nullptr) override;
VisualActor *createActorRenderer() override;
VisualProp *createPropRenderer() override;
SurfaceRenderer *createSurfaceRenderer() override;
FadeRenderer *createFadeRenderer() override;
void start2DMode();
void end2DMode();
void set3DMode() override;
bool computeLightsEnabled() override;
Common::Rect getViewport() const;
Common::Rect getUnscaledViewport() const;
void setupLights(const LightEntryArray &lights);
Graphics::Surface *getViewportScreenshot() const override;
bool supportsModdedAssets() const override { return false; }
private:
Common::Rect _viewport;
Common::Rect _unscaledViewport;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_H

View File

@@ -0,0 +1,453 @@
/* 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/gfx/tinyglactor.h"
#include "engines/stark/model/model.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/settings.h"
#include "engines/stark/gfx/texture.h"
#include "math/vector2d.h"
namespace Stark {
namespace Gfx {
TinyGLActorRenderer::TinyGLActorRenderer(TinyGLDriver *gfx) :
VisualActor(),
_gfx(gfx),
_faceVBO(nullptr) {
}
TinyGLActorRenderer::~TinyGLActorRenderer() {
clearVertices();
}
void TinyGLActorRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
if (_modelIsDirty) {
clearVertices();
uploadVertices();
_modelIsDirty = false;
}
// TODO: Move updates outside of the rendering code
_animHandler->animate(_time);
_model->updateBoundingBox();
bool drawShadow = false;
if (_castsShadow &&
StarkScene->shouldRenderShadows() &&
StarkSettings->getBoolSetting(Settings::kShadow)) {
drawShadow = true;
}
Math::Vector3d lightDirection;
_gfx->set3DMode();
Math::Matrix4 model = getModelMatrix(position, direction);
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // TinyGL expects matrices transposed
tglMatrixMode(TGL_MODELVIEW);
tglLoadMatrixf(modelViewMatrix.getData());
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // TinyGL expects matrices transposed
tglMatrixMode(TGL_PROJECTION);
tglLoadMatrixf(projectionMatrix.getData());
Math::Matrix4 normalMatrix;
projectionMatrix.transpose();
modelViewMatrix.transpose();
normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
Math::Matrix4 mvp;
if (drawShadow) {
mvp = view * model;
mvp.transpose();
Math::Matrix4 modelInverse = model;
modelInverse.inverse();
lightDirection = getShadowLightDirection(lights, position, modelInverse.getRotation());
}
Common::Array<Face *> faces = _model->getFaces();
Common::Array<Material *> mats = _model->getMaterials();
const Common::Array<BoneNode *> &bones = _model->getBones();
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
const Material *material = mats[(*face)->materialId];
Math::Vector3d color;
const Gfx::Texture *tex = resolveTexture(material);
if (tex) {
tex->bind();
tglEnable(TGL_TEXTURE_2D);
} else {
tglBindTexture(TGL_TEXTURE_2D, 0);
tglDisable(TGL_TEXTURE_2D);
}
auto vertexIndices = _faceEBO[*face];
auto numVertexIndices = (*face)->vertexIndices.size();
for (uint32 i = 0; i < numVertexIndices; i++) {
if (tex) {
color = Math::Vector3d(1.0f, 1.0f, 1.0f);
} else {
color = Math::Vector3d(material->r, material->g, material->b);
}
uint32 index = vertexIndices[i];
auto vertex = _faceVBO[index];
uint32 bone1 = vertex.bone1;
uint32 bone2 = vertex.bone2;
Math::Vector3d position1 = Math::Vector3d(vertex.pos1x, vertex.pos1y, vertex.pos1z);
Math::Vector3d position2 = Math::Vector3d(vertex.pos2x, vertex.pos2y, vertex.pos2z);
Math::Vector3d bone1Position = Math::Vector3d(bones[bone1]->_animPos.x(),
bones[bone1]->_animPos.y(),
bones[bone1]->_animPos.z());
Math::Vector3d bone2Position = Math::Vector3d(bones[bone2]->_animPos.x(),
bones[bone2]->_animPos.y(),
bones[bone2]->_animPos.z());
Math::Quaternion bone1Rotation = Math::Quaternion(bones[bone1]->_animRot.x(),
bones[bone1]->_animRot.y(),
bones[bone1]->_animRot.z(),
bones[bone1]->_animRot.w());
Math::Quaternion bone2Rotation = Math::Quaternion(bones[bone2]->_animRot.x(),
bones[bone2]->_animRot.y(),
bones[bone2]->_animRot.z(),
bones[bone2]->_animRot.w());
float boneWeight = vertex.boneWeight;
Math::Vector3d normal = Math::Vector3d(vertex.normalx, vertex.normaly, vertex.normalz);
// Compute the vertex position in eye-space
bone1Rotation.transform(position1);
position1 += bone1Position;
bone2Rotation.transform(position2);
position2 += bone2Position;
Math::Vector3d modelPosition = Math::Vector3d::interpolate(position2, position1, boneWeight);
vertex.x = modelPosition.x();
vertex.y = modelPosition.y();
vertex.z = modelPosition.z();
Math::Vector4d modelEyePosition;
modelEyePosition = modelViewMatrix * Math::Vector4d(modelPosition.x(),
modelPosition.y(),
modelPosition.z(),
1.0);
// Compute the vertex normal in eye-space
Math::Vector3d n1 = normal;
bone1Rotation.transform(n1);
Math::Vector3d n2 = normal;
bone2Rotation.transform(n2);
Math::Vector3d modelNormal = Math::Vector3d(Math::Vector3d::interpolate(n2, n1, boneWeight)).getNormalized();
vertex.nx = modelNormal.x();
vertex.ny = modelNormal.y();
vertex.nz = modelNormal.z();
Math::Vector3d modelEyeNormal;
modelEyeNormal = normalMatrix.getRotation() * modelNormal;
modelEyeNormal.normalize();
if (drawShadow) {
Math::Vector3d shadowPosition = modelPosition + lightDirection * (-modelPosition.y() / lightDirection.y());
vertex.sx = shadowPosition.x();
vertex.sy = 0.0f;
vertex.sz = shadowPosition.z();
}
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
Math::Vector3d lightColor = ambient->color;
for (uint li = 0; li < lights.size() - 1; li++) {
const LightEntry *l = lights[li + 1];
switch (l->type) {
case LightEntry::kPoint: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
vertexToLight.normalize();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, vertexToLight));
lightColor += l->color * attn * incidence;
break;
}
case LightEntry::kDirectional: {
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, -l->eyeDirection));
lightColor += (l->color * incidence);
break;
}
case LightEntry::kSpot: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
vertexToLight.normalize();
float incidence = MAX(0.0f, modelEyeNormal.dotProduct(vertexToLight));
float cosAngle = MAX(0.0f, vertexToLight.dotProduct(-l->eyeDirection));
float cone = CLIP((cosAngle - l->innerConeAngle.getCosine()) / MAX(0.001f, l->outerConeAngle.getCosine() - l->innerConeAngle.getCosine()), 0.0f, 1.0f);
lightColor += l->color * attn * incidence * cone;
break;
}
default:
break;
}
}
lightColor.x() = CLIP(lightColor.x(), 0.0f, 1.0f);
lightColor.y() = CLIP(lightColor.y(), 0.0f, 1.0f);
lightColor.z() = CLIP(lightColor.z(), 0.0f, 1.0f);
color = color * lightColor;
vertex.r = color.x();
vertex.g = color.y();
vertex.b = color.z();
_faceVBO[index] = vertex;
}
tglEnableClientState(TGL_VERTEX_ARRAY);
tglEnableClientState(TGL_COLOR_ARRAY);
if (tex)
tglEnableClientState(TGL_TEXTURE_COORD_ARRAY);
tglEnableClientState(TGL_NORMAL_ARRAY);
tglVertexPointer(3, TGL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].x);
if (tex)
tglTexCoordPointer(2, TGL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].texS);
tglNormalPointer(TGL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].nx);
tglColorPointer(3, TGL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].r);
tglDrawElements(TGL_TRIANGLES, numVertexIndices, TGL_UNSIGNED_INT, vertexIndices);
tglDisableClientState(TGL_VERTEX_ARRAY);
tglDisableClientState(TGL_COLOR_ARRAY);
tglDisableClientState(TGL_TEXTURE_COORD_ARRAY);
tglDisableClientState(TGL_NORMAL_ARRAY);
}
if (drawShadow) {
tglEnable(TGL_BLEND);
tglEnable(TGL_STENCIL_TEST);
tglDisable(TGL_TEXTURE_2D);
tglColor4f(0.0f, 0.0f, 0.0f, 0.5f);
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
tglEnableClientState(TGL_VERTEX_ARRAY);
tglVertexPointer(3, TGL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].sx);
tglDrawElements(TGL_TRIANGLES, (*face)->vertexIndices.size(), TGL_UNSIGNED_INT, _faceEBO[*face]);
tglDisableClientState(TGL_VERTEX_ARRAY);
}
tglEnable(TGL_TEXTURE_2D);
tglDisable(TGL_BLEND);
tglDisable(TGL_STENCIL_TEST);
}
}
void TinyGLActorRenderer::clearVertices() {
delete[] _faceVBO;
_faceVBO = nullptr;
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
delete[] it->_value;
}
_faceEBO.clear();
}
void TinyGLActorRenderer::uploadVertices() {
_faceVBO = createModelVBO(_model);
Common::Array<Face *> faces = _model->getFaces();
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
_faceEBO[*face] = createFaceEBO(*face);
}
}
ActorVertex *TinyGLActorRenderer::createModelVBO(const Model *model) {
const Common::Array<VertNode *> &modelVertices = model->getVertices();
auto vertices = new ActorVertex[modelVertices.size()];
// Build a vertex array
int i = 0;
for (Common::Array<VertNode *>::const_iterator tri = modelVertices.begin(); tri != modelVertices.end(); ++tri, i++) {
vertices[i].pos1x = (*tri)->_pos1.x();
vertices[i].pos1y = (*tri)->_pos1.y();
vertices[i].pos1z = (*tri)->_pos1.z();
vertices[i].pos2x = (*tri)->_pos2.x();
vertices[i].pos2y = (*tri)->_pos2.y();
vertices[i].pos2z = (*tri)->_pos2.z();
vertices[i].bone1 = (*tri)->_bone1;
vertices[i].bone2 = (*tri)->_bone2;
vertices[i].boneWeight = (*tri)->_boneWeight;
vertices[i].normalx = (*tri)->_normal.x();
vertices[i].normaly = (*tri)->_normal.y();
vertices[i].normalz = (*tri)->_normal.z();
vertices[i].texS = -(*tri)->_texS;
vertices[i].texT = (*tri)->_texT;
}
return vertices;
}
uint32 *TinyGLActorRenderer::createFaceEBO(const Face *face) {
auto indices = new uint32[face->vertexIndices.size()];
for (uint32 index = 0; index < face->vertexIndices.size(); index++) {
indices[index] = face->vertexIndices[index];
}
return indices;
}
Math::Vector3d TinyGLActorRenderer::getShadowLightDirection(const LightEntryArray &lights,
const Math::Vector3d &actorPosition, Math::Matrix3 worldToModelRot) {
Math::Vector3d sumDirection;
bool hasLight = false;
// Compute the contribution from each lights
// The ambient light is skipped intentionally
for (uint i = 1; i < lights.size(); ++i) {
LightEntry *light = lights[i];
bool contributes = false;
Math::Vector3d lightDirection;
switch (light->type) {
case LightEntry::kPoint:
contributes = getPointLightContribution(light, actorPosition, lightDirection);
break;
case LightEntry::kDirectional:
contributes = getDirectionalLightContribution(light, lightDirection);
break;
case LightEntry::kSpot:
contributes = getSpotLightContribution(light, actorPosition, lightDirection);
break;
case LightEntry::kAmbient:
default:
break;
}
if (contributes) {
sumDirection += lightDirection;
hasLight = true;
}
}
if (hasLight) {
// Clip the horizontal length
Math::Vector2d horizontalProjection(sumDirection.x(), sumDirection.y());
float shadowLength = MIN(horizontalProjection.getMagnitude(), StarkScene->getMaxShadowLength());
horizontalProjection.normalize();
horizontalProjection *= shadowLength;
sumDirection.x() = horizontalProjection.getX();
sumDirection.y() = horizontalProjection.getY();
sumDirection.z() = -1;
} else {
// Cast from above by default
sumDirection.x() = 0;
sumDirection.y() = 0;
sumDirection.z() = -1;
}
//Transform the direction to the model space and pass to the shader
return worldToModelRot * sumDirection;
}
bool TinyGLActorRenderer::getPointLightContribution(LightEntry *light,
const Math::Vector3d &actorPosition, Math::Vector3d &direction, float weight) {
float distance = light->position.getDistanceTo(actorPosition);
if (distance > light->falloffFar) {
return false;
}
float factor;
if (distance > light->falloffNear) {
if (light->falloffFar - light->falloffNear > 1) {
factor = 1 - (distance - light->falloffNear) / (light->falloffFar - light->falloffNear);
} else {
factor = 0;
}
} else {
factor = 1;
}
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
if (factor <= 0 || brightness <= 0) {
return false;
}
direction = actorPosition - light->position;
direction.normalize();
direction *= factor * brightness * weight;
return true;
}
bool TinyGLActorRenderer::getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction) {
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
if (brightness <= 0) {
return false;
}
direction = light->direction;
direction.normalize();
direction *= brightness;
return true;
}
bool TinyGLActorRenderer::getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction) {
Math::Vector3d lightToActor = actorPosition - light->position;
lightToActor.normalize();
float cosAngle = MAX(0.0f, lightToActor.dotProduct(light->direction));
float cone = (cosAngle - light->innerConeAngle.getCosine()) /
MAX(0.001f, light->outerConeAngle.getCosine() - light->innerConeAngle.getCosine());
cone = CLIP(cone, 0.0f, 1.0f);
if (cone <= 0) {
return false;
}
return getPointLightContribution(light, actorPosition, direction, cone);
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,101 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_ACTOR_H
#define STARK_GFX_TINYGL_ACTOR_H
#include "engines/stark/gfx/renderentry.h"
#include "engines/stark/visual/actor.h"
#include "engines/stark/gfx/tinygl.h"
#include "graphics/tinygl/tinygl.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
namespace Stark {
namespace Gfx {
class TinyGLDriver;
struct _ActorVertex {
float pos1x;
float pos1y;
float pos1z;
float pos2x;
float pos2y;
float pos2z;
uint32 bone1;
uint32 bone2;
float boneWeight;
float normalx;
float normaly;
float normalz;
float texS;
float texT;
float x;
float y;
float z;
float nx;
float ny;
float nz;
float sx;
float sy;
float sz;
float r;
float g;
float b;
};
typedef _ActorVertex ActorVertex;
class TinyGLActorRenderer : public VisualActor {
public:
TinyGLActorRenderer(TinyGLDriver *gfx);
virtual ~TinyGLActorRenderer();
void render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<Face *, uint32 *> FaceBufferMap;
TinyGLDriver *_gfx;
ActorVertex *_faceVBO;
FaceBufferMap _faceEBO;
void clearVertices();
void uploadVertices();
ActorVertex *createModelVBO(const Model *model);
uint32 *createFaceEBO(const Face *face);
void setLightArrayUniform(const LightEntryArray &lights);
Math::Vector3d getShadowLightDirection(const LightEntryArray &lights, const Math::Vector3d &actorPosition, Math::Matrix3 worldToModelRot);
bool getPointLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
Math::Vector3d &direction, float weight = 1.0f);
bool getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction);
bool getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition, Math::Vector3d &direction);
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_ACTOR_H

View File

@@ -0,0 +1,70 @@
/* 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/gfx/tinyglbitmap.h"
#include "engines/stark/gfx/driver.h"
#include "common/system.h"
#include "graphics/surface.h"
namespace Stark {
namespace Gfx {
TinyGlBitmap::TinyGlBitmap() :
Bitmap() {
_blitImage = tglGenBlitImage();
}
TinyGlBitmap::~TinyGlBitmap() {
tglDeleteBlitImage(_blitImage);
}
void TinyGlBitmap::bind() const {
}
void TinyGlBitmap::update(const Graphics::Surface *surface, const byte *palette) {
_width = surface->w;
_height = surface->h;
if (palette) {
// TinyGL doesn't currently support images with palettes, so we handle conversion here.
Graphics::Surface *convertedSurface = surface->convertTo(getBestPixelFormat(), palette);
tglUploadBlitImage(_blitImage, *convertedSurface, 0, false);
convertedSurface->free();
delete convertedSurface;
} else {
tglUploadBlitImage(_blitImage, *surface, 0, false);
}
}
void TinyGlBitmap::setSamplingFilter(Bitmap::SamplingFilter filter) {
}
Graphics::PixelFormat TinyGlBitmap::getBestPixelFormat() const {
return g_system->getScreenFormat();
}
TinyGL::BlitImage *TinyGlBitmap::getBlitImage() const {
return _blitImage;
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_BITMAP_H
#define STARK_GFX_TINYGL_BITMAP_H
#include "engines/stark/gfx/bitmap.h"
#include "graphics/tinygl/tinygl.h"
namespace Stark {
namespace Gfx {
/**
* A TinyGL bitmap wrapper
*/
class TinyGlBitmap : public Bitmap {
public:
TinyGlBitmap();
virtual ~TinyGlBitmap();
// Bitmap API
void bind() const override;
TinyGL::BlitImage *getBlitImage() const;
void update(const Graphics::Surface *surface, const byte *palette = nullptr) override;
void setSamplingFilter(SamplingFilter filter) override;
Graphics::PixelFormat getBestPixelFormat() const override;
protected:
TinyGL::BlitImage *_blitImage;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_TEXTURE_H

View File

@@ -0,0 +1,77 @@
/* 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/gfx/tinyglfade.h"
#include "engines/stark/gfx/tinygl.h"
namespace Stark {
namespace Gfx {
static const TGLfloat fadeVertices[] = {
// X Y
-1.0f, 1.0f,
1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
};
TinyGLFadeRenderer::TinyGLFadeRenderer(TinyGLDriver *gfx) :
FadeRenderer(),
_gfx(gfx) {
}
TinyGLFadeRenderer::~TinyGLFadeRenderer() {
}
void TinyGLFadeRenderer::render(float fadeLevel) {
_gfx->start2DMode();
tglMatrixMode(TGL_PROJECTION);
tglPushMatrix();
tglLoadIdentity();
tglMatrixMode(TGL_MODELVIEW);
tglPushMatrix();
tglLoadIdentity();
tglDisable(TGL_TEXTURE_2D);
tglColor4f(0.0f, 0.0f, 0.0f, 1.0f - fadeLevel);
tglEnableClientState(TGL_VERTEX_ARRAY);
tglVertexPointer(2, TGL_FLOAT, 2 * sizeof(TGLfloat), &fadeVertices[0]);
tglDrawArrays(TGL_TRIANGLE_STRIP, 0, 4);
tglDisableClientState(TGL_VERTEX_ARRAY);
tglMatrixMode(TGL_MODELVIEW);
tglPopMatrix();
tglMatrixMode(TGL_PROJECTION);
tglPopMatrix();
_gfx->end2DMode();
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,52 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_FADE_H
#define STARK_GFX_TINYGL_FADE_H
#include "engines/stark/gfx/faderenderer.h"
#include "graphics/tinygl/tinygl.h"
namespace Stark {
namespace Gfx {
class TinyGLDriver;
/**
* A programmable pipeline TinyGL fade screen renderer
*/
class TinyGLFadeRenderer : public FadeRenderer {
public:
TinyGLFadeRenderer(TinyGLDriver *gfx);
~TinyGLFadeRenderer();
// FadeRenderer API
void render(float fadeLevel);
private:
TinyGLDriver *_gfx;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_FADE_H

View File

@@ -0,0 +1,236 @@
/* 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/gfx/tinyglprop.h"
#include "engines/stark/gfx/texture.h"
#include "engines/stark/formats/biffmesh.h"
#include "engines/stark/scene.h"
#include "engines/stark/services/services.h"
namespace Stark {
namespace Gfx {
TinyGLPropRenderer::TinyGLPropRenderer(TinyGLDriver *gfx) :
VisualProp(),
_gfx(gfx),
_faceVBO(nullptr),
_modelIsDirty(true) {
}
TinyGLPropRenderer::~TinyGLPropRenderer() {
clearVertices();
}
void TinyGLPropRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
if (_modelIsDirty) {
clearVertices();
uploadVertices();
_modelIsDirty = false;
}
_gfx->set3DMode();
Math::Matrix4 model = getModelMatrix(position, direction);
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // TinyGL expects matrices transposed
tglMatrixMode(TGL_MODELVIEW);
tglLoadMatrixf(modelViewMatrix.getData());
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // TinyGL expects matrices transposed
tglMatrixMode(TGL_PROJECTION);
tglLoadMatrixf(projectionMatrix.getData());
Math::Matrix4 normalMatrix;
projectionMatrix.transpose();
modelViewMatrix.transpose();
normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
const Common::Array<Face> &faces = _model->getFaces();
const Common::Array<Material> &materials = _model->getMaterials();
for (Common::Array<Face>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
const Material &material = materials[face->materialId];
Math::Vector3d color;
const Gfx::Texture *tex = _texture->getTexture(material.texture);
if (tex) {
tex->bind();
tglEnable(TGL_TEXTURE_2D);
} else {
tglBindTexture(TGL_TEXTURE_2D, 0);
tglDisable(TGL_TEXTURE_2D);
}
auto vertexIndices = _faceEBO[face];
auto numVertexIndices = (face)->vertexIndices.size();
for (uint32 i = 0; i < numVertexIndices; i++) {
uint32 index = vertexIndices[i];
auto vertex = _faceVBO[index];
if (tex) {
color = Math::Vector3d(1.0f, 1.0f, 1.0f);
if (material.doubleSided) {
vertex.texS = vertex.stexS;
vertex.texT = 1.0f - vertex.stexT;
} else {
vertex.texS = 1.0f - vertex.stexS;
vertex.texT = 1.0f - vertex.stexT;
}
} else {
color = Math::Vector3d(material.r, material.g, material.b);
}
Math::Vector4d modelEyePosition = modelViewMatrix * Math::Vector4d(vertex.x, vertex.y, vertex.z, 1.0);
Math::Vector3d modelEyeNormal = normalMatrix.getRotation() * Math::Vector3d(vertex.nx, vertex.ny, vertex.nz);
modelEyeNormal.normalize();
static const uint maxLights = 10;
assert(lights.size() >= 1);
assert(lights.size() <= maxLights);
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
Math::Vector3d lightColor = ambient->color;
for (uint li = 0; li < lights.size() - 1; li++) {
const LightEntry *l = lights[li + 1];
switch (l->type) {
case LightEntry::kPoint: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
vertexToLight.normalize();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, vertexToLight));
lightColor += l->color * attn * incidence;
break;
}
case LightEntry::kDirectional: {
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, -l->eyeDirection));
lightColor += (l->color * incidence);
break;
}
case LightEntry::kSpot: {
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
float dist = vertexToLight.length();
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
vertexToLight.normalize();
float incidence = MAX(0.0f, modelEyeNormal.dotProduct(vertexToLight));
float cosAngle = MAX(0.0f, vertexToLight.dotProduct(-l->eyeDirection));
float cone = CLIP((cosAngle - l->innerConeAngle.getCosine()) / MAX(0.001f, l->outerConeAngle.getCosine() - l->innerConeAngle.getCosine()), 0.0f, 1.0f);
lightColor += l->color * attn * incidence * cone;
break;
}
default:
break;
}
}
lightColor.x() = CLIP(lightColor.x(), 0.0f, 1.0f);
lightColor.y() = CLIP(lightColor.y(), 0.0f, 1.0f);
lightColor.z() = CLIP(lightColor.z(), 0.0f, 1.0f);
color = color * lightColor;
vertex.r = color.x();
vertex.g = color.y();
vertex.b = color.z();
_faceVBO[index] = vertex;
}
tglEnableClientState(TGL_VERTEX_ARRAY);
tglEnableClientState(TGL_COLOR_ARRAY);
if (tex)
tglEnableClientState(TGL_TEXTURE_COORD_ARRAY);
tglEnableClientState(TGL_NORMAL_ARRAY);
tglVertexPointer(3, TGL_FLOAT, sizeof(PropVertex), &_faceVBO[0].x);
if (tex)
tglTexCoordPointer(2, TGL_FLOAT, sizeof(PropVertex), &_faceVBO[0].texS);
tglNormalPointer(TGL_FLOAT, sizeof(PropVertex), &_faceVBO[0].nx);
tglColorPointer(3, TGL_FLOAT, sizeof(PropVertex), &_faceVBO[0].r);
tglDrawElements(TGL_TRIANGLES, face->vertexIndices.size(), TGL_UNSIGNED_INT, vertexIndices);
tglDisableClientState(TGL_VERTEX_ARRAY);
tglDisableClientState(TGL_COLOR_ARRAY);
tglDisableClientState(TGL_TEXTURE_COORD_ARRAY);
tglDisableClientState(TGL_NORMAL_ARRAY);
}
}
void TinyGLPropRenderer::clearVertices() {
delete[] _faceVBO;
_faceVBO = nullptr;
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
delete[] it->_value;
}
_faceEBO.clear();
}
void TinyGLPropRenderer::uploadVertices() {
_faceVBO = createFaceVBO();
const Common::Array<Face> &faces = _model->getFaces();
for (Common::Array<Face>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
_faceEBO[face] = createFaceEBO(face);
}
}
PropVertex *TinyGLPropRenderer::createFaceVBO() {
const Common::Array<Formats::BiffMesh::Vertex> &modelVertices = _model->getVertices();
auto vertices = new PropVertex[modelVertices.size()];
// Build a vertex array
for (uint32 i = 0; i < modelVertices.size(); i++) {
vertices[i].x = modelVertices[i].position.x();
vertices[i].y = modelVertices[i].position.y();
vertices[i].z = modelVertices[i].position.z();
vertices[i].nx = modelVertices[i].normal.x();
vertices[i].ny = modelVertices[i].normal.y();
vertices[i].nz = modelVertices[i].normal.z();
vertices[i].stexS = modelVertices[i].texturePosition.x();
vertices[i].stexT = modelVertices[i].texturePosition.y();
}
return vertices;
}
uint32 *TinyGLPropRenderer::createFaceEBO(const Face *face) {
auto indices = new uint32[face->vertexIndices.size()];
for (uint32 index = 0; index < face->vertexIndices.size(); index++) {
indices[index] = face->vertexIndices[index];
}
return indices;
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,82 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_RENDERED_H
#define STARK_GFX_TINYGL_RENDERED_H
#include "engines/stark/model/model.h"
#include "engines/stark/visual/prop.h"
#include "engines/stark/gfx/tinygl.h"
#include "graphics/tinygl/tinygl.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
namespace Stark {
namespace Gfx {
class Driver;
struct _PropVertex {
float x;
float y;
float z;
float nx;
float ny;
float nz;
float stexS;
float stexT;
float texS;
float texT;
float r;
float g;
float b;
};
typedef _PropVertex PropVertex;
class TinyGLPropRenderer : public VisualProp {
public:
explicit TinyGLPropRenderer(TinyGLDriver *gfx);
~TinyGLPropRenderer() override;
void render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<const Face *, uint32 *> FaceBufferMap;
TinyGLDriver *_gfx;
bool _modelIsDirty;
PropVertex *_faceVBO;
FaceBufferMap _faceEBO;
void clearVertices();
void uploadVertices();
PropVertex *createFaceVBO();
uint32 *createFaceEBO(const Face *face);
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_S_RENDERED_H

View File

@@ -0,0 +1,157 @@
/* 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/gfx/tinyglsurface.h"
#include "engines/stark/gfx/tinyglbitmap.h"
#include "engines/stark/gfx/color.h"
#include "graphics/tinygl/tinygl.h"
namespace Stark {
namespace Gfx {
TinyGLSurfaceRenderer::TinyGLSurfaceRenderer(TinyGLDriver *gfx) :
SurfaceRenderer(),
_gfx(gfx) {
}
TinyGLSurfaceRenderer::~TinyGLSurfaceRenderer() {
}
void TinyGLSurfaceRenderer::render(const Bitmap *bitmap, const Common::Point &dest) {
render(bitmap, dest, bitmap->width(), bitmap->height());
}
void TinyGLSurfaceRenderer::render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) {
if (width == 0 || height == 0)
return;
_gfx->start2DMode();
Math::Vector2d sizeWH;
if (_noScalingOverride) {
sizeWH = normalizeCurrentCoordinates(width, height);
} else {
sizeWH = normalizeOriginalCoordinates(width, height);
}
auto verOffsetXY = normalizeOriginalCoordinates(dest.x, dest.y);
auto nativeViewport = _gfx->getViewport();
auto viewport = Math::Vector2d(nativeViewport.width(), nativeViewport.height());
auto blitImage = ((TinyGlBitmap *)const_cast<Bitmap *>(bitmap))->getBlitImage();
int blitImageWidth, blitImageHeight;
tglGetBlitImageSize(blitImage, blitImageWidth, blitImageHeight);
int posX = viewport.getX() * verOffsetXY.getX() + nativeViewport.left;
int posY = viewport.getY() * verOffsetXY.getY() + nativeViewport.top;
TinyGL::BlitTransform transform(posX, posY);
// W/A for not clipped bitmaps in prompt dialog
if (width == 256 && height == 256) {
blitImageHeight = viewport.getY() - dest.y;
blitImageWidth = viewport.getX() - dest.x;
}
transform.sourceRectangle(0, 0, blitImageWidth, blitImageHeight);
transform.tint(1.0, 1.0 - _fadeLevel, 1.0 - _fadeLevel, 1.0 - _fadeLevel);
tglBlit(blitImage, transform);
_gfx->end2DMode();
}
void TinyGLSurfaceRenderer::fill(const Color &color, const Common::Point &dest, uint width, uint height) {
_gfx->start2DMode();
SurfaceVertex vertices[4] = {};
convertToVertices(vertices, dest, width, height);
tglMatrixMode(TGL_PROJECTION);
tglPushMatrix();
tglLoadIdentity();
tglMatrixMode(TGL_MODELVIEW);
tglPushMatrix();
tglLoadIdentity();
tglDisable(TGL_TEXTURE_2D);
tglEnableClientState(TGL_VERTEX_ARRAY);
tglVertexPointer(2, TGL_FLOAT, sizeof(SurfaceVertex), &vertices[0].x);
tglColor4f((color.r / 255.0f) - _fadeLevel, (color.g / 255.0f) - _fadeLevel, (color.b / 255.0f) - _fadeLevel, color.a / 255.0f);
tglDrawArrays(TGL_TRIANGLE_STRIP, 0, 4);
tglDisableClientState(TGL_VERTEX_ARRAY);
tglMatrixMode(TGL_MODELVIEW);
tglPopMatrix();
tglMatrixMode(TGL_PROJECTION);
tglPopMatrix();
_gfx->end2DMode();
}
void TinyGLSurfaceRenderer::convertToVertices(SurfaceVertex *vertices, const Common::Point &dest, uint width, uint height) const {
const Math::Vector2d surfaceVertices[] = {
// X Y
{ 0.0f, 0.0f },
{ 1.0f, 0.0f },
{ 0.0f, 1.0f },
{ 1.0f, 1.0f },
};
Math::Vector2d verSizeWH;
if (_noScalingOverride) {
verSizeWH = normalizeCurrentCoordinates(width, height);
} else {
verSizeWH = normalizeOriginalCoordinates(width, height);
}
auto verOffsetXY = normalizeOriginalCoordinates(dest.x, dest.y);
auto nativeViewport = _gfx->getViewport();
auto viewport = Math::Vector2d(nativeViewport.width(), nativeViewport.height());
for (int32 v = 0; v < 4; v++) {
Math::Vector2d pos = verOffsetXY + (surfaceVertices[v] * verSizeWH);
if (_snapToGrid) {
// Align vertex coordinates to the native pixel grid
// This ensures text does not get garbled by nearest neighbors scaling
pos.setX(floor(pos.getX() * viewport.getX() + 0.5) / viewport.getX());
pos.setY(floor(pos.getY() * viewport.getY() + 0.5) / viewport.getY());
}
// position coords
vertices[v].x = pos.getX() * 2.0 - 1.0;
vertices[v].y = -1.0 * (pos.getY() * 2.0 - 1.0);
}
}
Math::Vector2d TinyGLSurfaceRenderer::normalizeOriginalCoordinates(int x, int y) const {
Common::Rect viewport = _gfx->getUnscaledViewport();
return Math::Vector2d(x / (float)viewport.width(), y / (float)viewport.height());
}
Math::Vector2d TinyGLSurfaceRenderer::normalizeCurrentCoordinates(int x, int y) const {
Common::Rect viewport = _gfx->getViewport();
return Math::Vector2d(x / (float)viewport.width(), y / (float)viewport.height());
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,67 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_SURFACE_H
#define STARK_GFX_TINYGL_SURFACE_H
#include "engines/stark/gfx/surfacerenderer.h"
#include "engines/stark/gfx/tinygl.h"
#include "graphics/tinygl/tinygl.h"
#include "math/vector2d.h"
namespace Stark {
namespace Gfx {
class TinyGLDriver;
class Bitmap;
/**
* A programmable pipeline TinyGL surface renderer
*/
class TinyGLSurfaceRenderer : public SurfaceRenderer {
public:
TinyGLSurfaceRenderer(TinyGLDriver *gfx);
virtual ~TinyGLSurfaceRenderer();
// SurfaceRenderer API
void render(const Bitmap *bitmap, const Common::Point &dest) override;
void render(const Bitmap *bitmap, const Common::Point &dest, uint width, uint height) override;
void fill(const Color &color, const Common::Point &dest, uint width, uint height) override;
private:
struct SurfaceVertex {
float x;
float y;
};
Math::Vector2d normalizeOriginalCoordinates(int x, int y) const;
Math::Vector2d normalizeCurrentCoordinates(int x, int y) const;
void convertToVertices(SurfaceVertex *vertices, const Common::Point &dest, uint width, uint height) const;
TinyGLDriver *_gfx;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_SURFACE_H

View File

@@ -0,0 +1,86 @@
/* 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/gfx/tinygltexture.h"
#include "engines/stark/gfx/driver.h"
#include "graphics/surface.h"
namespace Stark {
namespace Gfx {
TinyGlTexture::TinyGlTexture() :
Texture(),
_id(0),
_levelCount(0) {
tglGenTextures(1, &_id);
bind();
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MIN_FILTER, TGL_NEAREST);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MAG_FILTER, TGL_NEAREST);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_S, TGL_CLAMP_TO_EDGE);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_T, TGL_CLAMP_TO_EDGE);
}
TinyGlTexture::~TinyGlTexture() {
tglDeleteTextures(1, &_id);
}
void TinyGlTexture::bind() const {
tglBindTexture(TGL_TEXTURE_2D, _id);
}
void TinyGlTexture::updateLevel(uint32 level, const Graphics::Surface *surface, const byte *palette) {
if (surface->format != Driver::getRGBAPixelFormat()) {
// Convert the surface to texture format
Graphics::Surface *convertedSurface = surface->convertTo(Driver::getRGBAPixelFormat(), palette);
tglTexImage2D(TGL_TEXTURE_2D, 0, TGL_RGBA, convertedSurface->w, convertedSurface->h, 0, TGL_RGBA, TGL_UNSIGNED_BYTE, (char *)(convertedSurface->getPixels()));
convertedSurface->free();
delete convertedSurface;
} else {
// Convert the surface to texture format
tglTexImage2D(TGL_TEXTURE_2D, 0, TGL_RGBA, surface->w, surface->h, 0, TGL_RGBA, TGL_UNSIGNED_BYTE, const_cast<void *>(surface->getPixels()));
}
}
void TinyGlTexture::setLevelCount(uint32 count) {
_levelCount = count;
if (count >= 1) {
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_S, TGL_MIRRORED_REPEAT);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_T, TGL_MIRRORED_REPEAT);
}
}
void TinyGlTexture::addLevel(uint32 level, const Graphics::Surface *surface, const byte *palette) {
assert(level < _levelCount);
if (level == 0) {
updateLevel(level, surface, palette);
}
}
} // End of namespace Gfx
} // End of namespace Stark

View File

@@ -0,0 +1,55 @@
/* 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/>.
*
*/
#ifndef STARK_GFX_TINYGL_TEXTURE_H
#define STARK_GFX_TINYGL_TEXTURE_H
#include "engines/stark/gfx/texture.h"
#include "graphics/tinygl/tinygl.h"
namespace Stark {
namespace Gfx {
/**
* A TinyGL texture wrapper
*/
class TinyGlTexture : public Texture {
public:
TinyGlTexture();
virtual ~TinyGlTexture();
// Texture API
void bind() const override;
void setLevelCount(uint32 count) override;
void addLevel(uint32 level, const Graphics::Surface *surface, const byte *palette = nullptr) override;
protected:
void updateLevel(uint32 level, const Graphics::Surface *surface, const byte *palette = nullptr);
TGLuint _id;
uint32 _levelCount;
};
} // End of namespace Gfx
} // End of namespace Stark
#endif // STARK_GFX_TINYGL_TEXTURE_H

View File

@@ -0,0 +1,355 @@
/* 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/advancedDetector.h"
#include "engines/stark/savemetadata.h"
#include "engines/stark/stark.h"
#include "engines/stark/services/stateprovider.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/translation.h"
namespace Stark {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_ASSETS_MOD,
{
_s("Load modded assets"),
_s("Enable loading of external replacement assets."),
"enable_assets_mod",
true,
0,
0
}
},
{
GAMEOPTION_LINEAR_FILTERING,
{
_s("Enable linear filtering of the backgrounds images"),
_s("When linear filtering is enabled the background graphics are smoother in full screen mode, at the cost of some details."),
"use_linear_filtering",
true,
0,
0
}
},
{
GAMEOPTION_FONT_ANTIALIASING,
{
_s("Enable font anti-aliasing"),
_s("When font anti-aliasing is enabled, the text is smoother."),
"enable_font_antialiasing",
true,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
class StarkMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override {
return "stark";
}
Common::KeymapArray initKeymaps(const char *target) const override;
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
bool hasFeature(MetaEngineFeature f) const override {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportPlayTime) ||
(f == kSavesSupportCreationDate);
}
int getMaximumSaveSlot() const override {
return 999;
}
SaveStateList listSaves(const char *target) const override {
Common::StringArray filenames = StarkEngine::listSaveNames(target);
SaveStateList saveList;
for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) {
int slot = StarkEngine::getSaveNameSlot(target, *filename);
// Read the description from the save
Common::String description;
Common::InSaveFile *save = g_system->getSavefileManager()->openForLoading(*filename);
if (save) {
StateReadStream stream(save);
description = stream.readString();
}
saveList.push_back(SaveStateDescriptor(this, slot, description));
}
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override {
Common::String filename = StarkEngine::formatSaveName(target, slot);
Common::InSaveFile *save = g_system->getSavefileManager()->openForLoading(filename);
if (!save) {
return SaveStateDescriptor();
}
SaveStateDescriptor descriptor;
descriptor.setSaveSlot(slot);
SaveMetadata metadata;
Common::ErrorCode readError = metadata.read(save, filename);
if (readError != Common::kNoError) {
delete save;
return descriptor;
}
descriptor.setDescription(metadata.description);
if (metadata.version >= 9) {
Graphics::Surface *thumb = metadata.readGameScreenThumbnail(save);
descriptor.setThumbnail(thumb);
descriptor.setPlayTime(metadata.totalPlayTime);
descriptor.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay);
descriptor.setSaveTime(metadata.saveHour, metadata.saveMinute);
}
if (metadata.version >= 13) {
descriptor.setAutosave(metadata.isAutoSave);
}
delete save;
return descriptor;
}
bool removeSaveState(const char *target, int slot) const override {
Common::String filename = StarkEngine::formatSaveName(target, slot);
return g_system->getSavefileManager()->removeSavefile(filename);
}
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override {
*engine = new StarkEngine(syst, desc);
return Common::kNoError;
}
Common::String getSavegameFile(int saveGameIdx, const char *target) const override {
if (!target)
target = getName();
if (saveGameIdx == kSavegameFilePattern)
return Common::String::format("%s-###.tlj", target);
else
return StarkEngine::formatSaveName(target, saveGameIdx);
}
};
Common::KeymapArray StarkMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace Stark;
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "stark-default", _("Default keymappings"));
Keymap *gameKeyMap = new Keymap(Keymap::kKeymapTypeGame, "game-shortcuts", _("Game keymappings"));
Action *act;
act = new Action(kStandardActionLeftClick, _("Left click"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action(kStandardActionRightClick, _("Right click"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
// I18N: Opens in-game Diary
act = new Action("DIARYMENU", _("Diary menu"));
act->setCustomEngineActionEvent(kActionDiaryMenu);
act->addDefaultInputMapping("F1");
act->addDefaultInputMapping("JOY_X");
gameKeyMap->addAction(act);
act = new Action("SAVEGAME", _("Save game"));
act->setCustomEngineActionEvent(kActionSaveGame);
act->addDefaultInputMapping("F2");
gameKeyMap->addAction(act);
act = new Action("LOADGAME", _("Load game"));
act->setCustomEngineActionEvent(kActionLoadGame);
act->addDefaultInputMapping("F3");
gameKeyMap->addAction(act);
// I18N: Opens in-game conversation log
act = new Action("CONVOLOG", _("Conversation log"));
act->setCustomEngineActionEvent(kActionConversationLog);
act->addDefaultInputMapping("F4");
gameKeyMap->addAction(act);
// I18N: Opens in-game Diary. April is the female protagonist name
act = new Action("APRILSDIARY", _("April's diary (initially disabled)"));
act->setCustomEngineActionEvent(kActionAprilsDiary);
act->addDefaultInputMapping("F5");
gameKeyMap->addAction(act);
act = new Action("VIDREPLAY", _("Video replay"));
act->setCustomEngineActionEvent(kActionVideoReplay);
act->addDefaultInputMapping("F6");
gameKeyMap->addAction(act);
act = new Action("GAMESETTINGS", _("Game settings"));
act->setCustomEngineActionEvent(kActionGameSettings);
act->addDefaultInputMapping("F7");
gameKeyMap->addAction(act);
act = new Action("SAVESCRNSHOT", _("Save screenshot"));
act->setCustomEngineActionEvent(kActionSaveScreenshot);
act->addDefaultInputMapping("F8");
gameKeyMap->addAction(act);
act = new Action("TOGGLESUBS", _("Toggle subtitles"));
act->setCustomEngineActionEvent(kActionToggleSubtitles);
act->addDefaultInputMapping("F9");
gameKeyMap->addAction(act);
act = new Action("QUITTOMENU", _("Quit to menu"));
act->setCustomEngineActionEvent(kActionQuitToMenu);
act->addDefaultInputMapping("F10");
gameKeyMap->addAction(act);
act = new Action("CYCLEBACK", _("Cycle back through inventory cursor items"));
act->setCustomEngineActionEvent(kActionCycleForwardInventory);
act->addDefaultInputMapping("a");
act->addDefaultInputMapping("JOY_LEFT_TRIGGER");
gameKeyMap->addAction(act);
act = new Action("CYCLEFORWARD", _("Cycle forward through inventory cursor items"));
act->setCustomEngineActionEvent(kActionCycleBackInventory);
act->addDefaultInputMapping("s");
act->addDefaultInputMapping("JOY_RIGHT_TRIGGER");
gameKeyMap->addAction(act);
act = new Action("INVENTORY", _("Inventory"));
act->setCustomEngineActionEvent(kActionInventory);
act->addDefaultInputMapping("i");
act->addDefaultInputMapping("JOY_Y");
gameKeyMap->addAction(act);
// I18N: A popup on screen shows shows the exits
act = new Action("DISPLAYEXITS", _("Display all exits on current location"));
act->setCustomEngineActionEvent(kActionDisplayExits);
act->addDefaultInputMapping("x");
act->addDefaultInputMapping("JOY_RIGHT_STICK");
gameKeyMap->addAction(act);
act = new Action("EXITGAME", _("Quit game"));
act->setCustomEngineActionEvent(kActionExitGame);
act->addDefaultInputMapping("A+x");
act->addDefaultInputMapping("A+q");
gameKeyMap->addAction(act);
act = new Action("PAUSE", _("Pause game"));
act->setCustomEngineActionEvent(kActionPause);
act->addDefaultInputMapping("p");
act->addDefaultInputMapping("JOY_LEFT_STICK");
gameKeyMap->addAction(act);
act = new Action("SCROLLUPINV", _("Scroll up in inventory"));
act->setCustomEngineActionEvent(kActionInventoryScrollUp);
act->addDefaultInputMapping("PAGEUP");
act->addDefaultInputMapping("UP");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("SCROLLDOWNINV", _("Scroll down in inventory"));
act->setCustomEngineActionEvent(kActionInventoryScrollDown);
act->addDefaultInputMapping("PAGEDOWN");
act->addDefaultInputMapping("DOWN");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("SCROLLUPDILOG", _("Scroll up in your dialogs"));
act->setCustomEngineActionEvent(kActionDialogueScrollUp);
act->addDefaultInputMapping("PAGEUP");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("SCROLLDOWNDILOG", _("Scroll down in your dialogs"));
act->setCustomEngineActionEvent(kActionDialogueScrollDown);
act->addDefaultInputMapping("PAGEDOWN");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("SCROLLUPINVPREVDILOG", _("Go to next dialog"));
act->setCustomEngineActionEvent(kActionNextDialogue);
act->addDefaultInputMapping("DOWN");
gameKeyMap->addAction(act);
act = new Action("SCROLLDOWNINVNEXTDILOG", _("Go to previous dialogs"));
act->setCustomEngineActionEvent(kActionPrevDialogue);
act->addDefaultInputMapping("UP");
gameKeyMap->addAction(act);
act = new Action("SELECTDILOG", _("Select dialog"));
act->setCustomEngineActionEvent(kActionSelectDialogue);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("JOY_RIGHT");
gameKeyMap->addAction(act);
act = new Action("SKIP", _("Skip video sequence or dialog"));
act->setCustomEngineActionEvent(kActionSkip);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_BACK");
gameKeyMap->addAction(act);
KeymapArray keymaps(2);
keymaps[0] = engineKeyMap;
keymaps[1] = gameKeyMap;
return keymaps;
}
} // End of namespace Stark
#if PLUGIN_ENABLED_DYNAMIC(STARK)
REGISTER_PLUGIN_DYNAMIC(STARK, PLUGIN_TYPE_ENGINE, Stark::StarkMetaEngine);
#else
REGISTER_PLUGIN_STATIC(STARK, PLUGIN_TYPE_ENGINE, Stark::StarkMetaEngine);
#endif

View File

@@ -0,0 +1,198 @@
/* 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/model/animhandler.h"
#include "engines/stark/model/model.h"
#include "engines/stark/model/skeleton_anim.h"
namespace Stark {
AnimHandler::AnimHandler() :
_model(nullptr),
_anim(nullptr),
_animTime(-1),
_framesBeforeCandidateReady(0),
_candidateAnim(nullptr),
_candidateAnimTime(-1),
_blendAnim(nullptr),
_blendAnimTime(-1),
_blendTimeRemaining(0) {
}
AnimHandler::~AnimHandler() {
}
void AnimHandler::setAnim(SkeletonAnim *anim) {
if (_candidateAnim == anim) {
return;
}
if (_anim == anim) {
// If we already have the correct anim, clean any candidates
// that may have been set but not yet enacted as the active anim.
_candidateAnim = nullptr;
_candidateAnimTime = -1;
_framesBeforeCandidateReady = 0;
return;
}
// Don't use new animations the first frame they are set.
// Scripts may change animation the very next frame,
// causing animations to blend with animations that
// were only visible for one frame, leading to animation
// jumps. Instead store them as candidates.
_framesBeforeCandidateReady = 2; // 2 because we are at the end of the frame
_candidateAnim = anim;
_candidateAnimTime = 0;
}
void AnimHandler::setModel(Model *model) {
_model = model;
}
void AnimHandler::setNode(uint32 time, BoneNode *bone, const BoneNode *parent) {
const Common::Array<BoneNode *> &bones = _model->getBones();
if (_blendTimeRemaining <= 0) {
_anim->getCoordForBone(time, bone->_idx, bone->_animPos, bone->_animRot);
} else {
// Blend the coordinates of the previous and the current animation
Math::Vector3d previousAnimPos, animPos;
Math::Quaternion previousAnimRot, animRot;
_blendAnim->getCoordForBone(_blendAnimTime, bone->_idx, previousAnimPos, previousAnimRot);
_anim->getCoordForBone(time, bone->_idx, animPos, animRot);
float blendingRatio = 1.0 - _blendTimeRemaining / (float)_blendDuration;
bone->_animPos = previousAnimPos + (animPos - previousAnimPos) * blendingRatio;
bone->_animRot = previousAnimRot.slerpQuat(animRot, blendingRatio);
}
if (parent) {
parent->_animRot.transform(bone->_animPos);
bone->_animPos = parent->_animPos + bone->_animPos;
bone->_animRot = parent->_animRot * bone->_animRot;
}
for (uint i = 0; i < bone->_children.size(); ++i) {
setNode(time, bones[bone->_children[i]], bone);
}
}
void AnimHandler::animate(uint32 time) {
if (!_anim && _candidateAnim) {
// This is the first time we animate this item.
enactCandidate();
}
if (_candidateAnim && _anim && _anim->getBoneCount() != _model->getBones().size()) {
// We changed to an incompatible model
enactCandidate();
// And the anim we were previously blending with is incompatible as well
if (_blendAnim && _blendAnim->getBoneCount() != _model->getBones().size()) {
stopBlending();
}
}
if (_candidateAnim && _framesBeforeCandidateReady > 0) {
_candidateAnimTime = time;
_framesBeforeCandidateReady--;
// We need to animate here, because the model may have
// changed from under us.
const Common::Array<BoneNode *> &bones = _model->getBones();
setNode(_animTime, bones[0], nullptr);
return;
}
if (_candidateAnim && _framesBeforeCandidateReady <= 0) {
if (_anim) {
startBlending();
}
enactCandidate();
}
int32 deltaTime = time - _animTime;
if (deltaTime < 0 || time > _blendDuration / 2) {
deltaTime = 33;
}
updateBlending(deltaTime);
// Start at root bone
// For each child
// - Set childs animation coordinate
// - Process that childs children
const Common::Array<BoneNode *> &bones = _model->getBones();
if (deltaTime >= 0) {
setNode(time, bones[0], nullptr);
_animTime = time;
}
}
void AnimHandler::enactCandidate() {
_anim = _candidateAnim;
_animTime = _candidateAnimTime;
_candidateAnim = nullptr;
_candidateAnimTime = -1;
_framesBeforeCandidateReady = 0;
}
void AnimHandler::startBlending() {
_blendTimeRemaining = _blendDuration;
_blendAnim = _anim;
_blendAnimTime = _animTime;
}
void AnimHandler::updateBlending(int32 deltaTime) {
_blendTimeRemaining -= deltaTime;
if (_blendTimeRemaining > 0) {
// If we are blending, also update the previous animation's time
_blendAnimTime += deltaTime;
if (_blendAnimTime >= (int32) _blendAnim->getLength()) {
_blendAnimTime = _blendAnim->getLength() - 1;
}
} else {
// Otherwise make sure blending is not enabled
stopBlending();
}
}
void AnimHandler::stopBlending() {
_blendAnim = nullptr;
_blendAnimTime = -1;
_blendTimeRemaining = 0;
}
void AnimHandler::resetBlending() {
stopBlending();
if (_candidateAnim) {
enactCandidate();
}
}
} // End of namespace Stark

View File

@@ -0,0 +1,81 @@
/* 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/>.
*
*/
#ifndef STARK_MODEL_ANIM_HANDLER_H
#define STARK_MODEL_ANIM_HANDLER_H
#include "common/scummsys.h"
namespace Stark {
class Model;
class BoneNode;
class SkeletonAnim;
/**
* Animate a skeletal model's bones according to an animation
*/
class AnimHandler {
public:
AnimHandler();
~AnimHandler();
/**
* Increment the animation timestamp, and apply bone animations if required
*/
void animate(uint32 time);
/** Set the skeletal model to animate */
void setModel(Model *model);
/** Set the skeletal animation to use */
void setAnim(SkeletonAnim *anim);
/** Stop blending and forget about the previous animation */
void resetBlending();
private:
void enactCandidate();
void startBlending();
void updateBlending(int32 deltaTime);
void stopBlending();
void setNode(uint32 time, BoneNode *bone, const BoneNode *parent);
static const uint32 _blendDuration = 300; // ms
SkeletonAnim *_anim;
int32 _animTime;
int32 _framesBeforeCandidateReady;
SkeletonAnim *_candidateAnim;
int32 _candidateAnimTime;
SkeletonAnim *_blendAnim;
int32 _blendAnimTime;
int32 _blendTimeRemaining;
Model *_model;
};
} // End of namespace Stark
#endif // STARK_MODEL_ANIM_HANDLER_H

View File

@@ -0,0 +1,233 @@
/* 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/model/model.h"
#include "engines/stark/services/archiveloader.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/gfx/texture.h"
#include "math/aabb.h"
namespace Stark {
Model::Model() :
_u1(0),
_u2(0.0) {
}
Model::~Model() {
for (Common::Array<VertNode *>::iterator it = _vertices.begin(); it != _vertices.end(); ++it)
delete *it;
for (Common::Array<Material *>::iterator it = _materials.begin(); it != _materials.end(); ++it)
delete *it;
for (Common::Array<Face *>::iterator it = _faces.begin(); it != _faces.end(); ++it)
delete *it;
for (Common::Array<BoneNode *>::iterator it = _bones.begin(); it != _bones.end(); ++it)
delete *it;
}
void Model::readFromStream(ArchiveReadStream *stream) {
uint32 id = stream->readUint32LE();
if (id != 4) {
error("Wrong magic 1 while reading actor '%d'", id);
}
uint32 format = stream->readUint32LE();
if (format == 256) {
_u1 = stream->readUint32LE();
} else if (format == 16) {
_u1 = 0;
} else {
error("Wrong format while reading actor '%d'", format);
}
uint32 id2 = stream->readUint32LE();
if (id2 != 0xDEADBABE) {
error("Wrong magic 2 while reading actor '%d'", id2);
}
_u2 = stream->readFloatLE();
uint32 numMaterials = stream->readUint32LE();
for (uint i = 0; i < numMaterials; ++i) {
Material *node = new Material();
node->name = stream->readString();
stream->readUint32LE(); // CHECKME: Unknown data
node->texture = stream->readString();
node->r = stream->readFloatLE();
node->g = stream->readFloatLE();
node->b = stream->readFloatLE();
_materials.push_back(node);
}
uint32 numUnknowns = stream->readUint32LE();
if (numUnknowns != 0) {
error("Found a mesh with numUnknowns != 0");
}
readBones(stream);
uint32 numMeshes = stream->readUint32LE();
if (numMeshes != 1) {
error("Found a mesh with numMeshes != 1 (%d)", numMeshes);
}
_name = stream->readString();
uint32 numFaces = stream->readUint32LE();
for (uint32 j = 0; j < numFaces; ++j) {
uint faceVertexIndexOffset = _vertices.size();
Face *face = new Face();
face->materialId = stream->readUint32LE();
uint32 numVertices = stream->readUint32LE();
for (uint32 k = 0; k < numVertices; ++k) {
VertNode *vert = new VertNode();
vert->_pos1 = stream->readVector3();
vert->_pos2 = stream->readVector3();
vert->_normal = stream->readVector3();
vert->_texS = stream->readFloatLE();
vert->_texT = stream->readFloatLE();
vert->_bone1 = stream->readUint32LE();
vert->_bone2 = stream->readUint32LE();
vert->_boneWeight = stream->readFloatLE();
_vertices.push_back(vert);
}
uint32 numTriangles = stream->readUint32LE();
face->vertexIndices.resize(numTriangles * 3); // 3 vertex indices per triangle
for (uint32 k = 0; k < numTriangles; ++k) {
face->vertexIndices[k * 3 + 0] = stream->readUint32LE() + faceVertexIndexOffset;
face->vertexIndices[k * 3 + 1] = stream->readUint32LE() + faceVertexIndexOffset;
face->vertexIndices[k * 3 + 2] = stream->readUint32LE() + faceVertexIndexOffset;
}
_faces.push_back(face);
}
buildBonesBoundingBoxes();
}
void Model::readBones(ArchiveReadStream *stream) {
uint32 numBones = stream->readUint32LE();
for (uint32 i = 0; i < numBones; ++i) {
BoneNode *node = new BoneNode();
node->_name = stream->readString();
node->_u1 = stream->readFloatLE();
uint32 len = stream->readUint32LE();
for (uint32 j = 0; j < len; ++j)
node->_children.push_back(stream->readUint32LE());
node->_idx = _bones.size();
_bones.push_back(node);
}
for (uint32 i = 0; i < numBones; ++i) {
BoneNode *node = _bones[i];
for (uint j = 0; j < node->_children.size(); ++j) {
_bones[node->_children[j]]->_parent = i;
}
}
}
void Model::buildBonesBoundingBoxes() {
for (uint i = 0; i < _bones.size(); i++) {
buildBoneBoundingBox(_bones[i]);
}
}
void Model::buildBoneBoundingBox(BoneNode *bone) const {
bone->_boundingBox.reset();
// Add all the vertices with a non zero weight for the bone to the bone's bounding box
for (uint k = 0; k < _vertices.size(); k++) {
VertNode *vert = _vertices[k];
if (vert->_bone1 == bone->_idx) {
bone->_boundingBox.expand(vert->_pos1);
}
if (vert->_bone2 == bone->_idx) {
bone->_boundingBox.expand(vert->_pos2);
}
}
}
bool Model::intersectRay(const Math::Ray &ray) const {
for (uint i = 0; i < _bones.size(); i++) {
if (_bones[i]->intersectRay(ray)) {
return true;
}
}
return false;
}
void Model::updateBoundingBox() {
_boundingBox.reset();
for (uint i = 0; i < _bones.size(); i++) {
_bones[i]->expandModelSpaceBB(_boundingBox);
}
}
Math::AABB Model::getBoundingBox() const {
return _boundingBox;
}
bool BoneNode::intersectRay(const Math::Ray &ray) const {
Math::Ray localRay = ray;
localRay.translate(-_animPos);
localRay.rotate(_animRot.inverse());
return localRay.intersectAABB(_boundingBox);
}
void BoneNode::expandModelSpaceBB(Math::AABB &aabb) const {
// Transform the bounding box
Math::Vector3d min = _boundingBox.getMin();
Math::Vector3d max = _boundingBox.getMax();
Math::Vector3d verts[8];
verts[0].set(min.x(), min.y(), min.z());
verts[1].set(max.x(), min.y(), min.z());
verts[2].set(min.x(), max.y(), min.z());
verts[3].set(min.x(), min.y(), max.z());
verts[4].set(max.x(), max.y(), min.z());
verts[5].set(max.x(), min.y(), max.z());
verts[6].set(min.x(), max.y(), max.z());
verts[7].set(max.x(), max.y(), max.z());
for (int i = 0; i < 8; ++i) {
_animRot.transform(verts[i]);
verts[i] += _animPos;
aabb.expand(verts[i]);
}
}
} // End of namespace Stark

133
engines/stark/model/model.h Normal file
View File

@@ -0,0 +1,133 @@
/* 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/>.
*
*/
#ifndef STARK_MODEL_MODEL_H
#define STARK_MODEL_MODEL_H
#include "common/array.h"
#include "common/str.h"
#include "math/ray.h"
#include "math/vector3d.h"
namespace Stark {
namespace Gfx {
class TextureSet;
}
class ArchiveReadStream;
class VertNode {
public:
Math::Vector3d _pos1, _pos2;
Math::Vector3d _normal;
float _texS, _texT;
uint32 _bone1, _bone2;
float _boneWeight;
};
struct Face {
uint32 materialId;
Common::Array<uint32> vertexIndices;
Face() : materialId(0) {}
};
struct Material {
Common::String name;
Common::String texture;
float r, g, b;
bool doubleSided;
Material() : r(0), g(0), b(0), doubleSided(false) {};
};
class BoneNode {
public:
BoneNode() : _parent(-1), _idx(0), _u1(0) {}
~BoneNode() { }
/** Perform a collision test with the ray */
bool intersectRay(const Math::Ray &ray) const;
/** Expand a bounding box with the model space BB of this bone */
void expandModelSpaceBB(Math::AABB &aabb) const;
Common::String _name;
float _u1;
Common::Array<uint32> _children;
int _parent;
uint32 _idx;
Math::Vector3d _animPos;
Math::Quaternion _animRot;
/** Bone space bounding box */
Math::AABB _boundingBox;
};
/**
* A 3D Model
*/
class Model {
public:
Model();
~Model();
/**
* Try and initialise object from the specified stream
*/
void readFromStream(ArchiveReadStream *stream);
const Common::Array<VertNode *> &getVertices() const { return _vertices; }
const Common::Array<Face *> &getFaces() const { return _faces; }
const Common::Array<Material *> &getMaterials() const { return _materials; }
const Common::Array<BoneNode *> &getBones() const { return _bones; };
/** Perform a collision test with a ray */
bool intersectRay(const Math::Ray &ray) const;
/** Update the model bounding box with the current animation state */
void updateBoundingBox();
/** Retrieve the model space bounding box for the current animation state */
Math::AABB getBoundingBox() const;
private:
void buildBonesBoundingBoxes();
void buildBoneBoundingBox(BoneNode *bone) const;
void readBones(ArchiveReadStream *stream);
Common::String _name;
uint32 _u1;
float _u2;
Common::Array<VertNode *> _vertices;
Common::Array<Material *> _materials;
Common::Array<Face *> _faces;
Common::Array<BoneNode *> _bones;
Math::AABB _boundingBox;
};
} // End of namespace Stark
#endif // STARK_MODEL_MODEL_H

View File

@@ -0,0 +1,107 @@
/* 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/model/skeleton_anim.h"
#include "engines/stark/services/archiveloader.h"
namespace Stark {
SkeletonAnim::SkeletonAnim() :
_id(0),
_ver(0),
_u1(0),
_u2(0),
_time(0) {
}
void SkeletonAnim::createFromStream(ArchiveReadStream *stream) {
_id = stream->readUint32LE();
_ver = stream->readUint32LE();
if (_ver == 3) {
_u1 = 0;
_time = stream->readUint32LE();
_u2 = stream->readUint32LE();
} else {
_u1 = stream->readUint32LE();
_u2 = stream->readUint32LE();
_time = stream->readUint32LE();
}
if (_u2 != 0xdeadbabe) {
error("Wrong magic while reading animation");
}
uint32 num = stream->readUint32LE();
_boneAnims.resize(num);
for (uint32 i = 0; i < num; ++i) {
uint32 bone = stream->readUint32LE();
uint32 numKeys = stream->readUint32LE();
BoneAnim &boneAnim = _boneAnims[bone];
boneAnim._keys.resize(numKeys);
for (uint32 j = 0; j < numKeys; ++j) {
AnimKey &key = boneAnim._keys[j];
key._time = stream->readUint32LE();
key._rot = stream->readQuaternion();
key._pos = stream->readVector3();
}
}
}
void SkeletonAnim::getCoordForBone(uint32 time, int boneIdx, Math::Vector3d &pos, Math::Quaternion &rot) const {
const Common::Array<AnimKey> &keys = _boneAnims[boneIdx]._keys;
if (keys.size() == 1) {
// There is only one key for this bone, don't bother searching which one to use
pos = keys[0]._pos;
rot = keys[0]._rot;
return;
}
for (Common::Array<AnimKey>::const_iterator it = keys.begin(); it < keys.end(); ++it) {
if (it->_time > time) {
// Between two key frames, interpolate
const AnimKey *a = it;
--it;
const AnimKey *b = it;
float t = (float)(time - b->_time) / (float)(a->_time - b->_time);
pos = b->_pos + (a->_pos - b->_pos) * t;
rot = b->_rot.slerpQuat(a->_rot, t);
return;
} else if (it->_time == time || it == keys.end() - 1){
// At a key frame
// If not right one but didn't find any, then use last one as default
const AnimKey *key = it;
pos = key->_pos;
rot = key->_rot;
if (it == keys.end() - 1) {
warning("Unable to find keyframe for bone '%d' at %d ms, using default", boneIdx, time);
}
return;
}
}
}
} // End of namespace Stark

View File

@@ -0,0 +1,73 @@
/* 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/>.
*
*/
#ifndef STARK_MODEL_SKELETON_ANIM_H
#define STARK_MODEL_SKELETON_ANIM_H
#include "math/quat.h"
#include "math/vector3d.h"
#include "common/array.h"
namespace Stark {
class ArchiveReadStream;
/**
* Data structure responsible for skeletal animation of an actor object.
*/
class SkeletonAnim {
public:
SkeletonAnim();
void createFromStream(ArchiveReadStream *stream);
/**
* Get the interpolated bone coordinate for a given bone at a given animation timestamp
*/
void getCoordForBone(uint32 time, int boneIdx, Math::Vector3d &pos, Math::Quaternion &rot) const;
/**
* Get total animation length (in ms)
*/
uint32 getLength() const { return _time; }
/** The number of bones a skeleton must have to play this animation */
uint32 getBoneCount() const { return _boneAnims.size(); }
private:
struct AnimKey {
uint32 _time;
Math::Quaternion _rot;
Math::Vector3d _pos;
};
struct BoneAnim {
Common::Array<AnimKey> _keys;
};
uint32 _id, _ver, _u1, _u2, _time;
Common::Array<BoneAnim> _boneAnims;
};
} // End of namespace Stark
#endif // STARK_MODEL_SKELETON_ANIM_H

148
engines/stark/module.mk Normal file
View File

@@ -0,0 +1,148 @@
MODULE := engines/stark
MODULE_OBJS := \
console.o \
gfx/driver.o \
gfx/opengls.o \
gfx/openglsactor.o \
gfx/openglsfade.o \
gfx/openglsprop.o \
gfx/openglssurface.o \
gfx/opengl.o \
gfx/openglactor.o \
gfx/openglbitmap.o \
gfx/openglfade.o \
gfx/openglprop.o \
gfx/openglsurface.o \
gfx/opengltexture.o \
gfx/renderentry.o \
gfx/surfacerenderer.o \
gfx/texture.o \
formats/biff.o \
formats/biffmesh.o \
formats/dds.o \
formats/iss.o \
formats/tm.o \
formats/xarc.o \
formats/xmg.o \
formats/xrc.o \
metaengine.o \
model/animhandler.o \
model/model.o \
model/skeleton_anim.o \
movement/followpath.o \
movement/followpathlight.o \
movement/movement.o \
movement/shortestpath.o \
movement/stringpullingpath.o \
movement/turn.o \
movement/walk.o \
resources/anim.o \
resources/animhierarchy.o \
resources/animscript.o \
resources/animsoundtrigger.o \
resources/bonesmesh.o \
resources/bookmark.o \
resources/camera.o \
resources/container.o \
resources/command.o \
resources/dialog.o \
resources/direction.o \
resources/floor.o \
resources/floorface.o \
resources/floorfield.o \
resources/fmv.o \
resources/image.o \
resources/item.o \
resources/knowledge.o \
resources/knowledgeset.o \
resources/layer.o \
resources/level.o \
resources/light.o \
resources/lipsync.o \
resources/location.o \
resources/object.o \
resources/path.o \
resources/pattable.o \
resources/root.o \
resources/script.o \
resources/scroll.o \
resources/sound.o \
resources/speech.o \
resources/string.o \
resources/textureset.o \
resourcereference.o \
savemetadata.o \
scene.o \
services/archiveloader.o \
services/dialogplayer.o \
services/diary.o \
services/fontprovider.o \
services/gameinterface.o \
services/global.o \
services/resourceprovider.o \
services/services.o \
services/stateprovider.o \
services/staticprovider.o \
services/userinterface.o \
services/settings.o \
services/gamechapter.o \
services/gamemessage.o \
stark.o \
tools/abstractsyntaxtree.o \
tools/block.o \
tools/command.o \
tools/decompiler.o \
ui/cursor.o \
ui/dialogbox.o \
ui/menu/diaryindex.o \
ui/menu/locationscreen.o \
ui/menu/mainmenu.o \
ui/menu/settingsmenu.o \
ui/menu/saveloadmenu.o \
ui/menu/fmvmenu.o \
ui/menu/diarypages.o \
ui/menu/dialogmenu.o \
ui/window.o \
ui/world/actionmenu.o \
ui/world/button.o \
ui/world/clicktext.o \
ui/world/topmenu.o \
ui/world/dialogpanel.o \
ui/world/fmvscreen.o \
ui/world/gamescreen.o \
ui/world/gamewindow.o \
ui/world/inventorywindow.o \
visual/actor.o \
visual/effects/bubbles.o \
visual/effects/effect.o \
visual/effects/fireflies.o \
visual/effects/fish.o \
visual/explodingimage.o \
visual/flashingimage.o \
visual/image.o \
visual/prop.o \
visual/smacker.o \
visual/text.o
ifdef USE_TINYGL
MODULE_OBJS += \
gfx/tinygl.o \
gfx/tinyglactor.o \
gfx/tinyglbitmap.o \
gfx/tinyglfade.o \
gfx/tinyglprop.o \
gfx/tinyglsurface.o \
gfx/tinygltexture.o
endif
# This module can be built as a plugin
ifeq ($(ENABLE_STARK), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

View File

@@ -0,0 +1,159 @@
/* 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/movement/followpath.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/stateprovider.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/floor.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/path.h"
namespace Stark {
FollowPath::FollowPath(Resources::ItemVisual *item) :
Movement(item),
_path(nullptr),
_speed(0.0),
_position(0.0),
_previouslyEnabled(true),
_anim(nullptr) {
}
FollowPath::~FollowPath() {
}
void FollowPath::start() {
Movement::start();
_previouslyEnabled = _item->isEnabled();
_item->setEnabled(true);
updateItemPosition(0, 0);
changeItemAnim();
}
void FollowPath::stop(bool force) {
Movement::stop(force);
changeItemAnim();
_item->setEnabled(_previouslyEnabled);
}
void FollowPath::onGameLoop() {
// Compute the new position on the path
_position += _speed * StarkGlobal->getMillisecondsPerGameloop();
// Find the current path edge, and position on the path edge
uint currentEdge = 0;
float positionInEdge = _position;
for (uint i = 0; i < _path->getEdgeCount(); i++) {
float edgeLength = _path->getWeightedEdgeLength(i);
if (positionInEdge < edgeLength) {
break; // Found the current path edge
}
positionInEdge -= edgeLength;
currentEdge++;
}
// Check if we went beyond the path's end
if (currentEdge >= _path->getEdgeCount()) {
stop();
return;
}
updateItemPosition(currentEdge, positionInEdge);
}
void FollowPath::updateItemPosition(uint currentEdge, float positionInEdge) const {// Get the new position for the item
Math::Vector3d newPosition = _path->getWeightedPositionInEdge(currentEdge, positionInEdge);
// Update the item's properties in the scene
if (is3D()) {
Resources::FloorPositionedItem *item3D = Resources::Object::cast<Resources::FloorPositionedItem>(_item);
Resources::Floor *floor = StarkGlobal->getCurrent()->getFloor();
int32 floorFaceIndex = floor->findFaceContainingPoint(newPosition);
if (floorFaceIndex >= 0) {
item3D->setFloorFaceIndex(floorFaceIndex);
} else {
item3D->overrideSortKey(_path->getSortKey());
}
item3D->setPosition3D(newPosition);
Math::Vector3d direction = _path->getEdgeDirection(currentEdge);
item3D->setDirection(computeAngleBetweenVectorsXYPlane(direction, Math::Vector3d(1.0, 0.0, 0.0)));
} else {
Common::Point position2D = Common::Point(newPosition.x(), newPosition.y());
_item->setPosition2D(position2D);
}
}
void FollowPath::changeItemAnim() {
if (_ended) {
if (_anim) {
_item->resetActionAnim();
} else {
_item->setAnimActivity(Resources::Anim::kActorActivityIdle);
}
} else {
if (_anim) {
_item->playActionAnim(_anim);
} else {
_item->setAnimActivity(Resources::Anim::kActorActivityWalk);
}
}
}
void FollowPath::setPath(Resources::Path *path) {
_path = path;
}
void FollowPath::setSpeed(float speed) {
_speed = speed;
}
bool FollowPath::is3D() const {
return _path->getSubType() == Resources::Path::kPath3D;
}
void FollowPath::setAnim(Resources::Anim *anim) {
_anim = anim;
}
uint32 FollowPath::getType() const {
return kTypeFollowPath;
}
void FollowPath::saveLoad(ResourceSerializer *serializer) {
serializer->syncAsResourceReference(&_path);
serializer->syncAsResourceReference(&_anim);
serializer->syncAsFloat(_position);
serializer->syncAsFloat(_speed);
serializer->syncAsUint32LE(_previouslyEnabled);
}
} // End of namespace Stark

View File

@@ -0,0 +1,76 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_FOLLOW_PATH_H
#define STARK_MOVEMENT_FOLLOW_PATH_H
#include "engines/stark/movement/movement.h"
namespace Stark {
namespace Resources {
class Anim;
class Path;
}
/**
* Make an item follow pre-computed path
*
* Works for 2D and 3D items, with respectively 2D and 3D paths
*/
class FollowPath : public Movement {
public:
FollowPath(Resources::ItemVisual *item);
virtual ~FollowPath();
// Movement API
void start() override;
void onGameLoop() override;
void stop(bool force = false) override;
uint32 getType() const override;
void saveLoad(ResourceSerializer *serializer) override;
/** Set the path to follow */
void setPath(Resources::Path *path);
/** Set the movement speed on the path */
void setSpeed(float speed);
/** Override the animation to play while the item follows the path */
void setAnim(Resources::Anim *anim);
private:
void changeItemAnim();
void updateItemPosition(uint currentEdge, float positionInEdge) const;
bool is3D() const;
Resources::Path *_path;
float _speed;
float _position;
bool _previouslyEnabled;
Resources::Anim *_anim;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_FOLLOW_PATH_H

View File

@@ -0,0 +1,116 @@
/* 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/movement/followpathlight.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/stateprovider.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/floor.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/light.h"
#include "engines/stark/resources/path.h"
namespace Stark {
FollowPathLight::FollowPathLight(Resources::ItemVisual *item) :
Movement(item),
_light(nullptr),
_path(nullptr),
_speed(0.0),
_position(0.0),
_previouslyEnabled(true) {
}
FollowPathLight::~FollowPathLight() {
}
void FollowPathLight::start() {
Movement::start();
_previouslyEnabled = _item->isEnabled();
_item->setEnabled(true);
Math::Vector3d newPosition = _path->getWeightedPositionInEdge(0, 0);
_light->setPosition(newPosition);
}
void FollowPathLight::stop(bool force) {
Movement::stop(force);
_item->setEnabled(_previouslyEnabled);
}
void FollowPathLight::onGameLoop() {
// Compute the new position on the path
_position += _speed * StarkGlobal->getMillisecondsPerGameloop();
// Find the current path edge, and position on the path edge
uint currentEdge = 0;
float positionInEdge = _position;
for (uint i = 0; i < _path->getEdgeCount(); i++) {
float edgeLength = _path->getWeightedEdgeLength(i);
if (positionInEdge < edgeLength) {
break; // Found the current path edge
}
positionInEdge -= edgeLength;
currentEdge++;
}
// Check if we went beyond the path's end
if (currentEdge >= _path->getEdgeCount()) {
stop();
return;
}
// Set the new position for the light
Math::Vector3d newPosition = _path->getWeightedPositionInEdge(currentEdge, positionInEdge);
_light->setPosition(newPosition);
}
void FollowPathLight::setPath(Resources::Path *path) {
_path = path;
}
void FollowPathLight::setSpeed(float speed) {
_speed = speed;
}
void FollowPathLight::setLight(Resources::Light *light) {
_light = light;
}
uint32 FollowPathLight::getType() const {
return kTypeFollowPathLight;
}
void FollowPathLight::saveLoad(ResourceSerializer *serializer) {
serializer->syncAsResourceReference(&_path);
serializer->syncAsResourceReference(&_light);
serializer->syncAsFloat(_position);
serializer->syncAsFloat(_speed);
serializer->syncAsUint32LE(_previouslyEnabled);
}
} // End of namespace Stark

View File

@@ -0,0 +1,69 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_FOLLOW_PATH_LIGHT_H
#define STARK_MOVEMENT_FOLLOW_PATH_LIGHT_H
#include "engines/stark/movement/movement.h"
namespace Stark {
namespace Resources {
class Light;
class Path;
}
/**
* Make a light follow pre-computed path
*/
class FollowPathLight : public Movement {
public:
FollowPathLight(Resources::ItemVisual *item);
virtual ~FollowPathLight();
// Movement API
void start() override;
void onGameLoop() override;
void stop(bool force = false) override;
uint32 getType() const override;
void saveLoad(ResourceSerializer *serializer) override;
/** Set the path to follow */
void setPath(Resources::Path *path);
/** Set the light to move */
void setLight(Resources::Light *light);
/** Set the movement speed on the path */
void setSpeed(float speed);
private:
Resources::Path *_path;
Resources::Light *_light;
float _speed;
float _position;
bool _previouslyEnabled;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_FOLLOW_PATH_LIGHT_H

View File

@@ -0,0 +1,90 @@
/* 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/movement/movement.h"
#include "engines/stark/movement/walk.h"
#include "engines/stark/movement/followpath.h"
#include "engines/stark/movement/followpathlight.h"
#include "engines/stark/movement/turn.h"
#include "engines/stark/resources/item.h"
#include "common/textconsole.h"
namespace Stark {
Movement *Movement::construct(uint32 type, Resources::ItemVisual *item) {
switch (type) {
case kTypeWalk:
return new Walk(Resources::Object::cast<Resources::FloorPositionedItem>(item));
case kTypeFollowPath:
return new FollowPath(item);
case kTypeFollowPathLight:
return new FollowPathLight(item);
case kTypeTurn:
return new Turn(Resources::Object::cast<Resources::FloorPositionedItem>(item));
default:
error("Unexpected movement type '%d'", type);
}
}
Movement::Movement(Resources::ItemVisual *item) :
_ended(false),
_item(item),
_defaultTurnAngleSpeed(18.0f * 30.0f / 1000.0f) { // 18 degrees per gameloop at 30 fps
}
Movement::~Movement() {
}
void Movement::start() {
_ended = false;
}
void Movement::stop(bool force) {
_ended = true;
}
bool Movement::hasEnded() const {
return _ended;
}
float Movement::computeAngleBetweenVectorsXYPlane(const Math::Vector3d &v1, const Math::Vector3d &v2) const {
Math::Vector3d v1XY = v1;
v1XY.z() = 0.0;
Math::Vector3d v2XY = v2;
v2XY.z() = 0.0;
Math::Angle angle = Math::Vector3d::angle(v1XY, v2XY);
Math::Vector3d cross = Math::Vector3d::crossProduct(v1XY, v2XY);
if (cross.z() < 0) {
angle = -angle;
}
return angle.getDegrees();
}
bool Movement::hasReachedDestination() const {
return true;
}
} // End of namespace Stark

View File

@@ -0,0 +1,103 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_MOVEMENT_H
#define STARK_MOVEMENT_MOVEMENT_H
#include "math/vector3d.h"
namespace Stark {
namespace Resources {
class ItemVisual;
}
class ResourceSerializer;
/**
* Abstract movement of an item on the current location's floor
*/
class Movement {
public:
Movement(Resources::ItemVisual *item);
virtual ~Movement();
enum MovementType {
kTypeWalk = 1,
kTypeFollowPath = 2,
kTypeFollowPathLight = 3,
kTypeTurn = 4
};
/** Movement factory */
static Movement *construct(uint32 type, Resources::ItemVisual *item);
/** Obtain the effective movement type */
virtual uint32 getType() const = 0;
/**
* Initiate the movement
*/
virtual void start();
/**
* Stop / abort the movement
*/
virtual void stop(bool force = false);
/**
* Called once per game loop
*/
virtual void onGameLoop() = 0;
/**
* Has the movement stopped?
*/
bool hasEnded() const;
/**
* Has the movement reached its destination successfully?
*/
virtual bool hasReachedDestination() const;
/**
* Persist / restore the state of the movement so it can be resumed using 'start'
*/
virtual void saveLoad(ResourceSerializer *serializer) = 0;
protected:
enum TurnDirection {
kTurnNone,
kTurnLeft,
kTurnRight
};
const float _defaultTurnAngleSpeed; // Degrees per ms
float computeAngleBetweenVectorsXYPlane(const Math::Vector3d &v1, const Math::Vector3d &v2) const;
bool _ended;
Resources::ItemVisual *_item;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_MOVEMENT_H

View File

@@ -0,0 +1,100 @@
/* 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/movement/shortestpath.h"
#include "common/hash-ptr.h"
#include "engines/stark/resources/floor.h"
namespace Stark {
ShortestPath::NodeList ShortestPath::search(const Resources::FloorEdge *start, const Resources::FloorEdge *goal) {
NodeList frontier;
NodePrecedenceMap cameFrom;
NodeCostMap costSoFar;
frontier.push_back(start);
cameFrom[start] = nullptr;
costSoFar[start] = 0;
while (!frontier.empty()) {
const Resources::FloorEdge *current = popEdgeWithLowestCost(frontier, costSoFar);
if (current == goal)
break;
Common::Array<Resources::FloorEdge *> neighbours = current->getNeighbours();
for (uint i = 0; i < neighbours.size(); i++) {
const Resources::FloorEdge *next = neighbours[i];
if (!next->isEnabled())
continue;
float newCost = costSoFar[current] + current->costTo(next);
if (!costSoFar.contains(next) || newCost < costSoFar[next]) {
frontier.push_back(next);
cameFrom[next] = current;
costSoFar[next] = newCost;
}
}
}
return rebuildPath(start, goal, cameFrom);
}
ShortestPath::NodeList ShortestPath::rebuildPath(const Resources::FloorEdge *start, const Resources::FloorEdge *goal,
const NodePrecedenceMap &cameFrom) const {
NodeList path;
const Resources::FloorEdge *current = goal;
path.push_front(goal);
while (current && current != start) {
current = cameFrom.getValOrDefault(current, nullptr);
path.push_front(current);
}
if (current != start) {
// No path has been found from start to goal
return NodeList();
}
path.push_front(start);
return path;
}
const Resources::FloorEdge *ShortestPath::popEdgeWithLowestCost(NodeList &frontier, const NodeCostMap &costSoFar) const {
// Poor man's priority queue using a list ...
NodeList::iterator lowestCostItem = frontier.begin();
for (NodeList::iterator it = frontier.begin(); it != frontier.end(); it++) {
if (costSoFar[*it] < costSoFar[*lowestCostItem]) {
lowestCostItem = it;
}
}
const Resources::FloorEdge *result = *lowestCostItem;
frontier.erase(lowestCostItem);
return result;
}
} // End of namespace Stark

View File

@@ -0,0 +1,58 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_SHORTEST_PATH_H
#define STARK_MOVEMENT_SHORTEST_PATH_H
#include "common/list.h"
#include "common/hashmap.h"
namespace Stark {
namespace Resources {
class FloorEdge;
}
/**
* Find the shortest path between two nodes in a graph
*
* This is an implementation of Dijsktra's search algorithm
*/
class ShortestPath {
public:
typedef Common::List<const Resources::FloorEdge *> NodeList;
/** Computes the shortest path between the start and the goal graph nodes */
NodeList search(const Resources::FloorEdge *start, const Resources::FloorEdge *goal);
private:
typedef Common::HashMap<const Resources::FloorEdge *, const Resources::FloorEdge *> NodePrecedenceMap;
typedef Common::HashMap<const Resources::FloorEdge *, float> NodeCostMap;
const Resources::FloorEdge *popEdgeWithLowestCost(NodeList &frontier, const NodeCostMap &costSoFar) const;
NodeList rebuildPath(const Resources::FloorEdge *start, const Resources::FloorEdge *goal,
const NodePrecedenceMap &cameFrom) const;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_SHORTEST_PATH_H

View File

@@ -0,0 +1,73 @@
/* 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/movement/stringpullingpath.h"
#include "engines/stark/resources/floor.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "math/line3d.h"
namespace Stark {
StringPullingPath::StringPullingPath() :
_targetStep(1) {
}
void StringPullingPath::addStep(const Math::Vector3d &position) {
_steps.push_back(position);
}
void StringPullingPath::reset() {
_steps.clear();
_targetStep = 1;
}
Math::Vector3d StringPullingPath::computeWalkTarget(const Math::Vector3d &fromPosition) {
Current *current = StarkGlobal->getCurrent();
Resources::Floor *floor = current->getFloor();
// HACK: Sometimes the character gets stuck because of rounding errors
// If we detect the character is stuck on a step, just make it go to the next one.
// TODO: Improve the string pulling code so that the targets can also be points between two steps.
if (fromPosition.getDistanceTo(_steps[_targetStep]) < 1.0 && _targetStep < _steps.size() - 1) {
_targetStep++;
}
for (uint i = _targetStep + 1; i < _steps.size(); i++) {
Math::Line3d testSegment = Math::Line3d(fromPosition, _steps[i]);
if (!floor->isSegmentInside(testSegment)) {
break;
}
_targetStep = i;
}
return _steps[_targetStep];
}
bool StringPullingPath::hasSteps() const {
return _steps.size() > 1;
}
} // End of namespace Stark

View File

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_STRING_PULLING_PATH_H
#define STARK_MOVEMENT_STRING_PULLING_PATH_H
#include "common/array.h"
#include "math/vector3d.h"
namespace Stark {
/**
* Store a path and allow to walk along it smoothly
*
* The base principle of the string pulling algorithm is to skip steps
* if it is possible to walk directly to a later step in straight line.
*/
class StringPullingPath {
public:
StringPullingPath();
/** Append a step to the path */
void addStep(const Math::Vector3d &position);
/** Reset the steps, and the current target on the path */
void reset();
/** Move the walk target forward according to the position */
Math::Vector3d computeWalkTarget(const Math::Vector3d &fromPosition);
/** Returns true if this path is not degenerated (empty or single point) */
bool hasSteps() const;
private:
Common::Array<Math::Vector3d> _steps;
uint32 _targetStep;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_STRING_PULLING_PATH_H

View File

@@ -0,0 +1,104 @@
/* 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/movement/turn.h"
#include "math/matrix3.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/stateprovider.h"
namespace Stark {
Turn::Turn(Resources::FloorPositionedItem *item) :
Movement(item),
_item3D(item),
_turnSpeed(_defaultTurnAngleSpeed) {
}
Turn::~Turn() {
}
void Turn::onGameLoop() {
// Compute the direction to turn towards
Math::Vector3d direction = _targetDirection;
direction.z() = 0;
direction.normalize();
// Compute the angle with the current character direction
Math::Vector3d currentDirection = _item3D->getDirectionVector();
float directionDeltaAngle = computeAngleBetweenVectorsXYPlane(currentDirection, direction);
// If the angle between the current direction and the new one is too high,
// make the character turn on itself until the angle is low enough
TurnDirection turnDirection;
if (ABS(directionDeltaAngle) > getAngularSpeed() + 0.1f) {
turnDirection = directionDeltaAngle < 0 ? kTurnLeft : kTurnRight;
} else {
turnDirection = kTurnNone;
}
if (turnDirection == kTurnNone) {
direction = _targetDirection;
} else {
// Make the character turn towards the target direction
direction = currentDirection;
Math::Matrix3 rot;
rot.buildAroundZ(turnDirection == kTurnLeft ? -getAngularSpeed() : getAngularSpeed());
rot.transformVector(&direction);
}
// Update the item's direction
_item3D->setDirection(computeAngleBetweenVectorsXYPlane(direction, Math::Vector3d(1.0, 0.0, 0.0)));
// Check if we are close enough to the destination to stop
if (direction == _targetDirection) {
stop();
}
}
float Turn::getAngularSpeed() const {
return _turnSpeed * StarkGlobal->getMillisecondsPerGameloop();
}
void Turn::setTargetDirection(const Math::Vector3d &direction) {
_targetDirection = direction;
}
void Turn::setSpeed(float speed) {
_turnSpeed = speed;
}
uint32 Turn::getType() const {
return kTypeTurn;
}
void Turn::saveLoad(ResourceSerializer *serializer) {
serializer->syncAsVector3d(_targetDirection);
serializer->syncAsFloat(_turnSpeed);
}
} // End of namespace Stark

View File

@@ -0,0 +1,62 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_TURN_H
#define STARK_MOVEMENT_TURN_H
#include "engines/stark/movement/movement.h"
namespace Stark {
namespace Resources {
class FloorPositionedItem;
}
/**
* Make an item turn on itself towards a target direction
*/
class Turn : public Movement {
public:
Turn(Resources::FloorPositionedItem *item);
virtual ~Turn();
// Movement API
void onGameLoop() override;
uint32 getType() const override;
void saveLoad(ResourceSerializer *serializer) override;
/** Set the direction to turn towards */
void setTargetDirection(const Math::Vector3d &direction);
/** Override the default rotation speed */
void setSpeed(float speed);
private:
float getAngularSpeed() const;
Resources::FloorPositionedItem *_item3D;
Math::Vector3d _targetDirection;
float _turnSpeed;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_TURN_H

View File

@@ -0,0 +1,525 @@
/* 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/movement/walk.h"
#include "engines/stark/movement/shortestpath.h"
#include "engines/stark/movement/stringpullingpath.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/stateprovider.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/floor.h"
#include "engines/stark/resources/floorface.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/location.h"
#include "math/vector2d.h"
namespace Stark {
Walk::Walk(Resources::FloorPositionedItem *item) :
Movement(item),
_item3D(item),
_running(false),
_reachedDestination(false),
_turnDirection(kTurnNone),
_collisionWaitTimeout(-1),
_collisionWaitCount(0) {
_path = new StringPullingPath();
}
Walk::~Walk() {
delete _path;
}
void Walk::start() {
Movement::start();
updatePath();
changeItemAnim();
Resources::Location *location = StarkGlobal->getCurrent()->getLocation();
location->startFollowingCharacter();
}
void Walk::stop(bool force) {
if (force) {
_destinations.clear();
}
if (_destinations.empty()) {
Movement::stop(force);
changeItemAnim();
_avoidedItems.clear();
} else {
Math::Vector3d destination = _destinations.front();
_destinations.remove_at(0);
setDestination(destination);
updatePath();
}
}
void Walk::updatePath() const {
_path->reset();
Resources::Floor *floor = StarkGlobal->getCurrent()->getFloor();
Math::Vector3d startPosition = _item3D->getPosition3D();
int32 startFloorFaceIndex = floor->findFaceContainingPoint(startPosition);
if (startFloorFaceIndex == -1) {
startFloorFaceIndex = 0;
}
Resources::FloorFace *startFloorFace = floor->getFace(startFloorFaceIndex);
Resources::FloorEdge *startFloorEdge = startFloorFace->findNearestEdge(startPosition);
if (!startFloorEdge) {
// Unable to find enabled start edge
return;
}
int32 destinationFloorFaceIndex = floor->findFaceContainingPoint(_destination);
if (destinationFloorFaceIndex < 0) {
// Unable to find the destination's face
return;
}
Resources::FloorFace *destinationFloorFace = floor->getFace(destinationFloorFaceIndex);
Resources::FloorEdge *destinationFloorEdge = destinationFloorFace->findNearestEdge(_destination);
if (!destinationFloorEdge) {
// Unable to find enabled destination edge
return;
}
ShortestPath pathSearch;
ShortestPath::NodeList edgePath = pathSearch.search(startFloorEdge, destinationFloorEdge);
for (ShortestPath::NodeList::const_iterator it = edgePath.begin(); it != edgePath.end(); it++) {
_path->addStep((*it)->getPosition());
}
_path->addStep(_destination);
}
void Walk::queueDestinationToAvoidItem(Resources::FloorPositionedItem *item, const Math::Vector3d &destination) {
_destinations.push_back(destination);
_avoidedItems.push_back(item);
}
bool Walk::isItemAlreadyAvoided(Resources::FloorPositionedItem *item) const {
return Common::find(_avoidedItems.begin(), _avoidedItems.end(), item) != _avoidedItems.end();
}
void Walk::onGameLoop() {
Resources::ItemVisual *interactiveItem = StarkGlobal->getCurrent()->getInteractive();
if (_item != interactiveItem) {
// NPCs have a simple collision handling strategy.
// They stop when they collide with other items,
// and wait for their path to be clear.
doWalkCollisionSimple();
} else {
// April has a more advanced collision handling approach.
// She goes either left or right of the items on her path.
// When impossible to pick a direction, she walks until the
// obstacle is reached.
doWalkCollisionAvoid();
}
}
void Walk::doWalk() {
if (!_path->hasSteps()) {
// There is no path to the destination
stop();
return;
}
Resources::Floor *floor = StarkGlobal->getCurrent()->getFloor();
// Get the target to walk to
Math::Vector3d currentPosition = _item3D->getPosition3D();
Math::Vector3d target = _path->computeWalkTarget(currentPosition);
// Compute the direction to walk into
Math::Vector3d direction = target - currentPosition;
direction.z() = 0;
direction.normalize();
// Compute the angle with the current character direction
Math::Vector3d currentDirection = _item3D->getDirectionVector();
float directionDeltaAngle = computeAngleBetweenVectorsXYPlane(currentDirection, direction);
// If the angle between the current direction and the new one is too high,
// make the character turn on itself until the angle is low enough
if (ABS(directionDeltaAngle) > getAngularSpeed() + 0.1f) {
_turnDirection = directionDeltaAngle < 0 ? kTurnLeft : kTurnRight;
} else {
_turnDirection = kTurnNone;
}
float distancePerGameloop = computeDistancePerGameLoop();
Math::Vector3d newPosition;
if (_turnDirection == kTurnNone) {
// Compute the new position using the distance per gameloop
if (currentPosition.getDistanceTo(target) > distancePerGameloop) {
newPosition = currentPosition + direction * distancePerGameloop;
} else {
newPosition = target;
}
} else {
// The character does not change position when it is turning
newPosition = currentPosition;
direction = currentDirection;
Math::Matrix3 rot;
rot.buildAroundZ(_turnDirection == kTurnLeft ? -getAngularSpeed() : getAngularSpeed());
rot.transformVector(&direction);
}
_previousPosition = currentPosition;
_currentTarget = target;
// Some scripts expect the character position to be the exact destination
if (newPosition == _destination) {
_reachedDestination = true;
stop();
}
// Update the new position's height according to the floor
int32 newFloorFaceIndex = floor->findFaceContainingPoint(newPosition);
if (newFloorFaceIndex >= 0) {
floor->computePointHeightInFace(newPosition, newFloorFaceIndex);
} else {
warning("Item %s is walking off the floor", _item->getName().c_str());
}
// Update the item's properties
_item3D->setPosition3D(newPosition);
if (direction.getMagnitude() != 0.f) {
_item3D->setDirection(computeAngleBetweenVectorsXYPlane(direction, Math::Vector3d(1.0, 0.0, 0.0)));
}
if (newFloorFaceIndex >= 0) {
// When unable to find the face containing the new position, keep the previous one
// to prevent draw order glitches.
_item3D->setFloorFaceIndex(newFloorFaceIndex);
}
changeItemAnim();
}
bool Walk::isPointNearPath(const Math::Vector3d &point3d, const Math::Vector3d &pathStart3d, const Math::Vector3d &pathEnd3d) {
Math::Vector2d point = Math::Vector2d(point3d.x(), point3d.y());
Math::Vector2d pathStart = Math::Vector2d(pathStart3d.x(), pathStart3d.y());
Math::Vector2d pathEnd = Math::Vector2d(pathEnd3d.x(), pathEnd3d.y());
// Project the point onto the path
Math::Vector2d pointToStart = point - pathStart;
Math::Vector2d path = pathEnd - pathStart;
float dot = pointToStart.dotProduct(path);
float len = path.getSquareMagnitude();
float t = dot / len;
Math::Vector2d projection;
if (0.f <= t && t < 1.f) {
projection = path * t + pathStart;
} else {
projection = pathEnd;
}
// Check if the projection is near the actual point
return point.getDistanceTo(projection) <= (15.f + 15.f);
}
void Walk::doWalkCollisionSimple() {
if (_collisionWaitTimeout > 0) {
_collisionWaitTimeout -= StarkGlobal->getMillisecondsPerGameloop();
return;
} else {
_collisionWaitTimeout = -1;
}
Resources::Location *location = StarkGlobal->getCurrent()->getLocation();
Common::Array<Resources::ModelItem *> characters = location->listModelItems();
// Check if any of the other characters is in our way
for (uint i = 0; i < characters.size(); i++) {
Resources::ModelItem *otherItem = characters[i];
if (!otherItem || !otherItem->isEnabled() || otherItem == _item) continue;
Math::Vector3d otherPosition = otherItem->getPosition3D();
if (isPointNearPath(otherPosition, _previousPosition, _currentTarget)) {
if (_previousPosition.getDistanceTo(otherPosition) <= 15.f * 3.f) {
if (_collisionWaitCount >= 10) {
doWalk();
return;
}
// A collision is detected. Remove the walk animation, and wait a bit.
if (_item->getAnimActivity() != Resources::Anim::kActorActivityIdle) {
_item->setAnimActivity(Resources::Anim::kActorActivityIdle);
}
_collisionWaitCount++;
_collisionWaitTimeout = 500; // ms
return;
}
}
}
// The path is clear, walk normally
_collisionWaitCount = 0;
doWalk();
}
void Walk::doWalkCollisionAvoid() {
float collisionRadius = 15.f * 2.0999999f;
Math::Vector3d previousPosition = _item3D->getPosition3D();
doWalk();
Math::Vector3d newPosition = _item3D->getPosition3D();
Resources::Location *location = StarkGlobal->getCurrent()->getLocation();
Common::Array<Resources::ModelItem *> characters = location->listModelItems();
// Check if we're colliding with another character, but going away from it.
// In that case, the collision is being solved. There is nothing to do.
for (uint i = 0; i < characters.size(); i++) {
Resources::FloorPositionedItem *otherItem = characters[i];
if (!otherItem || !otherItem->isEnabled() || otherItem == _item) continue;
Math::Vector3d otherPosition = otherItem->getPosition3D();
Math::Vector2d newPosition2d(newPosition.x(), newPosition.y());
Math::Vector2d otherPosition2d(otherPosition.x(), otherPosition.y());
float newDistance = newPosition2d.getDistanceTo(otherPosition2d);
if (newDistance < 15.f + 15.f) {
Math::Vector2d previousPosition2d(previousPosition.x(), previousPosition.y());
float previousDistance = previousPosition2d.getDistanceTo(otherPosition2d);
if (previousDistance < newDistance) {
return;
}
}
}
Resources::Floor *floor = StarkGlobal->getCurrent()->getFloor();
for (uint i = 0; i < characters.size(); i++) {
Resources::FloorPositionedItem *otherItem = dynamic_cast<Resources::FloorPositionedItem *>(characters[i]);
if (!otherItem || !otherItem->isEnabled() || otherItem == _item || isItemAlreadyAvoided(otherItem)) continue;
Math::Vector3d otherPosition = otherItem->getPosition3D();
if (!isPointNearPath(otherPosition, _previousPosition, _currentTarget)) continue;
Math::Vector3d newPosition2d(newPosition.x(), newPosition.y(), 0.f);
Math::Vector3d otherPosition2d(otherPosition.x(), otherPosition.y(), 0.f);
Math::Vector3d directionToOther = otherPosition2d - newPosition2d;
float distanceToOther = directionToOther.getMagnitude();
directionToOther.normalize();
Math::Vector3d up(0.f, 0.f, 1.f);
Math::Vector3d rightDirection = Math::Vector3d::crossProduct(directionToOther, up);
rightDirection.normalize();
Math::Vector3d otherPositionNear = newPosition2d + directionToOther * (distanceToOther - collisionRadius);
Math::Vector3d otherPostionFar = newPosition2d + directionToOther * (distanceToOther + collisionRadius);
Math::Vector3d rightOfOtherNear = otherPositionNear + rightDirection * collisionRadius;
Math::Vector3d leftOfOtherNear = otherPositionNear - rightDirection * collisionRadius;
Math::Vector3d rightOfOtherFar = otherPostionFar + rightDirection * collisionRadius;
Math::Vector3d leftOfOtherFar = otherPostionFar - rightDirection * collisionRadius;
bool canGoRight = false;
if (floor->isSegmentInside(Math::Line3d(otherPositionNear, rightOfOtherNear))) {
if (floor->isSegmentInside(Math::Line3d(rightOfOtherNear, rightOfOtherFar))) {
canGoRight = true;
}
}
bool canGoLeft = false;
if (floor->isSegmentInside(Math::Line3d(otherPositionNear, leftOfOtherNear))) {
if (floor->isSegmentInside(Math::Line3d(leftOfOtherNear, leftOfOtherFar))) {
canGoLeft = true;
}
}
for (uint j = 0; j < characters.size(); j++) {
if (j == i) continue;
Resources::FloorPositionedItem *anotherItem = dynamic_cast<Resources::FloorPositionedItem *>(characters[j]);
if (!anotherItem || !anotherItem->isEnabled() || anotherItem == _item) continue;
Math::Vector3d anotherPosition = anotherItem->getPosition3D();
if (isPointNearPath(anotherPosition, otherPositionNear, rightOfOtherNear)) {
canGoRight = false;
}
if (isPointNearPath(anotherPosition, rightOfOtherNear, rightOfOtherFar)) {
canGoRight = false;
}
if (isPointNearPath(anotherPosition, otherPositionNear, leftOfOtherNear)) {
canGoLeft = false;
}
if (isPointNearPath(anotherPosition, leftOfOtherNear, leftOfOtherFar)) {
canGoLeft = false;
}
}
if (distanceToOther < collisionRadius) {
int32 floorFace = floor->findFaceContainingPoint(previousPosition);
if (floorFace >= 0) {
floor->computePointHeightInFace(previousPosition, floorFace);
_item3D->setFloorFaceIndex(floorFace);
_item3D->setPosition3D(previousPosition);
}
_reachedDestination = false;
// _skipped = false;
stop();
break;
}
// If our target destination is in the collision radius of the tested item
// Then adjust our destination to be just outside of the item's collision radius.
float distanceToDestination = _destination.getDistanceTo(otherPosition);
if (distanceToDestination < collisionRadius) {
setDestinationWithoutHeight(otherPosition - directionToOther * collisionRadius);
// _field_51 = true;
updatePath();
continue;
}
if (canGoLeft) {
Math::Vector3d lookDirection = _item3D->getDirectionVector();
Math::Vector3d previousToLeft = leftOfOtherNear - _previousPosition;
Math::Angle angle = Math::Vector3d::angle(lookDirection, previousToLeft);
if (angle > 270) {
canGoLeft = false;
}
}
if (canGoRight) {
Math::Vector3d lookDirection = _item3D->getDirectionVector();
Math::Vector3d previousToRight = rightOfOtherNear - _previousPosition;
Math::Angle angle = Math::Vector3d::angle(lookDirection, previousToRight);
if (angle > 270) {
canGoRight = false;
}
}
if (canGoRight && !canGoLeft) {
queueDestinationToAvoidItem(otherItem, _destination);
setDestinationWithoutHeight(rightOfOtherNear);
updatePath();
} else if (!canGoRight && canGoLeft) {
queueDestinationToAvoidItem(otherItem, _destination);
setDestinationWithoutHeight(leftOfOtherNear);
updatePath();
} else if (canGoRight && canGoLeft) {
Math::Vector3d forwardDirection = _currentTarget - _previousPosition;
Math::Vector3d cross = Math::Vector3d::crossProduct(forwardDirection, directionToOther);
if (cross.z() < 0.f) {
queueDestinationToAvoidItem(otherItem, _destination);
setDestinationWithoutHeight(leftOfOtherNear);
updatePath();
} else {
queueDestinationToAvoidItem(otherItem, _destination);
setDestinationWithoutHeight(rightOfOtherNear);
updatePath();
}
}
}
}
float Walk::getAngularSpeed() const {
return _defaultTurnAngleSpeed * StarkGlobal->getMillisecondsPerGameloop();
}
float Walk::computeDistancePerGameLoop() const {
Resources::Anim *anim = _item->getAnim();
float distancePerGameloop = anim->getMovementSpeed() * StarkGlobal->getMillisecondsPerGameloop() / 1000.f;
return distancePerGameloop;
}
void Walk::setDestination(const Math::Vector3d &destination) {
_destination = destination;
}
void Walk::setDestinationWithoutHeight(Math::Vector3d destination) {
Resources::Floor *floor = StarkGlobal->getCurrent()->getFloor();
int32 faceIndex = floor->findFaceContainingPoint(destination);
if (faceIndex >= 0) {
floor->computePointHeightInFace(destination, faceIndex);
}
setDestination(destination);
}
void Walk::setRunning() {
_running = true;
changeItemAnim();
}
void Walk::changeItemAnim() {
if (_ended) {
_item->setAnimActivity(Resources::Anim::kActorActivityIdle);
} else if (_turnDirection != kTurnNone) {
_item->setAnimActivity(Resources::Anim::kActorActivityIdle);
} else if (_running) {
_item->setAnimActivity(Resources::Anim::kActorActivityRun);
} else {
_item->setAnimActivity(Resources::Anim::kActorActivityWalk);
}
}
void Walk::changeDestination(const Math::Vector3d &destination) {
_collisionWaitTimeout = -1;
setDestination(destination);
updatePath();
}
bool Walk::hasReachedDestination() const {
return _reachedDestination;
}
uint32 Walk::getType() const {
return kTypeWalk;
}
void Walk::saveLoad(ResourceSerializer *serializer) {
serializer->syncAsVector3d(_destination);
serializer->syncAsUint32LE(_running);
}
} // End of namespace Stark

View File

@@ -0,0 +1,100 @@
/* 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/>.
*
*/
#ifndef STARK_MOVEMENT_WALK_H
#define STARK_MOVEMENT_WALK_H
#include "engines/stark/movement/movement.h"
#include "common/array.h"
namespace Stark {
class StringPullingPath;
namespace Resources {
class FloorPositionedItem;
}
/**
* Make an item walk / run to its destination on the current
* location's floor
*/
class Walk : public Movement {
public:
Walk(Resources::FloorPositionedItem *item);
virtual ~Walk();
// Movement API
void start() override;
void stop(bool force = false) override;
void onGameLoop() override;
bool hasReachedDestination() const override;
uint32 getType() const override;
void saveLoad(ResourceSerializer *serializer) override;
/** Set the destination */
void setDestination(const Math::Vector3d &destination);
void setDestinationWithoutHeight(Math::Vector3d destination);
/** Change the destination and recompute the path */
void changeDestination(const Math::Vector3d &destination);
/** Set the running flag */
void setRunning();
private:
void doWalk();
void doWalkCollisionSimple();
void doWalkCollisionAvoid();
float computeDistancePerGameLoop() const;
float getAngularSpeed() const;
void changeItemAnim();
void updatePath() const;
void queueDestinationToAvoidItem(Resources::FloorPositionedItem *item, const Math::Vector3d &destination);
bool isItemAlreadyAvoided(Resources::FloorPositionedItem *item) const;
static bool isPointNearPath(const Math::Vector3d &point3d, const Math::Vector3d &pathStart3d, const Math::Vector3d &pathEnd3d);
Resources::FloorPositionedItem *_item3D;
StringPullingPath *_path;
Math::Vector3d _destination;
Common::Array<Math::Vector3d> _destinations;
Common::Array<Resources::ItemVisual *> _avoidedItems;
bool _running;
bool _reachedDestination;
TurnDirection _turnDirection;
int32 _collisionWaitTimeout;
int32 _collisionWaitCount;
Math::Vector3d _previousPosition;
Math::Vector3d _currentTarget;
};
} // End of namespace Stark
#endif // STARK_MOVEMENT_WALK_H

View File

@@ -0,0 +1,207 @@
/* 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/resourcereference.h"
#include "engines/stark/debug.h"
#include "engines/stark/resources/level.h"
#include "engines/stark/resources/location.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/resourceprovider.h"
#include "engines/stark/services/staticprovider.h"
#include "engines/stark/services/services.h"
namespace Stark {
ResourceReference::PathElement::PathElement(Resources::Type type, uint16 index) :
_type(type), _index(index) {
}
Common::String ResourceReference::PathElement::describe() const {
return Common::String::format("(%s idx %d)", _type.getName(), _index);
}
ResourceReference::ResourceReference() {
}
void ResourceReference::addPathElement(Resources::Type type, uint16 index) {
_path.push_back(PathElement(type, index));
}
Resources::Object *ResourceReference::resolve() const {
Resources::Object *level = nullptr;
Resources::Object *resource = nullptr;
for (uint i = 0; i < _path.size(); i++) {
const PathElement &element = _path[i];
switch (element.getType().get()) {
case Resources::Type::kLevel:
if (StarkStaticProvider->isStaticLocation()) {
resource = level = StarkStaticProvider->getLevel();
assert(resource->getIndex() == element.getIndex());
} else if (element.getIndex()) {
resource = level = StarkResourceProvider->getLevel(element.getIndex());
} else {
resource = level = StarkGlobal->getLevel();
}
if (!level) {
error("Level '%d' not found", element.getIndex());
}
break;
case Resources::Type::kLocation:
if (!level) {
error("Cannot resolve location '%d' without resolving a level first", element.getIndex());
}
if (StarkStaticProvider->isStaticLocation()) {
resource = StarkStaticProvider->getLocation();
assert(resource->getIndex() == element.getIndex());
} else {
resource = StarkResourceProvider->getLocation(level->getIndex(), element.getIndex());
}
if (!resource) {
error("Location '%d' not found in level '%d'", element.getIndex(), level->getIndex());
}
break;
default:
assert(resource);
resource = resource->findChildWithIndex(element.getType(), element.getIndex());
break;
}
}
return resource;
}
bool ResourceReference::canResolve() const {
if (empty()) {
return false;
}
Resources::Object *level = nullptr;
for (uint i = 0; i < _path.size(); i++) {
const PathElement &element = _path[i];
switch (element.getType().get()) {
case Resources::Type::kLevel:
if (element.getIndex()) {
level = StarkResourceProvider->getLevel(element.getIndex());
} else {
level = StarkGlobal->getLevel();
}
if (!level) {
return false;
}
break;
case Resources::Type::kLocation: {
if (!level) {
return false;
}
Resources::Object *location = StarkResourceProvider->getLocation(level->getIndex(), element.getIndex());
if (!location) {
return false;
}
break;
}
default:
return true;
}
}
return true;
}
bool ResourceReference::empty() const {
return _path.empty();
}
Common::String ResourceReference::describe() const {
Common::String desc;
for (uint i = 0; i < _path.size(); i++) {
desc += _path[i].describe();
if (i != _path.size() - 1) {
desc += " ";
}
}
return desc;
}
void ResourceReference::buildFromResource(Resources::Object *resource) {
Common::Array<PathElement> reversePath;
while (resource && resource->getType() != Resources::Type::kRoot) {
reversePath.push_back(PathElement(resource->getType(), resource->getIndex()));
switch (resource->getType().get()) {
case Resources::Type::kLocation: {
Resources::Location *location = Resources::Object::cast<Resources::Location>(resource);
resource = StarkResourceProvider->getLevelFromLocation(location);
break;
}
default:
resource = resource->findParent<Resources::Object>();
break;
}
}
_path.clear();
for (int i = reversePath.size() - 1; i >= 0; i--) {
_path.push_back(reversePath[i]);
}
}
void ResourceReference::loadFromStream(Common::ReadStream *stream) {
_path.clear();
uint32 pathSize = stream->readUint32LE();
for (uint i = 0; i < pathSize; i++) {
byte rawType = stream->readByte();
Resources::Type type = Resources::Type((Resources::Type::ResourceType) (rawType));
uint16 index = stream->readUint16LE();
addPathElement(type, index);
}
}
void ResourceReference::saveToStream(Common::WriteStream *stream) {
stream->writeUint32LE(_path.size());
for (uint i = 0; i < _path.size(); i++) {
byte rawType = _path[i].getType().get();
uint16 index = _path[i].getIndex();
stream->writeByte(rawType);
stream->writeUint16LE(index);
}
}
} // End of namespace Stark

View File

@@ -0,0 +1,90 @@
/* 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/>.
*
*/
#ifndef STARK_RESOURCES_RESOURCE_REFERENCE_H
#define STARK_RESOURCES_RESOURCE_REFERENCE_H
#include "common/array.h"
#include "common/stream.h"
#include "engines/stark/resources/object.h"
namespace Stark {
/**
* A reference to a resource.
*
* Internally, the referenced resource is designed by its path
* in the resource tree.
*
*/
class ResourceReference {
public:
ResourceReference();
Common::String describe() const;
/** Read the reference from a stream */
void loadFromStream(Common::ReadStream *stream);
/** Write the reference to a stream */
void saveToStream(Common::WriteStream *stream);
/** Make the reference point to the specified object */
void buildFromResource(Resources::Object *resource);
/** Resolve the reference to the actual resource */
template <class T>
T* resolve() const;
/** Return true if this reference is a null pointer */
bool empty() const;
/** Can this reference be resolved using currently loaded archives? */
bool canResolve() const;
private:
void addPathElement(Resources::Type type, uint16 index);
Resources::Object *resolve() const;
class PathElement {
public:
PathElement(Resources::Type type, uint16 index);
Common::String describe() const;
Resources::Type getType() const { return _type; }
uint16 getIndex() const { return _index; }
private:
Resources::Type _type;
uint16 _index;
};
Common::Array<PathElement> _path;
};
template<class T>
T* ResourceReference::resolve() const {
return Resources::Object::cast<T>(resolve());
}
} // End of namespace Stark
#endif // STARK_RESOURCES_RESOURCE_REFERENCE_H

View File

@@ -0,0 +1,677 @@
/* 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 "common/debug.h"
#include "engines/stark/debug.h"
#include "engines/stark/formats/biffmesh.h"
#include "engines/stark/formats/tm.h"
#include "engines/stark/formats/xrc.h"
#include "engines/stark/gfx/driver.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/animscript.h"
#include "engines/stark/resources/bonesmesh.h"
#include "engines/stark/resources/direction.h"
#include "engines/stark/resources/image.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/location.h"
#include "engines/stark/resources/textureset.h"
#include "engines/stark/services/archiveloader.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/settings.h"
#include "engines/stark/services/stateprovider.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/model/skeleton_anim.h"
#include "engines/stark/visual/actor.h"
#include "engines/stark/visual/prop.h"
#include "engines/stark/visual/smacker.h"
namespace Stark {
namespace Resources {
Object *Anim::construct(Object *parent, byte subType, uint16 index, const Common::String &name) {
switch (subType) {
case kAnimImages:
return new AnimImages(parent, subType, index, name);
case kAnimProp:
return new AnimProp(parent, subType, index, name);
case kAnimVideo:
return new AnimVideo(parent, subType, index, name);
case kAnimSkeleton:
return new AnimSkeleton(parent, subType, index, name);
default:
error("Unknown anim subtype %d", subType);
}
}
Anim::~Anim() {
}
Anim::Anim(Object *parent, byte subType, uint16 index, const Common::String &name) :
Object(parent, subType, index, name),
_activity(0),
_currentFrame(0),
_numFrames(0),
_refCount(0) {
_type = TYPE;
}
void Anim::readData(Formats::XRCReadStream *stream) {
_activity = stream->readUint32LE();
_numFrames = stream->readUint32LE();
}
void Anim::selectFrame(uint32 frameIndex) {
}
uint32 Anim::getActivity() const {
return _activity;
}
void Anim::applyToItem(Item *item) {
_refCount++;
}
void Anim::removeFromItem(Item *item) {
_refCount--;
}
bool Anim::isInUse() const {
return _refCount > 0;
}
int Anim::getPointHotspotIndex(const Common::Point &point) const {
// Most anim types only have one hotspot
return 0;
}
void Anim::playAsAction(ItemVisual *item) {
AnimScript *animScript = findChild<AnimScript>();
animScript->goToScriptItem(0);
}
bool Anim::isAtTime(uint32 time) const {
warning("Anim::isAtTime is not implemented");
return true;
}
void Anim::shouldResetItem(bool resetItem) {
// Script animations don't keep track of the item
}
void Anim::resetItem() {
// Script animations don't keep track of the item
}
bool Anim::isDone() const {
AnimScript *animScript = findChild<AnimScript>();
return animScript->isDone();
}
uint32 Anim::getMovementSpeed() const {
return 100;
}
uint32 Anim::getIdleActionFrequency() const {
return 1;
}
void Anim::printData() {
debug("activity: %d", _activity);
debug("numFrames: %d", _numFrames);
}
AnimImages::~AnimImages() {
}
AnimImages::AnimImages(Object *parent, byte subType, uint16 index, const Common::String &name) :
Anim(parent, subType, index, name),
_field_3C(0),
_currentDirection(0),
_currentFrameImage(nullptr) {
}
void AnimImages::readData(Formats::XRCReadStream *stream) {
Anim::readData(stream);
_field_3C = stream->readFloatLE();
}
void AnimImages::onAllLoaded() {
Anim::onAllLoaded();
_directions = listChildren<Direction>();
}
void AnimImages::selectFrame(uint32 frameIndex) {
if (frameIndex > _numFrames) {
// The original silently ignores this as well
warning("Request for frame %d for anim '%s' has been ignored, it is above max frame %d", frameIndex, getName().c_str(), _numFrames);
_currentFrame = 0;
}
_currentFrame = frameIndex;
}
Visual *AnimImages::getVisual() {
Direction *direction = _directions[_currentDirection];
_currentFrameImage = direction->findChildWithIndex<Image>(_currentFrame);
return _currentFrameImage->getVisual();
}
void AnimImages::printData() {
Anim::printData();
debug("field_3C: %f", _field_3C);
}
int AnimImages::getPointHotspotIndex(const Common::Point &point) const {
if (_currentFrameImage) {
return _currentFrameImage->indexForPoint(point);
}
return -1;
}
Common::Point AnimImages::getHotspotPosition(uint index) const {
if (_currentFrameImage) {
return _currentFrameImage->getHotspotPosition(index);
}
return Common::Point(-1, -1);
}
void AnimImages::saveLoad(ResourceSerializer *serializer) {
Anim::saveLoad(serializer);
serializer->syncAsUint32LE(_currentFrame);
if (serializer->isLoading()) {
selectFrame(_currentFrame);
}
}
AnimProp::~AnimProp() {
delete _visual;
}
AnimProp::AnimProp(Object *parent, byte subType, uint16 index, const Common::String &name) :
Anim(parent, subType, index, name),
_movementSpeed(100) {
_visual = StarkGfx->createPropRenderer();
}
Visual *AnimProp::getVisual() {
return _visual;
}
uint32 AnimProp::getMovementSpeed() const {
return _movementSpeed;
}
void AnimProp::readData(Formats::XRCReadStream *stream) {
Anim::readData(stream);
_field_3C = stream->readString();
uint32 meshCount = stream->readUint32LE();
for (uint i = 0; i < meshCount; i++) {
_meshFilenames.push_back(Common::Path(stream->readString()));
}
_textureFilename = stream->readString();
_movementSpeed = stream->readUint32LE();
_archiveName = stream->getArchiveName();
}
void AnimProp::onPostRead() {
if (_meshFilenames.size() != 1) {
error("Unexpected mesh count in prop anim: '%d'", _meshFilenames.size());
}
ArchiveReadStream *stream = StarkArchiveLoader->getFile(_meshFilenames[0], _archiveName);
_visual->setModel(Formats::BiffMeshReader::read(stream));
delete stream;
stream = StarkArchiveLoader->getFile(_textureFilename, _archiveName);
_visual->setTexture(Formats::TextureSetReader::read(stream));
delete stream;
}
void AnimProp::printData() {
Anim::printData();
debug("field_3C: %s", _field_3C.c_str());
Common::String description;
for (uint32 i = 0; i < _meshFilenames.size(); i++) {
debug("meshFilename[%d]: %s", i, _meshFilenames[i].toString().c_str());
}
debug("textureFilename: %s", _textureFilename.toString().c_str());
debug("movementSpeed: %d", _movementSpeed);
}
AnimVideo::~AnimVideo() {
delete _smacker;
}
AnimVideo::AnimVideo(Object *parent, byte subType, uint16 index, const Common::String &name) :
Anim(parent, subType, index, name),
_width(0),
_height(0),
_smacker(nullptr),
_frameRateOverride(-1),
_preload(false),
_loop(false),
_actionItem(nullptr),
_shouldResetItem(true),
_done(false) {
}
void AnimVideo::readData(Formats::XRCReadStream *stream) {
Anim::readData(stream);
_smackerFile = stream->readString();
_width = stream->readUint32LE();
_height = stream->readUint32LE();
_positions.clear();
_sizes.clear();
uint32 size = stream->readUint32LE();
for (uint i = 0; i < size; i++) {
_positions.push_back(stream->readPoint());
_sizes.push_back(stream->readRect());
}
_loop = stream->readBool();
_frameRateOverride = stream->readUint32LE();
if (stream->isDataLeft()) {
_preload = stream->readBool();
}
_archiveName = stream->getArchiveName();
// WORKAROUND: Fix the position of various items being incorrect in the game datafiles
Location *location = findParent<Location>();
if (_name == "Mountain comes down" && location && location->getName() == "Below Floating Mountain") {
for (uint i = 0; i < _sizes.size(); i++) {
_positions[i].x = 352;
}
}
}
void AnimVideo::onAllLoaded() {
if (!_smacker) {
_smacker = new VisualSmacker(StarkGfx);
Common::SeekableReadStream *overrideStreamBink = nullptr;
Common::SeekableReadStream *overrideStreamSmacker = nullptr;
if (StarkSettings->isAssetsModEnabled() && StarkGfx->supportsModdedAssets()) {
overrideStreamBink = openOverrideFile(".bik");
if (!overrideStreamBink) {
overrideStreamSmacker = openOverrideFile(".smk");
}
}
Common::SeekableReadStream *stream = StarkArchiveLoader->getExternalFile(_smackerFile, _archiveName);
if (overrideStreamBink) {
_smacker->loadBink(overrideStreamBink);
_smacker->readOriginalSize(stream);
} else if (overrideStreamSmacker) {
_smacker->loadSmacker(overrideStreamSmacker);
_smacker->readOriginalSize(stream);
} else {
_smacker->loadSmacker(stream);
}
_smacker->overrideFrameRate(_frameRateOverride);
updateSmackerPosition();
}
}
Common::SeekableReadStream *AnimVideo::openOverrideFile(const Common::String &extension) const {
Common::String baseName(_smackerFile.baseName());
if (!baseName.hasSuffixIgnoreCase(".sss")) {
return nullptr;
}
baseName = Common::String(baseName.c_str(), baseName.size() - 4) + extension;
Common::Path filePath(_smackerFile.getParent().appendComponent(baseName));
filePath = StarkArchiveLoader->getExternalFilePath(filePath, _archiveName);
debugC(kDebugModding, "Attempting to load %s", filePath.toString(Common::Path::kNativeSeparator).c_str());
Common::SeekableReadStream *smkStream = SearchMan.createReadStreamForMember(filePath);
if (!smkStream) {
return nullptr;
}
debugC(kDebugModding, "Loaded %s", filePath.toString(Common::Path::kNativeSeparator).c_str());
return smkStream;
}
void AnimVideo::onGameLoop() {
if (!_smacker || !isInUse()) {
return; // Animation not in use, no need to update the movie
}
if (_smacker->isDone()) {
// The last frame has been reached
_done = true;
if (_shouldResetItem) {
resetItem();
}
if (_loop) {
_smacker->rewind();
}
}
if (!_smacker->isDone()) {
_smacker->update();
updateSmackerPosition();
}
}
void AnimVideo::resetItem() {
if (!_loop && _actionItem) {
// Reset our item if needed
if (_actionItem->getActionAnim() == this) {
_actionItem->resetActionAnim();
}
_actionItem = nullptr;
}
}
void AnimVideo::onEnginePause(bool pause) {
Object::onEnginePause(pause);
if (_smacker && isInUse()) {
_smacker->pause(pause);
}
}
Visual *AnimVideo::getVisual() {
return _smacker;
}
void AnimVideo::updateSmackerPosition() {
int frame = _smacker->getFrameNumber();
if (frame == -1) {
return;
}
if (frame < (int) _positions.size()) {
_smacker->setPosition(_positions[frame]);
}
}
void AnimVideo::shouldResetItem(bool resetItem) {
_shouldResetItem = resetItem;
}
void AnimVideo::playAsAction(ItemVisual *item) {
_actionItem = item;
_shouldResetItem = true;
_done = false;
if (!_loop) {
_smacker->rewind();
}
// Update here so we have something up to date to show when rendering this frame
_smacker->update();
}
bool AnimVideo::isAtTime(uint32 time) const {
uint32 currentTime = _smacker->getCurrentTime();
return currentTime >= time;
}
void AnimVideo::saveLoadCurrent(ResourceSerializer *serializer) {
Anim::saveLoadCurrent(serializer);
int32 frameNumber = _smacker->getFrameNumber();
serializer->syncAsSint32LE(frameNumber);
serializer->syncAsSint32LE(_refCount);
// TODO: Seek to the saved frame number when loading
}
void AnimVideo::printData() {
Anim::printData();
debug("smackerFile: %s", _smackerFile.toString().c_str());
debug("size: x %d, y %d", _width, _height);
Common::String description;
for (uint32 i = 0; i < _positions.size(); i++) {
description += Common::String::format("(x %d, y %d) ", _positions[i].x, _positions[i].y);
}
debug("positions: %s", description.c_str());
description.clear();
for (uint32 i = 0; i < _sizes.size(); i++) {
description += Common::String::format("(l %d, t %d, r %d, b %d) ",
_sizes[i].left, _sizes[i].top, _sizes[i].right, _sizes[i].bottom);
}
debug("sizes: %s", description.c_str());
debug("frameRateOverride: %d", _frameRateOverride);
debug("preload: %d", _preload);
debug("loop: %d", _loop);
}
AnimSkeleton::~AnimSkeleton() {
delete _visual;
delete _skeletonAnim;
}
AnimSkeleton::AnimSkeleton(Object *parent, byte subType, uint16 index, const Common::String &name) :
Anim(parent, subType, index, name),
_castsShadow(true),
_loop(false),
_movementSpeed(100),
_idleActionFrequency(1),
_skeletonAnim(nullptr),
_currentTime(0),
_totalTime(0),
_done(false),
_actionItem(nullptr),
_shouldResetItem(true) {
_visual = StarkGfx->createActorRenderer();
}
void AnimSkeleton::applyToItem(Item *item) {
Anim::applyToItem(item);
if (!_loop) {
_currentTime = 0;
}
if (_currentTime > _totalTime) {
_currentTime = 0;
}
debugC(kDebugAnimation, "%s: add %s", item->getName().c_str(), getName().c_str());
ModelItem *modelItem = Object::cast<ModelItem>(item);
BonesMesh *mesh = modelItem->findBonesMesh();
TextureSet *texture = modelItem->findTextureSet(TextureSet::kTextureNormal);
AnimHandler *animHandler = modelItem->getAnimHandler();
animHandler->setModel(mesh->getModel());
animHandler->setAnim(_skeletonAnim);
_visual->setModel(mesh->getModel());
_visual->setAnimHandler(animHandler);
_visual->setTexture(texture->getTexture());
_visual->setTextureFacial(nullptr);
_visual->setTime(_currentTime);
_visual->setCastShadow(_castsShadow);
}
void AnimSkeleton::removeFromItem(Item *item) {
Anim::removeFromItem(item);
debugC(kDebugAnimation, "%s: remove %s", item->getName().c_str(), getName().c_str());
_actionItem = nullptr;
}
Visual *AnimSkeleton::getVisual() {
return _visual;
}
void AnimSkeleton::readData(Formats::XRCReadStream *stream) {
Anim::readData(stream);
_animFilename = stream->readString();
stream->readString(); // Skipped in the original
stream->readString(); // Skipped in the original
stream->readString(); // Skipped in the original
_loop = stream->readBool();
_movementSpeed = stream->readUint32LE();
if (_movementSpeed < 1) {
_movementSpeed = 100;
}
if (stream->isDataLeft()) {
_castsShadow = stream->readBool();
} else {
_castsShadow = true;
}
if (stream->isDataLeft()) {
_idleActionFrequency = stream->readUint32LE();
} else {
_idleActionFrequency = 1;
}
_archiveName = stream->getArchiveName();
}
void AnimSkeleton::onPostRead() {
ArchiveReadStream *stream = StarkArchiveLoader->getFile(_animFilename, _archiveName);
_skeletonAnim = new SkeletonAnim();
_skeletonAnim->createFromStream(stream);
delete stream;
}
void AnimSkeleton::onAllLoaded() {
Anim::onAllLoaded();
_totalTime = _skeletonAnim->getLength();
_currentTime = 0;
}
void AnimSkeleton::onGameLoop() {
Anim::onGameLoop();
if (isInUse() && _totalTime) {
uint32 newTime = _currentTime + StarkGlobal->getMillisecondsPerGameloop();
if (!_loop && newTime >= _totalTime) {
_done = true;
if (_shouldResetItem) {
resetItem();
}
} else {
_currentTime = newTime % _totalTime;
_visual->setTime(_currentTime);
}
}
}
void AnimSkeleton::resetItem() {
if (_actionItem) {
if (_actionItem->getActionAnim() == this) {
_actionItem->resetActionAnim();
}
_actionItem = nullptr;
}
}
void AnimSkeleton::onPreDestroy() {
resetItem();
Anim::onPreDestroy();
}
uint32 AnimSkeleton::getMovementSpeed() const {
return _movementSpeed;
}
uint32 AnimSkeleton::getCurrentTime() const {
return _currentTime;
}
uint32 AnimSkeleton::getRemainingTime() const {
int32 remainingTime = _totalTime - _currentTime;
return CLIP<int32>(remainingTime, 0, _totalTime);
}
void AnimSkeleton::shouldResetItem(bool resetItem) {
_shouldResetItem = resetItem;
}
void AnimSkeleton::playAsAction(ItemVisual *item) {
_actionItem = item;
_done = false;
_shouldResetItem = true;
if (!_loop) {
_currentTime = 0;
}
}
bool AnimSkeleton::isAtTime(uint32 time) const {
return _currentTime >= time;
}
uint32 AnimSkeleton::getIdleActionFrequency() const {
return _idleActionFrequency;
}
void AnimSkeleton::printData() {
Anim::printData();
debug("filename: %s", _animFilename.toString().c_str());
debug("castsShadow: %d", _castsShadow);
debug("loop: %d", _loop);
debug("movementSpeed: %d", _movementSpeed);
debug("idleActionFrequency: %d", _idleActionFrequency);
}
} // End of namespace Resources
} // End of namespace Stark

View File

@@ -0,0 +1,337 @@
/* 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/>.
*
*/
#ifndef STARK_RESOURCES_ANIM_H
#define STARK_RESOURCES_ANIM_H
#include "common/path.h"
#include "common/rect.h"
#include "common/str.h"
#include "engines/stark/resources/object.h"
namespace Common {
class SeekableReadStream;
}
namespace Stark {
class SkeletonAnim;
class VisualActor;
class VisualProp;
class VisualSmacker;
class Visual;
namespace Formats {
class XRCReadStream;
}
namespace Resources {
class Direction;
class Image;
class Item;
class ItemVisual;
/**
* Animation base class
*
* Animations provide a time dependent visual state to Items
*/
class Anim : public Object {
public:
static const Type::ResourceType TYPE = Type::kAnim;
enum SubType {
kAnimImages = 1,
kAnimProp = 2,
kAnimVideo = 3,
kAnimSkeleton = 4
};
enum ActionUsage {
kActionUsagePassive = 1,
kActionUsageActive = 2
};
enum UIUsage {
kUIUsageInventory = 1,
kUIUsageUseCursorPassive = 4,
kUIUsageUseCursorActive = 5
};
enum ActorActivity {
kActorActivityIdle = 1,
kActorActivityWalk = 2,
kActorActivityTalk = 3,
kActorActivityRun = 6,
kActorActivityIdleAction = 10
};
/** Anim factory */
static Object *construct(Object *parent, byte subType, uint16 index, const Common::String &name);
Anim(Object *parent, byte subType, uint16 index, const Common::String &name);
~Anim() override;
// Resource API
void readData(Formats::XRCReadStream *stream) override;
/** Get current displayed frame */
uint32 getCurrentFrame() { return _currentFrame; }
/** Sets the animation frame to be displayed */
virtual void selectFrame(uint32 frameIndex);
/** Obtain the Visual to be used to render the animation */
virtual Visual *getVisual() = 0;
/** Associate the animation to an Item */
virtual void applyToItem(Item *item);
/** Dissociate the animation from an item */
virtual void removeFromItem(Item *item);
/** Check is the animation is being used by an item */
bool isInUse() const;
/** Obtain the purpose of this anim */
uint32 getActivity() const;
/** Return the hotspot index for a point given in relative coordinates */
virtual int getPointHotspotIndex(const Common::Point &point) const;
/** Get the hotspot position for a given index of a pat-table */
virtual Common::Point getHotspotPosition(uint index) const { return Common::Point(-1, -1); }
/**
* Play the animation as an action for an item.
*
* This sets up a callback to the item for when the animation completes.
*/
virtual void playAsAction(ItemVisual *item);
/** Checks if the elapsed time since the animation start is greater than a specified duration */
virtual bool isAtTime(uint32 time) const;
/** Get the anim movement speed in units per seconds */
virtual uint32 getMovementSpeed() const;
/** Get the chance the animation has to play among other idle actions from the same anim hierarchy */
virtual uint32 getIdleActionFrequency() const;
/**
* When this animation is playing as an action should a new animation
* be chosen for the item as soon as this one completes based on
* the item's activity?
* This is true by default, but setting it to false allows scripts
* to chose precisely the new animation to play, and to start it
* in the same frame as this one is removed.
*/
virtual void shouldResetItem(bool resetItem);
/**
* Remove this action animation for the item and select a new animation
* based on the item's current activity.
*/
virtual void resetItem();
/**
* Is this animation done playing.
*
* Only valid for animations started with playAsAction.
*/
virtual bool isDone() const;
protected:
void printData() override;
uint32 _activity;
uint32 _currentFrame;
uint32 _numFrames;
int32 _refCount;
};
/**
* Displays still images controlled by an AnimScript
*/
class AnimImages : public Anim {
public:
AnimImages(Object *parent, byte subType, uint16 index, const Common::String &name);
~AnimImages() override;
// Resource API
void readData(Formats::XRCReadStream *stream) override;
void onAllLoaded() override;
void saveLoad(ResourceSerializer *serializer) override;
// Anim API
void selectFrame(uint32 frameIndex) override;
Visual *getVisual() override;
int getPointHotspotIndex(const Common::Point &point) const override;
Common::Point getHotspotPosition(uint index) const override;
protected:
void printData() override;
float _field_3C;
uint32 _currentDirection;
Common::Array<Direction *> _directions;
Image *_currentFrameImage;
};
class AnimProp : public Anim {
public:
AnimProp(Object *parent, byte subType, uint16 index, const Common::String &name);
~AnimProp() override;
// Resource API
void readData(Formats::XRCReadStream *stream) override;
void onPostRead() override;
// Anim API
Visual *getVisual() override;
uint32 getMovementSpeed() const override;
protected:
void printData() override;
Common::String _field_3C;
Common::Array<Common::Path> _meshFilenames;
Common::Path _textureFilename;
uint32 _movementSpeed;
Common::Path _archiveName;
VisualProp *_visual;
};
/**
* Displays a Smacker video
*/
class AnimVideo : public Anim {
public:
AnimVideo(Object *parent, byte subType, uint16 index, const Common::String &name);
~AnimVideo() override;
// Resource API
void readData(Formats::XRCReadStream *stream) override;
void onAllLoaded() override;
void onGameLoop() override;
void onEnginePause(bool pause) override;
void saveLoadCurrent(ResourceSerializer *serializer) override;
// Anim API
Visual *getVisual() override;
void playAsAction(ItemVisual *item) override;
void shouldResetItem(bool resetItem) override;
void resetItem() override;
bool isAtTime(uint32 time) const override;
bool isDone() const override { return _done || !isInUse(); }
protected:
typedef Common::Array<Common::Point> PointArray;
typedef Common::Array<Common::Rect> RectArray;
void printData() override;
Common::SeekableReadStream *openOverrideFile(const Common::String &extension) const;
/** Update the position of the video for the current frame */
void updateSmackerPosition();
Common::Path _smackerFile;
Common::Path _archiveName;
VisualSmacker *_smacker;
uint32 _width;
uint32 _height;
PointArray _positions;
RectArray _sizes;
int32 _frameRateOverride;
bool _preload;
bool _loop;
bool _done;
ItemVisual *_actionItem;
bool _shouldResetItem;
};
/**
* Animates a 3D mesh skeleton
*/
class AnimSkeleton : public Anim {
public:
AnimSkeleton(Object *parent, byte subType, uint16 index, const Common::String &name);
~AnimSkeleton() override;
// Resource API
void readData(Formats::XRCReadStream *stream) override;
void onPostRead() override;
void onAllLoaded() override;
void onGameLoop() override;
void onPreDestroy() override;
// Anim API
void applyToItem(Item *item) override;
void removeFromItem(Item *item) override;
Visual *getVisual() override;
void playAsAction(ItemVisual *item) override;
bool isAtTime(uint32 time) const override;
bool isDone() const override { return _done || !isInUse(); }
uint32 getMovementSpeed() const override;
uint32 getIdleActionFrequency() const override;
void shouldResetItem(bool resetItem) override;
void resetItem() override;
/** Get the duration in milliseconds before the animation loops ends */
uint32 getRemainingTime() const;
/** Get the position in the animation loop in milliseconds */
uint32 getCurrentTime() const;
protected:
void printData() override;
bool _castsShadow;
Common::Path _archiveName;
Common::Path _animFilename;
bool _loop;
uint32 _movementSpeed;
uint32 _idleActionFrequency;
uint32 _totalTime;
uint32 _currentTime;
bool _done;
SkeletonAnim *_skeletonAnim;
VisualActor *_visual;
ItemVisual *_actionItem;
bool _shouldResetItem;
};
} // End of namespace Resources
} // End of namespace Stark
#endif // STARK_RESOURCES_ANIM_H

View File

@@ -0,0 +1,209 @@
/* 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/animhierarchy.h"
#include "common/debug.h"
#include "common/random.h"
#include "engines/stark/formats/xrc.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/bonesmesh.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/textureset.h"
#include "engines/stark/services/services.h"
namespace Stark {
namespace Resources {
AnimHierarchy::~AnimHierarchy() {
}
AnimHierarchy::AnimHierarchy(Object *parent, byte subType, uint16 index, const Common::String &name) :
Object(parent, subType, index, name),
_currentActivity(0),
_currentAnim(nullptr),
_field_5C(0),
_idleActionsFrequencySum(0) {
_type = TYPE;
}
void AnimHierarchy::readData(Formats::XRCReadStream *stream) {
_animationReferences.clear();
uint32 refCount = stream->readUint32LE();
for (uint32 i = 0; i < refCount; i++) {
_animationReferences.push_back(stream->readResourceReference());
}
_parentAnimHierarchyReference = stream->readResourceReference();
_field_5C = stream->readFloatLE();
}
void AnimHierarchy::onAllLoaded() {
Object::onAllLoaded();
loadActivityAnimations();
loadIdleAnimations();
}
void AnimHierarchy::loadActivityAnimations() {
AnimHierarchy *parentHierarchy = _parentAnimHierarchyReference.resolve<AnimHierarchy>();
// Activity animations are inherited from the parent ...
if (parentHierarchy) {
_activityAnimations = parentHierarchy->_activityAnimations;
}
// ... but can be overridden
for (uint i = 0; i < _animationReferences.size(); i++) {
Anim *anim = _animationReferences[i].resolve<Anim>();
bool inserted = false;
for (uint j = 0; j < _activityAnimations.size(); j++) {
if (_activityAnimations[j]->getActivity() == anim->getActivity()) {
_activityAnimations[j] = anim;
inserted = true;
}
}
if (!inserted) {
_activityAnimations.push_back(anim);
}
}
}
void AnimHierarchy::loadIdleAnimations() {
AnimHierarchy *parentHierarchy = _parentAnimHierarchyReference.resolve<AnimHierarchy>();
if (parentHierarchy) {
_idleAnimations = parentHierarchy->_idleAnimations;
}
for (uint i = 0; i < _animationReferences.size(); i++) {
Anim *anim = _animationReferences[i].resolve<Anim>();
if (anim->getActivity() == Anim::kActorActivityIdleAction) {
_idleAnimations.push_back(anim);
}
}
_idleActionsFrequencySum = 0;
for (uint i = 0; i < _idleAnimations.size(); i++) {
_idleActionsFrequencySum += _idleAnimations[i]->getIdleActionFrequency();
}
}
void AnimHierarchy::setItemAnim(ItemVisual *item, int32 activity) {
unselectItemAnim(item);
_currentActivity = activity;
selectItemAnim(item);
}
void AnimHierarchy::unselectItemAnim(ItemVisual *item) {
if (_currentAnim && _currentAnim->isInUse()) {
_currentAnim->removeFromItem(item);
}
_currentAnim = nullptr;
}
void AnimHierarchy::selectItemAnim(ItemVisual *item) {
// Search for an animation with the appropriate index
for (uint i = 0; i < _activityAnimations.size(); i++) {
if (_activityAnimations[i]->getActivity() == _currentActivity) {
_currentAnim = _activityAnimations[i];
break;
}
}
// Default to the first animation
if (!_currentAnim && !_activityAnimations.empty()) {
_currentAnim = _activityAnimations[0];
}
if (!_currentAnim) {
error("Failed to set an animation for item %s", item->getName().c_str());
}
if (!_currentAnim->isInUse()) {
_currentAnim->applyToItem(item);
}
}
Anim *AnimHierarchy::getCurrentAnim() {
return _currentAnim;
}
BonesMesh *AnimHierarchy::findBonesMesh() {
return findChild<BonesMesh>();
}
TextureSet *AnimHierarchy::findTextureSet(uint32 textureType) {
return findChildWithSubtype<TextureSet>(textureType);
}
Anim *AnimHierarchy::getAnimForActivity(uint32 activity) {
// Search for an animation with the appropriate use
for (uint i = 0; i < _activityAnimations.size(); i++) {
if (_activityAnimations[i]->getActivity() == activity) {
return _activityAnimations[i];
}
}
return nullptr;
}
Visual *AnimHierarchy::getVisualForUsage(uint32 usage) {
Anim *anim = getAnimForActivity(usage);
if (anim) {
return anim->getVisual();
}
return nullptr;
}
Anim *AnimHierarchy::getIdleActionAnim() const {
if (_idleActionsFrequencySum == 0) {
return nullptr; // There are no idle animations
}
int pick = StarkRandomSource->getRandomNumber(_idleActionsFrequencySum - 1);
for (uint i = 0; i < _idleAnimations.size(); i++) {
pick -= _idleAnimations[i]->getIdleActionFrequency();
if (pick < 0) {
return _idleAnimations[i];
}
}
return nullptr;
}
void AnimHierarchy::printData() {
for (uint i = 0; i < _animationReferences.size(); i++) {
debug("anim %d: %s", i, _animationReferences[i].describe().c_str());
}
debug("animHierarchy: %s", _parentAnimHierarchyReference.describe().c_str());
debug("field_5C: %f", _field_5C);
}
} // End of namespace Resources
} // End of namespace Stark

Some files were not shown because too many files have changed in this diff Show More