@luma.gl/engine 9.2.6 → 9.3.0-alpha.11

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 (198) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +11 -5
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +83 -47
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
  6. package/dist/animation-loop/make-animation-loop.js +8 -1
  7. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  9. package/dist/animation-loop/request-animation-frame.js +23 -6
  10. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  11. package/dist/compute/computation.d.ts +3 -7
  12. package/dist/compute/computation.d.ts.map +1 -1
  13. package/dist/compute/computation.js +16 -13
  14. package/dist/compute/computation.js.map +1 -1
  15. package/dist/compute/swap.d.ts +2 -0
  16. package/dist/compute/swap.d.ts.map +1 -1
  17. package/dist/compute/swap.js +10 -5
  18. package/dist/compute/swap.js.map +1 -1
  19. package/dist/debug/debug-framebuffer.d.ts +9 -4
  20. package/dist/debug/debug-framebuffer.d.ts.map +1 -1
  21. package/dist/debug/debug-framebuffer.js +91 -45
  22. package/dist/debug/debug-framebuffer.js.map +1 -1
  23. package/dist/dist.dev.js +2767 -1344
  24. package/dist/dist.min.js +326 -211
  25. package/dist/dynamic-texture/dynamic-texture.d.ts +102 -0
  26. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  27. package/dist/dynamic-texture/dynamic-texture.js +558 -0
  28. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  29. package/dist/dynamic-texture/texture-data.d.ts +144 -0
  30. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  31. package/dist/dynamic-texture/texture-data.js +208 -0
  32. package/dist/dynamic-texture/texture-data.js.map +1 -0
  33. package/dist/geometries/cone-geometry.d.ts +3 -1
  34. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  35. package/dist/geometries/cone-geometry.js.map +1 -1
  36. package/dist/geometries/cube-geometry.js +7 -7
  37. package/dist/geometries/cube-geometry.js.map +1 -1
  38. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  39. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  40. package/dist/geometries/cylinder-geometry.js.map +1 -1
  41. package/dist/geometries/ico-sphere-geometry.js +3 -1
  42. package/dist/geometries/ico-sphere-geometry.js.map +1 -1
  43. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  44. package/dist/geometry/gpu-geometry.js +11 -3
  45. package/dist/geometry/gpu-geometry.js.map +1 -1
  46. package/dist/index.cjs +2620 -1267
  47. package/dist/index.cjs.map +4 -4
  48. package/dist/index.d.ts +20 -6
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +12 -4
  51. package/dist/index.js.map +1 -1
  52. package/dist/material/material-factory.d.ts +73 -0
  53. package/dist/material/material-factory.d.ts.map +1 -0
  54. package/dist/material/material-factory.js +111 -0
  55. package/dist/material/material-factory.js.map +1 -0
  56. package/dist/material/material.d.ts +84 -0
  57. package/dist/material/material.d.ts.map +1 -0
  58. package/dist/material/material.js +176 -0
  59. package/dist/material/material.js.map +1 -0
  60. package/dist/model/model.d.ts +47 -16
  61. package/dist/model/model.d.ts.map +1 -1
  62. package/dist/model/model.js +148 -71
  63. package/dist/model/model.js.map +1 -1
  64. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  65. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  66. package/dist/model/split-uniforms-and-bindings.js +2 -2
  67. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  68. package/dist/models/billboard-texture-model.d.ts +8 -5
  69. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  70. package/dist/models/billboard-texture-model.js +79 -25
  71. package/dist/models/billboard-texture-model.js.map +1 -1
  72. package/dist/models/billboard-texture-module.d.ts +1 -1
  73. package/dist/models/billboard-texture-module.js +1 -1
  74. package/dist/models/clip-space.js +7 -7
  75. package/dist/models/directional-light-model.d.ts +7 -0
  76. package/dist/models/directional-light-model.d.ts.map +1 -0
  77. package/dist/models/directional-light-model.js +23 -0
  78. package/dist/models/directional-light-model.js.map +1 -0
  79. package/dist/models/light-model-utils.d.ts +69 -0
  80. package/dist/models/light-model-utils.d.ts.map +1 -0
  81. package/dist/models/light-model-utils.js +395 -0
  82. package/dist/models/light-model-utils.js.map +1 -0
  83. package/dist/models/point-light-model.d.ts +7 -0
  84. package/dist/models/point-light-model.d.ts.map +1 -0
  85. package/dist/models/point-light-model.js +22 -0
  86. package/dist/models/point-light-model.js.map +1 -0
  87. package/dist/models/spot-light-model.d.ts +7 -0
  88. package/dist/models/spot-light-model.d.ts.map +1 -0
  89. package/dist/models/spot-light-model.js +23 -0
  90. package/dist/models/spot-light-model.js.map +1 -0
  91. package/dist/modules/picking/color-picking.d.ts +5 -9
  92. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  93. package/dist/modules/picking/color-picking.js +122 -115
  94. package/dist/modules/picking/color-picking.js.map +1 -1
  95. package/dist/modules/picking/index-picking.d.ts +4 -4
  96. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  97. package/dist/modules/picking/index-picking.js +36 -16
  98. package/dist/modules/picking/index-picking.js.map +1 -1
  99. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  100. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  101. package/dist/modules/picking/legacy-color-picking.js +7 -0
  102. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  103. package/dist/modules/picking/picking-manager.d.ts +29 -3
  104. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  105. package/dist/modules/picking/picking-manager.js +188 -41
  106. package/dist/modules/picking/picking-manager.js.map +1 -1
  107. package/dist/modules/picking/picking-uniforms.d.ts +13 -12
  108. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  109. package/dist/modules/picking/picking-uniforms.js +27 -14
  110. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  111. package/dist/modules/picking/picking.d.ts +25 -0
  112. package/dist/modules/picking/picking.d.ts.map +1 -0
  113. package/dist/modules/picking/picking.js +18 -0
  114. package/dist/modules/picking/picking.js.map +1 -0
  115. package/dist/passes/get-fragment-shader.js +12 -27
  116. package/dist/passes/get-fragment-shader.js.map +1 -1
  117. package/dist/passes/shader-pass-renderer.d.ts +5 -7
  118. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  119. package/dist/passes/shader-pass-renderer.js +16 -42
  120. package/dist/passes/shader-pass-renderer.js.map +1 -1
  121. package/dist/scenegraph/group-node.d.ts +5 -0
  122. package/dist/scenegraph/group-node.d.ts.map +1 -1
  123. package/dist/scenegraph/group-node.js +12 -0
  124. package/dist/scenegraph/group-node.js.map +1 -1
  125. package/dist/scenegraph/model-node.d.ts +2 -2
  126. package/dist/scenegraph/model-node.d.ts.map +1 -1
  127. package/dist/scenegraph/model-node.js.map +1 -1
  128. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  129. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  130. package/dist/scenegraph/scenegraph-node.js +23 -15
  131. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  132. package/dist/shader-inputs.d.ts +9 -7
  133. package/dist/shader-inputs.d.ts.map +1 -1
  134. package/dist/shader-inputs.js +90 -13
  135. package/dist/shader-inputs.js.map +1 -1
  136. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  137. package/dist/utils/buffer-layout-order.js +12 -2
  138. package/dist/utils/buffer-layout-order.js.map +1 -1
  139. package/dist/utils/shader-module-utils.d.ts +7 -0
  140. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  141. package/dist/utils/shader-module-utils.js +46 -0
  142. package/dist/utils/shader-module-utils.js.map +1 -0
  143. package/package.json +6 -6
  144. package/src/animation-loop/animation-loop.ts +89 -50
  145. package/src/animation-loop/make-animation-loop.ts +14 -5
  146. package/src/animation-loop/request-animation-frame.ts +32 -6
  147. package/src/compute/computation.ts +32 -17
  148. package/src/compute/swap.ts +13 -7
  149. package/src/debug/debug-framebuffer.ts +139 -61
  150. package/src/dynamic-texture/dynamic-texture.ts +730 -0
  151. package/src/dynamic-texture/texture-data.ts +336 -0
  152. package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +1 -1
  153. package/src/geometries/cone-geometry.ts +6 -1
  154. package/src/geometries/cube-geometry.ts +7 -7
  155. package/src/geometries/cylinder-geometry.ts +5 -1
  156. package/src/geometries/ico-sphere-geometry.ts +3 -1
  157. package/src/geometry/gpu-geometry.ts +11 -3
  158. package/src/index.ts +38 -8
  159. package/src/material/material-factory.ts +157 -0
  160. package/src/material/material.ts +254 -0
  161. package/src/model/model.ts +196 -93
  162. package/src/model/split-uniforms-and-bindings.ts +8 -6
  163. package/src/models/billboard-texture-model.ts +90 -29
  164. package/src/models/billboard-texture-module.ts +1 -1
  165. package/src/models/clip-space.ts +7 -7
  166. package/src/models/directional-light-model.ts +32 -0
  167. package/src/models/light-model-utils.ts +587 -0
  168. package/src/models/point-light-model.ts +31 -0
  169. package/src/models/spot-light-model.ts +32 -0
  170. package/src/modules/picking/color-picking.ts +123 -122
  171. package/src/modules/picking/index-picking.ts +36 -16
  172. package/src/modules/picking/legacy-color-picking.ts +8 -0
  173. package/src/modules/picking/picking-manager.ts +252 -50
  174. package/src/modules/picking/picking-uniforms.ts +39 -24
  175. package/src/modules/picking/picking.ts +22 -0
  176. package/src/passes/get-fragment-shader.ts +12 -27
  177. package/src/passes/shader-pass-renderer.ts +25 -48
  178. package/src/scenegraph/group-node.ts +16 -0
  179. package/src/scenegraph/model-node.ts +2 -2
  180. package/src/scenegraph/scenegraph-node.ts +27 -16
  181. package/src/shader-inputs.ts +167 -26
  182. package/src/utils/buffer-layout-order.ts +18 -2
  183. package/src/utils/shader-module-utils.ts +65 -0
  184. package/dist/async-texture/async-texture.d.ts +0 -166
  185. package/dist/async-texture/async-texture.d.ts.map +0 -1
  186. package/dist/async-texture/async-texture.js +0 -386
  187. package/dist/async-texture/async-texture.js.map +0 -1
  188. package/dist/factories/pipeline-factory.d.ts +0 -37
  189. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  190. package/dist/factories/pipeline-factory.js +0 -181
  191. package/dist/factories/pipeline-factory.js.map +0 -1
  192. package/dist/factories/shader-factory.d.ts +0 -22
  193. package/dist/factories/shader-factory.d.ts.map +0 -1
  194. package/dist/factories/shader-factory.js +0 -88
  195. package/dist/factories/shader-factory.js.map +0 -1
  196. package/src/async-texture/async-texture.ts +0 -551
  197. package/src/factories/pipeline-factory.ts +0 -224
  198. package/src/factories/shader-factory.ts +0 -103
