SceneKit中点击SCNode任意位置改对应点颜色并重建几何的实现方案
SceneKit点击节点修改点击点颜色并重建几何实现方案
1. 实现点击检测
通过手势识别(iOS)或鼠标事件(macOS)捕获用户点击,再利用SceneKit的命中检测API定位点击的节点及几何细节:
// iOS 添加点击手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) scnView.addGestureRecognizer(tapGesture) @objc func handleTap(_ gesture: UITapGestureRecognizer) { let location = gesture.location(in: scnView) // 仅检测目标节点的命中结果 let hitResults = scnView.hitTest(location, options: [ SCNHitTestOption.searchMode: SCNHitTestSearchMode.all.rawValue, SCNHitTestOption.node: targetNode // 你的目标SCNode实例 ]) guard let hitResult = hitResults.first else { return } let targetGeometry = hitResult.node.geometry! let faceIndex = hitResult.faceIndex // 后续颜色修改、几何重建逻辑 }
2. 修改点击位置的颜色
2.1 确保几何包含颜色数据源
如果原模型没有颜色数据,需先初始化颜色数组并添加颜色数据源:
let vertexCount = targetGeometry.vertexCount // 初始化RGBA颜色数组,默认白色 var colors = [Float](repeating: 1.0, count: vertexCount * 4) for i in 0..<vertexCount { colors[i*4] = 1.0 // R colors[i*4+1] = 1.0 // G colors[i*4+2] = 1.0 // B colors[i*4+3] = 1.0 // A } // 创建颜色数据源 let colorData = Data(bytes: colors, count: colors.count * MemoryLayout<Float>.size) let colorSource = SCNGeometrySource( data: colorData, semantic: .color, vectorCount: vertexCount, usesFloatComponents: true, componentsPerVector: 4, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: 4 * MemoryLayout<Float>.size ) // 替换原有颜色源(无则添加) targetGeometry.sources = targetGeometry.sources.filter { $0.semantic != .color } + [colorSource]
2.2 定位点击面并修改对应顶点颜色
通过命中结果的faceIndex找到对应面的顶点索引,修改颜色数组后更新数据源:
guard let geometryElement = targetGeometry.elements.first else { return } let bytesPerIndex = geometryElement.bytesPerIndex // 计算点击面的顶点索引数据范围(以三角形面为例,每个面3个顶点) let faceDataOffset = faceIndex * 3 * bytesPerIndex var vertexIndices = [UInt32]() let indexData = geometryElement.data.subdata(in: faceDataOffset..<faceDataOffset + 3 * bytesPerIndex) indexData.copyBytes(to: &vertexIndices, count: 3 * bytesPerIndex) // 将点击面的顶点颜色改为红色 for index in vertexIndices { let i = Int(index) colors[i*4] = 1.0 // R colors[i*4+1] = 0.0 // G colors[i*4+2] = 0.0 // B } // 更新颜色数据源 let updatedColorData = Data(bytes: colors, count: colors.count * MemoryLayout<Float>.size) let updatedColorSource = SCNGeometrySource( data: updatedColorData, semantic: .color, vectorCount: vertexCount, usesFloatComponents: true, componentsPerVector: 4, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: 4 * MemoryLayout<Float>.size ) targetGeometry.sources = targetGeometry.sources.filter { $0.semantic != .color } + [updatedColorSource]
3. 重建几何结构(修改拓扑/形状)
如果需要基于点击位置改变几何形状(比如拉伸顶点),需修改顶点数据源:
guard let vertexSource = targetGeometry.sources.first(where: { $0.semantic == .vertex }) else { return } var vertices = [Float](repeating: 0.0, count: vertexCount * 3) vertexSource.data.copyBytes(to: &vertices, count: vertexCount * 3 * MemoryLayout<Float>.size) // 沿点击点法线方向偏移顶点(实现凸起效果) let hitNormal = hitResult.normal for index in vertexIndices { let i = Int(index) vertices[i*3] += hitNormal.x * 0.1 // X轴偏移 vertices[i*3+1] += hitNormal.y * 0.1 // Y轴偏移 vertices[i*3+2] += hitNormal.z * 0.1 // Z轴偏移 } // 更新顶点数据源 let updatedVertexData = Data(bytes: vertices, count: vertices.count * MemoryLayout<Float>.size) let updatedVertexSource = SCNGeometrySource( data: updatedVertexData, semantic: .vertex, vectorCount: vertexCount, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: 3 * MemoryLayout<Float>.size ) targetGeometry.sources = targetGeometry.sources.filter { $0.semantic != .vertex } + [updatedVertexSource]
关键注意事项
- 需匹配几何元素的
primitiveType(如三角形、四边形)和索引格式(bytesPerIndex),否则索引计算会出错。 - 频繁修改几何数据源可能影响性能,建议批量处理或使用
SCNMutableGeometry优化。 - 外部导入的模型(如.dae/.scn)需确保导出时包含顶点法线、索引等必要数据,或在代码中重新计算。
内容的提问来源于stack exchange,提问作者Moinuddin
相关产品推荐
相关产品推荐

