react-babylon-map 0.0.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 (211) hide show
  1. package/.claude/settings.local.json +78 -0
  2. package/demo.html +161 -0
  3. package/dist/cjs/main.js +520 -0
  4. package/dist/es/main.mjs +20 -0
  5. package/dist/es/main.mjs.map +1 -0
  6. package/dist/es/main10.mjs +33 -0
  7. package/dist/es/main10.mjs.map +1 -0
  8. package/dist/es/main11.mjs +12 -0
  9. package/dist/es/main11.mjs.map +1 -0
  10. package/dist/es/main12.mjs +14 -0
  11. package/dist/es/main12.mjs.map +1 -0
  12. package/dist/es/main13.mjs +12 -0
  13. package/dist/es/main13.mjs.map +1 -0
  14. package/dist/es/main14.mjs +5 -0
  15. package/dist/es/main14.mjs.map +1 -0
  16. package/dist/es/main15.mjs +12 -0
  17. package/dist/es/main15.mjs.map +1 -0
  18. package/dist/es/main16.mjs +25 -0
  19. package/dist/es/main16.mjs.map +1 -0
  20. package/dist/es/main17.mjs +54 -0
  21. package/dist/es/main17.mjs.map +1 -0
  22. package/dist/es/main18.mjs +88 -0
  23. package/dist/es/main18.mjs.map +1 -0
  24. package/dist/es/main19.mjs +18 -0
  25. package/dist/es/main19.mjs.map +1 -0
  26. package/dist/es/main2.mjs +9 -0
  27. package/dist/es/main2.mjs.map +1 -0
  28. package/dist/es/main20.mjs +21 -0
  29. package/dist/es/main20.mjs.map +1 -0
  30. package/dist/es/main21.mjs +61 -0
  31. package/dist/es/main21.mjs.map +1 -0
  32. package/dist/es/main3.mjs +46 -0
  33. package/dist/es/main3.mjs.map +1 -0
  34. package/dist/es/main4.mjs +23 -0
  35. package/dist/es/main4.mjs.map +1 -0
  36. package/dist/es/main5.mjs +69 -0
  37. package/dist/es/main5.mjs.map +1 -0
  38. package/dist/es/main6.mjs +35 -0
  39. package/dist/es/main6.mjs.map +1 -0
  40. package/dist/es/main7.mjs +65 -0
  41. package/dist/es/main7.mjs.map +1 -0
  42. package/dist/es/main8.mjs +14 -0
  43. package/dist/es/main8.mjs.map +1 -0
  44. package/dist/es/main9.mjs +26 -0
  45. package/dist/es/main9.mjs.map +1 -0
  46. package/dist/maplibre/cjs/main.js +520 -0
  47. package/dist/maplibre/es/main.mjs +20 -0
  48. package/dist/maplibre/es/main.mjs.map +1 -0
  49. package/dist/maplibre/es/main10.mjs +33 -0
  50. package/dist/maplibre/es/main10.mjs.map +1 -0
  51. package/dist/maplibre/es/main11.mjs +12 -0
  52. package/dist/maplibre/es/main11.mjs.map +1 -0
  53. package/dist/maplibre/es/main12.mjs +14 -0
  54. package/dist/maplibre/es/main12.mjs.map +1 -0
  55. package/dist/maplibre/es/main13.mjs +12 -0
  56. package/dist/maplibre/es/main13.mjs.map +1 -0
  57. package/dist/maplibre/es/main14.mjs +5 -0
  58. package/dist/maplibre/es/main14.mjs.map +1 -0
  59. package/dist/maplibre/es/main15.mjs +12 -0
  60. package/dist/maplibre/es/main15.mjs.map +1 -0
  61. package/dist/maplibre/es/main16.mjs +25 -0
  62. package/dist/maplibre/es/main16.mjs.map +1 -0
  63. package/dist/maplibre/es/main17.mjs +54 -0
  64. package/dist/maplibre/es/main17.mjs.map +1 -0
  65. package/dist/maplibre/es/main18.mjs +88 -0
  66. package/dist/maplibre/es/main18.mjs.map +1 -0
  67. package/dist/maplibre/es/main19.mjs +18 -0
  68. package/dist/maplibre/es/main19.mjs.map +1 -0
  69. package/dist/maplibre/es/main2.mjs +9 -0
  70. package/dist/maplibre/es/main2.mjs.map +1 -0
  71. package/dist/maplibre/es/main20.mjs +61 -0
  72. package/dist/maplibre/es/main20.mjs.map +1 -0
  73. package/dist/maplibre/es/main21.mjs +21 -0
  74. package/dist/maplibre/es/main21.mjs.map +1 -0
  75. package/dist/maplibre/es/main3.mjs +46 -0
  76. package/dist/maplibre/es/main3.mjs.map +1 -0
  77. package/dist/maplibre/es/main4.mjs +23 -0
  78. package/dist/maplibre/es/main4.mjs.map +1 -0
  79. package/dist/maplibre/es/main5.mjs +69 -0
  80. package/dist/maplibre/es/main5.mjs.map +1 -0
  81. package/dist/maplibre/es/main6.mjs +35 -0
  82. package/dist/maplibre/es/main6.mjs.map +1 -0
  83. package/dist/maplibre/es/main7.mjs +65 -0
  84. package/dist/maplibre/es/main7.mjs.map +1 -0
  85. package/dist/maplibre/es/main8.mjs +14 -0
  86. package/dist/maplibre/es/main8.mjs.map +1 -0
  87. package/dist/maplibre/es/main9.mjs +26 -0
  88. package/dist/maplibre/es/main9.mjs.map +1 -0
  89. package/dist/maplibre/types/api/canvas-props.d.ts +9 -0
  90. package/dist/maplibre/types/api/coordinates.d.ts +13 -0
  91. package/dist/maplibre/types/api/coords-to-vector-3.d.ts +3 -0
  92. package/dist/maplibre/types/api/coords.d.ts +5 -0
  93. package/dist/maplibre/types/api/index.d.ts +7 -0
  94. package/dist/maplibre/types/api/near-coordinates.d.ts +13 -0
  95. package/dist/maplibre/types/api/use-map.d.ts +3 -0
  96. package/dist/maplibre/types/api/vector-3-to-coords.d.ts +2 -0
  97. package/dist/maplibre/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
  98. package/dist/maplibre/types/core/canvas-in-layer/use-render.d.ts +15 -0
  99. package/dist/maplibre/types/core/canvas-in-layer/use-root.d.ts +11 -0
  100. package/dist/maplibre/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
  101. package/dist/maplibre/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
  102. package/dist/maplibre/types/core/canvas-overlay/render.d.ts +1 -0
  103. package/dist/maplibre/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
  104. package/dist/maplibre/types/core/coords-to-matrix.d.ts +9 -0
  105. package/dist/maplibre/types/core/earth-radius.d.ts +1 -0
  106. package/dist/maplibre/types/core/generic-map.d.ts +49 -0
  107. package/dist/maplibre/types/core/matrix-utils.d.ts +7 -0
  108. package/dist/maplibre/types/core/sync-camera.d.ts +7 -0
  109. package/dist/maplibre/types/core/use-babylon-map.d.ts +32 -0
  110. package/dist/maplibre/types/core/use-coords-to-matrix.d.ts +6 -0
  111. package/dist/maplibre/types/core/use-coords.d.ts +5 -0
  112. package/dist/maplibre/types/core/use-function.d.ts +1 -0
  113. package/dist/maplibre/types/maplibre/canvas.d.ts +4 -0
  114. package/dist/maplibre/types/maplibre.index.d.ts +4 -0
  115. package/dist/types/api/canvas-props.d.ts +9 -0
  116. package/dist/types/api/coordinates.d.ts +13 -0
  117. package/dist/types/api/coords-to-vector-3.d.ts +3 -0
  118. package/dist/types/api/coords.d.ts +5 -0
  119. package/dist/types/api/index.d.ts +7 -0
  120. package/dist/types/api/near-coordinates.d.ts +13 -0
  121. package/dist/types/api/use-map.d.ts +3 -0
  122. package/dist/types/api/vector-3-to-coords.d.ts +2 -0
  123. package/dist/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
  124. package/dist/types/core/canvas-in-layer/use-render.d.ts +15 -0
  125. package/dist/types/core/canvas-in-layer/use-root.d.ts +11 -0
  126. package/dist/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
  127. package/dist/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
  128. package/dist/types/core/canvas-overlay/render.d.ts +1 -0
  129. package/dist/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
  130. package/dist/types/core/coords-to-matrix.d.ts +9 -0
  131. package/dist/types/core/earth-radius.d.ts +1 -0
  132. package/dist/types/core/generic-map.d.ts +49 -0
  133. package/dist/types/core/matrix-utils.d.ts +7 -0
  134. package/dist/types/core/sync-camera.d.ts +7 -0
  135. package/dist/types/core/use-babylon-map.d.ts +32 -0
  136. package/dist/types/core/use-coords-to-matrix.d.ts +6 -0
  137. package/dist/types/core/use-coords.d.ts +5 -0
  138. package/dist/types/core/use-function.d.ts +1 -0
  139. package/dist/types/mapbox/canvas.d.ts +4 -0
  140. package/dist/types/mapbox.index.d.ts +4 -0
  141. package/package.json +58 -0
  142. package/plan.md +719 -0
  143. package/src/api/canvas-props.ts +10 -0
  144. package/src/api/coordinates.tsx +83 -0
  145. package/src/api/coords-to-vector-3.ts +39 -0
  146. package/src/api/coords.tsx +6 -0
  147. package/src/api/index.ts +7 -0
  148. package/src/api/near-coordinates.tsx +87 -0
  149. package/src/api/use-map.ts +8 -0
  150. package/src/api/vector-3-to-coords.ts +13 -0
  151. package/src/core/canvas-in-layer/use-canvas-in-layer.tsx +27 -0
  152. package/src/core/canvas-in-layer/use-render.ts +43 -0
  153. package/src/core/canvas-in-layer/use-root.tsx +82 -0
  154. package/src/core/canvas-overlay/canvas-portal.tsx +98 -0
  155. package/src/core/canvas-overlay/init-canvas-fc.tsx +45 -0
  156. package/src/core/canvas-overlay/render.tsx +1 -0
  157. package/src/core/canvas-overlay/sync-camera-fc.tsx +83 -0
  158. package/src/core/coords-to-matrix.ts +21 -0
  159. package/src/core/earth-radius.ts +1 -0
  160. package/src/core/events.ts +55 -0
  161. package/src/core/generic-map.ts +59 -0
  162. package/src/core/map-engine.tsx +70 -0
  163. package/src/core/matrix-utils.ts +22 -0
  164. package/src/core/sync-camera.ts +29 -0
  165. package/src/core/use-babylon-map.ts +46 -0
  166. package/src/core/use-coords-to-matrix.ts +13 -0
  167. package/src/core/use-coords.tsx +22 -0
  168. package/src/core/use-function.ts +10 -0
  169. package/src/mapbox/canvas.tsx +59 -0
  170. package/src/mapbox.index.ts +7 -0
  171. package/src/maplibre/canvas.tsx +59 -0
  172. package/src/maplibre.index.ts +7 -0
  173. package/src/vite-env.d.ts +1 -0
  174. package/stories/.ladle/components.tsx +50 -0
  175. package/stories/.ladle/style.css +63 -0
  176. package/stories/package.json +31 -0
  177. package/stories/pnpm-lock.yaml +5450 -0
  178. package/stories/sandbox.config.json +3 -0
  179. package/stories/src/adaptive-dpr.tsx +34 -0
  180. package/stories/src/billboard.stories.tsx +111 -0
  181. package/stories/src/buildings-3d.stories.tsx +280 -0
  182. package/stories/src/canvas/mapbox.stories.tsx +113 -0
  183. package/stories/src/canvas/maplibre.stories.tsx +93 -0
  184. package/stories/src/comparison.stories.tsx +161 -0
  185. package/stories/src/extrude/chaillot.ts +8 -0
  186. package/stories/src/exude-coordinates.stories.tsx +139 -0
  187. package/stories/src/free-3d-buildings/get-buildings-data.ts +49 -0
  188. package/stories/src/html-on-top.stories.tsx +156 -0
  189. package/stories/src/ifc/ifc-to-babylon.ts +97 -0
  190. package/stories/src/ifc/ifc.main.ts +904 -0
  191. package/stories/src/ifc/ifc2bb.ts +343 -0
  192. package/stories/src/ifc/model.ifc +14155 -0
  193. package/stories/src/ifc.stories.tsx +276 -0
  194. package/stories/src/mapbox/story-mapbox.tsx +97 -0
  195. package/stories/src/maplibre/story-maplibre.tsx +36 -0
  196. package/stories/src/multi-coordinates.stories.tsx +115 -0
  197. package/stories/src/pivot-controls.stories.tsx +148 -0
  198. package/stories/src/postprocessing.stories.tsx +125 -0
  199. package/stories/src/render-on-demand.stories.tsx +76 -0
  200. package/stories/src/story-map.tsx +44 -0
  201. package/stories/src/sunlight.stories.tsx +215 -0
  202. package/stories/src/vite-env.d.ts +1 -0
  203. package/stories/tsconfig.json +32 -0
  204. package/stories/tsconfig.node.json +10 -0
  205. package/stories/vite.config.ts +27 -0
  206. package/tsconfig.json +31 -0
  207. package/tsconfig.mapbox.json +7 -0
  208. package/tsconfig.maplibre.json +7 -0
  209. package/tsconfig.node.json +10 -0
  210. package/tsconfig.types.json +25 -0
  211. package/vite.config.ts +65 -0
