@woosh/meep-engine 2.40.0 → 2.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/core/binary/BinaryBuffer.js +6 -1
  2. package/core/collection/HashMap.d.ts +2 -0
  3. package/core/collection/HashMap.js +22 -0
  4. package/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -0
  5. package/core/geom/3d/normal/octahedron/decode_octahedron_to_unit.js +31 -0
  6. package/core/geom/3d/normal/octahedron/encode_unit_to_octahedron.js +33 -0
  7. package/core/geom/3d/normal/octahedron/encoding.spec.js +29 -0
  8. package/core/geom/3d/topology/struct/BinaryTopology.js +112 -0
  9. package/core/geom/Quaternion.js +8 -8
  10. package/core/geom/Quaternion.spec.js +13 -0
  11. package/core/geom/Vector1.d.ts +2 -0
  12. package/core/math/sign_not_zero.js +8 -0
  13. package/core/parser/simple/SimpleParser.js +3 -86
  14. package/core/parser/simple/readUnsignedInteger.js +66 -0
  15. package/core/parser/simple/skipWhitespace.js +21 -0
  16. package/editor/view/ecs/components/common/NumberController.js +24 -6
  17. package/engine/EngineHarness.js +6 -1
  18. package/engine/asset/AssetDescription.spec.js +27 -0
  19. package/engine/asset/loaders/GLTFAssetLoader.js +5 -3
  20. package/engine/asset/loaders/image/png/PNG.js +6 -0
  21. package/engine/asset/loaders/image/png/PNGReader.js +41 -0
  22. package/engine/ecs/foliage/ImpostorFoliage.js +4 -0
  23. package/engine/ecs/terrain/tiles/TerrainTile.js +2 -0
  24. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +22 -7
  25. package/engine/graphics/filter/ImageFilter.js +2 -1
  26. package/engine/graphics/geometry/MikkT/MikkTSpace.spec.js +7 -1
  27. package/engine/{asset/loaders/geometry/geometry → graphics/geometry/buffered}/computeBufferAttributeHash.js +1 -1
  28. package/engine/{asset/loaders/geometry/geometry → graphics/geometry/buffered}/computeGeometryEquality.js +2 -2
  29. package/engine/{asset/loaders/geometry/geometry → graphics/geometry/buffered}/computeGeometryHash.js +1 -1
  30. package/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.js +2 -2
  31. package/engine/graphics/impostors/card_cluster/FacePlaneAssignment.js +30 -0
  32. package/engine/graphics/impostors/card_cluster/README.md +13 -0
  33. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +332 -0
  34. package/engine/graphics/impostors/octahedral/ImpostorCaptureType.js +10 -0
  35. package/engine/graphics/impostors/octahedral/ImpostorDescription.js +63 -0
  36. package/engine/graphics/impostors/octahedral/README.md +29 -0
  37. package/engine/graphics/impostors/octahedral/bake/compute_bounding_sphere.js +42 -0
  38. package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +88 -0
  39. package/engine/graphics/impostors/octahedral/grid/HemiOctahedralUvEncoder.js +20 -0
  40. package/engine/graphics/impostors/octahedral/grid/OctahedralUvEncoder.js +25 -0
  41. package/engine/graphics/impostors/octahedral/grid/UvEncoder.js +21 -0
  42. package/engine/graphics/impostors/octahedral/prototypeBaker.js +138 -0
  43. package/engine/graphics/impostors/octahedral/shader/BakeShaderStandard.js +157 -0
  44. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderStandard.js +306 -0
  45. package/engine/graphics/impostors/octahedral/shader/glsl/common.glsl +206 -0
  46. package/engine/graphics/micron/convert_three_object_to_micron.js +2 -2
  47. package/engine/graphics/micron/plugin/MicronRenderPlugin.js +2 -2
  48. package/engine/graphics/particles/node-based/codegen/CodeGenerator.js +1 -1
  49. package/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.js +1 -1
  50. package/engine/graphics/render/forward_plus/LightManager.js +25 -0
  51. package/engine/graphics/render/forward_plus/data/TextureBackedMemoryRegion.js +6 -2
  52. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +6 -11
  53. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +268 -2
  54. package/engine/graphics/render/visibility/hiz/buildCanvasViewFromTexture.js +32 -10
  55. package/engine/graphics/shaders/ScreenSpaceQuadShader.js +4 -14
  56. package/engine/graphics/shaders/glsl_gen_swizzled_read.js +39 -0
  57. package/engine/graphics/texture/atlas/AtlasPatch.js +12 -6
  58. package/engine/logging/ConsoleLoggerBackend.js +15 -0
  59. package/engine/logging/Logger.js +24 -1
  60. package/engine/logging/LoggerBackend.js +1 -0
  61. package/engine/physics/fluid/FluidField.js +9 -0
  62. package/engine/physics/fluid/effector/AbstractFluidEffector.js +6 -0
  63. package/engine/physics/fluid/effector/GlobalFluidEffector.js +12 -0
  64. package/engine/physics/fluid/effector/WakeFluidEffector.js +8 -0
  65. package/engine/ui/GUIEngine.js +8 -1
  66. package/package.json +1 -1
  67. package/view/tooltip/gml/parser/readReferenceToken.js +1 -1
  68. package/engine/asset/GameAssetManager.js +0 -137
