visionOS空间计算实战开发教程Day 10 照片墙

本例选择了《天空之城》的25张照片,组成5x5的照片墙)。首先我们在setupContentEntity方法中构建了一个纹理数组,将这25张照片添加到数组images中。其中封装了setup方法,借助于visionOS对沉浸式空间的支持,我们创建了三个平面,组成具有立体感的照片墙。

setup方法中调用了addChildEntities,对images随机打散,通过quotientAndRemainder方法对5求商取余来设置xy的值,从而生成5x5的照片,z轴上仅以平面为基准做了小小的调整。将准备好的位置和纹理,传入makePlane方法进行配置返回实体再分别添加到3个平面中。

为增加趣味性,这里还定义了toggleSorted()方法,在沉浸式空间内点击时会打散(randomSetChildPositions()方法),再次点击又会重置收起(resetChildPositions())。完整的ViewModel.swift文件内容如下:

import SwiftUI
import RealityKit

@Observable
class ViewModel {
    
    private let planeSize = CGSize(width: 0.32, height: 0.18)
    private let maxPlaneSize = CGSize(width: 3.0, height: 2.0)
    
    private var  contentEntity = Entity()
    private var boardPlanes: [ModelEntity] = []
    private var images: [MaterialParameters.Texture] = []
    private var sorted = true
    
    func setupContentEntity() -> Entity {
        for i in 1..<26 {
            let name = "laputa\(String(format: "%03d", i))"
            if let texture = try? TextureResource.load(named: name) {
                images.append(MaterialParameters.Texture(texture))
            }
        }
        setup()
        return contentEntity
    }
    
    func toggleSorted() {
        if sorted {
            sorted.toggle()
            randomSetChildPositions()
        } else {
            sorted.toggle()
            resetChildPositions()
        }
    }
    
    // MARK: - Private
    
    private func setup() {
        for i in 0..<3 {
            let boardPlane = ModelEntity(
                mesh: .generatePlane(width: 3, height: 2),
                materials: [SimpleMaterial(color: .clear, isMetallic: false)]
            )
            
            boardPlane.position = SIMD3(x: 0, y : 2, z: -0.5 - 0.1 * Float(i + 1))
            
            contentEntity.addChild(boardPlane)
            boardPlanes.append(boardPlane)
            
            addChildEntities(boardPlane: boardPlane)
        }
    }
    
    private func addChildEntities(boardPlane: ModelEntity) {
        var i: Int = 0
        
        for image in images.shuffled().prefix(30) {
            let divisionResult = i.quotientAndRemainder(dividingBy: 5)
            
            let x: Float = Float(divisionResult.remainder) * 0.4 - 0.75
            let y: Float = Float(divisionResult.quotient) * 0.25 - 0.5
            let z: Float = boardPlane.position.z + Float(i) * 0.0001
            
            let entity = makePlane(name: "", position: SIMD3(x: x, y: y, z: z), texture: image)
            boardPlane.addChild(entity)
            i += 1
        }
    }
    
    private func makePlane(name: String, position: SIMD3, texture: MaterialParameters.Texture) -> ModelEntity {
        var material = SimpleMaterial()
        material.color = .init(texture: texture)
        
        let entity = ModelEntity(
            mesh: .generatePlane(width: 0.32, height: 0.18, cornerRadius: 0.0),
            materials: [material],
            collisionShape: .generateBox(width: 0.32, height: 0.18, depth: 0.1),
            mass: 0.0
        )
        
        entity.name = name
        entity.position = position
        entity.components.set(InputTargetComponent(allowedInputTypes: .indirect))
        
        return entity
    }
    
    private func move(entity: Entity, position: SIMD2) {
        let move = FromToByAnimation(
            name: "move",
            from: .init(scale: .init(repeating: 1), translation: entity.position),
            to: .init(scale: .init(repeating: 1), translation: .init(x: position.x, y: position.y, z: entity.position.z)),
            duration: 2.0,
            timing: .linear,
            bindTarget: .transform
        )
        let animation = try! AnimationResource.generate(with: move)
        entity.playAnimation(animation, transitionDuration: 2.0)
    }
    
    private func randomSetChildPositions() {
        let size = CGSize(width: planeSize.width *  1.2, height: planeSize.height * 1.2)
        for boardPlane in boardPlanes {
            let newPoints = randomPoints(count: boardPlane.children.count, size: size)
            for i in 0..(x, y))
                i += 1
            }
        }
    }
    
    private func randomPoints(count: Int, size: CGSize) -> [SIMD2] {
        var ret: [SIMD2] = []
        while ret.count < count {
            if let point = randomPoint(size: size, positions: ret) {
                ret.append(point)
            }
        }
        return ret
    }
    
    private func randomPoint(size: CGSize, positions: [SIMD2]) -> SIMD2? {
        for _ in 0..<5000 {
            let x = CGFloat.random(in: -maxPlaneSize.width...(maxPlaneSize.width / 2))
            let y = CGFloat.random(in: -maxPlaneSize.height...(maxPlaneSize.height / 2))
            let frame = CGRect(x: CGFloat(x), y: CGFloat(y), width: size.width, height: size.height)
            
            if positions.isEmpty {
                return SIMD2(Float(x), Float(y))
            } else {
                var intersects = false
                for position in positions {
                    let f = CGRect(x: CGFloat(position.x), y: CGFloat(position.y), width: size.width, height: size.height)
                    if f.intersects(frame) {
                        intersects = true
                    }
                }
                if !intersects {
                    return SIMD2(Float(frame.minX), Float(frame.minY))
                }
            }
        }
        return nil
    }
}

ImmersiveView中发生了Tap事件后会调用其中的toggleSorted()方法,其它代码与此前的示例并没什么不同。

struct ImmersiveView: View {
    @State var model = ViewModel()
    
    var body: some View {
        RealityView { content in
            content.add(model.setupContentEntity())
        }
        .onTapGesture {
            model.toggleSorted()
        }
    }
}

visionOS空间计算实战开发教程Day 10 照片墙_第1张图片

示例代码:GitHub仓库

其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记

你可能感兴趣的:(空间计算,apple,vision,pro,虚拟现实,增强现实,swiftui)