Refactoring editing tools
This commit is contained in:
@@ -77,6 +77,7 @@ add_subdirectory(assets/blender/vehicles)
|
||||
add_subdirectory(assets/blender/buildings/parts)
|
||||
add_subdirectory(assets/blender/characters)
|
||||
add_subdirectory(resources)
|
||||
add_subdirectory(src/text_editor)
|
||||
|
||||
add_executable(Game Game.cpp ${WATER_SRC})
|
||||
target_include_directories(Game PRIVATE src/gamedata)
|
||||
|
||||
@@ -74,7 +74,7 @@ add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend
|
||||
${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
"${FEMALE_OBJECTS}"
|
||||
"female"
|
||||
@@ -85,7 +85,7 @@ add_custom_command(
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py
|
||||
${VRM_IMPORTED_BLENDS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
VERBATIM
|
||||
@@ -423,6 +423,7 @@ function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND)
|
||||
endfunction()
|
||||
|
||||
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-bottom.blend)
|
||||
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-bottom.blend)
|
||||
|
||||
add_clothes_pipeline(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend" # INPUT_BLEND
|
||||
@@ -430,3 +431,9 @@ add_clothes_pipeline(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend" # FINAL_OUTPUT_BLEND
|
||||
)
|
||||
|
||||
add_clothes_pipeline(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-female.blend" # INPUT_BLEND
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-female-bottom_weighted.blend" # WEIGHTED_BLEND
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend" # FINAL_OUTPUT_BLEND
|
||||
)
|
||||
|
||||
|
||||
BIN
assets/blender/characters/clothes-female-bottom.blend
LFS
Normal file
BIN
assets/blender/characters/clothes-female-bottom.blend
LFS
Normal file
Binary file not shown.
Binary file not shown.
@@ -35,7 +35,7 @@ def get_transformation_matrices(obj):
|
||||
|
||||
return m, m_inv, m_normal
|
||||
|
||||
def raycast_and_adjust_vertices(target_body, bvh_cloth):
|
||||
def raycast_and_adjust_vertices(target_body, bvh_cloth, out_ray_length=0.15):
|
||||
"""Raycast from body to cloth and adjust vertices that intersect"""
|
||||
m_body, m_body_inv, m_body_normal = get_transformation_matrices(target_body)
|
||||
|
||||
@@ -49,7 +49,7 @@ def raycast_and_adjust_vertices(target_body, bvh_cloth):
|
||||
n_world = (m_body_normal @ v.normal).normalized()
|
||||
|
||||
# Raycast forward (into cloth) and backward (from inside cloth)
|
||||
hit_f, _, _, _ = bvh_cloth.ray_cast(v_world, n_world, 0.015)
|
||||
hit_f, _, _, _ = bvh_cloth.ray_cast(v_world, n_world, out_ray_length)
|
||||
hit_b, _, _, _ = bvh_cloth.ray_cast(v_world, -n_world, 0.005)
|
||||
|
||||
if hit_f or hit_b:
|
||||
@@ -151,9 +151,19 @@ def process_clothing_pair(clothing_obj, target_obj, whitelist, is_clothing_copy=
|
||||
|
||||
print(f"Processing: {clothing_name_for_final} -> {target_name} (using copy)")
|
||||
|
||||
# NEW CODE: Store custom properties from clothing before they might be lost
|
||||
clothing_props = {}
|
||||
for prop_name in ["ref_shapes", "ref_ray_length"]:
|
||||
if prop_name in clothing_obj:
|
||||
clothing_props[prop_name] = clothing_obj[prop_name]
|
||||
print(f" Stored property '{prop_name}' = {clothing_obj[prop_name]} from clothing")
|
||||
|
||||
# Step A: Raycast & adjust vertices
|
||||
out_ray_length = 0.015
|
||||
if "ref_ray_length" in clothing_obj:
|
||||
out_ray_length = clothing_obj["ref_ray_length"]
|
||||
bvh_cloth = setup_bvh_and_matrices(clothing_obj)
|
||||
hit_values = raycast_and_adjust_vertices(new_target, bvh_cloth)
|
||||
hit_values = raycast_and_adjust_vertices(new_target, bvh_cloth, out_ray_length)
|
||||
|
||||
# Step B: Remove hidden geometry
|
||||
protect_and_remove_hidden_geometry(new_target, hit_values)
|
||||
@@ -188,6 +198,12 @@ def process_clothing_pair(clothing_obj, target_obj, whitelist, is_clothing_copy=
|
||||
bpy.context.view_layer.objects.active = new_target
|
||||
bpy.ops.object.join()
|
||||
|
||||
# NEW CODE: Copy stored custom properties to combined object
|
||||
# After joining, new_target is the active object and contains the combined mesh
|
||||
for prop_name, prop_value in clothing_props.items():
|
||||
new_target[prop_name] = prop_value
|
||||
print(f" Copied property '{prop_name}' = {prop_value} to combined object")
|
||||
|
||||
# Rename the combined object using the appropriate clothing name
|
||||
new_target.name = f"{target_name}_{clothing_name_for_final}"
|
||||
whitelist.add(new_target)
|
||||
|
||||
Binary file not shown.
@@ -29,35 +29,63 @@ def parse_args():
|
||||
print("Usage: blender -b --python transfer_shape_keys.py -- <source_file> <target_file> <output_file>")
|
||||
sys.exit(1)
|
||||
|
||||
def load_source_data(source_path):
|
||||
"""Load source file and extract shape key information"""
|
||||
def get_source_object_name(target_obj_info):
|
||||
"""Get source object name from target's custom property or fallback values"""
|
||||
# Check for custom property "ref_shapes" in the stored info
|
||||
if target_obj_info.get('ref_shapes'):
|
||||
ref_name = target_obj_info['ref_shapes']
|
||||
print(f" Using ref_shapes custom property: '{ref_name}'")
|
||||
return ref_name
|
||||
|
||||
# Fallback to "Body_shapes" then "Body"
|
||||
print(f" No ref_shapes property found, trying fallbacks")
|
||||
return None
|
||||
|
||||
def load_source_data(source_path, target_obj_info):
|
||||
"""Load source file and extract shape key information using object name from target info"""
|
||||
print(f"\nLoading source file: {source_path}")
|
||||
|
||||
if not os.path.exists(source_path):
|
||||
print(f"Error: Source file not found: {source_path}")
|
||||
sys.exit(1)
|
||||
|
||||
current_file = bpy.data.filepath if bpy.data.filepath else None
|
||||
# Get source object name based on target's custom property from stored info
|
||||
source_obj_name = get_source_object_name(target_obj_info)
|
||||
|
||||
# Open source file
|
||||
bpy.ops.wm.open_mainfile(filepath=source_path)
|
||||
|
||||
body_object = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Body_shapes" and obj.type == 'MESH':
|
||||
body_object = obj
|
||||
break
|
||||
|
||||
# If we have a specific name from ref_shapes, try that first
|
||||
if source_obj_name:
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == source_obj_name and obj.type == 'MESH':
|
||||
body_object = obj
|
||||
print(f" Found source object: '{source_obj_name}' from ref_shapes")
|
||||
break
|
||||
|
||||
# If not found or no ref_shapes, try fallbacks
|
||||
if not body_object:
|
||||
fallback_names = ["Body_shapes", "Body"]
|
||||
for fallback_name in fallback_names:
|
||||
print(f" Trying fallback: '{fallback_name}'")
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == fallback_name and obj.type == 'MESH':
|
||||
body_object = obj
|
||||
source_obj_name = fallback_name
|
||||
print(f" Found source object: '{fallback_name}'")
|
||||
break
|
||||
if body_object:
|
||||
break
|
||||
|
||||
if not body_object:
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name == "Body" and obj.type == 'MESH':
|
||||
body_object = obj
|
||||
break
|
||||
if not body_object:
|
||||
print("Error: Could not find mesh object named 'Body' in source file")
|
||||
print("Error: Could not find mesh object in source file")
|
||||
print("Tried: ref_shapes property (if present), 'Body_shapes', 'Body'")
|
||||
sys.exit(1)
|
||||
|
||||
if not body_object.data.shape_keys:
|
||||
print("Error: Body object has no shape keys")
|
||||
print(f"Error: Source object '{source_obj_name}' has no shape keys")
|
||||
sys.exit(1)
|
||||
|
||||
shape_key_data = {
|
||||
@@ -73,7 +101,7 @@ def load_source_data(source_path):
|
||||
shape_key_data['polygons'].append([v for v in poly.vertices])
|
||||
|
||||
source_shape_keys = body_object.data.shape_keys.key_blocks
|
||||
print(f"Found Body object with {len(source_shape_keys)} shape keys")
|
||||
print(f"Found source object '{source_obj_name}' with {len(source_shape_keys)} shape keys")
|
||||
|
||||
for sk in source_shape_keys:
|
||||
shape_key_data['names'].append(sk.name)
|
||||
@@ -85,15 +113,13 @@ def load_source_data(source_path):
|
||||
|
||||
shape_key_data['vertex_positions'][sk.name] = vertex_positions
|
||||
|
||||
if current_file and os.path.exists(current_file):
|
||||
bpy.ops.wm.open_mainfile(filepath=current_file)
|
||||
elif current_file:
|
||||
bpy.ops.wm.read_homefile()
|
||||
# Store the source object name that was actually used
|
||||
shape_key_data['source_obj_name'] = source_obj_name
|
||||
|
||||
return shape_key_data
|
||||
|
||||
def load_target_file(target_path):
|
||||
"""Load the target .blend file and find objects with custom properties"""
|
||||
"""Load the target .blend file and collect information about target objects"""
|
||||
print(f"\nLoading target file: {target_path}")
|
||||
|
||||
if not os.path.exists(target_path):
|
||||
@@ -103,23 +129,41 @@ def load_target_file(target_path):
|
||||
temp_dir = os.path.join(os.path.dirname(target_path), "temp_blend_files")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
|
||||
temp_target = os.path.join(temp_dir, os.path.basename(target_path))
|
||||
# Create a unique temp file path
|
||||
temp_target = os.path.join(temp_dir, f"temp_processing_{os.path.basename(target_path)}")
|
||||
shutil.copy2(target_path, temp_target)
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=temp_target)
|
||||
|
||||
target_objects = []
|
||||
# Store information about target objects without keeping references
|
||||
target_objects_info = []
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.data:
|
||||
if all(prop in obj for prop in ['age', 'sex', 'slot']):
|
||||
target_objects.append(obj)
|
||||
print(f"Found target object: {obj.name}")
|
||||
print(f" - age: {obj['age']}")
|
||||
print(f" - sex: {obj['sex']}")
|
||||
print(f" - slot: {obj['slot']}")
|
||||
print(f" - vertices: {len(obj.data.vertices)}")
|
||||
obj_info = {
|
||||
'name': obj.name,
|
||||
'age': obj['age'],
|
||||
'sex': obj['sex'],
|
||||
'slot': obj['slot'],
|
||||
'vertex_count': len(obj.data.vertices),
|
||||
'ref_shapes': obj.get('ref_shapes', None) # Store ref_shapes if present
|
||||
}
|
||||
target_objects_info.append(obj_info)
|
||||
print(f"Found target object: {obj_info['name']}")
|
||||
print(f" - age: {obj_info['age']}")
|
||||
print(f" - sex: {obj_info['sex']}")
|
||||
print(f" - slot: {obj_info['slot']}")
|
||||
print(f" - vertices: {obj_info['vertex_count']}")
|
||||
if obj_info['ref_shapes']:
|
||||
print(f" - ref_shapes: '{obj_info['ref_shapes']}'")
|
||||
|
||||
return target_objects, temp_target
|
||||
return target_objects_info, temp_target
|
||||
|
||||
def get_target_object_by_name(obj_name):
|
||||
"""Get a valid reference to a target object by name"""
|
||||
if obj_name in bpy.data.objects:
|
||||
return bpy.data.objects[obj_name]
|
||||
return None
|
||||
|
||||
def delete_existing_shape_keys(target_obj):
|
||||
"""Delete all existing shape keys from target object"""
|
||||
@@ -921,6 +965,31 @@ def save_output_file(output_path, temp_target):
|
||||
|
||||
print(f"File saved successfully")
|
||||
|
||||
def process_target_object(target_obj_info, source_data, current_file_path):
|
||||
"""Process a single target object using its stored information"""
|
||||
print(f"\nProcessing: {target_obj_info['name']}")
|
||||
print(f" {'=' * 40}")
|
||||
|
||||
# Get a fresh reference to the target object
|
||||
target_obj = get_target_object_by_name(target_obj_info['name'])
|
||||
if not target_obj:
|
||||
print(f" Error: Could not find target object '{target_obj_info['name']}'")
|
||||
return False
|
||||
|
||||
ensure_shape_keys_structure(source_data['names'], target_obj)
|
||||
mapping = transfer_shape_keys(source_data, target_obj)
|
||||
verify_transfer(source_data['names'], target_obj)
|
||||
|
||||
# Test quality of 'fat' shape key if it exists
|
||||
for sk_name in source_data['names']:
|
||||
if sk_name == 'fat':
|
||||
test_shape_key_quality(target_obj, sk_name, mapping, source_data)
|
||||
break
|
||||
|
||||
print(f" {'=' * 40}")
|
||||
print(f" ✓ Completed")
|
||||
return True
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Blender Shape Key Transfer Script - Boundary Velocity Limiting")
|
||||
@@ -932,29 +1001,60 @@ def main():
|
||||
print(f"Output: {output_file}")
|
||||
|
||||
try:
|
||||
source_data = load_source_data(source_file)
|
||||
target_objects, temp_target = load_target_file(target_file)
|
||||
# First, load target file and collect information about target objects
|
||||
target_objects_info, temp_target = load_target_file(target_file)
|
||||
|
||||
if target_objects:
|
||||
for idx, target_obj in enumerate(target_objects, 1):
|
||||
print(f"\n[{idx}/{len(target_objects)}] Processing: {target_obj.name}")
|
||||
print(f" {'=' * 40}")
|
||||
|
||||
ensure_shape_keys_structure(source_data['names'], target_obj)
|
||||
mapping = transfer_shape_keys(source_data, target_obj)
|
||||
verify_transfer(source_data['names'], target_obj)
|
||||
|
||||
for sk_name in source_data['names']:
|
||||
if sk_name == 'fat':
|
||||
test_shape_key_quality(target_obj, sk_name, mapping, source_data)
|
||||
break
|
||||
|
||||
print(f" {'=' * 40}")
|
||||
print(f" ✓ Completed")
|
||||
|
||||
save_output_file(output_file, temp_target)
|
||||
else:
|
||||
if not target_objects_info:
|
||||
print("\nNo target objects found with required properties")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nFound {len(target_objects_info)} target objects to process")
|
||||
|
||||
# Create a working file that we'll update after each object
|
||||
working_file = temp_target
|
||||
|
||||
# Process each target object
|
||||
for idx, target_obj_info in enumerate(target_objects_info, 1):
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"[{idx}/{len(target_objects_info)}] Processing: {target_obj_info['name']}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
# Load source data using the target object's information
|
||||
source_data = load_source_data(source_file, target_obj_info)
|
||||
|
||||
# Load the current working file
|
||||
bpy.ops.wm.open_mainfile(filepath=working_file)
|
||||
|
||||
# Process the target object
|
||||
success = process_target_object(target_obj_info, source_data, source_file)
|
||||
|
||||
if not success:
|
||||
print(f" Failed to process {target_obj_info['name']}")
|
||||
continue
|
||||
|
||||
# Save progress to a new temp file
|
||||
temp_progress_file = os.path.join(os.path.dirname(working_file), f"progress_{idx}_{os.path.basename(target_file)}")
|
||||
bpy.ops.wm.save_as_mainfile(filepath=temp_progress_file)
|
||||
|
||||
# Update working file for next iteration
|
||||
working_file = temp_progress_file
|
||||
|
||||
print(f"\nProgress saved to: {temp_progress_file}")
|
||||
|
||||
# Copy the final working file to the output location
|
||||
print(f"\nCopying final result to: {output_file}")
|
||||
shutil.copy2(working_file, output_file)
|
||||
|
||||
# Clean up temp files
|
||||
try:
|
||||
temp_dir = os.path.dirname(temp_target)
|
||||
for file in os.listdir(temp_dir):
|
||||
if file.startswith("temp_") or file.startswith("progress_"):
|
||||
os.remove(os.path.join(temp_dir, file))
|
||||
if os.path.exists(temp_dir) and not os.listdir(temp_dir):
|
||||
os.rmdir(temp_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Script completed successfully!")
|
||||
@@ -968,3 +1068,4 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp Sun
|
||||
VehicleManagerModule.cpp AppModule.cpp StaticGeometryModule.cpp SmartObject.cpp SlotsModule.cpp QuestModule.cpp
|
||||
PlayerActionModule.cpp CharacterAIModule.cpp goap.cpp AnimationSystem.cpp)
|
||||
target_link_libraries(GameData PUBLIC
|
||||
items luamodule
|
||||
items luamodule text_editor
|
||||
flecs::flecs_static
|
||||
nlohmann_json::nlohmann_json
|
||||
OgreMain
|
||||
|
||||
@@ -1227,6 +1227,7 @@ struct EditorGUIListener : public Ogre::RenderTargetListener {
|
||||
if (changed)
|
||||
StaticGeometryModule::saveTemplates();
|
||||
}
|
||||
Items::itemsEditor();
|
||||
if (ImGui::SmallButton("Run script for all towns..."))
|
||||
Items::runScriptsForAllTowns();
|
||||
displayItems();
|
||||
@@ -1399,8 +1400,8 @@ EditorGUIModule::EditorGUIModule(flecs::world &ecs)
|
||||
.without<EditorGUIData>()
|
||||
.each([](const RenderWindow &window, const App &app, GUI &gui) {
|
||||
float vpScale = window.dpi / 96;
|
||||
if (vpScale < 1.0f)
|
||||
vpScale = 1.0f;
|
||||
if (vpScale < 1.0f)
|
||||
vpScale = 1.0f;
|
||||
Ogre::OverlayManager::getSingleton().setPixelRatio(
|
||||
vpScale);
|
||||
std::cout << "Editor GUI configure\n";
|
||||
|
||||
@@ -13,6 +13,6 @@ target_link_libraries(items PRIVATE
|
||||
OgreMain
|
||||
OgreBites
|
||||
editor
|
||||
physics
|
||||
physics text_editor
|
||||
Tracy::TracyClient
|
||||
)
|
||||
|
||||
@@ -208,7 +208,855 @@ void createItemsMenu()
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
static void _setCameraSelectedPos(flecs::entity e)
|
||||
{
|
||||
Ogre::Vector3 cursorPos;
|
||||
Ogre::Quaternion cursorOrientation;
|
||||
// setCursorSelectedPos(e, cursorPos, cursorOrientation);
|
||||
StaticGeometryModule::getItemPositionAndRotation(e, cursorPos,
|
||||
cursorOrientation);
|
||||
ECS::get<EditorGizmo>().sceneNode->_setDerivedPosition(cursorPos);
|
||||
ECS::get<EditorGizmo>().sceneNode->_setDerivedOrientation(
|
||||
cursorOrientation);
|
||||
Ogre::Vector3 cameraPos =
|
||||
ECS::get<Camera>().mCameraPivot->_getDerivedPosition();
|
||||
Ogre::Vector3 cameraOffset =
|
||||
cursorOrientation * Ogre::Vector3::UNIT_Z * 30.0f;
|
||||
cameraPos.x = cursorPos.x;
|
||||
cameraPos.z = cursorPos.z;
|
||||
cameraPos += cameraOffset;
|
||||
cameraPos.y = TerrainModule::get_height(
|
||||
ECS::get<Terrain>().mTerrainGroup, cameraPos) +
|
||||
10.0f;
|
||||
Ogre::TerrainGroup *tg = ECS::get<Terrain>().mTerrainGroup;
|
||||
long x, y;
|
||||
tg->convertWorldPositionToTerrainSlot(cameraPos, &x, &y);
|
||||
if (tg->getTerrain(x, y) && tg->getTerrain(x, y)->isLoaded()) {
|
||||
float height = tg->getHeightAtWorldPosition(cameraPos);
|
||||
cameraPos.y = height + 10.0f;
|
||||
}
|
||||
ECS::get<Camera>().mCameraPivot->_setDerivedPosition(cameraPos);
|
||||
ECS::get<Camera>().mCameraPivot->_setDerivedOrientation(
|
||||
cursorOrientation);
|
||||
cameraPos = ECS::get<Camera>().mCameraGoal->_getDerivedPosition();
|
||||
ECS::get<Camera>().mCameraNode->_setDerivedPosition(cameraPos);
|
||||
ECS::get<Camera>().mCameraNode->_setDerivedOrientation(
|
||||
ECS::get<Camera>().mCameraGoal->_getDerivedOrientation());
|
||||
// updateHeightmap();
|
||||
}
|
||||
void drawRectPreview(nlohmann::json::json_pointer ptr,
|
||||
const nlohmann::json &item)
|
||||
{
|
||||
// Reserve a square area spanning across the table
|
||||
ImVec2 canvas_p0 = ImGui::GetCursorScreenPos();
|
||||
ImVec2 canvas_sz = ImVec2(ImGui::GetContentRegionAvail().x, 200);
|
||||
ImVec2 canvas_p1 =
|
||||
ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);
|
||||
Ogre::String rectName = ptr.back();
|
||||
nlohmann::json::json_pointer tmpptr = ptr;
|
||||
tmpptr.pop_back();
|
||||
const nlohmann::json &colorRects = item[tmpptr];
|
||||
|
||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||
// Draw background
|
||||
draw_list->AddRectFilled(canvas_p0, canvas_p1,
|
||||
IM_COL32(50, 50, 50, 255));
|
||||
|
||||
for (auto &[name, data] : colorRects.items()) {
|
||||
std::cout << data.dump() << std::endl;
|
||||
// Map 0..1 to Screen Pixels
|
||||
ImVec2 p_min =
|
||||
ImVec2(canvas_p0.x + (float)data["left"] * canvas_sz.x,
|
||||
canvas_p0.y + (float)data["top"] * canvas_sz.y);
|
||||
ImVec2 p_max = ImVec2(
|
||||
canvas_p0.x + (float)data["right"] * canvas_sz.x,
|
||||
canvas_p0.y + (float)data["bottom"] * canvas_sz.y);
|
||||
|
||||
// Handle Color (assuming rgba are 0..255 or 0..1)
|
||||
ImVec4 col = ImVec4(data["color"]["r"], data["color"]["g"],
|
||||
data["color"]["b"], data["color"]["a"]);
|
||||
|
||||
// Draw the rect
|
||||
draw_list->AddRectFilled(p_min, p_max,
|
||||
ImGui::ColorConvertFloat4ToU32(col));
|
||||
|
||||
// Outline if selected
|
||||
if (rectName == name)
|
||||
draw_list->AddRect(p_min, p_max,
|
||||
IM_COL32(255, 255, 0, 255), 0.0f, 0,
|
||||
2.0f);
|
||||
}
|
||||
ImGui::Dummy(canvas_sz); // Advance cursor
|
||||
}
|
||||
#if 1
|
||||
bool DrawGridSelectorInTableCell(const char *id, ImVec2 &outValue,
|
||||
float cellSize = 20.0f)
|
||||
{
|
||||
bool changed = false;
|
||||
static ImVec2 hoveredCell(-1, -1);
|
||||
|
||||
// Push style to ensure consistent spacing
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
|
||||
ImDrawList *drawList = ImGui::GetWindowDrawList();
|
||||
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
for (int y = 0; y < 10; y++) {
|
||||
for (int x = 0; x < 10; x++) {
|
||||
ImVec2 cellMin = ImVec2(cursorPos.x + x * cellSize,
|
||||
cursorPos.y + y * cellSize);
|
||||
ImVec2 cellMax = ImVec2(cellMin.x + cellSize,
|
||||
cellMin.y + cellSize);
|
||||
|
||||
// Use a dummy for spacing and hit detection
|
||||
ImGui::PushID(y * 10 + x);
|
||||
ImGui::Dummy(ImVec2(cellSize, cellSize));
|
||||
|
||||
// Check for interaction
|
||||
if (ImGui::IsItemHovered()) {
|
||||
hoveredCell = ImVec2(x / 9.0f, y / 9.0f);
|
||||
|
||||
if (ImGui::IsMouseClicked(0)) {
|
||||
outValue = hoveredCell;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
// Draw cell
|
||||
bool isSelected = (outValue.x == x / 9.0f &&
|
||||
outValue.y == y / 9.0f);
|
||||
bool isHovered = (hoveredCell.x == x / 9.0f &&
|
||||
hoveredCell.y == y / 9.0f);
|
||||
|
||||
ImU32 color;
|
||||
if (isSelected)
|
||||
color = IM_COL32(100, 150, 255, 255);
|
||||
else if (isHovered)
|
||||
color = IM_COL32(80, 80, 120, 255);
|
||||
else
|
||||
color = IM_COL32(60, 60, 70, 255);
|
||||
|
||||
drawList->AddRectFilled(cellMin, cellMax, color);
|
||||
drawList->AddRect(cellMin, cellMax,
|
||||
IM_COL32(200, 200, 200, 255));
|
||||
|
||||
if (x < 9)
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Move cursor past the grid
|
||||
ImGui::Dummy(ImVec2(1, 1));
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
return changed;
|
||||
}
|
||||
enum EditType {
|
||||
EditNone = 0,
|
||||
EditString,
|
||||
EditMultilineString,
|
||||
EditColorRect,
|
||||
EditFloat,
|
||||
EditBool,
|
||||
EditInt,
|
||||
ViewJson,
|
||||
EditItemPosition,
|
||||
EditLuaScript,
|
||||
};
|
||||
struct PropertyEditor;
|
||||
// Property editor definition
|
||||
struct PropertyEditor {
|
||||
int editType;
|
||||
std::string name;
|
||||
std::function<bool(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j)>
|
||||
matches;
|
||||
std::function<void(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j)>
|
||||
render;
|
||||
std::function<void(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j)>
|
||||
apply;
|
||||
};
|
||||
struct DPropertyEditor : PropertyEditor {
|
||||
private:
|
||||
virtual bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) = 0;
|
||||
virtual void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) = 0;
|
||||
virtual void _apply(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
DPropertyEditor(int type, const std::string &name)
|
||||
{
|
||||
editType = type;
|
||||
this->name = name;
|
||||
matches = [this](const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) -> bool {
|
||||
return _matches(itemName, ptr, j);
|
||||
};
|
||||
render = [this](flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) { _render(entity, ptr, j); };
|
||||
apply = [this](flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) { _apply(entity, ptr, j); };
|
||||
}
|
||||
PropertyEditor getPropertyEditor()
|
||||
{
|
||||
return { editType, name, matches, render, apply };
|
||||
}
|
||||
virtual ~DPropertyEditor()
|
||||
{
|
||||
}
|
||||
};
|
||||
struct StringPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return j[ptr].is_string();
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
static char buffer[260];
|
||||
strncpy(buffer, j[ptr].get<Ogre::String>().c_str(),
|
||||
sizeof(buffer));
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", ptr.back().c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::InputText("##value", buffer, sizeof(buffer));
|
||||
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
j[ptr] = buffer;
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
}
|
||||
}
|
||||
StringPropertyEditor()
|
||||
: DPropertyEditor(EditString, "String Editor")
|
||||
{
|
||||
}
|
||||
~StringPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
// EditLuaScript
|
||||
struct LuaScriptPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return itemName == "cellScript" && j[ptr].is_string();
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
static char buffer[16384];
|
||||
strncpy(buffer, j[ptr].get<Ogre::String>().c_str(),
|
||||
sizeof(buffer));
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", ptr.back().c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::InputTextMultiline("##value", buffer, sizeof(buffer),
|
||||
ImVec2(0, 0),
|
||||
ImGuiInputTextFlags_AllowTabInput);
|
||||
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
j[ptr] = buffer;
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
}
|
||||
}
|
||||
LuaScriptPropertyEditor()
|
||||
: DPropertyEditor(EditLuaScript, "Lua Script Editor")
|
||||
{
|
||||
}
|
||||
~LuaScriptPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
struct MultilineStringPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return j[ptr].is_string() &&
|
||||
(j[ptr].get<std::string>().find("\n") !=
|
||||
std::string::npos ||
|
||||
j[ptr].get<std::string>().length() >= 100);
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
static char buffer[4096];
|
||||
strncpy(buffer, j[ptr].get<Ogre::String>().c_str(),
|
||||
sizeof(buffer));
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", ptr.back().c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::InputTextMultiline("##value", buffer, sizeof(buffer));
|
||||
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
j[ptr] = buffer;
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
}
|
||||
}
|
||||
MultilineStringPropertyEditor()
|
||||
: DPropertyEditor(EditMultilineString,
|
||||
"Multiline String Editor")
|
||||
{
|
||||
}
|
||||
~MultilineStringPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
// Float editor
|
||||
struct FloatPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return j[ptr].is_number_float();
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", ptr.back().c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
float val = j[ptr].get<float>();
|
||||
if (ImGui::InputFloat("##value", &val, 0.001f, 0.01f)) {
|
||||
j[ptr] = val;
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
StaticGeometryModule::updateItemGeometry(entity);
|
||||
}
|
||||
}
|
||||
FloatPropertyEditor()
|
||||
: DPropertyEditor(EditFloat, "Float Editor")
|
||||
{
|
||||
}
|
||||
~FloatPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
// int editor
|
||||
struct IntPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return j[ptr].is_number_integer();
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", ptr.back().c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
int val = j[ptr].get<int>();
|
||||
if (ImGui::InputInt("##value", &val, 1, 10)) {
|
||||
j[ptr] = val;
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
StaticGeometryModule::updateItemGeometry(entity);
|
||||
}
|
||||
}
|
||||
IntPropertyEditor()
|
||||
: DPropertyEditor(EditInt, "Int Editor")
|
||||
{
|
||||
}
|
||||
~IntPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
// bool editor
|
||||
struct BoolPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return j[ptr].is_boolean();
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", ptr.back().c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
bool val = j[ptr].get<bool>();
|
||||
if (ImGui::Checkbox("##value", &val)) {
|
||||
j[ptr] = val;
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
StaticGeometryModule::updateItemGeometry(entity);
|
||||
}
|
||||
}
|
||||
BoolPropertyEditor()
|
||||
: DPropertyEditor(EditBool, "Bool Editor")
|
||||
{
|
||||
}
|
||||
~BoolPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
// color rect editor
|
||||
struct ColorRectPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
if (ptr.empty())
|
||||
return false;
|
||||
if (!ptr.parent_pointer().empty() &&
|
||||
ptr.parent_pointer().back() == "colorRects") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
bool updateMaterial = false;
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
drawRectPreview(ptr, j);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Bounds");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
nlohmann::json &d = j[ptr];
|
||||
nlohmann::json &c = j[ptr]["color"];
|
||||
|
||||
float bounds[2] = { d["left"], d["top"] };
|
||||
if (ImGui::InputFloat("Left", &bounds[0], 0.1f, 0.1f, "%.1f")) {
|
||||
d["left"] = std::clamp(std::round(bounds[0] * 10.0f) /
|
||||
10.0f,
|
||||
0.0f, 1.0f);
|
||||
d["right"] = std::clamp(
|
||||
std::round((bounds[0] + 0.1f) * 10.0f) / 10.0f,
|
||||
0.0f, 1.0f);
|
||||
updateMaterial = true;
|
||||
}
|
||||
|
||||
if (ImGui::InputFloat("Top", &bounds[1], 0.1f, 0.1f, "%.1f")) {
|
||||
d["top"] = std::clamp(std::round(bounds[1] * 10.0f) /
|
||||
10.0f,
|
||||
0.0f, 1.0f);
|
||||
d["bottom"] = std::clamp(
|
||||
std::round((bounds[1] + 0.1f) * 10.0f) / 10.0f,
|
||||
0.0f, 1.0f);
|
||||
updateMaterial = true;
|
||||
}
|
||||
ImVec2 value;
|
||||
value.x = bounds[0];
|
||||
value.y = bounds[1];
|
||||
if (DrawGridSelectorInTableCell("##selectRect", value)) {
|
||||
bounds[0] = value.x;
|
||||
bounds[1] = value.y;
|
||||
d["left"] = std::clamp(std::round(bounds[0] * 10.0f) /
|
||||
10.0f,
|
||||
0.0f, 1.0f);
|
||||
d["right"] = std::clamp(
|
||||
std::round((bounds[0] + 0.1f) * 10.0f) / 10.0f,
|
||||
0.0f, 1.0f);
|
||||
d["top"] = std::clamp(std::round(bounds[1] * 10.0f) /
|
||||
10.0f,
|
||||
0.0f, 1.0f);
|
||||
d["bottom"] = std::clamp(
|
||||
std::round((bounds[1] + 0.1f) * 10.0f) / 10.0f,
|
||||
0.0f, 1.0f);
|
||||
updateMaterial = true;
|
||||
}
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Color");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
float col[4] = { c["r"], c["g"], c["b"], c["a"] };
|
||||
if (ImGui::ColorEdit4("##color", col)) {
|
||||
c["r"] = col[0];
|
||||
c["g"] = col[1];
|
||||
c["b"] = col[2];
|
||||
c["a"] = col[3];
|
||||
updateMaterial = true;
|
||||
}
|
||||
if (updateMaterial) {
|
||||
StaticGeometryModule::setItemProperties(entity,
|
||||
j.dump());
|
||||
Geometry::createTownMaterial(entity, true);
|
||||
}
|
||||
}
|
||||
ColorRectPropertyEditor()
|
||||
: DPropertyEditor(EditColorRect, "Color Rect Editor")
|
||||
{
|
||||
}
|
||||
~ColorRectPropertyEditor() override
|
||||
{
|
||||
}
|
||||
};
|
||||
template <typename T> struct registerPropertyEditor {
|
||||
registerPropertyEditor(std::vector<PropertyEditor> ®)
|
||||
{
|
||||
static T regdata;
|
||||
reg.push_back(regdata);
|
||||
}
|
||||
};
|
||||
static std::vector<PropertyEditor> propertyEditors;
|
||||
// ViewJson editor (fallback)
|
||||
struct FallbackPropertyEditor : DPropertyEditor {
|
||||
bool _matches(const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void _render(flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) override
|
||||
{
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextWrapped("%s", j[ptr].dump(4).c_str());
|
||||
}
|
||||
FallbackPropertyEditor()
|
||||
: DPropertyEditor(ViewJson, "JSON Viewer")
|
||||
{
|
||||
}
|
||||
};
|
||||
static registerPropertyEditor<StringPropertyEditor>
|
||||
stringPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<LuaScriptPropertyEditor>
|
||||
luaScriptPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<MultilineStringPropertyEditor>
|
||||
multilineStringPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<FloatPropertyEditor>
|
||||
floatPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<IntPropertyEditor>
|
||||
intPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<BoolPropertyEditor>
|
||||
boolPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<ColorRectPropertyEditor>
|
||||
colorRectPropertyEditor(propertyEditors);
|
||||
static registerPropertyEditor<FallbackPropertyEditor>
|
||||
fallbackPropertyEditor(propertyEditors);
|
||||
void itemsEditor()
|
||||
{
|
||||
struct EditorState {
|
||||
flecs::entity edited;
|
||||
nlohmann::json::json_pointer edited_ptr;
|
||||
int edited_type;
|
||||
EditorState()
|
||||
: edited_type(EditNone)
|
||||
{
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
edited = flecs::entity();
|
||||
edited_ptr = nlohmann::json::json_pointer();
|
||||
edited_type = EditNone;
|
||||
}
|
||||
};
|
||||
static EditorState state;
|
||||
// Helper to find matching editor
|
||||
auto findEditor = [](int editType, const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) -> PropertyEditor * {
|
||||
for (auto &editor : propertyEditors) {
|
||||
if (editor.editType == editType &&
|
||||
editor.matches(itemName, ptr, j)) {
|
||||
return &editor;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
// Leaf node detection - keeping your working logic
|
||||
auto is_leaf = [](const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr) -> bool {
|
||||
// Direct key matches
|
||||
if (itemName == "name" || itemName == "position" ||
|
||||
itemName == "orientation" || itemName == "rotation" ||
|
||||
itemName == "cells" || itemName == "furniture_cells" ||
|
||||
itemName == "roofs" || itemName == "pierPath" ||
|
||||
itemName == "cellScript") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parent-based matches
|
||||
if (!ptr.empty() && !ptr.parent_pointer().empty()) {
|
||||
std::string parent = ptr.parent_pointer().back();
|
||||
if (parent == "colorRects" || parent == "npcs" ||
|
||||
parent == "centerBuildings") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
// Edit type determination
|
||||
auto set_edit_type = [&findEditor](
|
||||
const std::string &itemName,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
const nlohmann::json &j) {
|
||||
// Try to find by specific rules first
|
||||
if (itemName == "name") {
|
||||
state.edited_type = EditString;
|
||||
} else if (itemName == "position" ||
|
||||
itemName == "orientation" ||
|
||||
itemName == "rotation" || itemName == "cells" ||
|
||||
itemName == "furniture_cells" ||
|
||||
itemName == "roofs" || itemName == "pierPath") {
|
||||
state.edited_type = ViewJson;
|
||||
} else if (itemName == "cellScript") {
|
||||
state.edited_type = EditMultilineString;
|
||||
} else if (!ptr.empty() && !ptr.parent_pointer().empty()) {
|
||||
std::string parent = ptr.parent_pointer().back();
|
||||
if (parent == "colorRects") {
|
||||
state.edited_type = EditColorRect;
|
||||
} else if (parent == "npcs" ||
|
||||
parent == "centerBuildings") {
|
||||
state.edited_type = ViewJson;
|
||||
} else {
|
||||
// Fall back to type-based detection
|
||||
if (j[ptr].is_number_float())
|
||||
state.edited_type = EditFloat;
|
||||
else if (j[ptr].is_number_integer())
|
||||
state.edited_type = EditInt;
|
||||
else if (j[ptr].is_boolean())
|
||||
state.edited_type = EditBool;
|
||||
else if (j[ptr].is_string())
|
||||
state.edited_type = EditString;
|
||||
else
|
||||
state.edited_type = ViewJson;
|
||||
}
|
||||
} else {
|
||||
// Fall back to type-based detection
|
||||
if (j[ptr].is_number_float())
|
||||
state.edited_type = EditFloat;
|
||||
else if (j[ptr].is_number_integer())
|
||||
state.edited_type = EditInt;
|
||||
else if (j[ptr].is_boolean())
|
||||
state.edited_type = EditBool;
|
||||
else if (j[ptr].is_string())
|
||||
state.edited_type = EditString;
|
||||
else
|
||||
state.edited_type = ViewJson;
|
||||
}
|
||||
};
|
||||
// Recursive hierarchy renderer
|
||||
std::function<void(
|
||||
const Ogre::String &, flecs::entity, ImGuiTreeNodeFlags,
|
||||
const nlohmann::json::json_pointer &, nlohmann::json &)>
|
||||
renderHierarchy;
|
||||
|
||||
renderHierarchy = [&renderHierarchy, &is_leaf, &set_edit_type](
|
||||
const Ogre::String &label, flecs::entity item,
|
||||
ImGuiTreeNodeFlags flags,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
nlohmann::json &j) {
|
||||
// Generate a unique ID for this node
|
||||
ImGui::PushID(
|
||||
static_cast<int>(reinterpret_cast<intptr_t>(&j[ptr])));
|
||||
|
||||
// Set flags
|
||||
ImGuiTreeNodeFlags nodeFlags = flags;
|
||||
if (state.edited == item && state.edited_ptr == ptr)
|
||||
nodeFlags |= ImGuiTreeNodeFlags_Selected;
|
||||
|
||||
// Render the node
|
||||
std::string displayLabel = label;
|
||||
if (!ptr.empty() && j[ptr].is_string() &&
|
||||
ptr.back() == "name") {
|
||||
// Optionally show value for name fields
|
||||
displayLabel += ": " + j[ptr].get<std::string>();
|
||||
}
|
||||
|
||||
bool nodeOpen = ImGui::TreeNodeEx((void *)&j[ptr], nodeFlags,
|
||||
"%s", displayLabel.c_str());
|
||||
|
||||
// Handle click
|
||||
if (ImGui::IsItemClicked()) {
|
||||
state.edited = item;
|
||||
state.edited_ptr = ptr;
|
||||
std::string itemName = ptr.empty() ? "" : ptr.back();
|
||||
set_edit_type(itemName, ptr, j);
|
||||
std::cout << ptr << std::endl;
|
||||
}
|
||||
|
||||
// Render children
|
||||
if (nodeOpen) {
|
||||
std::string itemName = ptr.empty() ? "" : ptr.back();
|
||||
bool leaf = is_leaf(itemName, ptr);
|
||||
|
||||
if (!leaf) {
|
||||
if (j[ptr].is_array()) {
|
||||
for (size_t i = 0; i < j[ptr].size();
|
||||
++i) {
|
||||
auto nptr = ptr / i;
|
||||
renderHierarchy(
|
||||
std::to_string(i), item,
|
||||
flags, nptr, j);
|
||||
}
|
||||
} else if (j[ptr].is_object()) {
|
||||
nlohmann::json &obj = j[ptr];
|
||||
for (auto it = obj.begin();
|
||||
it != obj.end(); ++it) {
|
||||
auto nptr = ptr / it.key();
|
||||
renderHierarchy(it.key(), item,
|
||||
flags, nptr, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
};
|
||||
// Property editor rendering using registry
|
||||
auto renderPropertyEditor =
|
||||
[&findEditor](flecs::entity entity,
|
||||
const nlohmann::json::json_pointer &ptr,
|
||||
int editType) {
|
||||
Ogre::String jsonStr =
|
||||
StaticGeometryModule::getItemProperties(entity);
|
||||
nlohmann::json j = nlohmann::json::parse(jsonStr);
|
||||
|
||||
std::string itemName = ptr.empty() ? "" : ptr.back();
|
||||
PropertyEditor *editor =
|
||||
findEditor(editType, itemName, ptr, j);
|
||||
|
||||
if (editor) {
|
||||
editor->render(entity, ptr, j);
|
||||
} else {
|
||||
// Fallback
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextWrapped("%s",
|
||||
j[ptr].dump(4).c_str());
|
||||
}
|
||||
};
|
||||
if (ImGui::SmallButton("Edit items..."))
|
||||
ImGui::OpenPopup("Edit Items Popup");
|
||||
if (ImGui::BeginPopupModal("Edit Items Popup", NULL,
|
||||
ImGuiWindowFlags_MenuBar)) {
|
||||
// Optional: Add a menu bar for "Add District/Lot" actions
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
if (ImGui::BeginMenu("File")) { /* ... */
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
// --- LEFT SIDE: HIERARCHY ---
|
||||
ImGui::BeginChild("HierarchyRegion", ImVec2(300, 400), true);
|
||||
std::list<std::pair<flecs::entity, Ogre::String> > items;
|
||||
StaticGeometryModule::getItemsProperties(&items);
|
||||
ImGuiTreeNodeFlags baseFlags =
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_DefaultOpen;
|
||||
for (const auto &itemPair : items) {
|
||||
flecs::entity entity = itemPair.first;
|
||||
const Ogre::String &jsonStr = itemPair.second;
|
||||
|
||||
ImGui::PushID(static_cast<int>(entity.id()));
|
||||
|
||||
nlohmann::json j = nlohmann::json::parse(jsonStr);
|
||||
|
||||
// Create label
|
||||
Ogre::String label =
|
||||
Ogre::StringConverter::toString(entity.id());
|
||||
label += ":" + j["type"].get<Ogre::String>();
|
||||
if (j.find("name") != j.end() &&
|
||||
!j["name"].get<Ogre::String>().empty())
|
||||
label = j["name"].get<Ogre::String>();
|
||||
|
||||
// Render hierarchy for this item
|
||||
renderHierarchy(label, entity, baseFlags,
|
||||
nlohmann::json::json_pointer(), j);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// --- RIGHT SIDE: PROPERTIES ---
|
||||
ImGui::BeginChild("PropertiesRegion", ImVec2(0, 400), true);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,
|
||||
ImVec2(10.0f, 4.0f));
|
||||
if (ImGui::BeginTable("PropTable", 2,
|
||||
ImGuiTableFlags_SizingStretchProp |
|
||||
ImGuiTableFlags_Resizable)) {
|
||||
ImGui::TableSetupColumn(
|
||||
"Property", ImGuiTableColumnFlags_WidthFixed,
|
||||
0.0f);
|
||||
ImGui::TableSetupColumn(
|
||||
"Value", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableHeadersRow();
|
||||
ImGui::TableNextRow();
|
||||
if (state.edited.is_valid() &&
|
||||
!state.edited_ptr.empty()) {
|
||||
renderPropertyEditor(state.edited,
|
||||
state.edited_ptr,
|
||||
state.edited_type);
|
||||
} else if (state.edited.is_valid()) {
|
||||
// Show item summary when no specific property is selected
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Item");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
Ogre::String jsonStr =
|
||||
StaticGeometryModule::getItemProperties(
|
||||
state.edited);
|
||||
nlohmann::json j =
|
||||
nlohmann::json::parse(jsonStr);
|
||||
ImGui::TextWrapped("%s", j.dump(4).c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
// DrawPropertyInspector(); // Uses the Table logic from before
|
||||
ImGui::EndChild();
|
||||
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
state.clear();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
namespace Geometry
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ void showItemPopup(const std::pair<flecs::entity, Ogre::String> &item);
|
||||
void showItemButtons(const std::pair<flecs::entity, Ogre::String> &item);
|
||||
void createItemsMenu();
|
||||
void runScriptsForAllTowns();
|
||||
void itemsEditor();
|
||||
}
|
||||
namespace Geometry
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user