gidaban79's avatar

Reactjs SVG as line - Convert to 3d

Hello guys, hope you are okay.

I am looking for some hit, advice about package maybe?

What i have to achive is make a floor plan, draw walls, then convert this 2d plan in 3d view. I heard about react fiber which can be really helpful with it, However how i could allow to draw walls first in 2d view?

0 likes
3 replies
LaryAI's avatar
Level 58

Hello,

To achieve the functionality you're looking for, you can break down the task into two main parts: drawing the 2D floor plan and then converting it into a 3D view.

For the 2D drawing part, you can use SVG within React to create a floor plan. You can either draw the SVG elements directly or use a library like react-sketch or react-draw to provide a drawing interface.

Once you have the 2D representation, you can use react-three-fiber, which is a React renderer for three.js, to create the 3D view. You'll need to convert the 2D coordinates into 3D objects.

Here's a basic outline of how you might approach this:

  1. Create a 2D drawing interface using SVG.
  2. Convert the SVG elements (lines for walls) into a data format that can be used to create 3D objects.
  3. Use react-three-fiber to create the 3D scene, camera, and lights.
  4. Map the 2D wall data to 3D objects (e.g., extrude the lines into walls with some thickness).

Here's a simple example of how you might set up the 2D drawing part:

import React from 'react';

function FloorPlan() {
  // Function to handle the drawing logic
  // This is where you would implement the logic to let users draw walls
  const handleDrawing = (event) => {
    // Drawing logic goes here
  };

  return (
    <svg width="500" height="500" onMouseDown={handleDrawing}>
      {/* Here you would render the lines representing walls */}
      {/* Example wall */}
      <line x1="10" y1="10" x2="100" y2="10" stroke="black" />
    </svg>
  );
}

export default FloorPlan;

For the 3D part with react-three-fiber, you would set up a scene like this:

import React from 'react';
import { Canvas } from '@react-three/fiber';
import { Box } from '@react-three/drei';

function ThreeDFloorPlan() {
  // This is where you would convert the 2D wall data into 3D walls
  // For simplicity, here's a static box representing a wall
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
      <pointLight position={[-10, -10, -10]} />
      <Box position={[0, 0, 0]}>
        <meshStandardMaterial attach="material" color="white" />
      </Box>
    </Canvas>
  );
}

export default ThreeDFloorPlan;

In a real application, you would dynamically create Box components based on the SVG wall data, possibly using an extrusion technique to give the walls thickness.

Remember that this is a high-level overview and each step can be quite complex, depending on the level of detail and interactivity you require. You may need to delve into the documentation of react-three-fiber and three.js for more advanced 3D features.

Nicholas185's avatar

To create a floor plan in React, use a library like Konva.js for 2D drawing. Implement features for users to draw walls, storing the relevant data (coordinates, lengths) in the React state. For the 3D conversion, consider React Three Fiber, integrating React with Three.js. Utilize stored 2D wall data to create corresponding 3D objects in the scene, handling user interaction such as zooming and rotating. The process involves capturing user input for 2D drawing, storing the data, and converting it into a 3D view using React libraries tailored for each dimension. ballsportsgear.com

gidaban79's avatar
import React, { useState } from 'react';
import { Stage, Layer, Line, Circle, Text, Label, Tag } from 'react-konva';
import { v4 as uuidv4 } from 'uuid';

