chili3d筆記22 正交投影3d重建筆記3 面構建

雙視圖重建3d solid

?

import { FaceNode } from "chili";
import {IDocument,IEdge,Logger,ShapeNode,XYZ
} from "chili-core";
import { Graph } from "graphlib";
function pointToString(point: XYZ): string {return `${point.x.toFixed(0)}-${point.y.toFixed(0)}-${point.z.toFixed(0)}`;}
function buildGraphFromEdges(edges: IEdge[]
): { graph: Graph; edgeIndexMap: Map<string, number[]> } {const graph = new Graph({ directed: false });const edgeIndexMap = new Map<string, number[]>();edges.forEach((edge, index) => {const start = pointToString(edge.curve().startPoint());const end = pointToString(edge.curve().endPoint());graph.setEdge(start, end);if (!edgeIndexMap.has(start)) edgeIndexMap.set(start, []);if (!edgeIndexMap.has(end)) edgeIndexMap.set(end, []);edgeIndexMap.get(start)!.push(index);edgeIndexMap.get(end)!.push(index);});return { graph, edgeIndexMap };
}
function findCyclesWithEdgeIndices(graph: Graph,edgeIndexMap: Map<string, number[]>
): { nodeCycles: string[][]; edgeCycles: number[][] } {const nodeCycles: string[][] = [];const edgeCycles: number[][] = [];const addedCycles = new Set<string>(); // 新增:用于記錄已添加的環路徑for (const start of graph.nodes()) {const stack: { current: string; path: string[]; visited: Set<string> }[] = [];stack.push({ current: start, path: [], visited: new Set() });while (stack.length > 0) {const { current, path, visited } = stack.pop()!;if (visited.has(current)) continue;const newPath = [...path, current];const newVisited = new Set(visited);newVisited.add(current);const neighbors = graph.neighbors(current) || [];for (const neighbor of neighbors) {if (newPath.length > 1 && newPath[newPath.length - 2] === neighbor) {continue;}if (neighbor === start && newPath.length >= 3) {// 構造唯一標識符并判斷是否重復const cycleKey = [...newPath].sort().join('-');if (!addedCycles.has(cycleKey)) {nodeCycles.push(newPath);addedCycles.add(cycleKey); // 標記為已添加// 提取邊索引邏輯const edgeIndices = new Set<number>();for (let i = 0; i < newPath.length; i++) {const a = newPath[i];const b = newPath[(i + 1) % newPath.length];const indicesA = edgeIndexMap.get(a) || [];const indicesB = edgeIndexMap.get(b) || [];const common = indicesA.filter((idx) => indicesB.includes(idx));common.forEach((idx) => edgeIndices.add(idx));}edgeCycles.push([...edgeIndices]);}} else if (!newVisited.has(neighbor)) {stack.push({current: neighbor,path: newPath,visited: newVisited,});}}}}return { nodeCycles, edgeCycles };
}
function isPointsCoplanar(points: XYZ[]): boolean {if (points.length <= 3) return true; // 3個點或更少一定共面const p0 = points[0];const p1 = points[1];const p2 = points[2];// 計算平面的法向量const v1 =new XYZ(p1.x - p0.x,p1.y - p0.y,p1.z - p0.z) ;const v2 =new XYZ(p2.x - p0.x,p2.y - p0.y,p2.z - p0.z ) ;const normal = crossProduct(v1, v2);// 平面方程 ax + by + cz + d = 0const a = normal.x;const b = normal.y;const c = normal.z;const d = -(a * p0.x + b * p0.y + c * p0.z);for (let i = 3; i < points.length; i++) {const p = points[i];const distance = Math.abs(a * p.x + b * p.y + c * p.z + d);if (distance > Number.EPSILON) {return false; // 點不共面}}return true;
}// 向量叉乘
function crossProduct(v1: XYZ, v2: XYZ): XYZ {const xyz=new XYZ(  v1.y * v2.z - v1.z * v2.y,v1.z * v2.x - v1.x * v2.z,v1.x * v2.y - v1.y * v2.x,)return xyz;
}
function addUniquePoint(point: XYZ, set: Set<string>, list: XYZ[]) {const key = `${point.x},${point.y},${point.z}`;if (!set.has(key)) {set.add(key);list.push(point);}
}
// 提取邊中的點
function extractPointsFromEdges(edges: IEdge[]): XYZ[] {const pointsSet = new Set<string>();const pointsList: XYZ[] = [];edges.forEach(edge => {const start = edge.curve().startPoint();const end = edge.curve().endPoint();addUniquePoint(start, pointsSet, pointsList);addUniquePoint(end, pointsSet, pointsList);});return pointsList;
}
export function face_rebuild(document: IDocument): void {const models=document.selection.getSelectedNodes().map((x) => x as ShapeNode).filter((x) => {if (x === undefined) return false;let shape = x.shape.value;if (shape === undefined) return false;return true;});document.selection.clearSelection();const edges = models.map((x) => x.shape.value.copy()) as IEdge[];  const { graph, edgeIndexMap } = buildGraphFromEdges(edges);
const { nodeCycles, edgeCycles } = findCyclesWithEdgeIndices(graph, edgeIndexMap);
const addedCycles = new Set<string>(); // 用于跟蹤已添加的環
const validEdgeCycles = edgeCycles.filter((cycle, i) => nodeCycles[i].length >= 4);
Logger.info("Edge Cycles:", validEdgeCycles);for (let i = 0; i < validEdgeCycles .length; i++) {const edgeCycle = validEdgeCycles [i];const nodeCycle = nodeCycles[i];// 提取該環涉及的所有邊const cycleEdges = edgeCycle.map(index => edges[index]);// 判斷這些邊是否共面const points = extractPointsFromEdges(cycleEdges);const isCoplanar = isPointsCoplanar(points);if (isCoplanar) {document.addNode(new FaceNode(document, cycleEdges));} else {Logger.info(`Skipped non-coplanar cycle (edges: ${edgeCycle.join(",")}):`, nodeCycle.join(" → "));}
}}

