Browse Source

实现伪3d球面

ios
王子贤 2 months ago
parent
commit
90884e905f
1 changed files with 164 additions and 0 deletions
  1. 164
      lib/components/sphere_cloud.dart

164
lib/components/sphere_cloud.dart

@ -0,0 +1,164 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
class SphereCloud extends StatefulWidget {
const SphereCloud({
super.key,
required this.items,
this.radius = 140,
this.itemSize = 48,
this.cameraZ = 400, //
this.focal = 240, //
this.minScale = 0.45,
this.maxScale = 1.25,
});
final List<Widget> items;
final double radius;
final double itemSize;
final double cameraZ;
final double focal;
final double minScale;
final double maxScale;
@override
State<SphereCloud> createState() => _SphereCloudState();
}
class _SphereCloudState extends State<SphereCloud> {
late final List<_Point3> _points;
//
double _rotX = 0.0;
double _rotY = 0.0;
@override
void initState() {
super.initState();
_points = _genFibonacciSphere(widget.items.length, widget.radius);
}
@override
void didUpdateWidget(covariant SphereCloud oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.items.length != widget.items.length ||
oldWidget.radius != widget.radius) {
_points
..clear()
..addAll(_genFibonacciSphere(widget.items.length, widget.radius));
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, c) {
final cx = c.maxWidth / 2;
final cy = c.maxHeight / 2;
// 2D
final projected = <_ProjectedItem>[];
for (int i = 0; i < _points.length; i++) {
final p = _rotate(_points[i], _rotX, _rotY);
// cameraZ - z
final denom = (widget.cameraZ - p.z).clamp(40.0, 1e9);
final perspective = widget.focal / denom;
final x2 = cx + p.x * perspective;
final y2 = cy + p.y * perspective;
final scale = perspective.clamp(widget.minScale, widget.maxScale);
projected.add(_ProjectedItem(
index: i,
x: x2,
y: y2,
z: p.z,
scale: scale,
));
}
// z ->
projected.sort((a, b) => a.z.compareTo(b.z));
return GestureDetector(
onPanUpdate: (d) {
//
setState(() {
_rotY += d.delta.dx * 0.01;
_rotX -= d.delta.dy * 0.01;
});
},
child: Stack(
clipBehavior: Clip.none,
children: [
for (final it in projected)
Positioned(
left: it.x - widget.itemSize / 2,
top: it.y - widget.itemSize / 2,
child: Transform.scale(
scale: it.scale,
child: SizedBox(
width: widget.itemSize,
height: widget.itemSize,
child: widget.items[it.index],
),
),
),
],
),
);
});
}
// Fibonacci sphere
List<_Point3> _genFibonacciSphere(int n, double r) {
if (n <= 0) return [];
final pts = <_Point3>[];
final offset = 2.0 / n;
final increment = math.pi * (3.0 - math.sqrt(5.0)); // golden angle
for (int i = 0; i < n; i++) {
final y = ((i * offset) - 1) + (offset / 2);
final rr = math.sqrt(1 - y * y);
final phi = i * increment;
final x = math.cos(phi) * rr;
final z = math.sin(phi) * rr;
pts.add(_Point3(x * r, y * r, z * r));
}
return pts;
}
// X/Y
_Point3 _rotate(_Point3 p, double rx, double ry) {
// rotate around X
final cosX = math.cos(rx), sinX = math.sin(rx);
var y1 = p.y * cosX - p.z * sinX;
var z1 = p.y * sinX + p.z * cosX;
// rotate around Y
final cosY = math.cos(ry), sinY = math.sin(ry);
var x2 = p.x * cosY + z1 * sinY;
var z2 = -p.x * sinY + z1 * cosY;
return _Point3(x2, y1, z2);
}
}
class _Point3 {
_Point3(this.x, this.y, this.z);
final double x, y, z;
}
class _ProjectedItem {
_ProjectedItem({
required this.index,
required this.x,
required this.y,
required this.z,
required this.scale,
});
final int index;
final double x, y, z;
final double scale;
}
Loading…
Cancel
Save