Model system rewrite

This commit is contained in:
Llennpie 2022-08-14 16:50:17 -04:00
parent 334d10ae57
commit e16f8807f3
9 changed files with 320 additions and 188 deletions

View file

@ -677,6 +677,7 @@ else
endif # End of LDFLAGS
LDFLAGS += -lstdc++
LDFLAGS += -ljsoncpp
# icon
ifeq ($(WINDOWS_BUILD),1)

23
dynos/README.txt Normal file
View file

@ -0,0 +1,23 @@
SATURN DYNOS GUIDE
------------------
Place custom eye textures in eyes/ ... Only PNG files or folders within folders are accepted.
Place color codes in colorcodes/ ... Only GS files are supported.
Place custom model packs in packs/ ...
HOW TO USE CUSTOM MODELS
------------------------
Any DynOS model pack (from the PC port) should work fine with Saturn.
Models created with Saturn may support custom textures. Find them in packs/<ModelFolder>/expressions/ ...
Just a friendly reminder - if you use someone else's model in a video, it's good etiquette to credit them when possible!
//
For a guide on how Saturn model packs can be created, check the wiki:
https://github.com/Llennpie/Saturn/wiki/DEV:-Creating-Models
By default, Saturn comes with two models - Shadow Mario and CmtSPARK Mario.
Both presets provide an example of how custom models can be used.

View file

@ -1,6 +0,0 @@
SUBMENU "saturn" "Saturn" "SATURN"
BIND "show_imgui" "Toggle Menu" "show_imgui" 0x0400 0x0014 0x100C 0xFFFF
BIND "machinima_camera" "Machinima Camera" "machinima_camera" 0x0800 0x0009 0x100B 0xFFFF
BIND "scroll_eyes" "Cycle Eye State" "scroll_eyes" 0x0100 0x000A 0x100E 0xFFFF
BIND "load_animation" "Load Animation" "load_animation" 0x0200 0x000B 0x100D 0xFFFF
ENDMENU

View file

