Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement basic light abstraction [flame_3d] #3182

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/flame_3d/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:math';
import 'package:example/crate.dart';
import 'package:example/keyboard_controlled_camera.dart';
import 'package:example/player_box.dart';
import 'package:example/rotating_light.dart';
import 'package:example/simple_hud.dart';
import 'package:flame/events.dart';
import 'package:flame/extensions.dart' as v64 show Vector2;
Expand Down Expand Up @@ -35,6 +36,8 @@ class ExampleGame3D extends FlameGame<World3D>
@override
FutureOr<void> onLoad() async {
world.addAll([
RotatingLight(),

// Add a player box
PlayerBox(),

Expand Down
20 changes: 20 additions & 0 deletions packages/flame_3d/example/lib/rotating_light.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:math';

import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';

class RotatingLight extends LightComponent {
RotatingLight()
: super.spot(
position: Vector3.zero(),
);

@override
void update(double dt) {
const radius = 15;
final angle = DateTime.now().millisecondsSinceEpoch / 4000;
final x = cos(angle) * radius;
final z = sin(angle) * radius;
position.setValues(x, 10, z);
wolfenrain marked this conversation as resolved.
Show resolved Hide resolved
}
}
3 changes: 3 additions & 0 deletions packages/flame_3d/lib/components.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export 'src/components/component_3d.dart';
export 'src/components/group_3d.dart';
export 'src/components/light_component.dart';
export 'src/components/mesh_component.dart';
export 'src/components/object_3d.dart';
10 changes: 9 additions & 1 deletion packages/flame_3d/lib/core.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
export 'package:vector_math/vector_math.dart'
show Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4, Quaternion, Aabb3;
show
Vector2,
Vector3,
Vector4,
Matrix2,
Matrix3,
Matrix4,
Quaternion,
Aabb3;

export 'extensions.dart';
1 change: 1 addition & 0 deletions packages/flame_3d/lib/resources.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flame/cache.dart';
import 'package:flame_3d/resources.dart';

export 'src/resources/light.dart';
export 'src/resources/material.dart';
export 'src/resources/mesh.dart';
export 'src/resources/resource.dart';
Expand Down
14 changes: 13 additions & 1 deletion packages/flame_3d/lib/src/camera/world_3d.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flame/components.dart' as flame;
import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/graphics.dart';
import 'package:flame_3d/resources.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';

Expand All @@ -17,12 +18,17 @@ class World3D extends flame.World with flame.HasGameReference {
super.children,
super.priority,
Color clearColor = const Color(0x00000000),
}) : device = GraphicsDevice(clearValue: clearColor);
}) : device = GraphicsDevice(clearValue: clearColor) {
children.register<LightComponent>();
}

/// The graphical device attached to this world.
@internal
final GraphicsDevice device;

Iterable<Light> get lights =>
children.query<LightComponent>().map((component) => component.light);

final _paint = Paint();

@internal
Expand All @@ -45,6 +51,8 @@ class World3D extends flame.World with flame.HasGameReference {
..begin(size);

culled = 0;

_prepareDevice();
// ignore: invalid_use_of_internal_member
super.renderFromCamera(canvas);

Expand All @@ -59,6 +67,10 @@ class World3D extends flame.World with flame.HasGameReference {
image.dispose();
}

void _prepareDevice() {
device.lights = lights;
}

// TODO(wolfenrain): this is only here for testing purposes
int culled = 0;
}
71 changes: 17 additions & 54 deletions packages/flame_3d/lib/src/components/component_3d.dart
Original file line number Diff line number Diff line change
@@ -1,48 +1,44 @@
import 'dart:ui';

import 'package:flame/components.dart' show Component, HasWorldReference;
import 'package:flame/game.dart' show FlameGame;
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';

