Предистория
Под впечатлением статей про Sprite Kit и про GestureRecognizer возникла идея портировать простенькое приложение себе на телефон. А так как там достаточно часто используются структуры CGPoint, то озадачился изучением перегрузки операторов в Swift и подсмотрел тут.
Реализация
В несколько шагов создал себе некоторую «библиотеку» операторов перегрузки для удобной работы с выражениями для структур CGPoint. Заодно на каждом шаге потестировал тестирование в XCode.
Фактически реализована полная алгебра операций для линейного пространства векторов (структуры CGPoint), скаляров (CGFloat) и частично операторов (CGAffineTransform).
Так как, данные функции перегрузки операторов описаны глобально, то функционал добавляется в любой проект легко — через добавление исходника.
Конечно, примеров использования перегрузки на вымышленных ситуациях достаточно много, но данная «библиотека операторов» возможно окажется:
- чуть более конкретным примером,
- готовым решением, дающим экономии пары часов рутинного набора и отладки простейшего кода (листинг исходника целиком приведен в конце).
Шаг за шагом
Шаг 1: операторы сложения/вычитания векторов
Стоит обратить внимание, как во втором случае с помощью prefix указывается на унарность оператора-префикса.
func +(left: CGPoint, right: CGPoint)->CGPoint{
return CGPoint(x: left.x+right.x, y: left.y+right.y)
}
prefix func -(right: CGPoint)->CGPoint{
return CGPoint(x: -right.x, y: -right.y)
}
func -(left: CGPoint, right: CGPoint)->CGPoint{
return left + (-right)
}
Шаг 2: умножение и деление вектора на скаляр
Здесь стоит обратить внимание на то, что первые два оператора образуют «симметрию» умножения скаляра и вектора.
func *(left: CGPoint, right: CGFloat)->CGPoint{
return CGPoint(x: left.x*right, y: left.y*right)
}
func *(left: CGFloat, right: CGPoint)->CGPoint{
return right*left
}
func /(left: CGPoint, right: CGFloat)->CGPoint{
return left*(1/right)
}
Шаг 3: совмещение вышеперечисленных операций с присвоением
Важно обратить внимание, как с помощью inout описывается изменяемость левого аргумента, что необходимо для операций с присвоением.
func +=(inout left:CGPoint, right:CGPoint){
left = left+right
}
func -=(inout left:CGPoint, right:CGPoint){
left = left-right
}
func *=(inout left:CGPoint, right:CGFloat){
left = left*right
}
func /=(inout left:CGPoint, right:CGFloat){
left = left/right
}
Шаг 4: скалярное и 'смешанное' произведение
Очень полезные вещи, так как первое произведение дает скаляр и пропорционально косинусу угла между векторами, а второе произведение является псевдоскаляром (то есть меняет знак при перестановке множителей) и пропорционально синусу угла между векторами (жест «вращение» вычисляет величину угла вероятнее всего как раз на базе этого выражения).
func *(left: CGPoint, right: CGPoint)->CGFloat{
return left.x*right.x + left.y*right.y
}
func ^(left: CGPoint, right: CGPoint)->CGFloat{
return left.x*right.y - left.y*right.x
}
Шаг 5: произведение вектора на оператор аффинного преобразования
Здесь порядка ради вводится только умножение вектора на оператор справа.
func *(left: CGPoint, right: CGAffineTransform)->CGPoint{
return CGPointApplyAffineTransform(left, right)
}
func *=(inout left:CGPoint, right:CGAffineTransform){
left = left*right
}
Шаг 6: произведение операторов аффинного преобразования
func *(left: CGAffineTransform, right: CGAffineTransform)->CGAffineTransform{
return CGAffineTransformConcat(left, right)
}
func *=(inout left:CGAffineTransform, right:CGAffineTransform){
left = left*right
}
Заключение
Приведенный пример являет необходимым и достаточным набором в текущем понимании моих потребностей. В частности, поэтому не реализована алгебра для матриц и для аналогичных структур (напр., CGSize).
Хочется отметить еще, что данные решения являются настолько очевидными и удобными, что будет неудивительным, если со временем подобная перегрузка войдет как часть стандартной библиотеки CoreGraphics.
Весь код целиком
Это можно уже копировать непосредственно в проект.
import Foundation
func +(left: CGPoint, right: CGPoint)->CGPoint{
return CGPoint(x: left.x+right.x, y: left.y+right.y)
}
prefix func -(right: CGPoint)->CGPoint{
return CGPoint(x: -right.x, y: -right.y)
}
func -(left: CGPoint, right: CGPoint)->CGPoint{
return left + (-right)
}
///////////////////////////////////////////////////
func *(left: CGPoint, right: CGFloat)->CGPoint{
return CGPoint(x: left.x*right, y: left.y*right)
}
func *(left: CGFloat, right: CGPoint)->CGPoint{
return right*left
}
func /(left: CGPoint, right: CGFloat)->CGPoint{
return left*(1/right)
}
///////////////////////////////////////////////////
func +=(inout left:CGPoint, right:CGPoint){
left = left+right
}
func -=(inout left:CGPoint, right:CGPoint){
left = left-right
}
func *=(inout left:CGPoint, right:CGFloat){
left = left*right
}
func /=(inout left:CGPoint, right:CGFloat){
left = left/right
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////
func *(left: CGPoint, right: CGPoint)->CGFloat{
return left.x*right.x + left.y*right.y
}
func /(left: CGPoint, right: CGPoint)->CGFloat{
return left.x*right.y - left.y*right.x
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////
func *(left: CGPoint, right: CGAffineTransform)->CGPoint{
return CGPointApplyAffineTransform(left, right)
}
func *=(inout left:CGPoint, right:CGAffineTransform){
left = left*right
}
///////////////////////////////////////////////////
func *(left: CGAffineTransform, right: CGAffineTransform)->CGAffineTransform{
return CGAffineTransformConcat(left, right)
}
func *=(inout left:CGAffineTransform, right:CGAffineTransform){
left = left*right
}