@woosh/meep-engine 2.41.0 → 2.42.1

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 (98) hide show
  1. package/core/assert.js +2 -2
  2. package/core/collection/array/array_swap.js +3 -3
  3. package/core/collection/map/AsyncLoadingCache.js +47 -0
  4. package/core/geom/3d/aabb/aabb3_compute_distance_above_plane_max.js +1 -1
  5. package/core/geom/3d/apply_mat4_transform_to_v3_array.js +2 -4
  6. package/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +28 -0
  7. package/core/geom/Quaternion.js +14 -0
  8. package/core/math/statistics/computeSampleSize_Cochran.js +3 -3
  9. package/editor/ecs/component/editors/geom/QuaternionEditor.js +12 -5
  10. package/engine/Engine.js +6 -1
  11. package/engine/EngineBootstrapper.js +2 -1
  12. package/engine/EngineHarness.js +13 -3
  13. package/engine/asset/AssetManager.js +97 -7
  14. package/engine/development/performance/AbstractMetric.js +1 -0
  15. package/engine/development/performance/RingBufferMetric.js +25 -4
  16. package/engine/ecs/EntityBuilder.js +29 -4
  17. package/engine/ecs/transform/Transform.js +23 -3
  18. package/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +17 -1
  19. package/engine/graphics/ecs/decal/v2/Decal.d.ts +11 -0
  20. package/engine/graphics/ecs/decal/v2/Decal.js +50 -0
  21. package/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts +8 -0
  22. package/engine/graphics/ecs/decal/v2/FPDecalSystem.js +201 -0
  23. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +278 -0
  24. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +8 -1
  25. package/engine/graphics/ecs/mesh-v2/allocate_v3.js +37 -0
  26. package/engine/graphics/ecs/mesh-v2/build_three_object.js +4 -0
  27. package/engine/graphics/geometry/MikkT/AddTriToGroup.js +10 -0
  28. package/engine/graphics/geometry/MikkT/AssignRecur.js +84 -0
  29. package/engine/graphics/geometry/MikkT/AvgTSpace.js +38 -0
  30. package/engine/graphics/geometry/MikkT/Build4RuleGroups.js +96 -0
  31. package/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +137 -0
  32. package/engine/graphics/geometry/MikkT/CalcTexArea.js +31 -0
  33. package/engine/graphics/geometry/MikkT/CompareSubGroups.js +26 -0
  34. package/engine/graphics/geometry/MikkT/DegenEpilogue.js +220 -0
  35. package/engine/graphics/geometry/MikkT/DegenPrologue.js +115 -0
  36. package/engine/graphics/geometry/MikkT/EvalTspace.js +128 -0
  37. package/engine/graphics/geometry/MikkT/GenerateInitialVerticesIndexList.js +48 -0
  38. package/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +184 -0
  39. package/engine/graphics/geometry/MikkT/GenerateTSpaces.js +226 -0
  40. package/engine/graphics/geometry/MikkT/GetEdge.js +45 -0
  41. package/engine/graphics/geometry/MikkT/GetNormal.js +16 -0
  42. package/engine/graphics/geometry/MikkT/GetPosition.js +25 -0
  43. package/engine/graphics/geometry/MikkT/GetTexCoord.js +18 -0
  44. package/engine/graphics/geometry/MikkT/InitTriInfo.js +180 -0
  45. package/engine/graphics/geometry/MikkT/Length.js +10 -0
  46. package/engine/graphics/geometry/MikkT/MakeIndex.js +18 -0
  47. package/engine/graphics/geometry/MikkT/MikkTSpace.js +197 -2068
  48. package/engine/graphics/geometry/MikkT/NormalizeSafe.js +21 -0
  49. package/engine/graphics/geometry/MikkT/NotZero.js +10 -0
  50. package/engine/graphics/geometry/MikkT/QuickSort.js +54 -0
  51. package/engine/graphics/geometry/MikkT/QuickSortEdges.js +71 -0
  52. package/engine/graphics/geometry/MikkT/SSubGroup.js +15 -0
  53. package/engine/graphics/geometry/MikkT/STSpace.js +36 -0
  54. package/engine/graphics/geometry/MikkT/constants/GROUP_WITH_ANY.js +1 -0
  55. package/engine/graphics/geometry/MikkT/constants/INTERNAL_RND_SORT_SEED.js +1 -0
  56. package/engine/graphics/geometry/MikkT/constants/MARK_DEGENERATE.js +1 -0
  57. package/engine/graphics/geometry/MikkT/constants/ORIENT_PRESERVING.js +1 -0
  58. package/engine/graphics/geometry/MikkT/constants/QUAD_ONE_DEGEN_TRI.js +1 -0
  59. package/engine/graphics/geometry/MikkT/m_getNormal.js +16 -0
  60. package/engine/graphics/geometry/MikkT/m_getNumFaces.js +8 -0
  61. package/engine/graphics/geometry/MikkT/m_getNumVerticesOfFace.js +11 -0
  62. package/engine/graphics/geometry/MikkT/m_getPosition.js +20 -0
  63. package/engine/graphics/geometry/MikkT/m_getTexCoord.js +16 -0
  64. package/engine/graphics/geometry/MikkT/m_setTSpace.js +35 -0
  65. package/engine/graphics/geometry/MikkT/m_setTSpaceBasic.js +22 -0
  66. package/engine/graphics/geometry/MikkT/malloc.js +16 -0
  67. package/engine/graphics/geometry/MikkT/v3_scale_dot_sub_normalize.js +52 -0
  68. package/engine/graphics/geometry/buffered/computeGeometryEquality.js +1 -1
  69. package/engine/graphics/geometry/buffered/computeGeometryHash.js +1 -1
  70. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +28 -14
  71. package/engine/graphics/impostors/octahedral/ImpostorDescription.js +6 -0
  72. package/engine/graphics/impostors/octahedral/README.md +1 -0
  73. package/engine/graphics/impostors/octahedral/bake/compute_bounding_sphere.js +25 -22
  74. package/engine/graphics/impostors/octahedral/bake/compute_bounding_sphere_radius_only.js +37 -0
  75. package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +30 -1
  76. package/engine/graphics/impostors/octahedral/grid/HemiOctahedralUvEncoder.js +1 -1
  77. package/engine/graphics/impostors/octahedral/prototypeBaker.js +121 -22
  78. package/engine/graphics/impostors/octahedral/shader/BakeShaderStandard.js +46 -7
  79. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.js +349 -0
  80. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderV1.js +74 -0
  81. package/engine/graphics/impostors/octahedral/shader/glsl/v1/common.glsl +209 -0
  82. package/engine/graphics/impostors/octahedral/shader/glsl/v1/flagment.glsl +80 -0
  83. package/engine/graphics/impostors/octahedral/shader/glsl/v1/vertex.glsl +350 -0
  84. package/engine/graphics/micron/render/v1/getTransformedPositionsCached.js +1 -1
  85. package/engine/graphics/render/forward_plus/LightManager.js +17 -7
  86. package/engine/graphics/render/forward_plus/data/TextureBackedMemoryRegion.js +7 -1
  87. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +13 -5
  88. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_PREAMBLE.js +3 -1
  89. package/engine/graphics/render/forward_plus/model/Decal.js +19 -2
  90. package/engine/graphics/render/forward_plus/plugin/MaterialTransformer.js +14 -2
  91. package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +2 -2
  92. package/engine/graphics/texture/sampler/Sampler2D.js +10 -10
  93. package/engine/graphics/texture/sampler/prototypeSamplerFiltering.js +117 -11
  94. package/engine/graphics/texture/sampler/resize/sampler2d_downsample_mipmap.js +66 -0
  95. package/engine/graphics/texture/sampler/sampler2_d_scale_down_lanczos.js +2 -2
  96. package/engine/intelligence/behavior/util/RotationBehavior.js +69 -0
  97. package/generation/example/filters/SampleGroundMoistureFilter.js +5 -5
  98. package/package.json +1 -1
