| /* | |
| * Copyright 2011 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkRRect.h" | |
| #include "include/core/SkShader.h" | |
| #include "src/core/SkColorPriv.h" | |
| #include "src/core/SkPathPriv.h" | |
| #include "src/core/SkRandom.h" | |
| #include "tools/DecodeUtils.h" | |
| #include "tools/Resources.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include "tools/viewer/ClickHandlerSlide.h" | |
| #include "tools/viewer/Slide.h" | |
| constexpr int W = 150; | |
| constexpr int H = 200; | |
| static void show_text(SkCanvas* canvas, bool doAA) { | |
| SkRandom rand; | |
| SkPaint paint; | |
| SkFont font(ToolUtils::DefaultTypeface(), 20); | |
| font.setEdging(doAA ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAlias); | |
| for (int i = 0; i < 200; ++i) { | |
| paint.setColor((SK_A32_MASK << SK_A32_SHIFT) | rand.nextU()); | |
| canvas->drawString("Hamburgefons", rand.nextSScalar1() * W, rand.nextSScalar1() * H + 20, | |
| font, paint); | |
| } | |
| } | |
| static void show_fill(SkCanvas* canvas, bool doAA) { | |
| SkRandom rand; | |
| SkPaint paint; | |
| paint.setAntiAlias(doAA); | |
| for (int i = 0; i < 50; ++i) { | |
| SkRect r; | |
| r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, | |
| rand.nextUScalar1() * W, rand.nextUScalar1() * H); | |
| paint.setColor(rand.nextU()); | |
| canvas->drawRect(r, paint); | |
| r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, | |
| rand.nextUScalar1() * W, rand.nextUScalar1() * H); | |
| paint.setColor(rand.nextU()); | |
| canvas->drawOval(r, paint); | |
| } | |
| } | |
| static SkScalar randRange(SkRandom& rand, SkScalar min, SkScalar max) { | |
| SkASSERT(min <= max); | |
| return min + rand.nextUScalar1() * (max - min); | |
| } | |
| static void show_stroke(SkCanvas* canvas, bool doAA, SkScalar strokeWidth, int n) { | |
| SkRandom rand; | |
| SkPaint paint; | |
| paint.setAntiAlias(doAA); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(strokeWidth); | |
| for (int i = 0; i < n; ++i) { | |
| SkRect r; | |
| r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, | |
| rand.nextUScalar1() * W, rand.nextUScalar1() * H); | |
| paint.setColor(rand.nextU()); | |
| canvas->drawRect(r, paint); | |
| r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, | |
| rand.nextUScalar1() * W, rand.nextUScalar1() * H); | |
| paint.setColor(rand.nextU()); | |
| canvas->drawOval(r, paint); | |
| const SkScalar minx = -SkIntToScalar(W)/4; | |
| const SkScalar maxx = 5*SkIntToScalar(W)/4; | |
| const SkScalar miny = -SkIntToScalar(H)/4; | |
| const SkScalar maxy = 5*SkIntToScalar(H)/4; | |
| paint.setColor(rand.nextU()); | |
| canvas->drawLine(randRange(rand, minx, maxx), randRange(rand, miny, maxy), | |
| randRange(rand, minx, maxx), randRange(rand, miny, maxy), | |
| paint); | |
| } | |
| } | |
| static void show_hair(SkCanvas* canvas, bool doAA) { | |
| show_stroke(canvas, doAA, 0, 150); | |
| } | |
| static void show_thick(SkCanvas* canvas, bool doAA) { | |
| show_stroke(canvas, doAA, SkIntToScalar(5), 50); | |
| } | |
| typedef void (*CanvasProc)(SkCanvas*, bool); | |
| class ClipSlide : public Slide { | |
| public: | |
| ClipSlide() { fName = "Clip"; } | |
| void draw(SkCanvas* canvas) override { | |
| canvas->drawColor(SK_ColorWHITE); | |
| canvas->translate(SkIntToScalar(20), SkIntToScalar(20)); | |
| static constexpr auto gProc = std::to_array<CanvasProc>({ | |
| show_text, | |
| show_thick, | |
| show_hair, | |
| show_fill, | |
| }); | |
| SkRect r = { 0, 0, SkIntToScalar(W), SkIntToScalar(H) }; | |
| r.inset(SK_Scalar1 / 4, SK_Scalar1 / 4); | |
| SkPath clipPath = SkPathBuilder().addRRect(SkRRect::MakeRectXY(r, 20, 20)).detach(); | |
| // clipPath.toggleInverseFillType(); | |
| for (int aa = 0; aa <= 1; ++aa) { | |
| canvas->save(); | |
| for (size_t i = 0; i < std::size(gProc); ++i) { | |
| canvas->save(); | |
| canvas->clipPath(clipPath, SkClipOp::kIntersect, SkToBool(aa)); | |
| // canvas->drawColor(SK_ColorWHITE); | |
| gProc[i](canvas, SkToBool(aa)); | |
| canvas->restore(); | |
| canvas->translate(W * SK_Scalar1 * 8 / 7, 0); | |
| } | |
| canvas->restore(); | |
| canvas->translate(0, H * SK_Scalar1 * 8 / 7); | |
| } | |
| } | |
| }; | |
| DEF_SLIDE( return new ClipSlide(); ) | |
| /////////////////////////////////////////////////////////////////////////////// | |
| struct SkHalfPlane { | |
| SkScalar fA, fB, fC; | |
| SkScalar eval(SkScalar x, SkScalar y) const { | |
| return fA * x + fB * y + fC; | |
| } | |
| SkScalar operator()(SkScalar x, SkScalar y) const { return this->eval(x, y); } | |
| bool twoPts(SkPoint pts[2]) const { | |
| // normalize plane to help with the perpendicular step, below | |
| SkScalar len = SkScalarSqrt(fA*fA + fB*fB); | |
| if (!len) { | |
| return false; | |
| } | |
| SkScalar denom = SkScalarInvert(len); | |
| SkScalar a = fA * denom; | |
| SkScalar b = fB * denom; | |
| SkScalar c = fC * denom; | |
| // We compute p0 on the half-plane by setting one of the components to 0 | |
| // We compute p1 by stepping from p0 along a perpendicular to the normal | |
| if (b) { | |
| pts[0] = { 0, -c / b }; | |
| pts[1] = { b, pts[0].fY - a}; | |
| } else if (a) { | |
| pts[0] = { -c / a, 0 }; | |
| pts[1] = { pts[0].fX + b, -a }; | |
| } else { | |
| return false; | |
| } | |
| SkASSERT(SkScalarNearlyZero(this->operator()(pts[0].fX, pts[0].fY))); | |
| SkASSERT(SkScalarNearlyZero(this->operator()(pts[1].fX, pts[1].fY))); | |
| return true; | |
| } | |
| enum Result { | |
| kAllNegative, | |
| kAllPositive, | |
| kMixed | |
| }; | |
| Result test(const SkRect& bounds) const { | |
| SkPoint diagMin, diagMax; | |
| if (fA >= 0) { | |
| diagMin.fX = bounds.fLeft; | |
| diagMax.fX = bounds.fRight; | |
| } else { | |
| diagMin.fX = bounds.fRight; | |
| diagMax.fX = bounds.fLeft; | |
| } | |
| if (fB >= 0) { | |
| diagMin.fY = bounds.fTop; | |
| diagMax.fY = bounds.fBottom; | |
| } else { | |
| diagMin.fY = bounds.fBottom; | |
| diagMax.fY = bounds.fTop; | |
| } | |
| SkScalar test = this->eval(diagMin.fX, diagMin.fY); | |
| SkScalar sign = test*this->eval(diagMax.fX, diagMin.fY); | |
| if (sign > 0) { | |
| // the path is either all on one side of the half-plane or the other | |
| if (test < 0) { | |
| return kAllNegative; | |
| } else { | |
| return kAllPositive; | |
| } | |
| } | |
| return kMixed; | |
| } | |
| }; | |
| #include "src/core/SkEdgeClipper.h" | |
| #include <array> | |
| static SkPath clip(const SkPath& path, SkPoint p0, SkPoint p1) { | |
| SkMatrix mx, inv; | |
| SkVector v = p1 - p0; | |
| mx.setAll(v.fX, -v.fY, p0.fX, | |
| v.fY, v.fX, p0.fY, | |
| 0, 0, 1); | |
| SkAssertResult(mx.invert(&inv)); | |
| SkPath rotated = path.makeTransform(inv); | |
| SkScalar big = 1e28f; | |
| SkRect clip = {-big, 0, big, big }; | |
| struct Rec { | |
| SkPathBuilder fResult; | |
| SkPoint fPrev = {0, 0}; | |
| } rec; | |
| SkEdgeClipper::ClipPath(SkPathPriv::Raw(rotated, SkResolveConvexity::kNo).value(), clip, false, | |
| [](SkEdgeClipper* clipper, bool newCtr, void* ctx) { | |
| Rec* rec = (Rec*)ctx; | |
| bool addLineTo = false; | |
| SkPoint pts[4]; | |
| while (auto verb = clipper->next(pts)) { | |
| if (newCtr) { | |
| rec->fResult.moveTo(pts[0]); | |
| rec->fPrev = pts[0]; | |
| newCtr = false; | |
| } | |
| if (addLineTo || pts[0] != rec->fPrev) { | |
| rec->fResult.lineTo(pts[0]); | |
| } | |
| switch (*verb) { | |
| case SkPathVerb::kLine: | |
| rec->fResult.lineTo(pts[1]); | |
| rec->fPrev = pts[1]; | |
| break; | |
| case SkPathVerb::kQuad: | |
| rec->fResult.quadTo(pts[1], pts[2]); | |
| rec->fPrev = pts[2]; | |
| break; | |
| case SkPathVerb::kCubic: | |
| rec->fResult.cubicTo(pts[1], pts[2], pts[3]); | |
| rec->fPrev = pts[3]; | |
| break; | |
| default: break; | |
| } | |
| addLineTo = true; | |
| } | |
| }, &rec); | |
| return rec.fResult.detach().makeTransform(mx); | |
| } | |
| static void draw_halfplane(SkCanvas* canvas, SkPoint p0, SkPoint p1, SkColor c) { | |
| SkVector v = p1 - p0; | |
| p0 = p0 - v * 1000; | |
| p1 = p1 + v * 1000; | |
| SkPaint paint; | |
| paint.setColor(c); | |
| canvas->drawLine(p0, p1, paint); | |
| } | |
| static SkPath make_path() { | |
| SkRandom rand; | |
| auto rand_pt = [&rand]() { | |
| auto x = rand.nextF(); | |
| auto y = rand.nextF(); | |
| return SkPoint{x * 400, y * 400}; | |
| }; | |
| SkPathBuilder path; | |
| for (int i = 0; i < 4; ++i) { | |
| SkPoint pts[6]; | |
| for (auto& p : pts) { | |
| p = rand_pt(); | |
| } | |
| path.moveTo(pts[0]).quadTo(pts[1], pts[2]).quadTo(pts[3], pts[4]).lineTo(pts[5]); | |
| } | |
| return path.detach(); | |
| } | |
| class HalfPlaneSlide : public ClickHandlerSlide { | |
| SkPoint fPts[2]; | |
| SkPath fPath; | |
| public: | |
| HalfPlaneSlide() { fName = "halfplane"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fPts[0] = {0, 0}; | |
| fPts[1] = {3, 2}; | |
| fPath = make_path(); | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| paint.setColor({0.5f, 0.5f, 0.5f, 1.0f}, nullptr); | |
| canvas->drawPath(fPath, paint); | |
| paint.setColor({0, 0, 0, 1}, nullptr); | |
| canvas->drawPath(clip(fPath, fPts[0], fPts[1]), paint); | |
| draw_halfplane(canvas, fPts[0], fPts[1], SK_ColorRED); | |
| } | |
| Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { | |
| return new Click; | |
| } | |
| bool onClick(Click* click) override { | |
| fPts[0] = click->fCurr; | |
| fPts[1] = fPts[0] + SkPoint{3, 2}; | |
| return true; | |
| } | |
| }; | |
| DEF_SLIDE( return new HalfPlaneSlide(); ) | |
| static void draw_halfplane(SkCanvas* canvas, const SkHalfPlane& p, SkColor c) { | |
| SkPoint pts[2]; | |
| p.twoPts(pts); | |
| draw_halfplane(canvas, pts[0], pts[1], c); | |
| } | |
| static void compute_half_planes(const SkMatrix& mx, SkScalar width, SkScalar height, | |
| SkHalfPlane planes[4]) { | |
| SkScalar a = mx[0], b = mx[1], c = mx[2], | |
| d = mx[3], e = mx[4], f = mx[5], | |
| g = mx[6], h = mx[7], i = mx[8]; | |
| planes[0] = { 2*g - 2*a/width, 2*h - 2*b/width, 2*i - 2*c/width }; | |
| planes[1] = { 2*a/width, 2*b/width, 2*c/width }; | |
| planes[2] = { 2*g - 2*d/height, 2*h - 2*e/height, 2*i - 2*f/height }; | |
| planes[3] = { 2*d/height, 2*e/height, 2*f/height }; | |
| } | |
| class HalfPlaneSlide2 : public ClickHandlerSlide { | |
| std::array<SkPoint, 4> fPts; | |
| SkPath fPath; | |
| public: | |
| HalfPlaneSlide2() { fName = "halfplane2"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fPath = make_path(); | |
| fPts = fPath.getBounds().toQuad(); | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| SkMatrix mx; | |
| { | |
| const std::array<SkPoint, 4> src = fPath.getBounds().toQuad(); | |
| mx.setPolyToPoly(src, fPts); | |
| } | |
| SkPaint paint; | |
| canvas->drawPath(fPath, paint); | |
| canvas->save(); | |
| canvas->concat(mx); | |
| paint.setColor(0x40FF0000); | |
| canvas->drawPath(fPath, paint); | |
| canvas->restore(); | |
| // draw the frame | |
| paint.setStrokeWidth(10); | |
| paint.setColor(SK_ColorGREEN); | |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, fPts, paint); | |
| // draw the half-planes | |
| SkHalfPlane planes[4]; | |
| compute_half_planes(mx, 400, 400, planes); | |
| for (auto& p : planes) { | |
| draw_halfplane(canvas, p, SK_ColorRED); | |
| } | |
| } | |
| Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { | |
| SkScalar r = 8; | |
| SkRect rect = SkRect::MakeXYWH(x - r, y - r, 2*r, 2*r); | |
| for (int i = 0; i < 4; ++i) { | |
| if (rect.contains(fPts[i].fX, fPts[i].fY)) { | |
| Click* c = new Click; | |
| c->fMeta.setS32("index", i); | |
| return c; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool onClick(Click* click) override { | |
| int32_t index; | |
| SkAssertResult(click->fMeta.findS32("index", &index)); | |
| SkASSERT(index >= 0 && index < 4); | |
| fPts[index] = click->fCurr; | |
| return true; | |
| } | |
| }; | |
| DEF_SLIDE( return new HalfPlaneSlide2(); ) | |
| static SkM44 inv(const SkM44& m) { | |
| SkM44 inverse; | |
| SkAssertResult(m.invert(&inverse)); | |
| return inverse; | |
| } | |
| static SkHalfPlane half_plane_w0(const SkMatrix& m) { | |
| return { m[SkMatrix::kMPersp0], m[SkMatrix::kMPersp1], m[SkMatrix::kMPersp2] - 0.05f }; | |
| } | |
| class CameraSlide : public ClickHandlerSlide { | |
| float fNear = 0.05f; | |
| float fFar = 4; | |
| float fAngle = SK_ScalarPI / 4; | |
| SkV3 fEye { 0, 0, 1.0f/std::tan(fAngle/2) - 1 }; | |
| SkV3 fCOA { 0, 0, 0 }; | |
| SkV3 fUp { 0, 1, 0 }; | |
| SkM44 fRot; | |
| SkV3 fTrans; | |
| void rotate(float x, float y, float z) { | |
| SkM44 r; | |
| if (x) { | |
| r.setRotateUnit({1, 0, 0}, x); | |
| } else if (y) { | |
| r.setRotateUnit({0, 1, 0}, y); | |
| } else { | |
| r.setRotateUnit({0, 0, 1}, z); | |
| } | |
| fRot = r * fRot; | |
| } | |
| public: | |
| SkM44 get44(const SkRect& r) const { | |
| SkScalar w = r.width(); | |
| SkScalar h = r.height(); | |
| SkM44 camera = SkM44::LookAt(fEye, fCOA, fUp), | |
| perspective = SkM44::Perspective(fNear, fFar, fAngle), | |
| translate = SkM44::Translate(fTrans.x, fTrans.y, fTrans.z), | |
| viewport = SkM44::Translate(r.centerX(), r.centerY(), 0) * | |
| SkM44::Scale(w*0.5f, h*0.5f, 1); | |
| return viewport * perspective * camera * translate * fRot * inv(viewport); | |
| } | |
| bool onChar(SkUnichar uni) override { | |
| float delta = SK_ScalarPI / 30; | |
| switch (uni) { | |
| case '8': this->rotate( delta, 0, 0); return true; | |
| case '2': this->rotate(-delta, 0, 0); return true; | |
| case '4': this->rotate(0, delta, 0); return true; | |
| case '6': this->rotate(0, -delta, 0); return true; | |
| case '-': this->rotate(0, 0, delta); return true; | |
| case '+': this->rotate(0, 0, -delta); return true; | |
| case 'i': fTrans.z += 0.1f; SkDebugf("z %g\n", fTrans.z); return true; | |
| case 'k': fTrans.z -= 0.1f; SkDebugf("z %g\n", fTrans.z); return true; | |
| case 'n': fNear += 0.1f; SkDebugf("near %g\n", fNear); return true; | |
| case 'N': fNear -= 0.1f; SkDebugf("near %g\n", fNear); return true; | |
| case 'f': fFar += 0.1f; SkDebugf("far %g\n", fFar); return true; | |
| case 'F': fFar -= 0.1f; SkDebugf("far %g\n", fFar); return true; | |
| default: break; | |
| } | |
| return false; | |
| } | |
| }; | |
| class HalfPlaneSlide3 : public CameraSlide { | |
| SkPath fPath; | |
| sk_sp<SkShader> fShader; | |
| bool fShowUnclipped = false; | |
| public: | |
| HalfPlaneSlide3() { fName = "halfplane3"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fPath = make_path(); | |
| fShader = ToolUtils::GetResourceAsImage("images/mandrill_128.png") | |
| ->makeShader(SkSamplingOptions(), SkMatrix::Scale(3, 3)); | |
| } | |
| bool onChar(SkUnichar uni) override { | |
| switch (uni) { | |
| case 'u': fShowUnclipped = !fShowUnclipped; return true; | |
| default: break; | |
| } | |
| return this->CameraSlide::onChar(uni); | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| SkM44 mx = this->get44({0, 0, 400, 400}); | |
| SkPaint paint; | |
| paint.setColor({0.75, 0.75, 0.75, 1}); | |
| canvas->drawPath(fPath, paint); | |
| paint.setShader(fShader); | |
| if (fShowUnclipped) { | |
| canvas->save(); | |
| canvas->concat(mx); | |
| paint.setAlphaf(0.33f); | |
| canvas->drawPath(fPath, paint); | |
| paint.setAlphaf(1.f); | |
| canvas->restore(); | |
| } | |
| SkColor planeColor = SK_ColorBLUE; | |
| SkPath clippedPath, *path = &fPath; | |
| if (SkPathPriv::PerspectiveClip(fPath, mx.asM33(), &clippedPath)) { | |
| path = &clippedPath; | |
| planeColor = SK_ColorRED; | |
| } | |
| canvas->save(); | |
| canvas->concat(mx); | |
| canvas->drawPath(*path, paint); | |
| canvas->restore(); | |
| SkHalfPlane hpw = half_plane_w0(mx.asM33()); | |
| draw_halfplane(canvas, hpw, planeColor); | |
| } | |
| protected: | |
| Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override { | |
| return nullptr; | |
| } | |
| bool onClick(Click* click) override { return false; } | |
| }; | |
| DEF_SLIDE( return new HalfPlaneSlide3(); ) | |
| class HalfPlaneCoonsSlide : public CameraSlide { | |
| SkPoint fPatch[12]; | |
| SkColor fColors[4] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK }; | |
| SkPoint fTex[4] = {{0, 0}, {256, 0}, {256, 256}, {0, 256}}; | |
| sk_sp<SkShader> fShader; | |
| bool fShowHandles = false; | |
| bool fShowSkeleton = false; | |
| bool fShowTex = false; | |
| public: | |
| HalfPlaneCoonsSlide() { fName = "halfplane-coons"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fPatch[0] = { 0, 0 }; | |
| fPatch[1] = { 100, 0 }; | |
| fPatch[2] = { 200, 0 }; | |
| fPatch[3] = { 300, 0 }; | |
| fPatch[4] = { 300, 100 }; | |
| fPatch[5] = { 300, 200 }; | |
| fPatch[6] = { 300, 300 }; | |
| fPatch[7] = { 200, 300 }; | |
| fPatch[8] = { 100, 300 }; | |
| fPatch[9] = { 0, 300 }; | |
| fPatch[10] = { 0, 200 }; | |
| fPatch[11] = { 0, 100 }; | |
| fShader = ToolUtils::GetResourceAsImage("images/mandrill_256.png") | |
| ->makeShader(SkSamplingOptions()); | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| canvas->save(); | |
| canvas->concat(this->get44({0, 0, 300, 300})); | |
| const SkPoint* tex = nullptr; | |
| const SkColor* col = nullptr; | |
| if (!fShowSkeleton) { | |
| if (fShowTex) { | |
| paint.setShader(fShader); | |
| tex = fTex; | |
| } else { | |
| col = fColors; | |
| } | |
| } | |
| canvas->drawPatch(fPatch, col, tex, SkBlendMode::kSrc, paint); | |
| paint.setShader(nullptr); | |
| if (fShowHandles) { | |
| paint.setAntiAlias(true); | |
| paint.setStrokeCap(SkPaint::kRound_Cap); | |
| paint.setStrokeWidth(8); | |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, fPatch, paint); | |
| paint.setColor(SK_ColorWHITE); | |
| paint.setStrokeWidth(6); | |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, fPatch, paint); | |
| } | |
| canvas->restore(); | |
| } | |
| bool onChar(SkUnichar uni) override { | |
| switch (uni) { | |
| case 'h': fShowHandles = !fShowHandles; return true; | |
| case 'k': fShowSkeleton = !fShowSkeleton; return true; | |
| case 't': fShowTex = !fShowTex; return true; | |
| default: break; | |
| } | |
| return this->CameraSlide::onChar(uni); | |
| } | |
| protected: | |
| Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { | |
| auto dist = [](SkPoint a, SkPoint b) { return (b - a).length(); }; | |
| const float tol = 15; | |
| for (int i = 0; i < 12; ++i) { | |
| if (dist({x,y}, fPatch[i]) <= tol) { | |
| return new Click([this, i](Click* c) { | |
| fPatch[i] = c->fCurr; | |
| return true; | |
| }); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool onClick(Click* click) override { return false; } | |
| }; | |
| DEF_SLIDE( return new HalfPlaneCoonsSlide(); ) |