const Floor = () => {
  const [lines, setLines] = useState([]);
  const [tempLine, setTempLine] = useState(null);
  const [selectedLineIndex, setSelectedLineIndex] = useState(null);
  const gridSize = 20;

  const snapToGrid = (x, y) => {
    return [Math.round(x / gridSize) * gridSize, Math.round(y / gridSize) * gridSize];
  };

  const handleOnClick = (e) => {
    const stage = e.target.getStage();
    const mousePos = stage.getPointerPosition();
    const snappedPos = snapToGrid(mousePos.x, mousePos.y);

    if (!tempLine) {
      const lastLine = lines[lines.length - 1];
      const startPoint = lastLine ? [lastLine[2], lastLine[3]] : snappedPos;
      setTempLine([...startPoint, ...snappedPos]);
    } else {
      setLines([...lines, tempLine]);
      setTempLine(null);
    }
  };

  const handleOnMouseMove = (e) => {
    if (tempLine) {
      const stage = e.target.getStage();
      const mousePos = stage.getPointerPosition();
      const snappedPos = snapToGrid(mousePos.x, mousePos.y);
      setTempLine([tempLine[0], tempLine[1], ...snappedPos]);
    }
  };

  const drawGrid = () => {
    const grid = [];
    for (let i = 0; i < window.innerWidth; i += gridSize) {
      grid.push(<Line points={[i, 0, i, window.innerHeight]} stroke="#ddd" strokeWidth={0.5} />);
    }

    for (let i = 0; i < window.innerHeight; i += gridSize) {
      grid.push(<Line points={[0, i, window.innerWidth, i]} stroke="#ddd" strokeWidth={0.5} />);
    }

    return grid;
  };

  const calculateLength = (line) => {
    const dx = line[2] - line[0];
    const dy = line[3] - line[1];
    return (Math.sqrt(dx * dx + dy * dy)).toFixed(0);
  };

  const drawDimensionLine = (line) => {
    const midPoint = [(line[0] + line[2]) / 2, (line[1] + line[3]) / 2];
    const length = calculateLength(line);
    const angle = Math.atan2(line[3] - line[1], line[2] - line[0]);
    const offset = 20;
    const endPoints = [
      midPoint[0] - offset * Math.sin(angle),
      midPoint[1] + offset * Math.cos(angle),
      midPoint[0] + offset * Math.sin(angle),
      midPoint[1] - offset * Math.cos(angle),
    ];
    return (
      <>
        <Line points={endPoints} stroke="black" strokeWidth={1} key={uuidv4()}/>
        <Text x={midPoint[0]} y={midPoint[1]} text={`${length} mm`} fontSize={15} rotation={-angle * (180 / Math.PI)} />
      </>
    );
  };
  const deleteSelectedLine = () => {
    if (selectedLineIndex !== null) {
      const newLines = lines.filter((_, index) => index !== selectedLineIndex);
      setLines(newLines);
      setSelectedLineIndex(null);
    }
  };
  const calculateAngle = (line1, line2) => {
    const v1 = { x: line1[2] - line1[0], y: line1[3] - line1[1] };
    const v2 = { x: line2[2] - line2[0], y: line2[3] - line2[1] };

    const dotProduct = v1.x * v2.x + v1.y * v2.y;
    const magnitude1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
    const magnitude2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);

    const angleInRadians = Math.acos(dotProduct / (magnitude1 * magnitude2));
    const angleInDegrees = angleInRadians * (180 / Math.PI);

    return angleInDegrees.toFixed(2);
  };

  const drawAngle = (line1, line2) => {
    const angle = calculateAngle(line1, line2);
    const position = { x: line1[2], y: line1[3] };

    return (
        <Text
            x={position.x}
            y={position.y}
            text={`${angle}°`}
            fontSize={15}
        />
    );
  };

  return (
      <Stage width={window.innerWidth} height={window.innerHeight} onClick={handleOnClick} onMouseMove={handleOnMouseMove}>
        <Layer>
          {drawGrid()}
          {lines.map((line, index) => (
              <>
                <Line
                    key={uuidv4()}
                    points={line}
                    stroke="black"
                    strokeWidth={5}
                    onClick={() => setSelectedLineIndex(index)}
                />
                <Circle key={`uuid-${index}`} x={line[2]} y={line[3]} radius={5} fill="black" />
                {drawDimensionLine(line, `${index}-dimension`)}
              </>
          ))}
          {lines.slice(0, -1).map((line, index) => (
              drawAngle(line, lines[index + 1])
          ))}
          {tempLine && (
              <React.Fragment key="tempLine">
                <Line points={tempLine} stroke="grey" strokeWidth={5} key={uuidv4()} />
                {drawDimensionLine(tempLine, 'tempLine-dimension')}
                {lines.length > 0 && drawAngle(lines[lines.length - 1], tempLine)}
              </React.Fragment>
          )}
          <Label x={20} y={20} onClick={deleteSelectedLine}>
            <Tag fill="white" pointerDirection="down" pointerWidth={10} pointerHeight={10} lineJoin="round" shadowColor="black" />
            <Text text="Delete Selected Line" padding={5} fill="black" />
          </Label>
        </Layer>
      </Stage>
  );
};

export default Floor;

i did already some work on it. For first time i think is not so bad. I can`t make temporary line to be visible after click and draw line. I have to click again to show temporary line.

Please or to participate in this conversation.