多了一個面少了一個面

問題應該是出在取點沒有按edge順序導致面不準確



8條邊輸出了10條

完美

import { FaceNode } from "chili";
import {IDocument,IEdge,Logger,ShapeNode,XYZ
} from "chili-core";
import { Graph } from "graphlib";
function pointToString(point: XYZ): string {return `${point.x.toFixed(1)}-${point.y.toFixed(1)}-${point.z.toFixed(1)}`;}
function buildGraphFromEdges(edges: IEdge[]
): { graph: Graph; edgeIndexMap: Map<string, number[]> } {const graph = new Graph({ directed: false });const edgeIndexMap = new Map<string, number[]>();const edgePairSet = new Map<string, number>(); // 新增:記錄已添加的邊對edges.forEach((edge, index) => {const start = pointToString(edge.curve().startPoint());const end = pointToString(edge.curve().endPoint());// 構建唯一鍵,保證無向邊的唯一性(無論起點終點順序)const key = [start, end].sort().join('|');// 如果該邊對已經存在,則不再重復添加圖邊if (!edgePairSet.has(key)) {graph.setEdge(start, end);edgePairSet.set(key, index);}// 維護每個點對應的邊索引if (!edgeIndexMap.has(start)) edgeIndexMap.set(start, []);if (!edgeIndexMap.has(end)) edgeIndexMap.set(end, []);edgeIndexMap.get(start)!.push(index);edgeIndexMap.get(end)!.push(index);});return { graph, edgeIndexMap };
}
function findCyclesWithEdgeIndices(graph: Graph,edgeIndexMap: Map<string, number[]>
): { nodeCycles: string[][]; edgeCycles: number[][] } {const nodeCycles: string[][] = [];const edgeCycles: number[][] = [];const addedCycles = new Set<string>(); // 記錄已添加的環路徑const usedEdgesInCycles = new Set<string>(); // 記錄所有已被使用的邊組合for (const start of graph.nodes()) {const stack: { current: string; path: string[]; visited: Set<string> }[] = [];stack.push({ current: start, path: [], visited: new Set() });while (stack.length > 0) {const { current, path, visited } = stack.pop()!;if (visited.has(current)) continue;const newPath = [...path, current];const newVisited = new Set(visited);newVisited.add(current);const neighbors = graph.neighbors(current) || [];for (const neighbor of neighbors) {// 跳過回頭路(避免 A → B → A)if (newPath.length > 1 && newPath[newPath.length - 2] === neighbor) {continue;}// 檢查是否回到起點并形成環if (neighbor === start && newPath.length >= 3) {// 構造唯一標識符(排序節點)用于去重const cycleKey = [...newPath].sort().join('-');if (!addedCycles.has(cycleKey)) {nodeCycles.push(newPath);addedCycles.add(cycleKey); // 標記為已添加// 提取邊索引,并確保每條邊只用一次const edgeIndices = new Set<number>();const usedEdgesInThisCycle = new Set<string>(); // 當前環中已使用的邊for (let i = 0; i < newPath.length; i++) {const a = newPath[i];const b = newPath[(i + 1) % newPath.length];// 邊的唯一標識(無向)const edgeKey = [a, b].sort().join('|');// 如果這條邊已經被這個環使用過,則跳過if (usedEdgesInThisCycle.has(edgeKey)) continue;// 獲取共享該邊的索引const indicesA = edgeIndexMap.get(a) || [];const indicesB = edgeIndexMap.get(b) || [];const common = indicesA.filter((idx) => indicesB.includes(idx));if (common.length > 0) {edgeIndices.add(common[0]); // 使用第一個匹配的邊索引usedEdgesInThisCycle.add(edgeKey); // 當前環中標記為已使用usedEdgesInCycles.add(edgeKey);   // 全局標記為已使用}}edgeCycles.push([...edgeIndices]);}} else if (!newVisited.has(neighbor)) {// 繼續深度優先搜索stack.push({current: neighbor,path: newPath,visited: newVisited,});}}}}return { nodeCycles, edgeCycles };
}
function isPointsCoplanar(points: XYZ[]): boolean {if (points.length <= 3) return true; // 3個點或更少一定共面const p0 = points[0];const p1 = points[1];const p2 = points[2];// 計算平面的法向量const v1 =new XYZ(p1.x - p0.x,p1.y - p0.y,p1.z - p0.z) ;const v2 =new XYZ(p2.x - p0.x,p2.y - p0.y,p2.z - p0.z ) ;const normal = crossProduct(v1, v2);// 平面方程 ax + by + cz + d = 0const a = normal.x;const b = normal.y;const c = normal.z;const d = -(a * p0.x + b * p0.y + c * p0.z);for (let i = 3; i < points.length; i++) {const p = points[i];const distance = Math.abs(a * p.x + b * p.y + c * p.z + d);if (distance > Number.EPSILON) {return false; // 點不共面}}return true;
}// 向量叉乘
function crossProduct(v1: XYZ, v2: XYZ): XYZ {const xyz=new XYZ(  v1.y * v2.z - v1.z * v2.y,v1.z * v2.x - v1.x * v2.z,v1.x * v2.y - v1.y * v2.x,)return xyz;
}
function addUniquePoint(point: XYZ, set: Set<string>, list: XYZ[]) {const key = `${point.x},${point.y},${point.z}`;if (!set.has(key)) {set.add(key);list.push(point);}
}
// 提取邊中的點
function extractPointsFromEdges(edges: IEdge[]): XYZ[] {const pointsSet = new Set<string>();const pointsList: XYZ[] = [];edges.forEach(edge => {const start = edge.curve().startPoint();const end = edge.curve().endPoint();addUniquePoint(start, pointsSet, pointsList);addUniquePoint(end, pointsSet, pointsList);});return pointsList;
}
export function face_rebuild(document: IDocument): void {const models=document.selection.getSelectedNodes().map((x) => x as ShapeNode).filter((x) => {if (x === undefined) return false;let shape = x.shape.value;if (shape === undefined) return false;return true;});document.selection.clearSelection();const edges = models.map((x) => x.shape.value.copy()) as IEdge[];  const { graph, edgeIndexMap } = buildGraphFromEdges(edges);
const { nodeCycles, edgeCycles } = findCyclesWithEdgeIndices(graph, edgeIndexMap);
const addedCycles = new Set<string>(); // 用于跟蹤已添加的環
const validEdgeCycles = edgeCycles.filter((cycle, i) => nodeCycles[i].length >= 4);
//Logger.info("Edge Cycles:", validEdgeCycles);for (let i = 0; i < validEdgeCycles .length; i++) {const edgeCycle = validEdgeCycles [i];const nodeCycle = nodeCycles[i];// 提取該環涉及的所有邊const cycleEdges = edgeCycle.map(index => edges[index]);// 判斷這些邊是否共面const points = extractPointsFromEdges(cycleEdges);const isCoplanar = isPointsCoplanar(points);if (isCoplanar) {Logger.info(`Rebuilding face (edges: ${edgeCycle.join(",")}):`, nodeCycle.join(" → "));cycleEdges.map(edge => (Logger.info(`  ${pointToString(edge.curve().startPoint())} → ${pointToString(edge.curve().endPoint())}`)));document.addNode(new FaceNode(document, cycleEdges));} else {//  Logger.info(`Skipped non-coplanar cycle (edges: ${edgeCycle.join(",")}):`, nodeCycle.join(" → "));}
}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/86919.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86919.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86919.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Kotlin 協程使用與通信

