亚洲熟妇av一区二区三区,久久久久久精品观看sss,免费观看四虎精品国产永久,国产成人精品一区二三区熟女,天堂网在线最新版www资源网

JavaScript實戰(zhàn)3D建模軟件開發(fā)

Subsurfer 是使用 HTML5 canvas 控件和 WebGL 用 Ja?vaScript 編寫的 3D 建模應(yīng)用程序。它實現(xiàn)了 Catmull-Clark 細(xì)分曲面算法。該程序的一個獨特功能是編輯窗口使用自定義 JavaScript 代碼在 2D 畫布上下文中實現(xiàn) 3D 投影。視圖窗口使用 WebGL 的 3D 畫布上下文。Subsurfer 是用 Notepad 編寫并在 Chrome 中調(diào)試的,源代碼可以從這里下載。

1、介紹

Subsurfer 中的建模基于立方體,每個模型都以立方體開始。頂部的按鈕選擇當(dāng)前工具。使用實體工具,你可以右鍵單擊實體并更改其某些屬性,例如顏色。使用滑塊工具完成模型的平移、縮放和旋轉(zhuǎn)。上下文菜單和顏色選擇器在Canvas控件中實現(xiàn)。此 3D 投影和所有模型編輯均在 2D 環(huán)境中完成。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

模型是通過將連續(xù)的細(xì)分曲面應(yīng)用于實體,結(jié)合擠出和切面的分裂來開發(fā)的。該界面是按鍵命令和使用實體、小平面、邊和頂點工具的右鍵單擊菜單的組合。在這里,我們看到了立方體表面細(xì)分的連續(xù)應(yīng)用。

JavaScript實戰(zhàn)3D建模軟件開發(fā)


復(fù)選框控制查看選項。在這里,我們看到選中了
清除輪廓選項的相同模型。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

在這里,我們看到一個被擠壓的刻面。擠壓是一個右鍵菜單項和一個擊鍵命令。使用 Facet 工具選擇 Facet。你可以單擊一個構(gòu)面,單擊并滾動以選擇多個構(gòu)面,或拖動一個框以凈選構(gòu)面。

擠出刻面時的一件重要事情是避免有共同的內(nèi)壁。當(dāng)擠壓法線指向同一方向的多個相鄰面時,可能會發(fā)生這種情況。共享內(nèi)墻會混淆 Catmull-Clark 算法,結(jié)果看起來不正確。為避免這種情況,在拉伸相鄰面時,除非它們的法線朝向不同的方向,否則最好使用“擠出組”命令。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

邊循環(huán)影響曲面細(xì)分將如何塑造模型??梢允褂?Bevel 命令(Facet 工具)或使用 Split 命令(Edge 工具)添加循環(huán)邊??梢允褂眠吘壒ぞ叩挠益I單擊菜單選項來選擇邊緣循環(huán)。

Subsurfer 中的每個面都是四邊形。Catmull-Clark 算法可以很好地處理四邊形,并且它們可以更容易地實現(xiàn)可以遍歷模型以查找邊緣循環(huán)和小平面循環(huán)的算法。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

頂點工具可以用來拖動頂點,就像面工具可以拖動面,邊工具可以拖動邊一樣。拖動模型元素時,顯示網(wǎng)格(網(wǎng)格復(fù)選框選項)很重要,這樣您就會知道您正在拖動哪個 2 維。否則,結(jié)果可能是意外和不受歡迎的。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

Subsurfer 有一個編輯窗口(2D 畫布上下文)和一個查看窗口(3D 畫布上下文)。它們由“編輯”和“查看”復(fù)選框控制。在這里,我們在“編輯”窗口中看到了一個模型,在“視圖”窗口中看到了它的 WebGL 等效模型。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

細(xì)分曲面建模生成具有平滑圓角曲線的形狀。通過仔細(xì)的規(guī)劃和耐心的編輯,可以通過小平面的擠壓、分割、縮放和傾斜、邊緣和頂點的平移以及平滑算法的連續(xù)應(yīng)用來生成復(fù)雜的模型。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

這是編輯窗口中 spacepig 模型的網(wǎng)格視圖。像所有 Subsurfer 模型一樣,它最初是一個立方體。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

Subsurfer 支持一些內(nèi)置紋理,例如木紋(如下所示)。名為textures.png的圖像文件包含所有紋理。

如果要從文件系統(tǒng)運行程序,瀏覽器安全設(shè)置將不允許網(wǎng)頁加載紋理圖像。HTML 頁面和 PNG 圖像都必須托管在同一臺服務(wù)器上。如果你有合適的軟件來設(shè)置它,您可以從 localhost 運行該程序?;蛘撸憧梢允褂锰厥獾拿钚羞x項運行Chrome.exe,以允許從文件系統(tǒng)加載紋理。需要執(zhí)行的命令是“ chrome.exe –allow-file-access-from-files”。在執(zhí)行此操作之前,你必須關(guān)閉所有 Chrome 實例。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

包括各種紋理,包括下面看到的 mod 佩斯利。有一個擠壓系列命令可以自動連續(xù)擠壓刻面,這有助于創(chuàng)造幻覺的洛夫克拉夫特式噩夢。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

源命令(左側(cè)按鈕)打開一個新選項卡,顯示當(dāng)前模型網(wǎng)格的文本表示。

JavaScript實戰(zhàn)3D建模軟件開發(fā)


Save、OpenDelete按鈕是使用 AJAX 調(diào)用實現(xiàn)和測試的,以將模型存儲在服務(wù)器上并按名稱檢索它們。但是,出于本文的目的,我不想在我的服務(wù)器上進(jìn)行任何點擊,因此我更改了路徑和名稱,因此按鈕不會做任何事情。你仍然可以使用提供的 AJAX 代碼,但你必須實現(xiàn)自己的 SOAP Web 服務(wù)并更改客戶端代碼以匹配。

但是,你仍然可以通過復(fù)制 Source 命令中的文本將模型保存在本地文件中,如上所示。如果想將本地保存的模型輸入到 Subsurfer 中,請使用輸入按鈕。這是左側(cè)的命令之一,但未在這些圖片中顯示。輸入命令會彈出一個表單,你只需將網(wǎng)格文本粘貼到字段中,如下所示。即使對于大型模型,這似乎也很有效。你可能會遇到瀏覽器安全設(shè)置的問題,但對我來說效果很好。

