@ntalmagor/3drize-viewer 0.1.22 → 0.1.24

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 (66) hide show
  1. package/dist/components/AnimatedObject.js +1 -1
  2. package/dist/components/Clouds.js +1 -1
  3. package/dist/components/CreatedObject.d.ts.map +1 -1
  4. package/dist/components/CreatedObject.js +4 -2
  5. package/dist/components/CustomPrimitive.js +31 -27
  6. package/dist/components/EffectsGroup.d.ts +1 -1
  7. package/dist/components/EffectsGroup.d.ts.map +1 -1
  8. package/dist/components/EffectsGroup.js +2 -1
  9. package/dist/components/EnvironmentManager.d.ts.map +1 -1
  10. package/dist/components/EnvironmentManager.js +18 -22
  11. package/dist/components/Galaxy.d.ts +3 -1
  12. package/dist/components/Galaxy.d.ts.map +1 -1
  13. package/dist/components/Galaxy.js +2 -2
  14. package/dist/components/HdrPlaylistRenderer.d.ts +10 -0
  15. package/dist/components/HdrPlaylistRenderer.d.ts.map +1 -0
  16. package/dist/components/HdrPlaylistRenderer.js +364 -0
  17. package/dist/components/LightObject.d.ts +8 -0
  18. package/dist/components/LightObject.d.ts.map +1 -0
  19. package/dist/components/LightObject.js +65 -0
  20. package/dist/components/LightsManager.d.ts +2 -3
  21. package/dist/components/LightsManager.d.ts.map +1 -1
  22. package/dist/components/LightsManager.js +89 -106
  23. package/dist/components/ObjectNode.d.ts.map +1 -1
  24. package/dist/components/ObjectNode.js +15 -6
  25. package/dist/components/ObjectsRenderer.js +1 -1
  26. package/dist/components/Ocean.js +1 -1
  27. package/dist/components/SceneBuilder.js +1 -1
  28. package/dist/components/SkyController.d.ts.map +1 -1
  29. package/dist/components/SkyController.js +1 -1
  30. package/dist/components/SkySystem.d.ts.map +1 -1
  31. package/dist/components/SkySystem.js +4 -2
  32. package/dist/components/Skybox.d.ts.map +1 -1
  33. package/dist/components/Skybox.js +37 -28
  34. package/dist/components/Stars.d.ts.map +1 -1
  35. package/dist/components/Stars.js +12 -2
  36. package/dist/hooks/useCameraAnimation.js +25 -19
  37. package/dist/hooks/useCubeCamera.js +4 -4
  38. package/dist/hooks/useInteractionEffects.d.ts +3 -3
  39. package/dist/hooks/useInteractionEffects.d.ts.map +1 -1
  40. package/dist/hooks/useInteractionEffects.js +66 -23
  41. package/dist/hooks/useLightAnimation.d.ts +10 -0
  42. package/dist/hooks/useLightAnimation.d.ts.map +1 -0
  43. package/dist/hooks/useLightAnimation.js +34 -0
  44. package/dist/hooks/useLightMouseMove.d.ts +11 -0
  45. package/dist/hooks/useLightMouseMove.d.ts.map +1 -0
  46. package/dist/hooks/useLightMouseMove.js +27 -0
  47. package/dist/hooks/useLightShadow.d.ts +6 -0
  48. package/dist/hooks/useLightShadow.d.ts.map +1 -0
  49. package/dist/hooks/useLightShadow.js +63 -0
  50. package/dist/hooks/useMaterialApplication.d.ts.map +1 -1
  51. package/dist/hooks/useMaterialApplication.js +65 -6
  52. package/dist/hooks/useMaterialUniforms.js +6 -6
  53. package/dist/hooks/useObjectAnimation.d.ts.map +1 -1
  54. package/dist/hooks/useObjectAnimation.js +3 -3
  55. package/dist/hooks/useObjectEdges.d.ts +1 -2
  56. package/dist/hooks/useObjectEdges.d.ts.map +1 -1
  57. package/dist/hooks/useObjectEdges.js +5 -5
  58. package/dist/hooks/useShadowApplication.d.ts +3 -0
  59. package/dist/hooks/useShadowApplication.d.ts.map +1 -0
  60. package/dist/hooks/useShadowApplication.js +31 -0
  61. package/dist/hooks/useSkySystem.d.ts.map +1 -1
  62. package/dist/hooks/useSkySystem.js +4 -3
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2 -1
  66. package/package.json +4 -3
@@ -17,7 +17,7 @@ visible = true, children, parentRef, handleObjectClick, onPointerEnter, onPointe
17
17
  const stepEase = useRef('linear');
18
18
  const animProgress = useRef(0);
19
19
  const hasHoverFunctionalities = hasHoverAnimation(createdObject.animations);
20
- const hasMouseInteraction = createdObject.interactions && createdObject.interactions.actions.length > 0;
20
+ const hasMouseInteraction = (createdObject.actions?.length ?? 0) > 0;
21
21
  const baseRef = useRef({ position: [...position], scale: [...scale], rotation: [...rotation] });
