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

2023-11-30 09:28

本文主要是介绍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 = truefunc 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: - Privateprivate 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<Float>(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 = 0for image in images.shuffled().prefix(30) {let divisionResult = i.quotientAndRemainder(dividingBy: 5)let x: Float = Float(divisionResult.remainder) * 0.4 - 0.75let y: Float = Float(divisionResult.quotient) * 0.25 - 0.5let z: Float = boardPlane.position.z + Float(i) * 0.0001let entity = makePlane(name: "", position: SIMD3<Float>(x: x, y: y, z: z), texture: image)boardPlane.addChild(entity)i += 1}}private func makePlane(name: String, position: SIMD3<Float>, 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 = nameentity.position = positionentity.components.set(InputTargetComponent(allowedInputTypes: .indirect))return entity}private func move(entity: Entity, position: SIMD2<Float>) {let move = FromToByAnimation<Transform>(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..<boardPlane.children.count {let entity = boardPlane.children[i]move(entity: entity, position: newPoints[i])}}}private func resetChildPositions() {for boardPlane in boardPlanes {var i: Int = 0for entity in boardPlane.children {let divisionResult = i.quotientAndRemainder(dividingBy: 5)let x: Float = Float(divisionResult.remainder) * 0.4 - 0.75let y: Float = Float(divisionResult.quotient) * 0.25 - 0.5move(entity: entity, position: SIMD2<Float>(x, y))i += 1}}}private func randomPoints(count: Int, size: CGSize) -> [SIMD2<Float>] {var ret: [SIMD2<Float>] = []while ret.count < count {if let point = randomPoint(size: size, positions: ret) {ret.append(point)}}return ret}private func randomPoint(size: CGSize, positions: [SIMD2<Float>]) -> SIMD2<Float>? {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>(Float(x), Float(y))} else {var intersects = falsefor 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>(Float(frame.minX), Float(frame.minY))}}}return nil}
}

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

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

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

示例代码:GitHub仓库

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

这篇关于visionOS空间计算实战开发教程Day 10 照片墙的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/436497

相关文章

MySQL 多列 IN 查询之语法、性能与实战技巧(最新整理)

《MySQL多列IN查询之语法、性能与实战技巧(最新整理)》本文详解MySQL多列IN查询,对比传统OR写法,强调其简洁高效,适合批量匹配复合键,通过联合索引、分批次优化提升性能,兼容多种数据库... 目录一、基础语法:多列 IN 的两种写法1. 直接值列表2. 子查询二、对比传统 OR 的写法三、性能分析

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、

PowerShell中15个提升运维效率关键命令实战指南

《PowerShell中15个提升运维效率关键命令实战指南》作为网络安全专业人员的必备技能,PowerShell在系统管理、日志分析、威胁检测和自动化响应方面展现出强大能力,下面我们就来看看15个提升... 目录一、PowerShell在网络安全中的战略价值二、网络安全关键场景命令实战1. 系统安全基线核查

Ubuntu如何分配​​未使用的空间

《Ubuntu如何分配​​未使用的空间》Ubuntu磁盘空间不足,实际未分配空间8.2G因LVM卷组名称格式差异(双破折号误写)导致无法扩展,确认正确卷组名后,使用lvextend和resize2fs... 目录1:原因2:操作3:报错5:解决问题:确认卷组名称​6:再次操作7:验证扩展是否成功8:问题已解

使用Docker构建Python Flask程序的详细教程

《使用Docker构建PythonFlask程序的详细教程》在当今的软件开发领域,容器化技术正变得越来越流行,而Docker无疑是其中的佼佼者,本文我们就来聊聊如何使用Docker构建一个简单的Py... 目录引言一、准备工作二、创建 Flask 应用程序三、创建 dockerfile四、构建 Docker

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

MySQL之InnoDB存储页的独立表空间解读

《MySQL之InnoDB存储页的独立表空间解读》:本文主要介绍MySQL之InnoDB存储页的独立表空间,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、独立表空间【1】表空间大小【2】区【3】组【4】段【5】区的类型【6】XDES Entry区结构【

在Spring Boot中集成RabbitMQ的实战记录

《在SpringBoot中集成RabbitMQ的实战记录》本文介绍SpringBoot集成RabbitMQ的步骤,涵盖配置连接、消息发送与接收,并对比两种定义Exchange与队列的方式:手动声明(... 目录前言准备工作1. 安装 RabbitMQ2. 消息发送者(Producer)配置1. 创建 Spr

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现