[flame_3d] Nested components are culled at their old position after an ancestor moves
メンテナーはふだん 1 日以内に返信
評価
この issue はまだ評価されていません。
説明
What happened?
When the transform of a Component3D changes, its descendants keep using the AABB that was cached for their old world position, so they are frustum culled based on where they used to be.
Component3D._onTransformChanged propagates the world transform dirty flag down the subtree, but the AABB dirty flag is only propagated up (markAabbDirty walks the parents). The children's cached AABBs are never invalidated, so after an ancestor moves:
- a child that was out of view stays culled even when it is moved into view, and
- a child that was in view keeps being drawn (or tested) as if it was still in its old spot.
This affects regular MeshComponents and ModelComponents, not only custom Object3Ds, as long as they are nested under a Component3D that moves. Common examples are a group that is moved as a whole, or a character with attached meshes.
What do you expect?
When a component's transform changes, the cached AABBs of all of its descendants should be invalidated too, so that they are culled based on their current world position.
How can we reproduce this?
- Run the
main.dartbelow withflutter run --enable-flutter-gpu(on Windows also add--enable-impeller). - Two cubes should be visible after a second: the red one is a direct child of the world, the green one is a
MeshComponentinside aComponent3Dgroup that starts behind the camera and moves to the origin after one second. - Only the red cube is ever rendered. The green cube's AABB is still cached at its initial position behind the camera.
What steps should take to fix this?
Mark the AABB dirty together with the world transform when propagating down the subtree in Component3D._markWorldTransformDirty, and mark the parent's AABB dirty from _onTransformChanged (calling markAabbDirty() on the component itself would return early, since the downward propagation already marked it).
The early return in _markWorldTransformDirty should then only happen when both flags are already dirty. A component's world transform can be clean while its AABB is dirty and vice versa, so checking only one of them would stop the propagation too early.
Do have an example of where the bug occurs?
import 'package:flame/game.dart';
import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';
import 'package:flame_3d/graphics.dart';
import 'package:flame_3d/resources.dart';
import 'package:flutter/widgets.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await GpuBackend.initialize();
runApp(GameWidget(game: ReproGame()));
}
class ReproGame extends FlameGame3D {
ReproGame()
: super(
camera: CameraComponent3D(
position: Vector3(0, 2, -6),
target: Vector3(0, 1, 0),
),
);
@override
Future<void> onLoad() async {
world.addAll([
// Red: not nested, world position (1.5, 1, 0). Rendered.
MeshComponent(
mesh: _cubeMesh(const Color(0xFFFF0000)),
position: Vector3(1.5, 1, 0),
),
// Green: starts behind the camera. After one second its parent moves
// it to world position (-1.5, 1, 0). Never rendered.
MovingGroup(
children: [
MeshComponent(
mesh: _cubeMesh(const Color(0xFF00FF00)),
position: Vector3(-1.5, 1, 0),
),
],
),
]);
}
Mesh _cubeMesh(Color color) => CuboidMesh(
size: Vector3.all(1),
material: UnlitMaterial(albedoColor: color),
);
}
/// A plain group that starts behind the camera and moves to the origin after
/// one second.
class MovingGroup extends Component3D {
MovingGroup({super.children}) : super(position: Vector3(0, 0, -30));
double _elapsed = 0;
@override
void update(double dt) {
super.update(dt);
_elapsed += dt;
if (_elapsed > 1 && position.z != 0) {
position.setZero();
}
}
}
Relevant log output
No errors or warnings are logged.
Execute in a terminal and put output into the code block below
Flutter 3.47.8 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 737ba8f885 (6 days ago) • 2026-10-22 21:37:31 -0700
Engine • hash dfd2e6cb7d319db05e3bf1503f3e2ac60f6288a2 (revision cf636b1dfb) (6 days ago) • 2026-10-23 02:13:38.000Z
Tools • Dart 3.13.7 • DevTools 2.60.0
Affected platforms
All
Other information
Reproduced on Windows (Impeller with the OpenGL ES backend), but the culling logic is platform independent.
Nested transforms were introduced in #3867. The world transform matrix is updated correctly there, only the cached AABBs used for culling are not.
Are you interested in working on a PR for this?
- I want to work on this
- 主要言語
- Dart
- スター
- 10.8k
- フォーク
- 1.1k
- 平均マージ
- 1日 21時間
- マージ済み PR(30日)
- 62
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
flame-engine/flame のほかの issue
-
`Viewfinder.visibleGameSize` is not reapplied when the viewport changes size対応中かも @ufrshubham が 1 日前に担当しました。 オープンbug
flame-engine/flame#4204 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
`Viewfinder.visibleGameSize` zooms in too far with a `FixedResolutionViewport`対応中かも @ufrshubham が 1 日前に担当しました。 オープンbug
flame-engine/flame#4203 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Camera bounds with `considerViewport: true` are not updated when the viewport is resized対応中かも @ufrshubham が 1 日前に担当しました。 オープンbug
flame-engine/flame#4202 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
`CameraComponent.setBounds` with `considerViewport: true` only works for bounds that start at (0, 0), at zoom 1対応中かも @ufrshubham が 1 日前に担当しました。 オープンbug
flame-engine/flame#4201 · コメント 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
`findPostProcessFromContext` returns `null` for components rendered with `CameraComponent.postProcess`対応中かも @ufrshubham が 1 日前に担当しました。 オープンbug
flame-engine/flame#4200 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
flame-engine/flame の issue をすべて見る
似ている issue
-
check-relative-paths reports links to existing directories as missing対応中かも @reidbaker-agent が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
google/skills_lint.dart#202 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
InlitX/GymMane#311 · リアクション 1 件 ·
メンテナーはふだん 4 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
zjs81/meshcore-open#634 ·
メンテナーはふだん 1 日以内に返信