react-x11 1.2.0 → 2.0.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 (158) hide show
  1. package/README.md +330 -81
  2. package/package.json +132 -14
  3. package/src/ClickToComponent.js +9 -2
  4. package/src/DevToolsIntegration.js +440 -32
  5. package/src/Reconciler.js +463 -165
  6. package/src/a11y.js +1299 -0
  7. package/src/acceleratorhooks.js +86 -0
  8. package/src/accelerators.js +128 -0
  9. package/src/activate.js +168 -0
  10. package/src/anchor.js +360 -0
  11. package/src/appcontext.js +161 -0
  12. package/src/appearance.js +748 -0
  13. package/src/appearancehooks.js +96 -0
  14. package/src/apphooks.js +73 -0
  15. package/src/application.js +748 -0
  16. package/src/atspi.js +1953 -0
  17. package/src/bus.js +545 -0
  18. package/src/bushooks.js +136 -0
  19. package/src/clientmessage.js +140 -0
  20. package/src/clipboard.js +121 -0
  21. package/src/components/Button.js +148 -33
  22. package/src/components/Checkbox.js +66 -32
  23. package/src/components/Dialog.js +96 -29
  24. package/src/components/FileDialog.js +491 -0
  25. package/src/components/Icon.js +329 -0
  26. package/src/components/Menu.js +797 -133
  27. package/src/components/PasswordInput.js +434 -0
  28. package/src/components/ProgressBar.js +91 -12
  29. package/src/components/Radio.js +78 -35
  30. package/src/components/Select.js +255 -65
  31. package/src/components/Slider.js +102 -41
  32. package/src/components/SplitPane.js +206 -0
  33. package/src/components/Switch.js +100 -22
  34. package/src/components/Table.js +529 -0
  35. package/src/components/Tabs.js +211 -0
  36. package/src/components/Tooltip.js +528 -54
  37. package/src/components/anchor.js +154 -107
  38. package/src/components/change.js +34 -0
  39. package/src/components/dnd.js +103 -0
  40. package/src/components/index.js +16 -3
  41. package/src/components/keys.js +17 -20
  42. package/src/components/scribble.js +247 -0
  43. package/src/components/theme.js +350 -42
  44. package/src/components/typeahead.js +12 -2
  45. package/src/compose.js +868 -0
  46. package/src/compositing.js +224 -0
  47. package/src/dbusmenu.js +384 -0
  48. package/src/debug.d.ts +63 -0
  49. package/src/debug.js +642 -0
  50. package/src/decorations.js +486 -0
  51. package/src/desktopsettings.js +209 -0
  52. package/src/desktopsettingshooks.js +62 -0
  53. package/src/dnd.js +1725 -0
  54. package/src/editmenu.js +272 -0
  55. package/src/errors.js +98 -0
  56. package/src/events.js +1406 -162
  57. package/src/extensions.js +45 -0
  58. package/src/filedialog.js +375 -0
  59. package/src/filedialoghooks.js +132 -0
  60. package/src/fonthooks.js +64 -0
  61. package/src/fonts.js +301 -0
  62. package/src/foreignnodes.js +519 -0
  63. package/src/frame/child.js +33 -0
  64. package/src/frame/childmain.js +274 -0
  65. package/src/frame/env.js +140 -0
  66. package/src/frame/index.js +444 -0
  67. package/src/frame/lifecycle.js +67 -0
  68. package/src/frame/protocol.js +179 -0
  69. package/src/frames.js +73 -0
  70. package/src/glbackend.js +114 -0
  71. package/src/glnodes.js +125 -67
  72. package/src/globalmenu.js +683 -0
  73. package/src/host.d.ts +88 -0
  74. package/src/host.js +42 -0
  75. package/src/idle.js +490 -0
  76. package/src/idlehooks.js +100 -0
  77. package/src/imagesource.js +349 -0
  78. package/src/index.d.ts +475 -0
  79. package/src/index.js +83 -9
  80. package/src/inputtime.js +206 -0
  81. package/src/jsx-dev-runtime.d.ts +21 -0
  82. package/src/jsx-dev-runtime.js +2 -0
  83. package/src/jsx-runtime.d.ts +39 -0
  84. package/src/jsx-runtime.js +6 -0
  85. package/src/keyboard.js +256 -0
  86. package/src/keyboardstate.js +278 -0
  87. package/src/keyboardstatehooks.js +58 -0
  88. package/src/keysyms.d.ts +149 -0
  89. package/src/keysyms.js +270 -0
  90. package/src/locale.js +170 -0
  91. package/src/localehooks.js +47 -0
  92. package/src/menuitem.js +223 -0
  93. package/src/node.d.ts +597 -0
  94. package/src/node.js +44 -0
  95. package/src/nodes.js +9546 -690
  96. package/src/ntk.d.ts +44 -0
  97. package/src/ntk.js +25 -0
  98. package/src/paintcache.js +366 -0
  99. package/src/palette.js +380 -0
  100. package/src/pastestate.js +66 -0
  101. package/src/portal.js +461 -0
  102. package/src/priority.js +26 -0
  103. package/src/refresh/index.d.ts +40 -0
  104. package/src/refresh/index.js +122 -0
  105. package/src/refresh/loader.d.ts +37 -0
  106. package/src/refresh/loader.js +401 -0
  107. package/src/refresh/register.d.ts +5 -0
  108. package/src/refresh/register.js +13 -0
  109. package/src/registry.js +232 -0
  110. package/src/scale.js +626 -0
  111. package/src/scalehooks.js +27 -0
  112. package/src/screencolor.js +640 -0
  113. package/src/screencolorhooks.js +101 -0
  114. package/src/screens.js +754 -0
  115. package/src/screenshooks.js +137 -0
  116. package/src/startup.js +302 -0
  117. package/src/style.d.ts +126 -0
  118. package/src/style.js +33 -0
  119. package/src/styles.js +1482 -6
  120. package/src/svgnodes.js +306 -0
  121. package/src/testing/a11y.js +484 -0
  122. package/src/testing/components.js +414 -0
  123. package/src/testing/events.js +407 -0
  124. package/src/testing/harness.js +455 -0
  125. package/src/testing/index.d.ts +558 -0
  126. package/src/testing/index.js +85 -0
  127. package/src/testing/mock-app.js +463 -0
  128. package/src/testing/pixels.js +152 -0
  129. package/src/testing/queries.js +224 -0
  130. package/src/textrange.js +83 -0
  131. package/src/textselection.js +439 -0
  132. package/src/trace-registry.js +63 -0
  133. package/src/transfer.js +93 -0
  134. package/src/types/appearance.d.ts +84 -0
  135. package/src/types/application.d.ts +173 -0
  136. package/src/types/components.d.ts +820 -0
  137. package/src/types/dbus.d.ts +177 -0
  138. package/src/types/elements.d.ts +897 -0
  139. package/src/types/events.d.ts +549 -0
  140. package/src/types/filedialog.d.ts +260 -0
  141. package/src/types/fonts.d.ts +124 -0
  142. package/src/types/frame.d.ts +146 -0
  143. package/src/types/globalmenu.d.ts +42 -0
  144. package/src/types/nodes.d.ts +199 -0
  145. package/src/types/screencolor.d.ts +84 -0
  146. package/src/types/style.d.ts +434 -0
  147. package/src/types/system.d.ts +287 -0
  148. package/src/windowid.js +151 -0
  149. package/src/windowstate.js +393 -0
  150. package/src/xsettings.js +336 -0
  151. package/src/yoga.d.ts +55 -0
  152. package/src/yoga.js +135 -0
  153. package/src/components/Canvas3D.js +0 -28
  154. package/src/geometry3d.js +0 -223
  155. package/src/pointer3d.js +0 -158
  156. package/src/raycast3d.js +0 -146
  157. package/src/richnodes.js +0 -436
  158. package/src/scene3d.js +0 -683
