RealityKit: поворот части 3D-модели по направлению к камере.IOS

Программируем под IOS
Ответить
Anonymous
 RealityKit: поворот части 3D-модели по направлению к камере.

Сообщение Anonymous »

У меня есть 3D-модель объекта со встроенной в нее анимацией. Модель также имеет скелет. Мне нужно, чтобы объект, отображаемый в AR, поворачивал голову в сторону камеры. Как это можно реализовать программно?
Я думал реализовать это путем манипулирования костями, но обнаружил, что RealityKit (iOS 16) не предоставляет такой возможности.
Если я загружаю модель как Entity, то у меня есть граф объектов типа Entity — геометрия модели, но нет костей.
Если Я загружаю модель как ModelEntity, тогда у меня вообще нет прямого доступа к дереву дочерних элементов, но есть 2 списка: JointNames и JointTransforms. Насколько я понимаю, это списки названий и трансформаций костей соответственно.
Ниже приведен код базовой загрузки объекта:

Код: Выделить всё

import SwiftUI
import RealityKit
import ARKit
import Combine

struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)

let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
arView.session.run(configuration)

addCouchingOverlay(arView: arView)
addModelToARView(arView: arView, context: context)

return arView
}

func updateUIView(_ uiView: ARView, context: Context) { }

func makeCoordinator() -> ARCoordinator {
ARCoordinator()
}

private func addModelToARView(arView: ARView, context: Context) {
Entity.loadModelAsync(named: "model.usdz").sink(
receiveCompletion: { completion in
if case let .failure(error) = completion {
print("Error loading model: \(error)")
}
},
receiveValue: { modelEntity in
configureModel(context: context, arView: arView, modelEntity: modelEntity)
}
).store(in: &context.coordinator.cancellables)
}
}

extension ARViewContainer {
private func addCouchingOverlay(arView: ARView) {
let coachingOverlay = ARCoachingOverlayView()
coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
coachingOverlay.session = arView.session
coachingOverlay.goal = .horizontalPlane
arView.addSubview(coachingOverlay)
}

private func configureModel(context: Context, arView: ARView, modelEntity: ModelEntity) {
let anchorEntity = AnchorEntity(plane: .horizontal)

anchorEntity.addChild(modelEntity)
arView.scene.addAnchor(anchorEntity)

let minScale: Float = 0.001
modelEntity.scale = [minScale, minScale, minScale]
context.coordinator.modelEntity = modelEntity

arView.scene.subscribe(to: SceneEvents.Update.self) { _ in
let currentScale = modelEntity.scale.x
if currentScale < minScale {
modelEntity.scale = [minScale, minScale, minScale]
}
}.store(in: &context.coordinator.cancellables)
}
}

class ARCoordinator {
var cancellables: Set = []

var modelEntity: ModelEntity?
}
Мне кажется, что самый правильный способ — манипулировать костями скелета 3D-модели. Но в RealityKit для iOS 16 эта функция недоступна напрямую. Или есть способы анимировать эффект на костях/суставах?
В ходе эксперимента я обнаружил, что при изменении трансформаций в списке JointTransforms визуально меняется сама модель. Поэтому я даже пытался собрать из Entity собственное дерево, задавая имена и преобразования из списков JointNames и JointTransforms. Затем я сохраняю их в объекте-координаторе для быстрого доступа к ним, а также индексы необходимых суставов для последующих обновлений значений в JointTransforms.
Я запустите функцию по таймеру, чтобы изменить трансформацию кости согласно следующей логике:
  • берём объекты головы, шеи и самой модели
    получите положение головы относительно шеи
  • выполните преобразование головы, используя вид (at:, from:, upVector:relativeTo:) функция
  • перезаписать значение в JointTransforms по индексу
Вот пример кода:

Код: Выделить всё

import Foundation
import RealityKit
import Combine

class ARCoordinator {
var cancellables: Set = []

var modelEntity: ModelEntity?
var entity: Entity?

var neck: Entity?
var head: Entity?
var lEye: Entity?
var rEye: Entity?

var headTransformIdx = 0
var lEyeTransformIdx = 0
var rEyeTransformIdx = 0

private var trackingTimer: Timer?

private var skeletoneIsBuilded = false
private var entities: [String: Entity] = [:]

func startCameraTrackingTimer(arView: ARView) {
trackingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
if let isBuilded = self?.skeletoneIsBuilded, isBuilded {
self?.updateModelOrientation(arView: arView)
}
}
}