JavaScript實戰(zhàn)3D建模軟件開發(fā)

包括各種 WebGL 著色器,可以從右上角的下拉菜單中選擇。WebGL 中的著色器是使用 GLSL 實現(xiàn)的。具有可選鏡面反射的平面著色和 Phong(平滑)著色是最有用的。邊緣銳利的物體應(yīng)使用平面陰影。帶有 Phong 陰影的立方體看起來很有趣。我還實現(xiàn)了一些不真實的自定義著色器,包括下圖的節(jié)日彩虹著色器(這不是紋理,它是自定義著色器)。這個著色器對物體在空間中的位置很敏感,所以當(dāng)物體旋轉(zhuǎn)時顏色會以一種非常詭異的方式變化。

程序中內(nèi)置了一個幫助文件和一個擊鍵列表(左側(cè)的最后兩個按鈕),但是開始使用 Subsurfer 的最快方法是使用擊鍵命令通過擠出刻面和平滑實體來進(jìn)行實驗,看看什么您可以制作各種奇怪而有趣的模型。擠出小平面的擊鍵命令是' e',平滑實體的擊鍵命令是's'。您將希望選擇 Facet 工具,以便您可以選擇 facet。您可以使用 Facet 工具(和大多數(shù)其他工具)通過在窗口中單擊鼠標(biāo)右鍵并拖動來旋轉(zhuǎn)模型。加號和減號鍵將放大或縮小。單擊一個構(gòu)面以將其選中。您還可以凈選擇構(gòu)面并單擊 拖動以選擇區(qū)域。可以同時擠出多個面。但是,如果進(jìn)行多次拉伸,請確保小平面不朝向完全相同的方向,否則您最終會得到共享的內(nèi)壁,這會影響細(xì)分算法。如果擠壓面向相同方向的相鄰面,最好使用擠壓組(擊鍵' g')。

2、使用代碼

你可以從本地文件系統(tǒng)運行 HTML 文件。如上所述,如果在本地運行,則會遇到安全問題,并且紋理不會在 WebGL 中顯示。

要解決此問題,請關(guān)閉所有 Chrome 實例并使用以下命令啟動 Chrome:“ chrome.exe –allow-file-access-from-files”。

此外,Save、OpenDelete按鈕被有效禁用。要保存模型,請使用Source命令(左側(cè)按鈕)復(fù)制網(wǎng)格規(guī)范。要將保存的模型輸入到 Subsurfer,請使用輸入命令并將網(wǎng)格文本粘貼到提供的表格中。

擠出刻面時的一件重要事情是避免有共同的內(nèi)壁。當(dāng)擠壓法線指向同一方向的多個相鄰面時,可能會發(fā)生這種情況。內(nèi)墻會弄亂 Catmull-Clark 算法的結(jié)果。為避免這種情況,在拉伸相鄰面時,除非它們的法線朝向不同的方向,否則最好使用“擠出組”命令。

3、構(gòu)建編輯視圖

應(yīng)用程序中有大約 14000 行代碼。WebGL 部分使用 James Coglan 的 Sylvester 矩陣數(shù)學(xué)庫,根據(jù)許可協(xié)議使用。在本文中,我將介紹使該程序正常工作的一些基本元素。我可能會在以后的文章中更深入地介紹一些主題。

本節(jié)介紹如何在 2D 繪圖環(huán)境中生成編輯視圖的 3D 投影。

該程序使用 HTML5 Canvas 控件,該控件具有兩個上下文。這是初始化程序 UI 的函數(shù)。它添加了兩個Canvas控件并為一個獲取 2D 上下文,為另一個獲取 webgl (3D) 上下文。如果 webgl 不可用,它會回退到實驗性 webgl。WebGL 功能似乎在所有主要瀏覽器上都得到了很好的支持。其余代碼為用戶輸入設(shè)置偵聽器并處理其他事務(wù),例如將可用的著色器選項添加到listbox.