22
22
  const targetRef = useRef({
23
23
  position: targetPosition ? [...targetPosition] : [...position],
@@ -35,7 +35,7 @@ const Clouds = ({ settings, timeSettings, }) => {
35
35
  const mouseOverrideRef = useRef({});
36
36
  const { animateNumeric } = useNumericAnimation();
37
37
  const effectiveMaterialSettings = materialSettings ?? DEFAULT_MATERIAL;
38
- useMaterialApplication(meshRef.current, effectiveMaterialSettings, true);
38
+ useMaterialApplication(settings.visible ? meshRef.current : null, effectiveMaterialSettings, true);
39
39
  const handleTimeUpdate = useCallback((state) => {
40
40
  const { normalizedTime, isDaytime, delta } = state;
41
41
  if (isDaytime) {
@@ -1 +1 @@
1
- {"version":3,"file":"CreatedObject.d.ts","sourceRoot":"","sources":["../../src/components/CreatedObject.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAEjE,OAAO,KAAK,EAAG,qBAAqB,EAAkC,MAAM,wBAAwB,CAAC;AAOrG,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAMjC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAWrD,UAAU,kBAAkB;IAC1B,aAAa,EAAE,qBAAqB,CAAC;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvE,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CAElH;AAED,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAwJ9C,CAAC;AAIH,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"CreatedObject.d.ts","sourceRoot":"","sources":["../../src/components/CreatedObject.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAEjE,OAAO,KAAK,EAAG,qBAAqB,EAAkC,MAAM,wBAAwB,CAAC;AAQrG,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAMjC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAWrD,UAAU,kBAAkB;IAC1B,aAAa,EAAE,qBAAqB,CAAC;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvE,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CAElH;AAED,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA6J9C,CAAC;AAIH,eAAe,aAAa,CAAC"}
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRef, useEffect, useState, memo } from "react";
3
3
  import { DEFAULT_EDGES_SETTINGS } from "@ntalmagor/3drize-core";
4
4
  import { useMaterialApplication } from "../hooks/useMaterialApplication.js";
5
+ import { useShadowApplication } from "../hooks/useShadowApplication.js";
5
6
  import { useTransformControls } from "../hooks/useTransformControls.js";
6
7
  import { RigidBody } from '@react-three/rapier';
7
8
  import { cameraManager } from "../utils/CameraSingleton.js";
@@ -25,9 +26,10 @@ const CreatedObject = memo(({ createdObject, children, shouldApply3driseMaterial
25
26
  const { controllersRef } = useTransformControls();
26
27
  const { handlePointerEnter, handlePointerLeave, hovered } = useFrameEffects(isSelected);
27
28
  const [mountedObject, setMountedObject] = useState(null);
28
- useObjectEdges(meshRef.current, createdObject.edgesSettings ?? DEFAULT_EDGES_SETTINGS);
29
+ useObjectEdges(createdObject.id, createdObject.edgesSettings ?? DEFAULT_EDGES_SETTINGS);
29
30
  // useObjectGenerativeEffects(mountedObject, createdObject.effects);
30
31
  useMaterialApplication(meshRef.current, createdObject.materialSettings, shouldApply3driseMaterial);
32
+ useShadowApplication(meshRef.current, createdObject.meshSettings.castShadow, createdObject.meshSettings.receiveShadow);
31
33
  const anchoredPosition = cameraManager.getAnchorPosition(createdObject.meshSettings.anchor?.anchor || 'center', 0, createdObject.meshSettings.anchor?.offset || { y: 0, x: 0, z: 0 }, createdObject.meshSettings.anchor?.padding || 0);
32
34
  useEffect(() => {
33
35
  if (meshRef.current) {
@@ -68,7 +70,7 @@ const CreatedObject = memo(({ createdObject, children, shouldApply3driseMaterial
68
70
  return;
69
71
  // e.stopPropagation();
70
72
  handlePointerEnter(e);
71
- if (createdObject.interactions) {
73
+ if (createdObject.actions?.length || createdObject.mouseMove?.enabled) {
72
74
  document.body.style.cursor = 'pointer';
73
75
  }
74
76
  };
@@ -1,47 +1,51 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useRef, useEffect, memo, useState } from "react";
3
- import { ObjectManager, findById } from "@ntalmagor/3drize-core";
4
- // import ObjectManager from "~/utils/ObjectManager";
5
- import { useMaterialApplication } from "../hooks/useMaterialApplication.js";
2
+ import { useRef, memo } from "react";
3
+ import { ObjectManager } from "@ntalmagor/3drize-core";
6
4
  import MaterialLibrary from "./MaterialLibrary.js";
7
- // import type { AnimatedTransform } from "~/types/mesh";
8
- import CreatedObject from "./CreatedObject.js";
9
5
  const CustomPrimitive = memo(({ createdObject, onObjectReady, handleObjectClick, }) => {
10
6
  // const { handleObjectClick } = useSceneClick();
11
7
  // console.log("Rendering CustomPrimitive with settings:", createdObject.name, createdObject);
12
8
  const meshRef = useRef(null);
13
- const effectStateRef = useRef(null);
14
- const [animatedTransform, setAnimatedTransform] = React.useState({
15
- position: createdObject.meshSettings.position,
16
- rotation: createdObject.meshSettings.rotation,
17
- scale: createdObject.meshSettings.scale,
18
- });
9
+ // const effectStateRef = useRef<EffectState | null>(null);
10
+ // const [animatedTransform, setAnimatedTransform] = React.useState<AnimatedTransform>({
11
+ // position: createdObject.meshSettings.position,
12
+ // rotation: createdObject.meshSettings.rotation,
13
+ // scale: createdObject.meshSettings.scale,
14
+ // });
19
15
  // Initialize transform controls
20
16
  // const { handlePointerEnter, handlePointerLeave, hovered } = useFrameEffects(true);
21
- useEffect(() => {
22
- setAnimatedTransform({
23
- position: createdObject.meshSettings.position,
24
- rotation: createdObject.meshSettings.rotation,
25
- scale: createdObject.meshSettings.scale,
26
- });
27
- }, [createdObject.meshSettings.position, createdObject.meshSettings.rotation, createdObject.meshSettings.scale]);
17
+ // useEffect(() => {
18
+ // setAnimatedTransform({
19
+ // position: createdObject.meshSettings.position,
20
+ // rotation: createdObject.meshSettings.rotation,
21
+ // scale: createdObject.meshSettings.scale,
22
+ // });
23
+ // }, [createdObject.meshSettings.position, createdObject.meshSettings.rotation, createdObject.meshSettings.scale]);
28
24
  const useDreiMaterials = createdObject.materialSettings.materialType === "advanced";
29
- const apply3driseMaterials = !useDreiMaterials;
30
- const willApplyMaterial = apply3driseMaterials && !!createdObject.materialSettings.materialVariant;
31
- const [materialReady, setMaterialReady] = useState(!willApplyMaterial);
25
+ // const apply3driseMaterials = !useDreiMaterials;
26
+ // const willApplyMaterial = apply3driseMaterials && !!createdObject.materialSettings.materialVariant;
27
+ // const [materialReady, setMaterialReady] = useState(!willApplyMaterial);
32
28
  // get initial object from ObjectManager to add to scene with primitive, then use findById to get updated reference with materials applied by useMaterialApplication
33
29
  const obj = ObjectManager.getObject(createdObject.id);
34
- const object = findById(createdObject.id);
35
- useMaterialApplication(object ?? null, createdObject.materialSettings, apply3driseMaterials, () => setMaterialReady(true));
30
+ // const object = findById(createdObject.id);
31
+ // useMaterialApplication(object ?? null, createdObject.materialSettings, apply3driseMaterials, () => setMaterialReady(true));
36
32
  if (!obj) {
37
33
  // console.warn(`CustomPrimitive - Object with ID ${createdObject.id} not found.`);
38
34
  return null;
39
35
  }
40
36
  return (
41
37
  // <Select enabled={isSelected}>
42
- _jsx(CreatedObject, { createdObject: createdObject, shouldApply3driseMaterial: false, handleObjectClick: handleObjectClick, onObjectReady: (object, ref) => {
43
- onObjectReady && onObjectReady(object, ref);
44
- }, children: _jsx("primitive", { ref: meshRef, name: obj.name, object: obj.object, visible: materialReady && createdObject.meshSettings.visible, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: useDreiMaterials && createdObject.type === 'mesh' && createdObject.meshSettings.visible && (_jsx(MaterialLibrary, { materialSettings: createdObject.materialSettings })) }, obj.id) })
38
+ // <CreatedObject createdObject={createdObject}
39
+ // shouldApply3driseMaterial={false} // Material is owned by useMaterialApplication above (applied to obj.object directly)
40
+ // handleObjectClick={handleObjectClick}
41
+ // onObjectReady={(object, ref) => {
42
+ // onObjectReady && onObjectReady(object, ref);
43
+ // }}
44
+ // >
45
+ _jsx("primitive", { ref: meshRef, name: obj.name, object: obj.object, visible: createdObject.meshSettings.visible, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: useDreiMaterials && createdObject.type === 'mesh' && createdObject.meshSettings.visible && (_jsx(MaterialLibrary, { materialSettings: createdObject.materialSettings })) }, obj.id)
46
+ // {/* <LineEdges object={obj.object} />
47
+ // <MeshEdges object={obj.object} /> */}
48
+ // </CreatedObject>
45
49
  // {/* </Select> */}
46
50
  );
47
51
  });
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { CreatedObjectSettings } from '@ntalmagor/3drize-core';
2
+ import { type CreatedObjectSettings } from '@ntalmagor/3drize-core';
3
3
  interface EffectsGroupProps {
4
4
  createdObject: CreatedObjectSettings;
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EffectsGroup.d.ts","sourceRoot":"","sources":["../../src/components/EffectsGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,UAAU,iBAAiB;IACzB,aAAa,EAAE,qBAAqB,CAAC;CACtC;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAS7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"EffectsGroup.d.ts","sourceRoot":"","sources":["../../src/components/EffectsGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAyB,KAAK,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAG3F,UAAU,iBAAiB;IACzB,aAAa,EAAE,qBAAqB,CAAC;CACtC;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAU7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { GEREATIVE_EFFECTS_KEY } from '@ntalmagor/3drize-core';
2
3
  import EffectsGenerator from './EffectsGenerator.js';
3
4
  const EffectsGroup = ({ createdObject }) => {
4
5
  if (!createdObject.effects || createdObject.effects.length === 0)
5
6
  return null;
6
- return (_jsx("group", { name: `${createdObject.id}-effects`, children: createdObject.effects.map((effect, index) => (_jsx(EffectsGenerator, { settings: effect }, index))) }));
7
+ return (_jsx("group", { name: `${createdObject.id}-effects`, userData: { [GEREATIVE_EFFECTS_KEY]: true }, children: createdObject.effects.map((effect, index) => (_jsx(EffectsGenerator, { settings: effect }, index))) }));
7
8
  };
8
9
  export default EffectsGroup;
@@ -1 +1 @@
1
- {"version":3,"file":"EnvironmentManager.d.ts","sourceRoot":"","sources":["../../src/components/EnvironmentManager.tsx"],"names":[],"mappings":"AAIA,OAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAe,MAAM,wBAAwB,CAAC;AAG7E,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,QAAA,MAAM,kBAAkB,kHAkHtB,CAAC;AAIH,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"EnvironmentManager.d.ts","sourceRoot":"","sources":["../../src/components/EnvironmentManager.tsx"],"names":[],"mappings":"AAIA,OAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAIhF,OAAO,KAAK,EAAE,iBAAiB,EAAe,MAAM,wBAAwB,CAAC;AAG7E,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,QAAA,MAAM,kBAAkB,kHAkHtB,CAAC;AAIH,eAAe,kBAAkB,CAAC"}
@@ -5,24 +5,9 @@ import { useThree } from '@react-three/fiber';
5
5
  import { Vector3 } from 'three';
6
6
  import SkySystem from './SkySystem.js';
7
7
  import { useCubeCamera } from '../hooks/useCubeCamera.js';
8
- const EnvironmentManager = forwardRef(({ environment, cubeCameraResolution = 512,
9
- // SkySystemProps all forwarded to SkySystem
10
- timeSettings,
11
- // sunSystemVisible = false,
12
- // sunSize,
13
- // moonSize,
14
- // skyboxScale,
15
- // sunIntensity,
16
- // castShadow,
17
- // shadowMapSize,
18
- // playerPosition,
19
- // starsSettings,
20
- // cloudsSettings,
21
- // cloudsMaterialSettings,
22
- // skyControllerSettings,
23
- // rainSettings,
24
- // fogSettings,
25
- onTimeOfDayChange, onTimeUpdate, }, ref) => {
8
+ import HdrPlaylistRenderer from './HdrPlaylistRenderer.js';
9
+ import { getTransitionSettings } from '@ntalmagor/3drize-core';
10
+ const EnvironmentManager = forwardRef(({ environment, cubeCameraResolution = 512, timeSettings, onTimeOfDayChange, onTimeUpdate, }, ref) => {
26
11
  const { scene } = useThree();
27
12
  const defaultHdr = {
28
13
  visible: false,
@@ -41,6 +26,10 @@ onTimeOfDayChange, onTimeUpdate, }, ref) => {
41
26
  };
42
27
  const hdr = environment?.hdr ?? defaultHdr;
43
28
  const backgroundName = hdr.visible ? hdr.name : null;
29
+ const urls = hdr.urls;
30
+ const playlistActive = !!urls && urls.length > 1;
31
+ const singleUrl = urls && urls.length === 1 ? urls[0] : undefined;
32
+ const hdrActive = !!(backgroundName || singleUrl || playlistActive);
44
33
  const sky = environment?.sky;
45
34
  const sunSystemVisible = sky?.sunSystem?.visible;
46
35
  const sunSize = sky?.sunSystem?.sunSize;
@@ -63,16 +52,23 @@ onTimeOfDayChange, onTimeUpdate, }, ref) => {
63
52
  timeSettings,
64
53
  resolution: cubeCameraResolution,
65
54
  });
55
+ // While the playlist is active, drei <Environment> unmounts; HdrPlaylistRenderer
56
+ // owns scene.environment + scene.background for the duration of the transitions.
66
57
  const environmentComponent = useMemo(() => {
67
- if (!backgroundName)
58
+ if (playlistActive)
68
59
  return null;
69
- return (_jsx(Environment, { preset: backgroundName, background: true, scene: scene, backgroundBlurriness: hdr.settings.backgroundBlurriness ?? 0, backgroundIntensity: hdr.settings.backgroundIntensity ?? 1, backgroundRotation: [0, hdr.settings.backgroundRotation ?? Math.PI / 2, 0], environmentIntensity: hdr.settings.environmentIntensity ?? 1, environmentRotation: [0, hdr.settings.environmentRotation ?? Math.PI / 2, 0], blur: hdr.settings.blur ?? 0, ground: {
60
+ if (!singleUrl && !backgroundName)
61
+ return null;
62
+ const sourceProps = singleUrl
63
+ ? { files: singleUrl }
64
+ : { preset: backgroundName };
65
+ return (_jsx(Environment, { ...sourceProps, background: true, scene: scene, backgroundBlurriness: hdr.settings.backgroundBlurriness ?? 0, backgroundIntensity: hdr.settings.backgroundIntensity ?? 1, backgroundRotation: [0, hdr.settings.backgroundRotation ?? Math.PI / 2, 0], environmentIntensity: hdr.settings.environmentIntensity ?? 1, environmentRotation: [0, hdr.settings.environmentRotation ?? Math.PI / 2, 0], blur: hdr.settings.blur ?? 0, ground: {
70
66
  radius: hdr.settings.groundRadius,
71
67
  height: hdr.settings.groundHeight,
72
68
  scale: hdr.settings.groundScale,
73
69
  } }));
74
- }, [backgroundName, hdr.settings, scene]);
75
- return (_jsxs(_Fragment, { children: [environmentComponent, _jsx(SkySystem, { ref: ref, timeSettings: timeSettings, visible: true, sunSystemVisible: sunSystemVisible, sunSize: sunSize, moonSize: moonSize, skyboxScale: skyboxScale, sunIntensity: sunIntensity, castShadow: castShadow, shadowMapSize: shadowMapSize, playerPosition: playerPosition, starsSettings: starsSettings, cloudsSettings: cloudsSettings, cloudsMaterialSettings: cloudsMaterialSettings, skyControllerSettings: skyControllerSettings, rainSettings: rainSettings, fogSettings: fogSettings, onTimeOfDayChange: onTimeOfDayChange, onTimeUpdate: onTimeUpdate })] }));
70
+ }, [playlistActive, singleUrl, backgroundName, hdr.settings, scene]);
71
+ return (_jsxs(_Fragment, { children: [environmentComponent, playlistActive && urls && (_jsx(HdrPlaylistRenderer, { urls: urls, transition: getTransitionSettings(hdr) })), _jsx(SkySystem, { ref: ref, timeSettings: timeSettings, visible: true, sunSystemVisible: sunSystemVisible, sunSize: sunSize, moonSize: moonSize, skyboxScale: skyboxScale, sunIntensity: sunIntensity, castShadow: castShadow, shadowMapSize: shadowMapSize, playerPosition: playerPosition, starsSettings: starsSettings, cloudsSettings: cloudsSettings, cloudsMaterialSettings: cloudsMaterialSettings, skyControllerSettings: skyControllerSettings, rainSettings: rainSettings, fogSettings: fogSettings, onTimeOfDayChange: onTimeOfDayChange, onTimeUpdate: onTimeUpdate })] }));
76
72
  });
77
73
  EnvironmentManager.displayName = 'EnvironmentManager';
78
74
  export default EnvironmentManager;
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
- import type { StarsSettings } from '@ntalmagor/3drize-core';
2
+ import type { StarsSettings, TimeSettings } from '@ntalmagor/3drize-core';
3
3
  export interface GalaxyProps {
4
4
  settings: StarsSettings;
5
+ timeSettings?: TimeSettings;
6
+ sunSystemEnabled?: boolean;
5
7
  }
6
8
  declare const Galaxy: React.FC<GalaxyProps>;
7
9
  export default Galaxy;
@@ -1 +1 @@
1
- {"version":3,"file":"Galaxy.d.ts","sourceRoot":"","sources":["../../src/components/Galaxy.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAI5D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CASjC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Galaxy.d.ts","sourceRoot":"","sources":["../../src/components/Galaxy.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI1E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAajC,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Stars from './Stars.js';
3
3
  import ShootingStars from './ShootingStars.js';
4
- const Galaxy = ({ settings }) => {
4
+ const Galaxy = ({ settings, timeSettings, sunSystemEnabled }) => {
5
5
  // if (!settings.visible) return null;
6
- return (_jsxs(_Fragment, { children: [_jsx(Stars, { settings: settings }), _jsx(ShootingStars, { settings: settings.shootingStars })] }));
6
+ return (_jsxs(_Fragment, { children: [_jsx(Stars, { settings: settings, timeSettings: timeSettings, sunSystemEnabled: sunSystemEnabled }), _jsx(ShootingStars, { settings: settings.shootingStars })] }));
7
7
  };
8
8
  export default Galaxy;
@@ -0,0 +1,10 @@
1
+ import type { HdrTransitionSettings } from '@ntalmagor/3drize-core';
2
+ export interface HdrPlaylistRendererProps {
3
+ /** Two or more equirect HDR URLs to cycle through. Caller ensures length >= 2. */
4
+ urls: string[];
5
+ /** Fully resolved transition settings (defaults already merged). */
6
+ transition: HdrTransitionSettings;
7
+ }
8
+ declare const HdrPlaylistRenderer: React.FC<HdrPlaylistRendererProps>;
9
+ export default HdrPlaylistRenderer;
10
+ //# sourceMappingURL=HdrPlaylistRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HdrPlaylistRenderer.d.ts","sourceRoot":"","sources":["../../src/components/HdrPlaylistRenderer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAqB,MAAM,wBAAwB,CAAC;AAEvF,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,oEAAoE;IACpE,UAAU,EAAE,qBAAqB,CAAC;CACnC;AA8KD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAgP3D,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,364 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useFrame, useThree } from '@react-three/fiber';
4
+ import * as THREE from 'three';
5
+ import { gsap } from 'gsap';
6
+ import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
7
+ const CUBE_SIZE = 256;
8
+ const MODE_INDEX = {
9
+ crossfade: 0,
10
+ wipe: 1,
11
+ iris: 2,
12
+ dissolve: 3,
13
+ 'vertical-divider': 4,
14
+ 'horizontal-divider': 5,
15
+ };
16
+ const isDividerMode = (m) => m === 4 || m === 5;
17
+ // ── Shaders ─────────────────────────────────────────────────────────────────
18
+ const cubeVertexShader = /* glsl */ `
19
+ varying vec3 vDir;
20
+ void main() {
21
+ vDir = position;
22
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
23
+ }
24
+ `;
25
+ const cubeFragmentShader = /* glsl */ `
26
+ precision highp float;
27
+ uniform samplerCube tA;
28
+ uniform samplerCube tB;
29
+ uniform float uMix;
30
+ uniform int uMode;
31
+ uniform vec3 uWipeAxis;
32
+ uniform vec3 uIrisCenter;
33
+ uniform float uNoiseScale;
34
+ uniform float uFeather;
35
+ varying vec3 vDir;
36
+
37
+ float hash3(vec3 p) {
38
+ p = fract(p * vec3(0.1031, 0.1030, 0.0973));
39
+ p += dot(p, p.yzx + 33.33);
40
+ return fract((p.x + p.y) * p.z);
41
+ }
42
+
43
+ float noise3(vec3 p) {
44
+ vec3 i = floor(p);
45
+ vec3 f = fract(p);
46
+ f = f * f * (3.0 - 2.0 * f);
47
+ float n000 = hash3(i);
48
+ float n100 = hash3(i + vec3(1.0, 0.0, 0.0));
49
+ float n010 = hash3(i + vec3(0.0, 1.0, 0.0));
50
+ float n110 = hash3(i + vec3(1.0, 1.0, 0.0));
51
+ float n001 = hash3(i + vec3(0.0, 0.0, 1.0));
52
+ float n101 = hash3(i + vec3(1.0, 0.0, 1.0));
53
+ float n011 = hash3(i + vec3(0.0, 1.0, 1.0));
54
+ float n111 = hash3(i + vec3(1.0, 1.0, 1.0));
55
+ float nx00 = mix(n000, n100, f.x);
56
+ float nx10 = mix(n010, n110, f.x);
57
+ float nx01 = mix(n001, n101, f.x);
58
+ float nx11 = mix(n011, n111, f.x);
59
+ float nxy0 = mix(nx00, nx10, f.y);
60
+ float nxy1 = mix(nx01, nx11, f.y);
61
+ return mix(nxy0, nxy1, f.z);
62
+ }
63
+
64
+ void main() {
65
+ vec3 dir = normalize(vDir);
66
+ vec4 a = textureCube(tA, dir);
67
+ vec4 b = textureCube(tB, dir);
68
+ float blend;
69
+
70
+ if (uMode == 0) {
71
+ blend = uMix;
72
+ } else if (uMode == 1) {
73
+ float s = dot(dir, normalize(uWipeAxis)) * 0.5 + 0.5;
74
+ float adj = uMix * (1.0 + 2.0 * uFeather) - uFeather;
75
+ blend = 1.0 - smoothstep(adj - uFeather, adj + uFeather, s);
76
+ } else if (uMode == 2) {
77
+ float ang = acos(clamp(dot(dir, normalize(uIrisCenter)), -1.0, 1.0));
78
+ float radius = uMix * (3.14159265 + 2.0 * uFeather) - uFeather;
79
+ blend = 1.0 - smoothstep(radius - uFeather, radius + uFeather, ang);
80
+ } else {
81
+ float n = noise3(dir * uNoiseScale);
82
+ float adj = uMix * (1.0 + 2.0 * uFeather) - uFeather;
83
+ blend = 1.0 - smoothstep(adj - uFeather, adj + uFeather, n);
84
+ }
85
+
86
+ gl_FragColor = mix(a, b, blend);
87
+ }
88
+ `;
89
+ const bgVertexShader = /* glsl */ `
90
+ uniform mat4 uCamWorldMatrix;
91
+ uniform mat4 uProjMatrixInv;
92
+ varying vec3 vWorldDir;
93
+ varying vec2 vScreenUV;
94
+ void main() {
95
+ vScreenUV = position.xy * 0.5 + 0.5;
96
+ vec4 viewPos = uProjMatrixInv * vec4(position.xy, 1.0, 1.0);
97
+ viewPos /= viewPos.w;
98
+ vWorldDir = (uCamWorldMatrix * vec4(viewPos.xyz, 0.0)).xyz;
99
+ gl_Position = vec4(position.xy, 1.0, 1.0);
100
+ }
101
+ `;
102
+ const bgFragmentShader = /* glsl */ `
103
+ precision highp float;
104
+ uniform samplerCube tA;
105
+ uniform samplerCube tB;
106
+ uniform int uMode;
107
+ uniform float uDividerX;
108
+ uniform float uDividerY;
109
+ uniform float uFeather;
110
+ varying vec3 vWorldDir;
111
+ varying vec2 vScreenUV;
112
+ void main() {
113
+ vec3 dir = normalize(vWorldDir);
114
+ vec4 a = textureCube(tA, dir);
115
+ vec4 b = textureCube(tB, dir);
116
+ float blend;
117
+ if (uMode == 4) {
118
+ blend = smoothstep(uDividerX - uFeather, uDividerX + uFeather, vScreenUV.x);
119
+ } else {
120
+ blend = smoothstep(uDividerY - uFeather, uDividerY + uFeather, vScreenUV.y);
121
+ }
122
+ gl_FragColor = mix(a, b, blend);
123
+ }
124
+ `;
125
+ // ── Loader ──────────────────────────────────────────────────────────────────
126
+ const loadEquirectHdr = (url) => new Promise((resolve, reject) => {
127
+ const loader = new RGBELoader();
128
+ loader.setDataType(THREE.HalfFloatType);
129
+ loader.load(url, (tex) => resolve(tex), undefined, reject);
130
+ });
131
+ // ── Order policy ────────────────────────────────────────────────────────────
132
+ const pickNext = (order, current, length, directionRef) => {
133
+ if (length <= 1)
134
+ return current;
135
+ if (order === 'sequential')
136
+ return (current + 1) % length;
137
+ if (order === 'random') {
138
+ let pick = current;
139
+ while (pick === current)
140
+ pick = Math.floor(Math.random() * length);
141
+ return pick;
142
+ }
143
+ // yoyo
144
+ let next = current + directionRef.current;
145
+ if (next >= length) {
146
+ directionRef.current = -1;
147
+ next = current - 1;
148
+ }
149
+ else if (next < 0) {
150
+ directionRef.current = 1;
151
+ next = current + 1;
152
+ }
153
+ return next;
154
+ };
155
+ // ── Component ───────────────────────────────────────────────────────────────
156
+ const HdrPlaylistRenderer = ({ urls, transition }) => {
157
+ const { scene, gl: renderer, camera } = useThree();
158
+ const mixRef = useRef(0);
159
+ const tlRef = useRef(null);
160
+ const stateRef = useRef(null);
161
+ const modeRef = useRef(MODE_INDEX[transition.types[0] ?? 'crossfade']);
162
+ const currentIdxRef = useRef(0);
163
+ const yoyoDirRef = useRef(1);
164
+ const typeIdxRef = useRef(0);
165
+ // Live transition snapshot — read each frame so slider changes apply instantly
166
+ // without rebuilding GPU resources or the timeline.
167
+ const transitionRef = useRef(transition);
168
+ useEffect(() => {
169
+ transitionRef.current = transition;
170
+ }, [transition]);
171
+ const bgMeshRef = useRef(null);
172
+ // Gates the timeline-build effect on the cube-RT pipeline being ready.
173
+ // Replaces a previous rAF poll — flips false → true once HDRs load and
174
+ // stateRef.current is populated, and back to false on URL/teardown changes.
175
+ const [pipelineReady, setPipelineReady] = useState(false);
176
+ // Stable uniforms object for the bg shader. Must NOT be inline JSX literal —
177
+ // R3F reassigns material.uniforms on parent re-render and would wipe tA/tB.
178
+ const bgUniforms = useMemo(() => ({
179
+ tA: { value: null },
180
+ tB: { value: null },
181
+ uMode: { value: 4 },
182
+ uDividerX: { value: 0.5 },
183
+ uDividerY: { value: 0.5 },
184
+ uFeather: { value: transition.dividerFeather ?? 0.003 },
185
+ uCamWorldMatrix: { value: new THREE.Matrix4() },
186
+ uProjMatrixInv: { value: new THREE.Matrix4() },
187
+ }),
188
+ // eslint-disable-next-line react-hooks/exhaustive-deps
189
+ []);
190
+ // ── Build cube RTs + blend pipeline (rebuilds only when urls change) ──────
191
+ const urlsKey = urls.join('|');
192
+ useEffect(() => {
193
+ let disposed = false;
194
+ (async () => {
195
+ let equirects = [];
196
+ try {
197
+ equirects = await Promise.all(urls.map(loadEquirectHdr));
198
+ }
199
+ catch (err) {
200
+ console.error('[HdrPlaylistRenderer] HDR load failed', err);
201
+ return;
202
+ }
203
+ if (disposed) {
204
+ equirects.forEach((t) => t.dispose());
205
+ return;
206
+ }
207
+ const cubeRTOpts = {
208
+ type: THREE.HalfFloatType,
209
+ format: THREE.RGBAFormat,
210
+ generateMipmaps: true,
211
+ minFilter: THREE.LinearMipmapLinearFilter,
212
+ };
213
+ const cubeRTs = equirects.map((eq) => {
214
+ const rt = new THREE.WebGLCubeRenderTarget(CUBE_SIZE, cubeRTOpts);
215
+ rt.fromEquirectangularTexture(renderer, eq);
216
+ eq.dispose();
217
+ return rt;
218
+ });
219
+ const outRT = new THREE.WebGLCubeRenderTarget(CUBE_SIZE, cubeRTOpts);
220
+ const cubeCamera = new THREE.CubeCamera(0.1, 10, outRT);
221
+ const material = new THREE.ShaderMaterial({
222
+ uniforms: {
223
+ tA: { value: cubeRTs[0].texture },
224
+ tB: { value: cubeRTs[1 % cubeRTs.length].texture },
225
+ uMix: { value: 0 },
226
+ uMode: { value: 0 },
227
+ uWipeAxis: { value: new THREE.Vector3(0, 1, 0) },
228
+ uIrisCenter: { value: new THREE.Vector3(0, 1, 0) },
229
+ uNoiseScale: { value: 4.0 },
230
+ uFeather: { value: 0.07 },
231
+ },
232
+ vertexShader: cubeVertexShader,
233
+ fragmentShader: cubeFragmentShader,
234
+ side: THREE.BackSide,
235
+ });
236
+ const geo = new THREE.BoxGeometry(1, 1, 1);
237
+ const mesh = new THREE.Mesh(geo, material);
238
+ const blendScene = new THREE.Scene();
239
+ blendScene.add(mesh);
240
+ blendScene.add(cubeCamera);
241
+ stateRef.current = {
242
+ blendScene,
243
+ cubeCamera,
244
+ outRT,
245
+ material,
246
+ cubeRTs,
247
+ mesh,
248
+ };
249
+ bgUniforms.tA.value = cubeRTs[0].texture;
250
+ bgUniforms.tB.value = cubeRTs[1 % cubeRTs.length].texture;
251
+ currentIdxRef.current = 0;
252
+ yoyoDirRef.current = 1;
253
+ // Signal readiness AFTER stateRef is populated. The timeline-build effect
254
+ // listens for this and runs without polling.
255
+ setPipelineReady(true);
256
+ })();
257
+ return () => {
258
+ disposed = true;
259
+ setPipelineReady(false);
260
+ const s = stateRef.current;
261
+ if (s) {
262
+ s.outRT.dispose();
263
+ s.cubeRTs.forEach((rt) => rt.dispose());
264
+ s.material.dispose();
265
+ s.mesh.geometry.dispose();
266
+ }
267
+ stateRef.current = null;
268
+ };
269
+ // eslint-disable-next-line react-hooks/exhaustive-deps
270
+ }, [urlsKey, renderer, bgUniforms]);
271
+ // ── Build/rebuild GSAP timeline when timing-or-order props change ──────────
272
+ // Gated on `pipelineReady` so we never run before stateRef.current is
273
+ // populated — no rAF polling needed; React schedules us once the pipeline
274
+ // effect calls setPipelineReady(true).
275
+ useEffect(() => {
276
+ if (!pipelineReady)
277
+ return;
278
+ const s = stateRef.current;
279
+ if (!s)
280
+ return;
281
+ let cancelled = false;
282
+ tlRef.current?.kill();
283
+ const fadeSec = Math.max(0.05, transition.durationMs / 1000);
284
+ const holdSec = Math.max(0, transition.holdMs / 1000);
285
+ const stepTo = (target) => {
286
+ const tl = gsap.timeline();
287
+ tl.to({}, { duration: holdSec });
288
+ tl.to(mixRef, { current: target, duration: fadeSec, ease: 'sine.inOut' });
289
+ return tl;
290
+ };
291
+ const advance = () => {
292
+ if (cancelled || !stateRef.current)
293
+ return;
294
+ const st = stateRef.current;
295
+ const length = st.cubeRTs.length;
296
+ const cur = currentIdxRef.current;
297
+ const next = pickNext(transitionRef.current.order, cur, length, yoyoDirRef);
298
+ const types = transitionRef.current.types?.length
299
+ ? transitionRef.current.types
300
+ : ['crossfade'];
301
+ const legType = types[typeIdxRef.current % types.length];
302
+ const legMode = MODE_INDEX[legType];
303
+ st.material.uniforms.uMode.value = isDividerMode(legMode) ? 0 : legMode;
304
+ st.material.uniforms.tA.value = st.cubeRTs[cur].texture;
305
+ st.material.uniforms.tB.value = st.cubeRTs[next].texture;
306
+ bgUniforms.tA.value = st.cubeRTs[cur].texture;
307
+ bgUniforms.tB.value = st.cubeRTs[next].texture;
308
+ modeRef.current = legMode;
309
+ mixRef.current = 0;
310
+ const tl = stepTo(1);
311
+ tl.eventCallback('onComplete', () => {
312
+ currentIdxRef.current = next;
313
+ typeIdxRef.current += 1;
314
+ if (!cancelled)
315
+ advance();
316
+ });
317
+ tlRef.current = tl;
318
+ };
319
+ advance();
320
+ return () => {
321
+ cancelled = true;
322
+ tlRef.current?.kill();
323
+ tlRef.current = null;
324
+ };
325
+ }, [pipelineReady, transition.durationMs, transition.holdMs, transition.order, urlsKey, bgUniforms]);
326
+ // ── Per-frame: render the blend and assign to scene ─────────────────────────
327
+ useFrame(() => {
328
+ const s = stateRef.current;
329
+ if (!s)
330
+ return;
331
+ const t = transitionRef.current;
332
+ s.material.uniforms.uMix.value = mixRef.current;
333
+ s.material.uniforms.uFeather.value = t.feather ?? 0.07;
334
+ s.material.uniforms.uNoiseScale.value = t.noiseScale ?? 4.0;
335
+ if (t.wipeAxis)
336
+ s.material.uniforms.uWipeAxis.value.set(...t.wipeAxis);
337
+ if (t.irisCenter)
338
+ s.material.uniforms.uIrisCenter.value.set(...t.irisCenter);
339
+ s.cubeCamera.update(renderer, s.blendScene);
340
+ scene.environment = s.outRT.texture;
341
+ const mode = modeRef.current;
342
+ const divider = isDividerMode(mode);
343
+ if (divider) {
344
+ scene.background = null;
345
+ if (bgMeshRef.current)
346
+ bgMeshRef.current.visible = true;
347
+ const feather = t.dividerFeather ?? 0.003;
348
+ const dividerPos = 1.0 - (mixRef.current * (1.0 + 2.0 * feather) - feather);
349
+ bgUniforms.uMode.value = mode;
350
+ bgUniforms.uDividerX.value = mode === 4 ? dividerPos : 0.5;
351
+ bgUniforms.uDividerY.value = mode === 5 ? dividerPos : 0.5;
352
+ bgUniforms.uFeather.value = feather;
353
+ bgUniforms.uCamWorldMatrix.value.copy(camera.matrixWorld);
354
+ bgUniforms.uProjMatrixInv.value.copy(camera.projectionMatrixInverse);
355
+ }
356
+ else {
357
+ scene.background = s.outRT.texture;
358
+ if (bgMeshRef.current)
359
+ bgMeshRef.current.visible = false;
360
+ }
361
+ });
362
+ return (_jsxs("mesh", { ref: bgMeshRef, frustumCulled: false, renderOrder: -1000, visible: false, children: [_jsx("planeGeometry", { args: [2, 2] }), _jsx("shaderMaterial", { depthTest: false, depthWrite: false, uniforms: bgUniforms, vertexShader: bgVertexShader, fragmentShader: bgFragmentShader })] }));
363
+ };
364
+ export default HdrPlaylistRenderer;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { CreatedObjectSettings } from '@ntalmagor/3drize-core';
3
+ type LightObjectProps = {
4
+ createdObject: CreatedObjectSettings;
5
+ };
6
+ declare const LightObject: React.FC<LightObjectProps>;
7
+ export default LightObject;
8
+ //# sourceMappingURL=LightObject.d.ts.map