private func prepareEntities() {
let neckName = "root/body/neck"
let headName = "root/body/neck/head"
let eyeLName = "root/body/neck/head/eye_left"
let eyeRName = "root/body/neck/head/eye_right"

if let entity = entities[neckName],
let idx = modelEntity?.jointNames.firstIndex(where: { $0.hasSuffix("/neck") }) {
neck = entity
}

if let entity = entities[headName],
let idx = modelEntity?.jointNames.firstIndex(where: { $0.hasSuffix("/head") }) {
head = entity
headTransformIdx = idx
}

if let entity = entities[eyeLName],
let idx = modelEntity?.jointNames.firstIndex(where: { $0.hasSuffix("/eye_left") }) {
lEye = entity
lEyeTransformIdx = idx
}

if let entity = entities[eyeRName],
let idx = modelEntity?.jointNames.firstIndex(where: { $0.hasSuffix("/eye_right") }) {
rEye = entity
rEyeTransformIdx = idx
}
}

func stopCameraTrackingTimer() {
trackingTimer?.invalidate()
}

private func updateModelOrientation(arView: ARView) {
if let modelEntity = modelEntity,
let neck = neck,
let head = head {

let position = head.position(relativeTo: neck)
head.look(
at: arView.cameraTransform.translation,
from: position,
upVector: [0, 1, 0],
relativeTo: neck
)

modelEntity.jointTransforms[headTransformIdx] = head.transform
}
}

func buildGraphAsync(jointNames: [String], jointTransforms: [Transform]) async -> Entity? {
let graphRoot: Entity? = await withTaskGroup(of: Entity?.self) { group in
group.addTask { [weak self] in
return self?.buildGraph(jointNames: jointNames, jointTransforms: jointTransforms)
}

return await group.first(where: { $0 != nil }) ?? nil
}

skeletoneIsBuilded = true
prepareEntities()

return graphRoot
}

private func buildGraph(jointNames: [String], jointTransforms: [Transform]) -> Entity? {
guard jointNames.count == jointTransforms.count else {
print("Error: the number of names and transformations does not match.")
return nil
}

var idx = 0
let root = Entity.create(name: jointNames[idx], transform: jointTransforms[idx])
entities = [jointNames[idx]: root]
idx += 1

while idx <  jointNames.count {
let name = jointNames[idx]
let transform = jointTransforms[idx]

let parentPathComponents = name.split(separator: "/").dropLast()
let parentName = parentPathComponents.joined(separator: "/")

let entity = Entity.create(name: name, transform: transform)
entities[name] = entity

if let parent = entities[parentName] {
parent.addChild(entity)
} else {
print("Parent not fount for: \(name)")
}

idx += 1
}

return root
}
}

extension Entity {
static func create(name: String, transform: Transform) -> Entity {
let entity = Entity()
entity.name = name
entity.transform = transform

return entity
}
}

struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
// ...

context.coordinator.startCameraTrackingTimer(arView: arView)

return arView
}

private func configureModel(context: Context, arView: ARView, modelEntity: ModelEntity) {
// ...

Task.detached {
let entity = await context.coordinator.buildGraphAsync(
jointNames: modelEntity.jointNames,
jointTransforms: modelEntity.jointTransforms
)

await MainActor.run {
context.coordinator.entity = entity
}
}
}
}
Я экспериментировал с разными вариантами вызова функции Look(at:, from:, upVector: относительныйTo:):
  • изменил значения в upVector
  • установил объект шеи и nil как относительный
В настоящее время у меня возникают следующие проблемы с этим подход:
  • если правильно выбрать вектор, то голова вращается, но только горизонтально. Движения по вертикальной оси нет.
  • Я заметил, что голова модели резко меняет направление в противоположную сторону еще до того, как камера окажется на другой стороне. То есть создается впечатление, будто точка, от которой рассчитывается направление на камеру, находится заметно ближе к наблюдателю, чем сам объект. то есть эта точка находится между объектом и камерой. Возможно надо как-то правильно настроить привязку...
Может кто-нибудь помочь с решением данной проблемы? В первую очередь меня интересует правильное решение реализации направления головы модели в сторону камеры
Заранее спасибо за любую помощь)

Подробнее здесь: https://stackoverflow.com/questions/791 ... the-camera
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»