Skip to content

cast_ray vs InfinitePlane3d gets no hit depending on the angle #1011

Description

@dylemma

I'm trying out Avian 0.7.0 with Bevy 0.19.0, and working on a system that will push the camera towards the player when it would be otherwise obstructed by some terrain. My approach is to project a ray from the player (the camera's focus) back to the camera, and if it hits terrain, restrict the camera's distance based on the hit distance. However, when modeling the ground with InfinitePlane3d or Plane3d, the ray cast goes straight through the terrain at certain rotations. It seems to work fine with Cuboid.

    let ground_shape = Cuboid::from_corners(
        Vec3::new(-30., 0., -30.),
        Vec3::new(30., -0.5, 30.),
    );

    // Ground, as an infinite plane/grid
    commands.spawn((
        Name::from("Ground"),
        Ground,
        CollisionLayers::new(GameLayer::Terrain, LayerMask::ALL),
        RigidBody::Static,
        Friction::default(),
        Transform::default(),

        // planar geometry - weird behavior
        Collider::from(InfinitePlane3d::default()),
        InfiniteGrid,
        CollisionMargin(0.1),

        // cubic geometry - works ok
        // Collider::from(ground_shape.clone()),
        // Mesh3d::from(meshes.add(ground_shape.clone())),
        // MeshMaterial3d(player_material.clone()),
    ));

In my camera control system, I have target set to the player's GlobalTransform's translation:

   if let Some(ray_hit) = spatial_query.cast_ray(
        target,
        -camera.forward(),
        camera_settings.orbit_distance,
        false,
        &SpatialQueryFilter::from_mask(GameLayer::Terrain)
    ) {
        let name = names.get(ray_hit.entity).map_or("Unknown", |n| n.as_str());
        println!("Camera obstructed by {} at distance: {:?}", name, ray_hit.distance);
    }

In this, I noticed that there was a 180% arc in which the camera would not be obstructed, despite being underground. So, I used Gizmos to project 16 rays at once from the player down through the ground at various angles:

    for i in 0..16 {
        let rads = PI * 2. * (i as Scalar / 16 as Scalar);
        let Rot2 { cos, sin} = Rot2::radians(rads);
        let ray_dir = Dir3::new(Vec3::new(cos, -0.2, sin)).unwrap();
        gizmos.line(target, target + (ray_dir * 20.), GREEN_400);
        if let Some(ray_hit) = spatial_query.cast_ray(
            target,
            ray_dir,
            20.,
            true,
            &SpatialQueryFilter::from_mask(GameLayer::Terrain)
        ) {
            // blue circle at the ray hit
            let hit_pos = target + (ray_dir * ray_hit.distance);
            gizmos.circle(Isometry3d::from_translation(hit_pos), 0.3, BLUE_400);
        } else {
            // pink circle at the end of the line, for no hit
            let end_pos = target + (ray_dir * 20.);
            gizmos.circle(Isometry3d::from_translation(end_pos), 0.3, PINK_400);
        }
    }
Image

If I swap out the plane for a cuboid (the commented-out code in the .spawn, above), every ray hits the ground as expected:

Image

I tried to rule out misconfigurations by adding a system to spin the ground collider around:

fn spin_ground(
    mut ground: Query<&mut Transform, With<Ground>>,
    time: Res<Time>,
) {
    for mut ground_transform in &mut ground {
        ground_transform.rotate_y(FRAC_PI_2 * time.delta_secs());
    }
}

and sure enough, the 180% degree arc of non-ray-cast-hitting spins along with the ground.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions