buildSphere method
Implementation
Future<SphereImage?> buildSphere(double maxWidth, double maxHeight) async {
if (widget.controller.surface == null ||
widget.controller.surfaceProcessed == null) {
return Future.value(null);
}
final sphereRadius = convertedRadius().roundToDouble();
final minX = math.max(-sphereRadius, -maxWidth / 2);
final minY = math.max(-sphereRadius, -maxHeight / 2);
final maxX = math.min(sphereRadius, maxWidth / 2);
final maxY = math.min(sphereRadius, maxHeight / 2);
final width = maxX - minX;
final height = maxY - minY;
final surfaceWidth = widget.controller.surface?.width.toDouble();
final surfaceHeight = widget.controller.surface?.height.toDouble();
final spherePixels = Uint32List(width.toInt() * height.toInt());
// Prepare rotation matrices
final rotationMatrixX = Matrix3.rotationX(math.pi / 2 - rotationX);
// final rotationMatrixY = Matrix3.rotationY(math.pi / 2 - rotationY);
final rotationMatrixZ = Matrix3.rotationZ(rotationZ + math.pi / 2);
final surfaceXRate = (surfaceWidth! - 1) / (2.0 * math.pi);
final surfaceYRate = (surfaceHeight! - 1) / math.pi;
for (var y = minY; y < maxY; y++) {
final sphereY = (height - y + minY - 1).toInt() * width;
for (var x = minX; x < maxX; x++) {
var zSquared = sphereRadius * sphereRadius - x * x - y * y;
if (zSquared > 0) {
var z = math.sqrt(zSquared);
var vector = Vector3(x, y, z);
// Apply rotations
vector = rotationMatrixX.transform(vector);
// vector = rotationMatrixY.transform(vector);
vector = rotationMatrixZ.transform(vector);
final lat = math.asin(vector.z / sphereRadius);
final lon = math.atan2(vector.y, vector.x);
final x0 = (lon + math.pi) * surfaceXRate;
final y0 = (math.pi / 2 - lat) * surfaceYRate;
// Bilinear interpolation for smoother texture mapping
final x0Floor = x0.floor();
final y0Floor = y0.floor();
final x0Ceil = (x0Floor + 1).clamp(0, surfaceWidth.toInt() - 1);
final y0Ceil = (y0Floor + 1).clamp(0, surfaceHeight.toInt() - 1);
final x0ClampedFloor = x0Floor.clamp(0, surfaceWidth.toInt() - 1);
final y0ClampedFloor = y0Floor.clamp(0, surfaceHeight.toInt() - 1);
final fx = x0 - x0Floor;
final fy = y0 - y0Floor;
final c00 = widget.controller.surfaceProcessed![
(y0ClampedFloor * surfaceWidth + x0ClampedFloor).toInt()];
final c10 = widget.controller.surfaceProcessed![
(y0ClampedFloor * surfaceWidth + x0Ceil).toInt()];
final c01 = widget.controller.surfaceProcessed![
(y0Ceil * surfaceWidth + x0ClampedFloor).toInt()];
final c11 = widget.controller.surfaceProcessed![
(y0Ceil * surfaceWidth + x0Ceil).toInt()];
// Extract RGBA components and interpolate
final r00 = (c00 >> 0) & 0xFF;
final g00 = (c00 >> 8) & 0xFF;
final b00 = (c00 >> 16) & 0xFF;
final a00 = (c00 >> 24) & 0xFF;
final r10 = (c10 >> 0) & 0xFF;
final g10 = (c10 >> 8) & 0xFF;
final b10 = (c10 >> 16) & 0xFF;
final a10 = (c10 >> 24) & 0xFF;
final r01 = (c01 >> 0) & 0xFF;
final g01 = (c01 >> 8) & 0xFF;
final b01 = (c01 >> 16) & 0xFF;
final a01 = (c01 >> 24) & 0xFF;
final r11 = (c11 >> 0) & 0xFF;
final g11 = (c11 >> 8) & 0xFF;
final b11 = (c11 >> 16) & 0xFF;
final a11 = (c11 >> 24) & 0xFF;
// Bilinear interpolation
final r = ((r00 * (1 - fx) + r10 * fx) * (1 - fy) +
(r01 * (1 - fx) + r11 * fx) * fy).round().clamp(0, 255);
final g = ((g00 * (1 - fx) + g10 * fx) * (1 - fy) +
(g01 * (1 - fx) + g11 * fx) * fy).round().clamp(0, 255);
final b = ((b00 * (1 - fx) + b10 * fx) * (1 - fy) +
(b01 * (1 - fx) + b11 * fx) * fy).round().clamp(0, 255);
final a = ((a00 * (1 - fx) + a10 * fx) * (1 - fy) +
(a01 * (1 - fx) + a11 * fx) * fy).round().clamp(0, 255);
final color = (a << 24) | (b << 16) | (g << 8) | r;
spherePixels[(sphereY + x - minX).toInt()] = color;
}
}
}
final completer = Completer<SphereImage>();
ui.decodeImageFromPixels(spherePixels.buffer.asUint8List(), width.toInt(),
height.toInt(), ui.PixelFormat.rgba8888, (image) {
final sphereImage = SphereImage(
image: image,
radius: sphereRadius,
origin: Offset(-minX, -minY),
offset: Offset(maxWidth / 2, maxHeight / 2),
);
completer.complete(sphereImage);
});
return completer.future;
}