@@ -6,17 +6,56 @@ import { sampler2D_scale_down_lanczos } from "./sampler2_d_scale_down_lanczos.js
6
6
  import { Sampler2D } from "./Sampler2D.js";
7
7
  import { genericResampleSampler2D } from "./genericResampleSampler2D.js";
8
8
  import Vector2 from "../../../../core/geom/Vector2.js";
9
+ import LabelView from "../../../../view/common/LabelView.js";
10
+ import EmptyView from "../../../../view/elements/EmptyView.js";
11
+ import { sampler2d_downsample_mipmap } from "./resize/sampler2d_downsample_mipmap.js";
9
12
 
10
13
  function grid(engine, input, transforms, size = new Vector2(input.width, input.height)) {
11
14
 
15
+ let draw_index = 0;
16
+
12
17
  for (let i = 0; i < transforms.length; i++) {
13
18
  const out = input.clone();
14
19
 
15
20
  out.resize(size.x, size.y);
16
21
 
17
- transforms[i](out, input);
22
+ const params = {
23
+ label: '',
24
+ skip: false
25
+ };
26
+
27
+ try {
28
+ transforms[i](out, input, params);
29
+ } catch (e) {
30
+ // error, skip
31
+ }
32
+
33
+ if (params.skip) {
34
+ continue;
35
+ }
36
+
37
+
38
+ const view = display(engine, out, draw_index * (size.x + 4), 0);
39
+
40
+ if (params.label.length > 0) {
41
+ view.addChild(new LabelView(params.label, {
42
+ css: {
43
+ position: 'absolute',
44
+ fontFamily: 'sans-serif',
45
+ fontWeight: 'bold',
46
+ top: 0,
47
+ left: 0,
48
+ zIndex: 1,
49
+ color: '#000000',
50
+ textShadow: '1px 1px 1px white',
51
+ background: 'rgba(255,255,255,0.7)',
52
+ padding: '2px'
53
+ }
54
+ }));
55
+ }
56
+
57
+ draw_index++;
18
58
 
19
- display(engine, out, i * (size.x + 4), 0);
20
59
  }
21
60
 
22
61
  }
