This commit is contained in:
lenn
2026-06-22 11:18:11 +08:00
parent 011bfe2450
commit b343e74a12
7 changed files with 232 additions and 45 deletions

View File

@@ -48,7 +48,7 @@
export let colorMapPreset: PressureColorMapPreset = "emerald";
export let matrixDisplayMode: MatrixDisplayMode = "dots";
export let stageViewMode: StageViewMode = "webgl";
export let modelUrl = "/models/je-skin-model.glb";
export let modelUrl = "/models/je-skin-model.gltf";
export let replaySectionLabel = "";
export let replayPlayLabel = "";
export let replayPauseLabel = "";

View File

@@ -19,12 +19,13 @@
const FLOOR_Y = -1.15;
const MODEL_FLOOR_CLEARANCE = 0.035;
const MODEL_TARGET_HEIGHT = 8.4;
const MODEL_TARGET_HEIGHT = 7.4;
const MODEL_MIN_SCALE = 0.02;
const MODEL_MAX_SCALE = 80;
const CAMERA_DISTANCE_FACTOR = 1.35;
const CAMERA_DISTANCE_MIN = 7.5;
const CAMERA_DISTANCE_MAX = 24;
const FIXED_CAMERA_POSITION = new THREE.Vector3(0.8, 3.25, 12.2);
const FIXED_CAMERA_TARGET = new THREE.Vector3(0, 2.55, 0);
const MODEL_FRONT_ROTATION_Y = -Math.PI / 2;
const MODEL_UPRIGHT_ROTATION_Z = Math.PI * 1.5;
$: copy =
locale === "zh-CN"
@@ -128,7 +129,41 @@
return Math.min(max, Math.max(min, value));
}
function materialToUnlit(material: THREE.Material): THREE.Material {
const source = material as THREE.MeshStandardMaterial & THREE.MeshPhysicalMaterial;
const unlit = new THREE.MeshBasicMaterial({
color: source.color?.clone() ?? new THREE.Color(0xffffff),
map: source.map ?? null,
transparent: source.transparent,
opacity: source.opacity,
alphaMap: source.alphaMap ?? null,
side: source.side,
depthWrite: source.depthWrite,
depthTest: source.depthTest,
vertexColors: source.vertexColors,
toneMapped: false
});
return unlit;
}
function applyUnlitMaterials(object: THREE.Object3D): void {
object.traverse((child) => {
const mesh = child as THREE.Mesh;
if (!mesh.isMesh || !mesh.material) {
return;
}
mesh.castShadow = false;
mesh.receiveShadow = false;
mesh.material = Array.isArray(mesh.material)
? mesh.material.map(materialToUnlit)
: materialToUnlit(mesh.material);
});
}
function normalizeObjectToStage(object: THREE.Object3D): THREE.Box3 {
object.rotation.set(0, MODEL_FRONT_ROTATION_Y, MODEL_UPRIGHT_ROTATION_Z);
object.updateMatrixWorld(true);
let bounds = new THREE.Box3().setFromObject(object);
const size = bounds.getSize(new THREE.Vector3());
@@ -149,19 +184,14 @@
}
function frameObject(object: THREE.Object3D, camera: THREE.PerspectiveCamera, controls: OrbitControls): void {
const bounds = normalizeObjectToStage(object);
const size = bounds.getSize(new THREE.Vector3());
const maxAxis = Math.max(size.x, size.y, size.z, 1);
const distance = clamp(maxAxis * CAMERA_DISTANCE_FACTOR, CAMERA_DISTANCE_MIN, CAMERA_DISTANCE_MAX);
const targetY = FLOOR_Y + Math.max(size.y * 0.46, 1.4);
camera.position.set(distance * 0.48, targetY + distance * 0.24, distance * 0.68);
camera.near = Math.max(distance / 80, 0.01);
camera.far = distance * 24;
normalizeObjectToStage(object);
camera.position.copy(FIXED_CAMERA_POSITION);
camera.near = 0.05;
camera.far = 600;
camera.updateProjectionMatrix();
controls.target.set(0, targetY, 0);
controls.minDistance = Math.max(distance * 0.32, 2);
controls.maxDistance = Math.max(distance * 2.5, 12);
controls.target.copy(FIXED_CAMERA_TARGET);
controls.minDistance = 2.4;
controls.maxDistance = 32;
controls.update();
}
@@ -186,14 +216,15 @@
scene.fog = new THREE.FogExp2(0x03070d, 0.028);
const camera = new THREE.PerspectiveCamera(38, 1, 0.05, 600);
camera.position.set(8, 6, 9);
camera.position.copy(FIXED_CAMERA_POSITION);
camera.lookAt(FIXED_CAMERA_TARGET);
const controls = new OrbitControls(camera, canvasEl);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.minDistance = 2.4;
controls.maxDistance = 32;
controls.target.set(0, FLOOR_Y + 3.2, 0);
controls.target.copy(FIXED_CAMERA_TARGET);
const labGroup = new THREE.Group();
scene.add(labGroup);
@@ -241,16 +272,8 @@
floor.position.y = FLOOR_Y - 0.018;
labGroup.add(floor);
const ambient = new THREE.AmbientLight(0x9fb8d0, 0.22);
const keyLight = new THREE.DirectionalLight(0x7be7ff, 1.5);
keyLight.position.set(8, 12, 8);
const rimLight = new THREE.PointLight(0xa6ff7a, 26, 24, 2.1);
rimLight.position.set(-4.5, 4.8, -3.6);
const sideLight = new THREE.PointLight(0x5c8cff, 15, 28, 1.7);
sideLight.position.set(5.8, 3.2, -5.4);
scene.add(ambient, keyLight, rimLight, sideLight);
let activeModel: THREE.Object3D = buildPlaceholderModel();
applyUnlitMaterials(activeModel);
scene.add(activeModel);
frameObject(activeModel, camera, controls);
@@ -261,13 +284,7 @@
scene.remove(activeModel);
disposeObject3D(activeModel);
activeModel = gltf.scene;
activeModel.traverse((child) => {
const mesh = child as THREE.Mesh;
if (mesh.isMesh) {
mesh.castShadow = true;
mesh.receiveShadow = true;
}
});
applyUnlitMaterials(activeModel);
scene.add(activeModel);
frameObject(activeModel, camera, controls);
loadState = "ready";