From cb2ce2300986cef9152448ac09a5272192bd02c3 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Wed, 25 Feb 2026 14:51:26 +0300 Subject: [PATCH] Removed mBodyNode from CharacterBase --- assets/blender/characters/CMakeLists.txt | 24 +- .../characters/clothes-male-bottom.blend | 4 +- .../characters/edited-normal-male.blend | 4 +- .../blender/characters/transfer_shape_keys.py | 970 ++++++++++++++++++ src/gamedata/BoatModule.cpp | 35 +- src/gamedata/CharacterAIModule.cpp | 36 +- src/gamedata/CharacterAnimationModule.cpp | 20 +- src/gamedata/CharacterManagerModule.cpp | 11 +- src/gamedata/CharacterModule.cpp | 20 +- src/gamedata/CharacterModule.h | 1 - src/gamedata/LuaModule/LuaData.cpp | 730 ++++++------- src/gamedata/PhysicsModule.cpp | 91 +- src/gamedata/PlayerActionModule.cpp | 17 +- src/gamedata/SlotsModule.cpp | 5 +- 14 files changed, 1497 insertions(+), 471 deletions(-) create mode 100644 assets/blender/characters/transfer_shape_keys.py diff --git a/assets/blender/characters/CMakeLists.txt b/assets/blender/characters/CMakeLists.txt index 17e0d74..b05084f 100644 --- a/assets/blender/characters/CMakeLists.txt +++ b/assets/blender/characters/CMakeLists.txt @@ -229,7 +229,17 @@ function(weight_clothes SRC) ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed ) endfunction() -weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-bottom.blend) +function(transfer_shape_keys SRC CLOTH DST) + add_custom_command( + OUTPUT ${DST} + COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py -- ${SRC} ${CLOTH} ${DST} + COMMAND ${CMAKE_COMMAND} -D FILE=${DST} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake + DEPENDS ${SRC} ${CLOTH} + ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed + ${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py + ) +endfunction() # Function to combine clothes into a blend file # Parameters: @@ -383,15 +393,21 @@ function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND) else() set(COMBINED_BLEND "${PIPELINE_INTERMEDIATE_DIR}/${TARGET_BASE}_combined.blend") endif() + set(SHAPED_BLEND "${PIPELINE_INTERMEDIATE_DIR}/${TARGET_BASE}_shaped.blend") + # Step 1: Combine clothes add_clothes_combination( "${INPUT_BLEND}" - "${WEIGHTED_BLEND}" + "${WEIGHTED_BLEND}" "${COMBINED_BLEND}" OUTPUT_DIR "${PIPELINE_INTERMEDIATE_DIR}" ) + transfer_shape_keys(${INPUT_BLEND} + ${COMBINED_BLEND} + ${SHAPED_BLEND} + ) # Step 2: Consolidate add_blend_consolidation( "${INPUT_BLEND}" @@ -401,11 +417,13 @@ function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND) # Create a custom target to drive the whole pipeline add_custom_target(${TARGET_BASE}_pipeline ALL - DEPENDS ${FINAL_OUTPUT_BLEND} + DEPENDS ${FINAL_OUTPUT_BLEND} ${SHAPED_BLEND} COMMENT "Running complete clothes pipeline for ${TARGET_BASE}" ) endfunction() +weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-bottom.blend) + add_clothes_pipeline( "${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend" # INPUT_BLEND "${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-male-bottom_weighted.blend" # WEIGHTED_BLEND diff --git a/assets/blender/characters/clothes-male-bottom.blend b/assets/blender/characters/clothes-male-bottom.blend index 97853a8..30f8cfa 100644 --- a/assets/blender/characters/clothes-male-bottom.blend +++ b/assets/blender/characters/clothes-male-bottom.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:250660db39e86488c68ced32623d8c0ac058a678d2d8d806edb67fd8d7f67e78 -size 1299736 +oid sha256:bd5e3372ee3c2c66f392d1b524a846cf71e5c5a4e50d0e18e573ee06a27f2baa +size 1285768 diff --git a/assets/blender/characters/edited-normal-male.blend b/assets/blender/characters/edited-normal-male.blend index beece4e..435079d 100644 --- a/assets/blender/characters/edited-normal-male.blend +++ b/assets/blender/characters/edited-normal-male.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bdc009dba953dca582555c93115b72b2beec8200ae5d32f19dbdc928c9c0082 -size 14333594 +oid sha256:406b5f878469fe8cb66d50825c7d3c0d1a6561b976bc5a6da6f99c64b2709768 +size 14736682 diff --git a/assets/blender/characters/transfer_shape_keys.py b/assets/blender/characters/transfer_shape_keys.py new file mode 100644 index 0000000..b8f7795 --- /dev/null +++ b/assets/blender/characters/transfer_shape_keys.py @@ -0,0 +1,970 @@ +""" +Blender 3.6.20 Script: Transfer Shape Keys with Boundary Velocity Limiting +Usage: blender -b --python transfer_shape_keys.py -- +""" + +import bpy +import sys +import os +import shutil +import mathutils +from mathutils.bvhtree import BVHTree +import numpy as np +from collections import defaultdict + +def parse_args(): + """Parse command line arguments after '--'""" + if '--' in sys.argv: + args_start = sys.argv.index('--') + 1 + args = sys.argv[args_start:] + + if len(args) >= 3: + return args[0], args[1], args[2] + else: + print("Error: Please provide source, target, and output .blend files") + print("Usage: blender -b --python transfer_shape_keys.py -- ") + sys.exit(1) + else: + print("Error: No arguments provided") + print("Usage: blender -b --python transfer_shape_keys.py -- ") + sys.exit(1) + +def load_source_data(source_path): + """Load source file and extract shape key information""" + 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 + + 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 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") + sys.exit(1) + + if not body_object.data.shape_keys: + print("Error: Body object has no shape keys") + sys.exit(1) + + shape_key_data = { + 'names': [], + 'vertex_positions': {}, + 'polygons': [], + 'is_relative': body_object.data.shape_keys.use_relative + } + + # Store polygon data for BVH + mesh = body_object.data + for poly in mesh.polygons: + 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") + + for sk in source_shape_keys: + shape_key_data['names'].append(sk.name) + print(f" - {sk.name}") + + vertex_positions = [] + for v in sk.data: + vertex_positions.append((v.co.x, v.co.y, v.co.z)) + + 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() + + return shape_key_data + +def load_target_file(target_path): + """Load the target .blend file and find objects with custom properties""" + print(f"\nLoading target file: {target_path}") + + if not os.path.exists(target_path): + print(f"Error: Target file not found: {target_path}") + sys.exit(1) + + 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)) + shutil.copy2(target_path, temp_target) + + bpy.ops.wm.open_mainfile(filepath=temp_target) + + target_objects = [] + 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)}") + + return target_objects, temp_target + +def delete_existing_shape_keys(target_obj): + """Delete all existing shape keys from target object""" + if not target_obj.data.shape_keys: + return False + + num_keys = len(target_obj.data.shape_keys.key_blocks) + print(f" Deleting {num_keys} existing shape keys...") + + bpy.context.view_layer.objects.active = target_obj + target_obj.select_set(True) + bpy.ops.object.mode_set(mode='OBJECT') + + while target_obj.data.shape_keys: + target_obj.active_shape_key_index = 0 + bpy.ops.object.shape_key_remove() + + target_obj.select_set(False) + return True + +def ensure_shape_keys_structure(shape_key_names, target_obj): + """Create shape key structure on target object""" + delete_existing_shape_keys(target_obj) + + bpy.context.view_layer.objects.active = target_obj + target_obj.select_set(True) + + # Create Basis with current mesh positions + target_obj.shape_key_add(name='Basis', from_mix=True) + print(f" Created Basis shape key") + + # Create other shape keys + for sk_name in shape_key_names: + if sk_name != 'Basis': + target_obj.shape_key_add(name=sk_name, from_mix=False) + print(f" Created shape key: {sk_name}") + + target_obj.data.shape_keys.use_relative = True + target_obj.select_set(False) + +def create_bvh_for_source(source_data, shape_key_name='Basis'): + """Create BVH tree for source mesh in specified pose""" + + positions = source_data['vertex_positions'][shape_key_name] + verts = [mathutils.Vector(v) for v in positions] + + # Create BVH tree + bvh = BVHTree.FromPolygons(verts, source_data['polygons'], all_triangles=False) + + return bvh, verts + +def compute_signed_distance_and_direction(bvh, point, reference_normal=None): + """Compute signed distance and direction to surface""" + + # Find closest point on surface + location, normal, index, distance = bvh.find_nearest(point) + + if location is None: + return None, None, None, None + + # Determine sign using reference normal + if reference_normal is not None: + to_surface = location - point + if to_surface.length > 0: + dot = to_surface.normalized().dot(reference_normal) + signed_distance = distance if dot > 0 else -distance + else: + signed_distance = 0 + else: + to_surface = point - location + if to_surface.length > 0 and normal.length > 0: + dot = to_surface.normalized().dot(normal) + signed_distance = distance if dot > 0 else -distance + else: + signed_distance = distance + + return location, normal, index, signed_distance + +def detect_boundary_vertices(target_obj, mapping, threshold=0.3): + """Detect vertices that lie on the boundary between inner and outer surfaces""" + + print(f" Detecting boundary vertices...") + + num_verts = len(mapping['target_verts']) + adjacency = mapping['adjacency'] + boundary_vertices = set() + + # For each vertex, check if its neighbors have different side flags + for i in range(num_verts): + if i >= len(mapping['side_flags']): + continue + + side_i = mapping['side_flags'][i] + if side_i == 0: # On surface, definitely boundary + boundary_vertices.add(i) + continue + + neighbors = adjacency[i] + opposite_side_count = 0 + total_neighbors = 0 + + for n in neighbors: + if n < len(mapping['side_flags']): + side_n = mapping['side_flags'][n] + if side_n != 0 and side_n != side_i: + opposite_side_count += 1 + total_neighbors += 1 + + # If significant portion of neighbors are on opposite side, this is a boundary + if total_neighbors > 0 and opposite_side_count / total_neighbors > threshold: + boundary_vertices.add(i) + + # Also check vertices that are close to the surface (small signed distance) + for i in range(num_verts): + if i < len(mapping['signed_distances']): + if abs(mapping['signed_distances'][i]) < 0.05: # Very close to surface + boundary_vertices.add(i) + + print(f" Found {len(boundary_vertices)} boundary vertices") + return boundary_vertices + +def compute_robust_surface_mapping(target_obj, source_data): + """Create robust surface mapping with signed distances""" + + print(f" Computing robust surface mapping...") + + # Get target vertices and normals + target_verts = [v.co.copy() for v in target_obj.data.vertices] + target_normals = [v.normal.copy() for v in target_obj.data.vertices] + num_verts = len(target_verts) + + # Create BVH for source basis + bvh_basis, basis_verts = create_bvh_for_source(source_data, 'Basis') + + # Build adjacency for later smoothing + adjacency = defaultdict(set) + mesh = target_obj.data + for edge in mesh.edges: + v1, v2 = edge.vertices + adjacency[v1].add(v2) + adjacency[v2].add(v1) + + mapping = { + 'target_verts': target_verts, + 'source_indices': [], + 'surface_points': [], + 'signed_distances': [], + 'direction_vectors': [], + 'face_indices': [], + 'barycentric_coords': [], + 'side_flags': [], + 'confidence': [], + 'adjacency': adjacency, + 'deformation_magnitude': [0.0] * num_verts, + 'boundary_vertices': set() + } + + # First pass: find closest points and determine side + for i, (target_pos, target_normal) in enumerate(zip(target_verts, target_normals)): + location, normal, index, signed_dist = compute_signed_distance_and_direction( + bvh_basis, target_pos, target_normal + ) + + if location is None: + # Fallback to nearest vertex + if i % 100 == 0: + print(f" Warning: Vertex {i} using nearest vertex fallback") + min_dist = float('inf') + nearest_idx = 0 + for j, src_pos in enumerate(basis_verts): + dist = (target_pos - src_pos).length + if dist < min_dist: + min_dist = dist + nearest_idx = j + + mapping['source_indices'].append([nearest_idx]) + mapping['surface_points'].append(basis_verts[nearest_idx]) + mapping['signed_distances'].append(min_dist) + mapping['direction_vectors'].append(target_pos - basis_verts[nearest_idx]) + mapping['face_indices'].append(-1) + mapping['barycentric_coords'].append([1.0]) + mapping['side_flags'].append(1) + mapping['confidence'].append(0.5) + else: + mapping['surface_points'].append(location) + mapping['signed_distances'].append(signed_dist) + + abs_dist = abs(signed_dist) + if abs_dist < 0.001: + side_flag = 0 + confidence = 1.0 + else: + ray_hits = 0 + for ray_dir in [target_normal, -target_normal, mathutils.Vector((1,0,0)), mathutils.Vector((-1,0,0))]: + hit, _, _, _ = bvh_basis.ray_cast(target_pos + ray_dir * 0.1, -ray_dir) + if hit is not None: + ray_hits += 1 + + side_flag = 1 if signed_dist > 0 else -1 + confidence = min(1.0, ray_hits / 4.0 + 0.5) + + mapping['side_flags'].append(side_flag) + mapping['confidence'].append(confidence) + + if index is not None and index < len(source_data['polygons']): + face = source_data['polygons'][index] + mapping['face_indices'].append(index) + + if len(face) == 3: + face_verts = [basis_verts[idx] for idx in face] + try: + coords = mathutils.geometry.barycentric_transform( + location, face_verts[0], face_verts[1], face_verts[2] + ) + mapping['barycentric_coords'].append(coords) + + min_dist = float('inf') + nearest_idx = face[0] + for idx in face: + dist = (location - basis_verts[idx]).length + if dist < min_dist: + min_dist = dist + nearest_idx = idx + mapping['source_indices'].append([nearest_idx]) + except: + min_dist = float('inf') + nearest_idx = face[0] + for idx in face: + dist = (location - basis_verts[idx]).length + if dist < min_dist: + min_dist = dist + nearest_idx = idx + mapping['source_indices'].append([nearest_idx]) + mapping['barycentric_coords'].append([1.0]) + else: + min_dist = float('inf') + nearest_idx = face[0] + for idx in face: + dist = (location - basis_verts[idx]).length + if dist < min_dist: + min_dist = dist + nearest_idx = idx + mapping['source_indices'].append([nearest_idx]) + mapping['barycentric_coords'].append([1.0]) + else: + mapping['face_indices'].append(-1) + mapping['source_indices'].append([0]) + mapping['barycentric_coords'].append([1.0]) + + direction = target_pos - location + mapping['direction_vectors'].append(direction) + + # Detect boundary vertices + mapping['boundary_vertices'] = detect_boundary_vertices(target_obj, mapping) + + print(f" Mapping complete. Side distribution: " + f"Outside: {mapping['side_flags'].count(1)}, " + f"Inside: {mapping['side_flags'].count(-1)}, " + f"On surface: {mapping['side_flags'].count(0)}") + + return mapping + +def interpolate_source_position_safe(source_data, sk_name, surface_point, face_idx, bary_coords, source_indices): + """Interpolate source position with side preservation""" + + if sk_name == 'Basis': + return surface_point + + source_positions = source_data['vertex_positions'][sk_name] + + if face_idx >= 0 and face_idx < len(source_data['polygons']): + face = source_data['polygons'][face_idx] + + if len(face) == 3 and len(bary_coords) == 3: + pos = mathutils.Vector((0, 0, 0)) + for j, vert_idx in enumerate(face): + if j < len(bary_coords) and vert_idx < len(source_positions): + pos += bary_coords[j] * mathutils.Vector(source_positions[vert_idx]) + return pos + + if source_indices and len(source_indices) > 0: + idx = source_indices[0] + if idx < len(source_positions): + return mathutils.Vector(source_positions[idx]) + + return surface_point + +def compute_deformation_magnitudes(source_data, mapping): + """Compute how much each vertex deforms in the source""" + + print(f" Computing deformation magnitudes...") + + basis_positions = [mathutils.Vector(v) for v in source_data['vertex_positions']['Basis']] + num_verts = len(mapping['target_verts']) + magnitudes = [0.0] * num_verts + + for sk_name in source_data['names']: + if sk_name == 'Basis': + continue + + sk_positions = [mathutils.Vector(v) for v in source_data['vertex_positions'][sk_name]] + + for i in range(num_verts): + if i < len(mapping['source_indices']): + src_indices = mapping['source_indices'][i] + if src_indices and len(src_indices) > 0: + src_idx = src_indices[0] + if src_idx < len(basis_positions) and src_idx < len(sk_positions): + deformation = (sk_positions[src_idx] - basis_positions[src_idx]).length + magnitudes[i] = max(magnitudes[i], deformation) + + max_mag = max(magnitudes) if magnitudes else 1.0 + if max_mag > 0: + magnitudes = [m / max_mag for m in magnitudes] + + high_count = sum(1 for m in magnitudes if m > 0.7) + print(f" High deformation vertices (>0.7): {high_count}/{num_verts}") + + return magnitudes + +def enforce_side_constraint(pos, surface_point, reference_normal, target_side, confidence, is_boundary=False): + """Enforce that vertex stays on correct side of surface""" + + to_surface = surface_point - pos + if to_surface.length < 0.001: + return pos + + current_side = 1 if to_surface.dot(reference_normal) < 0 else -1 + + # For boundary vertices, use very gentle enforcement + if is_boundary: + if current_side != target_side and target_side != 0: + # Just pull back slightly toward surface + proj_factor = to_surface.dot(reference_normal) / reference_normal.length_squared + proj_point = pos + reference_normal * proj_factor + + # Very small offset to stay near surface + offset = abs((surface_point - pos).length) * 0.2 + if target_side > 0: + return proj_point - reference_normal * offset + else: + return proj_point + reference_normal * offset + return pos + + # Regular vertices - stronger enforcement + if current_side != target_side and target_side != 0: + proj_factor = to_surface.dot(reference_normal) / reference_normal.length_squared + proj_point = pos + reference_normal * proj_factor + + offset = abs((surface_point - pos).length) * 0.95 + if target_side > 0: + corrected_pos = proj_point - reference_normal * offset + else: + corrected_pos = proj_point + reference_normal * offset + + return corrected_pos + + return pos + +def limit_boundary_velocity(target_idx, target_pos, deformed_surface, t, mapping): + """Limit how fast boundary vertices can move""" + + if target_idx not in mapping['boundary_vertices']: + return deformed_surface + + # Get the full deformation target + full_target = deformed_surface + + # Calculate how far this vertex should move at this t value + # Use a slower progression for boundary vertices + safe_t = t * 0.7 # Boundary vertices only move 70% as fast + + # Interpolate between start and target with limited speed + limited_pos = (1 - safe_t) * target_pos + safe_t * full_target + + return limited_pos + +def adaptive_damping(pos, target_pos, deformed_surface, magnitude, t, threshold=0.55, is_boundary=False): + """Apply adaptive damping based on deformation magnitude and t value""" + + if t <= threshold: + return pos + + # For boundary vertices, use much less damping to avoid pinching + if is_boundary: + return pos + + excess = (t - threshold) / (1.0 - threshold) + damping = min(1.0, magnitude * 0.8 + 0.2) + damped_weight = excess * damping * 0.5 + + safe_pos = (target_pos + deformed_surface) * 0.5 + damped_pos = (1 - damped_weight) * pos + damped_weight * safe_pos + + return damped_pos + +def compute_side_preserving_position(target_idx, sk_name, mapping, source_data, t): + """Compute position that preserves side and signed distance""" + + if target_idx >= len(mapping['target_verts']): + return mathutils.Vector((0, 0, 0)) + + target_pos = mapping['target_verts'][target_idx] + + if target_idx >= len(mapping['surface_points']): + return target_pos + + # Check if this is a boundary vertex + is_boundary = target_idx in mapping.get('boundary_vertices', set()) + + surface_point = mapping['surface_points'][target_idx] + signed_dist = mapping['signed_distances'][target_idx] if target_idx < len(mapping['signed_distances']) else 0 + side_flag = mapping['side_flags'][target_idx] if target_idx < len(mapping['side_flags']) else 1 + confidence = mapping['confidence'][target_idx] if target_idx < len(mapping['confidence']) else 0.5 + + face_idx = mapping['face_indices'][target_idx] if target_idx < len(mapping['face_indices']) else -1 + bary_coords = mapping['barycentric_coords'][target_idx] if target_idx < len(mapping['barycentric_coords']) else [1.0] + source_indices = mapping['source_indices'][target_idx] if target_idx < len(mapping['source_indices']) else [0] + + deformed_surface = interpolate_source_position_safe( + source_data, sk_name, surface_point, face_idx, bary_coords, source_indices + ) + + # For boundary vertices, limit their velocity + if is_boundary: + deformed_surface = limit_boundary_velocity(target_idx, target_pos, deformed_surface, t, mapping) + + basis_surface = surface_point + current_surface = (1 - t) * basis_surface + t * deformed_surface + + if target_idx < len(mapping['direction_vectors']): + reference_normal = mapping['direction_vectors'][target_idx].normalized() + else: + reference_normal = mathutils.Vector((0, 0, 1)) + + if reference_normal.length < 0.1: + reference_normal = mathutils.Vector((0, 0, 1)) + + abs_dist = abs(signed_dist) + + # For boundary vertices, use a much smaller offset to keep them near the surface + if is_boundary: + # Boundary vertices should stay very close to the surface + # Use a fraction of their original distance + abs_dist = abs_dist * 0.2 # Only 20% of original offset + else: + # Regular vertices use full offset but with confidence weighting + abs_dist = abs_dist * confidence + + if side_flag > 0: + base_pos = current_surface + reference_normal * abs_dist + elif side_flag < 0: + base_pos = current_surface - reference_normal * abs_dist + else: + base_pos = current_surface + + direct_mapped = (1 - t) * target_pos + t * deformed_surface + + # For boundary vertices, blend more heavily with direct mapping + if is_boundary: + blend_weight = 0.1 # Only 10% side-preserving for boundaries + else: + blend_weight = confidence * (1 - t * 0.3) + + blended_pos = (1 - blend_weight) * direct_mapped + blend_weight * base_pos + + if side_flag != 0: + final_pos = enforce_side_constraint( + blended_pos, current_surface, reference_normal, side_flag, confidence, is_boundary + ) + else: + final_pos = blended_pos + + if 'deformation_magnitude' in mapping: + if target_idx < len(mapping['deformation_magnitude']): + magnitude = mapping['deformation_magnitude'][target_idx] + final_pos = adaptive_damping(final_pos, target_pos, deformed_surface, magnitude, t, is_boundary=is_boundary) + + return final_pos + +def smooth_boundary_areas(target_obj, sk_name, mapping, source_data): + """Specifically smooth boundary vertices to prevent them from popping out""" + + print(f" Smoothing boundary areas for {sk_name}...") + + sk = target_obj.data.shape_keys.key_blocks[sk_name] + current_positions = [v.co.copy() for v in sk.data] + num_verts = len(current_positions) + + boundary_vertices = mapping.get('boundary_vertices', set()) + if not boundary_vertices: + return + + adjacency = mapping['adjacency'] + + # Multiple smoothing passes focused on boundaries + for iteration in range(3): # More iterations for boundaries + new_positions = current_positions.copy() + + for i in boundary_vertices: + if i >= num_verts: + continue + + neighbors = adjacency[i] + if not neighbors: + continue + + # Average with neighbors, weighting by side similarity + weighted_sum = mathutils.Vector((0, 0, 0)) + total_weight = 0 + + for n in neighbors: + if n < num_verts: + # Higher weight for neighbors on same side + if n < len(mapping['side_flags']) and i < len(mapping['side_flags']): + if mapping['side_flags'][n] == mapping['side_flags'][i]: + weight = 2.0 + else: + weight = 1.0 + else: + weight = 1.0 + + weighted_sum += current_positions[n] * weight + total_weight += weight + + if total_weight > 0: + avg_pos = weighted_sum / total_weight + + # Progressive smoothing + blend = 0.2 + iteration * 0.1 + new_positions[i] = (1 - blend) * current_positions[i] + blend * avg_pos + + current_positions = new_positions + + # Apply smoothed positions + for i, pos in enumerate(current_positions): + if i < len(sk.data): + sk.data[i].co = pos + + sk.data.update() + target_obj.data.update_tag() + bpy.context.view_layer.update() + + print(f" Boundary smoothing complete") + +def smooth_penetration_areas(target_obj, sk_name, mapping, source_data, threshold_t=0.55): + """Smooth areas where penetration occurs at high t values""" + + print(f" Smoothing penetration areas for {sk_name}...") + + sk = target_obj.data.shape_keys.key_blocks[sk_name] + current_positions = [v.co.copy() for v in sk.data] + num_verts = len(current_positions) + + boundary_vertices = mapping.get('boundary_vertices', set()) + + # Detect penetration at t=1.0 + problem_vertices = set() + bvh_deformed, _ = create_bvh_for_source(source_data, sk_name) + + for i, pos in enumerate(current_positions): + if i < len(mapping['surface_points']) and i < len(mapping['side_flags']): + surface_point = mapping['surface_points'][i] + target_side = mapping['side_flags'][i] + + if target_side != 0: + location, _, _, _ = bvh_deformed.find_nearest(pos) + if location and i < len(mapping['direction_vectors']): + to_surface = pos - location + current_side = 1 if to_surface.dot(mapping['direction_vectors'][i]) > 0 else -1 + + if current_side != target_side: + problem_vertices.add(i) + + if not problem_vertices: + print(f" No penetration detected") + return + + print(f" Found {len(problem_vertices)} penetrating vertices") + + # Separate boundary and interior problem vertices + boundary_problems = problem_vertices.intersection(boundary_vertices) + interior_problems = problem_vertices - boundary_vertices + + if boundary_problems: + print(f" {len(boundary_problems)} are boundary vertices - these need special care") + + # For boundary vertices, use very gentle smoothing + adjacency = mapping['adjacency'] + for iteration in range(2): + new_positions = current_positions.copy() + + for i in boundary_problems: + neighbors = adjacency[i] + if not neighbors: + continue + + # Only average with non-problem neighbors + weighted_sum = mathutils.Vector((0, 0, 0)) + total_weight = 0 + + for n in neighbors: + if n < num_verts and n not in boundary_problems: + weighted_sum += current_positions[n] + total_weight += 1 + + if total_weight > 0: + avg_pos = weighted_sum / total_weight + blend = 0.15 # Very gentle + new_positions[i] = (1 - blend) * current_positions[i] + blend * avg_pos + + current_positions = new_positions + + # Get vertices with large deformation + large_deformation = set() + if 'deformation_magnitude' in mapping: + for i, mag in enumerate(mapping['deformation_magnitude']): + if i < num_verts and mag > 0.7: + large_deformation.add(i) + + # Focus on interior problems with large deformation + focus_vertices = interior_problems.intersection(large_deformation) + if focus_vertices: + print(f" {len(focus_vertices)} interior high-deformation vertices") + + # Smooth interior problems + adjacency = mapping['adjacency'] + for iteration in range(3): + new_positions = current_positions.copy() + + for i in focus_vertices: + neighbors = adjacency[i] + if not neighbors: + continue + + weighted_sum = mathutils.Vector((0, 0, 0)) + total_weight = 0 + + for n in neighbors: + if n < num_verts: + weight = 1.0 + if n in boundary_vertices: + weight = 0.3 # Less influence from boundaries + + weighted_sum += current_positions[n] * weight + total_weight += weight + + if total_weight > 0: + avg_pos = weighted_sum / total_weight + blend = 0.3 + iteration * 0.1 + new_positions[i] = (1 - blend) * current_positions[i] + blend * avg_pos + + current_positions = new_positions + + # Apply smoothed positions + for i, pos in enumerate(current_positions): + if i < len(sk.data): + sk.data[i].co = pos + + sk.data.update() + target_obj.data.update_tag() + bpy.context.view_layer.update() + + print(f" Smoothing complete") + +def set_shape_key_with_side_preservation(target_obj, sk_name, mapping, source_data): + """Set shape key with side preservation""" + + print(f" Setting {sk_name} with side preservation...") + + if 'deformation_magnitude' not in mapping or len(mapping['deformation_magnitude']) != len(mapping['target_verts']): + mapping['deformation_magnitude'] = compute_deformation_magnitudes(source_data, mapping) + + target_obj.data.shape_keys.use_relative = False + sk = target_obj.data.shape_keys.key_blocks[sk_name] + + for i, v in enumerate(sk.data): + if i < len(mapping['target_verts']): + pos = compute_side_preserving_position(i, sk_name, mapping, source_data, 1.0) + v.co = pos + + sk.data.update() + target_obj.data.update_tag() + bpy.context.view_layer.update() + + target_obj.data.shape_keys.use_relative = True + target_obj.data.update_tag() + bpy.context.view_layer.update() + + # First smooth boundary areas + smooth_boundary_areas(target_obj, sk_name, mapping, source_data) + + # Then smooth penetration areas + smooth_penetration_areas(target_obj, sk_name, mapping, source_data) + +def test_shape_key_quality(target_obj, sk_name, mapping, source_data): + """Test shape key quality with side tracking""" + + sk = target_obj.data.shape_keys.key_blocks[sk_name] + + print(f" Testing quality of '{sk_name}':") + + test_values = [0.0, 0.3, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.77, 0.8, 0.9, 1.0] + prev_positions = None + boundary_vertices = mapping.get('boundary_vertices', set()) + + for val in test_values: + if val == 0.0: + sk.value = 0.0 + else: + target_obj.data.shape_keys.use_relative = False + for i, v in enumerate(sk.data): + if i < len(mapping['target_verts']): + pos = compute_side_preserving_position(i, sk_name, mapping, source_data, val) + v.co = pos + target_obj.data.shape_keys.use_relative = True + + target_obj.data.update_tag() + bpy.context.view_layer.update() + + if prev_positions is not None: + movements = [] + side_changes = 0 + boundary_issues = 0 + + for i, v in enumerate(target_obj.data.vertices): + if i < len(prev_positions) and i < len(mapping['side_flags']): + movement = (v.co - prev_positions[i]).length + if movement > 0.001: + movements.append(movement) + + if i < len(mapping['surface_points']) and i < len(mapping['direction_vectors']): + current_to_surface = v.co - mapping['surface_points'][i] + current_side = 1 if current_to_surface.dot(mapping['direction_vectors'][i]) > 0 else -1 + + if current_side != mapping['side_flags'][i] and mapping['side_flags'][i] != 0: + side_changes += 1 + if i in boundary_vertices: + boundary_issues += 1 + + if movements: + avg_movement = sum(movements) / len(movements) + max_movement = max(movements) + print(f" Value {val:.2f}: avg Δ {avg_movement:.4f}, max Δ {max_movement:.4f}") + if side_changes > 0: + print(f" ⚠ {side_changes} vertices changed side ({boundary_issues} on boundary)") + + prev_positions = [v.co.copy() for v in target_obj.data.vertices] + + sk.value = 0.0 + target_obj.data.update_tag() + bpy.context.view_layer.update() + +def transfer_shape_keys(source_data, target_obj): + """Transfer shape keys with side preservation""" + print(f" Transferring shape keys with side preservation...") + + mapping = compute_robust_surface_mapping(target_obj, source_data) + + for sk_name in source_data['names']: + if sk_name == 'Basis': + continue + + print(f" Processing: {sk_name}") + set_shape_key_with_side_preservation(target_obj, sk_name, mapping, source_data) + print(f" ✓ Transferred {sk_name}") + + return mapping + +def verify_transfer(source_names, target_obj): + """Verify shape keys were transferred correctly""" + target_names = [sk.name for sk in target_obj.data.shape_keys.key_blocks] + + print(f" Verification:") + print(f" Source keys: {len(source_names)}") + print(f" Target keys: {len(target_names)}") + + if set(source_names) == set(target_names): + print(f" ✓ All shape keys present") + +def save_output_file(output_path, temp_target): + """Save the modified file""" + print(f"\nSaving output file: {output_path}") + + output_dir = os.path.dirname(output_path) + if output_dir and not os.path.exists(output_dir): + os.makedirs(output_dir) + + bpy.ops.wm.save_as_mainfile(filepath=output_path) + + try: + if os.path.exists(temp_target): + os.remove(temp_target) + temp_dir = os.path.dirname(temp_target) + if os.path.exists(temp_dir) and not os.listdir(temp_dir): + os.rmdir(temp_dir) + except: + pass + + print(f"File saved successfully") + +def main(): + print("=" * 60) + print("Blender Shape Key Transfer Script - Boundary Velocity Limiting") + print("=" * 60) + + source_file, target_file, output_file = parse_args() + print(f"Source: {source_file}") + print(f"Target: {target_file}") + print(f"Output: {output_file}") + + try: + source_data = load_source_data(source_file) + target_objects, 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: + print("\nNo target objects found with required properties") + + print("\n" + "=" * 60) + print("Script completed successfully!") + print("=" * 60) + + except Exception as e: + print(f"\nError: {str(e)}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/src/gamedata/BoatModule.cpp b/src/gamedata/BoatModule.cpp index 10ac33e..a7846ea 100644 --- a/src/gamedata/BoatModule.cpp +++ b/src/gamedata/BoatModule.cpp @@ -201,16 +201,15 @@ BoatModule::BoatModule(flecs::world &ecs) ->_getDerivedOrientation(); if (ev.e2.has< CharacterBase>()) { - ev.e2.get_mut< - CharacterBase>() - .mBodyNode - ->_setDerivedPosition( - position); - ev.e2.get_mut< - CharacterBase>() - .mBodyNode - ->_setDerivedOrientation( - orientation); + Ogre::SceneNode *n = + ECS::get< + CharacterModule>() + .characterNodes + .at(ev.e2); + n->_setDerivedPosition( + position); + n->_setDerivedOrientation( + orientation); } } e.set( @@ -282,14 +281,14 @@ BoatModule::BoatModule(flecs::world &ecs) captainSeat ->_getDerivedOrientation(); if (ev.e2.has()) { - ev.e2.get_mut() - .mBodyNode - ->_setDerivedPosition( - position); - ev.e2.get_mut() - .mBodyNode - ->_setDerivedOrientation( - orientation); + Ogre::SceneNode *n = + ECS::get() + .characterNodes + .at(ev.e2); + n->_setDerivedPosition( + position); + n->_setDerivedOrientation( + orientation); } } else if (ev.event == "boat_control_exit") { } else if (ev.event == "actuator_exit") { diff --git a/src/gamedata/CharacterAIModule.cpp b/src/gamedata/CharacterAIModule.cpp index e7d2194..e03f4f6 100644 --- a/src/gamedata/CharacterAIModule.cpp +++ b/src/gamedata/CharacterAIModule.cpp @@ -188,17 +188,16 @@ public: int update(float delta) override { if (npc.e.is_valid()) { + Ogre::SceneNode *n = + ECS::get() + .characterNodes.at(npc.e); Ogre::Vector3 position = - npc.e.get() - .mBodyNode - ->_getDerivedPosition(); + n->_getDerivedPosition(); if (position.squaredDistance(targetPosition) >= radius * radius) { if (npc.e.is_valid()) - npc.e.get() - .mBodyNode - ->_setDerivedPosition( - targetPosition); + n->_setDerivedPosition( + targetPosition); npc.position = targetPosition; return BUSY; } @@ -588,11 +587,13 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) it++) { auto &npc = npcs.npcs.at(it->first); if (npc.e.is_valid() && - npc.e.has()) - npc.position = - npc.e.get() - .mBodyNode - ->_getDerivedPosition(); + npc.e.has()) { + Ogre::SceneNode *n = + ECS::get() + .characterNodes.at( + npc.e); + npc.position = n->_getDerivedPosition(); + } } }); ecs.system("UpdateBlackboards") @@ -1214,11 +1215,12 @@ void Blackboard::query_ai() const float distance = 10000.0f; Ogre::Vector3 position(0, 0, 0); if (npcs.npcs.at(index).e.is_valid() && - npcs.npcs.at(index).e.has()) - position = npcs.npcs.at(index) - .e.get() - .mBodyNode->_getDerivedPosition(); - else + npcs.npcs.at(index).e.has()) { + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + npcs.npcs.at(index).e); + position = n->_getDerivedPosition(); + } else from_json(npcs.npcs.at(index).props["position"], position); this->position = position; ActionNodeList &alist = ECS::get_mut(); diff --git a/src/gamedata/CharacterAnimationModule.cpp b/src/gamedata/CharacterAnimationModule.cpp index ef47181..7062fcf 100644 --- a/src/gamedata/CharacterAnimationModule.cpp +++ b/src/gamedata/CharacterAnimationModule.cpp @@ -29,8 +29,11 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) if (!anim.configured) { int i, j; e.set({}); + Ogre::SceneNode *n = + ECS::get() + .characterNodes.at(e); Ogre::Entity *ent = static_cast( - ch.mBodyNode->getAttachedObject(0)); + n->getAttachedObject(0)); ent->getSkeleton()->setBlendMode( Ogre::ANIMBLEND_CUMULATIVE); Ogre::AnimationStateSet *animStateSet = @@ -197,10 +200,15 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) ZoneScopedN("HandleRootMotionVelocity"); if (eng.delta < 0.0000001f) return; - if (!ch.mBodyNode) + if (ECS::get().characterNodes.find( + e) == + ECS::get().characterNodes.end()) return; - Ogre::Quaternion rot = ch.mBodyNode->getOrientation(); - Ogre::Vector3 pos = ch.mBodyNode->getPosition(); + const Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); + Ogre::Quaternion rot = n->getOrientation(); + Ogre::Vector3 pos = n->getPosition(); Ogre::Vector3 boneMotion = ch.mBoneMotion; v.velocity = Ogre::Vector3::ZERO; if (eng.delta <= 0.005) @@ -235,7 +243,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) v.velocity.y = 0.0f; #endif OgreAssert(v.velocity.squaredLength() < 1000.0f, - "Bad velocity setting"); + "Bad velocity setting " + Ogre::StringConverter::toString(safeDelta) + " " + Ogre::StringConverter::toString(boneMotion)); }); ecs.system("HandleRootMotion") @@ -244,8 +252,10 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) CharacterBase &ch, AnimationControl &anim, CharacterVelocity &v) { ZoneScopedN("HandleRootMotion"); +#if 0 if (!ch.mBodyNode) return; +#endif if (eng.delta < 0.0000001f) return; OgreAssert(eng.delta > 0.0f, "Zero delta"); diff --git a/src/gamedata/CharacterManagerModule.cpp b/src/gamedata/CharacterManagerModule.cpp index 13d5021..e3399c8 100644 --- a/src/gamedata/CharacterManagerModule.cpp +++ b/src/gamedata/CharacterManagerModule.cpp @@ -21,8 +21,9 @@ void createNPCActionNodes(flecs::entity town, int index) flecs::entity e = npc.e; nlohmann::json npcprops = npc.props; const CharacterBase &ch = e.get(); - Ogre::Vector3 characterPos = ch.mBodyNode->_getDerivedPosition(); - Ogre::Quaternion characterRot = ch.mBodyNode->_getDerivedOrientation(); + Ogre::SceneNode *n = ECS::get().characterNodes.at(e); + Ogre::Vector3 characterPos = n->_getDerivedPosition(); + Ogre::Quaternion characterRot = n->_getDerivedOrientation(); if (npc.actionNodes.size() > 0) { int i; for (i = 0; i < npc.actionNodes.size(); i++) { @@ -117,8 +118,8 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) if (!player.has()) return; Ogre::Vector3 cameraPos = - player.get() - .mBodyNode->_getDerivedPosition(); + ECS::get() + .mCameraNode->_getDerivedPosition(); for (auto &npc : npcs.npcs) { int index = npc.first; TownNPCs::NPCData &data = npc.second; @@ -140,6 +141,7 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) break; } } +#if 0 if (cameraPos.squaredDistance(npcPosition) > 22500.0f) { if (data.e.is_valid()) { @@ -148,6 +150,7 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) break; } } +#endif } for (auto &npc : npcs.npcs) { int index = npc.first; diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index 61f51a6..2e648c3 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -162,9 +162,9 @@ CharacterModule::CharacterModule(flecs::world &ecs) OgreAssert(characterOrientations.find(e) != characterOrientations.end(), "Bad orientation/position"); - ch.mBodyNode = characterNodes[e]; - ch.mBodyNode->setOrientation(characterOrientations[e]); - ch.mBodyNode->setPosition(characterPositions[e]); + Ogre::SceneNode *bodyNode = characterNodes[e]; + bodyNode->setOrientation(characterOrientations[e]); + bodyNode->setPosition(characterPositions[e]); OgreAssert( characterEntitiesFace[e]->getSkeleton()->hasBone( "Root"), @@ -304,7 +304,9 @@ CharacterModule::CharacterModule(flecs::world &ecs) ch.mGoalDirection.normalise(); Ogre::Quaternion toGoal = - ch.mBodyNode->getOrientation() + ECS::get() + .characterNodes.at(e) + ->getOrientation() .zAxis() .getRotationTo( ch.mGoalDirection); @@ -329,7 +331,9 @@ CharacterModule::CharacterModule(flecs::world &ecs) std::min( yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp(yawToGoal, 0, yawAtSpeed); - ch.mBodyNode->yaw(Ogre::Degree(yawToGoal)); + ECS::get() + .characterNodes.at(e) + ->yaw(Ogre::Degree(yawToGoal)); } }); #if 0 @@ -363,7 +367,7 @@ CharacterModule::CharacterModule(flecs::world &ecs) .kind(flecs::OnUpdate) .with() .with() - .each([](const EngineData &eng, Camera &camera, + .each([](flecs::entity e, const EngineData &eng, Camera &camera, const CharacterBase &ch) { ZoneScopedN("UpdateCamera"); float delta = eng.delta; @@ -391,7 +395,9 @@ CharacterModule::CharacterModule(flecs::world &ecs) } else { // place the camera pivot roughly at the character's shoulder camera.mCameraPivot->setPosition( - ch.mBodyNode->getPosition() + + ECS::get() + .characterNodes.at(e) + ->getPosition() + Ogre::Vector3::UNIT_Y * CAM_HEIGHT); // move the camera smoothly to the goal Ogre::Vector3 goalOffset = diff --git a/src/gamedata/CharacterModule.h b/src/gamedata/CharacterModule.h index 2b4e47d..38b9d26 100644 --- a/src/gamedata/CharacterModule.h +++ b/src/gamedata/CharacterModule.h @@ -23,7 +23,6 @@ struct CharacterBase { Ogre::Vector3 mBoneMotion; Ogre::Vector3 mBonePrevMotion; Ogre::Vector3 mGoalDirection; // actual intended direction in world-space - Ogre::SceneNode *mBodyNode; bool is_submerged; }; struct CharacterLocation { diff --git a/src/gamedata/LuaModule/LuaData.cpp b/src/gamedata/LuaModule/LuaData.cpp index e772a5d..68dbf39 100644 --- a/src/gamedata/LuaModule/LuaData.cpp +++ b/src/gamedata/LuaModule/LuaData.cpp @@ -457,34 +457,35 @@ LuaData::LuaData() Ogre::Vector3 position = target_node->_getDerivedPosition(); Ogre::Quaternion orientation = target_node->_getDerivedOrientation(); - if (object_e.has()) { - object_e.get_mut() - .mBodyNode->_setDerivedPosition(position); - object_e.get_mut() - .mBodyNode->_setDerivedOrientation(orientation); - } - return 0; - }); - lua_setglobal(L, "ecs_trigger_set_position"); - lua_pushcfunction(L, [](lua_State *L) -> int { - luaL_checktype(L, 1, LUA_TNUMBER); // trigger - int trigger = lua_tointeger(L, 1); - flecs::entity trigger_e = idmap.get_entity(trigger); - Ogre::SceneNode *node = trigger_e.get().node; - Ogre::Any animationAny = - node->getUserObjectBindings().getUserAny( - "trigger_animation"); - if (animationAny.has_value()) { - Ogre::String animation = - Ogre::any_cast(animationAny); - lua_pushstring(L, animation.c_str()); + if (object_e.has()) { + Ogre::SceneNode *bodyNode = + ECS::get().characterNodes.at( + object_e); + bodyNode->_setDerivedPosition(position); + bodyNode->_setDerivedOrientation(orientation); + } + return 0; + }); + lua_setglobal(L, "ecs_trigger_set_position"); + lua_pushcfunction(L, [](lua_State *L) -> int { + luaL_checktype(L, 1, LUA_TNUMBER); // trigger + int trigger = lua_tointeger(L, 1); + flecs::entity trigger_e = idmap.get_entity(trigger); + Ogre::SceneNode *node = trigger_e.get().node; + Ogre::Any animationAny = + node->getUserObjectBindings().getUserAny( + "trigger_animation"); + if (animationAny.has_value()) { + Ogre::String animation = + Ogre::any_cast(animationAny); + lua_pushstring(L, animation.c_str()); return 1; - } - lua_pushnil(L); - return 1; - }); - lua_setglobal(L, "ecs_trigger_get_animation"); - lua_pushcfunction(L, [](lua_State *L) -> int { + } + lua_pushnil(L); + return 1; + }); + lua_setglobal(L, "ecs_trigger_get_animation"); + lua_pushcfunction(L, [](lua_State *L) -> int { OgreAssert(lua_gettop(L) == 8, "Invalid parameters"); luaL_checktype(L, 1, LUA_TSTRING); // face luaL_checktype(L, 2, LUA_TSTRING); // hair @@ -504,101 +505,103 @@ LuaData::LuaData() float x = lua_tonumber(L, 5); float y = lua_tonumber(L, 6); float z = lua_tonumber(L, 7); - Ogre::Quaternion orientation(Ogre::Radian(yaw), - Ogre::Vector3::UNIT_Y); - Ogre::Vector3 npcPos(x, y, z); - flecs::entity e = - ECS::get_mut() + Ogre::Quaternion orientation(Ogre::Radian(yaw), + Ogre::Vector3::UNIT_Y); + Ogre::Vector3 npcPos(x, y, z); + flecs::entity e = + ECS::get_mut() .createCharacterData(face, hair, top, bottom, feet, npcPos, orientation); - ECS::modified(); - lua_pushinteger(L, idmap.add_entity(e)); - return 1; - }); - lua_setglobal(L, "ecs_npc_set"); - lua_pushcfunction(L, [](lua_State *L) -> int { - OgreAssert(lua_gettop(L) == 1, "Bad parameters"); - luaL_checktype(L, 1, LUA_TSTRING); // type - const char *fileName = lua_tostring(L, 1); - ECS::get().mScnMgr->getRootSceneNode()->saveChildren( - fileName); - return 0; - }); - lua_setglobal(L, "ecs_save_scene_debug"); - lua_pushcfunction(L, [](lua_State *L) -> int { - OgreAssert(lua_gettop(L) == 2, "Bad parameters"); - luaL_checktype(L, 1, LUA_TNUMBER); // object - luaL_checktype(L, 2, LUA_TSTRING); // name - int object = lua_tointeger(L, 1); - flecs::entity object_e = idmap.get_entity(object); - const char *fileName = lua_tostring(L, 2); - Ogre::SceneNode *node = nullptr; - if (object_e.has()) - node = object_e.get().mBodyNode; - else if (object_e.has()) - node = object_e.get().mNode; - if (node) - node->saveChildren(fileName); - return 0; - }); - lua_setglobal(L, "ecs_save_object_debug"); - lua_pushcfunction(L, [](lua_State *L) -> int { - luaL_checktype(L, 1, LUA_TBOOLEAN); // object - ECS::get_mut().enableDbgDraw = lua_toboolean(L, 1); - ECS::modified(); - return 0; - }); - lua_setglobal(L, "ecs_set_debug_drawing"); - lua_pushcfunction(L, [](lua_State *L) -> int { - OgreAssert(lua_gettop(L) >= 1, "Bad parameters"); - luaL_checktype(L, 1, LUA_TSTRING); - Ogre::String command = lua_tostring(L, 1); - if (command == "physics-control") { - OgreAssert(lua_gettop(L) == 3, "Bad parameters"); - luaL_checktype(L, 2, LUA_TNUMBER); // object - luaL_checktype(L, 3, LUA_TBOOLEAN); - int object = lua_tointeger(L, 2); - flecs::entity object_e = idmap.get_entity(object); - bool enable = lua_toboolean(L, 3); - OgreAssert(object_e.has(), - "Not a character"); - PhysicsModule::controlPhysics(object_e, enable); - object_e.add(); + ECS::modified(); + lua_pushinteger(L, idmap.add_entity(e)); + return 1; + }); + lua_setglobal(L, "ecs_npc_set"); + lua_pushcfunction(L, [](lua_State *L) -> int { + OgreAssert(lua_gettop(L) == 1, "Bad parameters"); + luaL_checktype(L, 1, LUA_TSTRING); // type + const char *fileName = lua_tostring(L, 1); + ECS::get().mScnMgr->getRootSceneNode()->saveChildren( + fileName); + return 0; + }); + lua_setglobal(L, "ecs_save_scene_debug"); + lua_pushcfunction(L, [](lua_State *L) -> int { + OgreAssert(lua_gettop(L) == 2, "Bad parameters"); + luaL_checktype(L, 1, LUA_TNUMBER); // object + luaL_checktype(L, 2, LUA_TSTRING); // name + int object = lua_tointeger(L, 1); + flecs::entity object_e = idmap.get_entity(object); + const char *fileName = lua_tostring(L, 2); + Ogre::SceneNode *node = nullptr; + if (object_e.has()) { + node = ECS::get().characterNodes.at( + object_e); + } else if (object_e.has()) + node = object_e.get().mNode; + if (node) + node->saveChildren(fileName); + return 0; + }); + lua_setglobal(L, "ecs_save_object_debug"); + lua_pushcfunction(L, [](lua_State *L) -> int { + luaL_checktype(L, 1, LUA_TBOOLEAN); // object + ECS::get_mut().enableDbgDraw = lua_toboolean(L, 1); + ECS::modified(); + return 0; + }); + lua_setglobal(L, "ecs_set_debug_drawing"); + lua_pushcfunction(L, [](lua_State *L) -> int { + OgreAssert(lua_gettop(L) >= 1, "Bad parameters"); + luaL_checktype(L, 1, LUA_TSTRING); + Ogre::String command = lua_tostring(L, 1); + if (command == "physics-control") { + OgreAssert(lua_gettop(L) == 3, "Bad parameters"); + luaL_checktype(L, 2, LUA_TNUMBER); // object + luaL_checktype(L, 3, LUA_TBOOLEAN); + int object = lua_tointeger(L, 2); + flecs::entity object_e = idmap.get_entity(object); + bool enable = lua_toboolean(L, 3); + OgreAssert(object_e.has(), + "Not a character"); + PhysicsModule::controlPhysics(object_e, enable); + object_e.add(); return 0; - } else if (command == "is-player") { + } else if (command == "is-player") { OgreAssert(lua_gettop(L) == 2, "Bad parameters"); - luaL_checktype(L, 2, LUA_TNUMBER); // object - int object = lua_tointeger(L, 2); + luaL_checktype(L, 2, LUA_TNUMBER); // object + int object = lua_tointeger(L, 2); flecs::entity object_e = idmap.get_entity(object); - lua_pushboolean(L, object_e.has()); - return 1; - } else if (command == "set-actuator") { - OgreAssert(lua_gettop(L) == 3, "Bad parameters"); - luaL_checktype(L, 2, LUA_TNUMBER); // object - luaL_checktype(L, 3, LUA_TSTRING); // animation - Ogre::String animation = lua_tostring(L, 3); + lua_pushboolean(L, object_e.has()); + return 1; + } else if (command == "set-actuator") { + OgreAssert(lua_gettop(L) == 3, "Bad parameters"); + luaL_checktype(L, 2, LUA_TNUMBER); // object + luaL_checktype(L, 3, LUA_TSTRING); // animation + Ogre::String animation = lua_tostring(L, 3); - int object = lua_tointeger(L, 2); - flecs::entity object_e = idmap.get_entity(object); - object_e.set( - { { 0, 0, 0 }, { 0, 0, 0 } }); - if (animation.length() > 0) - object_e.set( - { animation, { 0, 0, 0 } }); - else - object_e.remove(); + int object = lua_tointeger(L, 2); + flecs::entity object_e = idmap.get_entity(object); + object_e.set( + { { 0, 0, 0 }, { 0, 0, 0 } }); + if (animation.length() > 0) + object_e.set( + { animation, { 0, 0, 0 } }); + else + object_e.remove(); return 0; - } else if (command == "animation-state") { - OgreAssert(lua_gettop(L) >= 4, "Bad parameters"); - luaL_checktype(L, 2, LUA_TNUMBER); // object - luaL_checktype(L, 3, LUA_TSTRING); // node - luaL_checktype(L, 4, LUA_TSTRING); // state - if (lua_gettop(L) == 5) - luaL_checktype(L, 5, LUA_TBOOLEAN); // reset - int object = lua_tointeger(L, 2); - flecs::entity object_e = idmap.get_entity(object); - Ogre::String nodeName = lua_tostring(L, 3); - Ogre::String stateName = lua_tostring(L, 4); + } else if (command == "animation-state") { + OgreAssert(lua_gettop(L) >= 4, "Bad parameters"); + luaL_checktype(L, 2, LUA_TNUMBER); // object + luaL_checktype(L, 3, LUA_TSTRING); // node + luaL_checktype(L, 4, LUA_TSTRING); // state + if (lua_gettop(L) == 5) + luaL_checktype(L, 5, + LUA_TBOOLEAN); // reset + int object = lua_tointeger(L, 2); + flecs::entity object_e = idmap.get_entity(object); + Ogre::String nodeName = lua_tostring(L, 3); + Ogre::String stateName = lua_tostring(L, 4); #if 0 bool reset = false; if (lua_gettop(L) == 5) @@ -622,183 +625,183 @@ LuaData::LuaData() #endif OgreAssert(false, "Not implemented"); - return 0; - } else if (command == "params-set") { - OgreAssert(lua_gettop(L) == 4, "Invalid parameters"); - luaL_checktype(L, 2, LUA_TNUMBER); - luaL_checktype(L, 3, LUA_TSTRING); - luaL_checktype(L, 4, LUA_TBOOLEAN); - bool enable = lua_toboolean(L, 4); - flecs::entity e = idmap.get_entity(lua_tointeger(L, 2)); - Ogre::String what = lua_tostring(L, 3); - OgreAssert(e.is_valid(), "Invalid character"); - OgreAssert(e.has(), "Not a character"); - if (what == "gravity") { - /* clear momentum */ - if (e.has()) { - e.get_mut() - .gvelocity.y = 0.0f; - e.get_mut() - .velocity.y = 0.0f; - e.modified(); - } - if (enable) - e.add(); - else - e.remove(); - } else if (what == "buoyancy") { - if (enable) - e.add(); - else - e.remove(); - } else - OgreAssert(false, "Bad parameter " + what); - return 0; - } else { - OgreAssert(false, "bad argument " + command); - return 0; - } - }); - lua_setglobal(L, "ecs_character"); - lua_pushcfunction(L, [](lua_State *L) -> int { - OgreAssert(lua_gettop(L) == 3, "Bad parameters"); - luaL_checktype(L, 1, LUA_TNUMBER); // parent - luaL_checktype(L, 2, LUA_TNUMBER); // object - luaL_checktype(L, 3, LUA_TSTRING); // slot - int parent = lua_tointeger(L, 1); - int object = lua_tointeger(L, 2); - Ogre::String slot = lua_tostring(L, 3); - flecs::entity parent_e = idmap.get_entity(parent); - flecs::entity object_e = idmap.get_entity(object); - PhysicsModule::controlPhysics(object_e, false); - object_e.set({ parent_e, slot }); - return 0; - }); - lua_setglobal(L, "ecs_set_slot"); - lua_pushcfunction(L, [](lua_State *L) -> int { - flecs::entity e = ECS::get().lookup("player"); - int result = idmap.add_entity(e); - lua_pushinteger(L, result); - return result; - }); - lua_setglobal(L, "ecs_get_player_entity"); - lua_pushcfunction(L, [](lua_State *L) -> int { - luaL_checktype(L, 1, LUA_TNUMBER); // entity id - int id = lua_tointeger(L, 1); - flecs::entity e = idmap.get_entity(id); - LuaEcsEntity *edata = static_cast( - lua_newuserdata(L, sizeof(LuaEcsEntity))); - new (edata) LuaEcsEntity(); - edata->e = e; - edata->id = e; - luaL_getmetatable(L, "FlecsEntityMetaTable"); - lua_setmetatable(L, -2); - return 1; - }); - lua_setglobal(L, "ecs_get_entity"); - luaL_newmetatable(L, "FlecsEntityMetaTable"); - lua_pushstring(L, "__index"); - lua_pushcfunction(L, [](lua_State *L) -> int { - luaL_checktype(L, 1, LUA_TUSERDATA); //metatable - luaL_checktype(L, 2, LUA_TSTRING); //function - Ogre::String component = lua_tostring(L, 2); - if (component == "components") { - lua_pushvalue(L, 1); - lua_pushcclosure( - L, - [](lua_State *L) -> int { - luaL_checktype(L, lua_upvalueindex(1), - LUA_TUSERDATA); - LuaEcsEntity *ent = static_cast< - LuaEcsEntity *>(lua_touserdata( - L, lua_upvalueindex(1))); - std::cout << ent->id << " called!!!\n"; - ent->e.each([&](flecs::id id) { - flecs::entity cmp = - ECS::get().entity(id); - if (cmp.is_alive()) { - const char *name = - cmp.name(); - if (name) - std::cout - << "component: " - << name - << std::endl; - } - }); - return 0; - }, - 1); - } else if (component == "is_trigger") { - lua_pushvalue(L, 1); - lua_pushcclosure( - L, - [](lua_State *L) -> int { - luaL_checktype(L, lua_upvalueindex(1), - LUA_TUSERDATA); - LuaEcsEntity *ent = static_cast< - LuaEcsEntity *>(lua_touserdata( - L, lua_upvalueindex(1))); - lua_pushboolean( - L, ent->e.has()); - return 1; - }, - 1); - } else if (component == "is_character") { - lua_pushvalue(L, 1); - lua_pushcclosure( - L, - [](lua_State *L) -> int { - luaL_checktype(L, lua_upvalueindex(1), - LUA_TUSERDATA); - LuaEcsEntity *ent = static_cast< - LuaEcsEntity *>(lua_touserdata( - L, lua_upvalueindex(1))); - lua_pushboolean( - L, ent->e.has()); - return 1; - }, - 1); - } else if (component == "is_boat") { - lua_pushvalue(L, 1); - lua_pushcclosure( - L, - [](lua_State *L) -> int { - luaL_checktype(L, lua_upvalueindex(1), - LUA_TUSERDATA); - LuaEcsEntity *ent = static_cast< - LuaEcsEntity *>(lua_touserdata( - L, lua_upvalueindex(1))); - lua_pushboolean(L, + return 0; + } else if (command == "params-set") { + OgreAssert(lua_gettop(L) == 4, "Invalid parameters"); + luaL_checktype(L, 2, LUA_TNUMBER); + luaL_checktype(L, 3, LUA_TSTRING); + luaL_checktype(L, 4, LUA_TBOOLEAN); + bool enable = lua_toboolean(L, 4); + flecs::entity e = idmap.get_entity(lua_tointeger(L, 2)); + Ogre::String what = lua_tostring(L, 3); + OgreAssert(e.is_valid(), "Invalid character"); + OgreAssert(e.has(), "Not a character"); + if (what == "gravity") { + /* clear momentum */ + if (e.has()) { + e.get_mut() + .gvelocity.y = 0.0f; + e.get_mut() + .velocity.y = 0.0f; + e.modified(); + } + if (enable) + e.add(); + else + e.remove(); + } else if (what == "buoyancy") { + if (enable) + e.add(); + else + e.remove(); + } else + OgreAssert(false, "Bad parameter " + what); + return 0; + } else { + OgreAssert(false, "bad argument " + command); + return 0; + } + }); + lua_setglobal(L, "ecs_character"); + lua_pushcfunction(L, [](lua_State *L) -> int { + OgreAssert(lua_gettop(L) == 3, "Bad parameters"); + luaL_checktype(L, 1, LUA_TNUMBER); // parent + luaL_checktype(L, 2, LUA_TNUMBER); // object + luaL_checktype(L, 3, LUA_TSTRING); // slot + int parent = lua_tointeger(L, 1); + int object = lua_tointeger(L, 2); + Ogre::String slot = lua_tostring(L, 3); + flecs::entity parent_e = idmap.get_entity(parent); + flecs::entity object_e = idmap.get_entity(object); + PhysicsModule::controlPhysics(object_e, false); + object_e.set({ parent_e, slot }); + return 0; + }); + lua_setglobal(L, "ecs_set_slot"); + lua_pushcfunction(L, [](lua_State *L) -> int { + flecs::entity e = ECS::get().lookup("player"); + int result = idmap.add_entity(e); + lua_pushinteger(L, result); + return result; + }); + lua_setglobal(L, "ecs_get_player_entity"); + lua_pushcfunction(L, [](lua_State *L) -> int { + luaL_checktype(L, 1, LUA_TNUMBER); // entity id + int id = lua_tointeger(L, 1); + flecs::entity e = idmap.get_entity(id); + LuaEcsEntity *edata = static_cast( + lua_newuserdata(L, sizeof(LuaEcsEntity))); + new (edata) LuaEcsEntity(); + edata->e = e; + edata->id = e; + luaL_getmetatable(L, "FlecsEntityMetaTable"); + lua_setmetatable(L, -2); + return 1; + }); + lua_setglobal(L, "ecs_get_entity"); + luaL_newmetatable(L, "FlecsEntityMetaTable"); + lua_pushstring(L, "__index"); + lua_pushcfunction(L, [](lua_State *L) -> int { + luaL_checktype(L, 1, LUA_TUSERDATA); //metatable + luaL_checktype(L, 2, LUA_TSTRING); //function + Ogre::String component = lua_tostring(L, 2); + if (component == "components") { + lua_pushvalue(L, 1); + lua_pushcclosure( + L, + [](lua_State *L) -> int { + luaL_checktype(L, lua_upvalueindex(1), + LUA_TUSERDATA); + LuaEcsEntity *ent = static_cast< + LuaEcsEntity *>(lua_touserdata( + L, lua_upvalueindex(1))); + std::cout << ent->id << " called!!!\n"; + ent->e.each([&](flecs::id id) { + flecs::entity cmp = + ECS::get().entity(id); + if (cmp.is_alive()) { + const char *name = + cmp.name(); + if (name) + std::cout + << "component: " + << name + << std::endl; + } + }); + return 0; + }, + 1); + } else if (component == "is_trigger") { + lua_pushvalue(L, 1); + lua_pushcclosure( + L, + [](lua_State *L) -> int { + luaL_checktype(L, lua_upvalueindex(1), + LUA_TUSERDATA); + LuaEcsEntity *ent = static_cast< + LuaEcsEntity *>(lua_touserdata( + L, lua_upvalueindex(1))); + lua_pushboolean( + L, ent->e.has()); + return 1; + }, + 1); + } else if (component == "is_character") { + lua_pushvalue(L, 1); + lua_pushcclosure( + L, + [](lua_State *L) -> int { + luaL_checktype(L, lua_upvalueindex(1), + LUA_TUSERDATA); + LuaEcsEntity *ent = static_cast< + LuaEcsEntity *>(lua_touserdata( + L, lua_upvalueindex(1))); + lua_pushboolean( + L, ent->e.has()); + return 1; + }, + 1); + } else if (component == "is_boat") { + lua_pushvalue(L, 1); + lua_pushcclosure( + L, + [](lua_State *L) -> int { + luaL_checktype(L, lua_upvalueindex(1), + LUA_TUSERDATA); + LuaEcsEntity *ent = static_cast< + LuaEcsEntity *>(lua_touserdata( + L, lua_upvalueindex(1))); + lua_pushboolean(L, ent->e.has()); - return 1; - }, - 1); - } else if (component == "is_player") { - lua_pushvalue(L, 1); - lua_pushcclosure( - L, - [](lua_State *L) -> int { - luaL_checktype(L, lua_upvalueindex(1), - LUA_TUSERDATA); - LuaEcsEntity *ent = static_cast< - LuaEcsEntity *>(lua_touserdata( - L, lua_upvalueindex(1))); - lua_pushboolean(L, + return 1; + }, + 1); + } else if (component == "is_player") { + lua_pushvalue(L, 1); + lua_pushcclosure( + L, + [](lua_State *L) -> int { + luaL_checktype(L, lua_upvalueindex(1), + LUA_TUSERDATA); + LuaEcsEntity *ent = static_cast< + LuaEcsEntity *>(lua_touserdata( + L, lua_upvalueindex(1))); + lua_pushboolean(L, ent->e.has()); - return 1; - }, - 1); - } else - lua_pushnil(L); - return 1; - }); - lua_settable(L, -3); + return 1; + }, + 1); + } else + lua_pushnil(L); + return 1; + }); + lua_settable(L, -3); } LuaData::~LuaData() { - lua_close(L); + lua_close(L); } void LuaData::lateSetup() @@ -817,96 +820,95 @@ void LuaData::lateSetup() } } #endif - Ogre::DataStreamPtr stream = - Ogre::ResourceGroupManager::getSingleton().openResource( - "data.lua", "LuaScripts"); - std::cout << "stream: " << stream->getAsString() << "\n"; - if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) { - std::cout << "error: " << lua_tostring(L, -1) << "\n"; - OgreAssert(false, "Script failure"); - } + Ogre::DataStreamPtr stream = + Ogre::ResourceGroupManager::getSingleton().openResource( + "data.lua", "LuaScripts"); + std::cout << "stream: " << stream->getAsString() << "\n"; + if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) { + std::cout << "error: " << lua_tostring(L, -1) << "\n"; + OgreAssert(false, "Script failure"); + } - const char *lua_code = "\n\ + const char *lua_code = "\n\ function stuff()\n\ return 4\n\ end\n\ x = stuff()\n\ "; - luaL_dostring(L, lua_code); - lua_getglobal(L, "x"); - int x = lua_tonumber(L, 1); - std::cout << "lua: " << x << "\n"; + luaL_dostring(L, lua_code); + lua_getglobal(L, "x"); + int x = lua_tonumber(L, 1); + std::cout << "lua: " << x << "\n"; } LuaModule::LuaModule(flecs::world &ecs) { - ecs.module(); - ecs.import (); - ecs.import (); + ecs.module(); + ecs.import (); + ecs.import (); ecs.import (); - ecs.component(); - ecs.component() - .on_add([](LuaBase &lua) { - lua.mLua = new LuaData; - lua.setup_called = false; - lua.startup_called = false; - }) - .add(flecs::Singleton); - ecs.component().add(flecs::Singleton); - ecs.system("LuaUpdate") - .kind(flecs::OnUpdate) - .each([](const EngineData &eng, LuaBase &lua) { - if (!lua.setup_called) { - lua.mLua->lateSetup(); - lua.mLua->call_handler("setup"); - lua.setup_called = true; - } - if (!lua.startup_called) { - if (eng.startupDelay <= 0.0f) { - lua.mLua->call_handler("startup"); - lua.startup_called = true; + ecs.component(); + ecs.component() + .on_add([](LuaBase &lua) { + lua.mLua = new LuaData; + lua.setup_called = false; + lua.startup_called = false; + }) + .add(flecs::Singleton); + ecs.component().add(flecs::Singleton); + ecs.system("LuaUpdate") + .kind(flecs::OnUpdate) + .each([](const EngineData &eng, LuaBase &lua) { + if (!lua.setup_called) { + lua.mLua->lateSetup(); + lua.mLua->call_handler("setup"); + lua.setup_called = true; + } + if (!lua.startup_called) { + if (eng.startupDelay <= 0.0f) { + lua.mLua->call_handler("startup"); + lua.startup_called = true; } - } - }); - ecs.system( - "CreateChildTrigger") - .kind(flecs::OnUpdate) - .without() - .write() - .each([](flecs::entity e, const EngineData &env, - const LuaChildEventTrigger &lct) { - Ogre::SceneNode *parentNode = nullptr; - flecs::entity parent_e = lct.parent_e; - if (parent_e.has()) { - parentNode = - parent_e.get().mBodyNode; - OgreAssert( - parent_e.get().mBodyNode, - "bad node"); + } + }); + ecs.system( + "CreateChildTrigger") + .kind(flecs::OnUpdate) + .without() + .write() + .each([](flecs::entity e, const EngineData &env, + const LuaChildEventTrigger &lct) { + Ogre::SceneNode *parentNode = nullptr; + flecs::entity parent_e = lct.parent_e; + if (parent_e.has()) { + parentNode = + ECS::get() + .characterNodes.at(parent_e); + OgreAssert(parentNode, "bad node"); - } else if (parent_e.has()) { - parentNode = parent_e.get().mNode; - OgreAssert(parent_e.get().mNode, - "bad node"); - } else - return; - EventTrigger &trigger = e.ensure(); - OgreAssert(parentNode, "bad parent"); - trigger.position = lct.position; - trigger.halfheight = lct.halfheight; - trigger.radius = lct.radius; - trigger.event = lct.event; - trigger.parent = parentNode; - e.modified(); - }); - ecs.system("HandleLuaEvents") - .kind(flecs::OnUpdate) - .each([](LuaBase &base, LuaEvent &evt) { - while (!evt.events.empty()) { - LuaEvent::Event &ev = evt.events.front(); - base.mLua->call_handler(ev.event, ev.e1, ev.e2); - evt.events.pop_front(); - } - }); + } else if (parent_e.has()) { + parentNode = parent_e.get().mNode; + OgreAssert(parent_e.get().mNode, + "bad node"); + } else + return; + EventTrigger &trigger = e.ensure(); + OgreAssert(parentNode, "bad parent"); + trigger.position = lct.position; + trigger.halfheight = lct.halfheight; + trigger.radius = lct.radius; + trigger.event = lct.event; + trigger.parent = parentNode; + e.modified(); + }); + ecs.system("HandleLuaEvents") + .kind(flecs::OnUpdate) + .each([](LuaBase &base, LuaEvent &evt) { + while (!evt.events.empty()) { + LuaEvent::Event &ev = evt.events.front(); + base.mLua->call_handler(ev.event, ev.e1, ev.e2); + evt.events.pop_front(); + } + }); } } diff --git a/src/gamedata/PhysicsModule.cpp b/src/gamedata/PhysicsModule.cpp index 3cc1724..8be9fd9 100644 --- a/src/gamedata/PhysicsModule.cpp +++ b/src/gamedata/PhysicsModule.cpp @@ -187,9 +187,11 @@ PhysicsModule::PhysicsModule(flecs::world &ecs) const CharacterBase &base) { ZoneScopedN("SetupCharacterPh"); CharacterBody &b = e.ensure(); + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); b.ch.reset(JoltPhysicsWrapper::getSingleton() - .createCharacter(base.mBodyNode, - 1.75f, 0.23f)); + .createCharacter(n, 1.75f, 0.23f)); if (!e.has()) static_cast(b.ch.get()) ->AddToPhysicsSystem( @@ -534,8 +536,11 @@ PhysicsModule::PhysicsModule(flecs::world &ecs) const CharacterBase &chbase, const CharacterBody &body, CharacterVelocity &gr) { ZoneScopedN("HandleVelocity"); + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); if (e.has() && - chbase.mBodyNode->_getDerivedPosition().y > -0.5f) + n->_getDerivedPosition().y > -0.5f) e.remove(); Ogre::Vector3 v = gr.velocity; v.y = 0.0f; @@ -572,7 +577,10 @@ PhysicsModule::PhysicsModule(flecs::world &ecs) .each([this](flecs::entity e, CharacterBase &ch) { ZoneScopedNC("HandleSubmerge", 0xFF3030); float full_subm = 2.0f; - Ogre::Vector3 pos = ch.mBodyNode->getPosition(); + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); + Ogre::Vector3 pos = n->getPosition(); float current_subm = -Ogre::Math::Clamp( pos.y + Ogre::Math::Sin(ch.mTimer * 0.13f + 130.0f) * @@ -595,23 +603,23 @@ PhysicsModule::PhysicsModule(flecs::world &ecs) CharacterBase &ch, const CharacterBody &body, CharacterVelocity &gr) { ZoneScopedNC("HandleVelocityNoPhysics", 0xFF4040); + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); Ogre::Vector3 v = gr.velocity; // v.y = 0.0f; - ch.mBodyNode->_setDerivedPosition( - ch.mBodyNode->_getDerivedPosition() + - v * eng.delta); + n->_setDerivedPosition(n->_getDerivedPosition() + + v * eng.delta); gr.velocity = Ogre::Vector3::ZERO; if (e.has()) JoltPhysicsWrapper::getSingleton() .setPositionAndRotation( e.get(), - ch.mBodyNode - ->_getDerivedPosition(), - ch.mBodyNode - ->_getDerivedOrientation(), + n->_getDerivedPosition(), + n->_getDerivedOrientation(), false); if (e.has() && - ch.mBodyNode->_getDerivedPosition().y > -0.5f) { + n->_getDerivedPosition().y > -0.5f) { e.remove(); ch.is_submerged = false; ZoneTextF("remove in water"); @@ -646,26 +654,27 @@ void PhysicsModule::controlPhysics(flecs::entity e, bool enable) OgreAssert(e.has(), "No body component"); OgreAssert(e.has(), "No body id in entity"); - } - if (!JoltPhysicsWrapper::getSingleton().isAdded( - e.get())) { - Ogre::Vector3 position = - e.get() - .mBodyNode->_getDerivedPosition(); - Ogre::Quaternion orientation = - e.get() - .mBodyNode->_getDerivedOrientation(); - if (position.y >= -0.5f) - e.remove(); - JoltPhysicsWrapper::getSingleton() - .setPositionAndRotation(e.get(), - position, orientation, - false); - JoltPhysicsWrapper::getSingleton().addBody( - e.get(), - JPH::EActivation::Activate); - } - } else { + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); + if (!JoltPhysicsWrapper::getSingleton().isAdded( + e.get())) { + Ogre::Vector3 position = + n->_getDerivedPosition(); + Ogre::Quaternion orientation = + n->_getDerivedOrientation(); + if (position.y >= -0.5f) + e.remove(); + JoltPhysicsWrapper::getSingleton() + .setPositionAndRotation( + e.get(), position, + orientation, false); + JoltPhysicsWrapper::getSingleton().addBody( + e.get(), + JPH::EActivation::Activate); + } + } + } else { if (e.has()) { e.add(); if (!e.has()) @@ -673,14 +682,16 @@ void PhysicsModule::controlPhysics(flecs::entity e, bool enable) OgreAssert(e.has(), "No body component"); OgreAssert(e.has(), "No body id in entity"); - } - if (JoltPhysicsWrapper::getSingleton().isAdded( - e.get())) - JoltPhysicsWrapper::getSingleton().removeBody( - e.get()); - Ogre::Vector3 position = - e.get().mBodyNode->_getDerivedPosition(); - e.remove(); + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + e); + if (JoltPhysicsWrapper::getSingleton().isAdded( + e.get())) + JoltPhysicsWrapper::getSingleton().removeBody( + e.get()); + Ogre::Vector3 position = n->_getDerivedPosition(); + e.remove(); + } } } bool PhysicsModule::raycastQuery(const Ogre::Vector3 &startPos, diff --git a/src/gamedata/PlayerActionModule.cpp b/src/gamedata/PlayerActionModule.cpp index e8cf6f5..51f0da9 100644 --- a/src/gamedata/PlayerActionModule.cpp +++ b/src/gamedata/PlayerActionModule.cpp @@ -318,10 +318,12 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs) ECS::get() .getPlayer(); if (player.is_valid()) { + Ogre::SceneNode *n = + ECS::get() + .characterNodes.at( + player); Ogre::Vector3 playerPos = - player.get() - .mBodyNode - ->_getDerivedPosition(); + n->_getDerivedPosition(); list.UIquery(playerPos); } else { list.UIquery(cameraPos); @@ -479,10 +481,11 @@ out:; anode.position + anode.rotation * placeLocalOffset[place]; if (ch.is_valid() && ch.has()) { - ch.get() - .mBodyNode->_setDerivedOrientation(newRotation); - ch.get().mBodyNode->_setDerivedPosition( - newPosition); + Ogre::SceneNode *n = + ECS::get().characterNodes.at( + ch); + n->_setDerivedOrientation(newRotation); + n->_setDerivedPosition(newPosition); } if (actor >= 0) { town.get_mut().npcs[actor].position = diff --git a/src/gamedata/SlotsModule.cpp b/src/gamedata/SlotsModule.cpp index 2cf8548..76e8a89 100644 --- a/src/gamedata/SlotsModule.cpp +++ b/src/gamedata/SlotsModule.cpp @@ -45,7 +45,10 @@ SlotsModule::SlotsModule(flecs::world &ecs) slot.removeSlot(e); return; } - slot.addChild(ch.mBodyNode); + Ogre::SceneNode *n = + ECS::get() + .characterNodes.at(e); + slot.addChild(n); slot.createSlotData(e); std::cout << "base: " << slot.getSlotBase()->getName();