feat(web-platform): release V0.8.3 常规界面优化
web-platform-release / Build and publish release (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled

This commit is contained in:
2026-09-04 15:34:24 +08:00
parent 63d67a645b
commit fa5485049a
60 changed files with 4494 additions and 2073 deletions
+109 -2
View File
@@ -67,6 +67,7 @@ function objectPreview(object: EditableMapObject): THREE.Group {
group.name = `__platform_map_editor_${object.id}`;
group.userData.mapEditorObjectId = object.id;
group.userData.mapEditorLocked = object.placementMode === 'locked';
group.userData.mapEditorPlacementMode = object.placementMode;
group.position.fromArray(object.pose.position);
group.quaternion.set(
object.pose.quaternion[1],
@@ -132,8 +133,25 @@ export class MapEditorLayer {
private readonly pointer = new THREE.Vector2();
private readonly objects = new Map<string, THREE.Group>();
private readonly previewInstances = new Map<string, THREE.Group>();
private readonly surfaceIndicator = new THREE.Group();
private readonly surfaceRingMaterial = new THREE.MeshBasicMaterial({
color: 0x34d399,
transparent: true,
opacity: 0.42,
depthTest: false,
depthWrite: false,
side: THREE.DoubleSide,
});
private readonly surfaceLineMaterial = new THREE.LineBasicMaterial({
color: 0x6ee7b7,
transparent: true,
opacity: 0.7,
depthTest: false,
});
private selectedId: string | null = null;
private documentLoaded = false;
private surfaceIndicatorScale = 0.25;
private surfacePulseStarted = 0;
constructor(
private readonly scene: THREE.Scene,
@@ -143,7 +161,25 @@ export class MapEditorLayer {
) {
this.previewGroup.name = '__platform_map_editor_scene_previews__';
this.group.name = '__platform_map_editor__';
scene.add(this.previewGroup, this.group);
this.surfaceIndicator.name = '__platform_snap_surface_indicator__';
const surfaceRing = new THREE.Mesh(
new THREE.RingGeometry(0.72, 1, 48),
this.surfaceRingMaterial,
);
surfaceRing.renderOrder = 110;
const surfaceCross = new THREE.LineSegments(
new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(-1.18, 0, 0),
new THREE.Vector3(1.18, 0, 0),
new THREE.Vector3(0, -1.18, 0),
new THREE.Vector3(0, 1.18, 0),
]),
this.surfaceLineMaterial,
);
surfaceCross.renderOrder = 110;
this.surfaceIndicator.add(surfaceRing, surfaceCross);
this.surfaceIndicator.visible = false;
scene.add(this.previewGroup, this.group, this.surfaceIndicator);
this.transform = new TransformControls(camera, domElement);
this.transform.setSpace('world');
this.transform.setSize(0.8);
@@ -164,6 +200,39 @@ export class MapEditorLayer {
return this.documentLoaded || this.previewInstances.size > 0;
}
selectedBoundingSphere(): THREE.Sphere | null {
const selected = this.selectedId ? this.objects.get(this.selectedId) : undefined;
if (!selected) return null;
selected.updateWorldMatrix(true, true);
const bounds = new THREE.Box3().setFromObject(selected);
if (bounds.isEmpty()) return null;
return bounds.getBoundingSphere(new THREE.Sphere());
}
flashSurfaceAlignment(id?: string): void {
if (id && id !== this.selectedId) return;
const selected = this.selectedId ? this.objects.get(this.selectedId) : undefined;
if (!selected || selected.userData.mapEditorLocked) return;
this.positionSurfaceIndicator(selected);
this.startSurfacePulse();
this.surfaceIndicator.visible = true;
}
update(now: number): void {
const selected = this.selectedId ? this.objects.get(this.selectedId) : undefined;
if (!selected || selected.userData.mapEditorLocked) {
this.surfaceIndicator.visible = false;
return;
}
this.positionSurfaceIndicator(selected);
const elapsed = now - this.surfacePulseStarted;
const pulse = elapsed >= 0 && elapsed < 720 ? 1 - elapsed / 720 : 0;
const wave = pulse * (0.16 + Math.sin((elapsed / 720) * Math.PI * 4) * 0.08);
this.surfaceIndicator.scale.setScalar(this.surfaceIndicatorScale * (1 + wave));
this.surfaceRingMaterial.opacity = 0.32 + pulse * 0.38;
this.surfaceLineMaterial.opacity = 0.5 + pulse * 0.4;
}
setDocument(
document: EditableMapDocument | null,
instanceTransform: MapInstanceTransform = DEFAULT_MAP_INSTANCE_TRANSFORM,
@@ -231,6 +300,7 @@ export class MapEditorLayer {
}
selectObject(id: string | null, notify = false): void {
const previousId = this.selectedId;
this.selectedId = id && this.objects.has(id) ? id : null;
this.transform.detach();
for (const [objectId, root] of this.objects)
@@ -246,7 +316,11 @@ export class MapEditorLayer {
});
const selected = this.selectedId ? this.objects.get(this.selectedId) : undefined;
const editable = Boolean(selected && !selected.userData.mapEditorLocked);
if (selected && editable) this.transform.attach(selected);
if (selected && editable) {
this.transform.attach(selected);
this.positionSurfaceIndicator(selected);
if (previousId !== this.selectedId) this.startSurfacePulse();
} else this.surfaceIndicator.visible = false;
this.helper.visible = editable;
if (notify) this.callbacks.onSelect(this.selectedId);
}
@@ -285,6 +359,32 @@ export class MapEditorLayer {
return false;
}
private startSurfacePulse(): void {
const now = performance.now();
this.surfacePulseStarted = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
? now - 720
: now;
}
private positionSurfaceIndicator(selected: THREE.Group): void {
selected.updateWorldMatrix(true, true);
const bounds = new THREE.Box3().setFromObject(selected);
if (bounds.isEmpty()) {
this.surfaceIndicator.visible = false;
return;
}
const center = bounds.getCenter(new THREE.Vector3());
const size = bounds.getSize(new THREE.Vector3());
const placementMode = selected.userData.mapEditorPlacementMode;
const color = placementMode === 'gravity' ? 0x38bdf8 : 0x34d399;
this.surfaceRingMaterial.color.setHex(color);
this.surfaceLineMaterial.color.setHex(color);
this.surfaceIndicatorScale = Math.min(1.5, Math.max(0.16, Math.max(size.x, size.y) * 0.62));
this.surfaceIndicator.position.set(center.x, center.y, bounds.min.z + 0.004);
this.surfaceIndicator.scale.setScalar(this.surfaceIndicatorScale);
this.surfaceIndicator.visible = true;
}
private commitTransform(): void {
if (!this.selectedId) return;
const object = this.objects.get(this.selectedId);
@@ -323,6 +423,8 @@ export class MapEditorLayer {
this.clearPreviewInstances();
this.group.position.set(0, 0, 0);
this.group.rotation.set(0, 0, 0);
this.surfaceIndicator.visible = false;
this.surfacePulseStarted = 0;
this.transform.enabled = false;
this.callbacks.onDragging(false);
}
@@ -332,6 +434,11 @@ export class MapEditorLayer {
this.callbacks.onDragging(false);
this.transform.dispose();
this.helper.removeFromParent();
for (const child of this.surfaceIndicator.children)
if (child instanceof THREE.Mesh || child instanceof THREE.Line) child.geometry.dispose();
this.surfaceRingMaterial.dispose();
this.surfaceLineMaterial.dispose();
this.surfaceIndicator.removeFromParent();
this.previewGroup.removeFromParent();
this.group.removeFromParent();
}