🦀

chip5. bevyのプリミティブ・シェイプ

2024/09/23に公開

はじめに

2024/09/23時点の内容です。

  • rustc 1.81.0
  • bevy 0.14.2
    bevyは開発初期段階のOSSで、まだまだ破壊的なアップデートが入ります。
    でも、面白いですよ。

前回

chip4. bevyの名前付きカラー

bevyの図形は増加中

bevyはv0.13以降、内蔵する基本図形(Primitive Shapes)を増強しています。
 ・News → Bevy 0.13 → Primitive Shapes
 ・News → Bevy 0.14 → New Geometric Primitives
v0.14では2D図形を「厚く」して3D化する押し出し図形(Extruded Shapes)がサポートされました。
 ・News → Bevy 0.14 → Extruded Shapes

図形の一覧

公式の資料は以下のリンクを参照してください。
 ・bevy::math::primitives(一覧)
 ・Examples(WebGL2) → 2D Shapes
 ・Examples(WebGL2) → 3D Shapes

下表で◯付きの図形はメッシュ化して画面に表示できる図形です。
(言い換えるとMeshableトレイトを実装している型です)

種類 2D Type 3D Type 補足説明
直線 Line2d Line3d 無限の直線
線分 Segment2d Segment3d 有限の直線
折れ線 Polyline2d Polyline3d ヒープを使わない折れ線
折れ線 BoxedPolyline2d BoxedPolyline3d ヒープを使う折れ線
円弧 Arc2d
平面 Plane2d InfinitePlane3d 無限の平面
平面 Plane3d 有限の平面
楕円 Ellipse
円・球 Circle Sphere
扇形 CircularSector
弓形 CircularSegment
円環 Annulus Torus 2 つの円の間の領域:ドーナツ形
円錐 Cone
円錐台 ConicalFrustum
円柱 Cylinder
カプセル Capsule2d Capsule3d
三角形 Triangle2d Triangle3d
四面体 Tetrahedron
長方形・直方体 Rectangle Cuboid
菱形 Rhombus
正多角形 RegularPolygon
多角形 Polygon ヒープを使わない多角形
多角形 BoxedPolygon ヒープを使う多角形
押し出し Extrusion 2D形状を「厚く」した3D形状

メッシュ化できる図形を表示してみた

メッシュ化できる基本図形(2D/3D)と押し出し図形(3D)を表示してみました。

  • 左右の矢印キーで表示する図形が変わります
  • スペースキーでワイヤーフレームの表示がON/OFFします
    メッシュの表示
use bevy::
{   prelude::*,
    log::LogPlugin, //ログ出力の制御
    color::palettes::*,
    sprite::{ MaterialMesh2dBundle, Mesh2dHandle },
    pbr::wireframe::{ WireframePlugin, WireframeConfig }, //3Dワイヤーフレームの制御
    sprite::{ Wireframe2dPlugin, Wireframe2dConfig },     //2Dワイヤーフレームの制御
};

//メイン関数
fn main() -> AppExit
{   App::new()
        .add_plugins
        (   (   DefaultPlugins      //アプリの基本的な機能の面倒を見てもらう
                    .set( LogPlugin { filter: "error".into(), ..default() } ),
                WireframePlugin,    //3Dワイヤーフレームの面倒を見てもらう
                Wireframe2dPlugin,  //2Dワイヤーフレームの面倒を見てもらう
            )
        )

        //ワイヤーフレームの表示に使うResource
        .insert_resource( WireframeConfig   { global: true, default_color: css::GRAY.into() } )
        .insert_resource( Wireframe2dConfig { global: true, default_color: css::GRAY        } )

        .add_systems( Startup, spawn_meshable_shapes ) //図形をspawnする
        .add_systems( Update, show_meshable_shape )    //キー入力で表示図形を切り替える
        .run()
}

//spawnする図形をマークするComponent
#[derive( Component )]
struct SpawnedShape ( usize, String );

