mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
PageScene: restore raycaster-based drop position
DndAsset.onPointerUp captured event.pageX/Y but threw them away; both add* actions then defaulted position to origin. PageEditor's gallery onImageDrop tried an NDC unproject but gated on editorEngine.camera, which Phase 1's CameraController extraction left undefined — the guard short-circuited and worldPosition stayed undefined. Add pickDropPosition: a deps-pattern helper that mirrors the pre-refactor timeline.getPos() — raycasts from the camera through the cursor against scene children and returns the first hit (or origin if the ray misses or the camera/canvas isn't ready). Same idiom as CameraControllerDeps / GizmoControllerDeps from Phase 2; callers build the deps inline so the helper stays decoupled from Editor and any subsystem's import surface. Both drop sites — DndAsset.onPointerUp and PageEditor's 3D gallery onImageDrop — now call the helper and forward worldPosition to addCharacter / addObject / addShape.
This commit is contained in:
1 parent
3af5291150
commit
b95f6fdf91
3 files changed
+69
-26
No files matched your search
@@ -4,6 +4,7 @@ import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore";
|
||||
import { pageHeight, pageWidth } from "~/signals";
|
||||
import { AssetType } from "~/enums";
|
||||
import type Editor from "~/pages/PageScene/engine/editor";
|
||||
import { pickDropPosition } from "~/pages/PageScene/engine/pickDropPosition";
|
||||
import {
|
||||
addCharacter,
|
||||
addObject,
|
||||
@@ -88,16 +89,27 @@ class DndAsset {
|
||||
const positionX = event.pageX;
|
||||
const positionY = event.pageY;
|
||||
if (this.overCanvas(positionX, positionY)) {
|
||||
const worldPosition = pickDropPosition(
|
||||
{
|
||||
getCamera: () => editor.cameraController.camera,
|
||||
getCanvas: () => editor.renderer?.domElement,
|
||||
getRaycastTargets: () => editor.activeScene.scene.children,
|
||||
removeTransformControls: () =>
|
||||
editor.utils.removeTransformControls(true),
|
||||
},
|
||||
positionX,
|
||||
positionY,
|
||||
);
|
||||
if (mediaItem.type === AssetType.CHARACTER) {
|
||||
void addCharacter(editor, mediaItem);
|
||||
void addCharacter(editor, mediaItem, worldPosition);
|
||||
} else if (
|
||||
mediaItem.type === AssetType.OBJECT ||
|
||||
mediaItem.type === AssetType.SPLAT ||
|
||||
mediaItem.type === AssetType.SKYBOX
|
||||
) {
|
||||
void addObject(editor, mediaItem);
|
||||
void addObject(editor, mediaItem, worldPosition);
|
||||
} else if (mediaItem.type === AssetType.SHAPE) {
|
||||
void addShape(editor, mediaItem);
|
||||
void addShape(editor, mediaItem, worldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import { AssetType, AUTH_STATUS } from "~/enums";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { MediaItem } from "~/pages/PageScene/models";
|
||||
import { UploaderState } from "~/models";
|
||||
import * as THREE from "three";
|
||||
import { pickDropPosition } from "./engine/pickDropPosition";
|
||||
import {
|
||||
GalleryItem,
|
||||
onImageDrop,
|
||||
@@ -303,33 +303,23 @@ export const PageEditor = () => {
|
||||
if (tabStore.activeTabId === "3D") {
|
||||
handler = onImageDrop(
|
||||
(item: GalleryItem, position: { x: number; y: number }) => {
|
||||
console.log("3D Drop debug (event):", {
|
||||
editorEngine,
|
||||
camera: editorEngine?.camera,
|
||||
renderer: editorEngine?.renderer,
|
||||
position,
|
||||
});
|
||||
|
||||
// Calculate world position from cursor immediately on drop
|
||||
let worldPosition = undefined;
|
||||
if (editorEngine?.camera && editorEngine?.renderer && position) {
|
||||
const rect =
|
||||
editorEngine.renderer.domElement.getBoundingClientRect();
|
||||
// Convert client coordinates to canvas coordinates
|
||||
const canvasX = position.x - rect.left;
|
||||
const canvasY = position.y - rect.top;
|
||||
const ndcX = (canvasX / rect.width) * 2 - 1;
|
||||
const ndcY = -(canvasY / rect.height) * 2 + 1;
|
||||
const vector = new THREE.Vector3(ndcX, ndcY, 0.5);
|
||||
vector.unproject(editorEngine.camera);
|
||||
worldPosition = vector;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (!editorEngine) {
|
||||
console.warn("Cannot drop asset: editor engine not ready");
|
||||
return;
|
||||
}
|
||||
const worldPosition = pickDropPosition(
|
||||
{
|
||||
getCamera: () => editorEngine.cameraController.camera,
|
||||
getCanvas: () => editorEngine.renderer?.domElement,
|
||||
getRaycastTargets: () =>
|
||||
editorEngine.activeScene.scene.children,
|
||||
removeTransformControls: () =>
|
||||
editorEngine.utils.removeTransformControls(true),
|
||||
},
|
||||
position.x,
|
||||
position.y,
|
||||
);
|
||||
try {
|
||||
if (item.mediaClass === "dimensional") {
|
||||
const isCharacter = item.assetType === "character";
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
export type PickDropPositionDeps = {
|
||||
getCamera: () => THREE.PerspectiveCamera | null | undefined;
|
||||
getCanvas: () => HTMLCanvasElement | null | undefined;
|
||||
getRaycastTargets: () => THREE.Object3D[];
|
||||
removeTransformControls: () => void;
|
||||
};
|
||||
|
||||
// Cast a ray from the camera through the cursor and return the first
|
||||
// scene hit. Falls back to origin if the camera/canvas isn't ready or
|
||||
// nothing is hit. All dependencies arrive through `deps` so the picker
|
||||
// stays decoupled from Editor and any subsystem's import surface
|
||||
// (matches the Phase 2 deps pattern used by CameraController etc.).
|
||||
export function pickDropPosition(
|
||||
deps: PickDropPositionDeps,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
): THREE.Vector3 {
|
||||
const camera = deps.getCamera();
|
||||
const canvas = deps.getCanvas();
|
||||
if (!camera || !canvas) return new THREE.Vector3();
|
||||
|
||||
// Hide the gizmo so the raycast doesn't hit it.
|
||||
deps.removeTransformControls();
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const ndc = new THREE.Vector2(
|
||||
((pageX - rect.left) / rect.width) * 2 - 1,
|
||||
-((pageY - rect.top) / rect.height) * 2 + 1,
|
||||
);
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.layers.enable(0);
|
||||
raycaster.layers.enable(1);
|
||||
raycaster.setFromCamera(ndc, camera);
|
||||
|
||||
const hits = raycaster.intersectObjects(deps.getRaycastTargets(), true);
|
||||
if (hits.length > 0) return hits[0].point.clone();
|
||||
return new THREE.Vector3();
|
||||
}
|
||||
Reference in new issue
Block a user