@@ -11,7 +11,9 @@ import { MeshDepthMaterial, RGBADepthPacking } from "three";
11
11
  import { TextureAttachmentsByMaterialType } from "./material/TextureAttachmensByMaterialType.js";
12
12
  import { AssetLoader } from "./AssetLoader.js";
13
13
  import { ensureGeometryBoundingSphere } from "../../graphics/geometry/buffered/ensureGeometryBoundingSphere.js";
14
- import { computeSkinnedMeshBoundingVolumes } from "../../graphics/geometry/skining/computeSkinnedMeshBoundingVolumes.js";
14
+ import {
15
+ computeSkinnedMeshBoundingVolumes
16
+ } from "../../graphics/geometry/skining/computeSkinnedMeshBoundingVolumes.js";
15
17
  import { ensureGeometryBoundingBox } from "../../graphics/util/ensureGeometryBoundingBox.js";
16
18
  import { cloneObject3D } from "../../graphics/three/cloneObject3D.js";
17
19
  import GLTFTextureDDSExtension from "./gltf/extensions/MSFT_texture_dds.js";
@@ -20,8 +22,8 @@ import { computeObjectBoundingSphere } from "./gltf/computeObjectBoundingSphere.
20
22
  import { isMesh } from "./gltf/isMesh.js";
21
23
  import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
22
24
  import { HashSet } from "../../../core/collection/HashSet.js";
23
- import { computeGeometryHash } from "./geometry/geometry/computeGeometryHash.js";
24
- import { computeGeometryEquality } from "./geometry/geometry/computeGeometryEquality.js";
25
+ import { computeGeometryHash } from "../../graphics/geometry/buffered/computeGeometryHash.js";
26
+ import { computeGeometryEquality } from "../../graphics/geometry/buffered/computeGeometryEquality.js";
25
27
  import { traverseThreeObject } from "../../graphics/ecs/highlight/renderer/traverseThreeObject.js";
26
28
  import { computeTextureHash } from "./material/computeTextureHash.js";
27
29
  import { computeTextureEquality } from "./material/computeTextureEquality.js";
@@ -40,6 +40,12 @@ export function PNG() {
40
40
  */
41
41
  this.trns = null;
42
42
 
43
+
44
+ /**
45
+ * Text metadata coming from tEXt chunks
46
+ * @type {Object<string>}
47
+ */
48
+ this.text = {};
43
49
  }
44
50
 
45
51
  PNG.prototype.getWidth = function () {
@@ -157,12 +157,53 @@ PNGReader.prototype.decodeChunk = function () {
157
157
  case 'IEND':
158
158
  this.decodeIEND(chunk);
159
159
  break;
160
+ default:
161
+ //console.log(`Unsupported block ${type}`);
162
+ break;
160
163
  }
161
164
 
162
165
  return type;
163
166
 
164
167
  };
165
168
 
