Вычисление точек четырехугольника для имитации жидкости в контейнере (в Swift) - PullRequest
0 голосов
/ 12 июля 2020

Я пытаюсь создать форму FluidView в SwiftUI, которая действует как жидкость в контейнере, так что, когда устройство находится под определенным углом, форма / «жидкость» тоже. Форма также имеет определенную c емкость, percentFilled, которая указывает, какая часть родительского представления должна быть заполнена.

Используя эти ограничения, инвариант класса

lines.area == rect.area * percentFilled

, где lines - четырехугольник, а rect - ограничивающий прямоугольник. Этот инвариант подразумевает, что «объем» формы остается постоянным для каждого percentFilled независимо от угла наклона.

Вот что у меня есть на данный момент:

/// A View made using a specified angle and amount to fill
/// - Invariant: The area of the view is exactly equal to the area of the rectangle of the parent view times `percentFilled`
struct FluidView: Shape {
    var angle: CGFloat = 0.0
    var percentFilled: CGFloat = 0
    
    /// Creates a new FluidView
    /// - Parameters:
    ///   - angle: A value in the range `0...1`. A value of `0` indicates the view is horizontal, and an angle of `1` indicates the view is vertical (horizontal if viewed as landscape)
    ///   - percentFilled: the amount of the view bounds to fill represented as a value in the range `0...1`. A value of `x` indicates that `x * 100`% of the parent view is covered by this view
    init(angle: CGFloat = 0, percentFilled: CGFloat = 0) {
        precondition(0...1 ~= angle)
        precondition(0...1 ~= percentFilled)
        
        self.angle = angle
        self.percentFilled = percentFilled
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: rect.height * (1 - percentFilled))) // top left
        
        let lines = [
            (0,                                             rect.height                              ), // bottom left
            (rect.width * 1 / (1 + angle - percentFilled),  rect.height                              ), // bottom right
            (rect.width * 1 / (1 + angle - percentFilled),  rect.height * (1 + angle - percentFilled)), // top right
            (0,                                             rect.height * (1 - angle - percentFilled))  // top left
        ].map { x, y in
            // make sure no points exceed the bounds
            CGPoint(x: min(rect.width, x), y: min(rect.height, y))
        }
        
        // invariant
        assert(lines.area == rect.area * percentFilled)
        
        path.addLines(lines)
        return path
    }
}

Мне кажется, То, что у меня есть сейчас, несколько близко к цели, однако инвариант не работает. Я считаю, что мои y-координаты верны, однако я думаю, что мои вычисления для x-координат должны измениться, но я не уверен, что они должны изменить.

Любая помощь будет очень признательна, спасибо !

1 Ответ

0 голосов
/ 21 июля 2020

Попробуйте что-то вроде этого,

struct FilledShape<S: Shape>: View {
  let shape: S
  @State var angle: Angle = .zero
  @State var percentFull: CGFloat
  
  var gradient: Gradient {
    Gradient(stops: [Gradient.Stop(color: .red, location: 0),
                     Gradient.Stop(color: .red, location: percentFull),
                     Gradient.Stop(color: .blue, location: percentFull),
                     Gradient.Stop(color: .blue, location: 1)])
  }
  
  var body: some View {
    shape.rotation(angle)
      .fill(LinearGradient(gradient: gradient, startPoint: .bottom, endPoint: .top))
  }
  
}
struct ContentView: View {
  @State var angle: Angle = .degrees(30)
  var body: some View {
    FilledShape(shape: Rectangle(), angle: angle, percentFull: 0.3).frame(width: 100, height: 100)
  }
}

Дело в том, что процент заполнения - это на самом деле процент вверх по оси y, а не процент заполненной области. Вы можете использовать какой-то метод numeri c с GeometryReader, чтобы получить площадь и прочитать значение y в соответствующей сумме заполненной площади (или, если вы просто используете четырехугольники, это проще). Путем перебора:

extension Shape {
  func area(in box: CGRect) -> Int {
    var area = 0
    for x in 0..<Int(box.width) {
      for y in 0..<Int(box.height) {
        let point = CGPoint(x: x, y: y)
        if self.contains(point) {
          area += 1
        }
      }
    }
    return area
  }
}

В качестве другого подхода просмотрите SpriteKit и SKPhysicsBody.

...