model.js 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. 'use strict';
  2. const { updateElementReadonly, updateElementInvalid, getPropValue, setPropValue } = require('../../utils/assets');
  3. exports.template = /* html */`
  4. <div class="container">
  5. <ui-prop>
  6. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.GlTFUserData.normals.name" tooltip="i18n:ENGINE.assets.fbx.GlTFUserData.normals.title"></ui-label>
  7. <ui-select slot="content" class="normals-select"></ui-select>
  8. </ui-prop>
  9. <ui-prop>
  10. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.GlTFUserData.tangents.name" tooltip="i18n:ENGINE.assets.fbx.GlTFUserData.tangents.title"></ui-label>
  11. <ui-select slot="content" class="tangents-select"></ui-select>
  12. </ui-prop>
  13. <ui-prop>
  14. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.GlTFUserData.morphNormals.name" tooltip="i18n:ENGINE.assets.fbx.GlTFUserData.morphNormals.title"></ui-label>
  15. <ui-select slot="content" class="morphNormals-select"></ui-select>
  16. </ui-prop>
  17. <ui-prop>
  18. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.GlTFUserData.skipValidation.name" tooltip="i18n:ENGINE.assets.fbx.GlTFUserData.skipValidation.title"></ui-label>
  19. <ui-checkbox slot="content" class="skipValidation-checkbox"></ui-checkbox>
  20. </ui-prop>
  21. <ui-prop>
  22. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.disableMeshSplit.name" tooltip="i18n:ENGINE.assets.fbx.disableMeshSplit.title"></ui-label>
  23. <ui-checkbox slot="content" class="disableMeshSplit-checkbox"></ui-checkbox>
  24. </ui-prop>
  25. <ui-prop>
  26. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.allowMeshDataAccess.name" tooltip="i18n:ENGINE.assets.fbx.allowMeshDataAccess.title"></ui-label>
  27. <ui-checkbox slot="content" class="allowMeshDataAccess-checkbox"></ui-checkbox>
  28. </ui-prop>
  29. <ui-prop>
  30. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.addVertexColor.name" tooltip="i18n:ENGINE.assets.fbx.addVertexColor.title"></ui-label>
  31. <ui-checkbox slot="content" class="addVertexColor-checkbox"></ui-checkbox>
  32. </ui-prop>
  33. <ui-prop>
  34. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.promoteSingleRootNode.name" tooltip="i18n:ENGINE.assets.fbx.promoteSingleRootNode.title"></ui-label>
  35. <ui-checkbox slot="content" class="promoteSingleRootNode-checkbox"></ui-checkbox>
  36. </ui-prop>
  37. <ui-prop hidden>
  38. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.generateLightmapUVNode.name" tooltip="i18n:ENGINE.assets.fbx.generateLightmapUVNode.title"></ui-label>
  39. <ui-checkbox slot="content" class="generateLightmapUVNode-checkbox"></ui-checkbox>
  40. </ui-prop>
  41. <ui-section class="meshOptimize config" cache-expand="fbx-model-mesh-optimizer">
  42. <div slot="header" class="meshOptimize-header">
  43. <ui-checkbox slot="content" class="meshOptimize-checkbox"></ui-checkbox>
  44. <ui-label value="i18n:ENGINE.assets.fbx.meshOptimize.name" tooltip="i18n:ENGINE.assets.fbx.meshOptimize.title"></ui-label>
  45. </div>
  46. <div class="meshOptimize-options">
  47. <ui-prop>
  48. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshOptimize.vertexCache.name" tooltip="i18n:ENGINE.assets.fbx.meshOptimize.vertexCache.title"></ui-label>
  49. <ui-checkbox slot="content" class="meshOptimize-vertexCache-checkbox"></ui-checkbox>
  50. </ui-prop>
  51. <ui-prop>
  52. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshOptimize.vertexFetch.name" tooltip="i18n:ENGINE.assets.fbx.meshOptimize.vertexFetch.title"></ui-label>
  53. <ui-checkbox slot="content" class="meshOptimize-vertexFetch-checkbox"></ui-checkbox>
  54. </ui-prop>
  55. <ui-prop>
  56. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshOptimize.overdraw.name" tooltip="i18n:ENGINE.assets.fbx.meshOptimize.overdraw.title"></ui-label>
  57. <ui-checkbox slot="content" class="meshOptimize-overdraw-checkbox"></ui-checkbox>
  58. </ui-prop>
  59. </div>
  60. </ui-section>
  61. <ui-section class="meshSimplify config" cache-expand="fbx-model-mesh-simplifier">
  62. <div slot="header" class="meshSimplify-header">
  63. <ui-checkbox slot="content" class="meshSimplify-checkbox"></ui-checkbox>
  64. <ui-label value="i18n:ENGINE.assets.fbx.meshSimplify.name" tooltip="i18n:ENGINE.assets.fbx.meshSimplify.title"></ui-label>
  65. </div>
  66. <div class="meshSimplify-options">
  67. <ui-prop>
  68. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshSimplify.targetRatio.name" tooltip="i18n:ENGINE.assets.fbx.meshSimplify.targetRatio.title"></ui-label>
  69. <ui-slider slot="content" class="meshSimplify-targetRatio-slider" min="0" max="1" step="0.01"></ui-slider>
  70. </ui-prop>
  71. <ui-prop>
  72. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshSimplify.autoErrorRate.name" tooltip="i18n:ENGINE.assets.fbx.meshSimplify.autoErrorRate.title"></ui-label>
  73. <ui-checkbox slot="content" class="meshSimplify-autoErrorRate-checkbox"></ui-checkbox>
  74. </ui-prop>
  75. <ui-prop>
  76. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshSimplify.errorRate.name" tooltip="i18n:ENGINE.assets.fbx.meshSimplify.errorRate.title"></ui-label>
  77. <ui-slider slot="content" class="meshSimplify-errorRate-slider" min="0" max="1" step="0.01"></ui-slider>
  78. </ui-prop>
  79. <ui-prop>
  80. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshSimplify.lockBoundary.name" tooltip="i18n:ENGINE.assets.fbx.meshSimplify.lockBoundary.title"></ui-label>
  81. <ui-checkbox slot="content" class="meshSimplify-lockBoundary-checkbox"></ui-checkbox>
  82. </ui-prop>
  83. </div>
  84. </ui-section>
  85. <ui-section class="meshCluster config" cache-expand="fbx-model-mesh-cluster">
  86. <div slot="header" class="meshCluster-header">
  87. <ui-checkbox slot="content" class="meshCluster-checkbox"></ui-checkbox>
  88. <ui-label value="i18n:ENGINE.assets.fbx.meshCluster.name" tooltip="i18n:ENGINE.assets.fbx.meshCluster.title"></ui-label>
  89. </div>
  90. <div class="meshCluster-options">
  91. <ui-prop>
  92. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshCluster.generateBounding.name" tooltip="i18n:ENGINE.assets.fbx.meshCluster.generateBounding.title"></ui-label>
  93. <ui-checkbox slot="content" class="meshCluster-generateBounding-checkbox"></ui-checkbox>
  94. </ui-prop>
  95. </div>
  96. </ui-section>
  97. <ui-section class="meshCompress config" cache-expand="fbx-model-mesh-compressor">
  98. <div slot="header" class="meshCompress-header">
  99. <ui-checkbox slot="content" class="meshCompress-checkbox"></ui-checkbox>
  100. <ui-label value="i18n:ENGINE.assets.fbx.meshCompress.name" tooltip="i18n:ENGINE.assets.fbx.meshCompress.title"></ui-label>
  101. </div>
  102. <div class="meshCompress-options">
  103. <ui-prop>
  104. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshCompress.encode.name" tooltip="i18n:ENGINE.assets.fbx.meshCompress.encode.title"></ui-label>
  105. <ui-checkbox slot="content" class="meshCompress-encode-checkbox"></ui-checkbox>
  106. </ui-prop>
  107. <ui-prop>
  108. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshCompress.compress.name" tooltip="i18n:ENGINE.assets.fbx.meshCompress.compress.title"></ui-label>
  109. <ui-checkbox slot="content" class="meshCompress-compress-checkbox"></ui-checkbox>
  110. </ui-prop>
  111. <ui-prop>
  112. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.meshCompress.quantize.name" tooltip="i18n:ENGINE.assets.fbx.meshCompress.quantize.title"></ui-label>
  113. <ui-checkbox slot="content" class="meshCompress-quantize-checkbox"></ui-checkbox>
  114. </ui-prop>
  115. </div>
  116. </ui-section>
  117. <ui-section class="lods config" cache-expand="fbx-mode-lods">
  118. <div slot="header" class="lods-header">
  119. <ui-checkbox class="lods-checkbox"></ui-checkbox>
  120. <ui-label value="LODS" tooltip="To import LODs, please make sure the LOD mesh names are ending with _LOD#"></ui-label>
  121. </div>
  122. <div class="lod-items"></div>
  123. <div class="not-lod-mesh-label" hidden></div>
  124. <div class="no-lod-label" hidden>There is no LOD group found in the source file. If you want to generate LODs for this model, please use above settings.</div>
  125. <div class="load-mask">
  126. <ui-loading></ui-loading>
  127. </div>
  128. </ui-section>
  129. </div>
  130. `;
  131. exports.style = /* css */`
  132. ui-prop { margin-right: 4px; }
  133. ui-section.config { margin-right: 0; }
  134. .warn-words {
  135. color: var(--color-warn-fill);
  136. }
  137. .lods {
  138. margin-top: 0;
  139. }
  140. .lod-item .lod-item-header {
  141. flex: 1;
  142. display: flex;
  143. align-items: center;
  144. }
  145. .lod-item .lod-item-header .left {
  146. flex: 1;
  147. }
  148. .lod-item .lod-item-header .middle,
  149. .lod-item .lod-item-header .right {
  150. display: flex;
  151. flex: 2;
  152. text-align: right;
  153. }
  154. .lod-item .lod-item-header .middle {
  155. margin: 0 4px;
  156. }
  157. .lod-item .lod-item-header .middle[hidden] {
  158. display: none;
  159. }
  160. .lod-item .lod-item-header .middle > ui-num-input {
  161. width: 48px;
  162. margin-left: 4px;
  163. }
  164. .lod-item .lod-item-header .middle .face-count,
  165. .lod-item .lod-item-header .right .triangles {
  166. flex: 1;
  167. white-space: nowrap;
  168. overflow: hidden;
  169. text-overflow: ellipsis;
  170. width: 0;
  171. }
  172. .lod-item .lod-item-header .right .operator {
  173. display: none;
  174. margin-left: 8px;
  175. background: var(--color-default-fill);
  176. border-color: var(--color-default-border);
  177. border-radius: calc(var(--size-normal-radius) * 1px);
  178. }
  179. .lod-item .lod-item-header .right .operator > ui-icon {
  180. padding: 0 5px;
  181. transition: color 0.15s;
  182. color: var(--color-default-contrast-emphasis);
  183. position: relative;
  184. }
  185. .lod-item .lod-item-header .right .operator > ui-icon + ui-icon {
  186. margin-left: 1px;
  187. }
  188. .lod-item .lod-item-header .right .operator > ui-icon + ui-icon::after {
  189. content: '';
  190. display: block;
  191. width: 1px;
  192. height: 12px;
  193. position: absolute;
  194. top: 6px;
  195. left: -1px;
  196. background: var(--color-normal-fill-normal);
  197. }
  198. .lod-item .lod-item-header:hover .right .operator:not([hidden]) {
  199. display: flex;
  200. }
  201. .lod-item .lod-item-header .right .operator > ui-icon:hover {
  202. background: var(--color-hover-fill-weaker);
  203. color: var(--color-focus-contrast-emphasis);
  204. }
  205. .lods .not-lod-mesh-label,
  206. .lods .no-lod-label {
  207. color: var(--color-default-fill-weakest)
  208. }
  209. .lods .not-lod-mesh-label[hidden],
  210. .lods .no-lod-label[hidden] {
  211. display: none;
  212. }
  213. .lods .load-mask {
  214. position: absolute;
  215. width: 100%;
  216. height: 100%;
  217. top: 0;
  218. left: 0;
  219. text-align: center;
  220. background-color: #1b1d1db0;
  221. z-index: 10;
  222. display: none;
  223. }
  224. .lods .load-mask > ui-loading {
  225. position: absolute;
  226. top: 50%;
  227. left: 50%;
  228. transform: translate(-50%, -50%);
  229. }
  230. `;
  231. exports.$ = {
  232. container: '.container',
  233. normalsSelect: '.normals-select',
  234. tangentsSelect: '.tangents-select',
  235. morphNormalsSelect: '.morphNormals-select',
  236. skipValidationCheckbox: '.skipValidation-checkbox',
  237. disableMeshSplitCheckbox: '.disableMeshSplit-checkbox',
  238. allowMeshDataAccessCheckbox: '.allowMeshDataAccess-checkbox',
  239. addVertexColorCheckbox: '.addVertexColor-checkbox',
  240. promoteSingleRootNodeCheckbox: '.promoteSingleRootNode-checkbox',
  241. generateLightmapUVNodeCheckbox: '.generateLightmapUVNode-checkbox',
  242. // meshOptimizeOptions
  243. meshOptimizeCheckbox: '.meshOptimize-checkbox',
  244. meshOptimizeVertexCacheCheckbox: '.meshOptimize-vertexCache-checkbox',
  245. meshOptimizeVertexFetchCheckbox: '.meshOptimize-vertexFetch-checkbox',
  246. meshOptimizeOverdrawCheckbox: '.meshOptimize-overdraw-checkbox',
  247. // simplifyOptions
  248. meshSimplifyCheckbox: '.meshSimplify-checkbox',
  249. meshSimplifyTargetRatioSlider: '.meshSimplify-targetRatio-slider',
  250. meshSimplifyAutoErrorRateCheckbox: '.meshSimplify-autoErrorRate-checkbox',
  251. meshSimplifyErrorRateSlider: '.meshSimplify-errorRate-slider',
  252. meshSimplifyLockBoundaryCheckbox: '.meshSimplify-lockBoundary-checkbox',
  253. meshClusterCheckbox: '.meshCluster-checkbox',
  254. meshClusterGenerateBoundingCheckbox: '.meshCluster-generateBounding-checkbox',
  255. meshCompressCheckbox: '.meshCompress-checkbox',
  256. meshCompressEncodeCheckbox: '.meshCompress-encode-checkbox',
  257. meshCompressCompressCheckbox: '.meshCompress-compress-checkbox',
  258. meshCompressQuantizeCheckbox: '.meshCompress-quantize-checkbox',
  259. // lods
  260. lodsCheckbox: '.lods-checkbox',
  261. lodItems: '.lod-items',
  262. noLodLabel: '.no-lod-label',
  263. notLodMeshLabel: '.not-lod-mesh-label',
  264. loadMask: '.load-mask',
  265. };
  266. const Elements = {
  267. normals: {
  268. ready() {
  269. const panel = this;
  270. panel.$.normalsSelect.addEventListener('change', panel.setProp.bind(panel, 'normals', 'number'));
  271. panel.$.normalsSelect.addEventListener('confirm', () => {
  272. panel.dispatch('snapshot');
  273. });
  274. },
  275. update() {
  276. const panel = this;
  277. let optionsHtml = '';
  278. const types = ['optional', 'exclude', 'require', 'recalculate'];
  279. types.forEach((type, index) => {
  280. optionsHtml += `<option value="${index}"
  281. title="${panel.t(`GlTFUserData.normals.${type}.title`)}"
  282. >${panel.t(`GlTFUserData.normals.${type}.name`)}</option>`;
  283. });
  284. panel.$.normalsSelect.innerHTML = optionsHtml;
  285. panel.$.normalsSelect.value = getPropValue.call(panel, panel.meta.userData.normals, 2);
  286. updateElementInvalid.call(panel, panel.$.normalsSelect, 'normals');
  287. updateElementReadonly.call(panel, panel.$.normalsSelect);
  288. },
  289. },
  290. tangents: {
  291. ready() {
  292. const panel = this;
  293. panel.$.tangentsSelect.addEventListener('change', panel.setProp.bind(panel, 'tangents', 'number'));
  294. panel.$.tangentsSelect.addEventListener('confirm', () => {
  295. panel.dispatch('snapshot');
  296. });
  297. },
  298. update() {
  299. const panel = this;
  300. let optionsHtml = '';
  301. const types = ['optional', 'exclude', 'require', 'recalculate'];
  302. types.forEach((type, index) => {
  303. optionsHtml += `<option value="${index}"
  304. title="${panel.t(`GlTFUserData.tangents.${type}.title`)}"
  305. >${panel.t(`GlTFUserData.tangents.${type}.name`)}</option>`;
  306. });
  307. panel.$.tangentsSelect.innerHTML = optionsHtml;
  308. panel.$.tangentsSelect.value = getPropValue.call(panel, panel.meta.userData.tangents, 2);
  309. updateElementInvalid.call(panel, panel.$.tangentsSelect, 'tangents');
  310. updateElementReadonly.call(panel, panel.$.tangentsSelect);
  311. },
  312. },
  313. morphNormals: {
  314. ready() {
  315. const panel = this;
  316. panel.$.morphNormalsSelect.addEventListener('change', panel.setProp.bind(panel, 'morphNormals', 'number'));
  317. panel.$.morphNormalsSelect.addEventListener('confirm', () => {
  318. panel.dispatch('snapshot');
  319. });
  320. },
  321. update() {
  322. const panel = this;
  323. let optionsHtml = '';
  324. const types = ['optional', 'exclude'];
  325. types.forEach((type, index) => {
  326. optionsHtml += `<option value="${index}"
  327. title="${panel.t(`GlTFUserData.morphNormals.${type}.title`)}"
  328. >${panel.t(`GlTFUserData.morphNormals.${type}.name`)}</option>`;
  329. });
  330. panel.$.morphNormalsSelect.innerHTML = optionsHtml;
  331. panel.$.morphNormalsSelect.value = getPropValue.call(panel, panel.meta.userData.morphNormals, 1);
  332. updateElementInvalid.call(panel, panel.$.morphNormalsSelect, 'morphNormals');
  333. updateElementReadonly.call(panel, panel.$.morphNormalsSelect);
  334. },
  335. },
  336. skipValidation: {
  337. ready() {
  338. const panel = this;
  339. panel.$.skipValidationCheckbox.addEventListener('change', panel.setProp.bind(panel, 'skipValidation', 'boolean'));
  340. panel.$.skipValidationCheckbox.addEventListener('confirm', () => {
  341. panel.dispatch('snapshot');
  342. });
  343. },
  344. update() {
  345. const panel = this;
  346. panel.$.skipValidationCheckbox.value = getPropValue.call(panel, panel.meta.userData.skipValidation, true);
  347. updateElementInvalid.call(panel, panel.$.skipValidationCheckbox, 'skipValidation');
  348. updateElementReadonly.call(panel, panel.$.skipValidationCheckbox);
  349. },
  350. },
  351. disableMeshSplit: {
  352. ready() {
  353. const panel = this;
  354. panel.$.disableMeshSplitCheckbox.addEventListener('change', panel.setProp.bind(panel, 'disableMeshSplit', 'boolean'));
  355. panel.$.disableMeshSplitCheckbox.addEventListener('confirm', () => {
  356. panel.dispatch('snapshot');
  357. });
  358. },
  359. update() {
  360. const panel = this;
  361. panel.$.disableMeshSplitCheckbox.value = getPropValue.call(panel, panel.meta.userData.disableMeshSplit, true);
  362. updateElementInvalid.call(panel, panel.$.disableMeshSplitCheckbox, 'disableMeshSplit');
  363. updateElementReadonly.call(panel, panel.$.disableMeshSplitCheckbox);
  364. },
  365. },
  366. allowMeshDataAccess: {
  367. ready() {
  368. const panel = this;
  369. panel.$.allowMeshDataAccessCheckbox.addEventListener('change', panel.setProp.bind(panel, 'allowMeshDataAccess', 'boolean'));
  370. panel.$.allowMeshDataAccessCheckbox.addEventListener('confirm', () => {
  371. panel.dispatch('snapshot');
  372. });
  373. },
  374. update() {
  375. const panel = this;
  376. panel.$.allowMeshDataAccessCheckbox.value = getPropValue.call(panel, panel.meta.userData.allowMeshDataAccess, true);
  377. updateElementInvalid.call(panel, panel.$.allowMeshDataAccessCheckbox, 'allowMeshDataAccess');
  378. updateElementReadonly.call(panel, panel.$.allowMeshDataAccessCheckbox);
  379. },
  380. },
  381. addVertexColorCheckbox: {
  382. ready() {
  383. const panel = this;
  384. panel.$.addVertexColorCheckbox.addEventListener('change', panel.setProp.bind(panel, 'addVertexColor', 'boolean'));
  385. panel.$.addVertexColorCheckbox.addEventListener('confirm', () => {
  386. panel.dispatch('snapshot');
  387. });
  388. },
  389. update() {
  390. const panel = this;
  391. let defaultValue = false;
  392. if (panel.meta.userData) {
  393. defaultValue = getPropValue.call(panel, panel.meta.userData.addVertexColor, defaultValue);
  394. }
  395. panel.$.addVertexColorCheckbox.value = defaultValue;
  396. updateElementInvalid.call(panel, panel.$.addVertexColorCheckbox, 'addVertexColor');
  397. updateElementReadonly.call(panel, panel.$.addVertexColorCheckbox);
  398. },
  399. },
  400. // move this from ./fbx.js in v3.6.0
  401. promoteSingleRootNode: {
  402. ready() {
  403. const panel = this;
  404. panel.$.promoteSingleRootNodeCheckbox.addEventListener('change', panel.setProp.bind(panel, 'promoteSingleRootNode', 'boolean'));
  405. panel.$.promoteSingleRootNodeCheckbox.addEventListener('confirm', () => {
  406. panel.dispatch('snapshot');
  407. });
  408. },
  409. update() {
  410. const panel = this;
  411. let defaultValue = false;
  412. if (panel.meta.userData) {
  413. defaultValue = getPropValue.call(panel, panel.meta.userData.promoteSingleRootNode, defaultValue);
  414. }
  415. panel.$.promoteSingleRootNodeCheckbox.value = defaultValue;
  416. updateElementInvalid.call(panel, panel.$.promoteSingleRootNodeCheckbox, 'promoteSingleRootNode');
  417. updateElementReadonly.call(panel, panel.$.promoteSingleRootNodeCheckbox);
  418. },
  419. },
  420. // move this from ./fbx.js in v3.6.0
  421. generateLightmapUVNode: {
  422. ready() {
  423. const panel = this;
  424. panel.$.generateLightmapUVNodeCheckbox.addEventListener('change', panel.setProp.bind(panel, 'generateLightmapUVNode', 'boolean'));
  425. panel.$.generateLightmapUVNodeCheckbox.addEventListener('confirm', () => {
  426. panel.dispatch('snapshot');
  427. });
  428. },
  429. update() {
  430. const panel = this;
  431. let defaultValue = false;
  432. if (panel.meta.userData) {
  433. defaultValue = getPropValue.call(panel, panel.meta.userData.generateLightmapUVNode, defaultValue);
  434. }
  435. panel.$.generateLightmapUVNodeCheckbox.value = defaultValue;
  436. updateElementInvalid.call(panel, panel.$.generateLightmapUVNodeCheckbox, 'generateLightmapUVNode');
  437. updateElementReadonly.call(panel, panel.$.generateLightmapUVNodeCheckbox);
  438. },
  439. },
  440. // meshOptimize start
  441. meshOptimize: {
  442. ready() {
  443. const panel = this;
  444. panel.$.meshOptimizeCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshOptimize.enable', 'boolean'));
  445. panel.$.meshOptimizeCheckbox.addEventListener('confirm', () => {
  446. panel.dispatch('snapshot');
  447. });
  448. },
  449. update() {
  450. const panel = this;
  451. panel.$.meshOptimizeCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshOptimize, false, 'enable');
  452. updateElementInvalid.call(panel, panel.$.meshOptimizeCheckbox, 'meshOptimize.enable');
  453. updateElementReadonly.call(panel, panel.$.meshOptimizeCheckbox);
  454. },
  455. },
  456. meshOptimizeVertexCache: {
  457. ready() {
  458. const panel = this;
  459. panel.$.meshOptimizeVertexCacheCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshOptimize.vertexCache', 'boolean'));
  460. panel.$.meshOptimizeVertexCacheCheckbox.addEventListener('confirm', () => {
  461. panel.dispatch('snapshot');
  462. });
  463. },
  464. update() {
  465. const panel = this;
  466. panel.$.meshOptimizeVertexCacheCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshOptimize, false, 'vertexCache');
  467. updateElementInvalid.call(panel, panel.$.meshOptimizeVertexCacheCheckbox, 'meshOptimize.vertexCache');
  468. updateElementReadonly.call(panel, panel.$.meshOptimizeVertexCacheCheckbox);
  469. },
  470. },
  471. meshOptimizeVertexFetch: {
  472. ready() {
  473. const panel = this;
  474. panel.$.meshOptimizeVertexFetchCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshOptimize.vertexFetch', 'boolean'));
  475. panel.$.meshOptimizeVertexFetchCheckbox.addEventListener('confirm', () => {
  476. panel.dispatch('snapshot');
  477. });
  478. },
  479. update() {
  480. const panel = this;
  481. panel.$.meshOptimizeVertexFetchCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshOptimize, false, 'vertexFetch');
  482. updateElementInvalid.call(panel, panel.$.meshOptimizeVertexFetchCheckbox, 'meshOptimize.vertexFetch');
  483. updateElementReadonly.call(panel, panel.$.meshOptimizeVertexFetchCheckbox);
  484. },
  485. },
  486. meshOptimizeOverdraw: {
  487. ready() {
  488. const panel = this;
  489. panel.$.meshOptimizeOverdrawCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshOptimize.overdraw', 'boolean'));
  490. panel.$.meshOptimizeOverdrawCheckbox.addEventListener('confirm', () => {
  491. panel.dispatch('snapshot');
  492. });
  493. },
  494. update() {
  495. const panel = this;
  496. panel.$.meshOptimizeOverdrawCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshOptimize, false, 'overdraw');
  497. updateElementInvalid.call(panel, panel.$.meshOptimizeOverdrawCheckbox, 'meshOptimize.overdraw');
  498. updateElementReadonly.call(panel, panel.$.meshOptimizeOverdrawCheckbox);
  499. },
  500. },
  501. // meshOptimize end
  502. // meshSimplify start
  503. meshSimplify: {
  504. ready() {
  505. const panel = this;
  506. panel.$.meshSimplifyCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshSimplify.enable', 'boolean'));
  507. panel.$.meshSimplifyCheckbox.addEventListener('confirm', () => {
  508. panel.dispatch('snapshot');
  509. });
  510. },
  511. update() {
  512. const panel = this;
  513. panel.$.meshSimplifyCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshSimplify, false, 'enable');
  514. updateElementInvalid.call(panel, panel.$.meshSimplifyCheckbox, 'meshSimplify.enable');
  515. updateElementReadonly.call(panel, panel.$.meshSimplifyCheckbox);
  516. },
  517. },
  518. meshSimplifyTargetRatio: {
  519. ready() {
  520. const panel = this;
  521. panel.$.meshSimplifyTargetRatioSlider.addEventListener('change', panel.setProp.bind(panel, 'meshSimplify.targetRatio', 'number'));
  522. panel.$.meshSimplifyTargetRatioSlider.addEventListener('confirm', () => {
  523. panel.dispatch('snapshot');
  524. });
  525. },
  526. update() {
  527. const panel = this;
  528. panel.$.meshSimplifyTargetRatioSlider.value = getPropValue.call(panel, panel.meta.userData.meshSimplify, 1, 'targetRatio');
  529. updateElementInvalid.call(panel, panel.$.meshSimplifyTargetRatioSlider, 'meshSimplify.targetRatio');
  530. updateElementReadonly.call(panel, panel.$.meshSimplifyTargetRatioSlider);
  531. },
  532. },
  533. meshSimplifyAutoErrorRateCheckbox: {
  534. ready() {
  535. const panel = this;
  536. panel.$.meshSimplifyAutoErrorRateCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshSimplify.autoErrorRate', 'boolean'));
  537. panel.$.meshSimplifyAutoErrorRateCheckbox.addEventListener('confirm', () => {
  538. panel.dispatch('snapshot');
  539. });
  540. },
  541. update() {
  542. const panel = this;
  543. panel.$.meshSimplifyAutoErrorRateCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshSimplify, false, 'autoErrorRate');
  544. updateElementInvalid.call(panel, panel.$.meshSimplifyAutoErrorRateCheckbox, 'meshSimplify.autoErrorRate');
  545. updateElementReadonly.call(panel, panel.$.meshSimplifyAutoErrorRateCheckbox);
  546. },
  547. },
  548. meshSimplifyErrorRate: {
  549. ready() {
  550. const panel = this;
  551. panel.$.meshSimplifyErrorRateSlider.addEventListener('change', panel.setProp.bind(panel, 'meshSimplify.errorRate', 'number'));
  552. panel.$.meshSimplifyErrorRateSlider.addEventListener('confirm', () => {
  553. panel.dispatch('snapshot');
  554. });
  555. },
  556. update() {
  557. const panel = this;
  558. panel.$.meshSimplifyErrorRateSlider.value = getPropValue.call(panel, panel.meta.userData.meshSimplify, 1, 'errorRate');
  559. updateElementInvalid.call(panel, panel.$.meshSimplifyErrorRateSlider, 'meshSimplify.errorRate');
  560. updateElementReadonly.call(panel, panel.$.meshSimplifyErrorRateSlider);
  561. },
  562. },
  563. meshSimplifyLockBoundaryCheckbox: {
  564. ready() {
  565. const panel = this;
  566. panel.$.meshSimplifyLockBoundaryCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshSimplify.lockBoundary', 'boolean'));
  567. panel.$.meshSimplifyLockBoundaryCheckbox.addEventListener('confirm', () => {
  568. panel.dispatch('snapshot');
  569. });
  570. },
  571. update() {
  572. const panel = this;
  573. panel.$.meshSimplifyLockBoundaryCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshSimplify, false, 'lockBoundary');
  574. updateElementInvalid.call(panel, panel.$.meshSimplifyLockBoundaryCheckbox, 'meshSimplify.lockBoundary');
  575. updateElementReadonly.call(panel, panel.$.meshSimplifyLockBoundaryCheckbox);
  576. },
  577. },
  578. meshClusterCheckbox: {
  579. ready() {
  580. const panel = this;
  581. panel.$.meshClusterCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshCluster.enable', 'boolean'));
  582. panel.$.meshClusterCheckbox.addEventListener('confirm', () => {
  583. panel.dispatch('snapshot');
  584. });
  585. },
  586. update() {
  587. const panel = this;
  588. panel.$.meshClusterCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshCluster, false, 'enable');
  589. updateElementInvalid.call(panel, panel.$.meshClusterCheckbox, 'meshCluster.enable');
  590. updateElementReadonly.call(panel, panel.$.meshClusterCheckbox);
  591. },
  592. },
  593. meshClusterGenerateBoundingCheckbox: {
  594. ready() {
  595. const panel = this;
  596. panel.$.meshClusterGenerateBoundingCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshCluster.generateBounding', 'boolean'));
  597. panel.$.meshClusterGenerateBoundingCheckbox.addEventListener('confirm', () => {
  598. panel.dispatch('snapshot');
  599. });
  600. },
  601. update() {
  602. const panel = this;
  603. panel.$.meshClusterGenerateBoundingCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshCluster, false, 'generateBounding');
  604. updateElementInvalid.call(panel, panel.$.meshClusterGenerateBoundingCheckbox, 'meshCluster.generateBounding');
  605. updateElementReadonly.call(panel, panel.$.meshClusterGenerateBoundingCheckbox);
  606. },
  607. },
  608. // meshSimplify end
  609. // meshCompress start
  610. meshCompress: {
  611. ready() {
  612. const panel = this;
  613. panel.$.meshCompressCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshCompress.enable', 'boolean'));
  614. panel.$.meshCompressCheckbox.addEventListener('confirm', () => {
  615. panel.dispatch('snapshot');
  616. });
  617. },
  618. update() {
  619. const panel = this;
  620. panel.$.meshCompressCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshCompress, false, 'enable');
  621. updateElementInvalid.call(panel, panel.$.meshCompressCheckbox, 'meshCompress.enable');
  622. updateElementReadonly.call(panel, panel.$.meshCompressCheckbox);
  623. },
  624. },
  625. meshCompressEncode: {
  626. ready() {
  627. const panel = this;
  628. panel.$.meshCompressEncodeCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshCompress.encode', 'boolean'));
  629. panel.$.meshCompressEncodeCheckbox.addEventListener('confirm', () => {
  630. panel.dispatch('snapshot');
  631. });
  632. },
  633. update() {
  634. const panel = this;
  635. panel.$.meshCompressEncodeCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshCompress, false, 'encode');
  636. updateElementInvalid.call(panel, panel.$.meshCompressEncodeCheckbox, 'meshCompress.encode');
  637. updateElementReadonly.call(panel, panel.$.meshCompressEncodeCheckbox);
  638. },
  639. },
  640. meshCompressCompress: {
  641. ready() {
  642. const panel = this;
  643. panel.$.meshCompressCompressCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshCompress.compress', 'boolean'));
  644. panel.$.meshCompressCompressCheckbox.addEventListener('confirm', () => {
  645. panel.dispatch('snapshot');
  646. });
  647. },
  648. update() {
  649. const panel = this;
  650. panel.$.meshCompressCompressCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshCompress, false, 'compress');
  651. updateElementInvalid.call(panel, panel.$.meshCompressCompressCheckbox, 'meshCompress.compress');
  652. updateElementReadonly.call(panel, panel.$.meshCompressCompressCheckbox);
  653. },
  654. },
  655. meshCompressQuantize: {
  656. ready() {
  657. const panel = this;
  658. panel.$.meshCompressQuantizeCheckbox.addEventListener('change', panel.setProp.bind(panel, 'meshCompress.quantize', 'boolean'));
  659. panel.$.meshCompressQuantizeCheckbox.addEventListener('confirm', () => {
  660. panel.dispatch('snapshot');
  661. });
  662. },
  663. update() {
  664. const panel = this;
  665. panel.$.meshCompressQuantizeCheckbox.value = getPropValue.call(panel, panel.meta.userData.meshCompress, false, 'quantize');
  666. updateElementInvalid.call(panel, panel.$.meshCompressQuantizeCheckbox, 'meshCompress.quantize');
  667. updateElementReadonly.call(panel, panel.$.meshCompressQuantizeCheckbox);
  668. },
  669. },
  670. // meshCompress end
  671. // lods start
  672. lods: {
  673. ready() {
  674. const panel = this;
  675. // Listening lod on and off
  676. panel.$.lodsCheckbox.addEventListener('change', panel.setProp.bind(panel, 'lods.enable', 'boolean'));
  677. panel.$.lodsCheckbox.addEventListener('confirm', () => {
  678. panel.dispatch('snapshot');
  679. });
  680. // listening for screenRatio and faceCount changes
  681. panel.$.lodItems.addEventListener('change', (event) => {
  682. const path = event.target.getAttribute('path');
  683. const index = Number(event.target.getAttribute('key'));
  684. const value = Editor.Utils.Math.divide(event.target.value, 100);
  685. switch (path) {
  686. case 'screenRatio':
  687. // TODO: Min/max of the screenRatio for each level of LOD
  688. panel.metaList.forEach((meta) => {
  689. meta.userData.lods && (meta.userData.lods.options[index].screenRatio = value);
  690. });
  691. panel.dispatch('change');
  692. break;
  693. case 'faceCount':
  694. // TODO: Min/max of the faceCount for each level of LOD
  695. panel.metaList.forEach((meta) => {
  696. meta.userData.lods && (meta.userData.lods.options[index].faceCount = value);
  697. });
  698. panel.dispatch('change');
  699. break;
  700. }
  701. });
  702. panel.$.lodItems.addEventListener('confirm', () => {
  703. panel.dispatch('snapshot');
  704. });
  705. },
  706. update() {
  707. const panel = this;
  708. panel.$.lodsCheckbox.value = getPropValue.call(panel, panel.meta.userData.lods, false, 'enable');
  709. const lodOptions = panel.meta.userData.lods && panel.meta.userData.lods.options || [];
  710. const hasBuiltinLOD = panel.meta.userData.lods && panel.meta.userData.lods.hasBuiltinLOD || false;
  711. panel.$.lodItems.innerHTML = getLodItemHTML(lodOptions, panel.LODTriangleCounts, hasBuiltinLOD);
  712. hasBuiltinLOD ? panel.$.noLodLabel.setAttribute('hidden', '') : panel.$.noLodLabel.removeAttribute('hidden');
  713. if (panel.notLODTriangleCounts && panel.notLODTriangleCounts.length > 0) {
  714. const totalNotLODTriangleCounts = panel.notLODTriangleCounts.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
  715. panel.$.notLodMeshLabel.innerHTML = `There are ${panel.notLODTriangleCounts.length} non-LOD mesh(es) in the FBX, and the total triangles count is ${totalNotLODTriangleCounts}.`;
  716. panel.$.notLodMeshLabel.removeAttribute('hidden');
  717. } else {
  718. panel.$.notLodMeshLabel.setAttribute('hidden', '');
  719. }
  720. if (panel.$.loadMask.style.display === 'block' && this.asset.imported) {
  721. panel.$.loadMask.style.display = 'none';
  722. }
  723. // Listening to the addition and removal of the lod hierarchy
  724. const uiIcons = panel.$.lodItems.querySelectorAll('ui-icon[value="add"], .lod-items ui-icon[value="reduce"]');
  725. uiIcons.forEach((uiIcon) => {
  726. uiIcon.addEventListener('click', (event) => {
  727. event.stopPropagation();
  728. const path = event.target.getAttribute('path');
  729. const index = Number(event.target.getAttribute('key'));
  730. const lods = panel.meta.userData.lods;
  731. if (!lods) {
  732. return;
  733. }
  734. if (path === 'insertLod') {
  735. if (Object.keys(lods.options).length >= 8) {
  736. console.warn('Maximum 8 LOD, Can\'t add more LOD');
  737. return;
  738. }
  739. const preScreenRatio = lods.options[index].screenRatio;
  740. const nextScreenRatio = lods.options[index + 1] ? lods.options[index + 1].screenRatio : 0;
  741. const preFaceCount = lods.options[index].faceCount;
  742. const nextFaceCount = lods.options[index + 1] ? lods.options[index + 1].faceCount : 0;
  743. const option = {
  744. screenRatio: (preScreenRatio + nextScreenRatio) / 2,
  745. faceCount: (preFaceCount + nextFaceCount) / 2,
  746. };
  747. // Insert the specified lod level
  748. for (let keyIndex = Object.keys(lods.options).length - 1; keyIndex > index; keyIndex--) {
  749. lods.options[keyIndex + 1] = lods.options[keyIndex];
  750. panel.LODTriangleCounts[keyIndex + 1] = panel.LODTriangleCounts[keyIndex];
  751. }
  752. lods.options[index + 1] = option;
  753. panel.LODTriangleCounts[index + 1] = 0;
  754. // update panel
  755. Elements.lods.update.call(panel);
  756. panel.dispatch('change');
  757. panel.dispatch('snapshot');
  758. } else if (path === 'deleteLod') {
  759. if (Object.keys(lods.options).length <= 1) {
  760. console.warn('At least one LOD, Can\'t delete any more');
  761. return;
  762. }
  763. // Delete the specified lod level
  764. for (let key = index; key < Object.keys(lods.options).length; key++) {
  765. lods.options[key] = lods.options[key + 1];
  766. panel.LODTriangleCounts[key] = panel.LODTriangleCounts[key + 1];
  767. }
  768. lods.options.pop();
  769. panel.LODTriangleCounts.pop();
  770. // update panel
  771. Elements.lods.update.call(panel);
  772. panel.dispatch('change');
  773. panel.dispatch('snapshot');
  774. }
  775. });
  776. });
  777. updateElementInvalid.call(panel, panel.$.lodsCheckbox, 'lods.enable');
  778. updateElementReadonly.call(panel, panel.$.lodsCheckbox, hasBuiltinLOD);
  779. },
  780. },
  781. // lods end
  782. // when lang change
  783. i18n: {
  784. ready() {
  785. const panel = this;
  786. Elements.i18n.changeBind = Elements.i18n.change.bind(panel);
  787. Editor.Message.addBroadcastListener('i18n:change', Elements.i18n.changeBind);
  788. },
  789. close() {
  790. Editor.Message.removeBroadcastListener('i18n:change', Elements.i18n.changeBind);
  791. Elements.i18n.changeBind = undefined;
  792. },
  793. change() {
  794. const panel = this;
  795. Elements.normals.update.call(panel);
  796. Elements.tangents.update.call(panel);
  797. Elements.morphNormals.update.call(panel);
  798. },
  799. },
  800. };
  801. exports.methods = {
  802. t(key) {
  803. return Editor.I18n.t(`ENGINE.assets.fbx.${key}`);
  804. },
  805. setProp(prop, type, event) {
  806. setPropValue.call(this, prop, type, event);
  807. this.dispatch('change');
  808. this.dispatch('track', { tab: 'model', prop, value: event.target.value });
  809. },
  810. apply() {
  811. this.$.loadMask.style.display = 'block';
  812. },
  813. };
  814. exports.ready = function() {
  815. this.applyFun = this.apply.bind(this);
  816. Editor.Message.addBroadcastListener('fbx-inspector:apply', this.applyFun);
  817. for (const prop in Elements) {
  818. const element = Elements[prop];
  819. if (element.ready) {
  820. element.ready.call(this);
  821. }
  822. }
  823. };
  824. exports.update = function(assetList, metaList) {
  825. this.assetList = assetList;
  826. this.metaList = metaList;
  827. this.asset = assetList[0];
  828. this.meta = metaList[0];
  829. const { LODTriangleCounts, notLODTriangleCounts } = handleLODTriangleCounts(this.meta);
  830. this.LODTriangleCounts = LODTriangleCounts;
  831. this.notLODTriangleCounts = notLODTriangleCounts;
  832. for (const prop in Elements) {
  833. const element = Elements[prop];
  834. if (element.update) {
  835. element.update.call(this);
  836. }
  837. }
  838. };
  839. exports.close = function() {
  840. Editor.Message.removeBroadcastListener('fbx-inspector:apply', this.applyFun);
  841. for (const prop in Elements) {
  842. const element = Elements[prop];
  843. if (element.close) {
  844. element.close.call(this);
  845. }
  846. }
  847. };
  848. function handleLODTriangleCounts(meta) {
  849. if (!meta.userData.lods) {
  850. return [];
  851. }
  852. let LODTriangleCounts = new Array(meta.userData.lods.options.length).fill(0);
  853. let notLODTriangleCounts = new Array();
  854. for (const key in meta.subMetas) {
  855. const subMeta = meta.subMetas[key];
  856. if (subMeta.importer === 'gltf-mesh') {
  857. const { lodOptions, triangleCount, lodLevel } = subMeta.userData;
  858. const index = !meta.userData.lods.hasBuiltinLOD ? (lodOptions ? lodLevel : 0) : lodLevel;
  859. // When an FBX comes with LOD, there may be non-LOD meshes, and the count of these meshes should be calculated separately.
  860. if (index === undefined) {
  861. notLODTriangleCounts.push(triangleCount || 0);
  862. continue;
  863. }
  864. LODTriangleCounts[index] = (LODTriangleCounts[index] || 0) + (triangleCount || 0);
  865. }
  866. }
  867. return {
  868. LODTriangleCounts,
  869. notLODTriangleCounts,
  870. };
  871. }
  872. function getLodItemHTML(lodOptions, LODTriangleCounts, hasBuiltinLOD = false) {
  873. let lodItemsStr = '';
  874. for (const index in lodOptions) {
  875. const lodItem = lodOptions[index];
  876. lodItemsStr += `
  877. <div class="lod-item">
  878. <ui-section cache-expand="fbx-mode-lod-item-${index}">
  879. <header slot="header" class="lod-item-header">
  880. <div class="left">
  881. <span>LOD ${index}</span>
  882. </div>
  883. <div class="middle" ${index == 0 || lodOptions[0].faceCount == 0 ? 'hidden' : ''}>
  884. <span class="face-count">Face count(%)</span>
  885. <ui-num-input path="faceCount" min="0" max="100" key="${index}"
  886. value="${Editor.Utils.Math.multi(lodItem.faceCount, 100)}"
  887. ${hasBuiltinLOD ? 'disabled' : ''}>
  888. </ui-num-input>
  889. </div>
  890. <div class="right">
  891. <div class="triangles">
  892. <span> ${LODTriangleCounts[index] || 0} Triangles</span>
  893. </div>
  894. <div class="operator" ${hasBuiltinLOD ? 'hidden' : ''}>
  895. <ui-icon value="add" key="${index}" path="insertLod" tooltip="insert after this LOD"></ui-icon>
  896. <ui-icon value="reduce" key="${index}" path="deleteLod" tooltip="delete this LOD"></ui-icon>
  897. </div>
  898. </div>
  899. </header>
  900. <div class="lod-item-content">
  901. <ui-prop>
  902. <ui-label slot="label" value="Screen Ratio (%)"></ui-label>
  903. <ui-num-input slot="content" key="${index}" path="screenRatio" min="0" max="100"
  904. value="${Editor.Utils.Math.multi(lodItem.screenRatio, 100)}">
  905. </ui-num-input>
  906. </ui-prop>
  907. </div>
  908. </ui-section>
  909. </div>`;
  910. }
  911. return lodItemsStr;
  912. }