flutter3d
Showcase Changelog 38 packages API reference

A texture transform per map: the source

since 0.8.0 Shading and materials

Regions the guide quotes: texture (line 36), transforms (line 52), plates (line 67), live (line 117).

1/// Two plates with the same four-colour texture: one read as it is, one whose
2/// colour map and glow map are each moved by a transform of their own.
3///
4/// Quoted by `texture_transforms.md` and shown whole in the Source tab.
5library;
6
7import 'dart:math' as math;
8import 'dart:typed_data';
9
10import 'package:flutter3d/flutter3d.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class TextureTransformsDemo extends ShowcaseDemo {
15 double turn = math.pi / 4;
16 double repeat = 2.0;
17 double slide = 0.5;
19 static const SamplerOptions _nearestRepeat = SamplerOptions(
20 minFilter: MinMagFilter.nearest,
21 magFilter: MinMagFilter.nearest,
22 widthAddressMode: SamplerAddressMode.repeat,
23 heightAddressMode: SamplerAddressMode.repeat,
24 );
26 late final Material _moved;
28 @override
29 void configureView(DemoContext context) {
30 context.orbit
31 ..distance = 3.6
32 ..yaw = 0.0
33 ..pitch = 0.0;
34 }
36 /// Four texels, one colour each: red, green, blue and white.
37 TextureHandle _quadrants(GraphicsDevice device) =>
38 device.createTextureFromPixels(
39 width: 2,
40 height: 2,
41 format: TextureFormat.r8g8b8a8UNormInt,
42 pixels: ByteData.sublistView(
43 Uint8List.fromList(<int>[
44 ...<int>[230, 20, 20, 255],
45 ...<int>[20, 230, 20, 255],
46 ...<int>[20, 20, 230, 255],
47 ...<int>[230, 230, 230, 255],
48 ]),
49 ),
50 )!;
52 /// The colour map turned and tiled, the glow map slid sideways: two maps of
53 /// one material that no longer agree.
54 Map<MaterialMap, TextureTransform> _transforms() =>
55 <MaterialMap, TextureTransform>{
56 MaterialMap.baseColor: TextureTransform(
57 scale: Vector2(repeat, repeat),
58 rotation: turn,
59 ),
60 MaterialMap.emissive: TextureTransform(offset: Vector2(slide, 0.0)),
61 };
63 @override
64 Scene build(DemoContext context) {
65 final TextureHandle quadrants = _quadrants(context.device);
67 final Material asIs = Material(
68 name: 'as is',
69 lighting: LightingModel.pbrLayered,
70 baseColor: Vector4(0.3, 0.3, 0.3, 1.0),
71 albedo: quadrants,
72 albedoSampler: _nearestRepeat,
73 emissiveTexture: quadrants,
74 emissiveSampler: _nearestRepeat,
75 emissive: Vector3(0.5, 0.5, 0.5),
76 );
77 _moved = Material(
78 name: 'moved',
79 lighting: LightingModel.pbrLayered,
80 baseColor: Vector4(0.3, 0.3, 0.3, 1.0),
81 albedo: quadrants,
82 albedoSampler: _nearestRepeat,
83 emissiveTexture: quadrants,
84 emissiveSampler: _nearestRepeat,
85 emissive: Vector3(0.5, 0.5, 0.5),
86 textureTransforms: _transforms(),
87 );
89 final DeviceMesh plate = DeviceMesh.upload(
90 context.device,
91 const PlaneShape(width: 1.6, depth: 1.6).build(),
92 );
93 final Quaternion upright = Quaternion.axisAngle(
94 Vector3(1.0, 0.0, 0.0),
95 math.pi / 2,
96 );
97 return Scene()
98 ..ambientIntensity = 0.25
99 ..add(
100 MeshNode(plate, asIs, name: 'as is')
101 ..setRotation(upright)
102 ..setPosition(-0.95, 0.0, 0.0),
103 )
104 ..add(
105 MeshNode(plate, _moved, name: 'moved')
106 ..setRotation(upright)
107 ..setPosition(0.95, 0.0, 0.0),
108 )
109 ..add(
110 LightNode(name: 'sun', intensity: 3.0)
111 ..setLocalForward(Vector3(-0.2, -0.3, -1.0).normalized()),
112 );
113 }
115 @override
116 void update(DemoContext context, double dt) {
117 _moved.textureTransforms.addAll(_transforms());
118 }
120 @override
121 List<DemoControl> controls(DemoContext context) => <DemoControl>[
122 SliderControl(
123 'Colour map: turn',
124 min: 0,
125 max: 2 * math.pi,
126 value: () => turn,
127 onChanged: (double v) => turn = v,
128 format: (double v) => '${(v * 180 / math.pi).round()}°',
129 ),
130 SliderControl(
131 'Colour map: repeat',
132 min: 1,
133 max: 4,
134 value: () => repeat,
135 onChanged: (double v) => repeat = v,
136 format: (double v) => '${v.toStringAsFixed(1)}x',
137 ),
138 SliderControl(
139 'Glow map: slide',
140 min: 0,
141 // Half a tile: the map repeats, so a slide of one looks like nought.
142 max: 0.5,
143 value: () => slide,
144 onChanged: (double v) => slide = v,
145 ),
146 ];
148 @override
149 void verify(Scene scene, FrameResult frame) {
150 final MeshNode moved = scene.meshes.firstWhere(
151 (MeshNode m) => m.name == 'moved',
152 );
153 if (moved.material.textureTransforms.length != 2) {
154 throw StateError('the moved plate does not carry a transform per map');
155 }
156 if (frame.drawCalls < 2) {
157 throw StateError('the two plates were not both drawn');
158 }
159 }