一、協程基礎使用 1. 協程的三種創建方式 (1) launch - 啟動后臺作業 val job CoroutineScope(Dispatchers.IO).launch {// 后臺操作delay(1000)println("任務完成 ${Thread.currentThread().name}")// 輸出&#xff1a;任務完成 DefaultDispatcher-worker-1 } j…

Ubuntu服務器(公網)- Ubuntu客戶端(內網)的FRP內網穿透配置教程

以下是為Ubuntu服務器&#xff08;公網&#xff09;- Ubuntu客戶端&#xff08;內網&#xff09;的FRP內網穿透配置教程&#xff0c;基于最新版本&#xff08;2025年6月&#xff0c;使用frp_0.61.1_linux_amd64&#xff09;整理&#xff1a; 一、服務端配置&#xff08;公網Ubu…

什么是哈希函數(SHA-256)

SHA-256 是區塊鏈系統中最核心的加密基礎之一&#xff0c;尤其是在比特幣、以太坊、文件存證等場景中扮演“指紋識別器”的角色。下面是對它的詳細講解&#xff0c;包括原理、特點、用途和代碼示例。 &#x1f4cc; 一、什么是 SHA-256&#xff1f; SHA-256 是一種密碼學哈希函…

大模型的“Tomcat”:一文讀懂AI推理引擎(Inference Engine)

點擊下方“JavaEdge”&#xff0c;選擇“設為星標” 第一時間關注技術干貨&#xff01; 免責聲明~ 任何文章不要過度深思&#xff01; 萬事萬物都經不起審視&#xff0c;因為世上沒有同樣的成長環境&#xff0c;也沒有同樣的認知水平&#xff0c;更「沒有適用于所有人的解決方案…

《從0到1:C/C++音視頻開發自學完全指南》

從0到1&#xff1a;C/C音視頻開發自學完全指南 一、開篇&#xff1a;為什么選擇C/C切入音視頻開發&#xff1f; 當你刷著抖音短視頻、參加騰訊會議、觀看B站直播時&#xff0c;背后都是音視頻技術在支撐。根據艾瑞咨詢數據&#xff0c;2024年中國音視頻相關產業規模已突破5000…

微信小程序之單行溢出隱藏和雙行溢出隱藏

首先&#xff0c;我們做個text&#xff0c;加入了一個長文本&#xff0c;就像下面那樣&#xff1a; wxml : <view class"container"><text>劉德華&#xff08;Andy Lau&#xff09;&#xff0c;1961年9月27日出生于中國香港&#xff0c;華語影視男演員、…

PHP安裝使用教程

一、PHP 簡介 PHP&#xff08;Hypertext Preprocessor&#xff09;是一種廣泛應用的開源服務器端腳本語言&#xff0c;尤其適用于 Web 開發&#xff0c;可嵌入 HTML 中使用。其運行速度快、易學易用&#xff0c;支持多種數據庫和平臺。 二、PHP 安裝教程 2.1 支持平臺 PHP 支…

ThreadLocal、InheritableThreadLocal與TransmittableThreadLocal深度解析

文章目錄 一、概念說明1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 二、使用場景1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 三、存在的問題1、ThreadLocal2、InheritableThreadLocal3、TransmittableThreadLocal 四、示例…

ERP系統Bug記錄

2025.06.30 2025/06/30-10:51:02 [http-nio-9999-exec-3] com.yxx.jsh.erp.service.LogService - 異常碼[300],異常提示[數據查詢異常],異常[{}] java.lang.NullPointerException: nullat com.yxx.jsh.erp.base.TableSupport.getBuildPageRequest(TableSupport.java:46)at com…

C# Avalonia 的 Source Generators 用處

C# Avalonia 的 Source Generators 用處 文章目錄 **1. 自動生成 MVVM 綁定代碼****2. 強類型 XAML 數據綁定****3. 自動注冊視圖&#xff08;View&#xff09;與視圖模型&#xff08;ViewModel&#xff09;****4. 資源文件與本地化的強類型訪問****5. 路由事件與命令的自動化處…

stm32之測量占空比

#include "tim4.h"void TIM4_Init(void) {// 開啟時鐘RCC->APB1ENR | RCC_APB1ENR_TIM4EN;RCC->APB2ENR | RCC_APB2ENR_IOPBEN; // 使用 TIM4 的 GPIOB 時鐘// 配置 PB6 為浮空輸入 CNF 01 MODE 00GPIOB->CRL & ~GPIO_CRL_MODE6;GPIOB->CRL & ~G…

工廠模式 - Flutter中的UI組件工廠,按需生產各種“產品

想要動態創建不同風格的按鈕&#xff1f;想一鍵切換整個主題&#xff1f;工廠模式就是你的"生產流水線"&#xff01; 想象一下這個場景&#xff1a; 你決定擴大奶茶店業務&#xff0c;推出兩個品牌系列&#xff1a; 經典系列&#xff1a;傳統珍珠奶茶&#xff0c;紅…

基于 SpringBoot+Vue.js+ElementUI 的 Cosplay 論壇設計與實現7000字論文

基于 SpringBootVue.jsElementUI 的 Cosplay 論壇設計與實現 摘要 本論文設計并實現了一個基于 SpringBoot、Vue.js 和 ElementUI 的 Cosplay 論壇平臺。該平臺旨在為 Cosplay 愛好者提供一個集作品展示、交流互動、活動組織于一體的綜合性社區。論文首先分析了 Cosplay 論壇…

超標量處理器11-Alpha21264 處理器

1. 簡介 21264處理器是一款4-way&#xff0c;亂序執行的超標量處理器&#xff0c;采用0.35um的CMOS工藝&#xff0c;工作電壓是2.2V, 工作頻率是466-667MHz; 處理器能支持60條指令&#xff0c;也即ROB的深度是60; Load/Store指令也采取亂序執行, 總共7級流水。I-CACHE和D-CACH…

Spring 中 Bean 的生命周期

一、什么是 Bean 生命周期&#xff1f; Spring 中的 Bean 生命周期是指一個 Bean 從 被容器創建到 最終銷毀 所經歷的一系列過程。 它體現了 Spring IOC 容器在管理 Bean 實例時所執行的各個鉤子流程&#xff0c;包括初始化、依賴注入、增強處理、銷毀等多個環節。 二、Bean 生…

C++ 中 std::string 與 QString 的深度剖析

在 C 編程領域&#xff0c;std::string 和 QString 是兩種廣泛應用的字符串類型&#xff0c;它們在設計理念、功能特性以及適用場景上都呈現出鮮明的特點。本文將從多個維度對這兩種字符串類型進行深度剖析&#xff0c;并詳細闡述它們之間的相互轉化方法。 std::string 是 C 標…

不止于“修補”:我如何用Adobe AI重塑設計與視頻敘事流程

最近我深度體驗了一把來自英國Parvis School of Economics and Music的Adobe正版教育訂閱&#xff0c;在把玩PhotoShop、Premiere Pro這些“老伙計”的新功能時&#xff0c;的確挖到了一些寶藏&#xff0c;覺得非常有必要與大家說道說道。首先得聊聊這個訂閱給我的直觀感受&…

重頭開始學ROS(5)---阿克曼底盤的URDF建模與Gazebo控制(使用Xacro優化)

阿克曼底盤的URDF建模與Gazebo控制&#xff08;使用Xacro優化&#xff09; 阿克曼底盤建模 建模 我們使用后輪驅動&#xff0c;前輪轉向的阿克曼底盤模型。 那么對于后輪只需進行正常的continous joint連接即可 對于前輪&#xff0c;有兩個自由度&#xff0c;旋轉和轉向&…

RabbitMq中啟用NIO

? 所屬類 com.rabbitmq.client.ConnectionFactory&#x1f9e0; 使用背景 RabbitMQ Java 客戶端默認使用傳統的 阻塞 I/O (java.net.Socket) 實現。如果你希望&#xff1a; 更好地控制 線程數獲得更好的 并發性能降低 每個連接的線程占用在高并發連接或消費者數量較多的系統…

[Dify]-基礎篇2- 如何注冊并快速上手 Dify 平臺

在生成式 AI 應用開發新時代,如何快速搭建一個高效、可維護、易上線的 AI 工具,是每位開發者關注的核心。Dify,正是為此而生的一站式平臺。本篇將以新手視角,帶你從注冊賬號、配置環境,到構建應用、部署上線,手把手完成你的第一個 AI 項目。 注冊并設置工作環境 1. 賬號…