+
+ /*
+ * CCD routine
+ * ---------------------------------------------------
+ *
+ */
+ v3f lwr_prev,
+ lwr_now,
+ lwr_offs = { 0.0f, w->collider.radius, 0.0f };
+
+ v3_add( lwr_offs, w->state.prev_pos, lwr_prev );
+ v3_add( lwr_offs, player->rb.co, lwr_now );
+
+ v3f movedelta;
+ v3_sub( player->rb.co, w->state.prev_pos, movedelta );
+
+ float movedist = v3_length( movedelta );
+
+ if( movedist > 0.3f )
+ {
+ float t, sr = w->collider.radius-0.04f;
+ v3f n;
+
+ if( spherecast_world( lwr_prev, lwr_now, sr, &t, n ) != -1 )
+ {
+ v3_lerp( lwr_prev, lwr_now, vg_maxf(0.01f,t), player->rb.co );
+ player->rb.co[1] -= w->collider.radius;
+ rb_update_transform( &player->rb );
+
+ v3_add( player->rb.co, (v3f){0.0f, 1.0f, 0.0f}, mtx[3] );
+ debug_capsule( mtx, w->collider.radius, w->collider.height, VG__RED );
+ }
+ }
+
+ teleport_gate *gate;
+ if( (gate = world_intersect_gates( player->rb.co, w->state.prev_pos )) )
+ {
+ struct player_device_walk *w = dev->storage;
+
+ m4x3_mulv( gate->transport, player->rb.co, player->rb.co );
+ m3x3_mulv( gate->transport, player->rb.v, player->rb.v );
+ rb_update_transform( &player->rb );
+
+ /* analytical rotation of yaw */
+ v3f fwd_dir = { cosf(w->state.angles[0]),
+ 0.0f,
+ sinf(w->state.angles[0])};
+ m3x3_mulv( gate->transport, fwd_dir, fwd_dir );
+ w->state.angles[0] = atan2f( fwd_dir[2], fwd_dir[0] );
+
+ w->state_gate_storage = w->state;
+ player_pass_gate( player, gate );
+ }