1 module des.mc.core.skeleton; 2 3 import std.stdio; 4 import std.string; 5 import std.algorithm; 6 import std.traits; 7 public import des.math.linear; 8 9 struct Joint 10 { 11 vec3 pos; 12 float qual = 0.0f; 13 } 14 15 struct Skeleton 16 { 17 enum JointID 18 { 19 HEAD, 20 NECK, 21 22 LEFT_SHOULDER, 23 RIGHT_SHOULDER, 24 LEFT_ELBOW, 25 RIGHT_ELBOW, 26 LEFT_HAND, 27 RIGHT_HAND, 28 29 TORSO, 30 31 LEFT_HIP, 32 RIGHT_HIP, 33 LEFT_KNEE, 34 RIGHT_KNEE, 35 LEFT_FOOT, 36 RIGHT_FOOT 37 } 38 39 enum JointCount = [EnumMembers!JointID].length; 40 41 private Joint[JointCount] joints; 42 43 mixin( accessArray!("joints",Joint,JointID) ); 44 45 auto transform( in mat4 mtr, JointID[] jj = [] ) const 46 { 47 Skeleton ret; 48 auto en = cast(JointID[])[EnumMembers!JointID]; 49 if( jj.length ) 50 en = jj; 51 foreach( i; en ) 52 ret.joints[i] = Joint( (mtr * vec4(joints[i].pos,1.0)).xyz, joints[i].qual ); 53 54 return ret; 55 } 56 57 vec3 center() const 58 { 59 vec3 p; 60 foreach( joint; joints ) 61 p += joint.pos; 62 return p / joints.length; 63 } 64 65 Joint[JointCount] allJoints() const { return joints; } 66 void setJoints( in Joint[JointCount] j ) { joints = j; } 67 68 vec3[] allBones() const 69 { 70 vec3[] ret; 71 ret ~= joints[JointID.HEAD].pos; 72 ret ~= joints[JointID.NECK].pos; 73 74 ret ~= joints[JointID.NECK].pos; 75 ret ~= joints[JointID.LEFT_SHOULDER].pos; 76 77 ret ~= joints[JointID.NECK].pos; 78 ret ~= joints[JointID.RIGHT_SHOULDER].pos; 79 80 ret ~= joints[JointID.RIGHT_ELBOW].pos; 81 ret ~= joints[JointID.RIGHT_SHOULDER].pos; 82 83 ret ~= joints[JointID.LEFT_ELBOW].pos; 84 ret ~= joints[JointID.LEFT_SHOULDER].pos; 85 86 ret ~= joints[JointID.LEFT_ELBOW].pos; 87 ret ~= joints[JointID.LEFT_HAND].pos; 88 89 ret ~= joints[JointID.RIGHT_ELBOW].pos; 90 ret ~= joints[JointID.RIGHT_HAND].pos; 91 92 ret ~= joints[JointID.TORSO].pos; 93 ret ~= joints[JointID.NECK].pos; 94 95 ret ~= joints[JointID.TORSO].pos; 96 ret ~= joints[JointID.LEFT_HIP].pos; 97 98 ret ~= joints[JointID.TORSO].pos; 99 ret ~= joints[JointID.RIGHT_HIP].pos; 100 101 ret ~= joints[JointID.RIGHT_KNEE].pos; 102 ret ~= joints[JointID.RIGHT_HIP].pos; 103 104 ret ~= joints[JointID.LEFT_KNEE].pos; 105 ret ~= joints[JointID.LEFT_HIP].pos; 106 107 ret ~= joints[JointID.LEFT_KNEE].pos; 108 ret ~= joints[JointID.LEFT_FOOT].pos; 109 110 ret ~= joints[JointID.RIGHT_KNEE].pos; 111 ret ~= joints[JointID.RIGHT_FOOT].pos; 112 return ret; 113 } 114 115 static Skeleton fromJoints( in Joint[JointCount] jj ) 116 { 117 Skeleton ret; 118 ret.setJoints( jj ); 119 return ret; 120 } 121 } 122 123 version(unittest) 124 { 125 Joint[Skeleton.JointCount] pose_norm; 126 Skeleton utest_skeleton; 127 static this() 128 { 129 pose_norm[Skeleton.JointID.HEAD] = Joint( vec3(0,0,2), 1.0f ); 130 pose_norm[Skeleton.JointID.NECK] = Joint( vec3(0,0,1.8), 1.0f ); 131 132 pose_norm[Skeleton.JointID.LEFT_SHOULDER] = Joint( vec3(0, 0.2,1.7), 1.0f ); 133 pose_norm[Skeleton.JointID.RIGHT_SHOULDER] = Joint( vec3(0,-0.2,1.7), 1.0f ); 134 pose_norm[Skeleton.JointID.LEFT_ELBOW] = Joint( vec3(0, 0.3,1.2), 1.0f ); 135 pose_norm[Skeleton.JointID.RIGHT_ELBOW] = Joint( vec3(0,-0.3,1.2), 1.0f ); 136 pose_norm[Skeleton.JointID.LEFT_HAND] = Joint( vec3(0.2,0.3,0.9), 1.0f ); 137 pose_norm[Skeleton.JointID.RIGHT_HAND] = Joint( vec3(0.2,-0.3,0.9), 1.0f ); 138 139 pose_norm[Skeleton.JointID.TORSO] = Joint( vec3(0,0,1.2), 1.0f ); 140 141 pose_norm[Skeleton.JointID.LEFT_HIP] = Joint( vec3(0,0.15,1), 1.0f ); 142 pose_norm[Skeleton.JointID.RIGHT_HIP] = Joint( vec3(0,-0.15,1), 1.0f ); 143 pose_norm[Skeleton.JointID.LEFT_KNEE] = Joint( vec3(0,0.15,0.5), 1.0f ); 144 pose_norm[Skeleton.JointID.RIGHT_KNEE] = Joint( vec3(0,-0.15,0.5), 1.0f ); 145 pose_norm[Skeleton.JointID.LEFT_FOOT] = Joint( vec3(0,0.15,0), 1.0f ); 146 pose_norm[Skeleton.JointID.RIGHT_FOOT] = Joint( vec3(0,-0.15,0), 1.0f ); 147 utest_skeleton = Skeleton.fromJoints( pose_norm ); 148 } 149 150 Skeleton[] getFakeSkeletons( vec3 global_offset = vec3(0,0,0), 151 vec3[] offsets = [ vec3(0,2,0), vec3(1,-1,0) ] ) 152 { 153 Skeleton[] ret; 154 foreach( i, offset; offsets ) 155 ret ~= skeleton_offset( utest_skeleton, global_offset + offset ); 156 return ret; 157 } 158 } 159 160 unittest 161 { 162 auto ts = Skeleton.fromJoints( pose_norm ); 163 assert( pose_norm[Skeleton.JointID.HEAD] == ts.head ); 164 assert( pose_norm[Skeleton.JointID.RIGHT_HAND] == ts.rightHand ); 165 } 166 167 unittest 168 { 169 auto tr = mat4( 1.0, 0.0, 0.0, 1.0, 170 0.0, 1.0, 0.0, 0.0, 171 0.0, 0.0, 1.0, 0.0, 172 0.0, 0.0, 0.0, 1.0 ); 173 auto skel = Skeleton.fromJoints( pose_norm ); 174 auto tr_skel = skel.transform( tr ); 175 foreach( i, j; tr_skel.joints ) 176 assert( j.pos.x == skel.joints[i].pos.x + 1 ); 177 tr_skel = skel.transform( tr, [Skeleton.JointID.RIGHT_HAND, Skeleton.JointID.LEFT_HAND] ); 178 179 foreach( i, j; tr_skel.joints ) 180 { 181 if( i == Skeleton.JointID.RIGHT_HAND || i == Skeleton.JointID.LEFT_HAND ) 182 assert( j.pos.x == skel.joints[i].pos.x + 1 ); 183 else 184 assert( j.pos.x == skel.joints[i].pos.x ); 185 } 186 } 187 188 Skeleton skeleton_mlt( in Skeleton s, float v ) 189 { 190 auto s_joints = s.allJoints(); 191 foreach( ref joint; s_joints ) 192 { 193 joint.pos *= v; 194 joint.qual *= v; 195 } 196 return Skeleton.fromJoints( s_joints ); 197 } 198 199 unittest 200 { 201 auto ts = Skeleton.fromJoints( pose_norm ); 202 auto mts = skeleton_mlt( ts, 10 ); 203 assert( pose_norm[Skeleton.JointID.HEAD].pos * 10 == mts.head.pos ); 204 } 205 206 Skeleton skeleton_add( in Skeleton a, in Skeleton b ) 207 { 208 auto a_joints = a.allJoints(); 209 auto b_joints = b.allJoints(); 210 foreach( i, ref joint; a_joints ) 211 { 212 joint.pos += b_joints[i].pos; 213 joint.qual += b_joints[i].qual; 214 } 215 return Skeleton.fromJoints( a_joints ); 216 } 217 218 unittest 219 { 220 auto ts1 = Skeleton.fromJoints( pose_norm ); 221 auto ts2 = Skeleton.fromJoints( pose_norm ); 222 auto rts = skeleton_add( ts1, ts2 ); 223 assert( pose_norm[Skeleton.JointID.HEAD].pos * 2 == rts.head.pos ); 224 } 225 226 Skeleton skeleton_diff( in Skeleton a, in Skeleton b ) 227 { 228 auto a_joints = a.allJoints(); 229 auto b_joints = b.allJoints(); 230 foreach( i, ref joint; a_joints ) 231 { 232 joint.pos -= b_joints[i].pos; 233 joint.qual -= b_joints[i].qual; 234 } 235 return Skeleton.fromJoints( a_joints ); 236 } 237 238 unittest 239 { 240 auto ts1 = Skeleton.fromJoints( pose_norm ); 241 auto ts2 = skeleton_mlt( ts1, 2 ); 242 auto rts = skeleton_diff( ts2, ts1 ); 243 assert( pose_norm[Skeleton.JointID.HEAD].pos == rts.head.pos ); 244 } 245 246 Skeleton skeleton_div( in Skeleton s, float v ) 247 { return skeleton_mlt( s, 1.0f / v ); } 248 249 Skeleton skeleton_offset( in Skeleton a, in vec3 offset ) 250 { 251 auto a_joints = a.allJoints(); 252 foreach( ref joint; a_joints ) 253 joint.pos += offset; 254 return Skeleton.fromJoints( a_joints ); 255 } 256 257 private 258 { 259 @property string accessArray(string arrayName,arrayType,Enum)() 260 { 261 string[] ret; 262 263 string type = arrayType.stringof; 264 265 foreach( e; [EnumMembers!(Skeleton.JointID)] ) 266 { 267 string id = format("%s.%s",Enum.stringof,e); 268 string name = toProperName( id ); 269 string access = format( "%s[%s]", arrayName, id ); 270 ret ~= format( "@property ref %1$s %2$s() { return %3$s; }\n" ~ 271 "@property ref const(%1$s) %2$s() const { return %3$s; }\n", 272 type, name, access ); 273 } 274 275 return ret.join("\n"); 276 } 277 278 string toProperName( string str ) 279 { 280 auto words = str.split(".")[$-1].split("_"); 281 auto res = toLowerPure( words[0] ); 282 if( words.length > 1 ) 283 res = reduce!( (a,b) => a ~ b )( res, map!( a => a.capitalize )(words[1 .. $]) ); 284 return res; 285 } 286 287 unittest 288 { 289 assert( toProperName( "RIGHT_HAND" ) == "rightHand" ); 290 assert( toProperName( "RIGHT_HAND_FIRST_FINGER" ) == "rightHandFirstFinger" ); 291 assert( toProperName( "HEAD" ) == "head" ); 292 } 293 294 pure string toLowerPure( string str ) 295 { 296 auto diff = cast(ubyte)('A') - cast(ubyte)('a'); 297 char[] buf; 298 auto upperStart = cast(ubyte)('A'); 299 auto upperEnd = cast(ubyte)('Z'); 300 foreach( c; str ) 301 { 302 auto bc = cast(ubyte)(c); 303 if( bc >= upperStart && bc <= upperEnd ) 304 buf ~= cast(char)(bc-diff); 305 else 306 buf ~= c; 307 } 308 309 return buf.idup; 310 } 311 312 unittest 313 { 314 assert( toLowerPure( "OlolOlo" ) == "olololo" ); 315 assert( toLowerPure( "RIGHT" ) == "right" ); 316 assert( toLowerPure( "r10%Z" ) == "r10%z" ); 317 string rndstr = "`1234567890-=qwertyuiop[]\\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?"; 318 assert( toLowerPure( rndstr ) == rndstr.toLower ); 319 } 320 }