Skip to content

Commit

Permalink
Add sample spline
Browse files Browse the repository at this point in the history
  • Loading branch information
twangodev committed Aug 6, 2024
1 parent 3f9f036 commit 11047ca
Show file tree
Hide file tree
Showing 22 changed files with 323 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class OrganizationStructureData {
class OrganizationStructure {
static const String metadata = 'metadata.json';

static const String toolExtension = '.tool';
Expand Down
File renamed without changes.
75 changes: 75 additions & 0 deletions lib/extensions/canvas_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';

import '../geometry/fit_point_spline.dart';
import '../geometry/point.dart';
import '../widgets/editor/editor_config.dart';

extension CanvasExtension on Canvas {
void drawPoint(Point point, double scaleInverse) {
final Paint paint = Paint()
..color = Colors.white
..strokeWidth = 2 * scaleInverse
..style = PaintingStyle.fill;

final Paint outlinePaint = Paint()
..color = Colors.grey.shade900
..strokeWidth = 2 * scaleInverse
..style = PaintingStyle.stroke;

double radius = EditorConfig.pointRadius * scaleInverse;
drawCircle(point.toOffset(), radius, paint);
drawCircle(point.toOffset(), radius, outlinePaint);
}

// TODO figure out a way to draw the spline (auto resolving the Start/End points) - remove the parameters start and end
void drawFitPointSpline(FitPointSpline spline, FixedPoint a, FixedPoint b,
double scaleInverse, bool showControlPoints) {
final Paint splinePaint = Paint()
..color = Colors.white
..strokeWidth = 2 * scaleInverse
..style = PaintingStyle.stroke;

final Paint tangentHandleLinePaint = Paint()
..color = Colors.white
..strokeWidth = 1 * scaleInverse
..style = PaintingStyle.stroke;

List<VoidCallback> topLayer = [];

Path path = Path();
path.moveTo(a.x, a.y);
FixedPoint lastOut = a + spline.aRelativeOut;

for (int i = 0; i < spline.controlPoints.length; i++) {
ControlPoint current = spline.controlPoints[i];
FixedPoint inTangent = current.inTangent;

path.cubicTo(
lastOut.x, lastOut.y, inTangent.x, inTangent.y, current.x, current.y);

lastOut = current.outTangent;

if (!showControlPoints) continue;

FixedPoint outTangent = current.outTangent;
topLayer.add(() {
drawPoint(current, scaleInverse);

drawPoint(inTangent, scaleInverse);
drawPoint(outTangent, scaleInverse);
drawLine(inTangent.toOffset(), outTangent.toOffset(),
tangentHandleLinePaint);
});
}

FixedPoint bIn = b + spline.bRelativeIn;

path.cubicTo(lastOut.x, lastOut.y, bIn.x, bIn.y, b.x, b.y);

drawPath(path, splinePaint);

for (VoidCallback topLayerAction in topLayer) {
topLayerAction();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
extension ListExtensions<T> on List<T> {
extension ListExtension<T> on List<T> {
T removeFirst() {
return removeAt(0);
}

T get secondLast {
return this[length - 2]; // Return the second to last element
}
Expand Down
7 changes: 7 additions & 0 deletions lib/geometry/curve.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:toolfoam/models/json_serializable.dart';
import 'package:toolfoam/models/tf_id.dart';

abstract class Curve implements JsonSerializable {
abstract TfId a;
abstract TfId b;
}
47 changes: 47 additions & 0 deletions lib/geometry/fit_point_spline.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:toolfoam/geometry/point.dart';
import 'package:toolfoam/geometry/tangent_handle.dart';
import 'package:toolfoam/models/tf_id.dart';

import 'curve.dart';

class FitPointSpline extends Curve {
@override
TfId a;

@override
TfId b;

// Tangents at the start and end of the spline (relative to A and B)
FixedPoint aRelativeOut;
FixedPoint bRelativeIn;

List<ControlPoint> controlPoints = [];

FitPointSpline(
this.a, this.aRelativeOut, this.b, this.bRelativeIn, this.controlPoints);

factory FitPointSpline.auto(TfId aId, TfId bId, FixedPoint aPoint,
FixedPoint bPoint, List<FixedPoint> points) {
if (points.isEmpty) {
FixedPoint mid = (aPoint + bPoint) / 2;
return FitPointSpline(aId, aPoint - mid, bId, bPoint - mid, []);
}

List<ControlPoint> controlPoints =
ControlPoint.autoTangent(points, aPoint, bPoint);
FixedPoint aRelativeOut =
TangentHandle.autoNeighbours(aPoint, controlPoints.first)
.relativeInTangent;
FixedPoint bRelativeIn =
TangentHandle.autoNeighbours(controlPoints.last, bPoint)
.relativeOutTangent;

return FitPointSpline(aId, aRelativeOut, bId, bRelativeIn, controlPoints);
}

@override
Map<String, dynamic> toJson() {
// TODO: implement toJson
throw UnimplementedError();
}
}
35 changes: 34 additions & 1 deletion lib/geometry/point.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:toolfoam/geometry/tangent_handle.dart';
import 'package:toolfoam/models/json_serializable.dart';
import 'package:vector_math/vector_math_64.dart';

Expand Down Expand Up @@ -29,13 +30,14 @@ abstract class Point implements JsonSerializable {

double get distanceSquared => x * x + y * y;
double get distance => sqrt(distanceSquared);
FixedPoint get normalized => this / distance;
FixedPoint get unit => this / distance;
FixedPoint get perpendicular => FixedPoint(-y, x);

FixedPoint operator +(Point other) => FixedPoint(x + other.x, y + other.y);
FixedPoint operator -(Point other) => FixedPoint(x - other.x, y - other.y);
FixedPoint operator *(double scalar) => FixedPoint(x * scalar, y * scalar);
FixedPoint operator /(double scalar) => FixedPoint(x / scalar, y / scalar);
FixedPoint operator -() => FixedPoint(-x, -y);
bool operator <(Point other) => distanceSquared < other.distanceSquared;
bool operator <=(Point other) => distanceSquared <= other.distanceSquared;
bool operator >(Point other) => distanceSquared > other.distanceSquared;
Expand All @@ -62,4 +64,35 @@ class FixedPoint extends Point {
factory FixedPoint.fromOffset(Offset offset) {
return FixedPoint(offset.dx, offset.dy);
}

factory FixedPoint.fromVector2(Vector2 vector) {
return FixedPoint(vector.x, vector.y);
}
}

class ControlPoint extends FixedPoint {
final TangentHandle handle;

ControlPoint(super.x, super.y, this.handle);

FixedPoint get inTangent => this + handle.relativeInTangent;
FixedPoint get outTangent => this + handle.relativeOutTangent;

static List<ControlPoint> autoTangent(
List<FixedPoint> points, FixedPoint a, FixedPoint b) {
List<ControlPoint> controlPoints = [];

for (int i = 0; i < points.length; i++) {
FixedPoint point = points[i];
FixedPoint prev = point - (i == 0 ? a : points[i - 1]);
FixedPoint next = point - (i == points.length - 1 ? b : points[i + 1]);

TangentHandle handle = TangentHandle.autoNeighbours(prev, next);
controlPoints.add(ControlPoint(point.x, point.y, handle));

prev = point;
}

return controlPoints;
}
}
25 changes: 12 additions & 13 deletions lib/geometry/line.dart → lib/geometry/segment.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import 'package:flutter/material.dart';
import 'package:toolfoam/models/json_serializable.dart';
import 'package:toolfoam/models/tf_id.dart';

@immutable
class Line implements JsonSerializable {
final TfId a;
final TfId b;
import 'curve.dart';

const Line(this.a, this.b);
class Segment extends Curve {
@override
TfId a;

@override
TfId b;

Segment(this.a, this.b);

bool contains(TfId id) => id == a || id == b;

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! Line) return false;
if (other is! Segment) return false;
bool directlyEqual = a == other.a && b == other.b;
bool reverseEqual = a == other.b && b == other.a;
return directlyEqual || reverseEqual;
Expand All @@ -24,11 +26,8 @@ class Line implements JsonSerializable {
int get hashCode => a.hashCode * b.hashCode;

@override
String toString() => 'Line($a, $b)';
String toString() => 'Segment($a, $b)';

@override
Map<String, dynamic> toJson() => {
'start': a.toString(),
'end': b.toString(),
};
Map<String, dynamic> toJson() => {'start': a, 'end': b};
}
23 changes: 23 additions & 0 deletions lib/geometry/tangent_handle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:toolfoam/geometry/point.dart';

class TangentHandle {
FixedPoint inUnit;

double inMagnitude;
double outMagnitude;

FixedPoint get relativeInTangent => inUnit * -inMagnitude;
FixedPoint get relativeOutTangent => inUnit * outMagnitude;

TangentHandle(this.inUnit, this.inMagnitude, this.outMagnitude);

factory TangentHandle.autoNeighbours(Point a, Point b) {
FixedPoint tangent = (b - a) / 2;

double inMagnitude = tangent.distance / 3;
FixedPoint unit = tangent.unit;

FixedPoint inUnit = -unit;
return TangentHandle(inUnit, inMagnitude, inMagnitude);
}
}
6 changes: 4 additions & 2 deletions lib/models/editing_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ enum EditingTool {
),
rectangle(tooltip: 'Rectangle', icon: Symbols.rectangle_rounded),
circle(tooltip: 'Circle', icon: Symbols.circle_rounded),
spline(tooltip: 'Spline', icon: Symbols.conversion_path_rounded),
bezier(tooltip: 'Bezier', icon: Symbols.line_curve_rounded),
fitPointSpline(
tooltip: 'Fit Point Spline', icon: Symbols.conversion_path_rounded),
controlPointSpline(
tooltip: 'Control Point Spline', icon: Symbols.line_curve_rounded),
symmetry(tooltip: 'Symmetry', icon: Symbols.details),
autoNotch(tooltip: 'Auto Notch', icon: Symbols.shape_line_rounded);

Expand Down
4 changes: 4 additions & 0 deletions lib/models/fit_point_spline_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import '../geometry/fit_point_spline.dart';
import 'identifiable_json_map.dart';

class FitPointSplineData extends IdentifiableJsonHashMap<FitPointSpline> {}
2 changes: 1 addition & 1 deletion lib/models/fixed_point_data.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:toolfoam/geometry/point.dart';
import 'package:toolfoam/models/identifiable_json_bimap.dart';
import 'package:toolfoam/models/identifiable_json_map.dart';

class FixedPointData extends IdentifiableJsonBiMap<FixedPoint> {}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import 'dart:collection';

import 'package:flutter/material.dart';
import 'package:quiver/collection.dart';
import 'package:toolfoam/models/json_serializable.dart';
import 'package:toolfoam/models/tf_id.dart';

abstract class IdentifiableJsonBiMap<T extends JsonSerializable>
abstract class IdentifiableJsonMap<T extends JsonSerializable>
implements JsonSerializable {
@protected
final HashBiMap<TfId, T> map = HashBiMap();
abstract final Map<TfId, T> map;

IdentifiableJsonBiMap();
IdentifiableJsonMap();

Iterable<TfId> get ids => map.keys;
Iterable<T> get values => map.values;
Expand All @@ -18,7 +20,7 @@ abstract class IdentifiableJsonBiMap<T extends JsonSerializable>
bool containsValue(T value) => map.containsValue(value);

TfId add(T value) {
TfId? id = map.inverse[value];
TfId? id = getId(value);
if (id != null) {
return id;
}
Expand All @@ -34,7 +36,15 @@ abstract class IdentifiableJsonBiMap<T extends JsonSerializable>

T? remove(TfId id) => map.remove(id);

TfId? getId(T value) => map.inverse[value];
TfId? getId(T value) {
for (MapEntry entry in map.entries) {
TfId id = entry.key;
T v = entry.value;

if (value == v) return id;
}
return null;
}

MapEntry<String, dynamic> _transform(TfId id, T value) {
return MapEntry(id.toString(), value.toJson());
Expand All @@ -45,3 +55,20 @@ abstract class IdentifiableJsonBiMap<T extends JsonSerializable>
return map.map(_transform);
}
}

abstract class IdentifiableJsonHashMap<T extends JsonSerializable>
extends IdentifiableJsonMap<T> {
@override
final HashMap<TfId, T> map = HashMap();
}

abstract class IdentifiableJsonBiMap<T extends JsonSerializable>
extends IdentifiableJsonMap<T> {
@override
final BiMap<TfId, T> map = HashBiMap();

@override
TfId? getId(T value) {
return map.inverse[value];
}
}
6 changes: 3 additions & 3 deletions lib/models/line_data.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:toolfoam/geometry/line.dart';
import 'package:toolfoam/models/identifiable_json_bimap.dart';
import 'package:toolfoam/geometry/segment.dart';
import 'package:toolfoam/models/identifiable_json_map.dart';
import 'package:toolfoam/models/tf_id.dart';

class LineData extends IdentifiableJsonBiMap<Line> {
class LineData extends IdentifiableJsonHashMap<Segment> {
Iterable<TfId> dependsOn(TfId id) {
return map.entries
.where((entry) => entry.value.contains(id))
Expand Down
Loading

0 comments on commit 11047ca

Please sign in to comment.