HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
graphic_path.hpp
1// Copyright Take Vos 2019-2021.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "bezier_point.hpp" // export
8#include "bezier_curve.hpp" // export
9#include "bezier.hpp" // export
10#include "../utility/utility.hpp"
11#include "../geometry/geometry.hpp"
12#include "../image/image.hpp"
13#include "../color/color.hpp"
14#include "../macros.hpp"
15#include <vector>
16#include <utility>
17#include <cmath>
18
19hi_export_module(hikogui.graphic_path);
20
21hi_export namespace hi { inline namespace v1 {
22
29hi_export struct graphic_path {
33
37
41
44 void clear() noexcept
45 {
46 points.clear();
47 contourEndPoints.clear();
48 layerEndContours.clear();
49 }
50
53 [[nodiscard]] ssize_t numberOfContours() const noexcept
54 {
55 return ssize(contourEndPoints);
56 }
57
60 [[nodiscard]] ssize_t numberOfLayers() const noexcept
61 {
62 return ssize(layerEndContours);
63 }
64
67 [[nodiscard]] bool allLayersHaveSameColor() const noexcept
68 {
69 if (!hasLayers()) {
70 return true;
71 }
72
73 auto const& firstColor = layerEndContours.front().second;
74
75 for (auto const & [ endContour, color ] : layerEndContours) {
76 if (color != firstColor) {
77 return false;
78 }
79 }
80 return true;
81 }
82
85 [[nodiscard]] aarectangle boundingBox() const noexcept
86 {
87 if (ssize(points) == 0) {
88 return aarectangle{0.0, 0.0, 0.0, 0.0};
89 }
90
91 auto r = aarectangle{points.front().p, points.front().p};
92
93 for (auto const& point : points) {
94 r |= point.p;
95 }
96
97 return r;
98 }
99
104 void tryRemoveLayers() noexcept
105 {
106 if (!hasLayers()) {
107 return;
108 }
109
110 if (!allLayersHaveSameColor()) {
111 return;
112 }
113
114 layerEndContours.clear();
115 }
116
119 [[nodiscard]] std::vector<bezier_point>::const_iterator beginContour(ssize_t contourNr) const noexcept
120 {
121 return points.begin() + (contourNr == 0 ? 0 : contourEndPoints.at(contourNr - 1) + 1);
122 }
123
124 /* Return and end-iterator beyond the end point of a contour.
125 */
126 [[nodiscard]] std::vector<bezier_point>::const_iterator endContour(ssize_t contourNr) const noexcept
127 {
128 return points.begin() + contourEndPoints.at(contourNr) + 1;
129 }
130
131 /* Return the first contour index of a layer.
132 */
133 [[nodiscard]] ssize_t beginLayer(ssize_t layerNr) const noexcept
134 {
135 return layerNr == 0 ? 0 : layerEndContours.at(layerNr - 1).first + 1;
136 }
137
138 /* Return beyond the last contour index of a layer.
139 */
140 [[nodiscard]] ssize_t endLayer(ssize_t layerNr) const noexcept
141 {
142 return layerEndContours.at(layerNr).first + 1;
143 }
144
145 [[nodiscard]] std::vector<bezier_point> getbezier_pointsOfContour(ssize_t contourNr) const noexcept
146 {
147 auto const begin = points.begin() + (contourNr == 0 ? 0 : contourEndPoints.at(contourNr - 1) + 1);
148 auto const end = points.begin() + contourEndPoints.at(contourNr) + 1;
149 return std::vector(begin, end);
150 }
151
152 [[nodiscard]] std::vector<bezier_curve> getBeziersOfContour(ssize_t contourNr) const noexcept
153 {
154 auto first = beginContour(contourNr);
155 auto last = endContour(contourNr);
156 auto num_points = std::distance(first, last);
157 if (num_points < 3) {
158 // Contours with less than three points do not have volume and are invisible.
159 // Contours with one point are used for anchors when compositing compound glyphs.
160 return {};
161 }
162
163 return makeContourFromPoints(beginContour(contourNr), endContour(contourNr));
164 }
165
166 [[nodiscard]] std::vector<bezier_curve> getBeziers() const noexcept
167 {
168 hi_assert(!hasLayers());
169
171
172 for (auto contourNr = 0; contourNr < numberOfContours(); contourNr++) {
173 auto const beziers = getBeziersOfContour(contourNr);
174 r.insert(r.end(), beziers.begin(), beziers.end());
175 }
176 return r;
177 }
178
179 [[nodiscard]] std::pair<graphic_path, color> getLayer(ssize_t layerNr) const noexcept
180 {
181 hi_assert(hasLayers());
182
183 auto path = graphic_path{};
184
185 auto const begin = beginLayer(layerNr);
186 auto const end = endLayer(layerNr);
187 for (ssize_t contourNr = begin; contourNr != end; contourNr++) {
188 path.addContour(beginContour(contourNr), endContour(contourNr));
189 }
190
191 return {path, getColorOfLayer(layerNr)};
192 }
193
194 [[nodiscard]] color getColorOfLayer(ssize_t layerNr) const noexcept
195 {
196 return layerEndContours.at(layerNr).second;
197 }
198
199 void setColorOfLayer(ssize_t layerNr, color fill_color) noexcept
200 {
201 layerEndContours.at(layerNr).second = fill_color;
202 }
203
206 [[nodiscard]] bool isContourOpen() const noexcept
207 {
208 if (points.size() == 0) {
209 return false;
210 } else if (contourEndPoints.size() == 0) {
211 return true;
212 } else {
213 return contourEndPoints.back() != (ssize(points) - 1);
214 }
215 }
216
220 void closeContour() noexcept
221 {
222 if (isContourOpen()) {
223 contourEndPoints.push_back(ssize(points) - 1);
224 }
225 }
226
229 [[nodiscard]] bool hasLayers() const noexcept
230 {
231 return numberOfLayers() > 0;
232 }
233
236 [[nodiscard]] bool isLayerOpen() const noexcept
237 {
238 if (points.size() == 0) {
239 return false;
240 } else if (isContourOpen()) {
241 return true;
242 } else if (layerEndContours.size() == 0) {
243 return true;
244 } else {
245 return layerEndContours.back().first != (ssize(contourEndPoints) - 1);
246 }
247 }
248
252 void closeLayer(color fill_color) noexcept
253 {
254 closeContour();
255 if (isLayerOpen()) {
256 layerEndContours.emplace_back(ssize(contourEndPoints) - 1, fill_color);
257 }
258 }
259
263 void optimizeLayers() noexcept
264 {
265 if (ssize(layerEndContours) == 0) {
266 return;
267 }
268
269 decltype(layerEndContours) tmp;
270 tmp.reserve(ssize(layerEndContours));
271
272 auto prev_i = layerEndContours.begin();
273 for (auto i = prev_i + 1; i != layerEndContours.end(); ++i) {
274 // Add the last layer of a contiguous color.
275 if (prev_i->second != i->second) {
276 tmp.push_back(*prev_i);
277 }
278
279 prev_i = i;
280 }
281 tmp.push_back(*prev_i);
282
284 }
285
289 [[nodiscard]] point2 currentPosition() const noexcept
290 {
291 if (isContourOpen()) {
292 return points.back().p;
293 } else {
294 return point2{};
295 }
296 }
297
301 void moveTo(point2 position) noexcept
302 {
303 closeContour();
304 points.emplace_back(position, bezier_point::Type::Anchor);
305 }
306
310 void moveRelativeTo(vector2 direction) noexcept
311 {
312 hi_assert(isContourOpen());
313
314 auto const lastPosition = currentPosition();
315 closeContour();
316 points.emplace_back(lastPosition + direction, bezier_point::Type::Anchor);
317 }
318
319 void lineTo(point2 position) noexcept
320 {
321 hi_assert(isContourOpen());
322
323 points.emplace_back(position, bezier_point::Type::Anchor);
324 }
325
326 void lineRelativeTo(vector2 direction) noexcept
327 {
328 hi_assert(isContourOpen());
329
330 points.emplace_back(currentPosition() + direction, bezier_point::Type::Anchor);
331 }
332
333 void quadraticCurveTo(point2 controlPosition, point2 position) noexcept
334 {
335 hi_assert(isContourOpen());
336
337 points.emplace_back(controlPosition, bezier_point::Type::QuadraticControl);
338 points.emplace_back(position, bezier_point::Type::Anchor);
339 }
340
345 void quadraticCurveRelativeTo(vector2 controlDirection, vector2 direction) noexcept
346 {
347 hi_assert(isContourOpen());
348
349 auto const p = currentPosition();
350 points.emplace_back(p + controlDirection, bezier_point::Type::QuadraticControl);
351 points.emplace_back(p + direction, bezier_point::Type::Anchor);
352 }
353
354 void cubicCurveTo(point2 controlPosition1, point2 controlPosition2, point2 position) noexcept
355 {
356 hi_assert(isContourOpen());
357
358 points.emplace_back(controlPosition1, bezier_point::Type::CubicControl1);
359 points.emplace_back(controlPosition2, bezier_point::Type::CubicControl2);
360 points.emplace_back(position, bezier_point::Type::Anchor);
361 }
362
368 void cubicCurveRelativeTo(vector2 controlDirection1, vector2 controlDirection2, vector2 direction) noexcept
369 {
370 hi_assert(isContourOpen());
371
372 auto const p = currentPosition();
373 points.emplace_back(p + controlDirection1, bezier_point::Type::CubicControl1);
374 points.emplace_back(p + controlDirection2, bezier_point::Type::CubicControl2);
375 points.emplace_back(p + direction, bezier_point::Type::Anchor);
376 }
377
389 void arcTo(float radius, point2 position) noexcept
390 {
391 hi_assert(isContourOpen());
392
393 auto const r = std::abs(radius);
394 auto const P1 = currentPosition();
395 auto const P2 = position;
396 auto const Pm = midpoint(P1, P2);
397
398 auto const Vm2 = P2 - Pm;
399
400 // Calculate the half angle between vectors P0 - C and P2 - C.
401 auto const alpha = std::asin(hypot(Vm2) / r);
402
403 // Calculate the center point C. As the length of the normal of Vm2 at Pm.
404 auto const C = Pm + normal(Vm2) * std::cos(alpha) * radius;
405
406 // Calculate vectors from center to end points.
407 auto const VC1 = P1 - C;
408 auto const VC2 = P2 - C;
409
410 auto const q1 = squared_hypot(VC1);
411 auto const q2 = q1 + dot(VC1, VC2);
412 auto const k2 = (4.0f / 3.0f) * (std::sqrt(2.0f * q1 * q2) - q2) / cross(VC1, VC2);
413
414 // Calculate the control points.
415 auto const C1 = point2{(C.x() + VC1.x()) - k2 * VC1.y(), (C.y() + VC1.y()) + k2 * VC1.x()};
416 auto const C2 = point2{(C.x() + VC2.x()) + k2 * VC2.y(), (C.y() + VC2.y()) - k2 * VC2.x()};
417
418 cubicCurveTo(C1, C2, P2);
419 }
420
426 void addRectangle(aarectangle rectangle, corner_radii corners = corner_radii{0.0f, 0.0f, 0.0f, 0.0f}) noexcept
427 {
428 hi_assert(!isContourOpen());
429
430 auto const bl_radius = std::abs(corners.left_bottom());
431 auto const br_radius = std::abs(corners.right_bottom());
432 auto const tl_radius = std::abs(corners.left_top());
433 auto const tr_radius = std::abs(corners.right_top());
434
435 auto const blc = get<0>(rectangle);
436 auto const brc = get<1>(rectangle);
437 auto const tlc = get<2>(rectangle);
438 auto const trc = get<3>(rectangle);
439
440 auto const blc1 = blc + vector2{0.0f, bl_radius};
441 auto const blc2 = blc + vector2{bl_radius, 0.0f};
442 auto const brc1 = brc + vector2{-br_radius, 0.0f};
443 auto const brc2 = brc + vector2{0.0f, br_radius};
444 auto const tlc1 = tlc + vector2{tl_radius, 0.0f};
445 auto const tlc2 = tlc + vector2{0.0f, -tl_radius};
446 auto const trc1 = trc + vector2{0.0f, -tr_radius};
447 auto const trc2 = trc + vector2{-tr_radius, 0.0f};
448
449 moveTo(blc1);
450 if (corners.left_bottom() > 0.0) {
451 arcTo(bl_radius, blc2);
452 } else if (corners.left_bottom() < 0.0) {
453 lineTo(blc2);
454 }
455
456 lineTo(brc1);
457 if (corners.right_bottom() > 0.0) {
458 arcTo(br_radius, brc2);
459 } else if (corners.right_bottom() < 0.0) {
460 lineTo(blc2);
461 }
462
463 lineTo(tlc1);
464 if (corners.left_top() > 0.0) {
465 arcTo(tl_radius, tlc2);
466 } else if (corners.left_top() < 0.0) {
467 lineTo(tlc2);
468 }
469
470 lineTo(trc1);
471 if (corners.right_top() > 0.0) {
472 arcTo(tr_radius, trc2);
473 } else if (corners.right_top() < 0.0) {
474 lineTo(trc2);
475 }
476
477 closeContour();
478 }
479
484 void addCircle(point2 position, float radius) noexcept
485 {
486 hi_assert(!isContourOpen());
487
488 moveTo(point2{position.x(), position.y() - radius});
489 arcTo(radius, point2{position.x() + radius, position.y()});
490 arcTo(radius, point2{position.x(), position.y() + radius});
491 arcTo(radius, point2{position.x() - radius, position.y()});
492 arcTo(radius, point2{position.x(), position.y() - radius});
493 closeContour();
494 }
495
499 void addContour(std::vector<bezier_curve> const& contour) noexcept
500 {
501 hi_assert(!isContourOpen());
502
503 for (auto const& curve : contour) {
504 // Don't emit the first point, the last point of the contour will wrap around.
505 switch (curve.type) {
506 case bezier_curve::Type::Linear:
507 points.emplace_back(curve.P2, bezier_point::Type::Anchor);
508 break;
509 case bezier_curve::Type::Quadratic:
510 points.emplace_back(curve.C1, bezier_point::Type::QuadraticControl);
511 points.emplace_back(curve.P2, bezier_point::Type::Anchor);
512 break;
513 case bezier_curve::Type::Cubic:
514 points.emplace_back(curve.C1, bezier_point::Type::CubicControl1);
515 points.emplace_back(curve.C2, bezier_point::Type::CubicControl2);
516 points.emplace_back(curve.P2, bezier_point::Type::Anchor);
517 break;
518 default:
519 hi_no_default();
520 }
521 }
522
523 closeContour();
524 }
525
530 std::vector<bezier_point>::const_iterator const& begin,
531 std::vector<bezier_point>::const_iterator const& end) noexcept
532 {
533 hi_assert(!isContourOpen());
534 points.insert(points.end(), begin, end);
535 closeContour();
536 }
537
541 void addContour(std::vector<bezier_point> const& contour) noexcept
542 {
543 addContour(contour.begin(), contour.end());
544 }
545
548 void addPath(graphic_path const& path, color fill_color) noexcept
549 {
550 *this += path;
551 closeLayer(fill_color);
552 }
553
557 graphic_path const& path,
558 color strokeColor,
559 float strokeWidth,
561 float tolerance = 0.05f) noexcept
562 {
563 *this += path.toStroke(strokeWidth, line_join_style, tolerance);
564 closeLayer(strokeColor);
565 }
566
578 [[nodiscard]] graphic_path toStroke(
579 float strokeWidth = 1.0f,
581 float tolerance = 0.05f) const noexcept
582 {
583 hi_assert(!hasLayers());
584 hi_assert(!isContourOpen());
585
586 auto r = graphic_path{};
587
588 float starboardOffset = strokeWidth / 2;
589 float portOffset = -starboardOffset;
590
591 for (int i = 0; i < numberOfContours(); i++) {
592 auto const baseContour = getBeziersOfContour(i);
593
594 auto const starboardContour = makeParallelContour(baseContour, starboardOffset, line_join_style, tolerance);
595 r.addContour(starboardContour);
596
597 auto const portContour = makeInverseContour(makeParallelContour(baseContour, portOffset, line_join_style, tolerance));
598 r.addContour(portContour);
599 }
600
601 return r;
602 }
603
606 [[nodiscard]] graphic_path centerScale(extent2 extent, float padding = 0.0) const noexcept
607 {
608 auto max_size =
609 extent2{std::max(1.0f, extent.width() - (padding * 2.0f)), std::max(1.0f, extent.height() - (padding * 2.0f))};
610
611 auto bbox = boundingBox();
612 if (bbox.width() <= 0.0 || bbox.height() <= 0.0) {
613 return {};
614 }
615
616 auto const scale = std::min(max_size.width() / bbox.width(), max_size.height() / bbox.height());
617 bbox = scale2(scale) * bbox;
618
619 auto const offset = (point2{} - get<0>(bbox)) + (extent - bbox.size()) * 0.5;
620
621 return (translate2(offset) * scale2(scale, scale)) * *this;
622 }
623
624 graphic_path& operator+=(graphic_path const& rhs) noexcept
625 {
626 hi_assert(!isContourOpen());
627 hi_assert(!rhs.isContourOpen());
628
629 // Left hand layer can only be open if the right hand side contains no layers.
630 hi_assert(!rhs.hasLayers() || !isLayerOpen());
631
632 auto const pointOffset = ssize(points);
633 auto const contourOffset = ssize(contourEndPoints);
634
635 layerEndContours.reserve(layerEndContours.size() + rhs.layerEndContours.size());
636 for (auto const & [ x, fill_color ] : rhs.layerEndContours) {
637 layerEndContours.emplace_back(contourOffset + x, fill_color);
638 }
639
640 contourEndPoints.reserve(contourEndPoints.size() + rhs.contourEndPoints.size());
641 for (auto const x : rhs.contourEndPoints) {
642 contourEndPoints.push_back(pointOffset + x);
643 }
644
645 points.insert(points.end(), rhs.points.begin(), rhs.points.end());
646 return *this;
647 }
648
649 [[nodiscard]] friend graphic_path operator+(graphic_path lhs, graphic_path const& rhs) noexcept
650 {
651 return lhs += rhs;
652 }
653
654 friend graphic_path operator*(transformer2 auto const& lhs, graphic_path const& rhs) noexcept
655 {
656 auto rhs_ = rhs;
657 for (auto& point : rhs_.points) {
658 point = lhs * point;
659 }
660 return rhs_;
661 }
662};
663
668hi_export inline void fill(pixmap_span<sdf_r8> dst, graphic_path const& path) noexcept
669{
670 fill(dst, path.getBeziers());
671}
672
673}} // namespace hi::v1
Defined the color type.
@ end
Start from the end of the file.
@ begin
Start from the beginning of the file.
line_join_style
The way two lines should be joined.
Definition line_join_style.hpp:22
@ miter
The outer edge of both lines are extended until they meet to form a sharp corner.
The HikoGUI namespace.
Definition array_generic.hpp:20
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:32
constexpr std::vector< bezier_curve > makeContourFromPoints(std::vector< bezier_point >::const_iterator begin, std::vector< bezier_point >::const_iterator end) noexcept
Make a contour of Bezier curves from a list of points.
Definition bezier_curve.hpp:549
constexpr std::vector< bezier_curve > makeInverseContour(std::vector< bezier_curve > const &contour) noexcept
Inverse a contour.
Definition bezier_curve.hpp:613
constexpr std::vector< bezier_curve > makeParallelContour(std::vector< bezier_curve > const &contour, float offset, hi::line_join_style line_join_style, float tolerance) noexcept
Definition bezier_curve.hpp:635
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
This is a RGBA floating point color.
Definition color_intf.hpp:49
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:33
The 4 radii of the corners of a quad or rectangle.
Definition corner_radii.hpp:26
A high-level geometric extent.
Definition extent2.hpp:32
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:107
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:118
A rectangle / parallelogram in 3D space.
Definition rectangle.hpp:25
Definition scale2.hpp:18
Definition translate2.hpp:18
A high-level geometric vector Part of the high-level vector, point, mat and color types.
Definition vector2.hpp:27
A path is a vector graphics object.
Definition graphic_path.hpp:29
void addContour(std::vector< bezier_point >::const_iterator const &begin, std::vector< bezier_point >::const_iterator const &end) noexcept
Curve with the given bezier curve.
Definition graphic_path.hpp:529
void addRectangle(aarectangle rectangle, corner_radii corners=corner_radii{0.0f, 0.0f, 0.0f, 0.0f}) noexcept
Draw a rectangle.
Definition graphic_path.hpp:426
void clear() noexcept
Clear the path.
Definition graphic_path.hpp:44
void addStroke(graphic_path const &path, color strokeColor, float strokeWidth, hi::line_join_style line_join_style=line_join_style::miter, float tolerance=0.05f) noexcept
Stroke a path and close layer.
Definition graphic_path.hpp:556
std::vector< bezier_point > points
A set of all bezier points describing all bezier curves, contours and layers.
Definition graphic_path.hpp:32
void optimizeLayers() noexcept
Optimize layers.
Definition graphic_path.hpp:263
void closeLayer(color fill_color) noexcept
Close current contour.
Definition graphic_path.hpp:252
std::vector< bezier_point >::const_iterator beginContour(ssize_t contourNr) const noexcept
Return an iterator to the start point of a contour.
Definition graphic_path.hpp:119
void addContour(std::vector< bezier_curve > const &contour) noexcept
Contour with the given bezier curves.
Definition graphic_path.hpp:499
void addContour(std::vector< bezier_point > const &contour) noexcept
Curve with the given bezier curve.
Definition graphic_path.hpp:541
aarectangle boundingBox() const noexcept
Calculate bounding box.
Definition graphic_path.hpp:85
graphic_path toStroke(float strokeWidth=1.0f, line_join_style line_join_style=line_join_style::miter, float tolerance=0.05f) const noexcept
Convert path to stroke-path.
Definition graphic_path.hpp:578
bool allLayersHaveSameColor() const noexcept
Check if all layers have the same color.
Definition graphic_path.hpp:67
ssize_t numberOfLayers() const noexcept
Return the number of closed layers.
Definition graphic_path.hpp:60
void cubicCurveRelativeTo(vector2 controlDirection1, vector2 controlDirection2, vector2 direction) noexcept
Draw curve from the current position to the new direction.
Definition graphic_path.hpp:368
ssize_t numberOfContours() const noexcept
Return the number of closed contours.
Definition graphic_path.hpp:53
std::vector< std::pair< ssize_t, color > > layerEndContours
An color and index into.
Definition graphic_path.hpp:40
void arcTo(float radius, point2 position) noexcept
Draw an circular arc.
Definition graphic_path.hpp:389
void closeContour() noexcept
Close current contour.
Definition graphic_path.hpp:220
void addPath(graphic_path const &path, color fill_color) noexcept
Add path and close layer.
Definition graphic_path.hpp:548
void addCircle(point2 position, float radius) noexcept
Draw a circle.
Definition graphic_path.hpp:484
void moveTo(point2 position) noexcept
Start a new contour at position.
Definition graphic_path.hpp:301
point2 currentPosition() const noexcept
Get the currentPosition of the open contour.
Definition graphic_path.hpp:289
bool hasLayers() const noexcept
This path has layers.
Definition graphic_path.hpp:229
std::vector< ssize_t > contourEndPoints
An index into.
Definition graphic_path.hpp:36
bool isContourOpen() const noexcept
Return true if there is an open contour.
Definition graphic_path.hpp:206
void quadraticCurveRelativeTo(vector2 controlDirection, vector2 direction) noexcept
Draw curve from the current position to the new direction.
Definition graphic_path.hpp:345
bool isLayerOpen() const noexcept
Return true if there is an open layer.
Definition graphic_path.hpp:236
void moveRelativeTo(vector2 direction) noexcept
Start a new contour relative to current position.
Definition graphic_path.hpp:310
graphic_path centerScale(extent2 extent, float padding=0.0) const noexcept
Center and scale a path inside the extent with padding.
Definition graphic_path.hpp:606
void tryRemoveLayers() noexcept
Try to move the layers in a path.
Definition graphic_path.hpp:104
A non-owning 2D pixel-based image.
Definition pixmap_span.hpp:34
T asin(T... args)
T begin(T... args)
T cos(T... args)
T distance(T... args)
T end(T... args)
T insert(T... args)
T max(T... args)
T min(T... args)
T sqrt(T... args)
T swap(T... args)