Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
247 changes: 194 additions & 53 deletions gui_client/GUIClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ Copyright Glare Technologies Limited 2024 -
#include <unistd.h>
#include <malloc.h>
#endif
#include <zstd.h>
#include <zstd.h>
#include <algorithm>
#include <map>
#include <set>


static const Colour4f DEFAULT_OUTLINE_COLOUR = Colour4f::fromHTMLHexString("0ff7fb"); // light blue
Expand All @@ -137,8 +140,137 @@ static const URLString DEFAULT_AVATAR_MODEL_URL = "xbot.bmesh"; // This file sho

static const float MIN_SPOTLIGHT_CONE_ANGLE = 0.087266f;

static std::vector<AvatarRef> test_avatars;
static std::vector<double> test_avatar_phases;
static std::vector<AvatarRef> test_avatars;
static std::vector<double> test_avatar_phases;


namespace
{
bool isSupportedTextObjectFontPath(const std::string& path)
{
return
hasExtension(path, "ttf") ||
hasExtension(path, "otf") ||
hasExtension(path, "fon") ||
hasExtension(path, "woff");
}


std::string getTextObjectFontNameForPath(const std::string& path)
{
const std::string filename = FileUtils::getFilename(path);
const std::string extension = getExtension(filename);
if(extension.empty())
return filename;

return filename.substr(0, filename.size() - extension.size() - 1);
}


bool text_font_paths_scanned = false;
std::map<std::string, std::string> text_font_name_to_path_map;
std::map<std::string, TextRendererFontFaceSizeSetRef> text_font_face_sets;
std::set<std::string> unavailable_text_font_names;


void scanTextObjectFontPathsIfNeeded(const std::string& base_dir_path)
{
if(text_font_paths_scanned)
return;

text_font_paths_scanned = true;
text_font_name_to_path_map.clear();

std::vector<std::string> possible_paths;

if(!base_dir_path.empty())
{
possible_paths.push_back(base_dir_path + "/data/resources/fonts");
possible_paths.push_back(base_dir_path + "/resources/fonts");
}

#if EMSCRIPTEN
possible_paths.push_back("/data/resources/fonts");
possible_paths.push_back("data/resources/fonts");
possible_paths.push_back("./data/resources/fonts");
#endif

possible_paths.push_back("./resources/fonts");
possible_paths.push_back("resources/fonts");
possible_paths.push_back("../resources/fonts");
possible_paths.push_back("../../resources/fonts");
possible_paths.push_back("C:/programming/substrata/resources/fonts");

for(size_t i=0; i<possible_paths.size(); ++i)
{
const std::string& fonts_dir = possible_paths[i];
if(!FileUtils::isDirectory(fonts_dir))
continue;

try
{
std::vector<std::string> files = FileUtils::getFilesInDirFullPaths(fonts_dir);
std::sort(files.begin(), files.end());

for(size_t z=0; z<files.size(); ++z)
{
if(!isSupportedTextObjectFontPath(files[z]))
continue;

const std::string font_name = getTextObjectFontNameForPath(files[z]);
if(!font_name.empty() && (text_font_name_to_path_map.find(font_name) == text_font_name_to_path_map.end()))
text_font_name_to_path_map[font_name] = files[z];
}

if(!text_font_name_to_path_map.empty())
break; // Match ObjectEditor behaviour: use the first directory that yields fonts.
}
catch(glare::Exception&)
{}
}
}


TextRendererFontFaceSizeSet* getTextFontFaceSetForObject(GUIClient& gui_client, const WorldObject& ob)
{
if(gui_client.gl_ui.isNull() || (gui_client.gl_ui->getFonts() == NULL))
return NULL;

if(ob.text_font.empty() || (ob.text_font == "Default"))
return gui_client.gl_ui->getFonts();

{
auto res = text_font_face_sets.find(ob.text_font);
if(res != text_font_face_sets.end())
return res->second.ptr();
}

if(unavailable_text_font_names.find(ob.text_font) != unavailable_text_font_names.end())
return gui_client.gl_ui->getFonts();

scanTextObjectFontPathsIfNeeded(gui_client.base_dir_path);

auto path_res = text_font_name_to_path_map.find(ob.text_font);
if(path_res == text_font_name_to_path_map.end())
{
unavailable_text_font_names.insert(ob.text_font);
return gui_client.gl_ui->getFonts();
}

try
{
TextRendererFontFaceSizeSetRef font_set = new TextRendererFontFaceSizeSet(gui_client.gl_ui->getFonts()->renderer, path_res->second);
text_font_face_sets[ob.text_font] = font_set;
return font_set.ptr();
}
catch(glare::Exception& e)
{
conPrint("Failed to load text font '" + ob.text_font + "' from '" + path_res->second + "': " + e.what());
unavailable_text_font_names.insert(ob.text_font);
return gui_client.gl_ui->getFonts();
}
}
}


