diff --git a/ink/strokes/BUILD.bazel b/ink/strokes/BUILD.bazel index f7644341..856cf5be 100644 --- a/ink/strokes/BUILD.bazel +++ b/ink/strokes/BUILD.bazel @@ -28,6 +28,7 @@ cc_library( "//ink/brush:brush_coat", "//ink/brush:brush_family", "//ink/color", + "//ink/geometry:affine_transform", "//ink/geometry:mutable_mesh", "//ink/geometry:partitioned_mesh", "//ink/strokes/input:stroke_input_batch", diff --git a/ink/strokes/internal/jni/BUILD.bazel b/ink/strokes/internal/jni/BUILD.bazel index e5aae8da..cf149e94 100644 --- a/ink/strokes/internal/jni/BUILD.bazel +++ b/ink/strokes/internal/jni/BUILD.bazel @@ -40,6 +40,7 @@ cc_library( ":stroke_jni_helper", "//ink/brush", "//ink/brush/internal/jni:brush_native_helper", + "//ink/geometry:affine_transform", "//ink/geometry:partitioned_mesh", "//ink/geometry/internal/jni:partitioned_mesh_native_helper", "//ink/jni/internal:jni_defines", diff --git a/ink/strokes/internal/jni/stroke_jni.cc b/ink/strokes/internal/jni/stroke_jni.cc index a950fb04..6c7a471a 100644 --- a/ink/strokes/internal/jni/stroke_jni.cc +++ b/ink/strokes/internal/jni/stroke_jni.cc @@ -15,6 +15,7 @@ #include #include "ink/brush/internal/jni/brush_native_helper.h" +#include "ink/geometry/affine_transform.h" #include "ink/geometry/internal/jni/partitioned_mesh_native_helper.h" #include "ink/jni/internal/jni_defines.h" #include "ink/strokes/internal/jni/stroke_input_jni_helper.h" @@ -81,4 +82,28 @@ JNI_METHOD(strokes, StrokeNative, void, free) DeleteNativeStroke(native_pointer_to_stroke); } +JNI_METHOD(strokes, StrokeNative, jlongArray, partialErase) +(JNIEnv* env, jobject object, jlong target_stroke_ptr, jlong eraser_shape_ptr, + jfloat eraser_a, jfloat eraser_b, jfloat eraser_c, jfloat eraser_d, + jfloat eraser_e, jfloat eraser_f, jfloat stroke_a, jfloat stroke_b, + jfloat stroke_c, jfloat stroke_d, jfloat stroke_e, jfloat stroke_f) { + ink::AffineTransform eraser_transform(eraser_a, eraser_b, eraser_c, eraser_d, + eraser_e, eraser_f); + ink::AffineTransform stroke_transform(stroke_a, stroke_b, stroke_c, stroke_d, + stroke_e, stroke_f); + + std::vector fragments = + CastToStroke(target_stroke_ptr) + .PartialErase(CastToPartitionedMesh(eraser_shape_ptr), + eraser_transform, stroke_transform); + + jlongArray result = env->NewLongArray(fragments.size()); + jlong* elements = env->GetLongArrayElements(result, nullptr); + for (size_t i = 0; i < fragments.size(); ++i) { + elements[i] = NewNativeStroke(fragments[i]); + } + env->ReleaseLongArrayElements(result, elements, 0); + return result; +} + } // extern "C" diff --git a/ink/strokes/stroke.cc b/ink/strokes/stroke.cc index 5dba48a8..d2cffaec 100644 --- a/ink/strokes/stroke.cc +++ b/ink/strokes/stroke.cc @@ -212,4 +212,12 @@ void Stroke::RegenerateShape() { ABSL_DCHECK_EQ(shape_.RenderGroupCount(), brush_.CoatCount()); } +std::vector Stroke::PartialErase( + const PartitionedMesh& eraser_shape, + const AffineTransform& eraser_transform, + const AffineTransform& stroke_transform) const { + // TODO(b/504681427): Implement mesh subtraction. + return {*this}; +} + } // namespace ink diff --git a/ink/strokes/stroke.h b/ink/strokes/stroke.h index 4b4c71d7..775ed0ab 100644 --- a/ink/strokes/stroke.h +++ b/ink/strokes/stroke.h @@ -19,6 +19,7 @@ #include "ink/brush/brush.h" #include "ink/brush/brush_family.h" #include "ink/color/color.h" +#include "ink/geometry/affine_transform.h" #include "ink/geometry/partitioned_mesh.h" #include "ink/strokes/input/stroke_input_batch.h" #include "ink/types/duration.h" @@ -108,6 +109,19 @@ class Stroke { // shape if `inputs` is empty. void SetInputs(const StrokeInputBatch& inputs); + // Erases the `eraser_shape` from this stroke geometry using the given + // `eraser_transform` and `stroke_transform` that map the eraser and stroke + // to common coordinates. + // + // Each resulting fragment retains the original brush and inputs, but has a + // newly computed shape representing the portion remaining after erasure. The + // order of the fragments in the returned vector is arbitrary and carries no + // guarantee. + std::vector PartialErase( + const PartitionedMesh& eraser_shape, + const AffineTransform& eraser_transform, + const AffineTransform& stroke_transform) const; + private: // Regenerates the PartitionedMesh. void RegenerateShape(); diff --git a/ink/strokes/stroke_test.cc b/ink/strokes/stroke_test.cc index 25962faf..11ece3f4 100644 --- a/ink/strokes/stroke_test.cc +++ b/ink/strokes/stroke_test.cc @@ -608,5 +608,19 @@ void CanConstructStrokeFromAnyInputBatch(const Brush& brush, FUZZ_TEST(DISABLED_StrokeTest, CanConstructStrokeFromAnyInputBatch) .WithDomains(ValidBrush(), ArbitraryStrokeInputBatch()); +TEST(StrokeTest, PartialEraseWithEmptyEraserShapeReturnsStroke) { + Brush brush = CreateBrush(); + Stroke stroke(brush); + PartitionedMesh empty_eraser_shape; + AffineTransform identity = AffineTransform::Identity(); + + std::vector result = + stroke.PartialErase(empty_eraser_shape, identity, identity); + + ASSERT_THAT(result, SizeIs(1)); + EXPECT_THAT(result[0].GetBrush(), BrushEq(stroke.GetBrush())); + EXPECT_THAT(result[0].GetInputs(), StrokeInputBatchEq(stroke.GetInputs())); +} + } // namespace } // namespace ink