@popaya/pgsg-viewer 0.1.8 → 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-KDDVV5TI.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-RWHLWC6Z.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-RWHLWC6Z.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-RWHLWC6Z.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();
@@ -949,7 +1093,6 @@ var PGSGThreeViewer = class {
949
1093
  if (this.options.colliderSource) {
950
1094
  await this.loadCollider(this.options.colliderSource);
951
1095
  }
952
- this.scene.updateMatrixWorld(true);
953
1096
  if (this.isMobile()) {
954
1097
  this.initJoystick();
955
1098
  this.initModeToggle();
@@ -977,6 +1120,26 @@ var PGSGThreeViewer = class {
977
1120
  const now = performance.now();
978
1121
  const delta = (now - this.lastTime) / 1e3;
979
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
+ }
980
1143
  this.controls?.update?.(delta);
981
1144
  this.renderer.render(this.scene, this.camera);
982
1145
  });
@@ -1001,6 +1164,7 @@ var PGSGThreeViewer = class {
1001
1164
  this.renderer.dispose();
1002
1165
  }
1003
1166
  setCameraMode(mode) {
1167
+ this.currentMode = mode;
1004
1168
  console.log("[PGSGThreeViewer] setCameraMode:", mode);
1005
1169
  if (this.controls instanceof MeasurementController) {
1006
1170
  this.persistentMeasurements = this.controls.measurements;
@@ -1021,8 +1185,11 @@ var PGSGThreeViewer = class {
1021
1185
  case "walk":
1022
1186
  this.switchToWalk();
1023
1187
  break;
1188
+ case "floorplan":
1189
+ this.switchToFloorplan();
1190
+ break;
1024
1191
  default:
1025
- this.switchToMeasure();
1192
+ this.switchToOrbit();
1026
1193
  break;
1027
1194
  }
1028
1195
  }
@@ -1035,15 +1202,37 @@ var PGSGThreeViewer = class {
1035
1202
  this.updateModeLabel("WALK");
1036
1203
  }
1037
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
+ }
1038
1227
  initRenderer() {
1039
1228
  if (this.renderer) return;
1040
- this.renderer = new THREE4.WebGLRenderer({
1229
+ this.renderer = new THREE5.WebGLRenderer({
1041
1230
  antialias: this.options.renderer?.antialias ?? false,
1042
1231
  alpha: false,
1043
1232
  powerPreference: "high-performance"
1044
1233
  });
1045
1234
  this.renderer.setClearColor(1118481, 1);
1046
- this.renderer.outputColorSpace = THREE4.SRGBColorSpace;
1235
+ this.renderer.outputColorSpace = THREE5.SRGBColorSpace;
1047
1236
  this.renderer.setSize(
1048
1237
  this.container.clientWidth,
1049
1238
  this.container.clientHeight
@@ -1051,6 +1240,7 @@ var PGSGThreeViewer = class {
1051
1240
  this.renderer.setPixelRatio(
1052
1241
  this.options.renderer?.pixelRatio ?? window.devicePixelRatio
1053
1242
  );
1243
+ this.renderer.localClippingEnabled = true;
1054
1244
  this.container.appendChild(this.renderer.domElement);
1055
1245
  this.renderer.domElement.addEventListener("webglcontextlost", (e) => {
1056
1246
  e.preventDefault();
@@ -1058,14 +1248,15 @@ var PGSGThreeViewer = class {
1058
1248
  });
1059
1249
  }
1060
1250
  initScene() {
1061
- this.scene = new THREE4.Scene();
1062
- this.scene.background = new THREE4.Color(1118481);
1063
- const gridHelper = new THREE4.GridHelper(10, 10);
1064
- 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);
1065
1256
  this.scene.add(light);
1066
1257
  }
1067
1258
  initCamera() {
1068
- this.camera = new THREE4.PerspectiveCamera(
1259
+ this.camera = new THREE5.PerspectiveCamera(
1069
1260
  this.options.camera?.fov ?? 60,
1070
1261
  this.container.clientWidth / this.container.clientHeight,
1071
1262
  this.options.camera?.near ?? 0.01,
@@ -1125,9 +1316,9 @@ var PGSGThreeViewer = class {
1125
1316
  this.colliders.length = 0;
1126
1317
  this.colliderMesh.traverse((child) => {
1127
1318
  if (child.isMesh) {
1128
- child.material = new THREE4.MeshBasicMaterial({
1319
+ child.material = new THREE5.MeshBasicMaterial({
1129
1320
  visible: false,
1130
- side: THREE4.DoubleSide
1321
+ side: THREE5.DoubleSide
1131
1322
  });
1132
1323
  child.updateMatrixWorld(true);
1133
1324
  this.colliders.push(child);
@@ -1149,6 +1340,7 @@ var PGSGThreeViewer = class {
1149
1340
  };
1150
1341
  switchToMeasure() {
1151
1342
  this.controls?.dispose();
1343
+ this.clearCeilingClip();
1152
1344
  this.measurementController = new MeasurementController({
1153
1345
  dom: this.renderer.domElement,
1154
1346
  camera: this.camera,
@@ -1160,6 +1352,7 @@ var PGSGThreeViewer = class {
1160
1352
  }
1161
1353
  switchToWalk() {
1162
1354
  this.controls?.dispose();
1355
+ this.clearCeilingClip();
1163
1356
  const walk = new WalkCapsuleController({
1164
1357
  camera: this.camera,
1165
1358
  dom: this.renderer.domElement,
@@ -1176,6 +1369,7 @@ var PGSGThreeViewer = class {
1176
1369
  }
1177
1370
  switchToOrbit() {
1178
1371
  this.controls?.dispose();
1372
+ this.clearCeilingClip();
1179
1373
  const distance = 5;
1180
1374
  this.controls = new OrbitCameraController(
1181
1375
  this.camera,
@@ -1187,30 +1381,53 @@ var PGSGThreeViewer = class {
1187
1381
  }
1188
1382
  );
1189
1383
  }
1190
- getMeasurements() {
1191
- if (this.measurementController) {
1192
- return this.measurementController.getMeasurements();
1193
- }
1194
- 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);
1195
1402
  }
1196
- clearMeasurements() {
1197
- if (this.measurementController) {
1198
- this.measurementController.clearAll();
1199
- this.persistentMeasurements = [];
1200
- }
1403
+ applyCeilingClip(clipY) {
1404
+ const plane = new THREE5.Plane(new THREE5.Vector3(0, -1, 0), clipY);
1405
+ this.renderer.clippingPlanes = [plane];
1201
1406
  }
1202
- finalizePolyline() {
1203
- if (this.controls instanceof MeasurementController) {
1204
- this.controls.finalizePolyline();
1205
- }
1407
+ clearCeilingClip() {
1408
+ this.renderer.clippingPlanes = [];
1206
1409
  }
1207
- deleteMeasurement(index) {
1208
- if (this.measurementController) {
1209
- this.measurementController.deleteMeasurement(index);
1210
- }
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
+ };
1211
1428
  }
1212
1429
  };
1213
1430
  export {
1214
1431
  PGSGThreeViewer
1215
1432
  };
1216
- //# sourceMappingURL=pgsg-three-viewer-KDDVV5TI.js.map
1433
+ //# sourceMappingURL=pgsg-three-viewer-LWLUOAKM.js.map