function startModel(){ alertUser(""); filename = ""; setInterval(timerEvent, 10); makeCube(); canvas = document.createElement('canvas'); canvas2 = document.createElement('canvas'); document.body.appendChild(canvas); document.body.appendChild(canvas2); canvas.style.position = 'fixed'; canvas2.style.position = 'fixed'; ctx = canvas.getContext('2d'); gl = canvas2.getContext("webgl") || canvas2.getContext("experimental-webgl"); pos = new Point(0, 0); // last known position lastClickPos = new Point(0, 0); // last click position window.addEventListener('resize', resize); window.addEventListener('keydown', keyDown); window.addEventListener('keyup', keyRelease); canvas.addEventListener('mousemove', mouseMove); canvas.addEventListener('mousedown', mouseDown); canvas.addEventListener('mouseup', mouseUp); canvas.addEventListener('mouseenter', setPosition); canvas.addEventListener('click', click); canvas2.addEventListener('mousemove', mouseMoveGL); canvas2.addEventListener('mousedown', mouseDownGL); canvas2.addEventListener('mouseup', mouseUpGL); canvas.style.backgroundColor = colorString(canvasBackgroundColor, false); canvas.style.position = "absolute"; canvas.style.border = '1px solid black'; canvas2.style.position = "absolute"; canvas2.style.border = '1px solid black'; resize(); document.getElementById("checkboxoutlines").checked = false; document.getElementById("checkboxsolid").checked = true; document.getElementById("checkboxgrid").checked = false; document.getElementById("toolslider").checked = true; document.getElementById("checkboxtwosided").checked = true; document.getElementById("checkboxwebgl").checked = false; document.getElementById("checkbox2DWindow").checked = true; document.getElementById("checkboxtransparent").checked = false; if (gl != null) { gl.clearColor(canvasBackgroundColor.R / 255.0, canvasBackgroundColor.G / 255.0, canvasBackgroundColor.B / 255.0, 1.0); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } addShaderToList("Phong"); addShaderToList("Rainbow 1"); addShaderToList("Rainbow 2"); addShaderToList("Stripes"); addShaderToList("Chrome"); addShaderToList("Smear"); addShaderToList("Flat"); addShaderToList("T-Map"); addShaderToList("Comic"); addShaderToList("Comic 2"); addShaderToList("Topo"); addShaderToList("Paint By Numbers"); var rect = canvas.getBoundingClientRect(); origin = new Point(-(rect.width / 2), -(rect.height / 2)); setEditViewOptions(); hideInputForm();}

由于各種原因,程序中的所有編輯都是在 2D 上下文中完成的,因為我似乎更容易解決與 2D 上下文中的命中檢測和用戶交互相關(guān)的問題。在 2D 上下文中繪圖也比在 WebGL 中繪圖簡單得多。

為了在 2D 中創(chuàng)建 3D 投影,只需要發(fā)生一些事情。這是將 3D 點映射到二維的投影代碼。為了實現(xiàn)這一點,只需要想象一個位于模型和觀察者眼睛之間的沿 Z 軸的 X/Y 平面。然后計算從眼睛到每個 3D 模型頂點的光線與該平面相交的位置。

function To2D(p3d) // gives a 3D->2D perspective projection{ var point3d = new Point3D(p3d.x, p3d.y, p3d.z); RotateXYZ(point3d, myCenter, radiansX, radiansY, radiansZ); var xRise = point3d.x - myCenter.x; var yRise = point3d.y - myCenter.y; var zRunEye = zEyePlane - point3d.z; var zRunView = zViewingPlane - point3d.z; var factor = (zRunEye - zRunView) / zRunEye; var x = (myCenter.x (factor * xRise)); var y = (myCenter.y (factor * yRise)); x *= ctx.canvas.width; x /= docSize; y *= ctx.canvas.width; y /= docSize; var p = new Point(Math.floor(x), -Math.floor(y)); // have to flip sign of Y coordinate, this makes it match the GL side p.x -= origin.x; p.y -= origin.y; return p;}

請注意,上述函數(shù)所做的第一件事是將點從其實際位置旋轉(zhuǎn)到當(dāng)前查看位置。這是為用戶提供一種旋轉(zhuǎn)作品并從各個角度查看的方式。這也是一件小事,如下所示。每當(dāng)用戶輸入鼠標(biāo)輸入來旋轉(zhuǎn)視圖時,變量radiansX、 radiansY和radiansZ都會更新并重新繪制投影。

function RotateXYZ(p, rotation_point, radiansX, radiansY, radiansZ){ if (radiansZ != 0.0) // rotate about Z axis { radiansZ = normalize_radians(radiansZ); if (radiansZ != 0) { var ydiff = (p.y) - (rotation_point.y); var xdiff = (p.x) - (rotation_point.x); var xd = (xdiff * Math.cos(radiansZ)) - (ydiff * Math.sin(radiansZ)); xd = Math.round(xd, 0); var yd = (xdiff * Math.sin(radiansZ)) (ydiff * Math.cos(radiansZ)); yd = Math.round(yd, 0); p.x = rotation_point.x (xd); p.y = rotation_point.y (yd); } } if (radiansY != 0.0) // rotate about the Y axis { radiansY = normalize_radians(radiansY); if (radiansY != 0) { var zdiff = (p.z) - (rotation_point.z); var xdiff = (p.x) - (rotation_point.x); var xd = (xdiff * Math.cos(radiansY)) - (zdiff * Math.sin(radiansY)); xd = Math.round(xd, 0); var zd = (xdiff * Math.sin(radiansY)) (zdiff * Math.cos(radiansY)); zd = Math.round(zd, 0); p.x = rotation_point.x (xd); p.z = rotation_point.z (zd); } } if (radiansX != 0.0) // rotate about the X axis { radiansX = normalize_radians(radiansX); if (radiansX != 0) { var ydiff = (p.y) - (rotation_point.y); var zdiff = (p.z) - (rotation_point.z); var zd = (zdiff * Math.cos(radiansX)) - (ydiff * Math.sin(radiansX)); zd = Math.round(zd, 0); var yd = (zdiff * Math.sin(radiansX)) (ydiff * Math.cos(radiansX)); yd = Math.round(yd, 0); p.z = rotation_point.z (zd); p.y = rotation_point.y (yd); } } }

模型由方面組成。面由邊組成,邊由點組成。以下是保存模型的基本數(shù)據(jù)結(jié)構(gòu)。請注意,就本程序而言,立方體仍然是立方體,無論它有多少面。每個模型都以具有 6 個面的立方體開始,但隨著擠壓、分割和平滑算法的應(yīng)用,將向立方體添加更多面。

function cube(left, right, top, bottom, front, back){ if (left == undefined) { left = 0; } if (right == undefined) { right = 0; } if (top == undefined) { top = 0; } if (bottom == undefined) { bottom = 0; } if (front == undefined) { front = 0; } if (back == undefined) { back = 0; } this.color = new Color(190, 180, 190); // default solid color this.outlineColor = new Color(0, 0, 0); // default solid outline color this.textureName = ""; this.nSubdivide = 0; this.left = left; this.right = right; this.top = top; this.bottom = bottom; this.front = front; this.back = back; this.previousFacetLists = []; this.facets = []; var lefttopback = new Point3D(left, top, back); var lefttopfront = new Point3D(left, top, front); var righttopfront = new Point3D(right, top, front); var righttopback = new Point3D(right, top, back); var leftbottomback = new Point3D(left, bottom, back); var leftbottomfront = new Point3D(left, bottom, front); var rightbottomfront = new Point3D(right, bottom, front); var rightbottomback = new Point3D(right, bottom, back); var topPoints = []; topPoints.push(clonePoint3D(lefttopback)); topPoints.push(clonePoint3D(righttopback)); topPoints.push(clonePoint3D(righttopfront)); topPoints.push(clonePoint3D(lefttopfront)); topPoints.reverse(); var bottomPoints = []; bottomPoints.push(clonePoint3D(leftbottomfront)); bottomPoints.push(clonePoint3D(rightbottomfront)); bottomPoints.push(clonePoint3D(rightbottomback)); bottomPoints.push(clonePoint3D(leftbottomback)); bottomPoints.reverse(); var frontPoints = []; frontPoints.push(clonePoint3D(lefttopfront)); frontPoints.push(clonePoint3D(righttopfront)); frontPoints.push(clonePoint3D(rightbottomfront)); frontPoints.push(clonePoint3D(leftbottomfront)); frontPoints.reverse(); var backPoints = []; backPoints.push(clonePoint3D(righttopback)); backPoints.push(clonePoint3D(lefttopback)); backPoints.push(clonePoint3D(leftbottomback)); backPoints.push(clonePoint3D(rightbottomback)); backPoints.reverse(); var leftPoints = []; leftPoints.push(clonePoint3D(lefttopback)); leftPoints.push(clonePoint3D(lefttopfront)); leftPoints.push(clonePoint3D(leftbottomfront)); leftPoints.push(clonePoint3D(leftbottomback)); leftPoints.reverse(); var rightPoints = []; rightPoints.push(clonePoint3D(righttopfront)); rightPoints.push(clonePoint3D(righttopback)); rightPoints.push(clonePoint3D(rightbottomback)); rightPoints.push(clonePoint3D(rightbottomfront)); rightPoints.reverse(); var id = 1; var s1 = new Facet(); s1.ID = id ; s1.points = topPoints; this.facets.push(s1); var s2 = new Facet(); s2.ID = id ; s2.points = bottomPoints; this.facets.push(s2); var s3 = new Facet(); s3.ID = id ; s3.points = backPoints; this.facets.push(s3); var s4 = new Facet(); s4.ID = id ; s4.points = frontPoints; this.facets.push(s4); var s5 = new Facet(); s5.ID = id ; s5.points = leftPoints; this.facets.push(s5); var s6 = new Facet(); s6.ID = id ; s6.points = rightPoints; this.facets.push(s6); for (var n = 0; n < this.facets.length; n ) { this.facets[n].cube = this; }}function Facet(){ this.cube = -1; this.ID = -1; this.points = []; this.point1 = new Point(0, 0); this.point2 = new Point(0, 0); this.closed = false; this.fill = false; this.averagePoint3D = new Point3D(0, 0, 0); this.normal = -1; this.edges = []; this.neighbors = []; this.greatestRotatedZ = 0; this.greatestLeastRotatedZ = 0; this.averageRotatedZ = 0; this.boundsMin = new Point3D(0, 0, 0); this.boundsMax = new Point3D(0, 0, 0);}function Point3D(x, y, z){ this.x = x; this.y = y; this.z = z;}

要在 2D 中繪制模型,只需將每個 facet 描述的多邊形從 3D 映射到 2D,然后填充生成的 2D 多邊形。只有兩個并發(fā)癥。第一個是每個面必須根據(jù)其相對于表示光源的矢量的角度進(jìn)行著色。第二個是在給定當(dāng)前視圖旋轉(zhuǎn)的情況下,必須根據(jù)它們沿 Z 軸的位置從后到前對刻面進(jìn)行排序。這樣,首先繪制背面的刻面,而前面的刻面將它們遮住,這就是你想要的。

需要注意的是,這種通過沿 Z 軸對多邊形進(jìn)行排序來描繪實體對象的方法是一種近似。它不考慮構(gòu)面之間的交叉點。此外,當(dāng)對象包含凹面時,Z 排序會給出看起來不正確的結(jié)果。然而,當(dāng)對象沒有凹面并且表面之間沒有相交時,該方法會產(chǎn)生足夠好的結(jié)果。當(dāng)小平面相對于模型的大小較小時,異常的發(fā)生會大大減少,就像應(yīng)用了平滑時一樣。如果存在不規(guī)則性,你始終可以在編輯期間通過旋轉(zhuǎn)模型和/或使用“清除”和“輪廓”查看選項并將模型視為具有透明表面的線框來解決它們。任何此類異常都不會出現(xiàn)在“查看”窗口中,

要對多邊形著色,必須獲取其法線。這是一個垂直于刻面表面的向量(使用叉積計算)。計算此法線和光源矢量之間的角度(使用點積),這用于使刻面顏色變亮或變暗。如果角度更接近 0,則刻面顏色變亮。如果角度接近 180,則刻面顏色變暗。這是計算刻面法線并對刻面進(jìn)行著色的代碼。

function CalculateNormal(facet){ var normal = -1; if (facet.points.length > 2) { var p0 = facet.points[0]; var p1 = facet.points[1]; var p2 = facet.points[2]; var a = timesPoint(minusPoints(p1, p0), 8); var b = timesPoint(minusPoints(p2, p0), 8); normal = new line(clonePoint3D(p0), new Point3D((a.y * b.z) - (a.z * b.y), // cross product -((a.x * b.z) - (a.z * b.x)), (a.x * b.y) - (a.y * b.x)) ); normal.end = LengthPoint(normal, cubeSize * 2); var avg = averageFacetPoint(facet.points); normal.end.x = avg.x - normal.start.x; normal.end.y = avg.y - normal.start.y; normal.end.z = avg.z - normal.start.z; normal.start = avg; } return normal;}function getLightSourceAngle(normal){ var angle = 0; if (normal != -1) { angle = normalize_radians(vectorAngle (lightSource, minusPoints(ToRotated(normal.end), ToRotated(normal.start)))); } return angle;} function vectorAngle(vector1, vector2){ var angle = 0.0; var length1 = Math.sqrt((vector1.x * vector1.x) (vector1.y * vector1.y) (vector1.z * vector1.z)); var length2 = Math.sqrt((vector2.x * vector2.x) (vector2.y * vector2.y) (vector2.z * vector2.z)); var dot_product = (vector1.x * vector2.x vector1.y * vector2.y vector1.z * vector2.z); var cosine_of_angle = dot_product / (length1 * length2); angle = Math.acos(cosine_of_angle); return angle;} function ShadeFacet(color, angle){ var darken_range = 0.75; var lighten_range = 0.75; var result = new Color(color.R, color.G, color.B); if (angle > 180) { angle = 360 - angle; } if (angle > 90) // darken { var darken_amount = (angle - 90) / 90; darken_amount *= darken_range; var r = color.R - (color.R * darken_amount); var g = color.G - (color.G * darken_amount); var b = color.B - (color.B * darken_amount); r = Math.min(255, Math.max(0, r)); g = Math.min(255, Math.max(0, g)); b = Math.min(255, Math.max(0, b)); result = new Color(r, g, b); } else // lighten { var lighten_amount = (90 - angle) / 90; lighten_amount *= lighten_range; var r = color.R ((255 - color.R) * lighten_amount); var g = color.G ((255 - color.G) * lighten_amount); var b = color.B ((255 - color.B) * lighten_amount); r = Math.max(0, Math.min(255, r)); g = Math.max(0, Math.min(255, g)); b = Math.max(0, Math.min(255, b)); result = new Color(r, g, b); } return result;}

一旦刻面被著色,就必須將它們從后到前排序,這樣當(dāng)你按順序繪制它們時,最近的刻面將覆蓋它們后面的刻面。

function sortFacets(){ allFacets = []; for (var w = 0; w < cubes.length; w ) { var cube = cubes[w]; for (var i = 0; i < cube.facets.length; i ) { allFacets.push(cube.facets[i]); } } sortFacetsOnZ(allFacets);}function sortFacetsOnZ(facets){ for (var i = 0; i < facets.length; i ) { setAverageAndGreatestRotatedZ(facets[i]); } facets.sort( function(a, b) { if (a.greatestRotatedZ == b.greatestRotatedZ) { if (a.leastRotatedZ == b.leastRotatedZ) { return a.averageRotatedZ - b.averageRotatedZ; } else { return a.leastRotatedZ - b.leastRotatedZ; } } else { return a.greatestRotatedZ - b.greatestRotatedZ } } );}

下面是一些在 2D 上下文中使用 3D 投影繪制編輯顯示的代碼。這里發(fā)生的基本事情是sortFacets()和drawCubes()。這就是產(chǎn)生立體形狀錯覺的 3D 投影的原因。此處的其他代碼與更新 WebGL 視圖和編輯 UI 的繪圖元素有關(guān)。編輯 UI 元素包括矩形方向網(wǎng)格和上下文菜單,以及模型元素(面、邊、頂點),這些元素會受到翻轉(zhuǎn)行為和高亮行為的影響,必須根據(jù)當(dāng)前工具和鼠標(biāo)位置重新繪制不同的顏色。

function updateModel(){ for (var c = 0; c < cubes.length; c ) { updateCube(cubes[c]); } sortFacets(); reloadSceneGL(); draw();}function draw(){ if (isGL && gl != null) { drawSceneGL(); } if (is2dWindow || !isGL) { ctx.clearRect(0, 0, canvas.width, canvas.height); findGridOrientation(); if (gridChosen()) { drawGridXY(); } lineColor = lineColorShape; drawCubes(); if (mouseIsDown && draggingShape) { draw3DRectangleFrom2DPoints(mouseDownPos, pos, false, "white"); } if (hitLine != -1) { var pts = []; pts.push(To2D(hitLine.start)); pts.push(To2D(hitLine.end)); drawPolygonHighlighted(pts); } if (hitFacet != -1 && toolChosen() == "facet") { drawPolygon3d(hitFacet.points, true, true, "yellow", true); } for (var g = 0; g < selectedLines.length; g ) { var pts = []; pts.push(To2D(selectedLines[g].start)); pts.push(To2D(selectedLines[g].end)); drawPolygonSelected(pts); } if (hitVertex != -1) { drawVertex(hitVertex, false); } for (var qq = 0; qq < selectedVertexes.length; qq ) { drawVertex(selectedVertexes[qq], true); } if (lineDiv != -1 && lineDiv2 != -1) { drawLine2D(lineDiv, "blue"); drawLine2D(lineDiv2, "blue"); } if (draggingRect) { draw2DRectangleFrom2DPoints(mouseDownPos, pos, "black"); } if (colorPickMode.length > 0) { drawColors(0, 0, colorPickHeight); } drawMenu(); }}function drawCubes(){ var drawlines = isOutline || !isShade; var drawNormals = isNormals; var shadeSolids = isShade; var dual = isDualSided; for (var i = 0; i < allFacets.length; i ) { var facet = allFacets[i]; if (facet.normal == -1) { facet.normal = CalculateNormal(facet); } var c = facet.cube.color; if (colorPickMode.length == 0) { if (facet.cube == hitSolid) { c = new Color(23, 100, 123); } if (listHas(selectedSolids, facet.cube)) { c = new Color(200, 30, 144); } if (listHas(selectedFacets, facet)) { c = new Color(0, 255, 255); } } c = ShadeFacet(c, degrees_from_radians(getLightSourceAngle(facet.normal))); var show = true; if (!dual) { show = ShowFacet(degrees_from_radians(getFrontSourceAngle(facet.normal))); } var colorFillStyle = colorString(c, isTransparent); var colorOutlineStyle = colorString(facet.cube.outlineColor, isTransparent); if (listHas(selectedSolids, facet.cube)) { drawlines = true; colorOutlineStyle = "red"; } if (show) { drawPolygon3d(facet.points, true, shadeSolids || listHas(selectedFacets, facet), colorFillStyle, drawlines, colorOutlineStyle); if (drawNormals) { drawLine3D(facet.normal, "magenta"); } } }}function drawPolygon3d(points, isClosed, isFill, fillColor, isOutline, outlineColor){ var result = []; if (points.length > 0) { for (var i = 0; i < points.length; i ) { result.push(To2D(points[i])); } drawPolygon(result, isClosed, isFill, fillColor, isOutline, outlineColor); }}function drawPolygon(points, isClosed, isFill, fillColor, isOutline, outlineColor, lineThickness){ if (points.length > 0) { isClosed = isClosed ? isClosed : false; isFill = isFill ? isFill : false; if (isOutline === undefined) { isOutline = true; } if (lineThickness === undefined) { lineThickness = 1; } if (outlineColor === undefined) { outlineColor = lineColor; } ctx.beginPath(); ctx.lineWidth = lineThickness; ctx.lineCap = 'round'; ctx.strokeStyle = outlineColor; if (isFill) { ctx.fillStyle = fillColor; } ctx.moveTo(points[0].x, points[0].y); for (var i = 1; i < points.length; i ) { ctx.lineTo(points[i].x, points[i].y); } if (isClosed) { ctx.lineTo(points[0].x, points[0].y); } if (isFill) { ctx.fill(); } if (isOutline) { ctx.stroke(); } }}

4、構(gòu)建 WebGL 模型

因此 2D 編輯視圖的制作相當(dāng)簡單。WebGL 視圖的制作有點困難,將在以后的文章中更深入地討論。我只會展示一些將我們的 JavaScript 數(shù)據(jù)結(jié)構(gòu)綁定到我們模型的 WebGL 表示的代碼。有五個基本元素必須被緩沖并綁定到 WebGL。這是完成這項工作的主要功能。

function bindModelGL(){ bindVerticesGL(); bindColorsGL(); bindVertexIndicesGL(); bindTextureCoordinatesGL(); bindNormalsGL();}

將顏色綁定到我們的模型。每個立方體只能是一種顏色。每個方面都有一個指向其父多維數(shù)據(jù)集的指針。請注意,就我們的目的而言,多維數(shù)據(jù)集只是一個分面列表,它可能是也可能不是實際的多維數(shù)據(jù)集。所有面的列表將為我們提供每個頂點的正確顏色。每個頂點需要 4 個元素:R、G、B 和 A(表示透明度的 alpha 通道)。我們?yōu)?A 使用 1.0,因此我們的 WebGL 模型將始終是不透明的。

function bindColorsGL(){ if (isGL && gl != null) { var generatedColors = []; for (var i = 0; i < allFacets.length; i ) { var f = allFacets[i]; var c = color2FromColor(f.cube.color); var b = []; b.push(c.R); b.push(c.G); b.push(c.B); b.push(1.0); // repeat each color 4 times for the 4 vertices of each facet for (var s = 0; s < 4; s ) { generatedColors.push(b[0]); generatedColors.push(b[1]); generatedColors.push(b[2]); generatedColors.push(b[3]); } } cubeVerticesColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesColorBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW); }}

我們必須綁定刻面法線,以便 WebGL 可以對模型進(jìn)行著色。請注意,對于每個面法線,我們只需要 3 個數(shù)字。這是因為 WebGL 只關(guān)心法線的方向,而不關(guān)心它在空間中的位置。

這里的一個具體問題是 Subsurfer 支持 Phong 著色,這需要頂點法線。如果你認(rèn)為每個小平面法線垂直于小平面表面,那么頂點法線是包含該頂點的所有小平面的法線的平均值。因此,當(dāng) Phong 著色生效時,必須計算頂點法線。我們不在 2D 投影中使用這些,因為我們只做平面著色,所以我們只需要小平面法線。但是 WebGL 中的 Phong 著色需要頂點法線。如果我們在 WebGL 中進(jìn)行平面著色,則不必計算頂點法線。在平面著色的情況下,我們只是使用面法線作為每個頂點的法線。

function bindNormalsGL(){ if (isGL && gl != null) { cubeVerticesNormalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); var vertexNormals = []; for (q = 0; q < allFacets.length; q ) { var f = allFacets[q]; if (f.normal == -1) { f.normal = CalculateNormal(f); } } if (fastVertexNormalMethod) { if (isSmoothShading()) { allSortedPoints = getFacetPointsAndSetUpBackPointers(allFacets); sortPointsByXYZ(allSortedPoints); stageVertexNeighborFacets(allSortedPoints); } } if (isSmoothShading()) { for (q = 0; q < allFacets.length; q ) { var f = allFacets[q]; for (var j = 0; j < f.points.length; j ) { var p = f.points[j]; var vn = p.vertexNormal; if (vn == undefined) { vn = calculateVertexNormal(p, allFacets); p.vertexNormal = vn; } vertexNormals.push((vn.end.x / reductionFactor) - (vn.start.x / reductionFactor)); vertexNormals.push((vn.end.y / reductionFactor) - (vn.start.y / reductionFactor)); vertexNormals.push((vn.end.z / reductionFactor) - (vn.start.z / reductionFactor)); } } } else { for (q = 0; q < allFacets.length; q ) { var f = allFacets[q]; for (var i = 0; i < 4; i ) { vertexNormals.push((f.normal.end.x / reductionFactor) - (f.normal.start.x / reductionFactor)); vertexNormals.push((f.normal.end.y / reductionFactor) - (f.normal.start.y / reductionFactor)); vertexNormals.push((f.normal.end.z / reductionFactor) - (f.normal.start.z / reductionFactor)); } } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals), gl.STATIC_DRAW); }}

我們必須綁定模型中的每個頂點。盡管 WebGL 需要三角形而不是四邊形才能正常工作,但不必復(fù)制頂點,因為我們將在頂點緩沖區(qū)中提供索引列表。一些索引將被重復(fù),這給了我們?nèi)切巍?/span>

function bindVerticesGL(){ cubeVerticesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer); var vertices = []; for (var i = 0; i < allFacets.length; i ) { var f = allFacets[i]; for (var j = 0; j < f.points.length; j ) { var point3d = f.points[j]; vertices.push(point3d.x / reductionFactor); vertices.push(point3d.y / reductionFactor); vertices.push((point3d.z / reductionFactor)); } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);}

在這里,我們構(gòu)建頂點索引緩沖區(qū)并將其綁定到 WebGL。索引模式 0、1、2 后跟 0、2、3 將我們的四個面頂點劃分為兩個三角形。

function bindVertexIndicesGL(){ cubeVerticesIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer); var cubeVertexIndices = []; var t = 0; for (var i = 0; i < allFacets.length; i ) { cubeVertexIndices.push(t 0); cubeVertexIndices.push(t 1); cubeVertexIndices.push(t 2); cubeVertexIndices.push(t 0); cubeVertexIndices.push(t 2); cubeVertexIndices.push(t 3); t = 4; } gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);}

我們模型中的每個頂點都有 X、Y、Z 表示空間位置,加上另外兩個坐標(biāo) U 和 V,它們是紋理圖像的偏移量。U 和 V 值介于 0 和 1 之間。對于復(fù)雜的形狀,我們會自動分配 U 和 V 坐標(biāo),就好像紋理被包裹在圖像周圍一樣。這是由assignPolarUV_2()函數(shù)完成的。

function bindTextureCoordinatesGL(){ for (var i = 0; i < cubes.length; i ) { assignPolarUV_2(cubes[i], i); } cubeVerticesTextureCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); var textureCoordinates = []; for (var i = 0; i < allFacets.length; i ) { if (isPolarUV) { var f = allFacets[i]; textureCoordinates.push(f.points[0].u); textureCoordinates.push(f.points[0].v); textureCoordinates.push(f.points[1].u); textureCoordinates.push(f.points[1].v); textureCoordinates.push(f.points[2].u); textureCoordinates.push(f.points[2].v); textureCoordinates.push(f.points[3].u); textureCoordinates.push(f.points[3].v); } else { textureCoordinates.push(0.0); textureCoordinates.push(0.0); textureCoordinates.push(1.0); textureCoordinates.push(0.0); textureCoordinates.push(1.0); textureCoordinates.push(1.0); textureCoordinates.push(0.0); textureCoordinates.push(1.0); } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW);}

5、平面著色器

當(dāng)直接處理 WebGL 時,有必要編寫自己的著色器。這些是用稱為 GLSL 的語言編寫的。每個著色器都必須有一個main()過程。著色器是一個在計算機圖形芯片上編譯和加載的小程序。

著色器包含在scriptHTML 文件的標(biāo)簽中,可以按名稱尋址。如果您為模型使用紋理而不是純色,則需要不同的著色器。Subsurfer 具有用于顏色和紋理的平面著色器,以及用于顏色和紋理的 Phong(平滑)著色器。還包括幾個古怪的自定義著色器。在這里,我將只提到純色情況下的平面著色器和 Phong 著色器。

這是純色的平面著色器。您必須提供頂點著色器和片段著色器。這適用于您實現(xiàn)的每種類型的著色。頂點著色器為您提供每個頂點的顏色。片段著色器可以在頂點之間進(jìn)行插值以創(chuàng)建更平滑的外觀。它基本上為每個單獨的像素著色。

下面的平面著色器的外觀與我們在 JavaScript 中構(gòu)建的 3D 投影非常相似,它顯示在編輯窗口中。如果你看一下這個頂點著色器在做什么,它實際上和我們之前在 JavaScript 函數(shù)中看到的計算是一樣的shadeFacet()。它采用頂點法線(在這種情況下,與小平面法線相同)和光源方向矢量之間的角度(點積),并使用它來使小平面顏色變亮或變暗。但是著色器可以更快地完成它,因為它在大規(guī)模并行設(shè)備上運行。此外,它還考慮了光的顏色,以及定向光和環(huán)境光。請注意,在此著色器中,燈光顏色和方向是硬編碼的。

這里的片段著色器并沒有做太多,它只是一個傳遞。這是因為平面著色器沒有插值或平滑處理,因此平面上的所有像素都可以被著色為相同的顏色。

<script id="vertex-shader-color-flat" type="x-shader/x-vertex"> // VERTEX SHADER COLOR (FLAT) attribute highp vec3 aVertexNormal; attribute highp vec3 aVertexPosition; attribute vec4 aVertexColor; uniform highp mat4 uNormalMatrix; uniform highp mat4 uMVMatrix; uniform highp mat4 uPMatrix; varying highp vec3 vLighting; varying lowp vec4 vColor; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); highp vec3 ambientLight = vec3(0.5, 0.5, 0.5); highp vec3 directionalLightColor = vec3(0.5, 0.5, 0.5); highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); vLighting = ambientLight (directionalLightColor * directional); vColor = aVertexColor; }</script><script id="fragment-shader-color-flat" type="x-shader/x-fragment"> // FRAGMENT SHADER COLOR (FLAT) varying lowp vec4 vColor; varying highp vec3 vLighting; uniform sampler2D uSampler; void main(void) { gl_FragColor = vec4(vColor.rgb * vLighting, 1.0); }</script>

6、Phong 著色器

Phong 著色提供更平滑的外觀,因為它在頂點之間進(jìn)行插值以單獨著色每個像素。顏色 Phong 著色器如下所示。

請注意,頂點著色器并沒有發(fā)生太多事情。大多數(shù)動作都發(fā)生在片段著色器中,因為我們要計算每個單獨的像素。關(guān)于頂點著色器最有趣的一點是,轉(zhuǎn)換后的頂點法線被聲明為“可變的”。這將導(dǎo)致它為片段著色器中的每個像素進(jìn)行平滑插值。

所以這個片段著色器實際上對每個像素使用不同的法線。您看不到任何明確的代碼來執(zhí)行此操作,因為它內(nèi)置于 GLSL 語言和“可變”類型中。與平面著色器一樣,環(huán)境光和定向光的顏色是硬編碼的,光的方向也是如此。此外,使用光方向向量與頂點法線之間的角度計算顏色與平面著色器非常相似。這里的不同之處在于,計算發(fā)生在片段著色器中,對每個像素使用不同的插值法線值。這就是賦予光滑外觀的原因。Phong 著色器比平面著色器更慢,因為它必須進(jìn)行更多的計算。

關(guān)于 Phong 著色器的最后一件事是我已經(jīng)實現(xiàn)了鏡面反射。如果選中 UI 上的“鏡面反射”復(fù)選框,則統(tǒng)一值specularUniform將設(shè)置為1. 如果發(fā)生這種情況,只要光源和頂點法線之間的角度足夠小,該像素的顏色就會自動設(shè)置為白色。這會產(chǎn)生使模型看起來有光澤的鏡面高光。

<script id="shader-vs-normals-notexture-phong" type="x-shader/x-vertex"> // VERTEX SHADER COLOR (PHONG) attribute highp vec3 aVertexNormal; attribute highp vec3 aVertexPosition; attribute vec4 aVertexColor; uniform highp mat4 uNormalMatrix; uniform highp mat4 uMVMatrix; uniform highp mat4 uPMatrix; varying vec3 vTransformedNormal; varying vec4 vPosition; varying lowp vec4 vColor; void main(void) { vPosition = uMVMatrix * vec4(aVertexPosition, 1.0); gl_Position = uPMatrix * vPosition; vTransformedNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0)); vColor = aVertexColor; }</script><script id="shader-fs-normals-notexture-phong" type="x-shader/x-fragment"> // FRAGMENT SHADER COLOR (PHONG) precision mediump float; uniform int specularUniform; varying vec3 vTransformedNormal; varying vec4 vPosition; varying lowp vec4 vColor; void main(void) { vec3 pointLightingLocation; pointLightingLocation = vec3(0, 13.5, 13.5); vec3 ambientColor; ambientColor = vec3(0.5, 0.5, 0.5); vec3 pointLightingColor; pointLightingColor = vec3(0.5, 0.5, 0.5); vec3 lightWeighting; vec3 lightDirection = normalize(pointLightingLocation - vPosition.xyz); float directionalLightWeighting = max(dot(normalize(vTransformedNormal), lightDirection), 0.0); lightWeighting = ambientColor pointLightingColor * directionalLightWeighting; vec4 fragmentColor; fragmentColor = vColor; gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a); if (specularUniform == 1) { if (dot(normalize(vTransformedNormal), lightDirection) > 0.99) // specular { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } } } </script>

6、細(xì)分曲面算法

我本來打算多說一些關(guān)于 Catmull-Clark 細(xì)分曲面算法以及我在 JavaScript 中的實現(xiàn),但是這篇文章已經(jīng)太長了,所以我將把它留到以后的文章中。但是,如果查看代碼,你可以看到發(fā)生了什么。我只想說大部分動作發(fā)生在函數(shù)subdivisionSurfaceProcessFacet() 內(nèi),它通過計算稱為重心的加權(quán)平均值來細(xì)分單個面。使用計時器在三個函數(shù)中實現(xiàn)該算法的原因是,我可以在屏幕底部繪制一個進(jìn)度指示計。我不得不這樣做,因為 JavaScript 中沒有真正的線程。該算法采用一個分面列表并將其替換為一個列表,其中每個分面都已被四個新分面替換。請注意,當(dāng)模型中有孔時必須小心。位于此類孔邊界上的刻面被視為特殊情況。

function startSubdivision(solid){ informUser("Subdividing, please wait..."); subdivSurfaceLoopCounter = 0; var facets = solid.facets; solidToSubdivide = solid; isSubdividing = true; if (solid.nSubdivide == 0) { solid.previousFacetLists.push(solid.facets); } for (var i = 0; i < facets.length; i ) { facets[i].edges = getFacetLines(facets[i]); facets[i].averagePoint3D = averageFacetPoint(facets[i].points); } findFacetNeighborsAndAdjacents(facets); for (var i = 0; i < facets.length; i ) { var facet = facets[i]; for (var j = 0; j < facet.edges.length; j ) { var edge = facet.edges[j]; var list = []; list.push(edge.start); list.push(edge.end); if (edge.parentFacet != -1 && edge.adjacentFacet != -1) { list.push(edge.parentFacet.averagePoint3D); list.push(edge.adjacentFacet.averagePoint3D); } edge.edgePoint = averageFacetPoint(list); } } subdivTimerId = setTimeout(subdivisionSurfaceProcessFacet, 0); newSubdivFacets = [];} function subdivisionSurfaceProcessFacet(){ var facet = solidToSubdivide.facets[subdivSurfaceLoopCounter]; var nEdge = 0; var neighborsAndCorners = facetNeighborsPlusFacet(facet); for (var j = 0; j < facet.points.length; j ) { var p = facet.points[j]; var facepoints = []; var edgepoints = []; var facetsTouchingPoint = findFacetsTouchingPoint(p, neighborsAndCorners); for (var n = 0; n < facetsTouchingPoint.length; n ) { var f = facetsTouchingPoint[n]; facepoints.push(averageFacetPoint(f.points)); } var edgesTouchingPoint = findEdgesTouchingPoint(p, facetsTouchingPoint); for (var m = 0; m < edgesTouchingPoint.length; m ) { var l = edgesTouchingPoint[m]; edgepoints.push(midPoint3D(l.start, l.end)); } var onBorder = false; if (facepoints.length != edgepoints.length) { onBorder = true; // vertex is on a border } var F = averageFacetPoint(facepoints); var R = averageFacetPoint(edgepoints); var n = facepoints.length; var barycenter = roundPoint(divPoint(plusPoints (plusPoints(F, timesPoint(R, 2)), timesPoint(p, n - 3)), n)); var n1 = nEdge; if (n1 > facet.edges.length - 1) { n1 = 0; } var n2 = n1 - 1; if (n2 < 0) { n2 = facet.edges.length - 1; } if (onBorder) { var borderAverage = []; var etp = edgesTouchingPoint; for (var q = 0; q < etp.length; q ) { var l = etp[q]; if (lineIsOnBorder(l)) { borderAverage.push(midPoint3D(l.start, l.end)); } } borderAverage.push(clonePoint3D(p)); barycenter = averageFacetPoint(borderAverage); } var newFacet = new Facet(); newFacet.points.push(clonePoint3D(facet.edges[n2].edgePoint)); newFacet.points.push(clonePoint3D(barycenter)); newFacet.points.push(clonePoint3D(facet.edges[n1].edgePoint)); newFacet.points.push(clonePoint3D(facet.averagePoint3D)); newSubdivFacets.push(newFacet); newFacet.cube = solidToSubdivide; nEdge ; } drawThermometer(solidToSubdivide.facets.length, subdivSurfaceLoopCounter); subdivSurfaceLoopCounter ; if (subdivSurfaceLoopCounter >= solidToSubdivide.facets.length) { clearInterval(subdivTimerId); finishSubdivision(solidToSubdivide); } else { subdivTimerId = setTimeout(subdivisionSurfaceProcessFacet, 0); }}function finishSubdivision(parentShape){ parentShape.nSubdivide ; parentShape.facets = newSubdivFacets; fuseFaster(parentShape); selectedFacets = []; selectedLines = []; selectedVertexes = []; sortFacets(); setFacetCount(parentShape); isSubdividing = false; alertUser(""); reloadSceneGL(); draw();}


原文鏈接:http://www.bimant.com/blog/js-3d-modeler-dev-tutorial/

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 舉報,一經(jīng)查實,本站將立刻刪除。