/// {@template component_3d}
/// [Component3D]s are the basic building blocks for a 3D [FlameGame].
/// [Component3D] is a base class for any concept that lives in 3D space.
///
/// It is a [Component] implementation that represents a 3D object that can be
/// freely moved around in 3D space, rotated, and scaled.
///
/// The [Component3D] class has no visual representation of its own (except in
/// debug mode). It is common, therefore, to derive from this class
/// and implement a specific rendering logic.
/// The main property of this class is the [transform] (which combines
/// the [position], [rotation], and [scale]). Thus, the [Component3D] can be
/// seen as an object in 3D space.
///
/// It is typically not used directly, but rather use one of the following
/// implementations:
/// - [Object3D] for a 3D object that can be bound and rendered by the GPU
/// - [LightComponent] for a light source that affects how objects are rendered
/// - [Group3D] for a generic container for several [Component3D]s
///
/// The base [Component3D] class can also be used as a container
/// for several other components. In this case, changing the position,
/// rotating or scaling the [Component3D] will affect the whole
/// group as if it was a single entity.
///
/// The main property of this class is the [transform] (which combines
/// the [position], [rotation], and [scale]). Thus, the [Component3D] can be
/// seen as an object in 3D space where you can change its perceived
/// visualization.
///
/// See the [MeshComponent] for a [Component3D] that has a visual representation
/// by using [Mesh]es
/// {@endtemplate}
class Component3D extends Component with HasWorldReference<World3D> {
abstract class Component3D extends Component with HasWorldReference<World3D> {
final Transform3D transform;

/// {@macro component_3d}
Component3D({
Vector3? position,
Vector3? scale,
Quaternion? rotation,
}) : transform = Transform3D()
List<Component3D> children = const [],
}) : transform = Transform3D()
..position = position ?? Vector3.zero()
wolfenrain marked this conversation as resolved.
Show resolved Hide resolved
..rotation = rotation ?? Quaternion.euler(0, 0, 0)
..scale = scale ?? Vector3.all(1);

final Transform3D transform;
..scale = scale ?? Vector3.all(1),
super(children: children);

/// The total transformation matrix for the component. This matrix combines
/// translation, rotation and scale transforms into a single entity. The
Expand Down Expand Up @@ -80,37 +76,4 @@ class Component3D extends Component with HasWorldReference<World3D> {
/// Measure the distance (in parent's coordinate space) between this
/// component's anchor and the [other] component's anchor.
double distance(Component3D other) => position.distanceTo(other.position);

@override
void renderTree(Canvas canvas) {
super.renderTree(canvas);
final camera = CameraComponent3D.currentCamera;
assert(
camera != null,
'''Component is either not part of a World3D or the render is being called outside of the camera rendering''',
);
if (!shouldCull(camera!)) {
world.culled++;
return;
}

// We set the priority to the distance between the camera and the object.
// This ensures that our rendering is done in a specific order allowing for
// alpha blending.
//
// Note(wolfenrain): we should optimize this in the long run it currently
// sucks.
priority = -(CameraComponent3D.currentCamera!.position - position)
.length
.abs()
.toInt();

bind(world.device);
}

void bind(GraphicsDevice device) {}

bool shouldCull(CameraComponent3D camera) {
return camera.frustum.containsVector3(position);
}
}
19 changes: 19 additions & 0 deletions packages/flame_3d/lib/src/components/group_3d.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flame_3d/components.dart';

/// {@template group_3d}
/// [Group3D] is a [Component3D] that acts as a container for other
/// [Component3D]s.
///
/// Changing the position, rotating or scaling the [Component3D] will affect
/// the whole group as if it was a single entity.
wolfenrain marked this conversation as resolved.
Show resolved Hide resolved
///
wolfenrain marked this conversation as resolved.
Show resolved Hide resolved
/// {@endtemplate}
class Group3D extends Component3D {
/// {@macro group_3d}
Group3D({
super.position,
super.scale,
super.rotation,
super.children,
});
}
38 changes: 38 additions & 0 deletions packages/flame_3d/lib/src/components/light_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';
import 'package:flame_3d/resources.dart';

/// A [Component3D] that represents a light source in the 3D world.
class LightComponent extends Component3D {
LightComponent({
required this.source,
super.position,
});

LightComponent.spot({
Vector3? position,
}) : this(
source: SpotLight(),
position: position,
);

final LightSource source;

late final Light _light = Light(
transform: Transform3D(),
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
source: source,
);

Light get light {
return _light..transform.setFrom(transform);
}

@override
void onMount() {
assert(
parent is World3D,
'Lights must be added to the root of the World3D',
);
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
}
}
6 changes: 3 additions & 3 deletions packages/flame_3d/lib/src/components/mesh_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import 'package:flame_3d/src/camera/camera_component_3d.dart';
import 'package:flame_3d/src/graphics/graphics_device.dart';