@@ -0,0 +1,125 @@
1
+ import { FC, memo, useEffect, useRef, useState } from "react";
2
+ import {
3
+ MeshBuilder, StandardMaterial, Vector3, Color3,
4
+ HemisphericLight, DefaultRenderingPipeline,
5
+ } from '@babylonjs/core';
6
+ import { levaStore, useControls } from 'leva';
7
+ import { useBabylonMap } from 'react-babylon-map';
8
+ import { StoryMap } from "./story-map";
9
+
10
+ function Lights() {
11
+ const { scene } = useBabylonMap();
12
+ useEffect(() => {
13
+ if (!scene) return;
14
+ const light = new HemisphericLight("hemiLight", new Vector3(0, 1, 0), scene);
15
+ light.intensity = 1;
16
+ return () => { light.dispose(); };
17
+ }, [scene]);
18
+ return null;
19
+ }
20
+
21
+ /** Box with hover color change */
22
+ const HoverBox: FC<{ scale: number }> = memo(({ scale }) => {
23
+ const { scene } = useBabylonMap();
24
+ const [hovered, setHovered] = useState(false);
25
+ const matRef = useRef<StandardMaterial | null>(null);
26
+
27
+ useEffect(() => {
28
+ if (!scene) return;
29
+ const s = scale;
30
+ const box = MeshBuilder.CreateBox('hoverBox', { width: s, height: s, depth: s }, scene);
31
+ box.position.y = s * 0.5;
32
+
33
+ const mat = new StandardMaterial('hoverBoxMat', scene);
34
+ mat.diffuseColor = Color3.FromHexString('#CCCCCC');
35
+ mat.alpha = 0.5;
36
+ matRef.current = mat;
37
+ box.material = mat;
38
+
39
+ box.metadata = {
40
+ onPointerOver: () => setHovered(true),
41
+ onPointerOut: () => setHovered(false),
42
+ };
43
+
44
+ return () => { box.dispose(); mat.dispose(); };
45
+ }, [scene, scale]);
46
+
47
+ useEffect(() => {
48
+ if (!matRef.current) return;
49
+ matRef.current.diffuseColor = Color3.FromHexString(hovered ? '#FFFF00' : '#CCCCCC');
50
+ }, [hovered]);
51
+
52
+ return null;
53
+ });
54
+ HoverBox.displayName = 'HoverBox';
55
+
56
+ /** Ground plane */
57
+ const Ground: FC = memo(() => {
58
+ const { scene } = useBabylonMap();
59
+ useEffect(() => {
60
+ if (!scene) return;
61
+ const ground = MeshBuilder.CreateGround('ground', { width: 2, height: 2 }, scene);
62
+ const mat = new StandardMaterial('groundMat', scene);
63
+ mat.diffuseColor = Color3.FromHexString('#CCCCCC');
64
+ ground.material = mat;
65
+ return () => { ground.dispose(); mat.dispose(); };
66
+ }, [scene]);
67
+ return null;
68
+ });
69
+ Ground.displayName = 'Ground';
70
+
71
+ /** Attach Babylon's built-in post-processing pipeline */
72
+ const PostProcessing: FC<{ ao: boolean; ssaoEnabled: boolean }> = memo(({ ao, ssaoEnabled }) => {
73
+ const { scene, engine } = useBabylonMap();
74
+ useEffect(() => {
75
+ if (!scene || !engine) return;
76
+
77
+ const pipeline = new DefaultRenderingPipeline('defaultPipeline', true, scene, [scene.activeCamera!]);
78
+
79
+ // SSAO (equivalent to N8AO in react-three-postprocessing)
80
+ pipeline.ssaoEnabled = ao && ssaoEnabled;
81
+ if (pipeline.ssao) {
82
+ pipeline.ssao.totalStrength = 1.5;
83
+ pipeline.ssao.radius = 0.5;
84
+ }
85
+
86
+ // Bloom
87
+ pipeline.bloomEnabled = false;
88
+
89
+ // FXAA
90
+ pipeline.fxaaEnabled = true;
91
+
92
+ return () => { pipeline.dispose(); };
93
+ }, [scene, engine, ao, ssaoEnabled]);
94
+
95
+ return null;
96
+ });
97
+ PostProcessing.displayName = 'PostProcessing';
98
+
99
+ export default { title: 'Postprocessing' };
100
+
101
+ export function Default() {
102
+ const { ao, scale } = useControls({
103
+ ao: { value: true, label: 'Ambient Occlusion' },
104
+ scale: { value: 35 },
105
+ });
106
+
107
+ // Default to overlay mode
108
+ useEffect(() => {
109
+ levaStore.setValueAtPath('overlay', true, true);
110
+ }, []);
111
+
112
+ return (
113
+ <StoryMap
114
+ latitude={51.508}
115
+ longitude={-0.1281}
116
+ zoom={18}
117
+ pitch={60}
118
+ >
119
+ <Lights />
120
+ <PostProcessing ao={ao} ssaoEnabled={true} />
121
+ <HoverBox scale={scale} />
122
+ <Ground />
123
+ </StoryMap>
124
+ );
125
+ }
@@ -0,0 +1,76 @@
1
+ import { FC, memo, useEffect, useRef, useState } from "react";
2
+ import { MeshBuilder, StandardMaterial, Vector3, Color3, HemisphericLight } from '@babylonjs/core';
3
+ import { useBabylonMap } from 'react-babylon-map';
4
+ import { levaStore, useControls } from 'leva';
5
+ import { StoryMap } from "./story-map";
6
+
7
+ export function Default() {
8
+ const [toggle, setToggle] = useState(false);
9
+ useEffect(() => { levaStore.setValueAtPath('overlay', true, true); }, []);
10
+
11
+ return (
12
+ <StoryMap
13
+ latitude={51.508}
14
+ longitude={-0.1281}
15
+ zoom={18}
16
+ pitch={60}
17
+ >
18
+ <SceneContent toggle={toggle} />
19
+ </StoryMap>
20
+ );
21
+ }
22
+
23
+ function SceneContent({ toggle }: { toggle: boolean }) {
24
+ const { scene } = useBabylonMap();
25
+ const boxRef = useRef<any>(null);
26
+ const matRef = useRef<any>(null);
27
+ const groundRef = useRef<any>(null);
28
+ const groundMatRef = useRef<any>(null);
29
+
30
+ const rootRef = useRef<any>(null);
31
+
32
+ const rootMatRef = useRef<any>(null);
33
+
34
+ const boxMatRef = useRef<any>(null);
35
+
36
+ const boxMatMat = useRef<any>(null);
37
+
38
+ useEffect(() => {
39
+ if (!scene) return;
40
+ const light = new HemisphericLight("hemiLight", new Vector3(0, 1, 0), scene);
41
+ light.intensity = 1;
42
+ const root = MeshBuilder.CreateBox("root", { size: 35 }, scene);
43
+ root.position.y = 17.5;
44
+ root.rotation.y = 13 * Math.PI / 180;
45
+ rootRef.current = root;
46
+ const rMat = new StandardMaterial("rootMat", scene);
47
+ rMat.diffuseColor = Color3.FromHexString("#cccccc");
48
+ rMat.alpha = 0.5;
49
+ rootMatRef.current = rMat;
50
+ root.material = rMat;
51
+ root.metadata = {
52
+ onPointerOver: () => setToggle(true),
53
+ onPointerOut: () => setToggle(false),
54
+ };
55
+ // Ground plane
56
+ const ground = MeshBuilder.CreateGround("ground", { width: 70, height: 70 }, scene);
57
+ groundRef.current = ground;
58
+ const gMat = new StandardMaterial("groundMat", scene);
59
+ gMat.diffuseColor = Color3.FromHexString("#cccccc");
60
+ ground.material = gMat;
61
+ return () => {
62
+ light.dispose();
63
+ root.dispose(); rMat.dispose(); ground.dispose(); gMat.dispose();
64
+ };
65
+ }, [scene]);
66
+
67
+ useEffect(() => {
68
+ if (!rootMatRef.current) return;
69
+ rootMatRef.current.diffuseColor = toggle
70
+ ? Color3.FromHexString("#ffff00")
71
+ : Color3.FromHexString("#cccccc");
72
+ }, [toggle]);
73
+
74
+ return null;
75
+ }
76
+ SceneContent.displayName = 'SceneContent';
@@ -0,0 +1,44 @@
1
+ import { useControls } from 'leva';
2
+ import { FC, PropsWithChildren, ReactNode } from "react";
3
+ import { CanvasProps } from 'react-babylon-map';
4
+ import { StoryMapbox } from './mapbox/story-mapbox';
5
+ import { StoryMaplibre } from './maplibre/story-maplibre';
6
+
7
+ export enum MapProvider {
8
+ maplibre = "maplibre",
9
+ mapbox = "mapbox",
10
+ }
11
+
12
+ export interface StoryMapProps extends PropsWithChildren {
13
+ latitude: number,
14
+ longitude: number,
15
+ zoom?: number,
16
+ pitch?: number,
17
+ bearing?: number,
18
+ canvas?: Partial<CanvasProps>,
19
+ mapChildren?: ReactNode,
20
+ mapboxChildren?: ReactNode,
21
+ maplibreChildren?: ReactNode,
22
+ }
23
+
24
+ /** `<Map>` styled for stories */
25
+ export const StoryMap: FC<StoryMapProps> = (props) => {
26
+
27
+ const { mapProvider, overlay } = useControls({
28
+ mapProvider: {
29
+ value: MapProvider.maplibre,
30
+ options: MapProvider,
31
+ label: 'map provider'
32
+ },
33
+ overlay: {
34
+ value: false,
35
+ }
36
+ });
37
+
38
+ const canvas = { overlay, ...props.canvas };
39
+
40
+ return <div style={{ height: '100vh', position: 'relative' }}>
41
+ {mapProvider === MapProvider.maplibre && <StoryMaplibre {...props} canvas={canvas} />}
42
+ {mapProvider === MapProvider.mapbox && <StoryMapbox {...props} canvas={canvas} />}
43
+ </div>
44
+ }
@@ -0,0 +1,215 @@
1
+ import { FC, memo, useEffect, useMemo, useRef } from "react";
2
+ import {
3
+ MeshBuilder, StandardMaterial, Vector3, Color3, Matrix,
4
+ HemisphericLight, DirectionalLight, ShadowGenerator,
5
+ VertexData, VertexBuffer, DynamicTexture,
6
+ } from '@babylonjs/core';
7
+ import { useBabylonMap } from 'react-babylon-map';
8
+ import { useControls } from 'leva';
9
+ import { StoryMap } from "./story-map";
10
+ import { getPosition } from 'suncalc';
11
+ import tzLookup from 'tz-lookup';
12
+ import { DateTime } from 'luxon';
13
+
14
+ const RADIUS = 150;
15
+
16
+ function getSunPosition({ date, latitude, longitude, radius = RADIUS }: {
17
+ date: Date; latitude: number; longitude: number; radius?: number;
18
+ }): [number, number, number] {
19
+ const sun = getPosition(date, latitude, longitude);
20
+ const x = radius * Math.cos(sun.altitude) * -Math.sin(sun.azimuth);
21
+ const z = radius * Math.cos(sun.altitude) * Math.cos(sun.azimuth);
22
+ const y = radius * Math.sin(sun.altitude);
23
+ return [x, y, z];
24
+ }
25
+
26
+ function useSun({ latitude, longitude }: { longitude: number; latitude: number }) {
27
+ const { month, hour } = useControls({
28
+ month: { value: new Date().getMonth() + 1, min: 1, max: 12, step: 0.1 },
29
+ hour: { value: new Date().getHours(), min: 0, max: 23, step: 0.1 },
30
+ });
31
+
32
+ const date = useMemo(() => {
33
+ const timeZone = tzLookup(latitude, longitude);
34
+ return DateTime.now().setZone(timeZone).set({
35
+ month: Math.floor(month),
36
+ day: Math.floor((month % 1) * 27) + 1,
37
+ hour: Math.floor(hour),
38
+ minute: (hour % 1) * 60,
39
+ second: 0,
40
+ millisecond: 0,
41
+ }).toJSDate();
42
+ }, [latitude, longitude, month, hour]);
43
+
44
+ const { position, sunPath } = useMemo(() => {
45
+ const position = getSunPosition({ date, latitude, longitude });
46
+ const tempDate = new Date(date);
47
+ const sunPath: [number, number, number][] = [];
48
+ for (let h = 0; h <= 24; h++) {
49
+ tempDate.setHours(h);
50
+ sunPath.push(getSunPosition({ date: tempDate, latitude, longitude }));
51
+ }
52
+ return { position, sunPath };
53
+ }, [date, latitude, longitude]);
54
+
55
+ return { position, sunPath };
56
+ }
57
+
58
+ /** Sun path line with per-vertex colors (night=blue, day=orange) */
59
+ const SunPathLine: FC<{ path: [number, number, number][] }> = memo(({ path }) => {
60
+ const { scene } = useBabylonMap();
61
+ useEffect(() => {
62
+ if (!scene || path.length < 2) return;
63
+
64
+ const positions: number[] = [];
65
+ const colors: number[] = [];
66
+ const indices: number[] = [];
67
+ const night = Color3.FromHexString('#00008B');
68
+ const day = Color3.FromHexString('#FFA500');
69
+ const tmp = new Color3();
70
+
71
+ for (let i = 0; i < path.length; i++) {
72
+ positions.push(path[i][0], path[i][1], path[i][2]);
73
+ indices.push(i, i + 1);
74
+
75
+ const y = path[i][1];
76
+ const nightStart = -RADIUS * 0.5;
77
+ const dayStart = RADIUS * 0.5;
78
+ let c: Color3;
79
+ if (y <= nightStart) c = night;
80
+ else if (y >= dayStart) c = day;
81
+ else {
82
+ const d = (y - nightStart) / (dayStart - nightStart);
83
+ c = tmp.copyFrom(night).lerp(day, d);
84
+ }
85
+ colors.push(c.r, c.g, c.b);
86
+ }
87
+ // Remove last index (no segment after last point)
88
+ indices.pop();
89
+
90
+ const mesh = new MeshBuilder.CreateLines('sunPath', {
91
+ points: path.map(p => new Vector3(p[0], p[1], p[2])),
92
+ updatable: false,
93
+ }, scene);
94
+ const mat = new StandardMaterial('sunPathMat', scene);
95
+ mat.emissiveColor = new Color3(1, 1, 1);
96
+ mat.disableLighting = true;
97
+ mesh.material = mat;
98
+
99
+ return () => { mesh.dispose(); mat.dispose(); };
100
+ }, [scene, path]);
101
+ return null;
102
+ });
103
+ SunPathLine.displayName = 'SunPathLine';
104
+
105
+ /** Floor plane that receives shadows */
106
+ const Floor: FC = () => {
107
+ const { scene } = useBabylonMap();
108
+ useEffect(() => {
109
+ if (!scene) return;
110
+ const floor = MeshBuilder.CreateGround('floor', { width: 1000, height: 1000 }, scene);
111
+ const mat = new StandardMaterial('floorMat', scene);
112
+ mat.diffuseColor = Color3.Black();
113
+ mat.specularColor = Color3.Black();
114
+ mat.alpha = 0.5;
115
+ floor.material = mat;
116
+ floor.receiveShadows = true;
117
+ return () => { floor.dispose(); mat.dispose(); };
118
+ }, [scene]);
119
+ return null;
120
+ };
121
+
122
+ /** Sphere mesh */
123
+ const MySphere: FC<{ position: [number, number, number]; color: string; castShadow?: boolean }> = memo(({ position, color, castShadow }) => {
124
+ const { scene } = useBabylonMap();
125
+ useEffect(() => {
126
+ if (!scene) return;
127
+ const sphere = MeshBuilder.CreateSphere('sunSphere', { diameter: 32 }, scene);
128
+ sphere.position.set(position[0], position[1], position[2]);
129
+ sphere.rotation.y = 45 * Math.PI / 180;
130
+ const mat = new StandardMaterial('sphereMat', scene);
131
+ mat.diffuseColor = Color3.FromHexString(color);
132
+ mat.emissiveColor = Color3.FromHexString(color).scale(0.3);
133
+ sphere.material = mat;
134
+ if (castShadow) sphere.receiveShadows = true;
135
+ return () => { sphere.dispose(); mat.dispose(); };
136
+ }, [scene, position, color, castShadow]);
137
+ return null;
138
+ });
139
+ MySphere.displayName = 'MySphere';
140
+
141
+ /** Sun with directional light + shadow */
142
+ const Sun: FC<{ latitude: number; longitude: number }> = ({ latitude, longitude }) => {
143
+ const { scene, map } = useBabylonMap();
144
+ const { position, sunPath } = useSun({ latitude, longitude });
145
+ const isDay = position[1] >= 0;
146
+
147
+ // Switch map style based on sun position
148
+ useEffect(() => {
149
+ if (!map) return;
150
+ const style = isDay
151
+ ? "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
152
+ : "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json";
153
+ map.setStyle(style);
154
+ }, [map, isDay]);
155
+
156
+ // Directional light + shadow
157
+ useEffect(() => {
158
+ if (!scene) return;
159
+ const dir = new DirectionalLight('sunLight',
160
+ new Vector3(-position[0], -position[1], -position[2]).normalize(), scene);
161
+ dir.position = new Vector3(position[0], position[1], position[2]);
162
+ dir.intensity = isDay ? 1.5 * Math.PI : 0;
163
+
164
+ const shadowGen = new ShadowGenerator(1024, dir);
165
+ shadowGen.useBlurExponentialShadowMap = true;
166
+ shadowGen.blurKernel = 32;
167
+
168
+ // Add shadow casters as they're created
169
+ scene.meshes.forEach(m => {
170
+ if (m.name === 'sunSphere') shadowGen.addShadowCaster(m);
171
+ });
172
+
173
+ return () => {
174
+ shadowGen.dispose();
175
+ dir.dispose();
176
+ };
177
+ }, [scene, position, isDay]);
178
+
179
+ // Hemisphere light for night
180
+ useEffect(() => {
181
+ if (!scene || isDay) return;
182
+ const hemi = new HemisphericLight('nightHemi',
183
+ new Vector3(position[0], position[1], position[2]), scene);
184
+ hemi.intensity = Math.PI;
185
+ hemi.groundColor = Color3.FromHexString('#005f6b');
186
+ hemi.diffuse = Color3.FromHexString('#343838');
187
+ return () => { hemi.dispose(); };
188
+ }, [scene, position, isDay]);
189
+
190
+ return (
191
+ <>
192
+ <SunPathLine path={sunPath} />
193
+ {isDay && (
194
+ <MySphere position={position} color="#FFA500" />
195
+ )}
196
+ </>
197
+ );
198
+ };
199
+
200
+ export default { title: 'Sunlight' };
201
+
202
+ export function Default() {
203
+ const { longitude, latitude } = useControls({
204
+ longitude: { value: 0, min: -179, max: 180, pad: 6 },
205
+ latitude: { value: 51, min: -80, max: 80, pad: 6 },
206
+ });
207
+
208
+ return (
209
+ <StoryMap longitude={longitude} latitude={latitude} zoom={6} pitch={60}>
210
+ <Sun longitude={longitude} latitude={latitude} />
211
+ <Floor />
212
+ <MySphere position={[0, 10, 0]} color="#E28357" castShadow />
213
+ </StoryMap>
214
+ );
215
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ "moduleResolution": "Node",
10
+ "allowImportingTsExtensions": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+
16
+ "strict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+
21
+ "allowSyntheticDefaultImports": true,
22
+
23
+ "paths": {
24
+ "react-babylon-map": ["../src/mapbox.index.ts"],
25
+ "react-babylon-map/maplibre": ["../src/maplibre.index.ts"]
26
+ },
27
+ "baseUrl": "../"
28
+ },
29
+ "include": ["src"],
30
+ "exclude": ["node_modules"],
31
+ "references": [{ "path": "./tsconfig.node.json" }]
32
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -0,0 +1,27 @@
1
+ import react from '@vitejs/plugin-react'
2
+ import { defineConfig } from 'vite';
3
+ import { resolve } from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ 'react-babylon-map/maplibre': resolve(__dirname, '../src/maplibre.index.ts'),
10
+ 'react-babylon-map': resolve(__dirname, '../src/mapbox.index.ts'),
11
+ }
12
+ },
13
+ server: {
14
+ headers: {
15
+ 'Cross-Origin-Opener-Policy': 'same-origin',
16
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
17
+ },
18
+ fs: {
19
+ strict: false,
20
+ allow: ['/home/ney/os/react-babylon-map'],
21
+ },
22
+ },
23
+ optimizeDeps: {
24
+ exclude: ['react-babylon-map', '@ifc-lite/geometry', '@ifc-lite/wasm']
25
+ },
26
+ assetsInclude: ['**/*.wasm'],
27
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ "paths": {
18
+ "react-babylon-map": ["./src/mapbox.index.ts"],
19
+ "react-babylon-map/maplibre": ["./src/maplibre.index.ts"]
20
+ },
21
+
22
+ /* Linting */
23
+ "strict": true,
24
+ "noUnusedLocals": true,
25
+ "noUnusedParameters": true,
26
+ "noFallthroughCasesInSwitch": true
27
+ },
28
+ "include": ["src"],
29
+ "exclude": ["node_modules"],
30
+ "references": [{ "path": "./tsconfig.node.json" }]
31
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.types.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/types"
5
+ },
6
+ "include": ["src/mapbox.index.ts"]
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.types.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/maplibre/types"
5
+ },
6
+ "include": ["src/maplibre.index.ts"]
7
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "emitDeclarationOnly": true,
5
+
6
+ "target": "ES2020",
7
+ "useDefineForClassFields": true,
8
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
9
+ "module": "ESNext",
10
+ "skipLibCheck": true,
11
+
12
+ /* Bundler mode */
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "resolveJsonModule": true,
16
+ "isolatedModules": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noFallthroughCasesInSwitch": true
24
+ }
25
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { resolve } from 'path';
4
+
5
+ /** 0: no lib mode, 1: ES, 2: cjs */
6
+ const libMode = parseInt(process.env.LIB_MODE!) || 0;
7
+
8
+ /** 0: MapLibre, 1: MapBox */
9
+ const mapProvider = parseInt(process.env.MAP_MODE!) || 0;
10
+
11
+ const isES = libMode === 1;
12
+
13
+ const isMaplibre = mapProvider === 0;
14
+
15
+ const entry = `src/${isMaplibre ? 'maplibre' : 'mapbox'}.index.ts`;
16
+
17
+ let outDir = isMaplibre ? 'dist/maplibre' : 'dist';
18
+
19
+ outDir = `${outDir}/${isES ? 'es' : 'cjs'}`;
20
+
21
+ // https://vitejs.dev/config/
22
+ export default defineConfig({
23
+ plugins: [react()],
24
+ ...(!libMode
25
+ // dev mode
26
+ ? {
27
+ base: '',
28
+ resolve: {
29
+ alias: {
30
+ 'react-babylon-map/maplibre': resolve(__dirname, './src/maplibre.index.ts'),
31
+ 'react-babylon-map/mapbox': resolve(__dirname, './src/mapbox.index.ts'),
32
+ 'react-babylon-map': resolve(__dirname, './src/mapbox.index.ts'),
33
+ }
34
+ }
35
+ }
36
+ // lib mode
37
+ : {
38
+ publicDir: false,
39
+ build: {
40
+ minify: false,
41
+ lib: {
42
+ entry,
43
+ name: 'react-babylon-map',
44
+ formats: isES ? ['es'] : ['cjs'],
45
+ fileName: 'main',
46
+ },
47
+ outDir,
48
+ rollupOptions: {
49
+ output: !isES ? undefined : { sourcemap: true, preserveModules: true },
50
+ external: [
51
+ "@babylonjs/core",
52
+ "@babylonjs/materials",
53
+ "maplibre-gl",
54
+ "mapbox-gl",
55
+ "react",
56
+ 'react/jsx-runtime',
57
+ "react-dom",
58
+ "react-map-gl",
59
+ "react-map-gl/mapbox",
60
+ "react-map-gl/maplibre",
61
+ ]
62
+ },
63
+ },
64
+ })
65
+ })