GUIClient::GUIClient(const std::string& base_dir_path_, const std::string& appdata_path_, const ArgumentParser& args)
Expand Down Expand Up @@ -468,10 +600,14 @@ void GUIClient::afterGLInitInitialise(double device_pixel_ratio, Reference<OpenG
{
ZoneScoped; // Tracy profiler

opengl_engine = opengl_engine_;


this->only_load_most_important_obs = settings->getBoolValue(/*MainOptionsDialog::onlyLoadMostImportantObsKey=*/"only_load_most_important_obs", /*default value=*/onlyLoadMostImportantObjectsDefaultValue());
opengl_engine = opengl_engine_;

text_font_paths_scanned = false;
text_font_name_to_path_map.clear();
text_font_face_sets.clear();
unavailable_text_font_names.clear();

this->only_load_most_important_obs = settings->getBoolValue(/*MainOptionsDialog::onlyLoadMostImportantObsKey=*/"only_load_most_important_obs", /*default value=*/onlyLoadMostImportantObjectsDefaultValue());



Expand Down Expand Up @@ -2044,20 +2180,21 @@ static Colour4f computeSpotlightColour(const WorldObject& ob, float cone_start_a
}


void GUIClient::createGLAndPhysicsObsForText(const Matrix4f& ob_to_world_matrix, WorldObject* ob, bool use_materialise_effect, PhysicsObjectRef& physics_ob_out, GLObjectRef& opengl_ob_out)
{
ZoneScoped; // Tracy profiler

Rect2f rect_os;
OpenGLTextureRef atlas_texture;

const std::string use_text = ob->content.empty() ? " " : UTF8Utils::sanitiseUTF8String(ob->content);

const int font_size_px = 42;

std::vector<GLUIText::CharPositionInfo> char_positions_font_coords;
Reference<OpenGLMeshRenderData> meshdata = GLUIText::makeMeshDataForText(opengl_engine.ptr(), gl_ui->font_char_text_cache.ptr(), gl_ui->getFonts(), gl_ui->getEmojiFonts(), use_text,
/*font size px=*/font_size_px, /*vert_pos_scale=*/(1.f / font_size_px), /*render SDF=*/true, this->stack_allocator, rect_os, atlas_texture, char_positions_font_coords);
void GUIClient::createGLAndPhysicsObsForText(const Matrix4f& ob_to_world_matrix, WorldObject* ob, bool use_materialise_effect, PhysicsObjectRef& physics_ob_out, GLObjectRef& opengl_ob_out)
{
ZoneScoped; // Tracy profiler

Rect2f rect_os;
OpenGLTextureRef atlas_texture;

const std::string use_text = ob->content.empty() ? " " : UTF8Utils::sanitiseUTF8String(ob->content);

const int font_size_px = 42;
TextRendererFontFaceSizeSet* const text_font_set = getTextFontFaceSetForObject(*this, *ob);

std::vector<GLUIText::CharPositionInfo> char_positions_font_coords;
Reference<OpenGLMeshRenderData> meshdata = GLUIText::makeMeshDataForText(opengl_engine.ptr(), gl_ui->font_char_text_cache.ptr(), text_font_set, gl_ui->getEmojiFonts(), use_text,
/*font size px=*/font_size_px, /*vert_pos_scale=*/(1.f / font_size_px), /*render SDF=*/true, this->stack_allocator, rect_os, atlas_texture, char_positions_font_coords);

// We will make a physics object that has the same dimensions in object space as the text mesh vertices. This means we can use the same pos, rot and scale
// for the physics object as for the opengl object.
Expand Down Expand Up @@ -2316,17 +2453,18 @@ void GUIClient::loadModelForObject(WorldObject* ob, WorldStateLock& world_state_
physics_world->addObject(ob->physics_object);
}
}
else if(ob->object_type == WorldObject::ObjectType_Text)
{
if(ob->opengl_engine_ob.isNull())
{
assert(ob->physics_object.isNull());

BitUtils::zeroBit(ob->changed_flags, WorldObject::CONTENT_CHANGED);

recreateTextGraphicsAndPhysicsObs(ob);

loadScriptForObject(ob, world_state_lock); // Load any script for the object.
else if(ob->object_type == WorldObject::ObjectType_Text)
{
if(ob->opengl_engine_ob.isNull())
{
assert(ob->physics_object.isNull());

BitUtils::zeroBit(ob->changed_flags, WorldObject::CONTENT_CHANGED);
BitUtils::zeroBit(ob->changed_flags, WorldObject::TEXT_FONT_CHANGED);

recreateTextGraphicsAndPhysicsObs(ob);

loadScriptForObject(ob, world_state_lock); // Load any script for the object.
}
}
else if(ob->object_type == WorldObject::ObjectType_Portal)
Expand Down Expand Up @@ -6815,14 +6953,15 @@ void GUIClient::timerEvent(const MouseCursorState& mouse_cursor_state)
ui_interface->objectModelURLUpdated(*ob); // Update model URL in UI if we have selected the object.


if(ob->object_type == WorldObject::ObjectType_Text)
{
if(BitUtils::isBitSet(ob->changed_flags, WorldObject::CONTENT_CHANGED))
{
BitUtils::zeroBit(ob->changed_flags, WorldObject::CONTENT_CHANGED);
recreateTextGraphicsAndPhysicsObs(ob);
}
}
if(ob->object_type == WorldObject::ObjectType_Text)
{
if(BitUtils::isBitSet(ob->changed_flags, WorldObject::CONTENT_CHANGED) || BitUtils::isBitSet(ob->changed_flags, WorldObject::TEXT_FONT_CHANGED))
{
BitUtils::zeroBit(ob->changed_flags, WorldObject::CONTENT_CHANGED);
BitUtils::zeroBit(ob->changed_flags, WorldObject::TEXT_FONT_CHANGED);
recreateTextGraphicsAndPhysicsObs(ob);
}
}

loadAudioForObject(ob, /*loaded buffer=*/nullptr); // Check for re-loading audio if audio URL changed.

Expand Down Expand Up @@ -6905,11 +7044,12 @@ void GUIClient::timerEvent(const MouseCursorState& mouse_cursor_state)
{
// conPrint("GUICLIENT: timerEvent(): handling from_remote_content_dirty..");

if(ob->object_type == WorldObject::ObjectType_Text)
{
recreateTextGraphicsAndPhysicsObs(ob);
BitUtils::zeroBit(ob->changed_flags, WorldObject::CONTENT_CHANGED);
}
if(ob->object_type == WorldObject::ObjectType_Text)
{
recreateTextGraphicsAndPhysicsObs(ob);
BitUtils::zeroBit(ob->changed_flags, WorldObject::CONTENT_CHANGED);
BitUtils::zeroBit(ob->changed_flags, WorldObject::TEXT_FONT_CHANGED);
}
// TODO: handle non-text objects. Also move this code into some kind of objectChanged() function?

ob->from_remote_content_dirty = false;
Expand Down Expand Up @@ -12183,14 +12323,15 @@ void GUIClient::objectEdited()
selected_ob->opengl_engine_ob = opengl_ob;
}
}
else if(this->selected_ob->object_type == WorldObject::ObjectType_Text)
{
// Re-create opengl and physics objects
recreateTextGraphicsAndPhysicsObs(selected_ob.ptr());

BitUtils::zeroBit(selected_ob->changed_flags, WorldObject::CONTENT_CHANGED);

opengl_ob = selected_ob->opengl_engine_ob;//new_opengl_ob;
else if(this->selected_ob->object_type == WorldObject::ObjectType_Text)
{
// Re-create opengl and physics objects
recreateTextGraphicsAndPhysicsObs(selected_ob.ptr());

BitUtils::zeroBit(selected_ob->changed_flags, WorldObject::CONTENT_CHANGED);
BitUtils::zeroBit(selected_ob->changed_flags, WorldObject::TEXT_FONT_CHANGED);

opengl_ob = selected_ob->opengl_engine_ob;//new_opengl_ob;

opengl_engine->selectObject(/*new_opengl_ob*/selected_ob->opengl_engine_ob);
}
Expand Down
29 changes: 16 additions & 13 deletions gui_client/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,16 +883,18 @@ void MainWindow::showEditorDockWidget()
}


void MainWindow::setObjectEditorControlsEditable(bool editable)
{
ui->objectEditor->setControlsEditable(editable);
}
void MainWindow::setObjectEditorControlsEditable(bool editable)
{
ui->objectEditor->setTextFontFeatureSupported(gui_client.server_protocol_version >= 50);
ui->objectEditor->setControlsEditable(editable);
}


void MainWindow::setObjectEditorFromOb(const WorldObject& ob, int selected_mat_index, bool ob_in_editing_users_world)
{
ui->objectEditor->setFromObject(ob, selected_mat_index, ob_in_editing_users_world);
}
void MainWindow::setObjectEditorFromOb(const WorldObject& ob, int selected_mat_index, bool ob_in_editing_users_world)
{
ui->objectEditor->setTextFontFeatureSupported(gui_client.server_protocol_version >= 50);
ui->objectEditor->setFromObject(ob, selected_mat_index, ob_in_editing_users_world);
}


int MainWindow::getSelectedMatIndex()
Expand Down Expand Up @@ -1673,11 +1675,12 @@ void MainWindow::on_actionAdd_Text_triggered()
new_world_object->uid = UID(0); // Will be set by server
new_world_object->object_type = WorldObject::ObjectType_Text;
new_world_object->pos = ob_pos;
new_world_object->axis = toVec3f(total_rot_axis);
new_world_object->angle = total_rot_angle;
new_world_object->scale = Vec3f(0.4f);
new_world_object->content = "Some Text";
new_world_object->setAABBOS(js::AABBox(Vec4f(0,0,0,1), Vec4f(1,0,1,1)));
new_world_object->axis = toVec3f(total_rot_axis);
new_world_object->angle = total_rot_angle;
new_world_object->scale = Vec3f(0.4f);
new_world_object->content = "Some Text";
new_world_object->text_font = "Default";
new_world_object->setAABBOS(js::AABBox(Vec4f(0,0,0,1), Vec4f(1,0,1,1)));

new_world_object->materials.resize(1);
new_world_object->materials[0] = new WorldMaterial();
Expand Down
Loading