The FluidVolume could be used. You can specify per-object behavior with the DensityMultipliers dictionary so that the character floats differently than, say, a crate of similar mass and volume.
The FluidVolume is pretty heavy though. If you just need something really simple and just for the character alone, then making use of the character's consistent geometry could help. You can compute the exact amount of volume beneath the water plane using the plane equation, cylinder height, radius, and position (or you could just simplify the math ever so slightly by considering it to be a box). From there, you can apply a proper buoyancy force according to the density of the 'fluid' and the displaced volume. You'd probably want to damp the character motion a bit too to stop endless bobbing.
On a tangent, why differentiate between windows and Xbox like this? Vector3 is a value type so it shouldn't matter, but I suspect you know something about the 360 JIT compiler I don't...
The former results in a compiler error on the Xbox360. If I remember correctly, it's because the IDE-referenced xbox assemblies are actually stand-ins. Vector3 and a few other struct types have an extra private field in these stand-ins which cannot be initialized without explicitly calling a constructor.
So, this works on the PC, but not the Xbox:
Code: Select all
void Test(out Vector3 v)
{
v.X = 1;
v.Y = 1;
v.Z = 1;
}
The use of an ugly preprocessor directive rather than just using the Xbox360 way for both is a result of compulsive, sometimes misguided, micro-optimizing. Unfortunately, I was just never motivated enough to actually check the disassembly to see if there was any difference at all when default constructor is called explicitly on the PC. But now's a good a time as any to find out!
It's time for an
Adventure in (Dis)assembly!
Everything below will be compiled in release mode with optimizations enabled.
Here's the IL of the above Test method:
Code: Select all
.method private hidebysig instance void Test([out] valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3& v) cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: ldc.r4 1
L_0006: stfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3::X
L_000b: ldarg.1
L_000c: ldc.r4 1
L_0011: stfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3::Y
L_0016: ldarg.1
L_0017: ldc.r4 1
L_001c: stfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3::Z
L_0021: ret
}
Now, we'll modify the Test method so that it can compile on the Xbox360:
Code: Select all
void Test(out Vector3 v)
{
v = new Vector3();
v.X = 1;
v.Y = 1;
v.Z = 1;
}
And here's the disassembly for comparison:
Code: Select all
.method private hidebysig instance void Test([out] valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3& v) cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: initobj [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3
L_0007: ldarg.1
L_0008: ldc.r4 1
L_000d: stfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3::X
L_0012: ldarg.1
L_0013: ldc.r4 1
L_0018: stfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3::Y
L_001d: ldarg.1
L_001e: ldc.r4 1
L_0023: stfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector3::Z
L_0028: ret
}
So, at the IL level, using an explicit empty constructor is not optimized out. The initobj instruction goes through and sets the
default values for fields in the object before they are all set once again. This lack of optimization isn't very surprising; this stage should handle straightforward constant folding and similar activities, but the JIT is left to perform many optimizations.
In comparison, the first version without an explicit empty constructor just loads the three fields directly without first initializing them. This is potentially (mostly meaninglessly) faster.
Of course, this says nothing about what the JIT will do. The two methods could resolve to the exact same native instructions. This can be tested by actually examining the final native instructions.
For the version
without an empty constructor, the method was inlined into a few instructions:
Code: Select all
00000273 fld1
00000275 fstp dword ptr [ebp-48h]
00000278 fld1
0000027a fstp dword ptr [ebp-4Ch]
0000027d fld1
0000027f fstp dword ptr [ebp-50h]
For the version with an empty constructor, something interesting happens:
Code: Select all
00000273 fld1
00000275 fstp dword ptr [ebp-48h]
00000278 fld1
0000027a fstp dword ptr [ebp-4Ch]
0000027d fld1
0000027f fstp dword ptr [ebp-50h]
It's identical! Good job, JIT!
Let's try one more thing. I'll throw in a completely worthless loop to try to confuse the JIT. Here's what the method looks like now:
Code: Select all
void Test(out Vector3 v)
{
//v = new Vector3();
v.X = 1;
v.Y = 1;
v.Z = 1;
for (int i = 0; i < 100; i++)
{
v.X = 1;
v.Y = 1;
v.Z = 1;
}
}
A smartypants compiler would probably fold the loop into nonexistence. Let's see what the JIT does:
Code: Select all
0000000b fld1
0000000d fstp dword ptr [esi]
0000000f fld1
00000011 fstp dword ptr [esi+4]
00000014 fld1
00000016 fstp dword ptr [esi+8]
00000019 xor eax,eax
0000001b fld1
0000001d fstp dword ptr [esi]
0000001f fld1
00000021 fstp dword ptr [esi+4]
00000024 fld1
00000026 fstp dword ptr [esi+8]
00000029 inc eax
0000002a cmp eax,64h
0000002d jl 0000001B
0000002f pop esi
00000030 pop ebp
00000031 ret
Oops, the loop's still there and the method is no longer inlined.
How about if the empty constructor is uncommented?
Code: Select all
0000000c mov edi,esi
0000000e xor eax,eax
00000010 xorps xmm0,xmm0
00000013 movq mmword ptr [edi],xmm0
00000017 add edi,8
0000001a stos dword ptr es:[edi]
0000001b fld1
0000001d fstp dword ptr [esi]
0000001f fld1
00000021 fstp dword ptr [esi+4]
00000024 fld1
00000026 fstp dword ptr [esi+8]
00000029 fld1
0000002b fstp dword ptr [esi]
0000002d fld1
0000002f fstp dword ptr [esi+4]
00000032 fld1
00000034 fstp dword ptr [esi+8]
00000037 inc eax
00000038 cmp eax,64h
0000003b jl 00000029
0000003d pop esi
0000003e pop edi
0000003f pop ebp
00000040 ret
Those first 6 instructions weren't there before. They correspond to the v = new Vector3() line. You tried your best, JIT
(Of course, these results from the JIT on my machine do not necessarily match the results of a JIT on another platform. Additionally, future JIT implementations could completely change the story.)
So, from these results, it appears that avoiding explicit empty constructors on value types can, under certain circumstances, sometimes speed things up a tiny amount. Whether or not that is worth the ugliness of preprocessor directives is another question
[An interesting side note: see that 'xorps' and 'movq' at the top of the disassembly? The JIT gets some flak for not making use of some processor extensions that could speed up math routines, among other things. While it's true that the current .NET JIT doesn't try to do as many optimizations as its C/C++ counterparts, 'movq' is, in fact, an MMX SIMD instruction and 'xorps' is an SSE SIMD instruction! There's no theoretical limitation on the JIT's ability to make use of more such instructions and compiler tricks; it's an exciting thought that a new JIT could offer some significant numerical speedups without many code changes. In fact, WP7 did something quite similar with SIMD/ARM-NEON instructions, just on a more restricted level. On the other hand,
certain rumors suggest there may be other paths to extra managed language speediness
]