//図形名表示用のText
#[derive( Component )]
struct ShapeNameLabel;

//図形をspawnする
fn spawn_meshable_shapes
(   mut cmds: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials2d: ResMut<Assets<ColorMaterial>>,    //2Dのカラー用
    mut materials3d: ResMut<Assets<StandardMaterial>>, //3Dのカラー用
)
{   //2Dカメラをspawnする
    cmds.spawn( Camera2dBundle::default() )
        .insert( Camera { order: 1, ..default() } );

    //3Dカメラとライトをspawnする
    cmds.spawn( Camera3dBundle::default() )
        .insert( Camera { order: 0, ..default() } )
        .insert( Transform::from_translation( Vec3::ONE * 5.0 ).looking_at( Vec3::ZERO, Vec3::Y ) );

    cmds.spawn( DirectionalLightBundle::default() )
        .insert( Transform::from_translation( Vec3::ONE * 10.0 ).looking_at( Vec3::ZERO, Vec3::Y ) );

    //図形名表示用のUIをspawnする
    let style = TextStyle { font_size: 100.0, ..default() };
    let text = Text::from_section( "", style ); //placeholder
    cmds.spawn( ( TextBundle { text, ..default() }, ShapeNameLabel ) );

    //2Dのメッシュ化可能な基本図形 v0.14.2
    let primitives_2d = vec!
    [   ( "Ellipse"        , meshes.add( Ellipse::default()         ) ),
        ( "Circle"         , meshes.add( Circle::default()          ) ),
        ( "CircularSector" , meshes.add( CircularSector::default()  ) ),
        ( "CircularSegment", meshes.add( CircularSegment::default() ) ),
        ( "Annulus"        , meshes.add( Annulus::default()         ) ),
        ( "Capsule2d"      , meshes.add( Capsule2d::default()       ) ),
        ( "Triangle2d"     , meshes.add( Triangle2d::default()      ) ),
        ( "Rectangle"      , meshes.add( Rectangle::default()       ) ),
        ( "Rhombus"        , meshes.add( Rhombus::default()         ) ),
        ( "RegularPolygon" , meshes.add( RegularPolygon::default()  ) ),
    ];

    //3Dのメッシュ化可能な基本図形 v0.14.2
    let primitives_3d = vec!
    [   ( "Plane3d"       , meshes.add( Plane3d::default()        ) ),
        ( "Sphere"        , meshes.add( Sphere::default()         ) ),
        ( "Torus"         , meshes.add( Torus::default()          ) ),
        ( "Cone"          , meshes.add( Cone::default()           ) ),
        ( "ConicalFrustum", meshes.add( ConicalFrustum::default() ) ),
        ( "Cylinder"      , meshes.add( Cylinder::default()       ) ),
        ( "Capsule3d"     , meshes.add( Capsule3d::default()      ) ),
        ( "Triangle3d"    , meshes.add( Triangle3d::default()     ) ),
        ( "Tetrahedron"   , meshes.add( Tetrahedron::default()    ) ),
        ( "Cuboid"        , meshes.add( Cuboid::default()         ) ),
    ];

    //押し出し図形 v0.14.2
    let extrusions_3d = vec!
    [   ( "Extruded Ellipse"        , meshes.add( Extrusion::new( Ellipse::default()        , 1.0 ) ) ),
        ( "Extruded Circle"         , meshes.add( Extrusion::new( Circle::default()         , 1.0 ) ) ),
        ( "Extruded CircularSector" , meshes.add( Extrusion::new( CircularSector::default() , 1.0 ) ) ),
        ( "Extruded CircularSegment", meshes.add( Extrusion::new( CircularSegment::default(), 1.0 ) ) ),
        ( "Extruded Annulus"        , meshes.add( Extrusion::new( Annulus::default()        , 1.0 ) ) ),
        ( "Extruded Capsule2d"      , meshes.add( Extrusion::new( Capsule2d::default()      , 1.0 ) ) ),
        ( "Extruded Triangle2d"     , meshes.add( Extrusion::new( Triangle2d::default()     , 1.0 ) ) ),
        ( "Extruded Rectangle"      , meshes.add( Extrusion::new( Rectangle::default()      , 1.0 ) ) ),
        ( "Extruded Rhombus"        , meshes.add( Extrusion::new( Rhombus::default()        , 1.0 ) ) ),
        ( "Extruded RegularPolygon" , meshes.add( Extrusion::new( RegularPolygon::default() , 1.0 ) ) ),
    ];

    //ループの準備
    let mut index = 0;
    let color: Color = css::YELLOW.into();
    let visibility = Visibility::Hidden;
    let scaling_2d = Transform::from_scale( Vec3::ONE * 200.0 ); //2Dのdefault()は小さすぎ
    let scaling_3d = Transform::from_scale( Vec3::ONE *   2.0 );

    //図形をspawnする
    for ( label, shape )in primitives_2d
    {   cmds.spawn
        (   MaterialMesh2dBundle
            {   mesh: Mesh2dHandle( shape ),
                material: materials2d.add( color ),
                visibility,
                transform: scaling_2d,
                ..default()
            }
        )
        .insert( SpawnedShape ( index, label.to_string() ) ); //マーク
        index += 1;
    }
    for shapes in [ primitives_3d, extrusions_3d ] //3Dは基本図形と押し出し図形
    {   for ( label, shape ) in shapes
        {   cmds.spawn
            (   PbrBundle
                {   mesh: shape,
                    material: materials3d.add( color ),
                    visibility,
                    transform: scaling_3d,
                    ..default()
                }
            )
            .insert( SpawnedShape ( index, label.to_string() ) ); //マーク
            index += 1;
        }
    }
}

