feat(nds): physical dpad
This commit is contained in:
@@ -22,8 +22,23 @@ const scene = computed(() => model.value?.scene);
|
||||
|
||||
let topScreenTexture: THREE.CanvasTexture | null = null;
|
||||
let bottomScreenTexture: THREE.CanvasTexture | null = null;
|
||||
let topScreenMesh: THREE.Mesh | null = null;
|
||||
let bottomScreenMesh: THREE.Mesh | null = null;
|
||||
|
||||
/// meshes ///
|
||||
// screens
|
||||
const TOP_SCREEN = "Object_9";
|
||||
const BOTTOM_SCREEN = "Object_28";
|
||||
|
||||
// buttons
|
||||
const CROSS_BUTTON = "Object_21";
|
||||
|
||||
const meshes = new Map<string, THREE.Mesh>();
|
||||
|
||||
const requireMesh = (key: string): THREE.Mesh => {
|
||||
const mesh = meshes.get(key);
|
||||
if (!mesh) throw new Error(`Missing mesh '${key}'`);
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
const { camera, renderer } = useTresContext();
|
||||
|
||||
@@ -36,11 +51,15 @@ watch(
|
||||
topScreenTexture.minFilter = THREE.NearestFilter;
|
||||
topScreenTexture.magFilter = THREE.NearestFilter;
|
||||
topScreenTexture.flipY = false;
|
||||
topScreenTexture.repeat.set(1, 1024 / 404);
|
||||
topScreenTexture.offset.set(0, -4 / 1024);
|
||||
|
||||
bottomScreenTexture = new THREE.CanvasTexture(props.bottomScreenCanvas);
|
||||
bottomScreenTexture.minFilter = THREE.NearestFilter;
|
||||
bottomScreenTexture.magFilter = THREE.NearestFilter;
|
||||
bottomScreenTexture.flipY = false;
|
||||
bottomScreenTexture.repeat.set(1, 1024 / 532);
|
||||
bottomScreenTexture.offset.set(0, -1024 / 532 + 1);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -48,44 +67,86 @@ watch(
|
||||
watch(scene, () => {
|
||||
if (!scene.value) return;
|
||||
|
||||
meshes.clear();
|
||||
|
||||
scene.value.scale.set(100, 100, 100);
|
||||
|
||||
scene.value.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
const material = child.material as THREE.Material;
|
||||
|
||||
if (material.name?.includes("screen_up") && topScreenTexture) {
|
||||
topScreenTexture.repeat.set(1, 1024 / 404);
|
||||
topScreenTexture.offset.set(0, -4 / 1024);
|
||||
child.material = new THREE.MeshStandardMaterial({
|
||||
map: topScreenTexture,
|
||||
emissive: new THREE.Color(0x222222),
|
||||
emissiveIntensity: 0.5,
|
||||
});
|
||||
topScreenMesh = child;
|
||||
} else if (
|
||||
material.name?.includes("screen_down") &&
|
||||
bottomScreenTexture
|
||||
) {
|
||||
bottomScreenTexture.repeat.set(1, 1024 / 532);
|
||||
bottomScreenTexture.offset.set(0, -1024 / 532 + 1);
|
||||
child.material = new THREE.MeshStandardMaterial({
|
||||
map: bottomScreenTexture,
|
||||
emissive: new THREE.Color(0x222222),
|
||||
emissiveIntensity: 0.5,
|
||||
});
|
||||
bottomScreenMesh = child;
|
||||
}
|
||||
meshes.set(child.name, child);
|
||||
}
|
||||
});
|
||||
|
||||
if (!topScreenTexture || !bottomScreenTexture)
|
||||
throw new Error(
|
||||
"topScreenTexture and bottomScreenTexture should be initialized",
|
||||
);
|
||||
|
||||
requireMesh(TOP_SCREEN).material = new THREE.MeshStandardMaterial({
|
||||
map: topScreenTexture,
|
||||
emissive: new THREE.Color(0x222222),
|
||||
emissiveIntensity: 0.5,
|
||||
});
|
||||
|
||||
requireMesh(BOTTOM_SCREEN).material = new THREE.MeshStandardMaterial({
|
||||
map: bottomScreenTexture,
|
||||
emissive: new THREE.Color(0x222222),
|
||||
emissiveIntensity: 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
const { onRender } = useLoop();
|
||||
|
||||
onRender(() => {
|
||||
const physicalButtonsDown = new Set<string>();
|
||||
let mousePressedButton: string | null = null;
|
||||
|
||||
const keyButtonMappings: [key: string, physicalButton: string][] = [
|
||||
["ArrowUp", "UP"],
|
||||
["ArrowDown", "DOWN"],
|
||||
["ArrowLeft", "LEFT"],
|
||||
["ArrowRight", "RIGHT"],
|
||||
];
|
||||
|
||||
const keyToButton = new Map(keyButtonMappings);
|
||||
const buttonToKey = new Map(keyButtonMappings.map(([k, b]) => [b, k]));
|
||||
|
||||
useKeyDown((key) => {
|
||||
const button = keyToButton.get(key);
|
||||
if (button) physicalButtonsDown.add(button);
|
||||
});
|
||||
|
||||
useKeyUp((key) => {
|
||||
const button = keyToButton.get(key);
|
||||
if (button) physicalButtonsDown.delete(button);
|
||||
});
|
||||
|
||||
onRender(({ delta }) => {
|
||||
if (topScreenTexture) topScreenTexture.needsUpdate = true;
|
||||
if (bottomScreenTexture) bottomScreenTexture.needsUpdate = true;
|
||||
|
||||
const crossButton = meshes.get(CROSS_BUTTON);
|
||||
if (crossButton) {
|
||||
const PRESS_ANGLE = Math.PI / 28;
|
||||
const PRESS_SPEED = 150;
|
||||
|
||||
const targetRotation = new THREE.Vector3(0);
|
||||
if (physicalButtonsDown.has("UP")) targetRotation.setX(-PRESS_ANGLE);
|
||||
if (physicalButtonsDown.has("RIGHT")) targetRotation.setZ(-PRESS_ANGLE);
|
||||
if (physicalButtonsDown.has("DOWN")) targetRotation.setX(PRESS_ANGLE);
|
||||
if (physicalButtonsDown.has("LEFT")) targetRotation.setZ(PRESS_ANGLE);
|
||||
|
||||
const lerpFactor = Math.min(delta * PRESS_SPEED, 1);
|
||||
const currentRotation = new THREE.Vector3().setFromEuler(
|
||||
crossButton.rotation,
|
||||
);
|
||||
currentRotation.lerp(targetRotation, lerpFactor);
|
||||
crossButton.rotation.setFromVector3(currentRotation);
|
||||
}
|
||||
});
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (!scene.value) return;
|
||||
|
||||
const domElement = renderer.instance.domElement;
|
||||
const rect = domElement.getBoundingClientRect();
|
||||
|
||||
@@ -98,41 +159,87 @@ const handleClick = (event: MouseEvent) => {
|
||||
camera.activeCamera.value,
|
||||
);
|
||||
|
||||
if (topScreenMesh) {
|
||||
const intersects = raycaster.intersectObject(topScreenMesh);
|
||||
if (intersects[0]) {
|
||||
const uv = intersects[0].uv;
|
||||
if (uv) {
|
||||
const x = Math.floor(uv.x * 256);
|
||||
const y = Math.floor(uv.y * (1024 / 404) * 192);
|
||||
emit("topScreenClick", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const intersects = raycaster.intersectObjects(scene.value.children, true);
|
||||
const intersection = intersects[0];
|
||||
if (!intersection?.uv) return;
|
||||
|
||||
if (bottomScreenMesh) {
|
||||
const intersects = raycaster.intersectObject(bottomScreenMesh);
|
||||
if (intersects[0]) {
|
||||
const uv = intersects[0].uv;
|
||||
if (uv) {
|
||||
const x = Math.floor(uv.x * 256);
|
||||
const y = Math.floor(192 - (1 - uv.y) * (1024 / 532) * 192);
|
||||
switch (intersection.object.name) {
|
||||
case TOP_SCREEN:
|
||||
case BOTTOM_SCREEN: {
|
||||
const x = Math.floor(intersection.uv.x * 256);
|
||||
|
||||
if (intersection.object.name === TOP_SCREEN) {
|
||||
const y = Math.floor(intersection.uv.y * (1024 / 404) * 192);
|
||||
emit("topScreenClick", x, y);
|
||||
} else if (intersection.object.name === BOTTOM_SCREEN) {
|
||||
const y = Math.floor(
|
||||
192 - (1 - intersection.uv.y) * (1024 / 532) * 192,
|
||||
);
|
||||
emit("bottomScreenClick", x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CROSS_BUTTON: {
|
||||
const localPos = intersection.point
|
||||
.clone()
|
||||
.sub(intersection.object.getWorldPosition(new THREE.Vector3()));
|
||||
|
||||
const MIN_DIST = 0.45;
|
||||
let button: string | null = null;
|
||||
|
||||
if (localPos.z <= -MIN_DIST) button = "UP";
|
||||
else if (localPos.x >= MIN_DIST) button = "RIGHT";
|
||||
else if (localPos.z >= MIN_DIST) button = "DOWN";
|
||||
else if (localPos.x <= -MIN_DIST) button = "LEFT";
|
||||
|
||||
if (button) {
|
||||
if (mousePressedButton) {
|
||||
physicalButtonsDown.delete(mousePressedButton);
|
||||
const prevKey = buttonToKey.get(mousePressedButton);
|
||||
if (prevKey) {
|
||||
window.dispatchEvent(new KeyboardEvent("keyup", { key: prevKey }));
|
||||
}
|
||||
}
|
||||
|
||||
physicalButtonsDown.add(button);
|
||||
mousePressedButton = button;
|
||||
|
||||
const key = buttonToKey.get(button);
|
||||
if (key) {
|
||||
window.dispatchEvent(new KeyboardEvent("keydown", { key }));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (mousePressedButton) {
|
||||
physicalButtonsDown.delete(mousePressedButton);
|
||||
|
||||
const key = buttonToKey.get(mousePressedButton);
|
||||
if (key) {
|
||||
window.dispatchEvent(new KeyboardEvent("keyup", { key }));
|
||||
}
|
||||
|
||||
mousePressedButton = null;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (renderer) {
|
||||
renderer.instance.domElement.addEventListener("click", handleClick);
|
||||
renderer.instance.domElement.addEventListener("mousedown", handleClick);
|
||||
renderer.instance.domElement.addEventListener("mouseup", handleMouseUp);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (renderer) {
|
||||
renderer.instance.domElement.removeEventListener("click", handleClick);
|
||||
renderer.instance.domElement.removeEventListener("mousedown", handleClick);
|
||||
renderer.instance.domElement.removeEventListener("mouseup", handleMouseUp);
|
||||
}
|
||||
topScreenTexture?.dispose();
|
||||
bottomScreenTexture?.dispose();
|
||||
|
||||
Reference in New Issue
Block a user