@popaya/pgsg-viewer 0.1.9 → 0.1.10

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.
@@ -10,7 +10,7 @@ var PGSGViewer = class {
10
10
  const { PGSGPlayCanvasViewer } = await import("./pgsg-playcanvas-viewer-4WORM4WU.js");
11
11
  this.engine = new PGSGPlayCanvasViewer(this.options);
12
12
  } else {
13
- const { PGSGThreeViewer } = await import("./pgsg-three-viewer-TOCXQMZQ.js");
13
+ const { PGSGThreeViewer } = await import("./pgsg-three-viewer-LWLUOAKM.js");
14
14
  this.engine = new PGSGThreeViewer(this.options);
15
15
  }
16
16
  await this.engine.load();
@@ -36,4 +36,4 @@ var PGSGViewer = class {
36
36
  export {
37
37
  PGSGViewer
38
38
  };
39
- //# sourceMappingURL=chunk-3HV5SAZ7.js.map
39
+ //# sourceMappingURL=chunk-OUBHF2NG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewer/pgsg-viewer.ts"],"sourcesContent":["import type {\n ViewerType,\n PGSGViewerEngine,\n CameraMode,\n} from \"../viewers/types\";\nimport type { PGSGViewerOptions } from \"../core/types\";\n\nexport class PGSGViewer {\n private engine!: PGSGViewerEngine;\n\n constructor(\n private options: PGSGViewerOptions & { viewerType?: ViewerType },\n ) {}\n\n async load() {\n const type = this.options.viewerType ?? \"three\";\n\n if (type === \"playcanvas\") {\n const { PGSGPlayCanvasViewer } =\n await import(\"../viewers/playcanvas/pgsg-playcanvas-viewer\");\n this.engine = new PGSGPlayCanvasViewer(this.options);\n } else {\n const { PGSGThreeViewer } =\n await import(\"../viewers/three/pgsg-three-viewer\");\n this.engine = new PGSGThreeViewer(this.options);\n }\n\n await this.engine.load();\n this.engine.start?.();\n }\n\n resize() {\n this.engine?.resize();\n }\n\n destroy() {\n this.engine?.destroy();\n }\n\n setCameraMode(mode: CameraMode) {\n this.engine?.setCameraMode?.(mode);\n }\n\n finalizePolyline() {\n (this.engine as any)?.finalizePolyline?.();\n }\n\n deleteMeasurement(index: number) {\n (this.engine as any)?.deleteMeasurement?.(index);\n }\n}\n"],"mappings":";AAOO,IAAM,aAAN,MAAiB;AAAA,EAGtB,YACU,SACR;AADQ;AAAA,EACP;AAAA,EAJK;AAAA,EAMR,MAAM,OAAO;AACX,UAAM,OAAO,KAAK,QAAQ,cAAc;AAExC,QAAI,SAAS,cAAc;AACzB,YAAM,EAAE,qBAAqB,IAC3B,MAAM,OAAO,sCAA8C;AAC7D,WAAK,SAAS,IAAI,qBAAqB,KAAK,OAAO;AAAA,IACrD,OAAO;AACL,YAAM,EAAE,gBAAgB,IACtB,MAAM,OAAO,iCAAoC;AACnD,WAAK,SAAS,IAAI,gBAAgB,KAAK,OAAO;AAAA,IAChD;AAEA,UAAM,KAAK,OAAO,KAAK;AACvB,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA,EAEA,SAAS;AACP,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA,EAEA,UAAU;AACR,SAAK,QAAQ,QAAQ;AAAA,EACvB;AAAA,EAEA,cAAc,MAAkB;AAC9B,SAAK,QAAQ,gBAAgB,IAAI;AAAA,EACnC;AAAA,EAEA,mBAAmB;AACjB,IAAC,KAAK,QAAgB,mBAAmB;AAAA,EAC3C;AAAA,EAEA,kBAAkB,OAAe;AAC/B,IAAC,KAAK,QAAgB,oBAAoB,KAAK;AAAA,EACjD;AACF;","names":[]}
@@ -1,4 +1,5 @@
1
1
  type ViewerType = "three" | "playcanvas" | "spark";
2
+ type CameraMode = "walk" | "orbit" | "floorplan" | "measure";
2
3
 
3
4
  type SourceType = "pgsg" | "ply";
4
5
  interface PGSGSource {
@@ -43,7 +44,7 @@ declare class PGSGViewer {
43
44
  load(): Promise<void>;
44
45
  resize(): void;
45
46
  destroy(): void;
46
- setCameraMode(mode: "orbit" | "walk" | "measure"): void;
47
+ setCameraMode(mode: CameraMode): void;
47
48
  finalizePolyline(): void;
48
49
  deleteMeasurement(index: number): void;
49
50
  }
@@ -1,7 +1,7 @@
1
1
  import "../chunk-PYYLHUV6.js";
2
2
  import {
3
3
  PGSGViewer
4
- } from "../chunk-3HV5SAZ7.js";
4
+ } from "../chunk-OUBHF2NG.js";
5
5
  export {
6
6
  PGSGViewer
7
7
  };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import "./chunk-PYYLHUV6.js";
2
2
  import {
3
3
  PGSGViewer
4
- } from "./chunk-3HV5SAZ7.js";
4
+ } from "./chunk-OUBHF2NG.js";
5
5
  export {
6
6
  PGSGViewer
7
7
  };
