360 rotation viewer in words

To enhance the Car360View component to support a full spherical 360-degree view (including up, down, left, right, and all directions), we need to account for both horizontal (yaw) and vertical (pitch) rotations. This requires a 2D array of images representing different angles in both axes (e.g., yaw from 0° to 360° and pitch from -90° to 90°). The component will still support dragging (mouse/touch) and arrow buttons, but now for both horizontal and vertical navigation. Below is the updated React component code.

Sample Image Names for Spherical View

const carImages = [
  // Pitch -90° (looking straight up)
  [
    '/images/car_p-90_y000.jpg',
    '/images/car_p-90_y010.jpg',
    '/images/car_p-90_y020.jpg',
    // ... up to '/images/car_p-90_y350.jpg'
  ],
  // Pitch -80°
  [
    '/images/car_p-80_y000.jpg',
    '/images/car_p-80_y010.jpg',
    '/images/car_p-80_y020.jpg',
    // ... up to '/images/car_p-80_y350.jpg'
  ],
  // ... continue for pitch -70°, -60°, ..., 0° (neutral), ..., 80°
  // Pitch 0° (horizontal)
  [
    '/images/car_p000_y000.jpg',
    '/images/car_p000_y010.jpg',
    '/images/car_p000_y020.jpg',
    // ... up to '/images/car_p000_y350.jpg'
  ],
  // ... continue up to pitch 80°
  // Pitch 90° (looking straight down)
  [
    '/images/car_p090_y000.jpg',
    '/images/car_p090_y010.jpg',
    '/images/car_p090_y020.jpg',
    // ... up to '/images/car_p090_y350.jpg'
  ],
];

To support all directions (up, down, left, right), the images prop should be a 2D array where images[pitchIndex][yawIndex] corresponds to an image at a specific pitch (vertical angle) and yaw (horizontal angle). Assuming 19 pitch angles (from -90° to 90°, every 10°) and 36 yaw angles (0° to 350°, every 10°), here’s a sample structure:

import React, { useState, useRef } from 'react';