@@ -12,12 +12,15 @@ import {Device, Resource, Buffer, Framebuffer, Texture} from '@luma.gl/core';
12
12
  * @note the two resources can be destroyed by calling `destroy()`
13
13
  */
14
14
  export class Swap<T extends Resource<any>> {
15
+ id: string;
16
+
15
17
  /** The current resource - usually the source for renders or computations */
16
18
  current: T;
17
19
  /** The next resource - usually the target/destination for transforms / computations */
18
20
  next: T;
19
21
 
20
- constructor(props: {current: T; next: T}) {
22
+ constructor(props: {current: T; next: T; id?: string}) {
23
+ this.id = props.id || 'swap';
21
24
  this.current = props.current;
22
25
  this.next = props.next;
23
26
  }
@@ -41,14 +44,17 @@ export class SwapFramebuffers extends Swap<Framebuffer> {
41
44
  constructor(device: Device, props: FramebufferProps) {
42
45
  props = {...props};
43
46
 
47
+ const {width = 1, height = 1} = props;
48
+
44
49
  let colorAttachments = props.colorAttachments?.map(colorAttachment =>
45
50
  typeof colorAttachment !== 'string'
46
51
  ? colorAttachment
47
52
  : device.createTexture({
53
+ id: `${props.id}-texture-0`,
48
54
  format: colorAttachment,
49
55
  usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_SRC | Texture.COPY_DST,
50
- width: 1,
51
- height: 1
56
+ width,
57
+ height
52
58
  })
53
59
  );
54
60
 
@@ -58,11 +64,11 @@ export class SwapFramebuffers extends Swap<Framebuffer> {
58
64
  typeof colorAttachment !== 'string'
59
65
  ? colorAttachment
60
66
  : device.createTexture({
67
+ id: `${props.id}-texture-1`,
61
68
  format: colorAttachment,
62
- usage:
63
- Texture.TEXTURE | Texture.COPY_SRC | Texture.COPY_DST | Texture.RENDER_ATTACHMENT,
64
- width: 1,
65
- height: 1
69
+ usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_SRC | Texture.COPY_DST,
70
+ width,
71
+ height
66
72
  })
67
73
  );
68
74
 
@@ -2,75 +2,153 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {Framebuffer, Texture} from '@luma.gl/core';
6
- // import {copyTextureToImage} from '../debug/copy-texture-to-image';
5
+ import type {Device, Framebuffer, RenderPass, Texture} from '@luma.gl/core';
7
6
 
8
- /** Only works with 1st device? */
9
- let canvas: HTMLCanvasElement | null = null;
10
- let ctx: CanvasRenderingContext2D | null = null;
11
- // let targetImage: HTMLImageElement | null = null;
7
+ const DEBUG_FRAMEBUFFER_STATE_KEY = '__debugFramebufferState';
8
+ const DEFAULT_MARGIN_PX = 8;
12
9
 
13
- /** Debug utility to draw FBO contents onto screen */
14
- // eslint-disable-next-line
10
+ type DebugFramebufferOptions = {
11
+ id: string;
12
+ minimap?: boolean;
13
+ opaque?: boolean;
14
+ top?: string;
15
+ left?: string;
16
+ rgbaScale?: number;
17
+ };
18
+
19
+ type DebugFramebufferState = {
20
+ flushing: boolean;
21
+ queuedFramebuffers: Framebuffer[];
22
+ };
23
+
24
+ /**
25
+ * Debug utility to blit queued offscreen framebuffers into the default framebuffer
26
+ * without CPU readback. Currently implemented for WebGL only.
27
+ */
15
28
  export function debugFramebuffer(
16
- fbo: Framebuffer | Texture,
17
- {
18
- id,
19
- minimap,
20
- opaque,
21
- top = '0',
22
- left = '0',
23
- rgbaScale = 1
24
- }: {
25
- id: string;
26
- minimap?: boolean;
27
- opaque?: boolean;
28
- top?: string;
29
- left?: string;
30
- rgbaScale?: number;
29
+ renderPass: RenderPass,
30
+ source: Framebuffer | Texture | null,
31
+ options: DebugFramebufferOptions
32
+ ): void {
33
+ if (renderPass.device.type !== 'webgl') {
34
+ return;
35
+ }
36
+
37
+ const state = getDebugFramebufferState(renderPass.device);
38
+ if (state.flushing) {
39
+ return;
40
+ }
41
+
42
+ if (isDefaultRenderPass(renderPass)) {
43
+ flushDebugFramebuffers(renderPass, options, state);
44
+ return;
31
45
  }
32
- ) {
33
- if (!canvas) {
34
- canvas = document.createElement('canvas');
35
- canvas.id = id;
36
- canvas.title = id;
37
- canvas.style.zIndex = '100';
38
- canvas.style.position = 'absolute';
39
- canvas.style.top = top; // ⚠️
40
- canvas.style.left = left; // ⚠️
41
- canvas.style.border = 'blue 5px solid';
42
- canvas.style.transform = 'scaleY(-1)';
43
- document.body.appendChild(canvas);
44
-
45
- ctx = canvas.getContext('2d');
46
- // targetImage = new Image();
46
+
47
+ if (source && isFramebuffer(source) && source.handle !== null) {
48
+ if (!state.queuedFramebuffers.includes(source)) {
49
+ state.queuedFramebuffers.push(source);
50
+ }
47
51
  }
52
+ }
48
53
 
49
- // const canvasHeight = (minimap ? 2 : 1) * fbo.height;
50
- if (canvas.width !== fbo.width || canvas.height !== fbo.height) {
51
- canvas.width = fbo.width / 2;
52
- canvas.height = fbo.height / 2;
53
- canvas.style.width = '400px';
54
- canvas.style.height = '400px';
54
+ function flushDebugFramebuffers(
55
+ renderPass: RenderPass,
56
+ options: DebugFramebufferOptions,
57
+ state: DebugFramebufferState
58
+ ): void {
59
+ if (state.queuedFramebuffers.length === 0) {
60
+ return;
55
61
  }
56
62
 
57
- // const image = copyTextureToImage(fbo, {targetMaxHeight: 100, targetImage});
58
- // ctx.drawImage(image, 0, 0);
59
-
60
- const color = fbo.device.readPixelsToArrayWebGL(fbo);
61
- const imageData = ctx?.createImageData(fbo.width, fbo.height);
62
- if (imageData) {
63
- // Full map
64
- const offset = 0;
65
- // if (color.some((v) => v > 0)) {
66
- // console.error('THERE IS NON-ZERO DATA IN THE FBO!');
67
- // }
68
- for (let i = 0; i < color.length; i += 4) {
69
- imageData.data[offset + i + 0] = color[i + 0] * rgbaScale;
70
- imageData.data[offset + i + 1] = color[i + 1] * rgbaScale;
71
- imageData.data[offset + i + 2] = color[i + 2] * rgbaScale;
72
- imageData.data[offset + i + 3] = opaque ? 255 : color[i + 3] * rgbaScale;
63
+ const webglDevice = renderPass.device as Device & {gl: WebGL2RenderingContext};
64
+ const {gl} = webglDevice;
65
+ const previousReadFramebuffer = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
66
+ const previousDrawFramebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
67
+ const [targetWidth, targetHeight] = renderPass.device
68
+ .getDefaultCanvasContext()
69
+ .getDrawingBufferSize();
70
+
71
+ let topPx = parseCssPixel(options.top, DEFAULT_MARGIN_PX);
72
+ const leftPx = parseCssPixel(options.left, DEFAULT_MARGIN_PX);
73
+
74
+ state.flushing = true;
75
+ try {
76
+ for (const framebuffer of state.queuedFramebuffers) {
77
+ const [targetX0, targetY0, targetX1, targetY1, previewHeight] = getOverlayRect({
78
+ framebuffer,
79
+ targetWidth,
80
+ targetHeight,
81
+ topPx,
82
+ leftPx,
83
+ minimap: options.minimap
84
+ });
85
+
86
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer.handle as WebGLFramebuffer | null);
87
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
88
+ gl.blitFramebuffer(
89
+ 0,
90
+ 0,
91
+ framebuffer.width,
92
+ framebuffer.height,
93
+ targetX0,
94
+ targetY0,
95
+ targetX1,
96
+ targetY1,
97
+ gl.COLOR_BUFFER_BIT,
98
+ gl.NEAREST
99
+ );
100
+
101
+ topPx += previewHeight + DEFAULT_MARGIN_PX;
73
102
  }
74
- ctx?.putImageData(imageData, 0, 0);
103
+ } finally {
104
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, previousReadFramebuffer);
105
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, previousDrawFramebuffer);
106
+ state.flushing = false;
107
+ }
108
+ }
109
+
110
+ function getOverlayRect(options: {
111
+ framebuffer: Framebuffer;
112
+ targetWidth: number;
113
+ targetHeight: number;
114
+ topPx: number;
115
+ leftPx: number;
116
+ minimap?: boolean;
117
+ }): [number, number, number, number, number] {
118
+ const {framebuffer, targetWidth, targetHeight, topPx, leftPx, minimap} = options;
119
+ const maxWidth = minimap ? Math.max(Math.floor(targetWidth / 4), 1) : targetWidth;
120
+ const maxHeight = minimap ? Math.max(Math.floor(targetHeight / 4), 1) : targetHeight;
121
+ const scale = Math.min(maxWidth / framebuffer.width, maxHeight / framebuffer.height);
122
+ const previewWidth = Math.max(Math.floor(framebuffer.width * scale), 1);
123
+ const previewHeight = Math.max(Math.floor(framebuffer.height * scale), 1);
124
+ const targetX0 = leftPx;
125
+ const targetY0 = Math.max(targetHeight - topPx - previewHeight, 0);
126
+ const targetX1 = targetX0 + previewWidth;
127
+ const targetY1 = targetY0 + previewHeight;
128
+ return [targetX0, targetY0, targetX1, targetY1, previewHeight];
129
+ }
130
+
131
+ function getDebugFramebufferState(device: Device): DebugFramebufferState {
132
+ device.userData[DEBUG_FRAMEBUFFER_STATE_KEY] ||= {
133
+ flushing: false,
134
+ queuedFramebuffers: []
135
+ } satisfies DebugFramebufferState;
136
+ return device.userData[DEBUG_FRAMEBUFFER_STATE_KEY] as DebugFramebufferState;
137
+ }
138
+
139
+ function isFramebuffer(value: Framebuffer | Texture): value is Framebuffer {
140
+ return 'colorAttachments' in value;
141
+ }
142
+
143
+ function isDefaultRenderPass(renderPass: RenderPass): boolean {
144
+ const framebuffer = renderPass.props.framebuffer as {handle?: unknown} | null;
145
+ return !framebuffer || framebuffer.handle === null;
146
+ }
147
+
148
+ function parseCssPixel(value: string | undefined, defaultValue: number): number {
149
+ if (!value) {
150
+ return defaultValue;
75
151
  }
152
+ const parsedValue = Number.parseInt(value, 10);
153
+ return Number.isFinite(parsedValue) ? parsedValue : defaultValue;
76
154
  }