@popaya/pgsg-viewer 0.2.0 → 0.2.2

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-5BUDBGSE.js");
13
+ const { PGSGThreeViewer } = await import("./pgsg-three-viewer-TH7LXVVZ.js");
14
14
  this.engine = new PGSGThreeViewer(this.options);
15
15
  }
16
16
  await this.engine.load();
@@ -45,4 +45,4 @@ var PGSGViewer = class {
45
45
  export {
46
46
  PGSGViewer
47
47
  };
48
- //# sourceMappingURL=chunk-LFICZTG7.js.map
48
+ //# sourceMappingURL=chunk-MNOTPSNH.js.map
@@ -1,7 +1,7 @@
1
1
  import "../chunk-PYYLHUV6.js";
2
2
  import {
3
3
  PGSGViewer
4
- } from "../chunk-LFICZTG7.js";
4
+ } from "../chunk-MNOTPSNH.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-LFICZTG7.js";
4
+ } from "./chunk-MNOTPSNH.js";
5
5
  export {
6
6
  PGSGViewer
7
7
  };
@@ -1,7 +1,10 @@
1
1
  // src/viewers/three/pgsg-three-viewer.ts
2
2
  import * as THREE5 from "three";
3
- import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
4
- import { TransformControls } from "three/addons/controls/TransformControls.js";
3
+ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
4
+ import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
5
+ import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
6
+ import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
7
+ import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
5
8
  import * as RAPIER2 from "@dimforge/rapier3d-compat";
6
9
  import nipplejs from "nipplejs";
7
10
 