const Car360View = ({ images }) => {
  // images is a 2D array: images[pitchIndex][yawIndex]
  const [yawIndex, setYawIndex] = useState(0); // Horizontal angle (left-right)
  const [pitchIndex, setPitchIndex] = useState(Math.floor(images.length / 2)); // Vertical angle (up-down), start at neutral pitch
  const [dragging, setDragging] = useState(false);
  const [startX, setStartX] = useState(0);
  const [startY, setStartY] = useState(0);
  const [startYawIndex, setStartYawIndex] = useState(0);
  const [startPitchIndex, setStartPitchIndex] = useState(0);
  const containerRef = useRef(null);

  const pitchCount = images.length; // Number of vertical angles
  const yawCount = images[0]?.length || 1; // Number of horizontal angles per pitch

  // Handle arrow clicks
  const handleLeft = () => {
    setYawIndex((prev) => (prev - 1 + yawCount) % yawCount);
  };

  const handleRight = () => {
    setYawIndex((prev) => (prev + 1) % yawCount);
  };

  const handleUp = () => {
    setPitchIndex((prev) => Math.max(0, prev - 1)); // Prevent going above top
  };

  const handleDown = () => {
    setPitchIndex((prev) => Math.min(pitchCount - 1, prev + 1)); // Prevent going below bottom
  };

  // Mouse down handler
  const handleMouseDown = (e) => {
    e.preventDefault();
    setDragging(true);
    setStartX(e.pageX);
    setStartY(e.pageY);
    setStartYawIndex(yawIndex);
    setStartPitchIndex(pitchIndex);
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  };

  // Mouse move handler
  const handleMouseMove = (e) => {
    if (!dragging) return;
    e.preventDefault();
    const deltaX = e.pageX - startX;
    const deltaY = e.pageY - startY;
    const width = containerRef.current?.offsetWidth || 1;
    const height = containerRef.current?.offsetHeight || 1;

    // Update yaw (horizontal)
    const yawOffset = (deltaX / width) * yawCount;
    const newYawIndex = Math.round(startYawIndex + yawOffset) % yawCount;
    setYawIndex((newYawIndex + yawCount) % yawCount);

    // Update pitch (vertical)
    const pitchOffset = (deltaY / height) * pitchCount;
    const newPitchIndex = Math.round(startPitchIndex - pitchOffset); // Negative because dragging down increases pitch
    setPitchIndex(Math.max(0, Math.min(pitchCount - 1, newPitchIndex)));
  };

  // Mouse up handler
  const handleMouseUp = () => {
    setDragging(false);
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
  };

  // Touch start handler
  const handleTouchStart = (e) => {
    setDragging(true);
    setStartX(e.touches[0].pageX);
    setStartY(e.touches[0].pageY);
    setStartYawIndex(yawIndex);
    setStartPitchIndex(pitchIndex);
    document.addEventListener('touchmove', handleTouchMove, { passive: false });
    document.addEventListener('touchend', handleTouchEnd);
  };

  // Touch move handler
  const handleTouchMove = (e) => {
    if (!dragging || e.touches.length !== 1) return;
    e.preventDefault();
    const deltaX = e.touches[0].pageX - startX;
    const deltaY = e.touches[0].pageY - startY;
    const width = containerRef.current?.offsetWidth || 1;
    const height = containerRef.current?.offsetHeight || 1;

    // Update yaw (horizontal)
    const yawOffset = (deltaX / width) * yawCount;
    const newYawIndex = Math.round(startYawIndex + yawOffset) % yawCount;
    setYawIndex((newYawIndex + yawCount) % yawCount);

    // Update pitch (vertical)
    const pitchOffset = (deltaY / height) * pitchCount;
    const newPitchIndex = Math.round(startPitchIndex - pitchOffset);
    setPitchIndex(Math.max(0, Math.min(pitchCount - 1, newPitchIndex)));
  };

  // Touch end handler
  const handleTouchEnd = () => {
    setDragging(false);
    document.removeEventListener('touchmove', handleTouchMove);
    document.removeEventListener('touchend', handleTouchEnd);
  };

  return (
    <div
      ref={containerRef}
      style={{
        position: 'relative',
        width: '100%',
        maxWidth: '600px',
        margin: '0 auto',
        userSelect: 'none',
      }}
    >
      <img
        src={images[pitchIndex][yawIndex]}
        alt="360 view of car"
        style={{ width: '100%', display: 'block' }}
        draggable={false}
        onMouseDown={handleMouseDown}
        onTouchStart={handleTouchStart}
      />
      <button
        onClick={handleLeft}
        style={{
          position: 'absolute',
          left: '10px',
          top: '50%',
          transform: 'translateY(-50%)',
          background: 'rgba(0,0,0,0.5)',
          color: 'white',
          border: 'none',
          padding: '10px',
          cursor: 'pointer',
          fontSize: '20px',
        }}
      >
        ←
      </button>
      <button
        onClick={handleRight}
        style={{
          position: 'absolute',
          right: '10px',
          top: '50%',
          transform: 'translateY(-50%)',
          background: 'rgba(0,0,0,0.5)',
          color: 'white',
          border: 'none',
          padding: '10px',
          cursor: 'pointer',
          fontSize: '20px',
        }}
      >
        →
      </button>
      <button
        onClick={handleUp}
        style={{
          position: 'absolute',
          top: '10px',
          left: '50%',
          transform: 'translateX(-50%)',
          background: 'rgba(0,0,0,0.5)',
          color: 'white',
          border: 'none',
          padding: '10px',
          cursor: 'pointer',
          fontSize: '20px',
        }}
      >
        ↑
      </button>
      <button
        onClick={handleDown}
        style={{
          position: 'absolute',
          bottom: '10px',
          left: '50%',
          transform: 'translateX(-50%)',
          background: 'rgba(0,0,0,0.5)',
          color: 'white',
          border: 'none',
          padding: '10px',
          cursor: 'pointer',
          fontSize: '20px',
        }}
      >
        ↓
      </button>
    </div>
  );
};

export default Car360View;
Tags: No tags

Add a Comment

Your email address will not be published. Required fields are marked *