本节学习任务
ARKit局域网内如何实现多个手机AR画面同步
需求描述
一个用户打开AR应用,在房间中放置一个物体,然后其他用户加入游戏去找这个物体
游戏规则:只有玩家靠近这个物体1m的范围内才能发现这个物体
技术难点:
用户进入AR游戏时,手机的位置和角度不可能一直,所以就造成的物体不在同一个地方被发现
解决方案1:
就是上述所说的,让两个手机在同一个位置,朝向同一个方向同时启动手机,由于进入AR场景坐标系需要移动一下手机原始坐标系才能被准确的定位,所以就造成了很大的误差
解决方案2
不同手机在任何位置,都可以启动AR场景,然后当一个玩家藏好物体后,将自己的手机坐标系和其它用户的手机坐标系进行同步转换即可完成坐标值的统一
下面先看一张原理图
操作分为两步
第一步 启动AR(无论在什么位置和角度都可以)
第二步 转换坐标 (将玩家1藏物体的坐标,转换的到其它玩家坐标系上,现实中的同一个位置)
ARKit 规律探究
1.无论手机在什么角度和位置开启AR场景坐标系的Y轴总是和水平面垂直
2.标定手机是让手机表面平行方向一致,这个时候相当于将两个手机的照相机的坐标自身的坐标系是同一个坐标系,这个时候,将玩家1,放置物体的坐标(x1,y1,z1)先转换到相机坐标系中,转换后的坐标为(x2,y2,z2),之后在将这个坐标,转换至世界坐标系中,转换后的坐标为(x3,y3,z3)
3.完成上面转换后,只要在坐标x3,y3,z3处放置物体即可
底层深入分析
由于所有玩家的坐标系的y轴都是和水平面垂直的,所以我们看做坐标系的位置相对标定点的位置,是有沿着y轴旋转了一个角度,然后平移一个值所得,只要计算出两个坐标系之间相对旋转了多少度,平移了多少增量,只要将物体坐标,也按照这个规律,旋转+平移,就可以计算物体在其他玩家坐标系中的位置
先看一下底层算法实现
下面这个类主要作用是求出,偏移量和旋转角度Δθ,Δx,Δy,Δz
核心类 TARSceneConverter
import SceneKit
import Foundation
class TARSceneConverter{
var Δθ: Float = 0.0
var Δx: Float = 0.0
var Δy: Float = 0.0
var Δz: Float = 0.0
init(referBeginRef1:SCNVector3, referEndRef1: SCNVector3,
referBeginRef2: SCNVector3,referEndRef2: SCNVector3,
collisionPosition1: SCNVector3,
collisionPosition2: SCNVector3) {
calculateΔθByBeginPosition(referBeginRef1, endPosition1: referEndRef1, beginPosition2: referBeginRef2, endPosition2: referEndRef2)
calaulteFactor(position1: collisionPosition1, position2: collisionPosition2)
}
init() {
}
// 计算xz面上向量的旋转角度
// θ = -1 标识
func calculateRotationY(beginPosition:SCNVector3,endPosition:SCNVector3) -> Float{
var θ:Float = 0.0
let x = endPosition.x
let z = endPosition.z
if z > 0 && x > 0{
θ = atan(z/x)
}else if z > 0 && x < 0 {
θ = atan(z/x) + Float.pi
}else if z < 0 && x > 0{
θ = 2*Float.pi + atan(z/x)
}else if z < 0 && x < 0{
θ = Float.pi + atan(z/x)
}else if x == 0 {
if z > 0 {
θ = 0
}else if z < 0 {
θ = Float.pi
}else if z == 0 {
θ = 0
}
}
return θ
}
private func calculateΔθByBeginPosition(_ beginPostion1: SCNVector3,
endPosition1: SCNVector3,
beginPosition2: SCNVector3,
endPosition2: SCNVector3){
self.Δθ = Float( calculateRotationY(beginPosition: beginPostion1, endPosition: endPosition1) - calculateRotationY(beginPosition: beginPosition2, endPosition: endPosition2))
}
// 计算需要的因子
private func calaulteFactor(position1:SCNVector3,position2:SCNVector3){
let θ2 = calculateRotationY(beginPosition: SCNVector3Zero, endPosition: position2)
let θ21 = θ2 + self.Δθ
let x1 = position1.x
let y1 = position1.y
let z1 = position1.z
let x2 = position2.x
let y2 = position2.y
let z2 = position2.z
let r2 = sqrt(pow(x2, 2)+pow(z2, 2))
let x21 = Float(cos(θ21)) * r2
let z21 = Float(sin(θ21)) * r2
let y21 = y2
self.Δx = x21 - x1
self.Δy = y21 - y1
self.Δz = z21 - z1
}
// 转换值从机场景坐标
func convertPositionToMachine(position:SCNVector3)->SCNVector3{
let x1 = position.x
let y1 = position.y
let z1 = position.z
let x2 = x1 + self.Δx
let y2 = y1 + self.Δy
let z2 = z1 + self.Δz
print("Δx:\(self.Δx)-Δy\(self.Δy)-Δz\(self.Δz)")
print("x2:\(x2)-y2:\(y2)-z2:\(z2)")
let θ2 = calculateRotationY(beginPosition: SCNVector3Zero, endPosition: SCNVector3Make(x2, y2, z2))
print("角度\(θ2)")
let r2 = sqrt(pow(x2, 2)+pow(z2, 2))
print(r2)
let θ21 = θ2 - self.Δθ
let x21 = cos(θ21) * r2
let y21 = y2
let z21 = sin(θ21) * r2
return SCNVector3Make(x21, y21, z21)
}
// 先平移目标坐标系
func translatePositionToMachine(position:SCNVector3)->SCNVector3{
let x1 = position.x
let y1 = position.y
let z1 = position.z
let x2 = x1 + self.Δx
let y2 = y1 + self.Δy
let z2 = z1 + self.Δz
return SCNVector3Make(x2, y2, z2)
}
// 旋转至目标坐标系
func rotationByYToMachine(position:SCNVector3)->SCNVector3{
let x1 = position.x
let y1 = position.y
let z1 = position.z
let θ2 = calculateRotationY(beginPosition: SCNVector3Zero, endPosition: SCNVector3Make(x1, y1 ,z1))
print("角度\(θ2)")
let r2 = sqrt(pow(x1, 2)+pow(z1, 2))
print(r2)
let θ21 = θ2 - self.Δθ
let x21 = cos(θ21) * r2
let y21 = y1
let z21 = sin(θ21) * r2
return SCNVector3Make(x21, y21, z21)
}
func getFactor()->(Float,Float,Float,Float){
return (self.Δx,self.Δy,self.Δz,self.Δθ)
}
}
TRSceneConvert.switf
import Foundation
import SceneKit
private let sceneManager = TARSceneManager()
class TARSceneManager{
var referBeginPosition1: SCNVector3!
var referEndPosition1: SCNVector3!
var referBeginPosition2: SCNVector3!
var referEndPosition2: SCNVector3!
var collisionPosition1: SCNVector3!
var collisionPosition2:SCNVector3!
var converter: TARSceneConverter!
class func share() -> TARSceneManager{
return sceneManager
}
// 开始执行标记
func handleMark(){
converter = TARSceneConverter(referBeginRef1: referBeginPosition1, referEndRef1: referEndPosition1, referBeginRef2: referBeginPosition2, referEndRef2: referEndPosition2, collisionPosition1:collisionPosition1, collisionPosition2: collisionPosition2)
}
// 转换坐标到下面的场景
func convertPositionToFollowScene(position:SCNVector3)->SCNVector3{
if converter != nil {
return converter.convertPositionToMachine(position: position)
}else{
fatalError("converter is nil")
}
}
}
参数解释
var referBeginPosition1: SCNVector3! // 主机,初始化时,放置在主机相机坐标系的一个点,在世界坐标系的位置
var referEndPosition1: SCNVector3! //主机,标定时 ,上面那个节点在世界坐标系的位置
var referBeginPosition2: SCNVector3! // 主机,初始化时,放置在主机相机坐标系的一个点,在世界坐标系的位置
var referEndPosition2: SCNVector3! //主机,标定时 ,上面那个节点在世界坐标系的位置
var collisionPosition1: SCNVector3! // 标定时,相机1的世界坐标位置
var collisionPosition2:SCNVector3! // 标定时,相机2的世界坐标位置
这个算法有点复杂,其实有更好的算法,十句左右代码就可以搞定,下一篇我们演示一下,本节就先到这里,代码会发送群里(530957835),查看ToyAR,注意准备两台6S以上手机,让两个手机连上同一个局域网,注意修改在TGameRoom文件的主机ip地址,改成你手机主机的ip地址
self.client.connectHost(host: "192.168.8.108", port: 10001, success: {
self.client.writeData(user, success: {
})
}) { (err) in
print(err!)
}