@@ -294,6 +297,7 @@ var OrbitCameraController = class {
294
297
  lastTouchY = 0;
295
298
  externalMove = new THREE2.Vector2(0, 0);
296
299
  moveSpeed = 5;
300
+ enabled = true;
297
301
  constructor(camera, dom, opts) {
298
302
  this.camera = camera;
299
303
  this.dom = dom;
@@ -371,6 +375,7 @@ var OrbitCameraController = class {
371
375
  // Input
372
376
  // -----------------------------
373
377
  onMouseDown = (e) => {
378
+ if (!this.enabled) return;
374
379
  if (e.button === 0) {
375
380
  this.dragging = true;
376
381
  } else if (e.button === 2 || e.button === 1) {
@@ -384,6 +389,7 @@ var OrbitCameraController = class {
384
389
  this.panning = false;
385
390
  };
386
391
  onMouseMove = (e) => {
392
+ if (!this.enabled) return;
387
393
  if (!this.dragging && !this.panning) return;
388
394
  const dx = e.clientX - this.lastX;
389
395
  const dy = e.clientY - this.lastY;
@@ -412,6 +418,7 @@ var OrbitCameraController = class {
412
418
  }
413
419
  };
414
420
  onWheel = (e) => {
421
+ if (!this.enabled) return;
415
422
  e.preventDefault();
416
423
  const zoomFactor = Math.pow(this.zoomSpeed, e.deltaY / 100);
417
424
  this.distance *= zoomFactor;
@@ -423,11 +430,13 @@ var OrbitCameraController = class {
423
430
  this.updateCamera();
424
431
  };
425
432
  onTouchStart = (e) => {
433
+ if (!this.enabled) return;
426
434
  const t = e.touches[0];
427
435
  this.lastTouchX = t.clientX;
428
436
  this.lastTouchY = t.clientY;
429
437
  };
430
438
  onTouchMove = (e) => {
439
+ if (!this.enabled) return;
431
440
  const t = e.touches[0];
432
441
  const deltaX = t.clientX - this.lastTouchX;
433
442
  const deltaY = t.clientY - this.lastTouchY;
@@ -1006,6 +1015,14 @@ var PGSGThreeViewer = class {
1006
1015
  placementRotation = 0;
1007
1016
  transformControls;
1008
1017
  inputEnabled = true;
1018
+ placementMouseMoved = false;
1019
+ placedObjects = [];
1020
+ ghostYOffset = 0;
1021
+ composer;
1022
+ hoverOutline;
1023
+ selectOutline;
1024
+ selectedObject;
1025
+ hoveredObject;
1009
1026
  container;
1010
1027
  resizeObserver = null;
1011
1028
  controls;
@@ -1032,21 +1049,30 @@ var PGSGThreeViewer = class {
1032
1049
  this.initRenderer();
1033
1050
  this.initCamera();
1034
1051
  this.initScene();
1052
+ this.initPostProcessing();
1035
1053
  this.bindResize();
1036
1054
  this.resize();
1037
1055
  this.transformControls = new TransformControls(
1038
1056
  this.camera,
1039
1057
  this.renderer.domElement
1040
1058
  );
1041
- this.scene.add(this.transformControls);
1042
- console.log(this.transformControls instanceof THREE5.Object3D);
1059
+ this.scene.add(this.transformControls.getHelper());
1060
+ this.transformControls.getHelper().traverse((child) => {
1061
+ child.userData.ignoreOutline = true;
1062
+ });
1043
1063
  this.transformControls.addEventListener("dragging-changed", (event) => {
1044
1064
  if (!this.controls) return;
1045
1065
  this.controls.enabled = !event.value;
1046
1066
  });
1047
1067
  this.transformControls.addEventListener("mouseDown", () => {
1048
1068
  document.exitPointerLock?.();
1069
+ this.controls.enabled = false;
1070
+ });
1071
+ this.transformControls.addEventListener("mouseUp", () => {
1072
+ this.controls.enabled = true;
1049
1073
  });
1074
+ this.transformControls.setSize(0.75);
1075
+ this.transformControls.setSpace("local");
1050
1076
  await RAPIER2.init();
1051
1077
  this.world = new RAPIER2.World({
1052
1078
  x: 0,
@@ -1061,6 +1087,7 @@ var PGSGThreeViewer = class {
1061
1087
  this.splatPivot.clear();
1062
1088
  this.splatPivot.add(mesh);
1063
1089
  this.cvRoot.add(this.splatPivot);
1090
+ this.createNeutralEnvironment();
1064
1091
  if (this.options.colliderSource) {
1065
1092
  await this.loadCollider(this.options.colliderSource);
1066
1093
  }
@@ -1080,7 +1107,20 @@ var PGSGThreeViewer = class {
1080
1107
  if (e.key === "t") this.transformControls.setMode("translate");
1081
1108
  if (e.key === "s") this.transformControls.setMode("scale");
1082
1109
  });
1083
- this.renderer.domElement.addEventListener("click", this.onSceneClick);
1110
+ this.renderer.domElement.addEventListener("mousedown", () => {
1111
+ this.placementMouseMoved = false;
1112
+ });
1113
+ this.renderer.domElement.addEventListener("mousemove", () => {
1114
+ this.placementMouseMoved = true;
1115
+ });
1116
+ this.renderer.domElement.addEventListener("mouseup", (e) => {
1117
+ if (!this.placementMouseMoved) {
1118
+ this.onSceneClick(e);
1119
+ }
1120
+ });
1121
+ this.renderer.domElement.addEventListener("click", this.onSelectObject);
1122
+ this.renderer.domElement.addEventListener("mousemove", this.onHoverObject);
1123
+ window.addEventListener("keydown", this.onDeleteObject);
1084
1124
  }
1085
1125
  start() {
1086
1126
  if (this.running) return;
@@ -1096,7 +1136,7 @@ var PGSGThreeViewer = class {
1096
1136
  this.acc -= this.fixedDt;
1097
1137
  }
1098
1138
  this.controls?.update?.(delta);
1099
- this.renderer.render(this.scene, this.camera);
1139
+ this.composer.render();
1100
1140
  });
1101
1141
  }
1102
1142
  pause() {
@@ -1107,6 +1147,9 @@ var PGSGThreeViewer = class {
1107
1147
  const w = Math.max(1, this.container.clientWidth);
1108
1148
  const h = Math.max(1, this.container.clientHeight);
1109
1149
  this.renderer.setSize(w, h);
1150
+ this.composer?.setSize(w, h);
1151
+ this.hoverOutline?.setSize(w, h);
1152
+ this.selectOutline?.setSize(w, h);
1110
1153
  this.camera.aspect = w / h;
1111
1154
  this.camera.updateProjectionMatrix();
1112
1155
  }
@@ -1168,6 +1211,7 @@ var PGSGThreeViewer = class {
1168
1211
  this.pendingPlacementUrl = url;
1169
1212
  this.placementRotation = 0;
1170
1213
  console.log("[PGSG Viewer] Placement mode enabled");
1214
+ this.controls.enabled = false;
1171
1215
  const gltf = await this.gltfLoader.loadAsync(url);
1172
1216
  this.ghostModel = gltf.scene;
1173
1217
  const box = new THREE5.Box3().setFromObject(this.ghostModel);
@@ -1188,8 +1232,17 @@ var PGSGThreeViewer = class {
1188
1232
  this.onPlacementMove
1189
1233
  );
1190
1234
  this.renderer.domElement.addEventListener("wheel", this.onPlacementWheel);
1191
- this.renderer.domElement.addEventListener("click", this.onSceneClick);
1192
1235
  window.addEventListener("keydown", this.onPlacementCancel);
1236
+ const rect = this.renderer.domElement.getBoundingClientRect();
1237
+ this.mouse.set(0, 0);
1238
+ this.raycaster.setFromCamera(this.mouse, this.camera);
1239
+ const intersects = this.raycaster.intersectObjects(
1240
+ this.colliderMeshes,
1241
+ true
1242
+ );
1243
+ if (intersects.length) {
1244
+ this.ghostModel.position.copy(intersects[0].point);
1245
+ }
1193
1246
  }
1194
1247
  async addModelFromUrl(url, position) {
1195
1248
  return new Promise((resolve, reject) => {
@@ -1208,6 +1261,14 @@ var PGSGThreeViewer = class {
1208
1261
  }
1209
1262
  const floorBox = new THREE5.Box3().setFromObject(model);
1210
1263
  model.position.y -= floorBox.min.y;
1264
+ model.traverse((child) => {
1265
+ if (!child.isMesh) return;
1266
+ const mat = child.material;
1267
+ if (mat.isMeshStandardMaterial || mat.isMeshPhysicalMaterial) {
1268
+ mat.envMapIntensity = 0.8;
1269
+ mat.needsUpdate = true;
1270
+ }
1271
+ });
1211
1272
  this.cvRoot.add(model);
1212
1273
  resolve(model);
1213
1274
  },
@@ -1238,14 +1299,18 @@ var PGSGThreeViewer = class {
1238
1299
  this.renderer.setPixelRatio(
1239
1300
  this.options.renderer?.pixelRatio ?? window.devicePixelRatio
1240
1301
  );
1302
+ this.renderer.toneMapping = THREE5.ACESFilmicToneMapping;
1303
+ this.renderer.toneMappingExposure = 0.85;
1241
1304
  this.renderer.localClippingEnabled = true;
1242
1305
  this.container.appendChild(this.renderer.domElement);
1243
1306
  }
1244
1307
  initScene() {
1245
1308
  this.scene = new THREE5.Scene();
1246
1309
  this.scene.background = new THREE5.Color(1118481);
1247
- const light = new THREE5.HemisphereLight(16777215, 2236962, 1);
1248
- this.scene.add(light);
1310
+ const hemi = new THREE5.HemisphereLight(16777215, 4473924, 0.25);
1311
+ this.scene.add(hemi);
1312
+ const amb = new THREE5.AmbientLight(16777215, 0.35);
1313
+ this.scene.add(amb);
1249
1314
  }
1250
1315
  initCamera() {
1251
1316
  this.camera = new THREE5.PerspectiveCamera(
@@ -1257,6 +1322,32 @@ var PGSGThreeViewer = class {
1257
1322
  this.camera.position.set(2, 2, 5);
1258
1323
  this.camera.lookAt(0, 0, 0);
1259
1324
  }
1325
+ initPostProcessing() {
1326
+ this.composer = new EffectComposer(this.renderer);
1327
+ this.composer.addPass(new RenderPass(this.scene, this.camera));
1328
+ const size = new THREE5.Vector2(
1329
+ this.container.clientWidth,
1330
+ this.container.clientHeight
1331
+ );
1332
+ this.hoverOutline = new OutlinePass(size, this.scene, this.camera);
1333
+ this.hoverOutline.edgeStrength = 6;
1334
+ this.hoverOutline.edgeGlow = 1;
1335
+ this.hoverOutline.edgeThickness = 2;
1336
+ this.hoverOutline.pulsePeriod = 0;
1337
+ this.hoverOutline.visibleEdgeColor.set("#00e5ff");
1338
+ this.hoverOutline.hiddenEdgeColor.set("#00e5ff");
1339
+ this.composer.addPass(this.hoverOutline);
1340
+ this.selectOutline = new OutlinePass(size, this.scene, this.camera);
1341
+ this.selectOutline.edgeStrength = 8;
1342
+ this.selectOutline.edgeGlow = 1.2;
1343
+ this.selectOutline.edgeThickness = 2.5;
1344
+ this.selectOutline.pulsePeriod = 0;
1345
+ this.selectOutline.visibleEdgeColor.set("#ffd400");
1346
+ this.selectOutline.hiddenEdgeColor.set("#ffd400");
1347
+ this.composer.addPass(this.selectOutline);
1348
+ this.hoverOutline.clear = false;
1349
+ this.selectOutline.clear = false;
1350
+ }
1260
1351
  bindResize() {
1261
1352
  this.resizeObserver?.disconnect();
1262
1353
  this.resizeObserver = new ResizeObserver(() => this.resize());
@@ -1488,17 +1579,39 @@ var PGSGThreeViewer = class {
1488
1579
  }
1489
1580
  return hits;
1490
1581
  }
1582
+ collectMeshes(obj) {
1583
+ const meshes = [];
1584
+ obj.traverse((c) => {
1585
+ if (c.isMesh && !c.userData.ignoreOutline) meshes.push(c);
1586
+ });
1587
+ return meshes;
1588
+ }
1589
+ createNeutralEnvironment() {
1590
+ const pmrem = new THREE5.PMREMGenerator(this.renderer);
1591
+ const envScene = new THREE5.Scene();
1592
+ envScene.background = new THREE5.Color(16777215);
1593
+ const light = new THREE5.HemisphereLight(16777215, 4473924, 1);
1594
+ envScene.add(light);
1595
+ const envMap = pmrem.fromScene(envScene).texture;
1596
+ this.scene.environment = envMap;
1597
+ pmrem.dispose();
1598
+ }
1491
1599
  onSceneClick = async (event) => {
1600
+ if (this.placementMouseMoved) return;
1492
1601
  if (!this.pendingPlacementUrl || !this.ghostModel) return;
1493
1602
  const position = this.ghostModel.position.clone();
1494
1603
  const placed = await this.addModelFromUrl(
1495
1604
  this.pendingPlacementUrl,
1496
1605
  position
1497
1606
  );
1607
+ if (placed) {
1608
+ this.placedObjects.push(placed);
1609
+ }
1498
1610
  if (placed && this.transformControls) {
1499
1611
  placed.rotation.y = this.placementRotation;
1500
1612
  this.transformControls.attach(placed);
1501
1613
  }
1614
+ this.controls.enabled = true;
1502
1615
  this.scene.remove(this.ghostModel);
1503
1616
  this.ghostModel = void 0;
1504
1617
  this.pendingPlacementUrl = void 0;
@@ -1510,7 +1623,6 @@ var PGSGThreeViewer = class {
1510
1623
  "wheel",
1511
1624
  this.onPlacementWheel
1512
1625
  );
1513
- this.renderer.domElement.removeEventListener("click", this.onSceneClick);
1514
1626
  window.removeEventListener("keydown", this.onPlacementCancel);
1515
1627
  };
1516
1628
  onPlacementMove = (event) => {
@@ -1527,7 +1639,10 @@ var PGSGThreeViewer = class {
1527
1639
  );
1528
1640
  if (!intersects.length) return;
1529
1641
  const hit = intersects[0];
1642
+ const box = new THREE5.Box3().setFromObject(this.ghostModel);
1643
+ this.ghostYOffset = -box.min.y;
1530
1644
  this.ghostModel.position.copy(hit.point);
1645
+ this.ghostModel.position.y += this.ghostYOffset;
1531
1646
  if (hit.face) {
1532
1647
  const normal = this.getWorldNormalSafe(hit);
1533
1648
  if (normal) {
@@ -1538,6 +1653,8 @@ var PGSGThreeViewer = class {
1538
1653
  }
1539
1654
  };
1540
1655
  onPlacementWheel = (event) => {
1656
+ event.preventDefault();
1657
+ event.stopPropagation();
1541
1658
  if (!this.ghostModel) return;
1542
1659
  event.preventDefault();
1543
1660
  const step = Math.PI / 12;
@@ -1567,8 +1684,71 @@ var PGSGThreeViewer = class {
1567
1684
  );
1568
1685
  window.removeEventListener("keydown", this.onPlacementCancel);
1569
1686
  };
1687
+ onSelectObject = (event) => {
1688
+ if (this.pendingPlacementUrl) return;
1689
+ if (this.transformControls?.dragging) return;
1690
+ const rect = this.renderer.domElement.getBoundingClientRect();
1691
+ this.mouse.set(
1692
+ (event.clientX - rect.left) / rect.width * 2 - 1,
1693
+ -((event.clientY - rect.top) / rect.height) * 2 + 1
1694
+ );
1695
+ this.raycaster.setFromCamera(this.mouse, this.camera);
1696
+ const hits = this.raycaster.intersectObjects(this.placedObjects, true);
1697
+ if (!hits.length) {
1698
+ this.selectedObject = void 0;
1699
+ this.transformControls?.detach();
1700
+ this.selectOutline.selectedObjects = [];
1701
+ return;
1702
+ }
1703
+ let root = hits[0].object;
1704
+ while (root && !this.placedObjects.includes(root)) root = root.parent;
1705
+ if (!root) return;
1706
+ this.selectedObject = root;
1707
+ this.transformControls?.attach(root);
1708
+ this.selectOutline.selectedObjects = this.collectMeshes(root);
1709
+ };
1710
+ onHoverObject = (event) => {
1711
+ if (this.pendingPlacementUrl) return;
1712
+ if (this.transformControls?.dragging) return;
1713
+ const rect = this.renderer.domElement.getBoundingClientRect();
1714
+ this.mouse.set(
1715
+ (event.clientX - rect.left) / rect.width * 2 - 1,
1716
+ -((event.clientY - rect.top) / rect.height) * 2 + 1
1717
+ );
1718
+ this.raycaster.setFromCamera(this.mouse, this.camera);
1719
+ const hits = this.raycaster.intersectObjects(this.placedObjects, true);
1720
+ if (!hits.length) {
1721
+ this.hoveredObject = void 0;
1722
+ this.hoverOutline.selectedObjects = [];
1723
+ this.renderer.domElement.style.cursor = "default";
1724
+ return;
1725
+ }
1726
+ let root = hits[0].object;
1727
+ while (root && !this.placedObjects.includes(root)) root = root.parent;
1728
+ if (!root) return;
1729
+ if (root !== this.hoveredObject) {
1730
+ this.hoveredObject = root;
1731
+ this.hoverOutline.selectedObjects = this.collectMeshes(root);
1732
+ this.renderer.domElement.style.cursor = "pointer";
1733
+ }
1734
+ };
1735
+ onDeleteObject = (event) => {
1736
+ const tag = event.target?.tagName?.toLowerCase();
1737
+ if (tag === "input" || tag === "textarea") return;
1738
+ if (event.key !== "Delete" && event.key !== "Backspace") return;
1739
+ if (!this.selectedObject) return;
1740
+ event.preventDefault();
1741
+ this.selectedObject.parent?.remove(this.selectedObject);
1742
+ this.placedObjects = this.placedObjects.filter(
1743
+ (o) => o !== this.selectedObject
1744
+ );
1745
+ this.transformControls?.detach();
1746
+ this.selectOutline.selectedObjects = [];
1747
+ this.hoverOutline.selectedObjects = [];
1748
+ this.selectedObject = void 0;
1749
+ };
1570
1750
  };
1571
1751
  export {
1572
1752
  PGSGThreeViewer
1573
1753
  };
1574
- //# sourceMappingURL=pgsg-three-viewer-5BUDBGSE.js.map
1754
+ //# sourceMappingURL=pgsg-three-viewer-TH7LXVVZ.js.map