169
+ /**
170
+ * https://www.w3.org/TR/PNG/#11tEXt
171
+ * @param {Uint8Array} chunk
172
+ */
173
+ PNGReader.prototype.decodetEXt = function (chunk) {
174
+ const buff = BinaryBuffer.fromArrayBuffer(chunk.buffer);
175
+ const keyword = buff.readASCIICharacters(Number.POSITIVE_INFINITY, true);
176
+
177
+ const value = buff.readASCIICharacters(keyword.length - 1, false);
178
+
179
+ this.png.text[keyword] = value;
180
+ }
181
+ /**
182
+ * NOTE: untested
183
+ * https://www.w3.org/TR/PNG/#11iEXt
184
+ * @param {Uint8Array} chunk
185
+ */
186
+ PNGReader.prototype.decodeiEXt = function (chunk) {
187
+ const buff = BinaryBuffer.fromArrayBuffer(chunk.buffer);
188
+
189
+ const keyword = buff.readASCIICharacters(Number.POSITIVE_INFINITY, true);
190
+
191
+ const compression_flag = buff.readUint8();
192
+ const compression_method = buff.readUint8();
193
+ const language_tag = buff.readASCIICharacters(Number.POSITIVE_INFINITY, true);
194
+ const translated_keyword = buff.readUTF8String();
195
+
196
+ const separator = buff.readUint8();
197
+
198
+ if (separator !== 0) {
199
+ throw new Error('Expected Null Separator after Translated keyword');
200
+ }
201
+
202
+ const text = buff.readUTF8String();
203
+
204
+ this.png.text[keyword] = text;
205
+ }
206
+
166
207
  /**
167
208
  * http://www.w3.org/TR/2003/REC-PNG-20031110/#11IHDR
168
209
  * http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html#C.IHDR
@@ -19,6 +19,10 @@ import {
19
19
  import Vector2 from "../../../core/geom/Vector2.js";
20
20
  import Vector4 from "../../../core/geom/Vector4.js";
21
21
 
22
+ /**
23
+ * @deprecated use other options, such as ShadedMesh with INSTANCED draw mode or dedicated instanced mesh system such as Foliage2System
24
+ * @constructor
25
+ */
22
26
  const Foliage = function (options) {
23
27
  const size = options.size || 1;
24
28
  const lightMap = options.lightMap;
@@ -496,6 +496,8 @@ class TerrainTile {
496
496
  geometry_bb.x0, geometry_bb.y0, geometry_bb.z0,
497
497
  geometry_bb.x1, geometry_bb.y1, geometry_bb.z1
498
498
  );
499
+ // update changes up the tree
500
+ this.boundingBox.bubbleRefit();
499
501
  }
500
502
 
