🦀
chip5. bevyのプリミティブ・シェイプ
はじめに
2024/09/23時点の内容です。
- rustc 1.81.0
- bevy 0.14.2
bevyは開発初期段階のOSSで、まだまだ破壊的なアップデートが入ります。
でも、面白いですよ。
前回
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