Add transform session and gizmo support in viewport-host.ts
This commit is contained in:
@@ -33,6 +33,17 @@ import {
|
|||||||
import { EffectComposer } from "postprocessing";
|
import { EffectComposer } from "postprocessing";
|
||||||
|
|
||||||
import { isBrushFaceSelected, isBrushSelected, isModelInstanceSelected, type EditorSelection } from "../core/selection";
|
import { isBrushFaceSelected, isBrushSelected, isModelInstanceSelected, type EditorSelection } from "../core/selection";
|
||||||
|
import {
|
||||||
|
cloneTransformSession,
|
||||||
|
createInactiveTransformSession,
|
||||||
|
createTransformSession,
|
||||||
|
getTransformOperationLabel,
|
||||||
|
supportsTransformAxisConstraint,
|
||||||
|
type ActiveTransformSession,
|
||||||
|
type TransformAxis,
|
||||||
|
type TransformOperation,
|
||||||
|
type TransformSessionState
|
||||||
|
} from "../core/transform-session";
|
||||||
import type { ToolMode } from "../core/tool-mode";
|
import type { ToolMode } from "../core/tool-mode";
|
||||||
import type { Vec3 } from "../core/vector";
|
import type { Vec3 } from "../core/vector";
|
||||||
import { createModelInstanceRenderGroup, disposeModelInstance } from "../assets/model-instance-rendering";
|
import { createModelInstanceRenderGroup, disposeModelInstance } from "../assets/model-instance-rendering";
|
||||||
@@ -65,13 +76,14 @@ import {
|
|||||||
DEFAULT_TELEPORT_TARGET_YAW_DEGREES,
|
DEFAULT_TELEPORT_TARGET_YAW_DEGREES,
|
||||||
DEFAULT_TRIGGER_VOLUME_SIZE,
|
DEFAULT_TRIGGER_VOLUME_SIZE,
|
||||||
getEntityInstances,
|
getEntityInstances,
|
||||||
|
normalizeYawDegrees,
|
||||||
type EntityInstance,
|
type EntityInstance,
|
||||||
type PointLightEntity,
|
type PointLightEntity,
|
||||||
type SpotLightEntity
|
type SpotLightEntity
|
||||||
} from "../entities/entity-instances";
|
} from "../entities/entity-instances";
|
||||||
import { BOX_FACE_IDS, DEFAULT_BOX_BRUSH_SIZE, type BoxBrush, type BoxFaceId } from "../document/brushes";
|
import { BOX_FACE_IDS, DEFAULT_BOX_BRUSH_SIZE, type BoxBrush, type BoxFaceId } from "../document/brushes";
|
||||||
import { applyBoxBrushFaceUvsToGeometry } from "../geometry/box-face-uvs";
|
import { applyBoxBrushFaceUvsToGeometry } from "../geometry/box-face-uvs";
|
||||||
import { DEFAULT_GRID_SIZE, snapValueToGrid } from "../geometry/grid-snapping";
|
import { DEFAULT_GRID_SIZE, snapValueToGrid, snapVec3ToGrid } from "../geometry/grid-snapping";
|
||||||
import { createStarterMaterialSignature, createStarterMaterialTexture } from "../materials/starter-material-textures";
|
import { createStarterMaterialSignature, createStarterMaterialTexture } from "../materials/starter-material-textures";
|
||||||
import type { MaterialDef } from "../materials/starter-material-library";
|
import type { MaterialDef } from "../materials/starter-material-library";
|
||||||
import {
|
import {
|
||||||
@@ -130,6 +142,26 @@ const ORTHOGRAPHIC_CAMERA_DISTANCE = 100;
|
|||||||
const ORTHOGRAPHIC_FRUSTUM_HEIGHT = 20;
|
const ORTHOGRAPHIC_FRUSTUM_HEIGHT = 20;
|
||||||
const MIN_ORTHOGRAPHIC_ZOOM = 0.25;
|
const MIN_ORTHOGRAPHIC_ZOOM = 0.25;
|
||||||
const MAX_ORTHOGRAPHIC_ZOOM = 20;
|
const MAX_ORTHOGRAPHIC_ZOOM = 20;
|
||||||
|
const GIZMO_AXIS_COLORS: Record<TransformAxis, number> = {
|
||||||
|
x: 0xea655b,
|
||||||
|
y: 0x6ed06f,
|
||||||
|
z: 0x55a2ff
|
||||||
|
};
|
||||||
|
const GIZMO_ACTIVE_COLOR = 0xf7d2aa;
|
||||||
|
const GIZMO_INACTIVE_OPACITY = 0.82;
|
||||||
|
const GIZMO_ACTIVE_OPACITY = 1;
|
||||||
|
const GIZMO_TRANSLATE_LENGTH = 1.2;
|
||||||
|
const GIZMO_SCALE_LENGTH = 1;
|
||||||
|
const GIZMO_ROTATE_RADIUS = 1.05;
|
||||||
|
const GIZMO_ROTATE_TUBE = 0.035;
|
||||||
|
const GIZMO_PICK_THICKNESS = 0.18;
|
||||||
|
const GIZMO_PICK_RING_TUBE = 0.14;
|
||||||
|
const GIZMO_CENTER_HANDLE_SIZE = 0.16;
|
||||||
|
const GIZMO_SCREEN_SIZE_PERSPECTIVE = 0.11;
|
||||||
|
const GIZMO_SCREEN_SIZE_ORTHOGRAPHIC = 1.4;
|
||||||
|
const ROTATION_SNAP_DEGREES = 15;
|
||||||
|
const SCALE_SNAP_STEP = 0.1;
|
||||||
|
const MIN_SCALE_COMPONENT = 0.1;
|
||||||
|
|
||||||
interface CachedMaterialTexture {
|
interface CachedMaterialTexture {
|
||||||
signature: string;
|
signature: string;
|
||||||
@@ -171,6 +203,9 @@ export class ViewportHost {
|
|||||||
private readonly pointer = new Vector2();
|
private readonly pointer = new Vector2();
|
||||||
private readonly boxCreateIntersection = new Vector3();
|
private readonly boxCreateIntersection = new Vector3();
|
||||||
private readonly boxCreatePlane = new Plane(new Vector3(0, 1, 0), 0);
|
private readonly boxCreatePlane = new Plane(new Vector3(0, 1, 0), 0);
|
||||||
|
private readonly transformPlane = new Plane(new Vector3(0, 1, 0), 0);
|
||||||
|
private readonly transformIntersection = new Vector3();
|
||||||
|
private readonly transformGizmoGroup = new Group();
|
||||||
private readonly brushRenderObjects = new Map<string, BrushRenderObjects>();
|
private readonly brushRenderObjects = new Map<string, BrushRenderObjects>();
|
||||||
private readonly entityRenderObjects = new Map<string, EntityRenderObjects>();
|
private readonly entityRenderObjects = new Map<string, EntityRenderObjects>();
|
||||||
private readonly localLightRenderObjects = new Map<string, LocalLightRenderObjects>();
|
private readonly localLightRenderObjects = new Map<string, LocalLightRenderObjects>();
|
||||||
@@ -211,14 +246,38 @@ export class ViewportHost {
|
|||||||
private creationPreviewChangeHandler: ((toolPreview: ViewportToolPreview) => void) | null = null;
|
private creationPreviewChangeHandler: ((toolPreview: ViewportToolPreview) => void) | null = null;
|
||||||
private creationCommitHandler: ((toolPreview: CreationViewportToolPreview) => boolean) | null = null;
|
private creationCommitHandler: ((toolPreview: CreationViewportToolPreview) => boolean) | null = null;
|
||||||
private cameraStateChangeHandler: ((cameraState: ViewportPanelCameraState) => void) | null = null;
|
private cameraStateChangeHandler: ((cameraState: ViewportPanelCameraState) => void) | null = null;
|
||||||
|
private transformSessionChangeHandler: ((transformSession: TransformSessionState) => void) | null = null;
|
||||||
|
private transformCommitHandler: ((transformSession: ActiveTransformSession) => void) | null = null;
|
||||||
|
private transformCancelHandler: (() => void) | null = null;
|
||||||
private toolMode: ToolMode = "select";
|
private toolMode: ToolMode = "select";
|
||||||
private viewMode: ViewportViewMode = "perspective";
|
private viewMode: ViewportViewMode = "perspective";
|
||||||
private displayMode: ViewportDisplayMode = "normal";
|
private displayMode: ViewportDisplayMode = "normal";
|
||||||
|
private panelId: ViewportPanelId = "topLeft";
|
||||||
private creationPreview: CreationViewportToolPreview | null = null;
|
private creationPreview: CreationViewportToolPreview | null = null;
|
||||||
private creationPreviewTargetKey: string | null = null;
|
private creationPreviewTargetKey: string | null = null;
|
||||||
private creationPreviewObject: Group | null = null;
|
private creationPreviewObject: Group | null = null;
|
||||||
|
private currentTransformSession: TransformSessionState = createInactiveTransformSession();
|
||||||
private activeCameraDragPointerId: number | null = null;
|
private activeCameraDragPointerId: number | null = null;
|
||||||
private lastCameraDragClientPosition: { x: number; y: number } | null = null;
|
private lastCameraDragClientPosition: { x: number; y: number } | null = null;
|
||||||
|
private activeTransformDrag:
|
||||||
|
| {
|
||||||
|
pointerId: number;
|
||||||
|
sessionId: string;
|
||||||
|
axisConstraint: TransformAxis | null;
|
||||||
|
initialClientPosition: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| null = null;
|
||||||
|
private lastCanvasPointerPosition: { x: number; y: number } | null = null;
|
||||||
|
private keyboardTransformPointerOrigin:
|
||||||
|
| {
|
||||||
|
sessionId: string;
|
||||||
|
clientX: number;
|
||||||
|
clientY: number;
|
||||||
|
}
|
||||||
|
| null = null;
|
||||||
// Click-through cycling: track the last click position and the last picked object
|
// Click-through cycling: track the last click position and the last picked object
|
||||||
// so repeated clicks at the same spot cycle through overlapping objects.
|
// so repeated clicks at the same spot cycle through overlapping objects.
|
||||||
private lastClickPointer: { x: number; y: number } | null = null;
|
private lastClickPointer: { x: number; y: number } | null = null;
|
||||||
@@ -248,6 +307,8 @@ export class ViewportHost {
|
|||||||
this.scene.add(this.brushGroup);
|
this.scene.add(this.brushGroup);
|
||||||
this.scene.add(this.entityGroup);
|
this.scene.add(this.entityGroup);
|
||||||
this.scene.add(this.modelGroup);
|
this.scene.add(this.modelGroup);
|
||||||
|
this.transformGizmoGroup.visible = false;
|
||||||
|
this.scene.add(this.transformGizmoGroup);
|
||||||
this.boxCreatePreviewMesh.visible = false;
|
this.boxCreatePreviewMesh.visible = false;
|
||||||
this.boxCreatePreviewEdges.visible = false;
|
this.boxCreatePreviewEdges.visible = false;
|
||||||
this.scene.add(this.boxCreatePreviewMesh);
|
this.scene.add(this.boxCreatePreviewMesh);
|
||||||
@@ -257,6 +318,10 @@ export class ViewportHost {
|
|||||||
this.applyViewModePose();
|
this.applyViewModePose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPanelId(panelId: ViewportPanelId) {
|
||||||
|
this.panelId = panelId;
|
||||||
|
}
|
||||||
|
|
||||||
mount(container: HTMLElement) {
|
mount(container: HTMLElement) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.renderer.domElement.tabIndex = -1;
|
this.renderer.domElement.tabIndex = -1;
|
||||||
@@ -290,6 +355,8 @@ export class ViewportHost {
|
|||||||
this.rebuildBrushMeshes(document, selection);
|
this.rebuildBrushMeshes(document, selection);
|
||||||
this.rebuildEntityMarkers(document, selection);
|
this.rebuildEntityMarkers(document, selection);
|
||||||
this.rebuildModelInstances(document, selection);
|
this.rebuildModelInstances(document, selection);
|
||||||
|
this.applyTransformPreview();
|
||||||
|
this.syncTransformGizmo();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAssets(
|
updateAssets(
|
||||||
@@ -333,6 +400,18 @@ export class ViewportHost {
|
|||||||
this.cameraStateChangeHandler = handler;
|
this.cameraStateChangeHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTransformSessionChangeHandler(handler: ((transformSession: TransformSessionState) => void) | null) {
|
||||||
|
this.transformSessionChangeHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransformCommitHandler(handler: ((transformSession: ActiveTransformSession) => void) | null) {
|
||||||
|
this.transformCommitHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransformCancelHandler(handler: (() => void) | null) {
|
||||||
|
this.transformCancelHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
setCameraState(cameraState: ViewportPanelCameraState) {
|
setCameraState(cameraState: ViewportPanelCameraState) {
|
||||||
if (areViewportPanelCameraStatesEqual(this.createCameraStateSnapshot(), cameraState)) {
|
if (areViewportPanelCameraStatesEqual(this.createCameraStateSnapshot(), cameraState)) {
|
||||||
return;
|
return;
|
||||||
@@ -350,6 +429,29 @@ export class ViewportHost {
|
|||||||
this.syncCreationPreview(toolPreview);
|
this.syncCreationPreview(toolPreview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTransformSession(transformSession: TransformSessionState) {
|
||||||
|
this.currentTransformSession = cloneTransformSession(transformSession);
|
||||||
|
|
||||||
|
if (this.currentTransformSession.kind === "none") {
|
||||||
|
this.activeTransformDrag = null;
|
||||||
|
this.keyboardTransformPointerOrigin = null;
|
||||||
|
} else if (
|
||||||
|
this.currentTransformSession.sourcePanelId === this.panelId &&
|
||||||
|
this.currentTransformSession.source !== "gizmo" &&
|
||||||
|
(this.keyboardTransformPointerOrigin === null || this.keyboardTransformPointerOrigin.sessionId !== this.currentTransformSession.id)
|
||||||
|
) {
|
||||||
|
const pointerOrigin = this.getPointerOriginForTransformSession();
|
||||||
|
this.keyboardTransformPointerOrigin = {
|
||||||
|
sessionId: this.currentTransformSession.id,
|
||||||
|
clientX: pointerOrigin.x,
|
||||||
|
clientY: pointerOrigin.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applyTransformPreview();
|
||||||
|
this.syncTransformGizmo();
|
||||||
|
}
|
||||||
|
|
||||||
setToolMode(toolMode: ToolMode) {
|
setToolMode(toolMode: ToolMode) {
|
||||||
this.toolMode = toolMode;
|
this.toolMode = toolMode;
|
||||||
this.lastClickPointer = null;
|
this.lastClickPointer = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user