export.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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 { GraphicsRenderObject } from '../../mol-gl/render-object';
  7. import { MeshValues } from '../../mol-gl/renderable/mesh';
  8. import { LinesValues } from '../../mol-gl/renderable/lines';
  9. import { PointsValues } from '../../mol-gl/renderable/points';
  10. import { SpheresValues } from '../../mol-gl/renderable/spheres';
  11. import { CylindersValues } from '../../mol-gl/renderable/cylinders';
  12. import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
  13. import { TextureImage } from '../../mol-gl/renderable/util';
  14. import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
  15. import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
  16. import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
  17. import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
  18. import { RuntimeContext } from '../../mol-task';
  19. import { StringBuilder } from '../../mol-util';
  20. import { Color } from '../../mol-util/color/color';
  21. import { decodeFloatRGB } from '../../mol-util/float-packing';
  22. // avoiding namespace lookup improved performance in Chrome (Aug 2020)
  23. const v3fromArray = Vec3.fromArray;
  24. const v3transformMat4 = Vec3.transformMat4;
  25. const v3transformMat3 = Vec3.transformMat3;
  26. const mat3directionTransform = Mat3.directionTransform;
  27. type RenderObjectExportData = {
  28. [k: string]: string | Uint8Array | undefined
  29. }
  30. interface RenderObjectExporter<D extends RenderObjectExportData> {
  31. add(renderObject: GraphicsRenderObject, ctx: RuntimeContext): Promise<void> | undefined
  32. getData(): D
  33. }
  34. // http://paulbourke.net/dataformats/obj/
  35. // http://paulbourke.net/dataformats/mtl/
  36. export type ObjData = {
  37. obj: string
  38. mtl: string
  39. }
  40. export class ObjExporter implements RenderObjectExporter<ObjData> {
  41. private obj = StringBuilder.create();
  42. private mtl = StringBuilder.create();
  43. private vertexOffset = 0;
  44. private currentColor: Color | undefined;
  45. private currentAlpha: number | undefined;
  46. private materialSet = new Set<string>();
  47. private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
  48. const r = tSize.array[i * 3];
  49. const g = tSize.array[i * 3 + 1];
  50. const b = tSize.array[i * 3 + 2];
  51. return decodeFloatRGB(r, g, b);
  52. }
  53. private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
  54. const tSize = values.tSize.ref.value;
  55. let size = 0;
  56. switch (values.dSizeType.ref.value) {
  57. case 'uniform':
  58. size = values.uSize.ref.value;
  59. break;
  60. case 'instance':
  61. size = ObjExporter.getSizeFromTexture(tSize, instanceIndex) / 100;
  62. break;
  63. case 'group':
  64. size = ObjExporter.getSizeFromTexture(tSize, group) / 100;
  65. break;
  66. case 'groupInstance':
  67. const groupCount = values.uGroupCount.ref.value;
  68. size = ObjExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group) / 100;
  69. break;
  70. }
  71. return size * values.uSizeFactor.ref.value;
  72. }
  73. private updateMaterial(color: Color, alpha: number) {
  74. if (this.currentColor === color && this.currentAlpha === alpha) return;
  75. this.currentColor = color;
  76. this.currentAlpha = alpha;
  77. const material = Color.toHexString(color) + alpha;
  78. StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
  79. StringBuilder.newline(this.obj);
  80. if (!this.materialSet.has(material)) {
  81. this.materialSet.add(material);
  82. const [r, g, b] = Color.toRgbNormalized(color);
  83. const mtl = this.mtl;
  84. StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
  85. StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
  86. StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
  87. StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
  88. StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
  89. StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
  90. StringBuilder.writeFloat(mtl, r, 1000);
  91. StringBuilder.whitespace1(mtl);
  92. StringBuilder.writeFloat(mtl, g, 1000);
  93. StringBuilder.whitespace1(mtl);
  94. StringBuilder.writeFloat(mtl, b, 1000);
  95. StringBuilder.newline(mtl);
  96. StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
  97. StringBuilder.writeSafe(mtl, 'd '); // dissolve
  98. StringBuilder.writeFloat(mtl, alpha, 1000);
  99. StringBuilder.newline(mtl);
  100. }
  101. }
  102. private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array, groups: Float32Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, ctx: RuntimeContext) {
  103. const obj = this.obj;
  104. const t = Mat4();
  105. const n = Mat3();
  106. const tmpV = Vec3();
  107. const colorType = values.dColorType.ref.value;
  108. const tColor = values.tColor.ref.value.array;
  109. const uAlpha = values.uAlpha.ref.value;
  110. const aTransform = values.aTransform.ref.value;
  111. Mat4.fromArray(t, aTransform, instanceIndex * 16);
  112. mat3directionTransform(n, t);
  113. const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
  114. await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
  115. // position
  116. for (let i = 0; i < vertexCount; ++i) {
  117. if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
  118. v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * 3), t);
  119. StringBuilder.writeSafe(obj, 'v ');
  120. StringBuilder.writeFloat(obj, tmpV[0], 1000);
  121. StringBuilder.whitespace1(obj);
  122. StringBuilder.writeFloat(obj, tmpV[1], 1000);
  123. StringBuilder.whitespace1(obj);
  124. StringBuilder.writeFloat(obj, tmpV[2], 1000);
  125. StringBuilder.newline(obj);
  126. }
  127. // normal
  128. for (let i = 0; i < vertexCount; ++i) {
  129. if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
  130. v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * 3), n);
  131. StringBuilder.writeSafe(obj, 'vn ');
  132. StringBuilder.writeFloat(obj, tmpV[0], 100);
  133. StringBuilder.whitespace1(obj);
  134. StringBuilder.writeFloat(obj, tmpV[1], 100);
  135. StringBuilder.whitespace1(obj);
  136. StringBuilder.writeFloat(obj, tmpV[2], 100);
  137. StringBuilder.newline(obj);
  138. }
  139. // face
  140. for (let i = 0; i < drawCount; i += 3) {
  141. if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + i });
  142. let color: Color;
  143. switch (colorType) {
  144. case 'uniform':
  145. color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
  146. break;
  147. case 'instance':
  148. color = Color.fromArray(tColor, instanceIndex * 3);
  149. break;
  150. case 'group':
  151. color = Color.fromArray(tColor, groups[indices[i]] * 3);
  152. break;
  153. case 'groupInstance':
  154. const groupCount = values.uGroupCount.ref.value;
  155. const group = groups[indices[i]];
  156. color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
  157. break;
  158. case 'vertex':
  159. color = Color.fromArray(tColor, i * 3);
  160. break;
  161. case 'vertexInstance':
  162. color = Color.fromArray(tColor, (instanceIndex * drawCount + i) * 3);
  163. break;
  164. default: throw new Error('Unsupported color type.');
  165. }
  166. this.updateMaterial(color, uAlpha);
  167. const v1 = this.vertexOffset + indices[i] + 1;
  168. const v2 = this.vertexOffset + indices[i + 1] + 1;
  169. const v3 = this.vertexOffset + indices[i + 2] + 1;
  170. StringBuilder.writeSafe(obj, 'f ');
  171. StringBuilder.writeInteger(obj, v1);
  172. StringBuilder.writeSafe(obj, '//');
  173. StringBuilder.writeIntegerAndSpace(obj, v1);
  174. StringBuilder.writeInteger(obj, v2);
  175. StringBuilder.writeSafe(obj, '//');
  176. StringBuilder.writeIntegerAndSpace(obj, v2);
  177. StringBuilder.writeInteger(obj, v3);
  178. StringBuilder.writeSafe(obj, '//');
  179. StringBuilder.writeInteger(obj, v3);
  180. StringBuilder.newline(obj);
  181. }
  182. this.vertexOffset += vertexCount;
  183. }
  184. private async addMesh(values: MeshValues, ctx: RuntimeContext) {
  185. const aPosition = values.aPosition.ref.value;
  186. const aNormal = values.aNormal.ref.value;
  187. const elements = values.elements.ref.value;
  188. const aGroup = values.aGroup.ref.value;
  189. const instanceCount = values.instanceCount.ref.value;
  190. const vertexCount = values.uVertexCount.ref.value;
  191. const drawCount = values.drawCount.ref.value;
  192. for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
  193. await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, ctx);
  194. }
  195. }
  196. private async addLines(values: LinesValues, ctx: RuntimeContext) {
  197. // TODO
  198. }
  199. private async addPoints(values: PointsValues, ctx: RuntimeContext) {
  200. // TODO
  201. }
  202. private async addSpheres(values: SpheresValues, ctx: RuntimeContext) {
  203. const center = Vec3();
  204. const aPosition = values.aPosition.ref.value;
  205. const aGroup = values.aGroup.ref.value;
  206. const instanceCount = values.instanceCount.ref.value;
  207. const vertexCount = values.uVertexCount.ref.value;
  208. for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
  209. const state = MeshBuilder.createState(512, 256);
  210. for (let i = 0; i < vertexCount; i += 4) {
  211. v3fromArray(center, aPosition, i * 3);
  212. const group = aGroup[i];
  213. const radius = ObjExporter.getSize(values, instanceIndex, group);
  214. state.currentGroup = group;
  215. addSphere(state, center, radius, 2);
  216. }
  217. const mesh = MeshBuilder.getMesh(state);
  218. const vertices = mesh.vertexBuffer.ref.value;
  219. const normals = mesh.normalBuffer.ref.value;
  220. const indices = mesh.indexBuffer.ref.value;
  221. const groups = mesh.groupBuffer.ref.value;
  222. await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
  223. }
  224. }
  225. private async addCylinders(values: CylindersValues, ctx: RuntimeContext) {
  226. const start = Vec3();
  227. const end = Vec3();
  228. const aStart = values.aStart.ref.value;
  229. const aEnd = values.aEnd.ref.value;
  230. const aScale = values.aScale.ref.value;
  231. const aCap = values.aCap.ref.value;
  232. const aGroup = values.aGroup.ref.value;
  233. const instanceCount = values.instanceCount.ref.value;
  234. const vertexCount = values.uVertexCount.ref.value;
  235. for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
  236. const state = MeshBuilder.createState(512, 256);
  237. for (let i = 0; i < vertexCount; i += 6) {
  238. v3fromArray(start, aStart, i * 3);
  239. v3fromArray(end, aEnd, i * 3);
  240. const group = aGroup[i];
  241. const radius = ObjExporter.getSize(values, instanceIndex, group) * aScale[i];
  242. const cap = aCap[i];
  243. const topCap = cap === 1 || cap === 3;
  244. const bottomCap = cap >= 2;
  245. const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments: 32 };
  246. state.currentGroup = aGroup[i];
  247. addCylinder(state, start, end, 1, cylinderProps);
  248. }
  249. const mesh = MeshBuilder.getMesh(state);
  250. const vertices = mesh.vertexBuffer.ref.value;
  251. const normals = mesh.normalBuffer.ref.value;
  252. const indices = mesh.indexBuffer.ref.value;
  253. const groups = mesh.groupBuffer.ref.value;
  254. await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
  255. }
  256. }
  257. add(renderObject: GraphicsRenderObject, ctx: RuntimeContext) {
  258. if (!renderObject.state.visible) return;
  259. switch (renderObject.type) {
  260. case 'mesh':
  261. return this.addMesh(renderObject.values as MeshValues, ctx);
  262. case 'lines':
  263. return this.addLines(renderObject.values as LinesValues, ctx);
  264. case 'points':
  265. return this.addPoints(renderObject.values as PointsValues, ctx);
  266. case 'spheres':
  267. return this.addSpheres(renderObject.values as SpheresValues, ctx);
  268. case 'cylinders':
  269. return this.addCylinders(renderObject.values as CylindersValues, ctx);
  270. }
  271. }
  272. getData() {
  273. return {
  274. obj: StringBuilder.getString(this.obj),
  275. mtl: StringBuilder.getString(this.mtl)
  276. };
  277. }
  278. constructor(filename: string) {
  279. StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
  280. }
  281. }