У меня есть трехмерный объект (рекламный щит), который содержит рекламу на одной стороне, и я хотел бы определить, виден ли он (через камеру устройства машинного зрения) или нет для пользователя.
Вот код, который я использовал для расчета угла, но, похоже, он работает не так, как ожидалось, поскольку я получаю неправильное значение угла между камерой и рекламным щитом. Для этого я использовал формулу скалярного произведения. Но не уверен, правильно это или нет. Дайте мне знать, что не так в расчетах или альтернативный способ сделать это.
class cityViewModel {
var billboard = Entity()
}
struct CityView: View {
private var cameraTracker = VisionProCameraTracker()
var model = cityViewModel()
var body: some View {
RealityView { content in
// Add the initial RealityKit content
if let scene = try? await Entity(named: "newcity", in: citywithAdBundle) {
content.add(scene)
if let entity = scene.findEntity(named: "billboard") {
model.billboard = entity
print("Billboard position \(entity.position)")
}
updatingSceneEventsWith(content)
}
}
.task {
await cameraTracker.runArSession()
}
}
private func updatingSceneEventsWith(_ content: RealityViewContent) {
_ = content.subscribe(to: SceneEvents.Update.self) { _ in
Task {
let cameraTransform = await cameraTracker.getTransform()
let x2 = cameraTransform!.columns.3.x
let x1 = await model.billboard.position.x
let y2 = cameraTransform!.columns.3.y
let y1 = await model.billboard.position.y
let z2 = cameraTransform!.columns.3.z
let z1 = await model.billboard.position.z
let distance = sqrtf( (x2-x1) * (x2-x1) +
(y2-y1) * (y2-y1) +
(z2-z1) * (z2-z1) )
var formatted = String(format: "%.2f m", arguments: [distance])
print("Distance from camera to billboard is:", formatted)
let ab = (x1 * x2) + (y1 * y2) + (z1 * z2)
let magnitudeOfAd = sqrtf( (x1 * x1) + (y1 * y1) + (y1 * y1) )
let magnititeOfCamera = sqrtf( (x2 * x2) + (y2 * y2) + (y2 * y2) )
let cosTheta = ab / (magnitudeOfAd * magnititeOfCamera)
let angleInRadians = acos(cosTheta)
let angleInDegrees = angleInRadians * 180 / .pi
formatted = String(format: "%.2f m", arguments: [angleInDegrees])
print("Angle b/w camera and billboard is \(formatted) degrees.")
}
}
}
}
@Observable class VisionProCameraTracker {
let session = ARKitSession()
let worldTracking = WorldTrackingProvider()
func runArSession() async {
Task {
try? await session.run([worldTracking])
}
}
func getTransform() async -> simd_float4x4? {
guard let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: 4)
else { return nil }
let transform = deviceAnchor.originFromAnchorTransform
return transform
}
}
В SceneKit есть метод экземпляра isNode(_:insideFrustumOf:), который помогает нам получить значение true
, если ограничивающая рамка искомого узла пересекает усеченную пирамиду перспективной камеры, определенную узлом POV. Подобный метод нам бы очень помог. К сожалению, RealityKit для VisionOS 1.2 не имеет такого полезного метода. Так что будем довольствоваться только тем, что имеем.
Чтобы решить вашу проблему самым простым способом, я использовал метод экземпляра orientation(relativeTo:)
. Этот код позволяет найти угол отклонения 0...45 градусов по осям Y и X в любом направлении. Настройте отслеживание привязки устройства ARKit так же, как описано в вашем вопросе (поскольку мы будем использовать объект класса VisionProCameraTracker
). Затем создайте объект привязки и передайте ему матрицу преобразования привязки устройства. Теперь вы можете измерить угол.
Вот мой код:
import SwiftUI
import RealityKit
import ARKit
struct ContentView : View {
let vpct = VisionProCameraTracker()
let anchor = AnchorEntity(.head, trackingMode: .continuous)
@State var billboard = Entity()
var body: some View {
RealityView { rvc in
billboard = try! await Entity(named: "Billboard")
rvc.add(billboard)
rvc.add(anchor)
let _ = rvc.subscribe(to: SceneEvents.Update.self) { _ in
anchor.transform.matrix = vpct.getTransform()
let radAngle = anchor.orientation(relativeTo: billboard).angle
var radToDeg: Any = Int(radAngle * (180/Float.pi)) % 240
if (radToDeg as! Int) > 45 {
radToDeg = "Angle is greater than 45 degrees"
}
if anchor.position.z < billboard.position.z {
radToDeg = "Billboard is not visible"
}
print(radToDeg)
}
}
.task { await vpct.runArSession() }
}
}
Если вам нужно разделить значения углов X и Y, чтобы определить не только величину угла, но и его вектор, используйте этот код:
let _ = rvc.subscribe(to: SceneEvents.Update.self) { _ in
anchor.transform.matrix = vpct.getTransform()
let xVec = anchor.orientation(relativeTo: billboard).vector.x
let yVec = anchor.orientation(relativeTo: billboard).vector.y
let magnitude = anchor.orientation(relativeTo: billboard).angle.magnitude
let radAngleX = xVec * magnitude // radians
let radAngleY = yVec * magnitude // radians
let radToDeg_X = Int(radAngleX * (180/Float.pi)) % 91
let radToDeg_Y = Int(radAngleY * (180/Float.pi)) % 91
print("x:", radToDeg_X, "y:", radToDeg_Y)
}
Если вам интересно, что такое параметры real
и imaginary
кватерниона, прочтите этот пост.
Привет @Andy Jazz: Если мне нужно определить, виден ли этот рекламный щит или нет, как я могу этого добиться? Например, если пользователь приблизится к рекламному щите и начнет вращаться влево, то после некоторого поворота рекламный щит не будет виден, как показано во вложении.
В операторе if-else укажите максимальный угол отклонения, при котором весь рекламный щит не будет виден (выберите любой желаемый угол в градусах: 45, или 50, или 60). Вы можете создавать различные условия ограничения угла отклонения в зависимости от расстояния от камеры до рекламного щита. Если рекламный щит находится позади вас, приложение отобразит сообщение: «Билборд не виден».
Если пользователь перемещается в крайнее левое или правое положение и не меняет угол камеры, рекламный щит не виден. Как определить этот вариант использования?
В этом случае сравните диапазон position.x
и диапазон position.y
для камеры и рекламного щита.
Привет @Andy Jazz: я пытался узнать, по какой оси вращается камера устройства, но не нашел правильного пути. Есть ли способ определить, по какой оси вращается камера?
В RealityKit для VisionOS преобразование AnchorEntity(.head) скрыто по дизайну, поэтому я использовал преобразование привязки устройства. Кроме того, я использовал оператор модуля
... % 240
как способ избавиться от значений, находящихся в диапазоне0...180
за спиной пользователя.