@@ -30,11 +69,23 @@ function display(engine, sampler, x = 0, y = 0) {
30
69
 
31
70
  view.css({
32
71
  position: 'absolute',
33
- top: `${y}px`,
34
- left: `${x}px`
72
+ top: "0",
73
+ left: "0"
35
74
  });
36
75
 
37
- engine.gameView.addChild(view);
76
+ const container = new EmptyView({
77
+ css: {
78
+ position: 'absolute',
79
+ top: `${y}px`,
80
+ left: `${x}px`
81
+ }
82
+ });
83
+ container.size.set(sampler.width, sampler.height);
84
+ container.addChild(view);
85
+
86
+ engine.gameView.addChild(container);
87
+
88
+ return container;
38
89
  }
39
90
 
40
91
 
@@ -44,7 +95,13 @@ new EngineHarness().initialize({
44
95
  }
45
96
  }).then(async engine => {
46
97
 
47
- const asset = await engine.assetManager.promise("data/textures/utility/Lenna.png", 'image');
98
+ // const asset = await engine.assetManager.promise("data/textures/utility/Lenna.png", 'image');
99
+ // const asset = await engine.assetManager.promise("data/textures/utility/uv_map_reference.jpg", 'image');
100
+ const asset = await engine.assetManager.promise("data/textures/utility/image018.jpg", 'image');
101
+ // const asset = await engine.assetManager.promise("data/textures/utility/TESTIMAGES/SAMPLING/8BIT/RGB/2448x2448/SRC/img_2448x2448_3x8bit_SRC_RGB_clips.png", 'image');
102
+ // const asset = await engine.assetManager.promise("data/textures/utility/TESTIMAGES/SAMPLING/8BIT/RGB/2448x2448/SRC/img_2448x2448_3x8bit_SRC_RGB_coins.png", 'image');
103
+ // const asset = await engine.assetManager.promise("data/models/LowPolyTownshipSet/Small_house/diffuse_512.png", 'image');
104
+ // const asset = await engine.assetManager.promise("data/models/LowPolyTownshipSet/Barrel/diffuse_256.png", 'image');
48
105
  // const asset = await engine.assetManager.promise("data/textures/utility/resolver.jpeg", 'image');
49
106
  // const asset = await engine.assetManager.promise("data/textures/icons/500_skill_icons_02/Skill_nobg/skill_492_noBG.png", 'image');
50
107
  // const asset = await engine.assetManager.promise("data/textures/utility/sampling-test.png", 'image');
@@ -88,7 +145,9 @@ new EngineHarness().initialize({
88
145
 
89
146
  function test_sampling(source, engine) {
90
147
  grid(engine, source, [
91
- (out, input) => {
148
+ (out, input, params) => {
149
+ params.label = 'Nearest';
150
+
92
151
  const sample = [];
93
152
  for (let y = 0; y < out.height; y++) {
94
153
 
@@ -100,7 +159,11 @@ function test_sampling(source, engine) {
100
159
  }
101
160
  }
102
161
  },
103
- (out, input) => {
162
+ (out, input, params) => {
163
+
164
+ params.label = 'Bilinear';
165
+ params.skip = true;
166
+
104
167
  const sample = new Uint8ClampedArray(4);
105
168
  for (let y = 0; y < out.height; y++) {
106
169
 
@@ -112,7 +175,10 @@ function test_sampling(source, engine) {
112
175
  }
113
176
  }
114
177
  },
115
- (out, input) => {
178
+ (out, input, params) => {
179
+ params.label = 'Bicubic';
180
+ params.skip = true;
181
+
116
182
  const sample = new Uint8ClampedArray(4);
117
183
  for (let y = 0; y < out.height; y++) {
118
184
 
@@ -124,7 +190,10 @@ function test_sampling(source, engine) {
124
190
  }
125
191
  }
126
192
  },
127
- (out, input) => {
193
+ (out, input, params) => {
194
+ params.label = 'catmull-rom';
195
+ params.skip = true;
196
+
128
197
  const sample = new Uint8ClampedArray(4);
129
198
  for (let y = 0; y < out.height; y++) {
130
199
 
@@ -135,9 +204,46 @@ function test_sampling(source, engine) {
135
204
  out.set(x, y, sample);
136
205
  }
137
206
  }
207
+ },
208
+ (out, input, params) => {
209
+ params.label = 'mip';
210
+
211
+
212
+ sampler2d_downsample_mipmap(input, out);
213
+
214
+ },
215
+ (out, input, params) => {
216
+ const lobes = 2;
217
+ params.label = `lanczos_${lobes}`;
218
+ params.skip = true;
219
+
220
+ return;
221
+
222
+ sampler2D_scale_down_lanczos(input, out, lobes);
223
+
224
+ },
225
+ (out, input, params) => {
226
+ const lobes = 3;
227
+ params.label = `lanczos_${lobes}`;
228
+
229
+
230
+ sampler2D_scale_down_lanczos(input, out, lobes);
231
+
232
+ },
233
+ (out, input, params) => {
234
+ const lobes = 5;
235
+ params.skip = true;
236
+ params.label = `lanczos_${lobes}`;
237
+
238
+ return;
239
+
240
+ sampler2D_scale_down_lanczos(input, out, lobes);
241
+
138
242
  }
139
243
  ], new Vector2(
140
- 200, 200
244
+ // 100, 100
245
+ 1024, 1024
246
+ // 136, 136
141
247
  ));
142
248
  }
143
249
 
@@ -0,0 +1,66 @@
1
+ import { Sampler2D } from "../Sampler2D.js";
2
+ import { sampler2D_scale_down_linear } from "../sampler2D_scale_down_linear.js";
3
+ import { inverseLerp } from "../../../../../core/math/inverseLerp.js";
4
+ import { lerp } from "../../../../../core/math/lerp.js";
5
+
6
+ /**
7
+ * Emulated how downsampling is done in OpenGL using autogenerated mipmaps with LINEAR_MIPMAP_LINEAR parameters
8
+ * @param {Sampler2D} source
9
+ * @param {Sampler2D} destination
10
+ */
11
+ export function sampler2d_downsample_mipmap(source, destination) {
12
+ let current_mip = source;
13
+ let previous_mip = source;
14
+
15
+ const itemSize = current_mip.itemSize;
16
+
17
+ const dest_width = destination.width;
18
+ const dest_height = destination.height;
19
+
20
+ while (current_mip.width > dest_width && current_mip.width > 1) {
21
+ const new_w = Math.floor(current_mip.width * 0.5);
22
+ const new_h = Math.floor(current_mip.height * 0.5);
23
+
24
+
25
+ const out = new Sampler2D(new current_mip.data.constructor(new_w * new_h * itemSize), itemSize, new_w, new_h);
26
+
27
+ sampler2D_scale_down_linear(current_mip, out);
28
+
29
+ previous_mip = current_mip;
30
+ current_mip = out;
31
+ }
32
+
33
+ const mip_mix = inverseLerp(previous_mip.width, current_mip.width, dest_width);
34
+
35
+ const m1_hp_x = 0.5 / previous_mip.width;
36
+ const m1_hp_y = 0.5 / previous_mip.height;
37
+
38
+ const m0_hp_x = 0.5 / current_mip.width;
39
+ const m0_hp_y = 0.5 / current_mip.height;
40
+
41
+ const m1_sx = (previous_mip.width + 1) / (previous_mip.width);
42
+ const m1_sy = (previous_mip.height + 1) / (previous_mip.height);
43
+
44
+ const m0_sx = (current_mip.width + 1) / (current_mip.width);
45
+ const m0_sy = (current_mip.height + 1) / (current_mip.height);
46
+
47
+ for (let y = 0; y < dest_height; y++) {
48
+ const v = y / (dest_height - 1);
49
+
50
+ for (let x = 0; x < dest_width; x++) {
51
+
52
+ const u = x / (dest_width - 1);
53
+
54
+ for (let i = 0; i < itemSize; i++) {
55
+
56
+ const c0 = previous_mip.sampleChannelBilinearUV(u * m1_sx - m1_hp_x, v * m1_sy - m1_hp_y, i);
57
+ const c1 = current_mip.sampleChannelBilinearUV(u * m0_sx - m0_hp_x, v * m0_sy - m0_hp_y, i);
58
+
59
+ const c_out = lerp(c0, c1, mip_mix);
60
+
61
+ destination.data[(y * dest_width + x) * itemSize + i] = c_out;
62
+ }
63
+ }
64
+ }
65
+
66
+ }
@@ -77,12 +77,12 @@ export function sampler2D_scale_down_lanczos(source, destination, lobes = 3) {
77
77
 
78
78
  for (v = 0; v < d_h; v++) {
79
79
 
80
- const center_y = (v) * ratio_y;
80
+ const center_y = (v) * ratio_y - 0.5;
81
81
  const source_y0 = max2(Math.ceil(center_y - range2_y), 0);
82
82
  const source_y1 = min2(Math.floor(center_y + range2_y), source_height - 1);
83
83
 
84
84
  for (u = 0; u < d_w; u++) {
85
- const center_x = (u) * ratio_x;
85
+ const center_x = (u) * ratio_x - 0.5;
86
86
 
87
87
  const source_x0 = max2(Math.ceil(center_x - range2_x), 0);
88
88
  const source_x1 = min2(Math.floor(center_x + range2_x), source_width - 1);
@@ -0,0 +1,69 @@
1
+ import { EntityBehavior } from "../ecs/EntityBehavior.js";
2
+ import { Transform } from "../../../ecs/transform/Transform.js";
3
+ import Vector3 from "../../../../core/geom/Vector3.js";
4
+ import Quaternion from "../../../../core/geom/Quaternion.js";
5
+ import { BehaviorStatus } from "../BehaviorStatus.js";
6
+
7
+ export class RotationBehavior extends EntityBehavior {
8
+ constructor() {
9
+ super();
10
+
11
+ /**
12
+ * Rotation per second
13
+ * @type {Vector3}
14
+ */
15
+ this.axis = new Vector3();
16
+ this.speed = 1;
17
+
18
+ this.angle = 0;
19
+
20
+ this.__offset = new Quaternion();
21
+ }
22
+
23
+ static fromJSON(j) {
24
+ const r = new RotationBehavior();
25
+
26
+ r.fromJSON(j);
27
+
28
+ return r;
29
+ }
30
+
31
+ /**
32
+ *
33
+ * @param axis
34
+ * @param speed
35
+ * @param angle
36
+ */
37
+ fromJSON({ axis = Vector3.up, speed = 1, angle = 0 }) {
38
+ this.axis.fromJSON(axis);
39
+ this.speed = speed;
40
+ this.angle = angle;
41
+ }
42
+
43
+ initialize(context) {
44
+ super.initialize(context);
45
+
46
+
47
+ const transform = this.ecd.getComponent(this.entity, Transform);
48
+ this.__offset.copy(transform.rotation);
49
+ }
50
+
51
+ tick(timeDelta) {
52
+ /**
53
+ *
54
+ * @type {Transform}
55
+ */
56
+ const transform = this.ecd.getComponent(this.entity, Transform);
57
+
58
+ const angleDelta = this.speed * timeDelta;
59
+
60
+ this.angle += angleDelta;
61
+
62
+ const r = transform.rotation;
63
+
64
+ r.fromAxisAngle(this.axis, this.angle);
65
+ r.multiplyQuaternions(r, this.__offset);
66
+
67
+ return BehaviorStatus.Running;
68
+ }
69
+ }
@@ -9,8 +9,8 @@ import { CellFilterMax2 } from "../../filtering/numeric/math/CellFilterMax2.js";
9
9
  import { CellFilterSmoothStep } from "../../filtering/numeric/math/CellFilterSmoothStep.js";
10
10
  import { MohGridLayers } from "../../../../../generator/MohGridLayers.js";
11
11
  import { CellFilterDilate } from "../../filtering/numeric/complex/CellFilterDilate.js";
12
- import { DEG2RAD } from "three/src/math/MathUtils.js";
13
12
  import { CellFilterCache } from "../../filtering/numeric/CellFilterCache.js";
13
+ import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
14
14
 
15
15
  const fReadHeight = CellFilterSampleLayerLinear.from(MirGridLayers.Heights);
16
16
 
@@ -27,8 +27,8 @@ export const SampleGroundMoistureFilter = CellFilterMax2.from(
27
27
  CellFilterMax2.from(
28
28
  // flat areas are more likely to be keep moisture
29
29
  CellFilterSmoothStep.from(
30
- CellFilterLiteralFloat.from(40 * DEG2RAD),
31
- CellFilterLiteralFloat.from(10 * DEG2RAD),
30
+ CellFilterLiteralFloat.from(40 * DEG_TO_RAD),
31
+ CellFilterLiteralFloat.from(10 * DEG_TO_RAD),
32
32
  SLOPE
33
33
  ),
34
34
  // water accumulates in crevices
@@ -60,8 +60,8 @@ export const SampleGroundMoistureFilter = CellFilterMax2.from(
60
60
  NOTE: we don't have to worry about directly of the slope, as water always starts at 0, so any slope will be positive in effect
61
61
  */
62
62
  CellFilterSmoothStep.from(
63
- CellFilterLiteralFloat.from(35 * DEG2RAD),
64
- CellFilterLiteralFloat.from(10 * DEG2RAD),
63
+ CellFilterLiteralFloat.from(35 * DEG_TO_RAD),
64
+ CellFilterLiteralFloat.from(10 * DEG_TO_RAD),
65
65
  SLOPE
66
66
  ),
67
67
  )
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.41.0",
8
+ "version": "2.42.1",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",