@ -69,6 +69,8 @@ bool any_packs_selected;
int windowListSize = 325;
int cc_model_id;
int current_exp_index[6];
/*
Sets Mario's global colors from the CC editor color values.
*/
@ -285,17 +287,14 @@ void sdynos_imgui_menu() {
DynOS_Opt_SetValue(String("dynos_pack_%d", i), is_selected);
// reset stuff
model_mouth_enabled = false;
mouth_array.clear();
saturn_load_eye_folder("dynos/eyes/");
saturn_set_eye_texture(0);
saturn_load_eyes_from_model("dynos/packs/", label);
saturn_set_eye_texture(0);
saturn_load_mouths_from_model("dynos/packs/", label);
saturn_set_mouth_texture(0);
// Fetch model data
saturn_load_model_data(label);
for (int i = 0; i < 6; i++) {
current_exp_index[i] = 0;
}
if (is_selected)
std::cout << "Loaded " << current_model_data.name << " by " << current_model_data.author << std::endl;
one_pack_selectable = false;
for (int k = 0; k < sDynosPacks.Count(); k++) {
@ -305,10 +304,12 @@ void sdynos_imgui_menu() {
any_packs_selected = one_pack_selectable;
if (!any_packs_selected) {
model_mouth_enabled = false;
mouth_array.clear();
saturn_load_eye_folder("dynos/eyes/");
saturn_set_eye_texture(0);
ModelData blank;
current_model_data = blank;
using_model_eyes = false;
cc_model_support = true;
cc_spark_support = false;
}
}
if (ImGui::BeginPopupContextItem())
@ -320,6 +321,12 @@ void sdynos_imgui_menu() {
set_editor_from_global_cc(label);
ImGui::CloseCurrentPopup();
}
if (is_selected && current_model_data.name != "") {
ImGui::Separator();
ImGui::Text(current_model_data.name.c_str());
ImGui::Text(("By " + current_model_data.author).c_str());
ImGui::Text(("v" + current_model_data.version).c_str());
}
ImGui::EndPopup();
}
}
@ -344,59 +351,96 @@ void sdynos_imgui_menu() {
else if (current_eye_state == 4) {
scrollEyeState = 4;
if (eye_array.size() > 0) {
if (!using_model_eyes) {
if (eye_array.size() > 0) {
ImGui::BeginChild("###menu_custom_eye_selector", ImVec2(-FLT_MIN, 150), true);
for (int n = 0; n < eye_array.size(); n++) {
const bool is_eye_selected = (current_eye_index == n);
string entry_name = eye_array[n];
// Custom Eye Textures
ImGui::BeginChild("###menu_custom_eye_selector", ImVec2(-FLT_MIN, 150), true);
for (int n = 0; n < eye_array.size(); n++) {
const bool is_eye_selected = (current_eye_index == n);
string entry_name = eye_array[n];
if (ImGui::Selectable(entry_name.c_str(), is_eye_selected)) {
current_eye_index = n;
saturn_eye_selectable(entry_name, n);
if (ImGui::Selectable(entry_name.c_str(), is_eye_selected)) {
current_eye_index = n;
saturn_eye_selectable(entry_name, n);
}
}
ImGui::EndChild();
}
ImGui::EndChild();
}
}
else { scrollEyeState = current_eye_state; }
is_replacing_eyes = (scrollEyeState == 4 || model_mouth_enabled);
is_replacing_eyes = (scrollEyeState == 4 || current_model_data.expressions.size() > 0);
if (model_mouth_enabled) {
ImGui::Text("Mouth");
ImGui::SameLine(); imgui_bundled_help_marker(
"Place custom mouth PNG textures in dynos/packs/<pack_name>/mouths.");
if (current_model_data.name != "") {
if (mouth_array.size() > 0) {
ImGui::Separator();
// Custom Mouth Textures
// Metadata
ImGui::BeginChild("###menu_custom_mouth_selector", ImVec2(-FLT_MIN, 75), true);
for (int n = 0; n < mouth_array.size(); n++) {
const bool is_mouth_selected = (current_mouth_index == n);
string entry_name = mouth_array[n];
ImGui::Text(current_model_data.name.c_str());
ImGui::Text(("By " + current_model_data.author).c_str());
ImGui::Text(("v" + current_model_data.version).c_str());
if (ImGui::Selectable(entry_name.c_str(), is_mouth_selected)) {
current_mouth_index = n;
saturn_mouth_selectable(entry_name, n);
// Expressions
for (int i; i < current_model_data.expressions.size(); i++) {
Expression expression = current_model_data.expressions[i];
if (expression.name == "eye" || expression.name == "eyes") {
if (scrollEyeState != 4) {
continue;
}
// Eyes
ImGui::BeginChild(("###menu_custom_%s", expression.name.c_str()), ImVec2(-FLT_MIN, 75), true);
for (int n = 0; n < expression.textures.size(); n++) {
string entry_name = expression.textures[n];
bool is_selected = (current_exp_index[i] == n);
if (ImGui::Selectable(entry_name.c_str(), is_selected)) {
current_exp_index[i] = n;
saturn_set_model_texture(i, expression.path + expression.textures[n]);
}
}
ImGui::EndChild();
} else {
// Everything but eyes
if (expression.textures.size() > 0) {
string label_name = "###menu_" + expression.name;
const char* preview_label_name = (expression.name + " >> " + expression.textures[current_exp_index[i]]).c_str();
if (ImGui::BeginCombo(label_name.c_str(), preview_label_name, ImGuiComboFlags_None)) {
for (int n = 0; n < expression.textures.size(); n++) {
bool is_selected = (current_exp_index[i] == n);
string entry_name = expression.textures[n];
if (ImGui::Selectable(entry_name.c_str(), is_selected)) {
current_exp_index[i] = n;
saturn_set_model_texture(i, expression.path + expression.textures[n]);
}
}
ImGui::EndCombo();
}
}
}
ImGui::EndChild();
}
}
ImGui::Separator();
ImGui::Checkbox("CC Compatibility", &cc_model_support);
ImGui::SameLine(); imgui_bundled_help_marker(
"Toggles color code compatibility for model packs that support it.");
ImGui::Checkbox("CometSPARK Support", &cc_spark_support);
ImGui::SameLine(); imgui_bundled_help_marker(
"Grants a model extra color values. See the GitHub wiki for setup instructions.");
// CC Compatibility - Allows colors to be edited for models that support it
if ((current_model_data.name != "" && current_model_data.cc_support == true) || current_model_data.name == "") {
ImGui::Separator();
ImGui::Checkbox("CC Compatibility", &cc_model_support);
ImGui::SameLine(); imgui_bundled_help_marker(
"Toggles color code compatibility for model packs that support it.");
}
// CometSPARK Support - When a model is present, allows SPARK colors to be edited for models that support it
if (any_packs_selected) {
if ((current_model_data.name != "" && current_model_data.spark_support == true) || current_model_data.name == "") {
ImGui::Checkbox("CometSPARK Support", &cc_spark_support);
ImGui::SameLine(); imgui_bundled_help_marker(
"Grants a model extra color values. See the GitHub wiki for setup instructions.");
}
}
ImGui::EndMenu();
}
@ -456,10 +500,10 @@ void sdynos_imgui_update() {
std::string cc_name = ui_cc_name;
// We don't want to save a CC named "Mario", as it may cause file issues.
if (cc_name != "Mario") {
save_cc_file(cc_name);
save_cc_file(cc_name, global_gs_code());
} else {
strcpy(ui_cc_name, "Sample");
save_cc_file("Sample");
save_cc_file("Sample", global_gs_code());
}
saturn_load_cc_directory();
}

View file

@ -7,6 +7,7 @@
#include <string>
extern void set_editor_from_global_cc(std::string);
extern int numColorCodes;
extern "C" {
#endif

View file

@ -624,13 +624,13 @@ void paste_gs_code(string content) {
/*
Saves the global_gs_code() to a GS file.
*/
void save_cc_file(std::string name) {
void save_cc_file(std::string name, std::string gameshark) {
#ifdef __MINGW32__
std::ofstream file("dynos\\colorcodes\\" + name + ".gs");
#else
std::ofstream file("dynos/colorcodes/" + name + ".gs");
#endif
file << global_gs_code();
file << gameshark;
}
/*

View file

@ -100,7 +100,7 @@ extern std::vector<std::string> cc_array;
extern std::string global_gs_code();
void paste_gs_code(std::string content);
void save_cc_file(std::string name);
void save_cc_file(std::string name, std::string gameshark);
void delete_cc_file(std::string name);
void set_cc_from_model(std::string modelPath);

View file

@ -6,6 +6,7 @@
#include <SDL2/SDL.h>
#include "saturn/saturn.h"
#include "saturn/saturn_colors.h"
#include "saturn/imgui/saturn_imgui.h"
#include "saturn/libs/imgui/imgui.h"
@ -16,6 +17,7 @@
#include "pc/configfile.h"
extern "C" {
#include "game/mario.h"
#include "game/camera.h"
#include "game/level_update.h"
#include "sm64.h"
@ -32,6 +34,8 @@ using namespace std;
namespace fs = std::filesystem;
#include "pc/fs/fs.h"
#include <json/json.h>
bool is_replacing_eyes;
std::vector<string> eye_array;
@ -48,7 +52,7 @@ string current_mouth_dir_path;
int current_mouth_index;
bool model_mouth_enabled;
// Eye Folders
// Eye Folders, Non-Model
void saturn_load_eye_folder(std::string path) {
eye_array.clear();
@ -86,52 +90,6 @@ void saturn_load_eye_folder(std::string path) {
saturn_set_eye_texture(0);
}
/*
Loads a model's eye directory, if it exists.
*/
void saturn_load_eyes_from_model(std::string path, std::string folder_name) {
eye_array.clear();
fs::create_directory("res/gfx");
// our model may not have a eye folder
if (!fs::is_directory("dynos/packs/" + folder_name + "/eyes/")) {
model_eyes_enabled = false;
saturn_load_eye_folder("../");
return;
}
// return to root if we haven't loaded our model eyes yet
if (path == "../") path = "";
if (!model_eyes_enabled || path == "") {
current_eye_dir_path = "dynos/packs/" + folder_name + "/eyes/";
model_eyes_enabled = true;
}
// only update current path if folder exists
if (fs::is_directory(current_eye_dir_path + path)) {
current_eye_dir_path = current_eye_dir_path + path;
}
current_eye_pre_path = "../../" + current_eye_dir_path;
if (current_eye_dir_path != "dynos/packs/" + folder_name + "/eyes/") {
eye_array.push_back("../");
}
for (const auto & entry : fs::directory_iterator(current_eye_dir_path)) {
if (fs::is_directory(entry.path())) {
//eye_array.push_back(entry.path().stem().u8string() + "/");
} else {
string entryPath = entry.path().filename().u8string();
if (entryPath.find(".png") != string::npos) // only allow png files
eye_array.push_back(entryPath);
}
}
if (eye_array.size() > 0)
saturn_set_eye_texture(0);
}
void saturn_eye_selectable(std::string name, int index) {
if (name.find(".png") != string::npos) {
// this is an eye
@ -146,60 +104,25 @@ std::string current_mouth_folder;
std::string last_folder_name;
/*
Loads a model's mouth directory, if it exists.
Sets an eye texture with an eye_array index.
*/
void saturn_load_mouths_from_model(std::string path, std::string folder_name) {
mouth_array.clear();
fs::create_directory("res/gfx");
// allows folder_name to be undefined
if (folder_name == "") { folder_name = last_folder_name; }
else { last_folder_name = folder_name; }
// our model may not have a mouth folder
model_mouth_enabled = fs::is_directory("dynos/packs/" + folder_name + "/mouths/");
if (!model_mouth_enabled) {
void saturn_set_eye_texture(int index) {
if (eye_array[index].find(".png") == string::npos) {
// keep trying till we get a non-folder
saturn_set_eye_texture(index + 1);
return;
}
// return to root
if (path == "../") path = "";
if (path == "dynos/packs/" || path == "") {
current_mouth_dir_path = "dynos/packs/" + folder_name + "/mouths/";
}
// only update current path if folder exists
if (fs::is_directory(current_mouth_dir_path + path)) {
current_mouth_dir_path = current_mouth_dir_path + path;
}
current_mouth_pre_path = "../../dynos/packs/" + folder_name + "/mouths/";
if (current_mouth_dir_path != "dynos/packs/" + folder_name + "/mouths/") {
mouth_array.push_back("../");
}
for (const auto & entry : fs::directory_iterator(current_mouth_dir_path)) {
if (fs::is_directory(entry.path())) {
//mouth_array.push_back(entry.path().stem().u8string() + "/");
} else {
string entryPath = entry.path().filename().u8string();
if (entryPath.find(".png") != string::npos) // only allow png files
mouth_array.push_back(entryPath);
}
}
}
void saturn_mouth_selectable(std::string name, int index) {
if (name.find(".png") != string::npos) {
// this is a mouth
saturn_set_mouth_texture(index);
std::cout << current_mouth << std::endl;
} else {
saturn_load_mouths_from_model(name, ""); // not defined, we should have already picked a folder_name
current_eye_index = index;
current_eye = current_eye_pre_path + eye_array[index];
current_eye = current_eye.substr(0, current_eye.size() - 4);
}
}
// NEW SYSTEM, Model
string current_model_exp_tex[6];
bool using_model_eyes;
/*
Handles texture replacement. Called from gfx_pc.c
*/
@ -209,46 +132,174 @@ const void* saturn_bind_texture(const void* input) {
string texName = string(inputTexture);
if (current_model_data.name != "") {
for (int i = 0; i < current_model_data.expressions.size(); i++) {
// Could be either "saturn_eye" or "saturn_eyes", check for both
string pos_name1 = "saturn_" + current_model_data.expressions[i].name;
string pos_name2 = pos_name1.substr(0, pos_name1.size() - 1);
if (texName.find(pos_name1) != string::npos || texName.find(pos_name2) != string::npos) {
outputTexture = current_model_exp_tex[i].c_str();
//std::cout << current_model_exp_tex[i] << std::endl;
const void* output = static_cast<const void*>(outputTexture);
return output;
}
}
}
if (texName == "actors/mario/mario_eyes_left_unused.rgba16" || texName.find("saturn_eye") != string::npos) {
outputTexture = current_eye.c_str();
const void* output = static_cast<const void*>(outputTexture);
return output;
}
if (texName.find("saturn_mouth") != string::npos && model_mouth_enabled) {
outputTexture = current_mouth.c_str();
const void* output = static_cast<const void*>(outputTexture);
return output;
}
return input;
}
/*
Sets an eye texture with an eye_array index.
*/
void saturn_set_eye_texture(int index) {
if (eye_array[index].find(".png") == string::npos) {
// keep trying till we get a non-folder
saturn_set_eye_texture(index + 1);
return;
struct ModelData current_model_data;
void saturn_set_model_texture(int expIndex, string path) {
current_model_exp_tex[expIndex] = "../../" + path;
current_model_exp_tex[expIndex] = current_model_exp_tex[expIndex].substr(0, current_model_exp_tex[expIndex].size() - 4);
std::cout << current_model_exp_tex[expIndex] << std::endl;
}
void saturn_load_model_expression_entry(string folder_name, string expression_name) {
Expression ex_entry;
// Folder path, could be either something like "eye" OR "eyes"
string path = "";
string pos_path1 = "dynos/packs/" + folder_name + "/expressions/" + expression_name + "/";
string pos_path2 = "dynos/packs/" + folder_name + "/expressions/" + expression_name + "s/";
// Prefer "eye" over "eyes"
if (fs::is_directory(pos_path2)) { path = pos_path2; ex_entry.name = (expression_name + "s"); }
if (fs::is_directory(pos_path1)) { path = pos_path1; ex_entry.name = (expression_name + ""); }
// If both don't exist, cancel
if (path == "") { return; }
if (fs::is_empty(path)) { return; }
ex_entry.path = path;
// Load each .png in the path
for (const auto & entry : fs::directory_iterator(path)) {
if (fs::is_directory(entry.path())) {
// Ignore, this is a folder
} else {
string entryName = entry.path().filename().u8string();
if (entryName.find(".png") != string::npos) // Only allow .png files
ex_entry.textures.push_back(entryName);
}
}
current_eye_index = index;
current_eye = current_eye_pre_path + eye_array[index];
current_eye = current_eye.substr(0, current_eye.size() - 4);
if (ex_entry.textures.size() > 0)
current_model_data.expressions.push_back(ex_entry);
}
/*
Sets a mouth texture with a mouth_array index, if mouth support is enabled.
Loads a model.json from a given model (if it exists).
*/
void saturn_set_mouth_texture(int index) {
if (!model_mouth_enabled) return;
void saturn_load_model_json(std::string folder_name) {
// Reset current model data
ModelData blank;
current_model_data = blank;
using_model_eyes = false;
if (mouth_array[index].find(".png") == string::npos) {
// keep trying till we get a non-folder
saturn_set_mouth_texture(index + 1);
return;
// Load the json file
std::ifstream file("dynos/packs/" + folder_name + "/model.json");
if (!file.good()) { return; }
// Begin reading
Json::Value root;
file >> root;
current_model_data.name = root["name"].asString();
current_model_data.author = root["author"].asString();
current_model_data.version = root["version"].asString();
if (root.isMember("textures")) {
// Create res/gfx if it doesn't already exist
fs::create_directory("res/gfx");
// Texture entries : eyes, mouths
for(int i = 0; i < root["textures"].size(); i++) {
// Capped at 6
if (i > 6) break;
const char* index = std::to_string(i).c_str();
string expression_name = root["textures"][index].asString();
saturn_load_model_expression_entry(folder_name, expression_name);
// Choose first texture as default
current_model_exp_tex[i] = "../../" + current_model_data.expressions[i].path + current_model_data.expressions[i].textures[0];
current_model_exp_tex[i] = current_model_exp_tex[i].substr(0, current_model_exp_tex[i].size() - 4);
// Toggle model eyes
if (expression_name.find("eye") != string::npos) using_model_eyes = true;
}
}
}
void saturn_load_model_data(std::string folder_name) {
// Reset current model data
ModelData blank;
current_model_data = blank;
using_model_eyes = false;
// Load the json file
std::ifstream file("dynos/packs/" + folder_name + "/model.json");
if (file.good()) {
// Begin reading
Json::Value root;
file >> root;
current_model_data.name = root["name"].asString();
current_model_data.author = root["author"].asString();
current_model_data.version = root["version"].asString();
// CC support is enabled by default, SPARK is disabled
// This is just in case it wasn't defined in the model.json
current_model_data.cc_support = true;
current_model_data.spark_support = false;
if (root.isMember("cc_support")) {
current_model_data.cc_support = root["cc_support"].asBool();
cc_model_support = current_model_data.cc_support;
}
if (root.isMember("spark_support")) {
current_model_data.spark_support = root["spark_support"].asBool();
cc_spark_support = current_model_data.spark_support;
// If SPARK is enabled, enable CC support too (it needs it to work)
if (current_model_data.spark_support == true) {
current_model_data.cc_support = true;
cc_model_support = true;
}
}
}
string path = "dynos/packs/" + folder_name + "/expressions/";
if (!fs::is_directory(path)) return;
int i = 0;
for (const auto & entry : fs::directory_iterator(path)) {
if (fs::is_directory(entry.path())) {
string expression_name = entry.path().filename().u8string();
saturn_load_model_expression_entry(folder_name, expression_name);
// Choose first texture as default
current_model_exp_tex[i] = "../../" + current_model_data.expressions[i].path + current_model_data.expressions[i].textures[0];
current_model_exp_tex[i] = current_model_exp_tex[i].substr(0, current_model_exp_tex[i].size() - 4);
// Toggle model eyes
if (expression_name.find("eye") != string::npos) using_model_eyes = true;
i++;
} else {
// Ignore, these are files
}
}
current_mouth_index = index;
current_mouth = current_mouth_pre_path + mouth_array[index];
current_mouth = current_mouth.substr(0, current_mouth.size() - 4);
}

View file

@ -21,10 +21,29 @@ extern bool model_mouth_enabled;
void saturn_load_eye_folder(std::string);
void saturn_eye_selectable(std::string, int);
void saturn_load_eyes_from_model(std::string, std::string);
void saturn_load_mouths_from_model(std::string, std::string);
void saturn_mouth_selectable(std::string, int);
// New System
struct Expression {
std::string name;
std::string path;
std::vector<std::string> textures;
};
struct ModelData {
std::string name;
std::string author;
std::string version;
std::vector<Expression> expressions;
bool cc_support;
bool spark_support;
};
extern struct ModelData current_model_data;
extern bool using_model_eyes;
void saturn_load_model_json(std::string folder_name);
void saturn_load_model_data(std::string folder_name);
void saturn_set_model_texture(int expIndex, std::string path);
extern "C" {
#endif
@ -32,7 +51,6 @@ extern "C" {
const void* saturn_bind_texture(const void*);
void saturn_set_eye_texture(int);
void saturn_set_mouth_texture(int);
#ifdef __cplusplus
}
#endif