obj-exporter.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  5. */
  6. import { BaseValues } from '../../mol-gl/renderable/schema';
  7. import { asciiWrite } from '../../mol-io/common/ascii';
  8. import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
  9. import { RuntimeContext } from '../../mol-task';
  10. import { StringBuilder } from '../../mol-util';
  11. import { Color } from '../../mol-util/color/color';
  12. import { zip } from '../../mol-util/zip/zip';
  13. import { MeshExporter } from './mesh-exporter';
  14. // avoiding namespace lookup improved performance in Chrome (Aug 2020)
  15. const v3fromArray = Vec3.fromArray;
  16. const v3transformMat4 = Vec3.transformMat4;
  17. const v3transformMat3 = Vec3.transformMat3;
  18. const mat3directionTransform = Mat3.directionTransform;
  19. // http://paulbourke.net/dataformats/obj/
  20. // http://paulbourke.net/dataformats/mtl/
  21. export type ObjData = {
  22. obj: string
  23. mtl: string
  24. }
  25. export class ObjExporter extends MeshExporter<ObjData> {
  26. readonly fileExtension = 'zip';
  27. private obj = StringBuilder.create();
  28. private mtl = StringBuilder.create();
  29. private vertexOffset = 0;
  30. private currentColor: Color | undefined;
  31. private currentAlpha: number | undefined;
  32. private materialSet = new Set<string>();
  33. private updateMaterial(color: Color, alpha: number) {
  34. if (this.currentColor === color && this.currentAlpha === alpha) return;
  35. this.currentColor = color;
  36. this.currentAlpha = alpha;
  37. const material = Color.toHexString(color) + alpha;
  38. StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
  39. StringBuilder.newline(this.obj);
  40. if (!this.materialSet.has(material)) {
  41. this.materialSet.add(material);
  42. const [r, g, b] = Color.toRgbNormalized(color);
  43. const mtl = this.mtl;
  44. StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
  45. StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
  46. StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
  47. StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
  48. StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
  49. StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
  50. StringBuilder.writeFloat(mtl, r, 1000);
  51. StringBuilder.whitespace1(mtl);
  52. StringBuilder.writeFloat(mtl, g, 1000);
  53. StringBuilder.whitespace1(mtl);
  54. StringBuilder.writeFloat(mtl, b, 1000);
  55. StringBuilder.newline(mtl);
  56. StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
  57. StringBuilder.writeSafe(mtl, 'd '); // dissolve
  58. StringBuilder.writeFloat(mtl, alpha, 1000);
  59. StringBuilder.newline(mtl);
  60. }
  61. }
  62. protected async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, isGeoTexture: boolean, ctx: RuntimeContext) {
  63. const obj = this.obj;
  64. const t = Mat4();
  65. const n = Mat3();
  66. const tmpV = Vec3();
  67. const stride = isGeoTexture ? 4 : 3;
  68. const colorType = values.dColorType.ref.value;
  69. const tColor = values.tColor.ref.value.array;
  70. const uAlpha = values.uAlpha.ref.value;
  71. const aTransform = values.aTransform.ref.value;
  72. Mat4.fromArray(t, aTransform, instanceIndex * 16);
  73. mat3directionTransform(n, t);
  74. const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
  75. await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
  76. // position
  77. for (let i = 0; i < vertexCount; ++i) {
  78. if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
  79. v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
  80. StringBuilder.writeSafe(obj, 'v ');
  81. StringBuilder.writeFloat(obj, tmpV[0], 1000);
  82. StringBuilder.whitespace1(obj);
  83. StringBuilder.writeFloat(obj, tmpV[1], 1000);
  84. StringBuilder.whitespace1(obj);
  85. StringBuilder.writeFloat(obj, tmpV[2], 1000);
  86. StringBuilder.newline(obj);
  87. }
  88. // normal
  89. for (let i = 0; i < vertexCount; ++i) {
  90. if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
  91. v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
  92. StringBuilder.writeSafe(obj, 'vn ');
  93. StringBuilder.writeFloat(obj, tmpV[0], 100);
  94. StringBuilder.whitespace1(obj);
  95. StringBuilder.writeFloat(obj, tmpV[1], 100);
  96. StringBuilder.whitespace1(obj);
  97. StringBuilder.writeFloat(obj, tmpV[2], 100);
  98. StringBuilder.newline(obj);
  99. }
  100. // face
  101. for (let i = 0; i < drawCount; i += 3) {
  102. if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + i });
  103. let color: Color;
  104. switch (colorType) {
  105. case 'uniform':
  106. color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
  107. break;
  108. case 'instance':
  109. color = Color.fromArray(tColor, instanceIndex * 3);
  110. break;
  111. case 'group': {
  112. const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
  113. color = Color.fromArray(tColor, group * 3);
  114. break;
  115. }
  116. case 'groupInstance': {
  117. const groupCount = values.uGroupCount.ref.value;
  118. const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
  119. color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
  120. break;
  121. }
  122. case 'vertex':
  123. color = Color.fromArray(tColor, indices![i] * 3);
  124. break;
  125. case 'vertexInstance':
  126. color = Color.fromArray(tColor, (instanceIndex * drawCount + indices![i]) * 3);
  127. break;
  128. default: throw new Error('Unsupported color type.');
  129. }
  130. this.updateMaterial(color, uAlpha);
  131. const v1 = this.vertexOffset + (isGeoTexture ? i : indices![i]) + 1;
  132. const v2 = this.vertexOffset + (isGeoTexture ? i + 1 : indices![i + 1]) + 1;
  133. const v3 = this.vertexOffset + (isGeoTexture ? i + 2 : indices![i + 2]) + 1;
  134. StringBuilder.writeSafe(obj, 'f ');
  135. StringBuilder.writeInteger(obj, v1);
  136. StringBuilder.writeSafe(obj, '//');
  137. StringBuilder.writeIntegerAndSpace(obj, v1);
  138. StringBuilder.writeInteger(obj, v2);
  139. StringBuilder.writeSafe(obj, '//');
  140. StringBuilder.writeIntegerAndSpace(obj, v2);
  141. StringBuilder.writeInteger(obj, v3);
  142. StringBuilder.writeSafe(obj, '//');
  143. StringBuilder.writeInteger(obj, v3);
  144. StringBuilder.newline(obj);
  145. }
  146. this.vertexOffset += vertexCount;
  147. }
  148. getData() {
  149. return {
  150. obj: StringBuilder.getString(this.obj),
  151. mtl: StringBuilder.getString(this.mtl)
  152. };
  153. }
  154. async getBlob(ctx: RuntimeContext) {
  155. const { obj, mtl } = this.getData();
  156. const objData = new Uint8Array(obj.length);
  157. asciiWrite(objData, obj);
  158. const mtlData = new Uint8Array(mtl.length);
  159. asciiWrite(mtlData, mtl);
  160. const zipDataObj = {
  161. [this.filename + '.obj']: objData,
  162. [this.filename + '.mtl']: mtlData
  163. };
  164. return new Blob([await zip(ctx, zipDataObj)], { type: 'application/zip' });
  165. }
  166. constructor(private filename: string) {
  167. super();
  168. StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
  169. }
  170. }