@@ -1,5 +1,5 @@
1
1
  // src/viewers/three/pgsg-three-viewer.ts
2
- import * as THREE4 from "three";
2
+ import * as THREE5 from "three";
3
3
  import { GLTFLoader } from "three/examples/jsm/Addons.js";
4
4
  import nipplejs from "nipplejs";
5
5
 
@@ -904,6 +904,148 @@ var MeasurementController = class {
904
904
  }
905
905
  };
906
906
 
907
+ // src/viewers/three/controls/FloorplanController.ts
908
+ import * as THREE4 from "three";
909
+ var FloorplanController = class {
910
+ dom;
911
+ camera;
912
+ target;
913
+ height;
914
+ panSpeed;
915
+ zoomSpeed;
916
+ minHeight;
917
+ maxHeight;
918
+ isDragging = false;
919
+ lastX = 0;
920
+ lastY = 0;
921
+ // touch
922
+ touchMode = "none";
923
+ lastDist = 0;
924
+ constructor(opts) {
925
+ this.dom = opts.dom;
926
+ this.camera = opts.camera;
927
+ this.target = opts.target.clone();
928
+ this.height = opts.height;
929
+ this.panSpeed = opts.panSpeed ?? 0.01;
930
+ this.zoomSpeed = opts.zoomSpeed ?? 1.1;
931
+ this.minHeight = opts.minHeight ?? opts.height * 0.25;
932
+ this.maxHeight = opts.maxHeight ?? opts.height * 3;
933
+ this.applyCamera();
934
+ this.bind();
935
+ }
936
+ dispose() {
937
+ this.unbind();
938
+ }
939
+ update(_dt) {
940
+ this.applyCamera();
941
+ }
942
+ setTarget(t) {
943
+ this.target.copy(t);
944
+ this.applyCamera();
945
+ }
946
+ applyCamera() {
947
+ this.height = THREE4.MathUtils.clamp(
948
+ this.height,
949
+ this.minHeight,
950
+ this.maxHeight
951
+ );
952
+ this.camera.position.set(
953
+ this.target.x,
954
+ this.target.y + this.height,
955
+ this.target.z
956
+ );
957
+ this.camera.up.set(0, 0, -1);
958
+ this.camera.lookAt(this.target);
959
+ }
960
+ bind() {
961
+ this.dom.addEventListener("contextmenu", (e) => e.preventDefault());
962
+ this.dom.addEventListener("mousedown", this.onMouseDown);
963
+ window.addEventListener("mousemove", this.onMouseMove);
964
+ window.addEventListener("mouseup", this.onMouseUp);
965
+ this.dom.addEventListener("wheel", this.onWheel, { passive: false });
966
+ this.dom.addEventListener("touchstart", this.onTouchStart, {
967
+ passive: false
968
+ });
969
+ this.dom.addEventListener("touchmove", this.onTouchMove, {
970
+ passive: false
971
+ });
972
+ this.dom.addEventListener("touchend", this.onTouchEnd);
973
+ }
974
+ unbind() {
975
+ this.dom.removeEventListener("mousedown", this.onMouseDown);
976
+ window.removeEventListener("mousemove", this.onMouseMove);
977
+ window.removeEventListener("mouseup", this.onMouseUp);
978
+ this.dom.removeEventListener("wheel", this.onWheel);
979
+ this.dom.removeEventListener("touchstart", this.onTouchStart);
980
+ this.dom.removeEventListener("touchmove", this.onTouchMove);
981
+ this.dom.removeEventListener("touchend", this.onTouchEnd);
982
+ }
983
+ onMouseDown = (e) => {
984
+ if (e.button !== 0) return;
985
+ this.isDragging = true;
986
+ this.lastX = e.clientX;
987
+ this.lastY = e.clientY;
988
+ };
989
+ onMouseUp = () => {
990
+ this.isDragging = false;
991
+ };
992
+ onMouseMove = (e) => {
993
+ if (!this.isDragging) return;
994
+ const dx = e.clientX - this.lastX;
995
+ const dy = e.clientY - this.lastY;
996
+ this.lastX = e.clientX;
997
+ this.lastY = e.clientY;
998
+ this.target.x -= dx * this.panSpeed * this.height;
999
+ this.target.z += dy * this.panSpeed * this.height;
1000
+ this.applyCamera();
1001
+ };
1002
+ onWheel = (e) => {
1003
+ e.preventDefault();
1004
+ const factor = Math.pow(this.zoomSpeed, e.deltaY / 100);
1005
+ this.height *= factor;
1006
+ this.applyCamera();
1007
+ };
1008
+ onTouchStart = (e) => {
1009
+ if (e.touches.length === 1) {
1010
+ this.touchMode = "pan";
1011
+ this.lastX = e.touches[0].clientX;
1012
+ this.lastY = e.touches[0].clientY;
1013
+ } else if (e.touches.length === 2) {
1014
+ this.touchMode = "pinch";
1015
+ this.lastDist = this.touchDistance(e.touches[0], e.touches[1]);
1016
+ }
1017
+ };
1018
+ onTouchMove = (e) => {
1019
+ e.preventDefault();
1020
+ if (this.touchMode === "pan" && e.touches.length === 1) {
1021
+ const x = e.touches[0].clientX;
1022
+ const y = e.touches[0].clientY;
1023
+ const dx = x - this.lastX;
1024
+ const dy = y - this.lastY;
1025
+ this.lastX = x;
1026
+ this.lastY = y;
1027
+ this.target.x -= dx * this.panSpeed * this.height;
1028
+ this.target.z += dy * this.panSpeed * this.height;
1029
+ this.applyCamera();
1030
+ }
1031
+ if (this.touchMode === "pinch" && e.touches.length === 2) {
1032
+ const dist = this.touchDistance(e.touches[0], e.touches[1]);
1033
+ const ratio = this.lastDist / Math.max(1e-6, dist);
1034
+ this.lastDist = dist;
1035
+ this.height *= ratio;
1036
+ this.applyCamera();
1037
+ }
1038
+ };
1039
+ onTouchEnd = () => {
1040
+ this.touchMode = "none";
1041
+ };
1042
+ touchDistance(a, b) {
1043
+ const dx = a.clientX - b.clientX;
1044
+ const dy = a.clientY - b.clientY;
1045
+ return Math.sqrt(dx * dx + dy * dy);
1046
+ }
1047
+ };
1048
+
907
1049
  // src/viewers/three/pgsg-three-viewer.ts