/// {@template mesh_component}
/// A [Component3D] that renders a [Mesh] at the [position] with the [rotation]
/// An [Object3D] that renders a [Mesh] at the [position] with the [rotation]
/// and [scale] applied.
///
/// This is a commonly used subclass of [Component3D].
/// This is a commonly used subclass of [Object3D].
/// {@endtemplate}
class MeshComponent extends Component3D {
class MeshComponent extends Object3D {
/// {@macro mesh_component}
MeshComponent({
required Mesh mesh,
Expand Down
62 changes: 62 additions & 0 deletions packages/flame_3d/lib/src/components/object_3d.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:ui';

import 'package:flame/game.dart' show FlameGame;
import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/graphics.dart';
import 'package:flame_3d/resources.dart';

/// {@template object_3d}
/// [Object3D]s are the basic building blocks for a 3D [FlameGame].
///
/// It is an object that is positioned in 3D space and can be bind to be
/// rendered by a [GraphicsDevice].
///
/// However, it has no visual representation of its own (except in
/// debug mode). It is common, therefore, to derive from this class
/// and implement a specific rendering logic.
///
/// See the [MeshComponent] for a [Component3D] that has a visual representation
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
/// using a [Mesh].
/// {@endtemplate}
abstract class Object3D extends Component3D {
/// {@macro object_3d}
Object3D({
super.position,
super.scale,
super.rotation,
});

@override
void renderTree(Canvas canvas) {
super.renderTree(canvas);
final camera = CameraComponent3D.currentCamera;
assert(
camera != null,
'''Component is either not part of a World3D or the render is being called outside of the camera rendering''',
);
if (!shouldCull(camera!)) {
world.culled++;
return;
}

// We set the priority to the distance between the camera and the object.
// This ensures that our rendering is done in a specific order allowing for
// alpha blending.
//
// Note(wolfenrain): we should optimize this in the long run it currently
// sucks.
priority = -(CameraComponent3D.currentCamera!.position - position)
.length
.abs()
.toInt();
wolfenrain marked this conversation as resolved.
Show resolved Hide resolved

bind(world.device);
}

void bind(GraphicsDevice device);

bool shouldCull(CameraComponent3D camera) {
return camera.frustum.containsVector3(position);
}
}
2 changes: 1 addition & 1 deletion packages/flame_3d/lib/src/game/notifying_quaternion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,5 @@
}

@override
Float32List get storage => UnmodifiableFloat32ListView(super.storage);
Float32List get storage => super.storage.asUnmodifiableView();

Check warning on line 173 in packages/flame_3d/lib/src/game/notifying_quaternion.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

This API is available since SDK 3.3.0, but constraints '>=3.0.0 <4.0.0' don't guarantee it.

Try updating the SDK constraints.

Check warning on line 173 in packages/flame_3d/lib/src/game/notifying_quaternion.dart

View workflow job for this annotation

GitHub Actions / analyze

This API is available since SDK 3.3.0, but constraints '>=3.0.0 <4.0.0' don't guarantee it.

Try updating the SDK constraints.
}
2 changes: 1 addition & 1 deletion packages/flame_3d/lib/src/game/notifying_vector3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,5 +207,5 @@
}

@override
Float32List get storage => UnmodifiableFloat32ListView(super.storage);
Float32List get storage => super.storage.asUnmodifiableView();

Check warning on line 210 in packages/flame_3d/lib/src/game/notifying_vector3.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

This API is available since SDK 3.3.0, but constraints '>=3.0.0 <4.0.0' don't guarantee it.

Try updating the SDK constraints.

Check warning on line 210 in packages/flame_3d/lib/src/game/notifying_vector3.dart

View workflow job for this annotation

GitHub Actions / analyze

This API is available since SDK 3.3.0, but constraints '>=3.0.0 <4.0.0' don't guarantee it.

Try updating the SDK constraints.
}
4 changes: 4 additions & 0 deletions packages/flame_3d/lib/src/graphics/graphics_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import 'package:flame_3d/game.dart';
import 'package:flame_3d/resources.dart';
import 'package:flutter_gpu/gpu.dart' as gpu;

Check failure on line 6 in packages/flame_3d/lib/src/graphics/graphics_device.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

Target of URI doesn't exist: 'package:flutter_gpu/gpu.dart'.

Try creating the file referenced by the URI, or try using a URI for a file that does exist. See https://dart.dev/diagnostics/uri_does_not_exist to learn more about this problem.

Check failure on line 6 in packages/flame_3d/lib/src/graphics/graphics_device.dart

View workflow job for this annotation

GitHub Actions / analyze

Target of URI doesn't exist: 'package:flutter_gpu/gpu.dart'.

Try creating the file referenced by the URI, or try using a URI for a file that does exist. See https://dart.dev/diagnostics/uri_does_not_exist to learn more about this problem.

enum BlendState {
additive,
Expand Down Expand Up @@ -48,6 +48,10 @@

Size _previousSize = Size.zero;

/// Must be set by the rendering pipeline before elements are bound.
/// Can be accessed by elements in their bind method.
Iterable<Light> lights = [];
wolfenrain marked this conversation as resolved.
Show resolved Hide resolved

/// Begin a new rendering batch.
///
/// After [begin] is called the graphics device can be used to bind resources
Expand Down
3 changes: 3 additions & 0 deletions packages/flame_3d/lib/src/resources/light.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'light/light.dart';
export 'light/light_source.dart';
export 'light/spot_light.dart';
Loading
Loading