package/src/geometry3d.js DELETED
@@ -1,223 +0,0 @@
1
- // Geometry generators for the primitive `<*Geometry>` elements. Each takes
2
- // the same `args` tuple as its three.js counterpart and returns plain
3
- // arrays — positions, normals, uvs and a triangle index — which the display
4
- // list compiler turns into one server-side list (src/scene3d.js).
5
-
6
- /**
7
- * Build a parametric surface as a (segmentsX+1) x (segmentsY+1) vertex grid.
8
- * `point(u, v)` returns [position, normal] for u, v in 0..1.
9
- */
10
- function grid(segmentsX, segmentsY, point) {
11
- const positions = [];
12
- const normals = [];
13
- const uvs = [];
14
- const index = [];
15
- for (let iy = 0; iy <= segmentsY; iy++) {
16
- for (let ix = 0; ix <= segmentsX; ix++) {
17
- const u = ix / segmentsX;
18
- const v = iy / segmentsY;
19
- const [p, n] = point(u, v);
20
- positions.push(p[0], p[1], p[2]);
21
- normals.push(n[0], n[1], n[2]);
22
- uvs.push(u, 1 - v);
23
- }
24
- }
25
- const stride = segmentsX + 1;
26
- for (let iy = 0; iy < segmentsY; iy++) {
27
- for (let ix = 0; ix < segmentsX; ix++) {
28
- const a = iy * stride + ix;
29
- const b = a + 1;
30
- const c = a + stride;
31
- const d = c + 1;
32
- index.push(a, c, b, b, c, d);
33
- }
34
- }
35
- return { positions, normals, uvs, index };
36
- }
37
-
38
- function merge(parts) {
39
- const out = { positions: [], normals: [], uvs: [], index: [] };
40
- for (const part of parts) {
41
- const offset = out.positions.length / 3;
42
- out.positions.push(...part.positions);
43
- out.normals.push(...part.normals);
44
- out.uvs.push(...part.uvs);
45
- for (const i of part.index) out.index.push(i + offset);
46
- }
47
- return out;
48
- }
49
-
50
- export function planeGeometry([width = 1, height = 1, ws = 1, hs = 1] = []) {
51
- return grid(ws, hs, (u, v) => [
52
- [(u - 0.5) * width, (0.5 - v) * height, 0],
53
- [0, 0, 1],
54
- ]);
55
- }
56
-
57
- export function boxGeometry([
58
- width = 1,
59
- height = 1,
60
- depth = 1,
61
- ws = 1,
62
- hs = 1,
63
- ds = 1,
64
- ] = []) {
65
- // one grid per face, oriented by (right, up, normal) basis vectors
66
- const face = (right, up, normal, w, h, segU, segV, offset) =>
67
- grid(segU, segV, (u, v) => {
68
- const su = (u - 0.5) * w;
69
- const sv = (0.5 - v) * h;
70
- return [
71
- [
72
- right[0] * su + up[0] * sv + normal[0] * offset,
73
- right[1] * su + up[1] * sv + normal[1] * offset,
74
- right[2] * su + up[2] * sv + normal[2] * offset,
75
- ],
76
- normal,
77
- ];
78
- });
79
-
80
- const x = width / 2;
81
- const y = height / 2;
82
- const z = depth / 2;
83
- return merge([
84
- face([0, 0, 1], [0, 1, 0], [-1, 0, 0], depth, height, ds, hs, x), // -x
85
- face([0, 0, -1], [0, 1, 0], [1, 0, 0], depth, height, ds, hs, x), // +x
86
- face([1, 0, 0], [0, 0, -1], [0, 1, 0], width, depth, ws, ds, y), // +y
87
- face([1, 0, 0], [0, 0, 1], [0, -1, 0], width, depth, ws, ds, y), // -y
88
- face([-1, 0, 0], [0, 1, 0], [0, 0, -1], width, height, ws, hs, z), // -z
89
- face([1, 0, 0], [0, 1, 0], [0, 0, 1], width, height, ws, hs, z), // +z
90
- ]);
91
- }
92
-
93
- export function sphereGeometry([radius = 1, ws = 32, hs = 16] = []) {
94
- return grid(ws, hs, (u, v) => {
95
- const theta = u * Math.PI * 2;
96
- const phi = v * Math.PI;
97
- const n = [
98
- -Math.sin(phi) * Math.cos(theta),
99
- Math.cos(phi),
100
- Math.sin(phi) * Math.sin(theta),
101
- ];
102
- return [[n[0] * radius, n[1] * radius, n[2] * radius], n];
103
- });
104
- }
105
-
106
- export function cylinderGeometry([
107
- radiusTop = 1,
108
- radiusBottom = 1,
109
- height = 1,
110
- radialSegments = 32,
111
- heightSegments = 1,
112
- openEnded = false,
113
- ] = []) {
114
- const half = height / 2;
115
- const slope = (radiusBottom - radiusTop) / height;
116
- const side = grid(radialSegments, heightSegments, (u, v) => {
117
- const theta = u * Math.PI * 2;
118
- const radius = radiusTop + (radiusBottom - radiusTop) * v;
119
- const cos = Math.cos(theta);
120
- const sin = Math.sin(theta);
121
- const len = Math.hypot(1, slope) || 1;
122
- return [
123
- [radius * sin, half - v * height, radius * cos],
124
- [sin / len, slope / len, cos / len],
125
- ];
126
- });
127
- if (openEnded) return side;
128
-
129
- // caps: a triangle fan around the centre, expressed as a 1-segment grid
130
- const cap = (radius, y, dir) =>
131
- grid(radialSegments, 1, (u, v) => {
132
- const theta = u * Math.PI * 2 * dir;
133
- const r = v * radius;
134
- return [
135
- [r * Math.sin(theta), y, r * Math.cos(theta)],
136
- [0, dir, 0],
137
- ];
138
- });
139
- const parts = [side];
140
- if (radiusTop > 0) parts.push(cap(radiusTop, half, 1));
141
- if (radiusBottom > 0) parts.push(cap(radiusBottom, -half, -1));
142
- return merge(parts);
143
- }
144
-
145
- export function torusGeometry([
146
- radius = 1,
147
- tube = 0.4,
148
- radialSegments = 12,
149
- tubularSegments = 48,
150
- ] = []) {
151
- return grid(tubularSegments, radialSegments, (u, v) => {
152
- const theta = u * Math.PI * 2;
153
- const phi = v * Math.PI * 2;
154
- const cx = radius * Math.cos(theta);
155
- const cz = radius * Math.sin(theta);
156
- const n = [
157
- Math.cos(theta) * Math.cos(phi),
158
- Math.sin(phi),
159
- Math.sin(theta) * Math.cos(phi),
160
- ];
161
- return [[cx + tube * n[0], tube * n[1], cz + tube * n[2]], n];
162
- });
163
- }
164
-
165
- export const GEOMETRY_BUILDERS = {
166
- boxGeometry,
167
- planeGeometry,
168
- sphereGeometry,
169
- cylinderGeometry,
170
- torusGeometry,
171
- };
172
-
173
- /** `<bufferGeometry position={…} normal={…} uv={…} index={…} />` */
174
- export function bufferGeometry(props) {
175
- const positions = props.position ?? props.positions ?? [];
176
- const count = positions.length / 3;
177
- let normals = props.normal ?? props.normals;
178
- const index = props.index ?? null;
179
- if (!normals) normals = faceNormals(positions, index, count);
180
- return {
181
- positions,
182
- normals,
183
- uvs: props.uv ?? props.uvs ?? new Array(count * 2).fill(0),
184
- index,
185
- };
186
- }
187
-
188
- /** Flat normals from the triangles themselves, averaged per vertex. */
189
- function faceNormals(positions, index, count) {
190
- const normals = new Array(count * 3).fill(0);
191
- const tri = index ?? Array.from({ length: count }, (_, i) => i);
192
- for (let i = 0; i + 2 < tri.length; i += 3) {
193
- const [a, b, c] = [tri[i], tri[i + 1], tri[i + 2]];
194
- const ax = positions[a * 3];
195
- const ay = positions[a * 3 + 1];
196
- const az = positions[a * 3 + 2];
197
- const ux = positions[b * 3] - ax;
198
- const uy = positions[b * 3 + 1] - ay;
199
- const uz = positions[b * 3 + 2] - az;
200
- const vx = positions[c * 3] - ax;
201
- const vy = positions[c * 3 + 1] - ay;
202
- const vz = positions[c * 3 + 2] - az;
203
- const nx = uy * vz - uz * vy;
204
- const ny = uz * vx - ux * vz;
205
- const nz = ux * vy - uy * vx;
206
- for (const v of [a, b, c]) {
207
- normals[v * 3] += nx;
208
- normals[v * 3 + 1] += ny;
209
- normals[v * 3 + 2] += nz;
210
- }
211
- }
212
- for (let i = 0; i < normals.length; i += 3) {
213
- const len = Math.hypot(normals[i], normals[i + 1], normals[i + 2]);
214
- if (len > 0) {
215
- normals[i] /= len;
216
- normals[i + 1] /= len;
217
- normals[i + 2] /= len;
218
- } else {
219
- normals[i + 1] = 1;
220
- }
221
- }
222
- return normals;
223
- }
package/src/pointer3d.js DELETED
@@ -1,158 +0,0 @@
1
- // Pointer events for the 3D scene: X pointer events on the <glarea> window,
2
- // raycast against the CPU-side geometry (raycast3d.js), dispatched to the
3
- // mesh they hit and bubbled up its ancestors.
4
- //
5
- // The surface owns one X window, so this is a second, small event pipeline
6
- // beside the 2D EventManager rather than part of it: the hit test is a ray,
7
- // not a rect, and the events carry 3D information.
8
- import {
9
- runWithPriority,
10
- DiscreteEventPriority,
11
- ContinuousEventPriority,
12
- } from './priority.js';
13
- import { hasPointerHandlers, raycast } from './raycast3d.js';
14
-
15
- /** Does anything in this subtree listen for pointer events? */
16
- export function sceneWantsPointer(nodes) {
17
- for (const node of nodes) {
18
- if (!node.isObject3D) continue;
19
- if (hasPointerHandlers(node)) return true;
20
- if (sceneWantsPointer(node.children)) return true;
21
- }
22
- return false;
23
- }
24
-
25
- /** The dispatch path: the mesh that was hit, then its ancestors. */
26
- function bubblePath(node, surface) {
27
- const path = [];
28
- for (let n = node; n && n !== surface; n = n.parent) path.push(n);
29
- return path;
30
- }
31
-
32
- export class ScenePointer {
33
- constructor(surface) {
34
- this.surface = surface;
35
- this.hovered = null;
36
- this.pressed = null;
37
- this.attached = false;
38
- }
39
-
40
- attach(wnd) {
41
- if (this.attached || typeof wnd.on !== 'function') return;
42
- this.attached = true;
43
- wnd.on('mousedown', (ev) => this._onPointer('pointerdown', ev));
44
- wnd.on('mouseup', (ev) => this._onPointer('pointerup', ev));
45
- wnd.on('mousemove', (ev) => this._onPointer('pointermove', ev));
46
- wnd.on('mouseout', () => this._onLeave());
47
- }
48
-
49
- /** The nearest hit under the pointer, or null. */
50
- _pick(ev) {
51
- const camera = this.surface.scene.camera;
52
- if (!camera) return null;
53
- const [hit] = raycast(this.surface, ev.x, ev.y, camera);
54
- return hit ?? null;
55
- }
56
-
57
- _makeEvent(type, hit, native, target) {
58
- let stopped = false;
59
- return {
60
- type,
61
- target,
62
- object: hit?.object ?? null,
63
- point: hit?.point ?? null,
64
- distance: hit?.distance ?? null,
65
- face: hit?.face ?? null,
66
- uv: hit?.uv ?? null,
67
- x: native?.x,
68
- y: native?.y,
69
- nativeEvent: native,
70
- surface: this.surface,
71
- stopPropagation() {
72
- stopped = true;
73
- },
74
- get propagationStopped() {
75
- return stopped;
76
- },
77
- };
78
- }
79
-
80
- /** Call `on<Name>` along the bubble path until one stops propagation. */
81
- _dispatch(name, hit, native, node = hit?.object) {
82
- if (!node) return null;
83
- const prop = `on${name[0].toUpperCase()}${name.slice(1)}`;
84
- let event = null;
85
- for (const target of bubblePath(node, this.surface)) {
86
- const handler = target.props?.[prop];
87
- if (typeof handler !== 'function') continue;
88
- event = event ?? this._makeEvent(name.toLowerCase(), hit, native, target);
89
- event.target = target;
90
- handler(event);
91
- if (event.propagationStopped) break;
92
- }
93
- return event;
94
- }
95
-
96
- _onPointer(type, native) {
97
- const priority =
98
- type === 'pointermove' ? ContinuousEventPriority : DiscreteEventPriority;
99
- runWithPriority(priority, () => {
100
- const hit = this._pick(native);
101
- if (type === 'pointermove') {
102
- this._updateHover(hit, native);
103
- if (hit) this._dispatch('pointerMove', hit, native);
104
- return;
105
- }
106
- if (type === 'pointerdown') {
107
- this.pressed = hit?.object ?? null;
108
- if (hit) this._dispatch('pointerDown', hit, native);
109
- else
110
- this.surface.props.onPointerMissed?.(
111
- this._makeEvent('pointermissed', null, native, null),
112
- );
113
- return;
114
- }
115
- // pointerup: a click is down and up on the same object
116
- if (hit) this._dispatch('pointerUp', hit, native);
117
- if (hit && this.pressed === hit.object) {
118
- this._dispatch('click', hit, native);
119
- }
120
- this.pressed = null;
121
- });
122
- }
123
-
124
- /** enter/leave, diffed against the object hovered last time. */
125
- _updateHover(hit, native) {
126
- const next = hit?.object ?? null;
127
- if (next === this.hovered) return;
128
- const previous = this.hovered;
129
- this.hovered = next;
130
- if (previous && !previous.destroyed) {
131
- this._dispatch('pointerOut', null, native, previous);
132
- }
133
- if (next) this._dispatch('pointerOver', hit, native);
134
- this._applyCursor(next);
135
- }
136
-
137
- _applyCursor(node) {
138
- const wnd = this.surface.window;
139
- if (typeof wnd?.setCursor !== 'function') return;
140
- const cursor = node?.props?.cursor ?? null;
141
- if (cursor === this._cursor) return;
142
- this._cursor = cursor;
143
- wnd.setCursor(cursor);
144
- }
145
-
146
- _onLeave() {
147
- if (!this.hovered) return;
148
- runWithPriority(ContinuousEventPriority, () => {
149
- this._updateHover(null, null);
150
- });
151
- }
152
-
153
- /** A node left the tree: drop references to it. */
154
- forget(node) {
155
- if (this.hovered === node) this.hovered = null;
156
- if (this.pressed === node) this.pressed = null;
157
- }
158
- }
package/src/raycast3d.js DELETED
@@ -1,146 +0,0 @@
1
- // Client-side picking for the 3D scene.
2
- //
3
- // There is no GPU picking here: reading pixels back over the protocol is a
4
- // round trip per event, and on XQuartz GL output is not even readable
5
- // through GetImage. So the ray is cast against the CPU-side geometry — the
6
- // same arrays the display lists were compiled from — the way three.js does
7
- // it. Only meshes that (or whose ancestors) have pointer handlers take part,
8
- // which is r3f's one optimization that matters.
9
- import {
10
- invert,
11
- multiply,
12
- transformDirection,
13
- transformPoint,
14
- } from './mat4.js';
15
-
16
- const EPSILON = 1e-8;
17
-
18
- function normalize([x, y, z]) {
19
- const len = Math.hypot(x, y, z) || 1;
20
- return [x / len, y / len, z / len];
21
- }
22
-
23
- /**
24
- * Möller–Trumbore. Returns the ray parameter at the intersection, or null.
25
- * `dir` need not be unit length: `t` is then in the same units as `dir`,
26
- * which is what keeps object-space hits comparable in world space.
27
- */
28
- function intersectTriangle(origin, dir, a, b, c) {
29
- const e1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
30
- const e2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
31
- const p = [
32
- dir[1] * e2[2] - dir[2] * e2[1],
33
- dir[2] * e2[0] - dir[0] * e2[2],
34
- dir[0] * e2[1] - dir[1] * e2[0],
35
- ];
36
- const det = e1[0] * p[0] + e1[1] * p[1] + e1[2] * p[2];
37
- if (Math.abs(det) < EPSILON) return null;
38
- const inv = 1 / det;
39
- const t0 = [origin[0] - a[0], origin[1] - a[1], origin[2] - a[2]];
40
- const u = (t0[0] * p[0] + t0[1] * p[1] + t0[2] * p[2]) * inv;
41
- if (u < 0 || u > 1) return null;
42
- const q = [
43
- t0[1] * e1[2] - t0[2] * e1[1],
44
- t0[2] * e1[0] - t0[0] * e1[2],
45
- t0[0] * e1[1] - t0[1] * e1[0],
46
- ];
47
- const v = (dir[0] * q[0] + dir[1] * q[1] + dir[2] * q[2]) * inv;
48
- if (v < 0 || u + v > 1) return null;
49
- const t = (e2[0] * q[0] + e2[1] * q[1] + e2[2] * q[2]) * inv;
50
- return t > EPSILON ? { t, u, v } : null;
51
- }
52
-
53
- /**
54
- * The world-space ray through a pixel of the surface.
55
- * @returns {{origin: number[], direction: number[]}}
56
- */
57
- export function rayThrough(x, y, { width, height, projection, view }) {
58
- const inverse = invert(multiply(projection, view));
59
- if (!inverse) return null;
60
- const ndcX = (2 * x) / width - 1;
61
- const ndcY = 1 - (2 * y) / height;
62
- const near = transformPoint(inverse, [ndcX, ndcY, -1]);
63
- const far = transformPoint(inverse, [ndcX, ndcY, 1]);
64
- return {
65
- origin: near,
66
- direction: normalize([
67
- far[0] - near[0],
68
- far[1] - near[1],
69
- far[2] - near[2],
70
- ]),
71
- };
72
- }
73
-
74
- const POINTER_PROPS = [
75
- 'onClick',
76
- 'onPointerDown',
77
- 'onPointerUp',
78
- 'onPointerMove',
79
- 'onPointerOver',
80
- 'onPointerOut',
81
- ];
82
-
83
- export const hasPointerHandlers = (node) =>
84
- POINTER_PROPS.some((name) => typeof node.props?.[name] === 'function');
85
-
86
- /** Meshes worth testing: they, or an ancestor, listen for pointer events. */
87
- function pickable(nodes, inherited, out = []) {
88
- for (const node of nodes) {
89
- if (!node.isObject3D || !node.visible) continue;
90
- const listening = inherited || hasPointerHandlers(node);
91
- if (listening && node.kind === 'mesh' && node.geometry) out.push(node);
92
- pickable(node.children, listening, out);
93
- }
94
- return out;
95
- }
96
-
97
- /**
98
- * Intersect the scene under `surface` with the ray through pixel (x, y).
99
- * `world` matrices come from the last rendered frame.
100
- * @returns {Array<{object, distance, point, face}>} nearest first
101
- */
102
- export function raycast(surface, x, y, camera) {
103
- const ray = rayThrough(x, y, camera);
104
- if (!ray) return [];
105
- const hits = [];
106
-
107
- for (const mesh of pickable(surface.children, false)) {
108
- const world = mesh._world;
109
- const toObject = world ? invert(world) : null;
110
- if (!toObject) continue;
111
- const origin = transformPoint(toObject, ray.origin);
112
- const direction = transformDirection(toObject, ray.direction);
113
- const { positions, index } = mesh.geometry.data();
114
- const count = index ? index.length : positions.length / 3;
115
- const vertex = (i) => {
116
- const v = index ? index[i] : i;
117
- return [positions[v * 3], positions[v * 3 + 1], positions[v * 3 + 2]];
118
- };
119
-
120
- let best = null;
121
- for (let i = 0; i + 2 < count; i += 3) {
122
- const hit = intersectTriangle(
123
- origin,
124
- direction,
125
- vertex(i),
126
- vertex(i + 1),
127
- vertex(i + 2),
128
- );
129
- if (hit && (!best || hit.t < best.t)) best = { ...hit, face: i / 3 };
130
- }
131
- if (!best) continue;
132
- hits.push({
133
- object: mesh,
134
- distance: best.t,
135
- point: [
136
- ray.origin[0] + ray.direction[0] * best.t,
137
- ray.origin[1] + ray.direction[1] * best.t,
138
- ray.origin[2] + ray.direction[2] * best.t,
139
- ],
140
- face: best.face,
141
- uv: [best.u, best.v],
142
- });
143
- }
144
-
145
- return hits.sort((a, b) => a.distance - b.distance);
146
- }