908
1050
  var PGSGThreeViewer = class {
909
1051
  constructor(options) {
@@ -914,7 +1056,7 @@ var PGSGThreeViewer = class {
914
1056
  throw new Error("[PGSGThreeViewer] Container is required");
915
1057
  }
916
1058
  }
917
- scene = new THREE4.Scene();
1059
+ scene = new THREE5.Scene();
918
1060
  camera;
919
1061
  renderer;
920
1062
  splats;
@@ -922,15 +1064,17 @@ var PGSGThreeViewer = class {
922
1064
  resizeObserver = null;
923
1065
  controls;
924
1066
  running = false;
925
- orbitTarget = new THREE4.Vector3();
1067
+ orbitTarget = new THREE5.Vector3();
926
1068
  lastTime = performance.now();
927
1069
  colliders = [];
928
1070
  persistentMeasurements = [];
929
1071
  measurementController = null;
930
- worldRoot = new THREE4.Object3D();
931
- splatPivot = new THREE4.Object3D();
1072
+ worldRoot = new THREE5.Object3D();
1073
+ splatPivot = new THREE5.Object3D();
932
1074
  colliderMesh;
933
1075
  modeButton;
1076
+ currentMode = "orbit";
1077
+ camTween = null;
934
1078
  async load() {
935
1079
  this.initRenderer();
936
1080
  this.initCamera();
@@ -944,11 +1088,11 @@ var PGSGThreeViewer = class {
944
1088
  this.splatPivot.add(mesh);
945
1089
  this.worldRoot.add(this.splatPivot);
946
1090
  this.worldRoot.rotation.x = Math.PI;
1091
+ this.worldRoot.position.y -= bounds.min.y;
947
1092
  this.worldRoot.updateMatrixWorld(true);
948
1093
  if (this.options.colliderSource) {
949
1094
  await this.loadCollider(this.options.colliderSource);
950
1095
  }
951
- this.scene.updateMatrixWorld(true);
952
1096
  if (this.isMobile()) {
953
1097
  this.initJoystick();
954
1098
  this.initModeToggle();
@@ -976,6 +1120,26 @@ var PGSGThreeViewer = class {
976
1120
  const now = performance.now();
977
1121
  const delta = (now - this.lastTime) / 1e3;
978
1122
  this.lastTime = now;
1123
+ if (this.camTween) {
1124
+ this.camTween.t += delta;
1125
+ const a = THREE5.MathUtils.clamp(
1126
+ this.camTween.t / this.camTween.dur,
1127
+ 0,
1128
+ 1
1129
+ );
1130
+ const s = a * a * (3 - 2 * a);
1131
+ this.camera.position.lerpVectors(
1132
+ this.camTween.fromPos,
1133
+ this.camTween.toPos,
1134
+ s
1135
+ );
1136
+ this.camera.quaternion.slerpQuaternions(
1137
+ this.camTween.fromQuat,
1138
+ this.camTween.toQuat,
1139
+ s
1140
+ );
1141
+ if (a >= 1) this.camTween = null;
1142
+ }
979
1143
  this.controls?.update?.(delta);
980
1144
  this.renderer.render(this.scene, this.camera);
981
1145
  });
@@ -1000,6 +1164,7 @@ var PGSGThreeViewer = class {
1000
1164
  this.renderer.dispose();
1001
1165
  }
1002
1166
  setCameraMode(mode) {
1167
+ this.currentMode = mode;
1003
1168
  console.log("[PGSGThreeViewer] setCameraMode:", mode);
1004
1169
  if (this.controls instanceof MeasurementController) {
1005
1170
  this.persistentMeasurements = this.controls.measurements;
@@ -1020,8 +1185,11 @@ var PGSGThreeViewer = class {
1020
1185
  case "walk":
1021
1186
  this.switchToWalk();
1022
1187
  break;
1188
+ case "floorplan":
1189
+ this.switchToFloorplan();
1190
+ break;
1023
1191
  default:
1024
- this.switchToMeasure();
1192
+ this.switchToOrbit();
1025
1193
  break;
1026
1194
  }
1027
1195
  }
@@ -1034,15 +1202,37 @@ var PGSGThreeViewer = class {
1034
1202
  this.updateModeLabel("WALK");
1035
1203
  }
1036
1204
  }
1205
+ getMeasurements() {
1206
+ if (this.measurementController) {
1207
+ return this.measurementController.getMeasurements();
1208
+ }
1209
+ return [];
1210
+ }
1211
+ clearMeasurements() {
1212
+ if (this.measurementController) {
1213
+ this.measurementController.clearAll();
1214
+ this.persistentMeasurements = [];
1215
+ }
1216
+ }
1217
+ finalizePolyline() {
1218
+ if (this.controls instanceof MeasurementController) {
1219
+ this.controls.finalizePolyline();
1220
+ }
1221
+ }
1222
+ deleteMeasurement(index) {
1223
+ if (this.measurementController) {
1224
+ this.measurementController.deleteMeasurement(index);
1225
+ }
1226
+ }
1037
1227
  initRenderer() {
1038
1228
  if (this.renderer) return;
1039
- this.renderer = new THREE4.WebGLRenderer({
1229
+ this.renderer = new THREE5.WebGLRenderer({
1040
1230
  antialias: this.options.renderer?.antialias ?? false,
1041
1231
  alpha: false,
1042
1232
  powerPreference: "high-performance"
1043
1233
  });
1044
1234
  this.renderer.setClearColor(1118481, 1);
1045
- this.renderer.outputColorSpace = THREE4.SRGBColorSpace;
1235
+ this.renderer.outputColorSpace = THREE5.SRGBColorSpace;
1046
1236
  this.renderer.setSize(
1047
1237
  this.container.clientWidth,
1048
1238
  this.container.clientHeight
@@ -1050,6 +1240,7 @@ var PGSGThreeViewer = class {
1050
1240
  this.renderer.setPixelRatio(
1051
1241
  this.options.renderer?.pixelRatio ?? window.devicePixelRatio
1052
1242
  );
1243
+ this.renderer.localClippingEnabled = true;
1053
1244
  this.container.appendChild(this.renderer.domElement);
1054
1245
  this.renderer.domElement.addEventListener("webglcontextlost", (e) => {
1055
1246
  e.preventDefault();
@@ -1057,14 +1248,15 @@ var PGSGThreeViewer = class {
1057
1248
  });
1058
1249
  }
1059
1250
  initScene() {
1060
- this.scene = new THREE4.Scene();
1061
- this.scene.background = new THREE4.Color(1118481);
1062
- const gridHelper = new THREE4.GridHelper(10, 10);
1063
- const light = new THREE4.HemisphereLight(16777215, 2236962, 1);
1251
+ this.scene = new THREE5.Scene();
1252
+ this.scene.background = new THREE5.Color(1118481);
1253
+ const gridHelper = new THREE5.GridHelper(10, 10);
1254
+ this.scene.add(gridHelper);
1255
+ const light = new THREE5.HemisphereLight(16777215, 2236962, 1);
1064
1256
  this.scene.add(light);
1065
1257
  }
1066
1258
  initCamera() {
1067
- this.camera = new THREE4.PerspectiveCamera(
1259
+ this.camera = new THREE5.PerspectiveCamera(
1068
1260
  this.options.camera?.fov ?? 60,
1069
1261
  this.container.clientWidth / this.container.clientHeight,
1070
1262
  this.options.camera?.near ?? 0.01,
@@ -1124,9 +1316,9 @@ var PGSGThreeViewer = class {
1124
1316
  this.colliders.length = 0;
1125
1317
  this.colliderMesh.traverse((child) => {
1126
1318
  if (child.isMesh) {
1127
- child.material = new THREE4.MeshBasicMaterial({
1319
+ child.material = new THREE5.MeshBasicMaterial({
1128
1320
  visible: false,
1129
- side: THREE4.DoubleSide
1321
+ side: THREE5.DoubleSide
1130
1322
  });
1131
1323
  child.updateMatrixWorld(true);
1132
1324
  this.colliders.push(child);
@@ -1148,6 +1340,7 @@ var PGSGThreeViewer = class {
1148
1340
  };
1149
1341
  switchToMeasure() {
1150
1342
  this.controls?.dispose();
1343
+ this.clearCeilingClip();
1151
1344
  this.measurementController = new MeasurementController({
1152
1345
  dom: this.renderer.domElement,
1153
1346
  camera: this.camera,
@@ -1159,6 +1352,7 @@ var PGSGThreeViewer = class {
1159
1352
  }
1160
1353
  switchToWalk() {
1161
1354
  this.controls?.dispose();
1355
+ this.clearCeilingClip();
1162
1356
  const walk = new WalkCapsuleController({
1163
1357
  camera: this.camera,
1164
1358
  dom: this.renderer.domElement,
@@ -1175,6 +1369,7 @@ var PGSGThreeViewer = class {
1175
1369
  }
1176
1370
  switchToOrbit() {
1177
1371
  this.controls?.dispose();
1372
+ this.clearCeilingClip();
1178
1373
  const distance = 5;
1179
1374
  this.controls = new OrbitCameraController(
1180
1375
  this.camera,
@@ -1186,30 +1381,53 @@ var PGSGThreeViewer = class {
1186
1381
  }
1187
1382
  );
1188
1383
  }
1189
- getMeasurements() {
1190
- if (this.measurementController) {
1191
- return this.measurementController.getMeasurements();
1192
- }
1193
- return [];
1384
+ switchToFloorplan() {
1385
+ this.controls?.dispose();
1386
+ const box = new THREE5.Box3().setFromObject(this.worldRoot);
1387
+ const center = box.getCenter(new THREE5.Vector3());
1388
+ const size = box.getSize(new THREE5.Vector3());
1389
+ const height = Math.max(size.x, size.z) * 1.2;
1390
+ this.orbitTarget.copy(center);
1391
+ this.controls = new FloorplanController({
1392
+ dom: this.renderer.domElement,
1393
+ camera: this.camera,
1394
+ target: center,
1395
+ height,
1396
+ panSpeed: 15e-4,
1397
+ // tune
1398
+ zoomSpeed: 1.15
1399
+ });
1400
+ const clipY = this.getCeilingClipY();
1401
+ this.applyCeilingClip(clipY);
1194
1402
  }
1195
- clearMeasurements() {
1196
- if (this.measurementController) {
1197
- this.measurementController.clearAll();
1198
- this.persistentMeasurements = [];
1199
- }
1403
+ applyCeilingClip(clipY) {
1404
+ const plane = new THREE5.Plane(new THREE5.Vector3(0, -1, 0), clipY);
1405
+ this.renderer.clippingPlanes = [plane];
1200
1406
  }
1201
- finalizePolyline() {
1202
- if (this.controls instanceof MeasurementController) {
1203
- this.controls.finalizePolyline();
1204
- }
1407
+ clearCeilingClip() {
1408
+ this.renderer.clippingPlanes = [];
1205
1409
  }
1206
- deleteMeasurement(index) {
1207
- if (this.measurementController) {
1208
- this.measurementController.deleteMeasurement(index);
1209
- }
1410
+ getCeilingClipY() {
1411
+ if (!this.colliderMesh) return 0;
1412
+ const box = new THREE5.Box3().setFromObject(this.colliderMesh);
1413
+ const sizeY = box.max.y - box.min.y;
1414
+ return box.min.y + sizeY * 0.65;
1415
+ }
1416
+ startCameraTween(toPos, toLookAt, dur = 0.45) {
1417
+ const toQuat = new THREE5.Quaternion().setFromRotationMatrix(
1418
+ new THREE5.Matrix4().lookAt(toPos, toLookAt, new THREE5.Vector3(0, 1, 0))
1419
+ );
1420
+ this.camTween = {
1421
+ t: 0,
1422
+ dur,
1423
+ fromPos: this.camera.position.clone(),
1424
+ toPos: toPos.clone(),
1425
+ fromQuat: this.camera.quaternion.clone(),
1426
+ toQuat
1427
+ };
1210
1428
  }
1211
1429
  };
1212
1430
  export {
1213
1431
  PGSGThreeViewer
1214
1432
  };
1215
- //# sourceMappingURL=pgsg-three-viewer-TOCXQMZQ.js.map
1433
+ //# sourceMappingURL=pgsg-three-viewer-LWLUOAKM.js.map