材质吸取相关功能的准确性,需要用到“射线求交”AI写的相关功能时,总是对多维子材质下的子材质识别有偏差。
目前用的这个方案代替,但在某些极少的情况下,会出现结果上的不准确:
用户点击源物体
↓
pickObject() → 拿到被点击的 node + screenPt
↓
_mrResolvePick(node, screenPt) → _mrPickFaceOnNode(node, screenPt)
↓
返回 #(polyFaceIdx, matID)
↓
_mrLookupByFaceID(matA, matID) → 在 MultiMaterial 中定位子材质
↓
最终材质 = matA.materialList[slotIndex]
哪位前辈可以提供一段能准确识别物体表面ID的 脚本代码就好了。
以下是AI写的相对准确的识别物体ID和材质的方案:
-- ============================================================
-- 材质吸取 - 准确性测试
-- ============================================================
-- 功能:
-- 用户在视口点击模型表面 → 提取命中面的材质ID + 漫反射颜色
-- 输出 RayEx 格式: RayEx #((ray [pos] [dir]), matID, [r,g,b])
--
-- 求交流程 (两步兜底 + 两步修正):
-- 1. intersectRayEx → 3ds Max 原生射线求交 (主方案, 返回三角面号)
-- 2. snapshotAsMesh → 快照为 TriMesh + 手写 M-T 求交 (兜底方案)
-- └ 范围优化: 先用 intersectRay 取命中点, 只遍历附近100单位内的面
-- 3. triToPolyFace → 三角面号 → 多边形面号 (Editable_Poly 专用)
-- 4. verifyFaceByCenter → 面心距离交叉验证 (±3窗口修正)
--
-- 兼容材质类型:
-- Corona / VRay / Standard / Physical / Multi-Sub-Object
-- ============================================================
-- 销毁已存在的对话框 (防止重复运行报错)
try (destroydialog matPickTestRollout) catch()
-- ==========================================
-- Rollout UI 定义
-- ==========================================
rollout matPickTestRollout "材质吸取 - 准确性测试" width:400
(
group "测试按钮:"
(
button btn_test "点击视口拾取材质" width:380 height:30
)
-- 状态标签: 显示最近一次拾取的结果摘要
label lbl_last "(未测试)" align:#center
-- 日志列表框: 显示拾取过程中的信息
listbox lb_log "" height:15
-- ==========================================
-- 日志与字符串工具函数
-- ==========================================
local logMsgs = #() -- 日志缓冲数组, 最多保留100条
-- valStr: 安全转字符串 (兼容任意类型, 失败返回占位符)
fn valStr v = (
try (
formattedPrint v format:""
) catch (
try (v as string) catch ("<转换失败>")
)
)
-- numStr: 数值转字符串 (用于日志输出)
fn numStr v = (
try (
formattedPrint v format:""
) catch (
try (v as string) catch ("?")
)
)
-- addLog: 追加一条日志到列表框 (超过100条自动丢弃最旧的)
fn addLog msg = (
append logMsgs (valStr msg)
if logMsgs.count > 100 do deleteItem logMsgs 1
lb_log.items = logMsgs
)
-- logHeader: 输出带分隔线的标题 (同时写入 Listener 和 UI)
fn logHeader testName = (
addLog ("========== " + testName + " ==========")
)
-- logNodeInfo: 输出节点的详细信息 (名称/类型/面数/材质/子材质列表)
fn logNodeInfo n = (
if n == undefined do return()
local mat = try(n.material)catch(undefined)
local matType = if mat != undefined then (classOf mat as string) else "(无)"
addLog (" 节点: " + n.name + " (" + matType + ")")
)
-- ==========================================
-- 颜色工具函数
-- ==========================================
-- normColor: 颜色归一化
-- 输入: 0-255 范围的 color 或 Point3
-- 输出: 0-1 范围的 color
-- 异常时返回灰色 (0.5, 0.5, 0.5)
fn normColor c =
(
if c == undefined do return (color 0.5 0.5 0.5)
-- Point3 → color 类型转换
if classOf c == Point3 do c = color c.x c.y c.z
-- 如果任一通道 > 1.0, 认为是 0-255 范围, 除以 255 归一化
if (c.r > 1.0 or c.g > 1.0 or c.b > 1.0) do
c = color (c.r/255.0) (c.g/255.0) (c.b/255.0)
c
)
-- getMtlColor: 提取材质的漫反射基础色
-- 按优先级检测属性, 兼容多种材质类型:
-- baseColor → Physical Material / Corona
-- diffuse → Standard / VRay
-- color → 部分自定义材质
-- albedo → 部分新材质
-- 全部找不到时返回灰色 (128, 128, 128)
fn getMtlColor m =
(
if m == undefined do return (normColor (color 128 128 128))
if (isProperty m #baseColor) do return (normColor m.baseColor)
if (isProperty m #diffuse) do return (normColor m.diffuse)
if (isProperty m #color) do return (normColor m.color)
if (isProperty m #albedo) do return (normColor m.albedo)
return (normColor (color 128 128 128))
)
-- getHitColor: 按 matID 取子材质的漫反射颜色
-- 如果是 MultiMaterial (多维子材质), 按 matID 取对应子材质
-- 如果是普通材质, 直接取材质本身颜色
fn getHitColor obj matID =
(
local m = obj.material
if m == undefined do return (normColor (color 128 128 128))
local subM = m
if classOf m == MultiMaterial then
(
-- matID 范围校验后取对应子材质
if matID >= 1 and matID <= m.numsubs do subM = m[matID]
)
getMtlColor subM
)
-- getMatNameByID: 按 matID 获取材质名称 (用于日志显示)
-- 多维子材质: 返回对应子槽位的名称
-- 普通材质: 返回材质本身名称
fn getMatNameByID obj matID =
(
if obj == undefined or obj.material == undefined do return "(无材质)"
local m = obj.material
if classOf m == MultiMaterial then
(
if matID >= 1 and matID <= m.numsubs then
(
local sub = m[matID]
if sub != undefined then
return (try(sub.name)catch("(未命名子材质)"))
else
return "(子材质" + (matID as string) + "为空)"
)
else
return "(matID " + (matID as string) + " 越界)"
)
else
(
return (try(m.name)catch("(未命名材质)"))
)
)
-- ==========================================
-- 调试日志 (输出到 MAXScript Listener)
-- ==========================================
fn stepLog msg = ( format "[步骤] %\n" msg )
fn detailLog msg = ( format " ├ %\n" msg )
fn okLog msg = ( format " └ ✓ %\n" msg )
fn failLog msg = ( format " └ ✗ %\n" msg )
-- ==========================================
-- 射线求交引擎
-- ==========================================
-- --------------------------------------------------
-- manualIntersect: 手写 Möller-Trumbore 射线-三角形求交
-- --------------------------------------------------
-- 与全量遍历不同, 本函数支持"范围优化":
-- 如果传入了 hitPoint (来自 intersectRay 的命中点),
-- 则只遍历面中心距离 hitPoint < threshold 的三角面,
-- 大幅减少计算量 (从万级降到几十级)
--
-- 参数:
-- mesh - TriMesh 网格对象
-- theRay - 射线 (ray pos dir)
-- hitPoint - (可选) 预估命中点, 用于缩小搜索范围
-- threshold - 搜索半径 (世界单位, 默认100)
--
-- 返回: #(三角面索引, 距离t) 或 undefined
fn manualIntersect mesh theRay hitPoint:undefined threshold:100.0 =
(
local closestT = 1e9 -- 最近命中距离 (初始化为极大值)
local closestFace = 0 -- 最近命中面索引
local nFaces = try(mesh.numfaces) catch(0)
-- 第一步: 筛选候选面
local facesToCheck = #()
if hitPoint != undefined then
(
-- ★ 范围优化模式: 只收集面中心距命中点 < threshold 的面
for f = 1 to nFaces do
(
local fv = try(getFace mesh f) catch(undefined)
if fv != undefined do
(
local v1 = try(getVert mesh fv.x) catch(undefined)
local v2 = try(getVert mesh fv.y) catch(undefined)
local v3 = try(getVert mesh fv.z) catch(undefined)
if v1 != undefined and v2 != undefined and v3 != undefined do
(
-- 计算三角形面心
local center = (v1 + v2 + v3) / 3.0
-- 只保留 threshold 范围内的面
if distance center hitPoint < threshold do append facesToCheck f
)
)
)
detailLog ("M-T 范围优化: 候选面 " + (facesToCheck.count as string) + "/" + (nFaces as string))
)
else
(
-- 无命中点时, 退化为全量遍历
for f = 1 to nFaces do append facesToCheck f
detailLog ("M-T 全量遍历: " + (nFaces as string) + " 面")
)
-- 第二步: 对候选面执行 M-T 求交
for f in facesToCheck do
(
local fv = try(getFace mesh f) catch(undefined)
if fv != undefined do
(
local v1 = try(getVert mesh fv.x) catch(undefined)
local v2 = try(getVert mesh fv.y) catch(undefined)
local v3 = try(getVert mesh fv.z) catch(undefined)
if v1 != undefined and v2 != undefined and v3 != undefined do
(
-- === Möller-Trumbore 算法开始 ===
-- e1, e2: 三角形两条边向量
local e1 = v2 - v1
local e2 = v3 - v1
-- pvec: 射线方向与 e2 的叉积
local pvec = cross theRay.dir e2
-- det: 行列式, 用于判断射线是否与三角形平面平行
local det = dot e1 pvec
-- 行列式接近0 → 射线与三角形平行, 跳过
if abs det > 0.0001 do
(
local invDet = 1.0 / det
-- tvec: 射线起点到三角形顶点 v1 的向量
local tvec = theRay.pos - v1
-- u: 重心坐标分量1 (必须在 [0,1] 区间)
local u = (dot tvec pvec) * invDet
if u >= 0 and u <= 1 do
(
-- qvec: tvec 与 e1 的叉积
local qvec = cross tvec e1
-- v: 重心坐标分量2 (u+v 必须 ≤ 1)
local v2uv = (dot theRay.dir qvec) * invDet
if v2uv >= 0 and (u + v2uv) <= 1 do
(
-- t: 射线参数, 表示从起点到命中点的距离
local t = (dot e2 qvec) * invDet
-- 取最近的有效命中 (t > 0.001 避免自交)
if t > 0.001 and t < closestT do
(
closestT = t
closestFace = f
)
)
)
)
-- === Möller-Trumbore 算法结束 ===
)
)
)
if closestFace > 0 then #(closestFace, closestT) else undefined
)
-- --------------------------------------------------
-- triToPolyFace: 三角面索引 → 多边形面索引
-- --------------------------------------------------
-- 背景:
-- intersectRayEx / snapshotAsMesh 返回的是三角面号
-- 但 Editable_Poly 的 polyop.getFaceMatID 需要多边形面号
-- 一个多边形 (n边形) 会被三角化为 (n-2) 个三角面
-- 本函数按 (deg-2) 累加, 找到三角面对应的多边形面
--
-- 参数:
-- node - 目标节点
-- triFaceIdx - 三角面索引 (从1开始)
--
-- 返回: 多边形面索引 (从1开始), 或 0 表示未找到
-- 非 Editable_Poly 对象直接返回原值 (无需转换)
fn triToPolyFace node triFaceIdx =
(
if triFaceIdx <= 0 do return 0
local isPoly = try(classOf node == Editable_Poly) catch(false)
if not isPoly do return triFaceIdx -- 非 Editable_Poly 无需转换
local accumulated = 0
local nFaces = try(polyOp.getNumFaces node) catch(0)
for p = 1 to nFaces do
(
-- getFaceDeg: 获取多边形顶点数 (3=三角形, 4=四边形, 5=五边形...)
local deg = try(polyOp.getFaceDeg node p) catch(4)
if deg == undefined or deg < 3 do deg = 4
-- n边形 → (n-2) 个三角面
accumulated += (deg - 2)
-- 三角面号落在累加范围内 → 返回多边形面号
if triFaceIdx <= accumulated do return p
)
0
)
-- --------------------------------------------------
-- verifyFaceByCenter: 面心距离交叉验证
-- --------------------------------------------------
-- 用途:
-- M-T 或 intersectRayEx 返回的面号可能有偏差
-- (尤其在相邻面边界处)
-- 本函数在候选面 ±window 范围内,
-- 找面心距离命中点最近的多边形面, 作为修正结果
--
-- 参数:
-- node - 目标节点 (仅对 Editable_Poly 有效)
-- polyF - 候选多边面号
-- hitPoint - 射线命中点 (世界坐标)
-- window - 搜索窗口 (默认 ±3 面)
--
-- 返回: #(修正后面号, 修正前面号)
fn verifyFaceByCenter node polyF hitPoint window:3 =
(
-- 非 Editable_Poly 不做验证
if classOf node != Editable_Poly do return #(polyF, polyF)
local nFaces = try(polyOp.getNumFaces node) catch(0)
if nFaces == 0 do return #(polyF, polyF)
-- 计算搜索范围 [lo, hi], 确保不越界
local lo = amax 1 (polyF - window)
local hi = amin nFaces (polyF + window)
local bestF = polyF -- 最佳面号, 初始为候选面
local bestD = 1e10 -- 最佳距离, 初始为极大值
-- 遍历窗口内的面, 找面心最近的
for i = lo to hi do
(
-- getFaceCenter 返回局部坐标的面心
local center = [0,0,0]
try (center = polyOp.getFaceCenter node i) catch()
-- 转换到世界坐标
local worldCenter = center * node.objectTransform
local d = distance hitPoint worldCenter
if d < bestD do (bestD = d; bestF = i)
)
detailLog ("面心验证: 修正前=" + (polyF as string) + " → 修正后=" + (bestF as string) + " (距离=" + (bestD as string) + ")")
#(bestF, polyF)
)
-- ==========================================
-- 核心: 射线面求交主函数
-- ==========================================
-- --------------------------------------------------
-- pickFaceOnNode: 对单个节点执行完整的面求交流程
-- --------------------------------------------------
-- 流程:
-- 1. intersectRayEx (3ds Max 原生) → 三角面号 → triToPolyFace 转换
-- 2. [兜底] snapshotAsMesh + M-T (范围优化) → 三角面号 → triToPolyFace 转换
-- 3. 面心交叉验证 (Editable_Poly 专用)
-- 4. 三级降级取 matID
--
-- 参数:
-- node - 目标节点
-- theRay - 射线 (ray pos dir)
--
-- 返回: #(多边面号, matID, 三角面号, 距离) 或 undefined
fn pickFaceOnNode node theRay =
(
if node == undefined do return undefined
-- 归一化射线方向 (M-T 算法要求单位向量)
local ray = theRay
local dirLen = length ray.dir
if dirLen > 0 and dirLen != 1.0 do ray.dir = ray.dir / dirLen
-- 初始化状态变量
local faceIdx = 0 -- 多边形面号 (最终用)
local dist = -1.0 -- 命中距离
local found = false -- 是否已找到命中面
local meshMatID = 0 -- 从 mesh 获取的 matID (兜底用)
local triFaceIdx = 0 -- 三角面号 (中间值)
stepLog ("pickFaceOnNode 开始 | 对象: " + node.name + " | 类型: " + (classOf node as string))
detailLog ("射线起点: " + (ray.pos as string))
detailLog ("射线方向: " + (ray.dir as string))
-- ==========================================
-- 步骤1: intersectRayEx (3ds Max 原生射线求交)
-- ==========================================
-- 优势: 内置加速结构, 速度快
-- 返回: hitRecord (含 .face .dist 属性) 或 Array 或 undefined
try
(
stepLog "步骤1: intersectRayEx (3ds Max 原生射线求交)"
local hrEx = intersectRayEx node ray
if hrEx != undefined then
(
local triF = undefined
local d = -1.0
-- intersectRayEx 返回格式不统一, 需兼容两种:
-- 格式1: Array #(hitResult, faceIndex)
-- 格式2: hitRecord 对象 (.face / .dist 属性)
if classOf hrEx == Array and hrEx.count >= 2 then
(
-- Array 格式: hrEx[1] = Ray 命中结果, hrEx[2] = 面索引
triF = hrEx[2]
d = try(distance hrEx[1].pos ray.pos) catch(-1.0)
detailLog ("intersectRayEx [Array格式] 三角面=" + (triF as string) + " 距离=" + (d as string))
)
else
(
-- hitRecord 格式: 直接取 .face 和 .dist 属性
triF = try(hrEx.face) catch(undefined)
d = try(hrEx.dist) catch(-1.0)
detailLog ("intersectRayEx [hitRecord格式] 三角面=" + (triF as string) + " 距离=" + (d as string))
)
-- 拿到三角面号后, 转换为多边形面号
if triF != undefined and triF > 0 do
(
dist = d
triFaceIdx = triF
faceIdx = triToPolyFace node triF
detailLog ("三角面" + (triF as string) + " → 多边面" + (faceIdx as string))
if faceIdx > 0 do found = true
)
)
else
(
detailLog "intersectRayEx 返回 undefined"
)
)
catch ( err )
(
failLog ("intersectRayEx 异常: " + (err as string))
)
-- ==========================================
-- 步骤2: snapshotAsMesh + M-T (兜底方案)
-- ==========================================
-- 触发条件: 步骤1 未命中
-- 原理: 快照为 TriMesh → 手写 M-T 逐三角形求交
-- 优化: 先用 intersectRay 取命中点, 只遍历附近100单位内的面
if not found do
(
stepLog "步骤2: snapshotAsMesh + M-T (兜底方案)"
-- snapshotAsMesh: 获取节点的三角网格快照 (含世界变换)
local m = try(snapshotAsMesh node) catch(undefined)
if m != undefined then
(
-- ==========================================
-- 2a: 检测顶点空间 (局部 vs 世界)
-- ==========================================
-- snapshotAsMesh 的顶点可能在局部空间, 也可能在世界空间
-- 通过比较第一个顶点与节点 boundingbox 来判断
local bbMin = node.min; local bbMax = node.max
local v1 = try(getVert m 1) catch([0,0,0])
local inWorld = (v1.x >= bbMin.x-0.5 and v1.x <= bbMax.x+0.5)
if not inWorld do
(
-- 顶点在局部空间 → 手动乘变换矩阵转到世界空间
detailLog "顶点在局部空间,变换到世界空间"
local tm = node.transform
for v = 1 to m.numverts do setVert m v ((getVert m v) * tm)
)
update m
-- ==========================================
-- 2b: 用 intersectRay 取命中点 (缩小 M-T 搜索范围)
-- ==========================================
-- 仅对 Editable_Poly 执行 (其他类型步骤1通常已成功)
local hitPoint = undefined
local isEPoly = try(classOf node == Editable_Poly) catch(false)
if isEPoly do
(
-- intersectRay: 3ds Max 原生, 返回 Ray (pos=命中点, dir=反射方向)
local hrRay = try(intersectRay node ray) catch(undefined)
if hrRay != undefined do
(
hitPoint = hrRay.pos
detailLog ("intersectRay 命中点: " + (hitPoint as string))
)
)
stepLog "2c: 执行 M-T 求交 (范围优化)"
local result = manualIntersect m ray hitPoint:hitPoint threshold:100.0
if result != undefined then
(
triFaceIdx = result[1]
dist = result[2]
if triFaceIdx > 0 do
(
-- 从 mesh 获取 matID (作为兜底值)
try (meshMatID = meshop.getFaceMatID m triFaceIdx) catch ()
detailLog ("M-T 命中三角面: " + (triFaceIdx as string) + " | 距离=" + (dist as string) + " | meshMatID=" + (meshMatID as string))
-- 三角面号 → 多边形面号
faceIdx = triToPolyFace node triFaceIdx
detailLog ("三角面" + (triFaceIdx as string) + " → 多边面" + (faceIdx as string))
if faceIdx > 0 do found = true
)
)
else
(
failLog "M-T 未命中"
)
-- 释放 mesh 快照内存
free m
)
else
(
failLog "snapshotAsMesh 失败"
)
)
-- 两步求交都失败
if not found do
(
failLog "所有求交方案失败"
addLog " ✗ 求交失败"
return undefined
)
-- ==========================================
-- 步骤3: 面心交叉验证 (仅 Editable_Poly)
-- ==========================================
-- 用命中点重算世界坐标, 在候选面 ±3 范围内找面心最近的面
if classOf node == Editable_Poly and dist > 0 do
(
stepLog "步骤3: 面心交叉验证 (Editable_Poly)"
-- 用射线起点 + 方向 * 距离 = 命中点世界坐标
local hitPt = ray.pos + (normalize ray.dir) * dist
local vResult = verifyFaceByCenter node faceIdx hitPt
local origFace = faceIdx
faceIdx = vResult[1] -- 采纳修正后的面号
if faceIdx != origFace then
(
detailLog ("面心修正: 面" + (numStr origFace) + " → 面" + (numStr faceIdx))
addLog (" 面心修正: 面" + (numStr origFace) + " → 面" + (numStr faceIdx))
)
else
detailLog "面心验证: 无需修正"
)
stepLog "步骤4: 获取面材质 ID (三级降级)"
-- 优先级1: polyop.getFaceMatID (Editable_Poly 专用, 最准)
-- 优先级2: getFaceMatID (Mesh 通用)
-- 优先级3: meshMatID (步骤2从 mesh 获取的兜底值)
local matID = 0
try (matID = polyOp.getFaceMatID node faceIdx) catch ()
if matID != undefined and matID > 0 do detailLog ("优先级1 polyop.getFaceMatID → matID=" + (matID as string))
if matID == undefined or matID == 0 do
(
try (matID = getFaceMatID node faceIdx) catch ()
if matID != undefined and matID > 0 do detailLog ("优先级2 getFaceMatID → matID=" + (matID as string))
)
if matID == undefined or matID == 0 do
(
matID = meshMatID
detailLog ("优先级3 meshMatID (兜底) → matID=" + (matID as string))
)
if matID == undefined do matID = 0
okLog ("最终结果: 多边面=" + (faceIdx as string) + " | 三角面=" + (triFaceIdx as string) + " | matID=" + (matID as string))
return #(faceIdx, matID, triFaceIdx, dist)
)
-- ==========================================
-- RayEx 格式输出
-- ==========================================
-- RayExOutput: 格式化输出 RayEx 结果 (精简版)
-- Listener: RayEx #((ray [pos] [dir]), matID, [r,g,b])
-- UI日志: 物体/面ID/材质信息
fn RayExOutput obj hitPos matID faceID triFaceID colorRGB matName =
(
-- Listener 精简输出: RayEx 格式 + 物体面材质摘要
format "RayEx #((ray [%]) % [%])\n" hitPos matID colorRGB
format " >> % | 面% (三角面%) | matID:% | %\n" obj.name faceID triFaceID matID matName
-- UI 日志精简输出
addLog (obj.name + " | 面" + (numStr faceID) + " | matID:" + (numStr matID) + " | " + matName)
)
-- ==========================================
-- 核心分析函数: 射线 → 命中对象 → 面求交 → 输出
-- ==========================================
-- analyzeRay: 完整分析流程入口
-- 1. intersectRayScene: 射线与场景中所有对象求交
-- 2. 取首个命中对象
-- 3. pickFaceOnNode: 对命中对象做面级求交
-- 4. 提取颜色 + 材质名 → RayEx 输出
--
-- 返回: #(对象, 命中坐标, matID, 多边面号, 三角面号, 颜色, 材质名) 或 undefined
fn analyzeRay theRay =
(
stepLog "analyzeRay: 开始射线分析"
detailLog ("射线起点: " + (theRay.pos as string) + " | 方向: " + (theRay.dir as string))
-- intersectRayScene: 射线与场景所有对象求交
stepLog "intersectRayScene: 射线与场景对象求交..."
local hits = intersectRayScene theRay
if hits.count == 0 do
(
failLog "未命中任何对象"
addLog "RayEx: 未命中任何对象"
return undefined
)
detailLog ("场景命中 " + (hits.count as string) + " 个对象, 取最近的")
-- 取第一个命中对象 (距离最近的)
local hitPair = hits[1]
local obj = hitPair[1] -- 命中的节点
local hitPos = hitPair[2].pos -- 命中点世界坐标
detailLog ("命中对象: " + obj.name + " | 命中坐标: " + (hitPos as string))
-- 对命中对象执行面级求交
stepLog "pickFaceOnNode: 面级求交..."
local result = pickFaceOnNode obj theRay
if result == undefined do
(
failLog "面级求交失败"
addLog " 求交失败,无法提取材质"
return undefined
)
-- 解包求交结果
local faceIdx = result[1] -- 多边形面号
local matID = result[2] -- 材质 ID
local triFaceID = result[3] -- 三角面号
local dist = result[4] -- 命中距离
-- 提取材质颜色和名称
stepLog "提取材质信息"
local col = getHitColor obj matID
local matName = getMatNameByID obj matID
detailLog ("材质名称: " + matName + " | 颜色: [" + (numStr col.r) + "," + (numStr col.g) + "," + (numStr col.b) + "]")
-- 输出结果
stepLog "输出 RayEx 结果"
RayExOutput obj hitPos matID faceIdx triFaceID col matName
return #(obj, hitPos, matID, faceIdx, triFaceID, col, matName)
)
-- ==========================================
-- 交互式拾取工具 (视口鼠标点击)
-- ==========================================
tool RayPickTool
(
-- mousePoint: 鼠标点击事件 (clickno=1 表示第一次点击)
on mousePoint clickno do
(
if clickno == 1 do
(
stepLog "视口点击拾取"
detailLog ("屏幕坐标: " + (mouse.pos as string))
-- mapScreenToWorldRay: 屏幕坐标 → 世界射线
local theRay = mapScreenToWorldRay mouse.pos
detailLog ("屏幕→世界射线: pos=" + (theRay.pos as string) + " dir=" + (theRay.dir as string))
-- 执行完整分析流程
local result = analyzeRay theRay
if result != undefined do
(
local obj = result[1]
local matID = result[3]
local faceIdx = result[4]
local triFaceID = result[5]
-- 更新状态标签
lbl_last.text = obj.name + " | 多边面" + (numStr faceIdx) + " | 三角面" + (numStr triFaceID) + " | matID=" + (numStr matID)
)
-- 拾取完成后停止工具, 还原鼠标状态
stepLog "拾取完成, 还原鼠标状态"
stopTool RayPickTool
)
)
-- mouseMove: 空实现 (必须声明, 否则工具会退出)
on mouseMove pt do ()
)
-- ==========================================
-- 按钮事件
-- ==========================================
-- btn_test: 主按钮 — 启动拾取工具
on btn_test pressed do
(
logMsgs = #()
lb_log.items = #()
format "\n========================================\n"
format " 材质吸取 - 准确性测试\n"
format "========================================\n"
stepLog "启动拾取工具"
addLog "点击视口对象拾取"
startTool RayPickTool
)
-- 对话框打开时的初始化日志
on matPickTestRollout open do (
addLog "点击按钮后在视口拾取对象"
)
)
-- 创建对话框
createDialog matPickTestRollout