501
503
  /**
@@ -2,6 +2,22 @@ import { EntityNode } from "../../../ecs/parent/EntityNode.js";
2
2
  import { Transform } from "../../../ecs/transform/Transform.js";
3
3
  import { ShadedGeometry } from "./ShadedGeometry.js";
4
4
 
5
+ /**
6
+ *
7
+ * @param {Transform} transform
8
+ * @param {THREE.Object3D} three_object
9
+ */
10
+ function copy_three_transform(transform, three_object) {
11
+
12
+ // copy object transform
13
+ transform.position.copy(three_object.position);
14
+ transform.scale.copy(three_object.scale);
15
+ transform.rotation.copy(three_object.quaternion);
16
+
17
+ //
18
+ transform.matrix.set(three_object.matrix.elements);
19
+ }
20
+
5
21
  /**
6
22
  *
7
23
  * @param {THREE.Object3D} root
@@ -13,16 +29,15 @@ export function three_object_to_entity_composition(root) {
13
29
  const node_transform = node.transform;
14
30
 
15
31
  // copy object transform
16
- node_transform.position.copy(root.position);
17
- node_transform.scale.copy(root.scale);
18
- node_transform.rotation.copy(root.quaternion);
19
-
20
- //
21
- node_transform.matrix.set(root.matrix.elements);
32
+ copy_three_transform(node_transform, root);
22
33
 
23
34
  const entity = node.entity;
24
35
 
25
- entity.add(new Transform());
36
+ const transform = new Transform();
37
+ // initialize world transform
38
+ transform.fromMatrix4(root.matrixWorld.elements);
39
+
40
+ entity.add(transform);
26
41
 
27
42
  if (root.isMesh) {
28
43
  if (root.isSkinnedMesh) {
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
 
6
- import { LinearFilter, Mesh, OrthographicCamera, Scene, ShaderMaterial, WebGLRenderTarget } from 'three';
6
+ import { DoubleSide, LinearFilter, Mesh, OrthographicCamera, Scene, ShaderMaterial, WebGLRenderTarget } from 'three';
7
7
  import { FULL_SCREEN_TRIANGLE_GEOMETRY } from "../geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js";
8
8
  import { FULL_SCREEN_QUAD_VERTEX_SHADER } from "../FULL_SCREEN_QUAD_VERTEX_SHADER.js";
9
9
 
@@ -41,6 +41,7 @@ function processTexture(
41
41
  material.fog = false;
42
42
  material.uniformsNeedUpdate = true;
43
43
  material.needsUpdate = true;
44
+ material.side = DoubleSide;
44
45
 
45
46
  const quad = new Mesh(FULL_SCREEN_TRIANGLE_GEOMETRY, material);
46
47
 
@@ -14,7 +14,13 @@ test('single triangle', () => {
14
14
 
15
15
  ctx.geometry_buffer_index = [0, 1, 2];
16
16
  ctx.geometry_buffer_vertex_position = [1, 2, 3, 4, 5, 6, 7, 8, 9];
17
- ctx.geometry_buffer_vertex_normal = [0, 1, 0, 0, 1, 0, 0, 1, 0];
17
+
18
+ ctx.geometry_buffer_vertex_normal = [
19
+ 0, 1, 0,
20
+ 0, 1, 0,
21
+ 0, 1, 0
22
+ ];
23
+
18
24
  ctx.geometry_buffer_vertex_uv = [0, 0, 0, 0, 0, 0];
19
25
 
20
26
  genTangSpace(ctx);
@@ -1,4 +1,4 @@
1
- import { computeStridedIntegerArrayHash } from "../../../../computeStridedIntegerArrayHash.js";
1
+ import { computeStridedIntegerArrayHash } from "../../../computeStridedIntegerArrayHash.js";
2
2
 
3
3
  /**
4
4
  *
@@ -1,5 +1,5 @@
1
- import { isArrayEqualStrict } from "../../../../../core/collection/array/isArrayEqualStrict.js";
2
- import { is_typed_array_equals } from "../../../../../core/collection/array/typed/is_typed_array_equals.js";
1
+ import { isArrayEqualStrict } from "../../../../core/collection/array/isArrayEqualStrict.d.ts";
2
+ import { is_typed_array_equals } from "../../../../core/collection/array/typed/is_typed_array_equals.js";
3
3
 
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { computeBufferAttributeHash } from "./computeBufferAttributeHash.js";
2
- import { computeStringHash } from "../../../../../core/primitives/strings/computeStringHash.js";
2
+ import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.d.ts";
3
3
 
4
4
  /**
5
5
  *
@@ -1,7 +1,7 @@
1
1
  import { StaticMaterialCache } from "../../../../asset/loaders/material/StaticMaterialCache.js";
2
2
  import { HashMap } from "../../../../../core/collection/HashMap.js";
3
- import { computeGeometryEquality } from "../../../../asset/loaders/geometry/geometry/computeGeometryEquality.js";
4
- import { computeGeometryHash } from "../../../../asset/loaders/geometry/geometry/computeGeometryHash.js";
3
+ import { computeGeometryEquality } from "../../buffered/computeGeometryEquality.js";
4
+ import { computeGeometryHash } from "../../buffered/computeGeometryHash.js";
5
5
  import { array_copy } from "../../../../../core/collection/array/copyArray.js";
6
6
  import { mat4 } from "gl-matrix";
7
7
  import { Group, Matrix4, Mesh } from "three";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A geometry is separated into individual triangles, with each triangle projected onto a single plane.
3
+ * This class contains and fully describes such assignment
4
+ */
5
+ export class FacePlaneAssignment {
6
+ /**
7
+ * Flat plane data array
8
+ * Format: [normal_0_x, normal_0_y, normal_0_z, plane_0_offset, normal_1_x, ... ]
9
+ * @type {number[]}
10
+ */
11
+ planes = [];
12
+
13
+ /**
14
+ *
15
+ * @type {number}
16
+ */
17
+ plane_count = 0;
18
+
19
+ /**
20
+ *
21
+ * @type {THREE.BufferGeometry|null}
22
+ */
23
+ geometry = null;
24
+
25
+ /**
26
+ * Indices of planes associated with each face (triangle) for a given face index
27
+ * @type {number[]}
28
+ */
29
+ face_assignments = [];
30
+ }
@@ -0,0 +1,13 @@
1
+ # Card Cluster Impostors
2
+
3
+ The system is fairly trivial in concept. It generates a specified number of *cards* (quads) along with textures to represent original mesh.
4
+
5
+ The idea for this system was inspired by Sean Feeley's talk at GDC 2019 "Interactive Wind and Vegetation in 'God of War'", where he briefly explained how impostors work in their engine.
6
+
7
+ ---
8
+ ### References
9
+
10
+ 1. [2003] "Billboard Clouds for Extreme Model Simplification" by Xavier Decoret, Fr ´ edo Durand, Franc ´ ¸ois X. Sillion, Julie Dorsey
11
+ 2. [2005] "Extreme Model Simplification for Forest Rendering" by Anton L. Fuhrmann, Eike Umlauf and Stephan Mantler
12
+ 3. [2005] "Tree Rendering with Billboard Clouds" by Ismael Garcia, Mateu Sbert, and László Szirmay-Kalos
13
+ 4. [2009] "Implementation of an Improved Billboard Cloud Algorithm" by Christian Luksch
@@ -0,0 +1,332 @@
1
+ import { ImpostorCaptureType } from "./ImpostorCaptureType.js";
2
+ import { assert } from "../../../../core/assert.js";
3
+ import { isPowerOfTwo } from "../../../../core/math/isPowerOrTwo.js";
4
+ import { Mesh, OrthographicCamera, Scene, Vector4, WebGLMultipleRenderTargets } from "three";
5
+ import { ImpostorDescription } from "./ImpostorDescription.js";
6
+ import { mat4 } from "gl-matrix";
7
+ import { BakeShaderStandard } from "./shader/BakeShaderStandard.js";
8
+ import { Sampler2D } from "../../texture/sampler/Sampler2D.js";
9
+ import Signal from "../../../../core/events/signal/Signal.js";
10
+ import { HashMap } from "../../../../core/collection/HashMap.js";
11
+ import { computeMaterialHash } from "../../../asset/loaders/material/computeMaterialHash.js";
12
+ import { computeMaterialEquality } from "../../../asset/loaders/material/computeMaterialEquality.js";
13
+ import { UvEncoder } from "./grid/UvEncoder.js";
14
+ import { OctahedralUvEncoder } from "./grid/OctahedralUvEncoder.js";
15
+ import { HemiOctahedralUvEncoder } from "./grid/HemiOctahedralUvEncoder.js";
16
+ import { prepare_bake_material } from "./bake/prepare_bake_material.js";
17
+ import { compute_bounding_sphere } from "./bake/compute_bounding_sphere.js";
18
+ import { collectIteratorValueToArray } from "../../../../core/collection/IteratorUtils.js";
19
+
20
+ export class ImpostorBaker {
21
+
22
+ /**
23
+ *
24
+ * @type {THREE.WebGLRenderer|null}
25
+ * @private
26
+ */
27
+ _renderer = null;
28
+
29
+ /**
30
+ *
31
+ * @param {THREE.WebGLRenderer} v
32
+ */
33
+ set renderer(v) {
34
+ this._renderer = v;
35
+ }
36
+
37
+
38
+ /**
39
+ *
40
+ * @param {number} distance
41
+ * @param {{mesh:ShadedGeometry, transform:mat4}[]} objects
42
+ * @param {number} resolution
43
+ * @param {number} frames
44
+ * @param {UvEncoder} encoder
45
+ */
46
+ bake_internal({
47
+ distance,
48
+ objects,
49
+ resolution,
50
+ frames,
51
+ encoder
52
+ }) {
53
+
54
+ assert.isNumber(distance, 'distance');
55
+ assert.isNonNegativeInteger(resolution, 'resolution');
56
+ assert.isNonNegativeInteger(frames, 'frames');
57
+
58
+ const unit_sphere_direction = [];
59
+
60
+ const cam = new OrthographicCamera();
61
+ const rt = new WebGLMultipleRenderTargets(resolution, resolution, 3);
62
+ rt.scissorTest = false;
63
+ rt.stencilBuffer = false;
64
+
65
+ rt.texture[0].name = 'diffuse+alpha';
66
+ rt.texture[1].name = 'normal+depth';
67
+ rt.texture[2].name = 'orm'; // Occlusion, Roughness, Metalness
68
+
69
+
70
+ const renderer = this._renderer;
71
+
72
+ // remember render state
73
+ const _rt = renderer.getRenderTarget();
74
+ const _vp = new Vector4();
75
+ renderer.getViewport(_vp);
76
+ const _autoClear = renderer.autoClear;
77
+
78
+ const gl = this._renderer.getContext();
79
+
80
+ // Baking should be done without anti-aliasing, so we make sure to disable it
81
+ const _enabled_antialias = gl.isEnabled(gl.SAMPLE_ALPHA_TO_COVERAGE);
82
+
83
+ if (_enabled_antialias) {
84
+ gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
85
+ }
86
+
87
+
88
+ // set new render state
89
+ renderer.autoClear = false;
90
+ renderer.setRenderTarget(rt);
91
+ renderer.setClearColor(0xFFFFFF, 0);
92
+ renderer.clearColor();
93
+
94
+ const frame_width = resolution / frames;
95
+ const frame_height = resolution / frames;
96
+
97
+ const object_count = objects.length;
98
+
99
+ // construct scene
100
+ const scene = new Scene();
101
+
102
+ scene.autoUpdate = false;
103
+ scene.matrixAutoUpdate = false;
104
+ scene.matrixWorldNeedsUpdate = false;
105
+
106
+ const id = new ImpostorDescription();
107
+ id.frame_count = frames;
108
+ id.resolution = resolution;
109
+
110
+ /**
111
+ * Fired when all the rendering is done to indicate that cleanup should happen
112
+ * @type {Signal}
113
+ */
114
+ const cleanup_signal = new Signal();
115
+
116
+ /**
117
+ *
118
+ * @type {HashMap<Material, BakeShaderStandard>}
119
+ */
120
+ const bake_material_map = new HashMap({
121
+ keyHashFunction: computeMaterialHash,
122
+ keyEqualityFunction: computeMaterialEquality
123
+ });
124
+
125
+
126
+ for (let k = 0; k < object_count; k++) {
127
+ const object = objects[k];
128
+
129
+ const source_mesh = object.mesh;
130
+
131
+ // ensure tangents are generated
132
+ // buffer_geometry_ensure_tangents(source_mesh.geometry);
133
+
134
+ const source_material = source_mesh.material;
135
+
136
+ // obtain material for baking
137
+ const bake_material = bake_material_map.getOrCompute(source_material, (source_material) => {
138
+
139
+ const bake_material = new BakeShaderStandard();
140
+
141
+ // prepare bake material to match the source material
142
+ prepare_bake_material(bake_material, source_material, cleanup_signal);
143
+
144
+ cleanup_signal.addOne(bake_material.dispose, bake_material);
145
+
146
+ id.source_material_count++;
147
+
148
+ return bake_material;
149
+ });
150
+
151
+
152
+ const geometry = source_mesh.geometry;
153
+ const mesh = new Mesh(geometry, bake_material);
154
+
155
+ mesh.matrixAutoUpdate = false;
156
+ mesh.matrixWorldNeedsUpdate = false;
157
+ mesh.frustumCulled = false;
158
+
159
+ mat4.copy(mesh.matrixWorld.elements, object.transform);
160
+
161
+ scene.add(mesh);
162
+
163
+ // update stats
164
+ id.source_geometry_polygon_count += geometry.getIndex().count / 3;
165
+ id.source_geometry_vertex_count += geometry.getAttribute('position').count;
166
+ id.source_instance_count++;
167
+ }
168
+
169
+
170
+ const bake_material_array = collectIteratorValueToArray([], bake_material_map.values());
171
+
172
+ // traverse octahedron
173
+ const max_frame_index = frames - 1;
174
+
175
+ for (let i = 0; i < frames; i++) {
176
+ const octahedron_u = max_frame_index > 0 ? (i / max_frame_index) : 0;
177
+
178
+ for (let j = 0; j < frames; j++) {
179
+
180
+ const octahedron_v = max_frame_index > 0 ? (j / max_frame_index) : 0;
181
+
182
+ // compute vector direction where to place camera
183
+ encoder.uv_to_unit(unit_sphere_direction, [octahedron_u, octahedron_v]);
184
+
185
+ // offset by the radius of the sphere
186
+ const camera_px = distance * unit_sphere_direction[0];
187
+ const camera_py = distance * unit_sphere_direction[1];
188
+ const camera_pz = distance * unit_sphere_direction[2];
189
+
190
+ // console.log(`UV:${octahedron_u.toFixed(2)},${octahedron_v.toFixed(2)}\t V3:${unit_sphere_direction.map(n => n.toFixed(2)).join(', ')}`);
191
+
192
+ // construct projection matrix
193
+ cam.left = -distance;
194
+ cam.right = distance;
195
+ cam.top = distance;
196
+ cam.bottom = -distance;
197
+ cam.near = 0;
198
+ cam.far = distance * 2;
199
+
200
+ cam.position.set(
201
+ camera_px,
202
+ camera_py,
203
+ camera_pz
204
+ );
205
+
206
+ cam.lookAt(0, 0, 0);
207
+ cam.updateProjectionMatrix();
208
+
209
+ // update materials
210
+ for (let k = 0; k < bake_material_array.length; k++) {
211
+ const mat = bake_material_array[k];
212
+
213
+ mat.uniforms.projection_params.value.set(
214
+ 0, 0, 0, 1 / cam.far
215
+ );
216
+ mat.uniformsNeedUpdate = true;
217
+ }
218
+
219
+ //TODO consider doing super-sampling for some textures for better-looking results
220
+
221
+ /*
222
+ TODO it's possible to reduce bounding sphere in some cases where thin geometry would produce 0 texels
223
+ in the actual render, but we can't know that without doing the rendering first.
224
+ Consider doing a cheap pre-pass, rendering out pixels and checking if we can crop the bounding sphere further
225
+ */
226
+
227
+ renderer.setViewport(i * frame_width, j * frame_height, frame_width, frame_height);
228
+ renderer.render(scene, cam);
229
+ }
230
+ }
231
+
232
+ /*
233
+ TODO dilate non-color textures to prevent artifacts when sampling around the edges
234
+ */
235
+
236
+ //restore renderer state
237
+ renderer.setRenderTarget(_rt);
238
+ renderer.setViewport(_vp);
239
+ renderer.autoClear = _autoClear;
240
+ if (_enabled_antialias) {
241
+ gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
242
+ }
243
+
244
+ // cleanup
245
+ cleanup_signal.send0();
246
+
247
+ // const sampler = convertTexture2Sampler2D(rt.texture);
248
+
249
+
250
+ id.atlas = Sampler2D.uint8(1, resolution, resolution);
251
+ // id.atlas = sampler;
252
+ id.rt = rt;
253
+
254
+ return id;
255
+ }
256
+
257
+ /**
258
+ *
259
+ * @param {{mesh:ShadedGeometry, transform:mat4}[]} objects objects that should make up the impostor. Baking will be done around the origin, so make sure to position meshes accordingly to make good use of texture space.
260
+ * @param {number} frames how many views to capture for the atlas in each X and Y direction. Setting this to 4 will result in 16 (4*4) views captured, higher value will sacrifice detail but result in transitions being more smooth
261
+ * @param {number} resolution resolution of the final asset, higher = more detail
262
+ * @param {ImpostorCaptureType} type
263
+ * @returns {ImpostorDescription}
264
+ */
265
+ bake({
266
+ objects,
267
+ frames = 12,
268
+ resolution = 1024,
269
+ type = ImpostorCaptureType.FullSphere
270
+ }) {
271
+
272
+ console.time('bake');
273
+
274
+ assert.defined(objects, 'object');
275
+ assert.isNonNegativeInteger(frames, 'frames');
276
+ assert.isNonNegativeInteger(resolution, 'resolution');
277
+ assert.greaterThanOrEqual(frames, 1, 'number of frames must be >= 1');
278
+
279
+ if (frames > resolution) {
280
+ throw new Error(`Frame count(=${frames}) is greater than the resolution(=${resolution}) of the bake texture, packing description is unachievable`);
281
+ }
282
+
283
+ const frame_resolution = resolution / frames;
284
+
285
+ if (frames >= resolution / 4) {
286
+ console.warn(`Bake configuration will produce less frames, this will likely result in a useless impostor that contains too little texel density to properly represent the underlying scene. [frames=${frames}, resolution=${resolution}, resulting density = ${Math.pow(frame_resolution, 2)} texels per frame]`);
287
+ }
288
+
289
+ if (resolution % frames !== 0 && frame_resolution < 16) {
290
+ console.warn(`To get results at low frame resolutions number of frames should be a divisor of resolution, instead got [frames=${frames}, resolution=${resolution}, resolution/frames=${frame_resolution}]`);
291
+ }
292
+
293
+ if (!isPowerOfTwo(resolution)) {
294
+ throw new Error(` 'resolution' must be a power of two, instead was '${resolution}'`);
295
+ }
296
+
297
+ if (frames < 2) {
298
+ console.warn(`Frame count is set to ${frames}, which is too low to be useful. Consider values of 2 and above. A good default is 12`);
299
+ }
300
+
301
+ if (this._renderer === null) {
302
+ throw new Error('No renderer attached. Renderer is required for baking.');
303
+ }
304
+
305
+ const objects_radius = compute_bounding_sphere(objects);
306
+
307
+ // we need to compute bounding sphere around origin of the object
308
+ let encoder;
309
+ if (type === ImpostorCaptureType.FullSphere) {
310
+ encoder = new OctahedralUvEncoder();
311
+ } else if (type === ImpostorCaptureType.Hemisphere) {
312
+ encoder = new HemiOctahedralUvEncoder();
313
+ } else {
314
+ throw new Error(`Unsupported capture mode '${type}'`);
315
+ }
316
+
317
+ const r = this.bake_internal({
318
+ resolution,
319
+ frames,
320
+ objects,
321
+ distance: objects_radius,
322
+ encoder
323
+ });
324
+
325
+ r.capture_type = type;
326
+ r.sphere_radius = objects_radius;
327
+
328
+ console.timeEnd('bake');
329
+
330
+ return r;
331
+ }
332
+ }
@@ -0,0 +1,10 @@
1
+ export const ImpostorCaptureType = {
2
+ /**
3
+ * Capture from all sides
4
+ */
5
+ FullSphere: 0,
6
+ /**
7
+ * Only capture from the top hemisphere, good for things that will never be seen from "below", such as trees, houses etc.
8
+ */
9
+ Hemisphere: 1
10
+ };
@@ -0,0 +1,63 @@
1
+ import { ImpostorCaptureType } from "./ImpostorCaptureType.js";
2
+
3
+ export class ImpostorDescription {
4
+ /**
5
+ * How many XY frames should be captured
6
+ * More frames will result in less distortion, but lower texel density (lower resolution for each individual frame)
7
+ * @type {number}
8
+ */
9
+ frame_count = 16;
10
+
11
+ /**
12
+ * How the impostor is being captured
13
+ * @type {number}
14
+ */
15
+ capture_type = ImpostorCaptureType.FullSphere;
16
+
17
+ /**
18
+ * Resolution of the texture atlas used for baking. All "frames" will be packed here
19
+ * Must be power of two
20
+ * @type {number}
21
+ */
22
+ resolution = 1024;
23
+
24
+ /**
25
+ * Radius of baking sphere, this is centered on origin
26
+ * @type {number}
27
+ */
28
+ sphere_radius = 0;
29
+
30
+ /**
31
+ * Actual baked data
32
+ * @type {Sampler2D|null}
33
+ */
34
+ atlas = null;
35
+
36
+ /**
37
+ * Source scene from which impostor was baked
38
+ * @type {number}
39
+ */
40
+ source_geometry_polygon_count = 0;
41
+ /**
42
+ * @type {number}
43
+ */
44
+ source_geometry_vertex_count = 0;
45
+ /**
46
+ *
47
+ * @type {number}
48
+ */
49
+ source_material_count = 0;
50
+ /**
51
+ *
52
+ * @type {number}
53
+ */
54
+ source_instance_count = 0;
55
+
56
+
57
+ /**
58
+ * TODO: replace with something non-three.js specific
59
+ * @deprecated use non-three.js specific accessors instead
60
+ * @type {THREE.WebGLMultipleRenderTargets}
61
+ */
62
+ rt = null;
63
+ }