//キー入力で図形の表示を切り替える
fn show_meshable_shape
(   mut qry_shapes: Query<( &mut Visibility, &SpawnedShape )>, //spawnした図形を検索
    mut qry_text: Query< &mut Text, With<ShapeNameLabel> >, //図形名表示用Textを検索
    input: Res<ButtonInput<KeyCode>>, //キー入力判定用のResource
    mut wireframe_config  : ResMut<WireframeConfig>,   //3Dワイヤーフレーム制御用のResource
    mut wireframe2d_config: ResMut<Wireframe2dConfig>, //2Dワイヤーフレーム制御用のResource
    mut index: Local<Option<usize>>, //表示中の図形の番号(初期化ではOption::Noneになる)
)
{   //ワイヤーフレームの表示/非表示切替
    if input.just_pressed( KeyCode::Space )
    {   wireframe_config.global   = ! wireframe_config.global;
        wireframe2d_config.global = ! wireframe2d_config.global;
    }

    //表示する図形の番号を左右矢印キーで決める
    let last_index = qry_shapes.iter().len() - 1;
    let ( new, old ) = if let Some ( mut index ) = *index
    {   let old = index;
        for &key in input.get_just_pressed()
        {   match key
            {   KeyCode::ArrowRight => { index = if index == last_index { 0 } else { index + 1 } },
                KeyCode::ArrowLeft  => { index = if index == 0 { last_index } else { index - 1 } },
                _ => (),
            }
        }

        //キー入力による変化がない場合、ここで関数脱出
        if index == old { return }

        ( index, old )
    }
    else { ( 0, 0 ) }; //最初だけ

    //図形と図形名の表示を切り替える
    for ( mut visibility, &SpawnedShape ( index, ref label ) ) in qry_shapes.iter_mut()
    {   if index == old { *visibility = Visibility::Hidden; } //古い図形の消去
        if index == new
        {   *visibility = Visibility::Visible; //新しい図形の表示
            if let Ok ( mut text ) = qry_text.get_single_mut()
            {   text.sections[ 0 ].value = label.clone(); //看板の書き換え
            }
        }
    }

    //表示中の図形の番号を更新する
    *index = Some( new );
}

Discussion