Gap filling, improvements for character pipeline

This commit is contained in:
2026-05-25 01:42:28 +03:00
parent c7ef9283cd
commit eea50adfcb
10 changed files with 507 additions and 26 deletions
+28 -2
View File
@@ -425,9 +425,35 @@ weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-top.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-bottom.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-feet.blend)
# Propagate missing shape keys (like "fat") from Body_shapes to base body parts.
# This ensures all body part meshes have consistent shape keys, preventing seams.
function(add_shape_key_propagation INPUT_BLEND OUTPUT_BLEND)
add_custom_command(
OUTPUT ${OUTPUT_BLEND}
DEPENDS ${INPUT_BLEND}
${CMAKE_CURRENT_SOURCE_DIR}/add_missing_shape_keys.py
${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
COMMAND ${CMAKE_COMMAND} -E copy ${INPUT_BLEND} ${OUTPUT_BLEND}
COMMAND ${BLENDER} -b -Y ${OUTPUT_BLEND}
-P ${CMAKE_CURRENT_SOURCE_DIR}/add_missing_shape_keys.py --
${OUTPUT_BLEND} ${OUTPUT_BLEND}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endfunction()
add_shape_key_propagation(
${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-shapes.blend
)
add_shape_key_propagation(
${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-female.blend
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-shapes.blend
)
# male
add_clothes_pipeline(
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-shapes.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-male-bottom_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated-bottom.blend" # BOTTOM_OUTPUT_BLEND
)
@@ -446,7 +472,7 @@ add_clothes_pipeline(
# female
add_clothes_pipeline(
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-female.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-shapes.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-female-bottom_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated-bottom.blend" # FINAL_OUTPUT_BLEND
)
@@ -0,0 +1,139 @@
"""
Add missing shape keys from Body_shapes to body part objects.
Preserves existing shape keys.
Usage: blender -b --python add_missing_shape_keys.py -- <blend_file> <output_file>
"""
import bpy
import sys
import os
import mathutils
from mathutils.bvhtree import BVHTree
# Import functions from transfer_shape_keys.py
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from transfer_shape_keys import (
compute_robust_surface_mapping,
compute_side_preserving_position,
smooth_boundary_areas,
smooth_penetration_areas,
create_bvh_for_source,
load_source_data,
fix_seams_across_objects
)
def get_source_object_name(target_obj_info):
if target_obj_info.get('ref_shapes'):
return target_obj_info['ref_shapes']
return None
def add_missing_shape_keys(source_data, target_obj):
"""Add only missing shape keys to target object, preserving existing ones."""
if not target_obj.data.shape_keys:
target_obj.shape_key_add(name='Basis', from_mix=False)
existing_names = {kb.name for kb in target_obj.data.shape_keys.key_blocks}
missing_names = [n for n in source_data['names'] if n != 'Basis' and n not in existing_names]
if not missing_names:
print(f" {target_obj.name}: all shape keys present, skipping")
return
print(f" {target_obj.name}: adding missing keys: {missing_names}")
# Create missing shape keys
for sk_name in missing_names:
target_obj.shape_key_add(name=sk_name, from_mix=False)
target_obj.data.shape_keys.use_relative = True
# Compute mapping
mapping = compute_robust_surface_mapping(target_obj, source_data)
# Set values for missing shape keys
for sk_name in missing_names:
print(f" Setting {sk_name}...")
sk = target_obj.data.shape_keys.key_blocks[sk_name]
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, 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()
smooth_boundary_areas(target_obj, sk_name, mapping, source_data)
smooth_penetration_areas(target_obj, sk_name, mapping, source_data)
print(f" ✓ Added {sk_name}")
def process_blend(blend_path, output_path):
print(f"Processing: {blend_path}")
bpy.ops.wm.open_mainfile(filepath=blend_path)
# Find source object
source_obj = None
for name in ['Body_shapes', 'Body']:
if name in bpy.data.objects and bpy.data.objects[name].type == 'MESH':
obj = bpy.data.objects[name]
if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) > 1:
source_obj = obj
print(f"Found source: {name}")
break
if not source_obj:
print("ERROR: No source object with shape keys found")
return False
# Build source data
source_data = {
'names': [kb.name for kb in source_obj.data.shape_keys.key_blocks],
'vertex_positions': {},
'polygons': [],
'is_relative': source_obj.data.shape_keys.use_relative,
'source_obj_name': source_obj.name
}
for poly in source_obj.data.polygons:
source_data['polygons'].append([v for v in poly.vertices])
for sk in source_obj.data.shape_keys.key_blocks:
source_data['vertex_positions'][sk.name] = [(v.co.x, v.co.y, v.co.z) for v in sk.data]
print(f"Source shape keys: {source_data['names']}")
# Process body part objects
required_props = {'age', 'sex', 'slot'}
modified = False
for obj in bpy.data.objects:
if obj.type == 'MESH' and all(p in obj for p in required_props):
if obj == source_obj:
continue
add_missing_shape_keys(source_data, obj)
modified = True
# Fix seams: synchronize shape key offsets for vertices sharing
# the same basis position across body parts.
fix_seams_across_objects()
bpy.ops.wm.save_as_mainfile(filepath=output_path)
print(f"Saved: {output_path}")
return True
if __name__ == "__main__":
try:
args = sys.argv[sys.argv.index("--") + 1:]
if len(args) >= 2:
process_blend(args[0], args[1])
else:
print("Usage: blender -b -P script.py -- <input.blend> <output.blend>")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
+5 -2
View File
@@ -66,8 +66,11 @@ def raycast_and_adjust_vertices(target_body, bvh_cloth, out_ray_length=0.15):
new_co = m_body_inv @ (v_world + offset)
v.co = new_co
# Update shape keys if they exist
if has_shape_keys:
# Update shape keys if they exist.
# In relative mode, Blender automatically uses v.co as basis,
# so shape key offsets don't need updating. Only update if
# the shape keys are in absolute mode (which we don't use).
if has_shape_keys and not target_body.data.shape_keys.use_relative:
for kb in target_body.data.shape_keys.key_blocks:
kb.data[i].co = new_co
@@ -0,0 +1,91 @@
import bpy
import sys
import os
def propagate_shape_keys(blend_path, output_path):
"""Copy missing shape keys from Body_shapes to all body part objects."""
print(f"Propagating shape keys from {blend_path} to {output_path}")
bpy.ops.wm.open_mainfile(filepath=blend_path)
# Find source object with all shape keys
source_obj = None
for name in ['Body_shapes', 'Body']:
if name in bpy.data.objects and bpy.data.objects[name].type == 'MESH':
obj = bpy.data.objects[name]
if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) > 1:
source_obj = obj
print(f"Found source object: {name}")
break
if not source_obj:
print("ERROR: No source object with shape keys found")
return False
source_keys = source_obj.data.shape_keys.key_blocks
source_basis = source_keys['Basis'].data
modified = False
for obj in bpy.data.objects:
if obj.type != 'MESH':
continue
# Only process body part objects
if not all(p in obj for p in ['age', 'sex', 'slot']):
continue
# Skip the source object itself
if obj == source_obj:
continue
if not obj.data.shape_keys:
# Create basis if missing
obj.shape_key_add(name='Basis', from_mix=False)
existing_names = {kb.name for kb in obj.data.shape_keys.key_blocks}
for sk in source_keys:
if sk.name == 'Basis':
continue
if sk.name in existing_names:
print(f" {obj.name}: already has '{sk.name}', skipping")
continue
# Copy shape key
print(f" {obj.name}: adding '{sk.name}' from {source_obj.name}")
new_sk = obj.shape_key_add(name=sk.name, from_mix=False)
# Transfer vertex positions relative to basis
# We need to map source vertices to target vertices
# For now, assume same topology (body parts were separated from same mesh)
num_verts = min(len(new_sk.data), len(sk.data), len(source_basis))
for i in range(num_verts):
# The shape key position in source
src_pos = sk.data[i].co
src_basis_pos = source_basis[i].co
# Offset from basis
offset = src_pos - src_basis_pos
# Apply to target using target's basis position
new_sk.data[i].co = obj.data.shape_keys.key_blocks['Basis'].data[i].co + offset
new_sk.data.update()
modified = True
if modified:
obj.data.update_tag()
bpy.context.view_layer.update()
bpy.ops.wm.save_as_mainfile(filepath=output_path)
print(f"Saved: {output_path}")
return True
if __name__ == "__main__":
try:
args = sys.argv[sys.argv.index("--") + 1:]
if len(args) >= 2:
propagate_shape_keys(args[0], args[1])
else:
print("Usage: blender -b -P script.py -- <input.blend> <output.blend>")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
+123 -11
View File
@@ -243,12 +243,16 @@ def compute_signed_distance_and_direction(bvh, point, reference_normal=None):
if location is None:
return None, None, None, None
# Determine sign using reference normal
# Determine sign using reference normal.
# For points outside (in the direction of the normal), signed_distance
# must be positive. For points inside, negative.
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
# Inverted relative to the no-reference-normal branch because
# to_surface here points *toward* the surface, not away.
signed_distance = -distance if dot > 0 else distance
else:
signed_distance = 0
else:
@@ -743,15 +747,21 @@ def smooth_penetration_areas(target_obj, sk_name, mapping, source_data, threshol
problem_vertices = set()
bvh_deformed, _ = create_bvh_for_source(source_data, sk_name)
# Shape keys are in relative mode here, so current_positions are offsets.
# The BVH is built from absolute source positions, so we must convert
# relative offsets to absolute positions before querying it.
basis_positions = mapping['target_verts']
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 target_side != 0 and i < len(basis_positions):
abs_pos = basis_positions[i] + pos
location, _, _, _ = bvh_deformed.find_nearest(abs_pos)
if location and i < len(mapping['direction_vectors']):
to_surface = pos - location
to_surface = abs_pos - location
current_side = 1 if to_surface.dot(mapping['direction_vectors'][i]) > 0 else -1
if current_side != target_side:
@@ -1009,16 +1019,109 @@ def process_target_object(target_obj_info, source_data, current_file_path):
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 get_body_part_objects():
"""Find all mesh objects that are body parts (have age/sex/slot)"""
result = []
required_props = {'age', 'sex', 'slot'}
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.data and all(p in obj for p in required_props):
result.append(obj)
return result
def round_pos(co, tolerance=0.0001):
"""Round a coordinate to tolerance-based buckets for spatial hashing"""
return (round(co.x / tolerance),
round(co.y / tolerance),
round(co.z / tolerance))
def fix_seams_across_objects(tolerance=0.0001):
"""
Post-process shape keys to ensure vertices sharing the same basis
position (within tolerance) get identical offsets. This fixes seams
between body parts and UV seams within the same part.
"""
body_parts = get_body_part_objects()
if not body_parts:
print("No body part objects found for seam fixing")
return
print(f"\n{'=' * 60}")
print("SEAM FIX: Synchronizing shape key offsets across body parts")
print(f"{'=' * 60}")
print(f"Found {len(body_parts)} body part objects")
# Collect shape key names from first object that has them
shape_key_names = []
for obj in body_parts:
if obj.data.shape_keys and obj.data.shape_keys.key_blocks:
for kb in obj.data.shape_keys.key_blocks:
if kb.name != 'Basis':
shape_key_names.append(kb.name)
break
if not shape_key_names:
print("No shape keys found, skipping seam fix")
return
print(f"Synchronizing {len(shape_key_names)} shape key(s): {', '.join(shape_key_names)}")
fixed_vertices = 0
fixed_groups = 0
for sk_name in shape_key_names:
# Build spatial hash: position_key -> list of (obj, vert_idx, offset)
pos_map = {}
for obj in body_parts:
if not obj.data.shape_keys or sk_name not in obj.data.shape_keys.key_blocks:
continue
sk = obj.data.shape_keys.key_blocks[sk_name]
for i, v in enumerate(obj.data.vertices):
if i >= len(sk.data):
continue
pos_key = round_pos(v.co, tolerance)
offset = sk.data[i].co.copy()
if pos_key not in pos_map:
pos_map[pos_key] = []
pos_map[pos_key].append((obj, i, offset))
# Average offsets for each position group with multiple vertices
for pos_key, entries in pos_map.items():
if len(entries) < 2:
continue
# Compute average offset
avg_offset = mathutils.Vector((0.0, 0.0, 0.0))
for obj, idx, offset in entries:
avg_offset += offset
avg_offset /= len(entries)
# Apply averaged offset to all vertices in this group
for obj, idx, offset in entries:
sk = obj.data.shape_keys.key_blocks[sk_name]
sk.data[idx].co = avg_offset.copy()
fixed_vertices += 1
fixed_groups += 1
# Update all meshes
for obj in body_parts:
if obj.data.shape_keys:
for kb in obj.data.shape_keys.key_blocks:
kb.data.update()
obj.data.update_tag()
bpy.context.view_layer.update()
print(f"Fixed {fixed_vertices} vertices across {fixed_groups} position groups")
print(f"Seam fix complete")
def main():
print("=" * 60)
print("Blender Shape Key Transfer Script - Boundary Velocity Limiting")
@@ -1077,6 +1180,15 @@ def main():
print(f"\nProgress saved to: {temp_progress_file}")
# Post-process: fix seams by synchronizing offsets across body parts
print(f"\nLoading final working file for seam fix...")
bpy.ops.wm.open_mainfile(filepath=working_file)
fix_seams_across_objects()
# Save after seam fix
print(f"\nSaving after seam fix...")
bpy.ops.wm.save_as_mainfile(filepath=working_file)
# Copy the final working file to the output location
print(f"\nCopying final result to: {output_file}")
shutil.copy2(working_file, output_file)
+23 -3
View File
@@ -268,6 +268,17 @@ for mapping in[CommandLineMapping()]:
elif mapping.auto_discover and ob.type == 'MESH' and ob.name not in mapping.objs:
if not ob.name.endswith("-noimp"):
ob.name = ob.name + "-noimp"
# Remove .001 suffixed duplicates that may have leaked from consolidate step
# These are objects that have the same base name as an object in mapping.objs
# but with a .001 suffix (e.g., BodyBottom.001 when BodyBottom is in mapping.objs)
if mapping.auto_discover:
for ob in list(bpy.data.objects):
if ob.type == 'MESH' and ob.name.endswith('.001'):
base_name = ob.name[:-4]
if base_name in mapping.objs:
print(f"Removing duplicate '{ob.name}' (base '{base_name}' already in export list)")
bpy.data.objects.remove(ob, do_unlink=True)
print("Removing original armature and actions...")
orig_arm = bpy.data.objects[mapping.armature_name + '_orig']
@@ -327,9 +338,18 @@ for mapping in[CommandLineMapping()]:
obj = bpy.data.objects.get(name)
if obj and obj.type == 'MESH':
# 1. Rename Mesh Data
if not obj.data.name.startswith(prefix):
obj.data.name = prefix + obj.data.name
# 1. Rename Mesh Data to match object name (not mesh data name)
# This prevents .001 suffixed mesh data names (e.g., BodyBottom.001)
# from becoming male_BodyBottom.001 and clashing with male_BodyBottom.
new_mesh_name = prefix + name
if obj.data.name != new_mesh_name:
# Check if another mesh data already has this name
if new_mesh_name in bpy.data.meshes and bpy.data.meshes[new_mesh_name] != obj.data:
old = bpy.data.meshes[new_mesh_name]
old.name = new_mesh_name + "_old"
print(f"Renamed conflicting mesh data '{old.name}' for object '{name}'")
obj.data.name = new_mesh_name
print(f"Renamed mesh data from '{obj.data.name}' to '{new_mesh_name}' for object '{name}'")
# 2. Iterate through all Material Slots on the object
for slot in obj.material_slots:
+1
View File
@@ -43,6 +43,7 @@ set(EDITSCENE_SOURCES
systems/SaveLoadDialog.cpp
systems/PlayerControllerSystem.cpp
systems/CharacterSlotSystem.cpp
systems/OgreEntityHack.cpp
systems/CharacterRegistry.cpp
systems/MarkovNameGenerator.cpp
systems/PregnancySystem.cpp
@@ -1,5 +1,6 @@
#include "CharacterSlotSystem.hpp"
#include "CharacterRegistry.hpp"
#include "OgreEntityHack.hpp"
#include "../components/Transform.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/CharacterIdentity.hpp"
@@ -540,6 +541,15 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
findCatalogEntry(age, cs.sex, masterSlot, masterMesh);
applyShapeKeys(e, masterEnt, entry);
/* Re-prepare temp buffers after enabling vertex animation.
* OGRE's Entity::_initialise calls prepareTempBlendBuffers()
* before our animation state is enabled. If no skeletal
* animation was active, mSoftwareVertexAnimVertexData was
* never created, causing pose animation to corrupt the
* mesh's shared vertex buffer directly.
*/
prepareEntityTempBlendBuffers(masterEnt);
/* Notify AnimationTreeSystem that entity changed */
if (e.has<AnimationTreeComponent>())
e.get_mut<AnimationTreeComponent>().dirty = true;
@@ -575,6 +585,14 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
const nlohmann::json *entry =
findCatalogEntry(age, cs.sex, slot, mesh);
applyShapeKeys(e, partEnt, entry);
/* Re-prepare temp buffers after skeleton sharing and
* enabling vertex animation. shareSkeletonInstanceWith
* replaces the part's AnimationStateSet but does not
* recreate temp blend buffers, which are needed for
* proper per-entity pose animation.
*/
prepareEntityTempBlendBuffers(partEnt);
} catch (const Ogre::Exception &ex) {
Ogre::LogManager::getSingleton().logMessage(
"[CharacterSlotSystem] buildCharacter: FAILED to load part '" +
@@ -585,7 +603,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e,
}
void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
const nlohmann::json *entry)
const nlohmann::json *entry)
{
if (!ent || !entry)
return;
@@ -600,6 +618,21 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
anim = mesh->getAnimation("ShapeKeys");
} catch (...) {
anim = mesh->createAnimation("ShapeKeys", 1.0f);
}
/* Ensure the animation has at least one pose track.
* We create tracks for ALL vertex data that has poses,
* not just handle 0, to handle meshes with mixed shared/dedicated data.
*/
bool hasTrack = false;
for (unsigned short i = 0; i < anim->getNumVertexTracks(); ++i) {
if (anim->getVertexTrack(i)->getAnimationType() ==
Ogre::VAT_POSE) {
hasTrack = true;
break;
}
}
if (!hasTrack) {
Ogre::VertexAnimationTrack *track =
anim->createVertexTrack(0, Ogre::VAT_POSE);
Ogre::VertexPoseKeyFrame *kf =
@@ -608,9 +641,34 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
kf->addPoseReference(static_cast<ushort>(i), 0.0f);
}
if (!ent->hasAnimationState("ShapeKeys"))
return;
Ogre::AnimationState *as = ent->getAnimationState("ShapeKeys");
/* Ensure the entity has an animation state for ShapeKeys.
* shareSkeletonInstanceWith() replaces the part entity's
* AnimationStateSet with the master's. If the master doesn't
* have ShapeKeys, the part entity won't have it either.
* We work around this by creating the state on the shared
* AnimationStateSet when needed.
*/
Ogre::AnimationState *as = nullptr;
if (ent->hasAnimationState("ShapeKeys")) {
as = ent->getAnimationState("ShapeKeys");
} else {
/* Create the state on the entity's current AnimationStateSet.
* After shareSkeletonInstanceWith(), this is the master's set,
* so all parts sharing the skeleton will see it.
*/
Ogre::AnimationStateSet *stateSet =
ent->getAllAnimationStates();
if (stateSet) {
as = stateSet->createAnimationState(
"ShapeKeys", 0.0, 1.0, 1.0, false);
} else {
Ogre::LogManager::getSingleton().logMessage(
"[CharacterSlotSystem] applyShapeKeys: entity '" +
ent->getName() + "' mesh '" + mesh->getName() +
"' has no AnimationStateSet");
return;
}
}
as->setEnabled(true);
as->setLoop(false);
@@ -636,10 +694,16 @@ void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
continue;
if (it->second >= mesh->getPoseCount())
continue;
/* Update the keyframe's pose reference influence */
Ogre::VertexAnimationTrack *track =
anim->getVertexTrack(0);
if (track) {
/* Update the keyframe's pose reference influence
* on ALL pose tracks in the animation, not just track 0.
*/
for (unsigned short t = 0;
t < anim->getNumVertexTracks(); ++t) {
Ogre::VertexAnimationTrack *track =
anim->getVertexTrack(t);
if (!track || track->getAnimationType() !=
Ogre::VAT_POSE)
continue;
Ogre::VertexPoseKeyFrame *kf =
track->getVertexPoseKeyFrame(0);
if (kf)
@@ -0,0 +1,13 @@
/*
* Workaround: OGRE's Entity::prepareTempBlendBuffers() is private, but we need
* to call it after shareSkeletonInstanceWith() to ensure per-entity temporary
* vertex buffers are created for pose animation.
*/
#define private public
#include <OgreEntity.h>
#undef private
void prepareEntityTempBlendBuffers(Ogre::Entity *ent)
{
ent->prepareTempBlendBuffers();
}
@@ -0,0 +1,12 @@
#ifndef OGRE_ENTITY_HACK_HPP
#define OGRE_ENTITY_HACK_HPP
#include <OgrePrerequisites.h>
namespace Ogre {
class Entity;
}
void prepareEntityTempBlendBuffers(Ogre::Entity *ent);
#endif